mirror of
https://github.com/pantor/inja.git
synced 2026-03-04 00:06:26 +00:00
use central throw function
This commit is contained in:
63
src/inja.hpp
63
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<std::string::const_iterator> {
|
||||
size_t offset_ = 0;
|
||||
unsigned int group_offset_ = 0;
|
||||
@@ -106,7 +98,6 @@ public:
|
||||
};
|
||||
|
||||
|
||||
|
||||
template<typename T>
|
||||
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<typename T>
|
||||
inline MatchType<T> search(const std::string& input, std::map<T, Regex>& regexes, size_t position) {
|
||||
// Map to vectors
|
||||
@@ -190,7 +179,7 @@ inline MatchType<T> search(const std::string& input, std::map<T, Regex>& 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<T> match(const std::string& input, std::map<T, Regex, S> 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<bool>();
|
||||
try {
|
||||
return var.get<bool>();
|
||||
} catch (json::type_error& e) {
|
||||
inja_throw("json_error", e.what());
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T = json>
|
||||
T eval_expression(const Parsed::ElementExpression& element, const json &data) {
|
||||
return eval_function(element, data).get<T>();
|
||||
const json var = eval_function(element, data);
|
||||
try {
|
||||
return var.get<T>();
|
||||
} 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<Parsed::Delimiter, Regex> 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
|
||||
*/
|
||||
|
||||
@@ -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") {
|
||||
|
||||
@@ -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") {
|
||||
|
||||
Reference in New Issue
Block a user