diff --git a/Cpp/fost-urlhandler/CMakeLists.txt b/Cpp/fost-urlhandler/CMakeLists.txt index c243c71..f21f02b 100644 --- a/Cpp/fost-urlhandler/CMakeLists.txt +++ b/Cpp/fost-urlhandler/CMakeLists.txt @@ -3,10 +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 @@ -22,6 +25,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) @@ -33,6 +37,8 @@ if(TARGET check) add_library(fost-urlhandler-smoke STATIC EXCLUDE_FROM_ALL 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 @@ -43,6 +49,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/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/matcher.cpp b/Cpp/fost-urlhandler/matcher.cpp new file mode 100644 index 0000000..26be87e --- /dev/null +++ b/Cpp/fost-urlhandler/matcher.cpp @@ -0,0 +1,71 @@ +/** + 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, fostlib::match_predicate mp) { + 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"); + } + } + } + if (not mp || mp(m)) { + 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( + fostlib::json const &configuration, fostlib::string const &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/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/fost-urlhandler/view.matcher.cpp b/Cpp/fost-urlhandler/view.matcher.cpp new file mode 100644 index 0000000..a4f05bf --- /dev/null +++ b/Cpp/fost-urlhandler/view.matcher.cpp @@ -0,0 +1,82 @@ +/** + 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], + "execute": "" + }], + "no-match": "" // fallback + } + } + + * This view doesn't consume any path + */ + if (!configuration.has_key("no-match") + || !configuration.has_key("match")) { + throw fostlib::exceptions::not_implemented( + __PRETTY_FUNCTION__, + "fost.view.match required keys [\"no-match\", " + "\"match\"] in the" + "configuration \"\""); + } + auto &match_config = configuration["match"]; + fostlib::log::debug(fostlib::c_fost_web_urlhandler)("path", path)( + "configuration", match_config); + + 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["no-match"], path, req, host); + } + } 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..ec11ce1 --- /dev/null +++ b/Cpp/fost-urlhandler/view.matcher.tests.cpp @@ -0,0 +1,185 @@ +/** + 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 { + const class test_echo_req_view : public fostlib::urlhandler::view { + public: + 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 { + auto const req_body = fostlib::coerce( + fostlib::coerce(req.data()->data())); + boost::shared_ptr response{ + new fostlib::text_body(req_body, req.headers())}; + 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" + },{ + "path": ["/shop", 1, "/employee", 2, "/address", 3], + "execute": "fost.view.test.echo" + }], + "no-match": "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::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", "no-match", "fost.response.404"); + return config; + } +} + +FSL_TEST_FUNCTION(throw_exception_when_no_fallback_view_defined) { + /* + { + "view": "fost.view.match", + "configuration": { + "match": [{}] + } + } + */ + fostlib::http::server::request req("GET", "/"); + fostlib::json config{}; + fostlib::insert(config, "view", "fost.view.match"); + fostlib::json path1{}; + 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(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(); + 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"); +} + +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); + + 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_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"); + auto [response, status] = fostlib::urlhandler::view::execute( + config, "/some/random/url", req, fostlib::host{}); + FSL_CHECK_EQ(status, 404); +} + + +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/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); + + +} diff --git a/Cpp/include/fost/matcher.hpp b/Cpp/include/fost/matcher.hpp new file mode 100644 index 0000000..a5361ad --- /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; + }; + + + using match_predicate = std::function; + fostlib::nullable + matcher(const fostlib::json &config, const fostlib::string &path, match_predicate = match_predicate{}); + + +} 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;