Commit 763d588e authored by Nico Caprioli's avatar Nico Caprioli

Master merge and tests

* Manually merged with master
* Fixed bugs
* invokeHandler modified in findRoute
* router_tests
parent 01eb1fbf
......@@ -68,9 +68,7 @@ struct Route {
enum class Status { Match, NotFound };
enum class Result {
Ok, Failure
};
enum class Result { Ok, Failure };
typedef std::function<Result(const Request, Http::ResponseWriter)> Handler;
......@@ -84,8 +82,6 @@ struct Route {
Handler handler_;
};
namespace Private {
class RouterHandler;
}
......@@ -112,20 +108,29 @@ private:
static FragmentType getFragmentType(const std::string_view &fragment);
Route::Status invokeRouteHandler(const std::string_view &path,
const Http::Request& req, Http::ResponseWriter response,
std::vector<TypedParam> &params,
std::vector<TypedParam> &splats) const;
std::tuple<std::shared_ptr<Route>, std::vector<TypedParam>, std::vector<TypedParam>>
findRoute(const std::string_view &path,
std::vector<TypedParam> &params,
std::vector<TypedParam> &splats) const;
public:
FragmentTreeNode();
explicit FragmentTreeNode(const std::shared_ptr<char> &resourceReference);
void addRoute(const std::string_view &path, Route::Handler &handler, std::shared_ptr<char> &resourceReference);
void addRoute(const std::string_view &path, const Route::Handler &handler,
const std::shared_ptr<char> &resourceReference);
bool removeRoute(const std::string_view &path);
Route::Status invokeRouteHandler(const Http::Request& req, Http::ResponseWriter response) const;
/**
* Finds the correct route for the given path.
* \param[in] path Requested resource path (usually derived from request)
* \throws std::runtime_error An empty path was given
* \return Found route with its resolved parameters and splats (if no route is found, first element of the tuple
* is a null pointer).
*/
std::tuple<std::shared_ptr<Route>, std::vector<TypedParam>, std::vector<TypedParam>>
findRoute(const std::string_view &path) const;
};
class Router {
......@@ -150,9 +155,7 @@ public:
inline bool hasNotFoundHandler() { return notFoundHandler != nullptr; }
void invokeNotFoundHandler(const Http::Request &req, Http::ResponseWriter resp) const;
Route::Status route(const Http::Request& request, Http::ResponseWriter response);
Status route(const Http::Request& request, Http::ResponseWriter response);
Route::Status route(const Http::Request& request, Http::ResponseWriter response);
private:
void addRoute(Http::Method method, std::string resource, Route::Handler handler);
......
......@@ -46,7 +46,7 @@ namespace std {
string_view
substr(size_type pos = 0, size_type count = npos) const {
substr(size_type pos, size_type count = npos) const {
if (pos > size_) {
throw std::out_of_range("Out of range.");
}
......
......@@ -58,28 +58,6 @@ Request::splat() const {
return splats_;
}
namespace Private {
RouterHandler::RouterHandler(const Rest::Router& router)
: router(router)
{
}
void
RouterHandler::onRequest(
const Http::Request& req,
Http::ResponseWriter response)
{
auto resp = response.clone();
auto result = router.route(req, std::move(resp));
/* @Feature: add support for a custom NotFound handler */
if (result == Route::Status::NotFound)
response.send(Http::Code::Not_Found, "Could not find a matching route");
}
} // namespace Private
FragmentTreeNode::FragmentTreeNode(): resourceRef_(), fixed_(), param_(), optional_(), splat_(), route_() {
std::shared_ptr<char> ptr(new char[1], std::default_delete<char[]>());
ptr.get()[0] = '/';
......@@ -91,23 +69,15 @@ FragmentTreeNode::FragmentTreeNode(const std::shared_ptr<char> &resourceReferenc
FragmentTreeNode::FragmentType
FragmentTreeNode::getFragmentType(const std::string_view &fragment) {
// Let's search for any '?'
auto pos = fragment.find('?');
if (pos != std::string_view::npos) {
if (fragment[0] != ':') {
throw std::runtime_error("Only optional parameters are currently supported");
}
if (pos != fragment.length() - 1) {
throw std::runtime_error("? should be at the end of the string");
}
return FragmentType::Optional;
}
pos = fragment.find('*');
if (pos != std::string_view::npos && pos != 0) {
throw std::runtime_error("Invalid splat parameter");
}
auto optpos = fragment.find('?');
if (fragment[0] == ':') {
if (optpos != std::string_view::npos) {
if (optpos != fragment.length() - 1) {
throw std::runtime_error("? should be at the end of the string");
}
return FragmentType::Optional;
}
return FragmentType::Param;
} else if (fragment[0] == '*') {
if (fragment.length() > 1) {
......@@ -115,13 +85,19 @@ FragmentTreeNode::getFragmentType(const std::string_view &fragment) {
}
return FragmentType::Splat;
}
if (optpos != std::string_view::npos) {
throw std::runtime_error("Only optional parameters are currently supported");
}
return FragmentType::Fixed;
}
void
FragmentTreeNode::addRoute(const std::string_view &path,
Route::Handler &handler,
std::shared_ptr<char> &resourceReference) {
const Route::Handler &handler,
const std::shared_ptr<char> &resourceReference) {
if (path.length() == 0) {
throw std::runtime_error("Invalid zero-length URL.");
}
......@@ -218,9 +194,9 @@ bool Pistache::Rest::FragmentTreeNode::removeRoute(const std::string_view &path)
optional_.empty() && splat_ == nullptr && route_ == nullptr;
}
Route::Status Pistache::Rest::FragmentTreeNode::invokeRouteHandler(
const std::string_view &path, const Http::Request &req,
Http::ResponseWriter response,
std::tuple<std::shared_ptr<Route>, std::vector<TypedParam>, std::vector<TypedParam>>
Pistache::Rest::FragmentTreeNode::findRoute(
const std::string_view &path,
std::vector<TypedParam> &params,
std::vector<TypedParam> &splats) const {
if (path.length() == 0) {
......@@ -232,60 +208,71 @@ Route::Status Pistache::Rest::FragmentTreeNode::invokeRouteHandler(
} else {
currPath = path;
}
if (currPath.find('/') == std::string_view::npos) { // current leaf requested
route_->invokeHandler(Request(req, std::move(params), std::move(splats)), std::move(response));
return Route::Status::Match;
if (currPath.find('/') == std::string_view::npos) { // current leaf requested, or empty final optional
// in case of more than one optional at this point, as it is an ambuiguity,
// it is resolved by using the first optional
if (!optional_.empty()) {
auto optional = optional_.begin();
params.emplace_back(optional->first, std::string_view());
return optional->second->findRoute(currPath, params, splats);
} else return std::make_tuple(route_, std::move(params), std::move(splats));
} else { // recursion to correct descendant
const auto pos = currPath.find('/', 1);
const auto next = (pos == std::string_view::npos) ? currPath.substr(1) : currPath.substr(pos); // complete lower path
auto mid = (pos == std::string_view::npos) ? currPath.substr(1) : currPath.substr(1, currPath.find('/', pos) - 1); // middle resource name
if (fixed_.count(mid) != 0) {
try {
return fixed_.at(mid)->invokeRouteHandler(next, req, std::move(response), params, splats);
} catch (std::runtime_error &) {
}
auto result = fixed_.at(mid)->findRoute(next, params, splats);
auto route = std::get<0>(result);
if (route != nullptr) return result;
}
for (const auto &param: param_) {
try {
params.push_back(TypedParam(param.first, mid));
return param.second->invokeRouteHandler(next, req, std::move(response), params, splats);
} catch (std::runtime_error &) {
params.pop_back();
}
params.emplace_back(param.first, mid);
auto result = param.second->findRoute(next, params, splats);
auto route = std::get<0>(result);
if (route != nullptr) return result;
else params.pop_back();
}
for (const auto &optional: optional_) {
try {
return optional.second->invokeRouteHandler(next, req, std::move(response), params, splats);
params.emplace_back(optional.first, mid);
auto result = optional.second->findRoute(next, params, splats);
auto route = std::get<0>(result);
if (route != nullptr) return result;
else {
params.pop_back();
// try empty optional
params.emplace_back(optional.first, std::string_view());
result = optional.second->findRoute(currPath, params, splats);
route = std::get<0>(result);
if (route != nullptr) return result;
else params.pop_back();
}
catch (std::runtime_error &) {}
}
try {
if (splat_!= nullptr) {
splats.push_back(TypedParam(mid, mid));
return splat_->invokeRouteHandler(next, req, std::move(response), params, splats);
}
} catch (std::runtime_error &) {
splats.pop_back();
if (splat_ != nullptr) {
splats.emplace_back(mid, mid);
auto result = splat_->findRoute(next, params, splats);
auto route = std::get<0>(result);
if (route != nullptr) return result;
else splats.pop_back();
}
throw std::runtime_error("Cannot find route on given leaf.");
return std::make_tuple(nullptr, std::vector<TypedParam>(), std::vector<TypedParam>());
}
}
Route::Status
Pistache::Rest::FragmentTreeNode::invokeRouteHandler(const Http::Request& req, Http::ResponseWriter response) const {
std::tuple<std::shared_ptr<Route>, std::vector<TypedParam>, std::vector<TypedParam>>
Pistache::Rest::FragmentTreeNode::findRoute(const std::string_view &path) const {
std::vector<TypedParam> params;
std::vector<TypedParam> splats;
std::string_view path = {req.resource().data(), req.resource().length()};
try {
return invokeRouteHandler(path, req, std::move(response), params, splats);
} catch (std::runtime_error&) {
return Route::Status::NotFound;
return findRoute(path, params, splats);
}
namespace Private {
......@@ -303,7 +290,7 @@ RouterHandler::onRequest(
auto result = router.route(req, std::move(resp));
/* @Feature: add support for a custom NotFound handler */
if (result == Router::Status::NotFound)
if (result == Route::Status::NotFound)
{
if (router.hasNotFoundHandler())
{
......@@ -314,6 +301,7 @@ RouterHandler::onRequest(
response.send(Http::Code::Not_Found, "Could not find a matching route");
}
}
} // namespace Private
Router
Router::fromDescription(const Rest::Description& desc) {
......@@ -396,20 +384,28 @@ Router::invokeNotFoundHandler(const Http::Request &req, Http::ResponseWriter res
notFoundHandler(Rest::Request(std::move(req), std::vector<TypedParam>(), std::vector<TypedParam>()), std::move(resp));
}
Router::Status
Route::Status
Router::route(const Http::Request& req, Http::ResponseWriter response) {
auto& r = routes[req.method()];
try {
return r.invokeRouteHandler(req, std::move(response));
} catch (std::runtime_error&) {
for (const auto &handler: customHandlers) {
auto resp = response.clone();
auto result = handler(Request(req, std::vector<TypedParam>(), std::vector<TypedParam>()), std::move(resp));
if (result == Route::Result::Ok) return Route::Status::Match;
}
return Route::Status::NotFound;
std::string_view path {req.resource().data(), req.resource().size()};
auto result = r.findRoute(path);
auto route = std::get<0>(result);
if (route != nullptr) {
auto params = std::get<1>(result);
auto splats = std::get<2>(result);
route->invokeHandler(Request(req, std::move(params), std::move(splats)), std::move(response));
return Route::Status::Match;
}
for (const auto& handler: customHandlers) {
auto resp = response.clone();
auto handler1 = handler(Request(req, std::vector<TypedParam>(), std::vector<TypedParam>()), std::move(resp));
if (handler1 == Route::Result::Ok) return Route::Status::Match;
}
if (hasNotFoundHandler()) invokeNotFoundHandler(req, std::move(response));
return Route::Status::NotFound;
}
void
......@@ -447,9 +443,9 @@ void Options(Router& router, std::string resource, Route::Handler handler) {
router.options(std::move(resource), std::move(handler));
}
void Remove(Router& router, Http::Method method, std::string resource) {
router.removeRoute(method, resource);
router.removeRoute(method, std::move(resource));
}
void NotFound(Router& router, Route::Handler handler) {
router.addNotFoundHandler(std::move(handler));
......
......@@ -12,23 +12,25 @@
using namespace Pistache;
bool match(const Rest::Route& route, const std::string& req) {
return std::get<0>(route.match(req));
bool match(const Rest::FragmentTreeNode& routes, const std::string& req) {
std::shared_ptr<Rest::Route> route;
std::tie(route, std::ignore, std::ignore) = routes.findRoute({req.data(), req.size()});
return route != nullptr;
}
bool matchParams(
const Rest::Route& route, const std::string& req,
const Rest::FragmentTreeNode& routes, const std::string& req,
std::initializer_list<std::pair<std::string, std::string>> list)
{
bool ok;
std::shared_ptr<Rest::Route> route;
std::vector<Rest::TypedParam> params;
std::tie(ok, params, std::ignore) = route.match(req);
std::tie(route, params, std::ignore) = routes.findRoute({req.data(), req.size()});
if (!ok) return false;
if (route == nullptr) return false;
for (const auto& p: list) {
auto it = std::find_if(params.begin(), params.end(), [&](const Rest::TypedParam& param) {
return param.name() == p.first;
return param.name() == std::string_view(p.first.data(), p.first.size());
});
if (it == std::end(params)) {
std::cerr << "Did not find param '" << p.first << "'" << std::endl;
......@@ -45,14 +47,14 @@ bool matchParams(
}
bool matchSplat(
const Rest::Route& route, const std::string& req,
const Rest::FragmentTreeNode& routes, const std::string& req,
std::initializer_list<std::string> list)
{
bool ok;
std::shared_ptr<Rest::Route> route;
std::vector<Rest::TypedParam> splats;
std::tie(ok, std::ignore, splats) = route.match(req);
if (!ok) return false;
std::tie(route, std::ignore, splats) = routes.findRoute({req.data(), req.size()});
if (route == nullptr) return false;
if (list.size() != splats.size()) {
std::cerr << "Size mismatch (" << list.size() << " != " << splats.size() << ")"
......@@ -74,55 +76,57 @@ bool matchSplat(
return true;
}
Rest::Route
makeRoute(std::string value) {
auto noop = [](const Http::Request&, Http::Response) { return Rest::Route::Result::Ok; };
return Rest::Route(value, Http::Method::Get, noop);
}
TEST(router_test, test_fixed_routes) {
auto r1 = makeRoute("/v1/hello");
ASSERT_TRUE(match(r1, "/v1/hello"));
ASSERT_FALSE(match(r1, "/v2/hello"));
ASSERT_FALSE(match(r1, "/v1/hell0"));
Rest::FragmentTreeNode routes;
routes.addRoute(std::string_view("/v1/hello"), nullptr, nullptr);
ASSERT_TRUE(match(routes, "/v1/hello"));
ASSERT_FALSE(match(routes, "/v2/hello"));
ASSERT_FALSE(match(routes, "/v1/hell0"));
auto r2 = makeRoute("/a/b/c");
ASSERT_TRUE(match(r2, "/a/b/c"));
routes.addRoute(std::string_view("/a/b/c"), nullptr, nullptr);
ASSERT_TRUE(match(routes, "/a/b/c"));
}
TEST(router_test, test_parameters) {
auto r1 = makeRoute("/v1/hello/:name");
ASSERT_TRUE(matchParams(r1, "/v1/hello/joe", {
Rest::FragmentTreeNode routes;
routes.addRoute(std::string_view("/v1/hello/:name"), nullptr, nullptr);
ASSERT_TRUE(matchParams(routes, "/v1/hello/joe", {
{ ":name", "joe" }
}));
auto r2 = makeRoute("/greetings/:from/:to");
ASSERT_TRUE(matchParams(r2, "/greetings/foo/bar", {
routes.addRoute(std::string_view("/greetings/:from/:to"), nullptr, nullptr);
ASSERT_TRUE(matchParams(routes, "/greetings/foo/bar", {
{ ":from", "foo" },
{ ":to" , "bar" }
}));
}
TEST(router_test, test_optional) {
auto r1 = makeRoute("/get/:key?");
ASSERT_TRUE(match(r1, "/get"));
ASSERT_TRUE(match(r1, "/get/"));
ASSERT_TRUE(matchParams(r1, "/get/foo", {
Rest::FragmentTreeNode routes;
routes.addRoute(std::string_view("/get/:key?"), nullptr, nullptr);
ASSERT_TRUE(match(routes, "/get"));
ASSERT_TRUE(match(routes, "/get/"));
ASSERT_TRUE(matchParams(routes, "/get/foo", {
{ ":key", "foo" }
}));
ASSERT_TRUE(matchParams(r1, "/get/foo/", {
ASSERT_TRUE(matchParams(routes, "/get/foo/", {
{ ":key", "foo" }
}));
ASSERT_FALSE(match(r1, "/get/foo/bar"));
ASSERT_FALSE(match(routes, "/get/foo/bar"));
}
TEST(router_test, test_splat) {
auto r1 = makeRoute("/say/*/to/*");
ASSERT_TRUE(match(r1, "/say/hello/to/user"));
ASSERT_FALSE(match(r1, "/say/hello/to"));
ASSERT_FALSE(match(r1, "/say/hello/to/user/please"));
Rest::FragmentTreeNode routes;
routes.addRoute(std::string_view("/say/*/to/*"), nullptr, nullptr);
ASSERT_TRUE(matchSplat(r1, "/say/hello/to/user", { "hello", "user" }));
ASSERT_TRUE(matchSplat(r1, "/say/hello/to/user/", { "hello", "user" }));
}
ASSERT_TRUE(match(routes, "/say/hello/to/user"));
ASSERT_FALSE(match(routes, "/say/hello/to"));
ASSERT_FALSE(match(routes, "/say/hello/to/user/please"));
ASSERT_TRUE(matchSplat(routes, "/say/hello/to/user", { "hello", "user" }));
ASSERT_TRUE(matchSplat(routes, "/say/hello/to/user/", { "hello", "user" }));
}
\ No newline at end of file
......@@ -5,7 +5,7 @@ TEST(string_view_test, substr_test) {
std::string_view orig ("test");
std::string_view targ ("est");
sub = orig.substr(1);
std::string_view sub = orig.substr(1);
ASSERT_EQ(sub, targ);
sub = orig.substr(1, 10);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment