From 9d4faed431fbc2e882bf009003bc647255e95050 Mon Sep 17 00:00:00 2001 From: Tle Ekkul Date: Wed, 22 Jan 2020 13:13:40 +0700 Subject: [PATCH 01/11] Move matcher from fostgres to fost-web --- Cpp/fost-urlhandler/CMakeLists.txt | 2 + Cpp/fost-urlhandler/matcher.cpp | 67 +++++++++++++++++++++++++ Cpp/fost-urlhandler/matcher.tests.cpp | 72 +++++++++++++++++++++++++++ Cpp/include/fost/matcher.hpp | 28 +++++++++++ 4 files changed, 169 insertions(+) create mode 100644 Cpp/fost-urlhandler/matcher.cpp create mode 100644 Cpp/fost-urlhandler/matcher.tests.cpp create mode 100644 Cpp/include/fost/matcher.hpp diff --git a/Cpp/fost-urlhandler/CMakeLists.txt b/Cpp/fost-urlhandler/CMakeLists.txt index c243c71..d4b6ba9 100644 --- a/Cpp/fost-urlhandler/CMakeLists.txt +++ b/Cpp/fost-urlhandler/CMakeLists.txt @@ -3,6 +3,7 @@ add_library(fost-urlhandler control.exception.cpp control.status-condition.cpp fost-urlhandler.cpp + matcher.cpp middleware.logging.cpp middleware.request.cpp middleware.template.cpp @@ -33,6 +34,7 @@ if(TARGET check) add_library(fost-urlhandler-smoke STATIC EXCLUDE_FROM_ALL control.exception.tests.cpp control.status-condition-tests.cpp + matcher.tests.cpp responses.tests.cpp responses.301-tests.cpp responses.302-tests.cpp diff --git a/Cpp/fost-urlhandler/matcher.cpp b/Cpp/fost-urlhandler/matcher.cpp new file mode 100644 index 0000000..66cffd2 --- /dev/null +++ b/Cpp/fost-urlhandler/matcher.cpp @@ -0,0 +1,67 @@ +/** + Copyright 2016-2020 Red Anchor Trading Co. Ltd. + + Distributed under the Boost Software License, Version 1.0. + See + */ + + +#include +#include + +#include +#include + + +namespace { + fostlib::nullable + check(const fostlib::json &conf, const fostlib::split_type &parts) { + if (not conf.has_key("path")) return fostlib::null; + const fostlib::json &o = conf["path"]; + if (o.size() == parts.size()) { + fostlib::match m{conf}; + for (std::size_t index{0}; index < o.size(); ++index) { + auto s = fostlib::coerce(o[index]); + if (s.starts_with("/")) { + if (parts[index] + != (static_cast(s)).substr(1)) { + return fostlib::null; + } + } else { + const auto n = fostlib::coerce(o[index]); + if (n > 0) { + // We have a numeric match so this path part needs to + // be exposed as argument number n. This entails + // placing it at position (n-1) in the arguments array + if (n > m.arguments.size()) { m.arguments.resize(n); } + m.arguments[n - 1] = fostlib::coerce( + fostlib::url::filepath_string(parts[index])); + } else { + throw fostlib::exceptions::not_implemented( + __FUNCTION__, + "Path arguments numbers cannot be zero"); + } + } + } + return m; + } + fostlib::log::debug(fostlib::c_fost_web_urlhandler)("", "Path size mismatch")( + "o", "size", o.size())("parts", "size", parts.size()); + return fostlib::null; + } +} + + +fostlib::nullable fostlib::matcher( + const fostlib::json &configuration, const fostlib::string &path) { + auto parts = fostlib::split(path, "/"); + if (configuration.isobject()) { + return check(configuration, parts); + } else if (configuration.isarray()) { + for (auto conf : configuration) { + auto m = check(conf, parts); + if (m) return m; + } + } + return fostlib::null; +} diff --git a/Cpp/fost-urlhandler/matcher.tests.cpp b/Cpp/fost-urlhandler/matcher.tests.cpp new file mode 100644 index 0000000..cf8aba0 --- /dev/null +++ b/Cpp/fost-urlhandler/matcher.tests.cpp @@ -0,0 +1,72 @@ +/** + Copyright 2016-2019 Red Anchor Trading Co. Ltd. + + Distributed under the Boost Software License, Version 1.0. + See + */ + + +#include +#include +#include + + +FSL_TEST_SUITE(matcher); + + +FSL_TEST_FUNCTION(empty) { + FSL_CHECK(not fostlib::matcher(fostlib::json(), "")); +} + + +FSL_TEST_FUNCTION(args_mismatch_1) { + fostlib::json config; + fostlib::push_back(config, "path", 1); + auto m = fostlib::matcher(config, ""); + FSL_CHECK(not m); +} + + +FSL_TEST_FUNCTION(args_match_1) { + fostlib::json config; + fostlib::push_back(config, "path", 1); + auto m = fostlib::matcher(config, "first"); + FSL_CHECK(m); + FSL_CHECK_EQ(m.value().arguments.size(), 1u); + FSL_CHECK_EQ(m.value().arguments[0], "first"); +} + + +FSL_TEST_FUNCTION(args_match_2) { + fostlib::json config; + fostlib::push_back(config, "path", 2); + fostlib::push_back(config, "path", 1); + auto m = fostlib::matcher(config, "second/first/"); + FSL_CHECK(m); + FSL_CHECK_EQ(m.value().arguments.size(), 2u); + FSL_CHECK_EQ(m.value().arguments[0], "first"); + FSL_CHECK_EQ(m.value().arguments[1], "second"); +} + + +FSL_TEST_FUNCTION(args_with_fixed_strings_match_1) { + fostlib::json config; + fostlib::push_back(config, "path", 1); + fostlib::push_back(config, "path", "/foo"); + fostlib::push_back(config, "path", 2); + auto m = fostlib::matcher(config, "first/foo/second/"); + FSL_CHECK(m); + FSL_CHECK_EQ(m.value().arguments.size(), 2u); + FSL_CHECK_EQ(m.value().arguments[0], "first"); + FSL_CHECK_EQ(m.value().arguments[1], "second"); +} + + +FSL_TEST_FUNCTION(args_with_fixed_strings_mismatch_1) { + fostlib::json config; + fostlib::push_back(config, "path", 1); + fostlib::push_back(config, "path", "/foo"); + fostlib::push_back(config, "path", 2); + auto m = fostlib::matcher(config, "first/bar/second/"); + FSL_CHECK(not m); +} diff --git a/Cpp/include/fost/matcher.hpp b/Cpp/include/fost/matcher.hpp new file mode 100644 index 0000000..0124690 --- /dev/null +++ b/Cpp/include/fost/matcher.hpp @@ -0,0 +1,28 @@ +/** + Copyright 2020 Red Anchor Trading Co. Ltd. + + Distributed under the Boost Software License, Version 1.0. + See + */ + + +#pragma once + + +#include + + +namespace fostlib { + + + struct match { + fostlib::json configuration; + std::vector arguments; + }; + + + fostlib::nullable + matcher(const fostlib::json &config, const fostlib::string &path); + + +} From a2237f3404a9f53a06938e5422e86978ef5c7205 Mon Sep 17 00:00:00 2001 From: Tle Ekkul Date: Wed, 22 Jan 2020 19:06:44 +0700 Subject: [PATCH 02/11] Add fost.view.match view --- Cpp/fost-urlhandler/CMakeLists.txt | 2 + Cpp/fost-urlhandler/matcher.cpp | 7 +-- Cpp/fost-urlhandler/view.matcher.cpp | 63 ++++++++++++++++++++++ Cpp/fost-urlhandler/view.matcher.tests.cpp | 50 +++++++++++++++++ Cpp/include/fost/urlhandler.hpp | 4 ++ 5 files changed, 123 insertions(+), 3 deletions(-) create mode 100644 Cpp/fost-urlhandler/view.matcher.cpp create mode 100644 Cpp/fost-urlhandler/view.matcher.tests.cpp diff --git a/Cpp/fost-urlhandler/CMakeLists.txt b/Cpp/fost-urlhandler/CMakeLists.txt index d4b6ba9..a3183f4 100644 --- a/Cpp/fost-urlhandler/CMakeLists.txt +++ b/Cpp/fost-urlhandler/CMakeLists.txt @@ -23,6 +23,7 @@ add_library(fost-urlhandler schema.validate.cpp test.throw.cpp view.cpp + view.matcher.cpp ) target_include_directories(fost-urlhandler PUBLIC ../include) target_link_libraries(fost-urlhandler fost-inet f5-json-schema) @@ -45,6 +46,7 @@ if(TARGET check) schema.validate-tests.cpp test.throw.tests.cpp view-tests.cpp + view.matcher.tests.cpp view.request-id.tests.cpp ) target_link_libraries(fost-urlhandler-smoke fost-urlhandler) diff --git a/Cpp/fost-urlhandler/matcher.cpp b/Cpp/fost-urlhandler/matcher.cpp index 66cffd2..655b133 100644 --- a/Cpp/fost-urlhandler/matcher.cpp +++ b/Cpp/fost-urlhandler/matcher.cpp @@ -45,15 +45,16 @@ namespace { } return m; } - fostlib::log::debug(fostlib::c_fost_web_urlhandler)("", "Path size mismatch")( - "o", "size", o.size())("parts", "size", parts.size()); + fostlib::log::debug(fostlib::c_fost_web_urlhandler)( + "", "Path size mismatch")("o", "size", o.size())( + "parts", "size", parts.size()); return fostlib::null; } } fostlib::nullable fostlib::matcher( - const fostlib::json &configuration, const fostlib::string &path) { + fostlib::json const &configuration, fostlib::string const &path) { auto parts = fostlib::split(path, "/"); if (configuration.isobject()) { return check(configuration, parts); diff --git a/Cpp/fost-urlhandler/view.matcher.cpp b/Cpp/fost-urlhandler/view.matcher.cpp new file mode 100644 index 0000000..9a7470a --- /dev/null +++ b/Cpp/fost-urlhandler/view.matcher.cpp @@ -0,0 +1,63 @@ +/** + Copyright 2020 Red Anchor Trading Co. Ltd. + + Distributed under the Boost Software License, Version 1.0. + See + */ + + +#include "fost-urlhandler.hpp" +#include +#include +#include "fost/matcher.hpp" + + +namespace { + + const class view_matcher : public fostlib::urlhandler::view { + public: + view_matcher() : view("fost.view.match") {} + + std::pair, int> operator()( + const fostlib::json &configuration, + const fostlib::string &path, + fostlib::http::server::request &req, + const fostlib::host &host) const { + /* + Execute the view that match with the defined path, + If matched, then set the headers __1, __2, ... so the underlying + views can use it. if not matched, the fallback view should be + executed. + + Example: { + "view": "fost.view.match", + "configuration": { + "match": [{ + "path": ["/test", 1], + "precondition": [], + "execute": "" + },{ // fallback + "path": [], + "execute": "" + }] + } + } + */ + auto &match_config = configuration["match"]; + fostlib::log::debug(fostlib::c_fost_web_urlhandler)("path", path); + + for (auto m : match_config) { + auto matched = fostlib::matcher(m, path); + if (matched) { return execute(m["execute"], path, req, host); } + } + throw fostlib::exceptions::not_implemented( + __PRETTY_FUNCTION__, "fost.view.match not implemented"); + } + } c_view_matcher; + + +} + + +const fostlib::urlhandler::view &fostlib::urlhandler::view_matcher = + c_view_matcher; diff --git a/Cpp/fost-urlhandler/view.matcher.tests.cpp b/Cpp/fost-urlhandler/view.matcher.tests.cpp new file mode 100644 index 0000000..d967c59 --- /dev/null +++ b/Cpp/fost-urlhandler/view.matcher.tests.cpp @@ -0,0 +1,50 @@ +/** + Copyright 2020 Red Anchor Trading Co. Ltd. + + Distributed under the Boost Software License, Version 1.0. + See + */ + +#include +#include +#include +#include + +FSL_TEST_SUITE(view_matcher); + +namespace { + void check_fost_req_id( + const fostlib::mime &req, const fostlib::ascii_string &message) { + FSL_CHECK(req.headers().exists("Fost-Request-ID")); + } +} + +FSL_TEST_FUNCTION(view_matcher_call_correct_view) { + /* + { + "view": "fost.view.match", + "configuration": { + "match": [{ + "path": ["/test", 1], + "execute": "fost.response.200" + },{ // fallback + "path": [], + "execute": "fost.response.404" + }] + } + } + */ + fostlib::http::server::request req("GET", "/test/fred"); + fostlib::json config{}; + fostlib::insert(config, "view", "fost.view.match"); + + fostlib::json path1{}; + fostlib::push_back(path1, "path", "/test"); + fostlib::push_back(path1, "path", 1); + fostlib::insert(path1, "execute", "fost.response.200"); + fostlib::push_back(config, "configuration", "match", path1); + fostlib::log::debug(fostlib::c_fost_web_urlhandler)("", config); + auto [response, status] = fostlib::urlhandler::view::execute( + config, "/test/fred", req, fostlib::host{}); + FSL_CHECK_EQ(status, 200); +} diff --git a/Cpp/include/fost/urlhandler.hpp b/Cpp/include/fost/urlhandler.hpp index f4a7963..51f49ea 100644 --- a/Cpp/include/fost/urlhandler.hpp +++ b/Cpp/include/fost/urlhandler.hpp @@ -125,6 +125,10 @@ namespace fostlib { FOST_URLHANDLER_DECLSPEC extern const view &schema_validation; + /// match with path + FOST_URLHANDLER_DECLSPEC + extern const view &view_matcher; + /// Generic response handler FOST_URLHANDLER_DECLSPEC extern const view &response_generic; From 3bcdcd7e53e106c515c398a4c87b93402499ef2b Mon Sep 17 00:00:00 2001 From: Tle Ekkul Date: Thu, 23 Jan 2020 18:36:45 +0700 Subject: [PATCH 03/11] Test fost.view.match set headers --- Cpp/fost-urlhandler/view.matcher.cpp | 12 ++++++++++- Cpp/fost-urlhandler/view.matcher.tests.cpp | 24 ++++++++++++++++------ 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/Cpp/fost-urlhandler/view.matcher.cpp b/Cpp/fost-urlhandler/view.matcher.cpp index 9a7470a..bcf2fd7 100644 --- a/Cpp/fost-urlhandler/view.matcher.cpp +++ b/Cpp/fost-urlhandler/view.matcher.cpp @@ -48,7 +48,17 @@ namespace { for (auto m : match_config) { auto matched = fostlib::matcher(m, path); - if (matched) { return execute(m["execute"], path, req, host); } + if (matched) { + for (std::size_t i = 0; + i != matched.value().arguments.size(); i++) { + req.headers().set( + f5::u8string{"__"} + + fostlib::coerce( + i + 1), + matched.value().arguments[i]); + } + return execute(m["execute"], path, req, host); + } } throw fostlib::exceptions::not_implemented( __PRETTY_FUNCTION__, "fost.view.match not implemented"); diff --git a/Cpp/fost-urlhandler/view.matcher.tests.cpp b/Cpp/fost-urlhandler/view.matcher.tests.cpp index d967c59..688e260 100644 --- a/Cpp/fost-urlhandler/view.matcher.tests.cpp +++ b/Cpp/fost-urlhandler/view.matcher.tests.cpp @@ -13,10 +13,23 @@ FSL_TEST_SUITE(view_matcher); namespace { - void check_fost_req_id( - const fostlib::mime &req, const fostlib::ascii_string &message) { - FSL_CHECK(req.headers().exists("Fost-Request-ID")); - } + const class check_fost_view_match_set_header : + public fostlib::urlhandler::view { + public: + check_fost_view_match_set_header() + : view("fost.view.test.match_set_header") {} + + std::pair, int> operator()( + const fostlib::json &config, + const fostlib::string &path, + fostlib::http::server::request &req, + const fostlib::host &host) const { + FSL_CHECK_EQ(req.headers().exists("__1"), true); + boost::shared_ptr response{ + new fostlib::text_body(L"OK")}; + return std::make_pair(response, 200); + } + } c_check_fost_view_match_set_header; } FSL_TEST_FUNCTION(view_matcher_call_correct_view) { @@ -41,9 +54,8 @@ FSL_TEST_FUNCTION(view_matcher_call_correct_view) { fostlib::json path1{}; fostlib::push_back(path1, "path", "/test"); fostlib::push_back(path1, "path", 1); - fostlib::insert(path1, "execute", "fost.response.200"); + fostlib::insert(path1, "execute", "fost.view.test.match_set_header"); fostlib::push_back(config, "configuration", "match", path1); - fostlib::log::debug(fostlib::c_fost_web_urlhandler)("", config); auto [response, status] = fostlib::urlhandler::view::execute( config, "/test/fred", req, fostlib::host{}); FSL_CHECK_EQ(status, 200); From 145f9e0a853e75aaa6916f3d4c8e0fc5fd24710f Mon Sep 17 00:00:00 2001 From: Tle Ekkul Date: Thu, 23 Jan 2020 18:41:16 +0700 Subject: [PATCH 04/11] test the value in header --- Cpp/fost-urlhandler/view.matcher.tests.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Cpp/fost-urlhandler/view.matcher.tests.cpp b/Cpp/fost-urlhandler/view.matcher.tests.cpp index 688e260..513b12f 100644 --- a/Cpp/fost-urlhandler/view.matcher.tests.cpp +++ b/Cpp/fost-urlhandler/view.matcher.tests.cpp @@ -25,6 +25,7 @@ namespace { fostlib::http::server::request &req, const fostlib::host &host) const { FSL_CHECK_EQ(req.headers().exists("__1"), true); + FSL_CHECK_EQ(req.headers()["__1"].value(), "fred"); boost::shared_ptr response{ new fostlib::text_body(L"OK")}; return std::make_pair(response, 200); From e48d22e882e56b742b0f7b3d13e52cf4f4f1ff87 Mon Sep 17 00:00:00 2001 From: Tle Ekkul Date: Fri, 24 Jan 2020 00:36:48 +0700 Subject: [PATCH 05/11] Have an echo view for testing, can execute fallback view --- Cpp/fost-urlhandler/view.matcher.cpp | 28 +++-- Cpp/fost-urlhandler/view.matcher.tests.cpp | 134 ++++++++++++++++++--- 2 files changed, 133 insertions(+), 29 deletions(-) diff --git a/Cpp/fost-urlhandler/view.matcher.cpp b/Cpp/fost-urlhandler/view.matcher.cpp index bcf2fd7..7463ac8 100644 --- a/Cpp/fost-urlhandler/view.matcher.cpp +++ b/Cpp/fost-urlhandler/view.matcher.cpp @@ -30,19 +30,22 @@ namespace { executed. Example: { - "view": "fost.view.match", - "configuration": { - "match": [{ - "path": ["/test", 1], - "precondition": [], - "execute": "" - },{ // fallback - "path": [], - "execute": "" - }] - } + "view": "fost.view.match", + "configuration": { + "match": [{ + "path": ["/test", 1], + "execute": "" + }], + "": "" // fallback + } } */ + if (!configuration.has_key("")) { + throw fostlib::exceptions::not_implemented( + __PRETTY_FUNCTION__, + "fost.view.match required fallback view in " + "configuration \"\""); + } auto &match_config = configuration["match"]; fostlib::log::debug(fostlib::c_fost_web_urlhandler)("path", path); @@ -60,8 +63,7 @@ namespace { return execute(m["execute"], path, req, host); } } - throw fostlib::exceptions::not_implemented( - __PRETTY_FUNCTION__, "fost.view.match not implemented"); + return execute(configuration[""], path, req, host); } } c_view_matcher; diff --git a/Cpp/fost-urlhandler/view.matcher.tests.cpp b/Cpp/fost-urlhandler/view.matcher.tests.cpp index 513b12f..cf60ea8 100644 --- a/Cpp/fost-urlhandler/view.matcher.tests.cpp +++ b/Cpp/fost-urlhandler/view.matcher.tests.cpp @@ -13,38 +13,33 @@ FSL_TEST_SUITE(view_matcher); namespace { - const class check_fost_view_match_set_header : - public fostlib::urlhandler::view { + const class test_echo_req_view : public fostlib::urlhandler::view { public: - check_fost_view_match_set_header() - : view("fost.view.test.match_set_header") {} + test_echo_req_view() : view("fost.view.test.echo") {} std::pair, int> operator()( const fostlib::json &config, const fostlib::string &path, fostlib::http::server::request &req, const fostlib::host &host) const { - FSL_CHECK_EQ(req.headers().exists("__1"), true); - FSL_CHECK_EQ(req.headers()["__1"].value(), "fred"); + auto const req_body = fostlib::coerce( + fostlib::coerce(req.data()->data())); boost::shared_ptr response{ - new fostlib::text_body(L"OK")}; + new fostlib::text_body(req_body, req.headers())}; return std::make_pair(response, 200); } - } c_check_fost_view_match_set_header; + } c_test_echo_req; } -FSL_TEST_FUNCTION(view_matcher_call_correct_view) { +FSL_TEST_FUNCTION(throw_exception_when_no_fallback_view_defined) { /* { "view": "fost.view.match", "configuration": { - "match": [{ + "match": [{ "path": ["/test", 1], - "execute": "fost.response.200" - },{ // fallback - "path": [], - "execute": "fost.response.404" - }] + "execute": "fost.view.test.echo" + }] } } */ @@ -55,9 +50,116 @@ FSL_TEST_FUNCTION(view_matcher_call_correct_view) { fostlib::json path1{}; fostlib::push_back(path1, "path", "/test"); fostlib::push_back(path1, "path", 1); - fostlib::insert(path1, "execute", "fost.view.test.match_set_header"); + fostlib::insert(path1, "execute", "fost.view.test.echo"); fostlib::push_back(config, "configuration", "match", path1); + FSL_CHECK_EXCEPTION( + fostlib::urlhandler::view::execute( + config, "/test/fred", req, fostlib::host{}), + fostlib::exceptions::not_implemented &); +} + +FSL_TEST_FUNCTION(view_matcher_can_match_one_view) { + /* + { + "view": "fost.view.match", + "configuration": { + "match": [{ + "path": ["/test", 1], + "execute": "fost.view.test.echo" + }], + "": "fost.response.404" + } + } + */ + fostlib::http::server::request req("GET", "/test/fred"); + fostlib::json config{}; + fostlib::insert(config, "view", "fost.view.match"); + + fostlib::json path1{}; + fostlib::push_back(path1, "path", "/test"); + fostlib::push_back(path1, "path", 1); + fostlib::insert(path1, "execute", "fost.view.test.echo"); + fostlib::push_back(config, "configuration", "match", path1); + fostlib::insert(config, "configuration", "", "fost.response.404"); auto [response, status] = fostlib::urlhandler::view::execute( config, "/test/fred", req, fostlib::host{}); FSL_CHECK_EQ(status, 200); + + FSL_CHECK_EQ(response->headers().exists("__1"), true); + FSL_CHECK_EQ(response->headers()["__1"].value(), "fred"); + + auto [response2, status2] = fostlib::urlhandler::view::execute( + config, "/test/barney", req, fostlib::host{}); + FSL_CHECK_EQ(status2, 200); + + FSL_CHECK_EQ(response2->headers().exists("__1"), true); + FSL_CHECK_EQ(response2->headers()["__1"].value(), "barney"); } + +FSL_TEST_FUNCTION(view_matcher_can_match_two_args) { + /* + { + "view": "fost.view.match", + "configuration": { + "match": [{ + "path": ["/shop", 1, "/employee", 2], + "execute": "fost.view.test.echo" + }], + "": "fost.response.404" + + } + } + */ + fostlib::http::server::request req("GET", "/shop/coffee/employee/ploy"); + fostlib::json config{}; + fostlib::insert(config, "view", "fost.view.match"); + + fostlib::json path1{}; + fostlib::push_back(path1, "path", "/shop"); + fostlib::push_back(path1, "path", 1); + fostlib::push_back(path1, "path", "/employee"); + fostlib::push_back(path1, "path", 2); + fostlib::insert(path1, "execute", "fost.view.test.echo"); + fostlib::push_back(config, "configuration", "match", path1); + fostlib::insert(config, "configuration", "", "fost.response.404"); + + auto [response, status] = fostlib::urlhandler::view::execute( + config, "/shop/coffee/employee/ploy", req, fostlib::host{}); + FSL_CHECK_EQ(status, 200); + + FSL_CHECK_EQ(response->headers().exists("__1"), true); + FSL_CHECK_EQ(response->headers()["__1"].value(), "coffee"); + FSL_CHECK_EQ(response->headers().exists("__2"), true); + FSL_CHECK_EQ(response->headers()["__2"].value(), "ploy"); +} + + +FSL_TEST_FUNCTION(view_matcher_support_fallback) { + /* + { + "view": "fost.view.match", + "configuration": { + "match": [{ + "path": ["/shop", 1, "/employee", 2], + "execute": "fost.view.test.echo" + }] + "": "fost.response.404" + } + } + */ + fostlib::http::server::request req("GET", "/some/random/url"); + fostlib::json config{}; + fostlib::insert(config, "view", "fost.view.match"); + + fostlib::json path1{}; + fostlib::push_back(path1, "path", "/shop"); + fostlib::push_back(path1, "path", 1); + fostlib::push_back(path1, "path", "/employee"); + fostlib::push_back(path1, "path", 2); + fostlib::insert(path1, "execute", "fost.view.test.echo"); + fostlib::push_back(config, "configuration", "match", path1); + fostlib::insert(config, "configuration", "", "fost.response.404"); + auto [response, status] = fostlib::urlhandler::view::execute( + config, "/some/random/url", req, fostlib::host{}); + FSL_CHECK_EQ(status, 404); +} \ No newline at end of file From 7c29551f4a06fbc1b093aa76de1dfb04113bee2e Mon Sep 17 00:00:00 2001 From: Tle Ekkul Date: Fri, 24 Jan 2020 00:44:58 +0700 Subject: [PATCH 06/11] restructure tests --- Cpp/fost-urlhandler/view.matcher.cpp | 2 + Cpp/fost-urlhandler/view.matcher.tests.cpp | 131 +++++++-------------- 2 files changed, 47 insertions(+), 86 deletions(-) diff --git a/Cpp/fost-urlhandler/view.matcher.cpp b/Cpp/fost-urlhandler/view.matcher.cpp index 7463ac8..48eac2d 100644 --- a/Cpp/fost-urlhandler/view.matcher.cpp +++ b/Cpp/fost-urlhandler/view.matcher.cpp @@ -39,6 +39,8 @@ namespace { "": "" // fallback } } + + * This view doesn't consume any path */ if (!configuration.has_key("")) { throw fostlib::exceptions::not_implemented( diff --git a/Cpp/fost-urlhandler/view.matcher.tests.cpp b/Cpp/fost-urlhandler/view.matcher.tests.cpp index cf60ea8..06c949a 100644 --- a/Cpp/fost-urlhandler/view.matcher.tests.cpp +++ b/Cpp/fost-urlhandler/view.matcher.tests.cpp @@ -29,6 +29,43 @@ namespace { return std::make_pair(response, 200); } } c_test_echo_req; + + fostlib::json configuration() { + /* + { + "view": "fost.view.match", + "configuration": { + "match": [{ + "path": ["/test", 1], + "execute": "fost.view.test.echo" + },{ + "path": ["/shop", 1, "/employee", 2], + "execute": "fost.view.test.echo" + }], + "": "fost.response.404" + } + } + */ + fostlib::json config{}; + fostlib::insert(config, "view", "fost.view.match"); + + fostlib::json path1{}; + fostlib::push_back(path1, "path", "/test"); + fostlib::push_back(path1, "path", 1); + fostlib::insert(path1, "execute", "fost.view.test.echo"); + fostlib::push_back(config, "configuration", "match", path1); + + fostlib::json path2{}; + fostlib::push_back(path2, "path", "/shop"); + fostlib::push_back(path2, "path", 1); + fostlib::push_back(path2, "path", "/employee"); + fostlib::push_back(path2, "path", 2); + fostlib::insert(path2, "execute", "fost.view.test.echo"); + fostlib::push_back(config, "configuration", "match", path2); + + fostlib::insert(config, "configuration", "", "fost.response.404"); + return config; + } } FSL_TEST_FUNCTION(throw_exception_when_no_fallback_view_defined) { @@ -43,14 +80,10 @@ FSL_TEST_FUNCTION(throw_exception_when_no_fallback_view_defined) { } } */ - fostlib::http::server::request req("GET", "/test/fred"); + fostlib::http::server::request req("GET", "/"); fostlib::json config{}; fostlib::insert(config, "view", "fost.view.match"); - fostlib::json path1{}; - fostlib::push_back(path1, "path", "/test"); - fostlib::push_back(path1, "path", 1); - fostlib::insert(path1, "execute", "fost.view.test.echo"); fostlib::push_back(config, "configuration", "match", path1); FSL_CHECK_EXCEPTION( fostlib::urlhandler::view::execute( @@ -58,71 +91,20 @@ FSL_TEST_FUNCTION(throw_exception_when_no_fallback_view_defined) { fostlib::exceptions::not_implemented &); } -FSL_TEST_FUNCTION(view_matcher_can_match_one_view) { - /* - { - "view": "fost.view.match", - "configuration": { - "match": [{ - "path": ["/test", 1], - "execute": "fost.view.test.echo" - }], - "": "fost.response.404" - } - } - */ - fostlib::http::server::request req("GET", "/test/fred"); - fostlib::json config{}; - fostlib::insert(config, "view", "fost.view.match"); - - fostlib::json path1{}; - fostlib::push_back(path1, "path", "/test"); - fostlib::push_back(path1, "path", 1); - fostlib::insert(path1, "execute", "fost.view.test.echo"); - fostlib::push_back(config, "configuration", "match", path1); - fostlib::insert(config, "configuration", "", "fost.response.404"); +FSL_TEST_FUNCTION(view_matcher_can_match_one_path) { + fostlib::http::server::request req("GET", "/"); + auto const config = configuration(); auto [response, status] = fostlib::urlhandler::view::execute( config, "/test/fred", req, fostlib::host{}); FSL_CHECK_EQ(status, 200); FSL_CHECK_EQ(response->headers().exists("__1"), true); FSL_CHECK_EQ(response->headers()["__1"].value(), "fred"); - - auto [response2, status2] = fostlib::urlhandler::view::execute( - config, "/test/barney", req, fostlib::host{}); - FSL_CHECK_EQ(status2, 200); - - FSL_CHECK_EQ(response2->headers().exists("__1"), true); - FSL_CHECK_EQ(response2->headers()["__1"].value(), "barney"); } -FSL_TEST_FUNCTION(view_matcher_can_match_two_args) { - /* - { - "view": "fost.view.match", - "configuration": { - "match": [{ - "path": ["/shop", 1, "/employee", 2], - "execute": "fost.view.test.echo" - }], - "": "fost.response.404" - - } - } - */ - fostlib::http::server::request req("GET", "/shop/coffee/employee/ploy"); - fostlib::json config{}; - fostlib::insert(config, "view", "fost.view.match"); - - fostlib::json path1{}; - fostlib::push_back(path1, "path", "/shop"); - fostlib::push_back(path1, "path", 1); - fostlib::push_back(path1, "path", "/employee"); - fostlib::push_back(path1, "path", 2); - fostlib::insert(path1, "execute", "fost.view.test.echo"); - fostlib::push_back(config, "configuration", "match", path1); - fostlib::insert(config, "configuration", "", "fost.response.404"); - +FSL_TEST_FUNCTION(view_matcher_can_match_two_path) { + auto const config = configuration(); + fostlib::http::server::request req("GET", "/"); auto [response, status] = fostlib::urlhandler::view::execute( config, "/shop/coffee/employee/ploy", req, fostlib::host{}); FSL_CHECK_EQ(status, 200); @@ -133,32 +115,9 @@ FSL_TEST_FUNCTION(view_matcher_can_match_two_args) { FSL_CHECK_EQ(response->headers()["__2"].value(), "ploy"); } - FSL_TEST_FUNCTION(view_matcher_support_fallback) { - /* - { - "view": "fost.view.match", - "configuration": { - "match": [{ - "path": ["/shop", 1, "/employee", 2], - "execute": "fost.view.test.echo" - }] - "": "fost.response.404" - } - } - */ + auto const config = configuration(); fostlib::http::server::request req("GET", "/some/random/url"); - fostlib::json config{}; - fostlib::insert(config, "view", "fost.view.match"); - - fostlib::json path1{}; - fostlib::push_back(path1, "path", "/shop"); - fostlib::push_back(path1, "path", 1); - fostlib::push_back(path1, "path", "/employee"); - fostlib::push_back(path1, "path", 2); - fostlib::insert(path1, "execute", "fost.view.test.echo"); - fostlib::push_back(config, "configuration", "match", path1); - fostlib::insert(config, "configuration", "", "fost.response.404"); auto [response, status] = fostlib::urlhandler::view::execute( config, "/some/random/url", req, fostlib::host{}); FSL_CHECK_EQ(status, 404); From ebdea63f31768e68bd38cbbd9b5d7f95b5b79df3 Mon Sep 17 00:00:00 2001 From: Tle Ekkul Date: Mon, 27 Jan 2020 01:17:34 +0700 Subject: [PATCH 07/11] Add test matcher can match with the longest path --- Cpp/fost-urlhandler/view.matcher.cpp | 29 +++++++++--------- Cpp/fost-urlhandler/view.matcher.tests.cpp | 34 +++++++++++++++++++--- 2 files changed, 46 insertions(+), 17 deletions(-) diff --git a/Cpp/fost-urlhandler/view.matcher.cpp b/Cpp/fost-urlhandler/view.matcher.cpp index 48eac2d..75c07ed 100644 --- a/Cpp/fost-urlhandler/view.matcher.cpp +++ b/Cpp/fost-urlhandler/view.matcher.cpp @@ -49,21 +49,24 @@ namespace { "configuration \"\""); } auto &match_config = configuration["match"]; - fostlib::log::debug(fostlib::c_fost_web_urlhandler)("path", path); + fostlib::log::debug(fostlib::c_fost_web_urlhandler)("path", path)( + "configuration", match_config); - for (auto m : match_config) { - auto matched = fostlib::matcher(m, path); - if (matched) { - for (std::size_t i = 0; - i != matched.value().arguments.size(); i++) { - req.headers().set( - f5::u8string{"__"} - + fostlib::coerce( - i + 1), - matched.value().arguments[i]); - } - return execute(m["execute"], path, req, host); + auto matched = fostlib::matcher(match_config, path); + if (matched) { + for (std::size_t i = 0; i != matched.value().arguments.size(); + i++) { + req.headers().set( + f5::u8string{"__"} + + fostlib::coerce(i + 1), + matched.value().arguments[i]); } + fostlib::log::debug(fostlib::c_fost_web_urlhandler)( + "matched", + path)("configuration", matched.value().configuration); + return execute( + matched.value().configuration["execute"], path, req, + host); } return execute(configuration[""], path, req, host); } diff --git a/Cpp/fost-urlhandler/view.matcher.tests.cpp b/Cpp/fost-urlhandler/view.matcher.tests.cpp index 06c949a..2a123ad 100644 --- a/Cpp/fost-urlhandler/view.matcher.tests.cpp +++ b/Cpp/fost-urlhandler/view.matcher.tests.cpp @@ -41,6 +41,9 @@ namespace { },{ "path": ["/shop", 1, "/employee", 2], "execute": "fost.view.test.echo" + },{ + "path": ["/shop", 1, "/employee", 2, "/address", 3], + "execute": "fost.view.test.echo" }], "": "fost.response.404" } @@ -60,9 +63,19 @@ namespace { fostlib::push_back(path2, "path", 1); fostlib::push_back(path2, "path", "/employee"); fostlib::push_back(path2, "path", 2); + fostlib::push_back(path2, "path", "/address"); + fostlib::push_back(path2, "path", 3); fostlib::insert(path2, "execute", "fost.view.test.echo"); fostlib::push_back(config, "configuration", "match", path2); + fostlib::json path3{}; + fostlib::push_back(path3, "path", "/shop"); + fostlib::push_back(path3, "path", 1); + fostlib::push_back(path3, "path", "/employee"); + fostlib::push_back(path3, "path", 2); + fostlib::insert(path3, "execute", "fost.view.test.echo"); + fostlib::push_back(config, "configuration", "match", path3); + fostlib::insert(config, "configuration", "", "fost.response.404"); return config; } @@ -73,10 +86,7 @@ FSL_TEST_FUNCTION(throw_exception_when_no_fallback_view_defined) { { "view": "fost.view.match", "configuration": { - "match": [{ - "path": ["/test", 1], - "execute": "fost.view.test.echo" - }] + "match": [{}] } } */ @@ -115,6 +125,22 @@ FSL_TEST_FUNCTION(view_matcher_can_match_two_path) { FSL_CHECK_EQ(response->headers()["__2"].value(), "ploy"); } +FSL_TEST_FUNCTION(view_matcher_match_the_longest_path) { + auto const config = configuration(); + fostlib::http::server::request req("GET", "/"); + auto [response, status] = fostlib::urlhandler::view::execute( + config, "/shop/coffee/employee/ploy/address/home", req, + fostlib::host{}); + FSL_CHECK_EQ(status, 200); + + FSL_CHECK_EQ(response->headers().exists("__1"), true); + FSL_CHECK_EQ(response->headers()["__1"].value(), "coffee"); + FSL_CHECK_EQ(response->headers().exists("__2"), true); + FSL_CHECK_EQ(response->headers()["__2"].value(), "ploy"); + FSL_CHECK_EQ(response->headers().exists("__3"), true); + FSL_CHECK_EQ(response->headers()["__3"].value(), "home"); +} + FSL_TEST_FUNCTION(view_matcher_support_fallback) { auto const config = configuration(); fostlib::http::server::request req("GET", "/some/random/url"); From f5ad982fbb0e840e202f25dc2d9361021cc68305 Mon Sep 17 00:00:00 2001 From: Tle Ekkul Date: Mon, 27 Jan 2020 01:21:46 +0700 Subject: [PATCH 08/11] Check keys in view and try to produce a helpful error message --- Cpp/fost-urlhandler/view.matcher.cpp | 8 +++++--- Cpp/fost-urlhandler/view.matcher.tests.cpp | 22 ++++++++++++++++++++-- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/Cpp/fost-urlhandler/view.matcher.cpp b/Cpp/fost-urlhandler/view.matcher.cpp index 75c07ed..5d98aa1 100644 --- a/Cpp/fost-urlhandler/view.matcher.cpp +++ b/Cpp/fost-urlhandler/view.matcher.cpp @@ -42,10 +42,12 @@ namespace { * This view doesn't consume any path */ - if (!configuration.has_key("")) { + if (!configuration.has_key("no-match") + || !configuration.has_key("match")) { throw fostlib::exceptions::not_implemented( __PRETTY_FUNCTION__, - "fost.view.match required fallback view in " + "fost.view.match required keys [\"no-match\", " + "\"match\"] in the" "configuration \"\""); } auto &match_config = configuration["match"]; @@ -68,7 +70,7 @@ namespace { matched.value().configuration["execute"], path, req, host); } - return execute(configuration[""], path, req, host); + return execute(configuration["no-match"], path, req, host); } } c_view_matcher; diff --git a/Cpp/fost-urlhandler/view.matcher.tests.cpp b/Cpp/fost-urlhandler/view.matcher.tests.cpp index 2a123ad..85a59ce 100644 --- a/Cpp/fost-urlhandler/view.matcher.tests.cpp +++ b/Cpp/fost-urlhandler/view.matcher.tests.cpp @@ -45,7 +45,7 @@ namespace { "path": ["/shop", 1, "/employee", 2, "/address", 3], "execute": "fost.view.test.echo" }], - "": "fost.response.404" + "no-match": "fost.response.404" } } */ @@ -76,7 +76,8 @@ namespace { fostlib::insert(path3, "execute", "fost.view.test.echo"); fostlib::push_back(config, "configuration", "match", path3); - fostlib::insert(config, "configuration", "", "fost.response.404"); + fostlib::insert( + config, "configuration", "no-match", "fost.response.404"); return config; } } @@ -101,6 +102,23 @@ FSL_TEST_FUNCTION(throw_exception_when_no_fallback_view_defined) { fostlib::exceptions::not_implemented &); } +FSL_TEST_FUNCTION(throw_exception_when_no_match_view_defined) { + /* + { + "view": "fost.view.match", + "configuration": { + } + } + */ + fostlib::http::server::request req("GET", "/"); + fostlib::json config{}; + fostlib::insert(config, "view", "fost.view.match"); + FSL_CHECK_EXCEPTION( + fostlib::urlhandler::view::execute( + config, "/test/fred", req, fostlib::host{}), + fostlib::exceptions::not_implemented &); +} + FSL_TEST_FUNCTION(view_matcher_can_match_one_path) { fostlib::http::server::request req("GET", "/"); auto const config = configuration(); From 31b632fe06c6db9e62af5ae6fe9b941bf37a3d77 Mon Sep 17 00:00:00 2001 From: Tle Ekkul Date: Mon, 27 Jan 2020 11:36:44 +0700 Subject: [PATCH 09/11] update comment --- Cpp/fost-urlhandler/view.matcher.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cpp/fost-urlhandler/view.matcher.cpp b/Cpp/fost-urlhandler/view.matcher.cpp index 5d98aa1..a4f05bf 100644 --- a/Cpp/fost-urlhandler/view.matcher.cpp +++ b/Cpp/fost-urlhandler/view.matcher.cpp @@ -36,7 +36,7 @@ namespace { "path": ["/test", 1], "execute": "" }], - "": "" // fallback + "no-match": "" // fallback } } From 2e1cc53aa4aed007bb43483949c5b968c7dbd67c Mon Sep 17 00:00:00 2001 From: Tle Ekkul Date: Mon, 27 Jan 2020 13:21:03 +0700 Subject: [PATCH 10/11] Move fsigma and precondition from fostgres --- Cpp/fost-urlhandler/CMakeLists.txt | 3 + Cpp/fost-urlhandler/fsigma.cpp | 144 +++++++++++++++++++ Cpp/fost-urlhandler/precondition.cpp | 105 ++++++++++++++ Cpp/fost-urlhandler/precondition.hpp | 26 ++++ Cpp/fost-urlhandler/precondition.tests.cpp | 160 +++++++++++++++++++++ Cpp/include/fost/fsigma.hpp | 62 ++++++++ 6 files changed, 500 insertions(+) create mode 100644 Cpp/fost-urlhandler/fsigma.cpp create mode 100644 Cpp/fost-urlhandler/precondition.cpp create mode 100644 Cpp/fost-urlhandler/precondition.hpp create mode 100644 Cpp/fost-urlhandler/precondition.tests.cpp create mode 100644 Cpp/include/fost/fsigma.hpp diff --git a/Cpp/fost-urlhandler/CMakeLists.txt b/Cpp/fost-urlhandler/CMakeLists.txt index a3183f4..f21f02b 100644 --- a/Cpp/fost-urlhandler/CMakeLists.txt +++ b/Cpp/fost-urlhandler/CMakeLists.txt @@ -3,11 +3,13 @@ add_library(fost-urlhandler control.exception.cpp control.status-condition.cpp fost-urlhandler.cpp + fsigma.cpp matcher.cpp middleware.logging.cpp middleware.request.cpp middleware.template.cpp mime-types.cpp + precondition.cpp responses.cpp responses.301.cpp responses.302.cpp @@ -36,6 +38,7 @@ if(TARGET check) control.exception.tests.cpp control.status-condition-tests.cpp matcher.tests.cpp + precondition.tests.cpp responses.tests.cpp responses.301-tests.cpp responses.302-tests.cpp diff --git a/Cpp/fost-urlhandler/fsigma.cpp b/Cpp/fost-urlhandler/fsigma.cpp new file mode 100644 index 0000000..6e5cb0d --- /dev/null +++ b/Cpp/fost-urlhandler/fsigma.cpp @@ -0,0 +1,144 @@ +/** + Copyright 2016-2020 Red Anchor Trading Co. Ltd. + + Distributed under the Boost Software License, Version 1.0. + See + */ + + +#include +#include + + +/** + ## fsigma::frame + */ + + +fsigma::frame::frame(frame *f) : parent(f) {} + + +fostlib::json fsigma::frame::argument( + const fostlib::string &name, + fostlib::json::const_iterator &pos, + fostlib::json::const_iterator end) { + if (pos == end) { + throw fostlib::exceptions::not_implemented( + __PRETTY_FUNCTION__, "Argument not found", name); + } else { + auto result = *pos; + ++pos; + return result; + } +} + + +fostlib::string fsigma::frame::resolve_string(const fostlib::json &code) { + if (code.isatom()) { + return fostlib::coerce(code); + } else if (code.isarray()) { + frame stack(this); + return resolve_string(call(stack, code)); + } else { + throw fostlib::exceptions::not_implemented( + __PRETTY_FUNCTION__, "Can't resolve to a string", code); + } +} + + +int64_t fsigma::frame::resolve_int(const fostlib::json &code) { + if (code.isatom()) { + return fostlib::coerce(code); + } else { + throw fostlib::exceptions::not_implemented( + __PRETTY_FUNCTION__, "Can't resolve to an int", code); + } +} + + +fostlib::json fsigma::frame::resolve(const fostlib::json &code) { + /// S-expressions are always a JSON array. Everything else is a literal + /// and doesn't need to be resolved. + if (code.isarray()) { + frame stack(this); + return call(stack, code); + } else { + return code; + } +} + + +/// This is dynamic rather than lexical scoping, which is.... not great +fostlib::json fsigma::frame::lookup(const fostlib::string &name) const { + auto fnp = symbols.find(name); + if (fnp == symbols.end()) { + if (parent) { + return parent->lookup(name); + } else { + throw fostlib::exceptions::not_implemented( + __PRETTY_FUNCTION__, "Sumbol not found", name); + } + } else { + return fnp->second; + } +} + + +/// This is dynamic rather than lexical scoping, which is.... not great +fsigma::frame::builtin + fsigma::frame::lookup_function(const fostlib::string &name) const { + auto fnp = native.find(name); + if (fnp == native.end()) { + if (parent) { + return parent->lookup_function(name); + } else { + throw fostlib::exceptions::not_implemented( + __PRETTY_FUNCTION__, "Function not found", name); + } + } else { + return fnp->second; + } +} + + +/** + ## fsigma::call + */ + + +fostlib::json fsigma::call(frame &stack, const fostlib::json &sexpr) { + if (not sexpr.isarray()) { + throw fostlib::exceptions::not_implemented( + __PRETTY_FUNCTION__, "Script isn't an array/s-expression", + sexpr); + } else if (sexpr.size() == 0) { + throw fostlib::exceptions::not_implemented( + __PRETTY_FUNCTION__, "The script was empty"); + } else { + return call( + stack, stack.resolve_string(*sexpr.begin()), ++sexpr.begin(), + sexpr.end()); + } +} + + +fostlib::json fsigma::call( + frame &stack, + const fostlib::string &name, + fostlib::json::const_iterator begin, + fostlib::json::const_iterator end) { + try { + frame::builtin function(stack.lookup_function(name)); + return function(stack, begin, end); + } catch (fostlib::exceptions::exception &e) { + // Built a stack frame + fostlib::json sf; + fostlib::push_back(sf, name); + for (auto iter = begin; iter != end; ++iter) { + fostlib::push_back(sf, *iter); + } + // Add to the back trace + fostlib::push_back(e.data(), "fg", "backtrace", sf); + throw; + } +} diff --git a/Cpp/fost-urlhandler/precondition.cpp b/Cpp/fost-urlhandler/precondition.cpp new file mode 100644 index 0000000..1779580 --- /dev/null +++ b/Cpp/fost-urlhandler/precondition.cpp @@ -0,0 +1,105 @@ +/** + Copyright 2019-2020 Red Anchor Trading Co. Ltd. + + Distributed under the Boost Software License, Version 1.0. + See + */ + + +#include "precondition.hpp" + + +namespace { + + + fostlib::json + header(const fostlib::http::server::request &req, + fsigma::frame &stack, + fostlib::json::const_iterator pos, + fostlib::json::const_iterator end) { + auto const name = + stack.resolve_string(stack.argument("name", pos, end)); + if (req.headers().exists(name)) { + return fostlib::json{req.headers()[name].value()}; + } else { + return fostlib::json{}; + } + } + + fostlib::json + match(const std::vector &args, + fsigma::frame &stack, + fostlib::json::const_iterator pos, + fostlib::json::const_iterator end) { + auto const arg_idx = + stack.resolve_int(stack.argument("arg_idx", pos, end)); + if (arg_idx > 0 && arg_idx <= args.size()) { + return fostlib::json{args[arg_idx - 1]}; + } + return fostlib::json{}; + } + + fostlib::json + eq(fsigma::frame &stack, + fostlib::json::const_iterator pos, + fostlib::json::const_iterator end) { + auto const val = stack.resolve(stack.argument("value", pos, end)); + while (pos != end) { + if (val + != stack.resolve(stack.argument("comparing_value", pos, end))) { + return fostlib::json{}; + } + } + return fostlib::json{val}; + } + + + fostlib::json logic_or( + fsigma::frame &stack, + fostlib::json::const_iterator pos, + fostlib::json::const_iterator end) { + auto const val = stack.resolve(stack.argument("value", pos, end)); + while (pos != end) { + if (auto ev = stack.resolve( + stack.argument("comparing_value", pos, end)); + not ev.isnull()) { + return ev; + } + } + return fostlib::json{}; + } + + +} + + +fsigma::frame fostlib::preconditions( + const fostlib::http::server::request &req, + const std::vector &args) { + fsigma::frame f{nullptr}; + + f.native["header"] = [&req](fsigma::frame &stack, + fostlib::json::const_iterator pos, + fostlib::json::const_iterator end) { + return header(req, stack, pos, end); + }; + + f.native["match"] = [&args](fsigma::frame &stack, + fostlib::json::const_iterator pos, + fostlib::json::const_iterator end) { + return match(args, stack, pos, end); + }; + + f.native["eq"] = [](fsigma::frame &stack, fostlib::json::const_iterator pos, + fostlib::json::const_iterator end) { + return eq(stack, pos, end); + }; + + + f.native["or"] = [](fsigma::frame &stack, fostlib::json::const_iterator pos, + fostlib::json::const_iterator end) { + return logic_or(stack, pos, end); + }; + + return f; +} diff --git a/Cpp/fost-urlhandler/precondition.hpp b/Cpp/fost-urlhandler/precondition.hpp new file mode 100644 index 0000000..b5c485f --- /dev/null +++ b/Cpp/fost-urlhandler/precondition.hpp @@ -0,0 +1,26 @@ +/** + Copyright 2019-2020 Red Anchor Trading Co. Ltd. + + Distributed under the Boost Software License, Version 1.0. + See + */ + + +#pragma once + + +#include +#include +#include + + +namespace fostlib { + + + /// Returns the base frame with the available preconditions + fsigma::frame preconditions( + const fostlib::http::server::request &, + const std::vector &); + + +} diff --git a/Cpp/fost-urlhandler/precondition.tests.cpp b/Cpp/fost-urlhandler/precondition.tests.cpp new file mode 100644 index 0000000..d254aca --- /dev/null +++ b/Cpp/fost-urlhandler/precondition.tests.cpp @@ -0,0 +1,160 @@ +/** + Copyright 2019 Red Anchor Trading Co. Ltd. + + Distributed under the Boost Software License, Version 1.0. + See + */ + + +#include "precondition.hpp" +#include +#include + + +FSL_TEST_SUITE(preconditions); + + +FSL_TEST_FUNCTION(header) { + fostlib::mime::mime_headers heads; + heads.add("Content-Type", "application/json"); + fostlib::http::server::request req( + "GET", "/", std::make_unique(heads)); + auto stack = fostlib::preconditions(req, std::vector{}); + + fostlib::json args; + fostlib::push_back(args, "Content-Type"); + FSL_CHECK_EQ( + fsigma::call(stack, "header", args.begin(), args.end()), + fostlib::json{"application/json"}); + fostlib::jcursor{0}.set(args, fostlib::json{"Not-a-header"}); + FSL_CHECK_EQ( + fsigma::call(stack, "header", args.begin(), args.end()), + fostlib::json{}); +} + +FSL_TEST_FUNCTION(match) { + fostlib::http::server::request req{"GET", "/"}; + std::vector matched_args; + matched_args.push_back("first-arg"); + matched_args.push_back("second-arg"); + auto stack = fostlib::preconditions(req, matched_args); + + /// Can retrieve arguments from matcher + fostlib::json args; + fostlib::push_back(args, 1); + FSL_CHECK_EQ( + fsigma::call(stack, "match", args.begin(), args.end()), + fostlib::json{"first-arg"}); + fostlib::jcursor{0}.set(args, 2); + FSL_CHECK_EQ( + fsigma::call(stack, "match", args.begin(), args.end()), + fostlib::json{"second-arg"}); + + /// Index out of range should return null + fostlib::jcursor{0}.set(args, 3); + FSL_CHECK_EQ( + fsigma::call(stack, "match", args.begin(), args.end()), + fostlib::json{}); + fostlib::jcursor{0}.set(args, -5); + FSL_CHECK_EQ( + fsigma::call(stack, "match", args.begin(), args.end()), + fostlib::json{}); +} + +FSL_TEST_FUNCTION(eq) { + fostlib::mime::mime_headers heads; + heads.add("UserID", "test"); + fostlib::http::server::request req{ + "GET", "/", std::make_unique(heads)}; + std::vector matched_args; + matched_args.push_back("test"); + auto stack = fostlib::preconditions(req, matched_args); + + /// eq will return the evaluating value + fostlib::json args; + fostlib::push_back(args, 1); + FSL_CHECK_EQ( + fsigma::call(stack, "eq", args.begin(), args.end()), + fostlib::json{1}); + + fostlib::jcursor{0}.set(args, fostlib::json{"random string"}); + FSL_CHECK_EQ( + fsigma::call(stack, "eq", args.begin(), args.end()), + fostlib::json{"random string"}); + + /// return evaluating value when values are + /// equals ["random string", "random string"] + fostlib::push_back(args, fostlib::json{"random string"}); + FSL_CHECK_EQ( + fsigma::call(stack, "eq", args.begin(), args.end()), + fostlib::json{"random string"}); + + /// return null when values are not equals + /// ["random string", "random string", "other-string"] + fostlib::push_back(args, fostlib::json{"other-string"}); + FSL_CHECK_EQ( + fsigma::call(stack, "eq", args.begin(), args.end()), + fostlib::json{}); + + /// eq evaluate the arguments before compare + /// [ ["match", 1], ["header", "Authorization"] ] + fostlib::json ar; + fostlib::push_back(ar, 0, "match"); + fostlib::push_back(ar, 0, 1); + fostlib::push_back(ar, 1, "header"); + fostlib::push_back(ar, 1, "UserID"); + FSL_CHECK_EQ( + fsigma::call(stack, "eq", ar.begin(), ar.end()), + fostlib::json{"test"}); +} + + +FSL_TEST_FUNCTION(or) { + fostlib::mime::mime_headers heads; + heads.add("UserID", "test"); + fostlib::http::server::request req{ + "GET", "/", std::make_unique(heads)}; + std::vector matched_args; + matched_args.push_back("test"); + auto stack = fostlib::preconditions(req, matched_args); + + /// "or" will return null if the evaluating value is null or empty + fostlib::json args; + fostlib::push_back(args, ""); + FSL_CHECK_EQ( + fsigma::call(stack, "or", args.begin(), args.end()), + fostlib::json{}); + + fostlib::jcursor{0}.set(args, fostlib::json{}); + FSL_CHECK_EQ( + fsigma::call(stack, "or", args.begin(), args.end()), + fostlib::json{}); + + /// return the first evaluating value that is not null or empty + fostlib::push_back(args, fostlib::json{"random string"}); + FSL_CHECK_EQ( + fsigma::call(stack, "or", args.begin(), args.end()), + fostlib::json{"random string"}); + + /// "or" evaluate the arguments before compare + /// ["eq", "random_string", "another_random_string"] + /// in this case, should return null + fostlib::json ar; + fostlib::json eq1; + fostlib::push_back(eq1, "eq"); + fostlib::push_back(eq1, "random_string"); + fostlib::push_back(eq1, "another_random_string"); + fostlib::push_back(ar, eq1); + FSL_CHECK_EQ( + fsigma::call(stack, "or", ar.begin(), ar.end()), fostlib::json{}); + + /// ["eq", "random_string", "another_random_string" ], ["eq", "test", "test" ] + fostlib::json eq2; + fostlib::push_back(eq2, "eq"); + fostlib::push_back(eq2, "test"); + fostlib::push_back(eq2, "test"); + fostlib::push_back(ar, eq2); + FSL_CHECK_EQ( + fsigma::call(stack, "or", ar.begin(), ar.end()), + fostlib::json{"test"}); +} diff --git a/Cpp/include/fost/fsigma.hpp b/Cpp/include/fost/fsigma.hpp new file mode 100644 index 0000000..cb7beed --- /dev/null +++ b/Cpp/include/fost/fsigma.hpp @@ -0,0 +1,62 @@ +/** + Copyright 2016-2019 Red Anchor Trading Co. Ltd. + + Distributed under the Boost Software License, Version 1.0. + See + */ + + +#pragma once + + +#include + + +namespace fsigma { + + + /// A stack frame + class frame { + public: + using builtin = std::function; + + frame(frame *parent); + + frame *parent; + std::map native; + std::map symbols; + + /// Pop an argument off the head of the args list + fostlib::json argument( + const fostlib::string &name, + fostlib::json::const_iterator &pos, + fostlib::json::const_iterator end); + + /// Turn an expression into a string + fostlib::string resolve_string(const fostlib::json &); + /// Turn an expression into an integer + int64_t resolve_int(const fostlib::json &); + /// Expect that the JSON represents executable code + fostlib::json resolve(const fostlib::json &); + + /// Lookup a symbol + fostlib::json lookup(const fostlib::string &name) const; + /// Resolve a function + builtin lookup_function(const fostlib::string &name) const; + }; + + + /// Call a JSON s-expr + fostlib::json call(frame &parent, const fostlib::json &sexpr); + /// Call a named function + fostlib::json + call(frame &parent, + const fostlib::string &name, + fostlib::json::const_iterator begin, + fostlib::json::const_iterator end); + + +} From c3c7ecb40f0075c37e845964a2b5f23a376bdf2c Mon Sep 17 00:00:00 2001 From: Tle Ekkul Date: Mon, 3 Feb 2020 16:38:30 +0700 Subject: [PATCH 11/11] [WIP] support inversion of control --- Cpp/fost-urlhandler/matcher.cpp | 7 +++++-- Cpp/fost-urlhandler/view.matcher.tests.cpp | 19 ++++++++++++++++++- Cpp/include/fost/matcher.hpp | 4 ++-- 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/Cpp/fost-urlhandler/matcher.cpp b/Cpp/fost-urlhandler/matcher.cpp index 655b133..26be87e 100644 --- a/Cpp/fost-urlhandler/matcher.cpp +++ b/Cpp/fost-urlhandler/matcher.cpp @@ -13,9 +13,10 @@ #include + namespace { fostlib::nullable - check(const fostlib::json &conf, const fostlib::split_type &parts) { + check(const fostlib::json &conf, const fostlib::split_type &parts, fostlib::match_predicate mp) { if (not conf.has_key("path")) return fostlib::null; const fostlib::json &o = conf["path"]; if (o.size() == parts.size()) { @@ -43,7 +44,9 @@ namespace { } } } - return m; + if (not mp || mp(m)) { + return m; + } } fostlib::log::debug(fostlib::c_fost_web_urlhandler)( "", "Path size mismatch")("o", "size", o.size())( diff --git a/Cpp/fost-urlhandler/view.matcher.tests.cpp b/Cpp/fost-urlhandler/view.matcher.tests.cpp index 85a59ce..ec11ce1 100644 --- a/Cpp/fost-urlhandler/view.matcher.tests.cpp +++ b/Cpp/fost-urlhandler/view.matcher.tests.cpp @@ -165,4 +165,21 @@ FSL_TEST_FUNCTION(view_matcher_support_fallback) { auto [response, status] = fostlib::urlhandler::view::execute( config, "/some/random/url", req, fostlib::host{}); FSL_CHECK_EQ(status, 404); -} \ No newline at end of file +} + + +FSL_TEST_FUNCTION(view_matcher_support_preconditions) { + auto const config = configuration(); + /* + match[0] = { + "path": ["/test", 1], + "precondition": ["eq", ["match", 1], ["header", "__user"]] + "execute": "fost.view.test.echo" + } + It should not match if the precondition is false + */ + fostlib::http::server::request req("GET", "/test/fred"); + auto [response, status] = fostlib::urlhandler::view::execute( + config, "/test/fred", req, fostlib::host{}); + FSL_CHECK_EQ(status, 404); +} diff --git a/Cpp/include/fost/matcher.hpp b/Cpp/include/fost/matcher.hpp index 0124690..a5361ad 100644 --- a/Cpp/include/fost/matcher.hpp +++ b/Cpp/include/fost/matcher.hpp @@ -8,7 +8,6 @@ #pragma once - #include @@ -21,8 +20,9 @@ namespace fostlib { }; + using match_predicate = std::function; fostlib::nullable - matcher(const fostlib::json &config, const fostlib::string &path); + matcher(const fostlib::json &config, const fostlib::string &path, match_predicate = match_predicate{}); }