From 7e32e8bcfeb70535ce1691681d2342e533f643c4 Mon Sep 17 00:00:00 2001 From: pantor Date: Sat, 24 Feb 2018 14:50:38 +0100 Subject: [PATCH] use central throw function --- src/inja.hpp | 63 ++++++++++++++++++++------------- test/src/unit-renderer.cpp | 38 ++++++++++---------- test/src/unit-string-helper.cpp | 7 ++-- 3 files changed, 60 insertions(+), 48 deletions(-) diff --git a/src/inja.hpp b/src/inja.hpp index d77a082..8ba0b36 100644 --- a/src/inja.hpp +++ b/src/inja.hpp @@ -54,21 +54,14 @@ namespace inja { using json = nlohmann::json; - /*! -@brief dot notation to json pointer notiation +@brief throw an error with a given message */ -inline std::string dot_to_json_pointer_notation(std::string dot) { - std::string result = dot; - while (result.find(".") != std::string::npos) { - result.replace(result.find("."), 1, "/"); - } - result.insert(0, "/"); - return result; +inline void inja_throw(std::string type, std::string message) { + throw std::runtime_error("[inja.exception." + type + "] " + message); } - /*! @brief inja regex class, saves string pattern in addition to std::regex */ @@ -83,7 +76,6 @@ public: }; - class Match: public std::match_results { size_t offset_ = 0; unsigned int group_offset_ = 0; @@ -106,7 +98,6 @@ public: }; - template class MatchType: public Match { T type_; @@ -122,7 +113,6 @@ public: }; - class MatchClosed { public: Match open_match, close_match; @@ -150,7 +140,6 @@ inline Match search(const std::string& input, Regex regex, size_t position) { } - template inline MatchType search(const std::string& input, std::map& regexes, size_t position) { // Map to vectors @@ -190,7 +179,7 @@ inline MatchType search(const std::string& input, std::map& regexes } } - throw std::runtime_error("Error while searching in input: " + input); + inja_throw("regex_search_error", "error while searching in input: " + input); return search_match; } @@ -231,7 +220,6 @@ inline MatchType match(const std::string& input, std::map regexe } - enum class ElementNotation { Dot, Pointer @@ -360,7 +348,6 @@ struct Parsed { }; - class Template { public: const Parsed::Element parsed_template; @@ -379,12 +366,23 @@ public: if (var.empty()) { return false; } else if (var.is_number()) { return (var != 0); } else if (var.is_string()) { return not var.empty(); } - return var.get(); + try { + return var.get(); + } catch (json::type_error& e) { + inja_throw("json_error", e.what()); + throw; + } } template T eval_expression(const Parsed::ElementExpression& element, const json &data) { - return eval_function(element, data).get(); + const json var = eval_function(element, data); + try { + return var.get(); + } catch (json::type_error& e) { + inja_throw("json_error", e.what()); + throw; + } } json eval_function(const Parsed::ElementExpression& element, const json& data) { @@ -481,7 +479,11 @@ public: return eval_expression(element.args[0], data) != eval_expression(element.args[1], data); } case Parsed::Function::ReadJson: { - return data.at(json::json_pointer(element.command)); + try { + return data.at(json::json_pointer(element.command)); + } catch (std::exception&) { + inja_throw("render_error", "variable '" + element.command + "' not found"); + } } case Parsed::Function::Result: { return element.result; @@ -499,7 +501,7 @@ public: } } - throw std::runtime_error("Unknown function in renderer."); + inja_throw("render_error", "unknown function in renderer: " + element.command); return json(); } @@ -580,6 +582,9 @@ class Parser { public: ElementNotation element_notation = ElementNotation::Pointer; + /*! + @brief create a corresponding regex for a function name with a number of arguments seperated by , + */ static Regex function_regex(std::string name, int number_arguments) { std::string pattern = name; if (number_arguments > 0) { @@ -593,6 +598,18 @@ public: return Regex{"\\s*" + pattern + "\\s*"}; } + /*! + @brief dot notation to json pointer notiation + */ + static std::string dot_to_json_pointer_notation(std::string dot) { + std::string result = dot; + while (result.find(".") != std::string::npos) { + result.replace(result.find("."), 1, "/"); + } + result.insert(0, "/"); + return result; + } + std::map regex_map_delimiters = { {Parsed::Delimiter::Statement, Regex{"\\{\\%\\s*(.+?)\\s*\\%\\}"}}, {Parsed::Delimiter::LineStatement, Regex{"(?:^|\\n)##\\s*(.+)\\s*"}}, @@ -749,7 +766,7 @@ public: break; } default: { - throw std::runtime_error("Unknown loop statement."); + inja_throw("parser_error", "unknown loop statement"); } } break; @@ -856,7 +873,6 @@ public: }; - /*! @brief Environment class */ @@ -964,7 +980,6 @@ public: }; - /*! @brief render with default settings */ diff --git a/test/src/unit-renderer.cpp b/test/src/unit-renderer.cpp index 68a69d5..d793f10 100644 --- a/test/src/unit-renderer.cpp +++ b/test/src/unit-renderer.cpp @@ -39,7 +39,7 @@ TEST_CASE("types") { CHECK( env.render("Hello {{ brother/name }}!", data) == "Hello Chris!" ); CHECK( env.render("Hello {{ brother/daughter0/name }}!", data) == "Hello Maria!" ); - // CHECK_THROWS_WITH( env.render("{{unknown}}", data), "[json.exception.out_of_range.403] key 'unknown' not found" ); + CHECK_THROWS_WITH( env.render("{{unknown}}", data), "[inja.exception.render_error] variable '/unknown' not found" ); } SECTION("comments") { @@ -53,8 +53,8 @@ TEST_CASE("types") { CHECK( env.render("Hello {% for name in names %}{{ index }}: {{ name }}, {% endfor %}!", data) == "Hello 0: Jeff, 1: Seb, !" ); CHECK( env.render("{% for type, name in relatives %}{{ type }}: {{ name }}, {% endfor %}", data) == "brother: Chris, mother: Maria, sister: Jenny, " ); - CHECK_THROWS_WITH( env.render("{% for name ins names %}a{% endfor %}", data), "Unknown loop statement." ); - // CHECK_THROWS_WITH( env.render("{% for name in relatives %}{{ name }}{% endfor %}", data), "[json.exception.type_error.302] type must be array, but is object" ); + CHECK_THROWS_WITH( env.render("{% for name ins names %}a{% endfor %}", data), "[inja.exception.parser_error] unknown loop statement" ); + CHECK_THROWS_WITH( env.render("{% for name in relatives %}{{ name }}{% endfor %}", data), "[inja.exception.json_error] [json.exception.type_error.302] type must be array, but is object" ); } SECTION("conditionals") { @@ -86,78 +86,78 @@ TEST_CASE("functions") { CHECK( env.render("{{ upper( name ) }}", data) == "PETER" ); CHECK( env.render("{{ upper(city) }}", data) == "NEW YORK" ); CHECK( env.render("{{ upper(upper(name)) }}", data) == "PETER" ); - // CHECK_THROWS_WITH( env.render("{{ upper(5) }}", data), "[json.exception.type_error.302] type must be string, but is number" ); - // CHECK_THROWS_WITH( env.render("{{ upper(true) }}", data), "[json.exception.type_error.302] type must be string, but is boolean" ); + CHECK_THROWS_WITH( env.render("{{ upper(5) }}", data), "[inja.exception.json_error] [json.exception.type_error.302] type must be string, but is number" ); + CHECK_THROWS_WITH( env.render("{{ upper(true) }}", data), "[inja.exception.json_error] [json.exception.type_error.302] type must be string, but is boolean" ); } SECTION("lower") { CHECK( env.render("{{ lower(name) }}", data) == "peter" ); CHECK( env.render("{{ lower(city) }}", data) == "new york" ); - // CHECK_THROWS_WITH( env.render("{{ lower(5.45) }}", data), "[json.exception.type_error.302] type must be string, but is number" ); + CHECK_THROWS_WITH( env.render("{{ lower(5.45) }}", data), "[inja.exception.json_error] [json.exception.type_error.302] type must be string, but is number" ); } SECTION("range") { CHECK( env.render("{{ range(2) }}", data) == "[0,1]" ); CHECK( env.render("{{ range(4) }}", data) == "[0,1,2,3]" ); - // CHECK_THROWS_WITH( env.render("{{ range(name) }}", data), "[json.exception.type_error.302] type must be number, but is string" ); + CHECK_THROWS_WITH( env.render("{{ range(name) }}", data), "[inja.exception.json_error] [json.exception.type_error.302] type must be number, but is string" ); } SECTION("length") { CHECK( env.render("{{ length(names) }}", data) == "4" ); - // CHECK_THROWS_WITH( env.render("{{ length(5) }}", data), "[json.exception.type_error.302] type must be array, but is number" ); + CHECK_THROWS_WITH( env.render("{{ length(5) }}", data), "[inja.exception.json_error] [json.exception.type_error.302] type must be array, but is number" ); } SECTION("sort") { CHECK( env.render("{{ sort([3, 2, 1]) }}", data) == "[1,2,3]" ); CHECK( env.render("{{ sort([\"bob\", \"charlie\", \"alice\"]) }}", data) == "[\"alice\",\"bob\",\"charlie\"]" ); - // CHECK_THROWS_WITH( env.render("{{ sort(5) }}", data), "[json.exception.type_error.302] type must be array, but is number" ); + CHECK_THROWS_WITH( env.render("{{ sort(5) }}", data), "[inja.exception.json_error] [json.exception.type_error.302] type must be array, but is number" ); } SECTION("first") { CHECK( env.render("{{ first(names) }}", data) == "Jeff" ); - // CHECK_THROWS_WITH( env.render("{{ length(5) }}", data), "[json.exception.type_error.302] type must be array, but is number" ); + CHECK_THROWS_WITH( env.render("{{ first(5) }}", data), "[inja.exception.json_error] [json.exception.type_error.302] type must be array, but is number" ); } SECTION("last") { CHECK( env.render("{{ last(names) }}", data) == "Tom" ); - // CHECK_THROWS_WITH( env.render("{{ length(5) }}", data), "[json.exception.type_error.302] type must be array, but is number" ); + CHECK_THROWS_WITH( env.render("{{ last(5) }}", data), "[inja.exception.json_error] [json.exception.type_error.302] type must be array, but is number" ); } SECTION("round") { CHECK( env.render("{{ round(4, 0) }}", data) == "4.0" ); CHECK( env.render("{{ round(temperature, 2) }}", data) == "25.68" ); - // CHECK_THROWS_WITH( env.render("{{ round(name, 2) }}", data), "[json.exception.type_error.302] type must be number, but is string" ); + CHECK_THROWS_WITH( env.render("{{ round(name, 2) }}", data), "[inja.exception.json_error] [json.exception.type_error.302] type must be number, but is string" ); } SECTION("divisibleBy") { CHECK( env.render("{{ divisibleBy(50, 5) }}", data) == "true" ); CHECK( env.render("{{ divisibleBy(12, 3) }}", data) == "true" ); CHECK( env.render("{{ divisibleBy(11, 3) }}", data) == "false" ); - // CHECK_THROWS_WITH( env.render("{{ divisibleBy(name, 2) }}", data), "[json.exception.type_error.302] type must be number, but is string" ); + CHECK_THROWS_WITH( env.render("{{ divisibleBy(name, 2) }}", data), "[inja.exception.json_error] [json.exception.type_error.302] type must be number, but is string" ); } SECTION("odd") { CHECK( env.render("{{ odd(11) }}", data) == "true" ); CHECK( env.render("{{ odd(12) }}", data) == "false" ); - // CHECK_THROWS_WITH( env.render("{{ odd(name) }}", data), "[json.exception.type_error.302] type must be number, but is string" ); + CHECK_THROWS_WITH( env.render("{{ odd(name) }}", data), "[inja.exception.json_error] [json.exception.type_error.302] type must be number, but is string" ); } SECTION("even") { CHECK( env.render("{{ even(11) }}", data) == "false" ); CHECK( env.render("{{ even(12) }}", data) == "true" ); - // CHECK_THROWS_WITH( env.render("{{ even(name) }}", data), "[json.exception.type_error.302] type must be number, but is string" ); + CHECK_THROWS_WITH( env.render("{{ even(name) }}", data), "[inja.exception.json_error] [json.exception.type_error.302] type must be number, but is string" ); } SECTION("max") { CHECK( env.render("{{ max([1, 2, 3]) }}", data) == "3" ); CHECK( env.render("{{ max([-5.2, 100.2, 2.4]) }}", data) == "100.2" ); - // CHECK_THROWS_WITH( env.render("{{ even(name) }}", data), "[json.exception.type_error.302] type must be number, but is string" ); + CHECK_THROWS_WITH( env.render("{{ max(name) }}", data), "[inja.exception.json_error] [json.exception.type_error.302] type must be array, but is string" ); } SECTION("min") { CHECK( env.render("{{ min([1, 2, 3]) }}", data) == "1" ); CHECK( env.render("{{ min([-5.2, 100.2, 2.4]) }}", data) == "-5.2" ); - // CHECK_THROWS_WITH( env.render("{{ even(name) }}", data), "[json.exception.type_error.302] type must be number, but is string" ); + CHECK_THROWS_WITH( env.render("{{ min(name) }}", data), "[inja.exception.json_error] [json.exception.type_error.302] type must be array, but is string" ); } SECTION("default") { @@ -166,7 +166,7 @@ TEST_CASE("functions") { CHECK( env.render("{{ default(name, \"nobody\") }}", data) == "Peter" ); CHECK( env.render("{{ default(surname, \"nobody\") }}", data) == "nobody" ); - // CHECK_THROWS_WITH( env.render("{{ default(surname, lastname) }}", data), "[json.exception.out_of_range.403] key 'lastname' not found" ); + CHECK_THROWS_WITH( env.render("{{ default(surname, lastname) }}", data), "[inja.exception.render_error] variable '/lastname' not found" ); } } @@ -268,7 +268,7 @@ TEST_CASE("other-syntax") { CHECK( env.render("Hello {{ brother.name }}!", data) == "Hello Chris!" ); CHECK( env.render("Hello {{ brother.daughter0.name }}!", data) == "Hello Maria!" ); - // CHECK_THROWS_WITH( env.render("{{unknown}}", data), "[json.exception.out_of_range.403] key 'unknown' not found" ); + CHECK_THROWS_WITH( env.render("{{unknown.name}}", data), "[inja.exception.render_error] variable '/unknown/name' not found" ); } SECTION("other expression syntax") { diff --git a/test/src/unit-string-helper.cpp b/test/src/unit-string-helper.cpp index 8361f67..f8b36a3 100644 --- a/test/src/unit-string-helper.cpp +++ b/test/src/unit-string-helper.cpp @@ -5,8 +5,8 @@ TEST_CASE("dot to pointer") { - CHECK( inja::dot_to_json_pointer_notation("person.names.surname") == "/person/names/surname" ); - CHECK( inja::dot_to_json_pointer_notation("guests.2") == "/guests/2" ); + CHECK( inja::Parser::dot_to_json_pointer_notation("person.names.surname") == "/person/names/surname" ); + CHECK( inja::Parser::dot_to_json_pointer_notation("guests.2") == "/guests/2" ); } TEST_CASE("basic-search") { @@ -155,9 +155,6 @@ TEST_CASE("match-functions") { CHECK( inja::match(" upper(lower()) ", map_regex).type() == inja::Parsed::Function::Upper ); CHECK( inja::match("lower(upper(test))", map_regex).type() == inja::Parsed::Function::Lower ); CHECK( inja::match("round(2, 3)", map_regex).type() == inja::Parsed::Function::Round ); - - // CHECK_THROWS_WITH( inja::match("test(var)", map_regex), "Could not match input: test(var)" ); - // CHECK_THROWS_WITH( inja::match("round(var)", map_regex), "Could not match input: round(var)" ); } TEST_CASE("create-regex-functions") {