diff --git a/README.md b/README.md index ff806c8..618c45c 100644 --- a/README.md +++ b/README.md @@ -210,6 +210,32 @@ mstch::config::escape = [](const std::string& str) -> std::string { }; ``` + +### Handling missing keys + +By default, mstch does not consider missing keys a problem but simply ignores +them. If the client expects all keys referenced by a template to exist, mstch +can be configured to throw an exception if a key is not found: + +```c++ +#include +#include +#include + +int main() { + mstch::config::throw_on_missing_key = true; + const std::string view{"{{a-missing-key}}"}; + mstch::map context{{"an-existing-key", std::string{"a value"}}}; + try { + std::cout << mstch::render(view, context) << std::endl; + } catch (const mstch::key_not_found& e) { + std::cout << "Failed to render the template: " << e.what() << std::endl; + } + return 0; +} +``` + + ## Requirements - A C++ compiler with decent C++11 support. Currently tested with: diff --git a/include/mstch/mstch.hpp b/include/mstch/mstch.hpp index 58d3330..82b1ffa 100644 --- a/include/mstch/mstch.hpp +++ b/include/mstch/mstch.hpp @@ -5,13 +5,31 @@ #include #include #include +#include #include namespace mstch { +class key_not_found : public std::exception { + public: + key_not_found(const std::string& name): + description(std::string{"The key '"} + name + "' is missing") + { + } + + const char* what() const noexcept override + { + return description.c_str(); + } + +private: + const std::string description; +}; + struct config { static std::function escape; + static bool throw_on_missing_key; }; namespace internal { diff --git a/src/mstch.cpp b/src/mstch.cpp index 4d84e97..d74dbd8 100644 --- a/src/mstch.cpp +++ b/src/mstch.cpp @@ -6,6 +6,7 @@ using namespace mstch; std::function mstch::config::escape; +bool mstch::config::throw_on_missing_key = false; std::string mstch::render( const std::string& tmplt, diff --git a/src/render_context.cpp b/src/render_context.cpp index 90b2ffc..5e74d41 100644 --- a/src/render_context.cpp +++ b/src/render_context.cpp @@ -43,6 +43,8 @@ const mstch::node& render_context::find_node( for (auto& node: current_nodes) if (visit(has_token(token), *node)) return visit(get_token(token, *node), *node); + if (mstch::config::throw_on_missing_key) + throw key_not_found{token}; return null_node; } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 061bc2e..8c5698b 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -54,6 +54,9 @@ add_executable(mstch_test test_main.cpp) target_link_libraries(mstch_test mstch) add_dependencies(mstch_test test_data_hpp specs_data_hpp) +list(APPEND tests "missing_key_is_ignored") +list(APPEND tests "missing_key_is_reported") + foreach(test ${tests}) add_test(NAME ${test} COMMAND mstch_test ${test}) endforeach(test) diff --git a/test/test_main.cpp b/test/test_main.cpp index a52fe3c..88a4758 100644 --- a/test/test_main.cpp +++ b/test/test_main.cpp @@ -153,3 +153,17 @@ SPECS_TEST(inverted) SPECS_TEST(partials) SPECS_TEST(sections) SPECS_TEST(lambdas) + +TEST_CASE("missing_key_is_ignored") { + mstch::config::throw_on_missing_key = false; + const std::string view{"{{a-non-existing-key}}"}; + mstch::map context{{"an-existing-key", std::string{"a value"}}}; + REQUIRE_NOTHROW(mstch::render(view, context)); +} + +TEST_CASE("missing_key_is_reported") { + mstch::config::throw_on_missing_key = true; + const std::string view{"{{a-missing-key}}"}; + mstch::map context{{"an-existing-key", std::string{"a value"}}}; + REQUIRE_THROWS_AS(mstch::render(view, context), mstch::key_not_found); +}