From 081aa2dbe05dc10cbad87a9d6c3b62ffef30e7a3 Mon Sep 17 00:00:00 2001 From: Hannu Lounento Date: Tue, 9 Apr 2019 13:52:16 +0300 Subject: [PATCH 1/2] Add a test case for not throwing if a key is missing The new test case verifies the current behavior that no exceptions are thrown if a key mentioned in a template is not found. The Catch2 TEST_CASE macro was used directly instead of the MSTCH_TEST or other macros because this test case does not verify output of the mstch::render with a given input, what MSTCH_TEST and friends are for. Signed-off-by: Hannu Lounento --- test/CMakeLists.txt | 2 ++ test/test_main.cpp | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 061bc2e..1fd18a3 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -54,6 +54,8 @@ 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") + 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..a8e1597 100644 --- a/test/test_main.cpp +++ b/test/test_main.cpp @@ -153,3 +153,9 @@ SPECS_TEST(inverted) SPECS_TEST(partials) SPECS_TEST(sections) SPECS_TEST(lambdas) + +TEST_CASE("missing_key_is_ignored") { + const std::string view{"{{a-non-existing-key}}"}; + mstch::map context{{"an-existing-key", std::string{"a value"}}}; + REQUIRE_NOTHROW(mstch::render(view, context)); +} From cd073c8877265313ab429566cc008efe6c1023ce Mon Sep 17 00:00:00 2001 From: Hannu Lounento Date: Tue, 9 Apr 2019 13:52:16 +0300 Subject: [PATCH 2/2] Throw on missing keys Throwing an exception enables clients to detect failures to render template completely due to missing keys. This is useful in cases where clients expect templates and the data structure to always match and treat missing keys as failures. Define a custom exception to enable clients to identify the specific type of the error. Signed-off-by: Hannu Lounento --- README.md | 26 ++++++++++++++++++++++++++ include/mstch/mstch.hpp | 18 ++++++++++++++++++ src/mstch.cpp | 1 + src/render_context.cpp | 2 ++ test/CMakeLists.txt | 1 + test/test_main.cpp | 8 ++++++++ 6 files changed, 56 insertions(+) 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 1fd18a3..8c5698b 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -55,6 +55,7 @@ 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}) diff --git a/test/test_main.cpp b/test/test_main.cpp index a8e1597..88a4758 100644 --- a/test/test_main.cpp +++ b/test/test_main.cpp @@ -155,7 +155,15 @@ 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); +}