Commit a64024b5 authored by octal's avatar octal

Implemented splat parameters

parent 9620f79d
...@@ -201,7 +201,7 @@ private: ...@@ -201,7 +201,7 @@ private:
}); });
if (it == std::end(metrics)) { if (it == std::end(metrics)) {
response.send(Http::Code::Not_Found); response.send(Http::Code::Not_Found, "Metric does not exist");
} else { } else {
const auto& metric = *it; const auto& metric = *it;
response.send(Http::Code::Ok, std::to_string(metric.value())); response.send(Http::Code::Ok, std::to_string(metric.value()));
......
...@@ -13,9 +13,11 @@ namespace Rest { ...@@ -13,9 +13,11 @@ namespace Rest {
Request::Request( Request::Request(
const Http::Request& request, const Http::Request& request,
std::vector<TypedParam>&& params) std::vector<TypedParam>&& params,
std::vector<TypedParam>&& splats)
: Http::Request(request) : Http::Request(request)
, params_(std::move(params)) , params_(std::move(params))
, splats_(std::move(splats))
{ {
} }
...@@ -41,7 +43,21 @@ Request::param(std::string name) const { ...@@ -41,7 +43,21 @@ Request::param(std::string name) const {
return *it; return *it;
} }
Router::Route::Fragment::Fragment(std::string value) TypedParam
Request::splatAt(size_t index) const {
if (index >= splats_.size()) {
throw std::out_of_range("Request splat index out of range");
}
return splats_[index];
}
std::vector<TypedParam>
Request::splat() const {
return splats_;
}
Route::Fragment::Fragment(std::string value)
{ {
if (value.empty()) if (value.empty())
throw std::runtime_error("Invalid empty fragment"); throw std::runtime_error("Invalid empty fragment");
...@@ -50,13 +66,11 @@ Router::Route::Fragment::Fragment(std::string value) ...@@ -50,13 +66,11 @@ Router::Route::Fragment::Fragment(std::string value)
} }
bool bool
Router::Route::Fragment::match(const std::string& raw) const { Route::Fragment::match(const std::string& raw) const {
if (flags.hasFlag(Flag::Fixed)) { if (flags.hasFlag(Flag::Fixed)) {
if (!flags.hasFlag(Flag::Optional)) { return raw == value_;
return raw == value_;
}
} }
else if (flags.hasFlag(Flag::Parameter)) { else if (flags.hasFlag(Flag::Parameter) || flags.hasFlag(Flag::Splat)) {
return true; return true;
} }
...@@ -64,16 +78,22 @@ Router::Route::Fragment::match(const std::string& raw) const { ...@@ -64,16 +78,22 @@ Router::Route::Fragment::match(const std::string& raw) const {
} }
bool bool
Router::Route::Fragment::match(const Fragment& other) const { Route::Fragment::match(const Fragment& other) const {
return match(other.value()); return match(other.value());
} }
void void
Router::Route::Fragment::init(std::string value) { Route::Fragment::init(std::string value) {
if (value[0] == ':') if (value[0] == ':')
flags.setFlag(Flag::Parameter); flags.setFlag(Flag::Parameter);
else else if (value[0] == '*') {
if (value.size() > 1)
throw std::runtime_error("Invalid splat parameter");
flags.setFlag(Flag::Splat);
}
else {
flags.setFlag(Flag::Fixed); flags.setFlag(Flag::Fixed);
}
// Let's search for any '?' // Let's search for any '?'
auto pos = value.find('?'); auto pos = value.find('?');
...@@ -94,7 +114,7 @@ Router::Route::Fragment::init(std::string value) { ...@@ -94,7 +114,7 @@ Router::Route::Fragment::init(std::string value) {
} }
void void
Router::Route::Fragment::checkInvariant() const { Route::Fragment::checkInvariant() const {
auto check = [this](std::initializer_list<Flag> exclusiveFlags) { auto check = [this](std::initializer_list<Flag> exclusiveFlags) {
for (auto flag: exclusiveFlags) { for (auto flag: exclusiveFlags) {
if (!flags.hasFlag(flag)) return; if (!flags.hasFlag(flag)) return;
...@@ -106,11 +126,14 @@ Router::Route::Fragment::checkInvariant() const { ...@@ -106,11 +126,14 @@ Router::Route::Fragment::checkInvariant() const {
check({ Flag::Fixed, Flag::Optional }); check({ Flag::Fixed, Flag::Optional });
check({ Flag::Fixed, Flag::Parameter }); check({ Flag::Fixed, Flag::Parameter });
check({ Flag::Splat, Flag::Fixed });
check({ Flag::Splat, Flag::Optional });
check({ Flag::Splat, Flag::Parameter });
} }
std::vector<Router::Route::Fragment> std::vector<Route::Fragment>
Router::Route::Fragment::fromUrl(const std::string& url) { Route::Fragment::fromUrl(const std::string& url) {
std::vector<Router::Route::Fragment> fragments; std::vector<Route::Fragment> fragments;
std::istringstream iss(url); std::istringstream iss(url);
std::string p; std::string p;
...@@ -125,29 +148,38 @@ Router::Route::Fragment::fromUrl(const std::string& url) { ...@@ -125,29 +148,38 @@ Router::Route::Fragment::fromUrl(const std::string& url) {
} }
bool bool
Router::Route::Fragment::isParameter() const { Route::Fragment::isParameter() const {
return flags.hasFlag(Flag::Parameter); return flags.hasFlag(Flag::Parameter);
} }
bool bool
Router::Route::Fragment::isOptional() const { Route::Fragment::isOptional() const {
return isParameter() && flags.hasFlag(Flag::Optional); return isParameter() && flags.hasFlag(Flag::Optional);
} }
std::pair<bool, std::vector<TypedParam>> bool
Router::Route::match(const Http::Request& req) const Route::Fragment::isSplat() const {
return flags.hasFlag(Flag::Splat);
}
std::tuple<bool, std::vector<TypedParam>, std::vector<TypedParam>>
Route::match(const Http::Request& req) const
{ {
return match(req.resource()); return match(req.resource());
} }
std::pair<bool, std::vector<TypedParam>> std::tuple<bool, std::vector<TypedParam>, std::vector<TypedParam>>
Router::Route::match(const std::string& req) const Route::match(const std::string& req) const
{ {
#define UNMATCH() std::make_tuple(false, std::vector<TypedParam>(), std::vector<TypedParam>())
auto reqFragments = Fragment::fromUrl(req); auto reqFragments = Fragment::fromUrl(req);
if (reqFragments.size() > fragments_.size()) if (reqFragments.size() > fragments_.size())
return std::make_pair(false, std::vector<TypedParam>()); return UNMATCH();
std::vector<TypedParam> params;
std::vector<TypedParam> splats;
std::vector<TypedParam> extractedParams;
for (std::vector<Fragment>::size_type i = 0; i < fragments_.size(); ++i) { for (std::vector<Fragment>::size_type i = 0; i < fragments_.size(); ++i) {
const auto& fragment = fragments_[i]; const auto& fragment = fragments_[i];
...@@ -155,30 +187,36 @@ Router::Route::match(const std::string& req) const ...@@ -155,30 +187,36 @@ Router::Route::match(const std::string& req) const
if (fragment.isOptional()) if (fragment.isOptional())
continue; continue;
return std::make_pair(false, std::vector<TypedParam>()); return UNMATCH();
} }
const auto& reqFragment = reqFragments[i]; const auto& reqFragment = reqFragments[i];
if (!fragment.match(reqFragment)) if (!fragment.match(reqFragment))
return std::make_pair(false, std::vector<TypedParam>()); return UNMATCH();
if (fragment.isParameter()) { if (fragment.isParameter()) {
extractedParams.push_back(TypedParam(fragment.value(), reqFragment.value())); params.push_back(TypedParam(fragment.value(), reqFragment.value()));
} else if (fragment.isSplat()) {
splats.push_back(TypedParam(reqFragment.value(), reqFragment.value()));
} }
} }
return make_pair(true, std::move(extractedParams)); #undef UNMATCH
return make_tuple(true, std::move(params), std::move(splats));
} }
Router::HttpHandler::HttpHandler( namespace Private {
const std::unordered_map<Http::Method, std::vector<Router::Route>>& routes)
HttpHandler::HttpHandler(
const std::unordered_map<Http::Method, std::vector<Route>>& routes)
: routes(routes) : routes(routes)
{ {
} }
void void
Router::HttpHandler::onRequest( HttpHandler::onRequest(
const Http::Request& req, const Http::Request& req,
Http::Response response, Http::Response response,
Http::Timeout timeout) Http::Timeout timeout)
...@@ -187,9 +225,10 @@ Router::HttpHandler::onRequest( ...@@ -187,9 +225,10 @@ Router::HttpHandler::onRequest(
for (const auto& route: r) { for (const auto& route: r) {
bool match; bool match;
std::vector<TypedParam> params; std::vector<TypedParam> params;
std::tie(match, params) = route.match(req); std::vector<TypedParam> splats;
std::tie(match, params, splats) = route.match(req);
if (match) { if (match) {
route.invokeHandler(Request(req, std::move(params)), std::move(response)); route.invokeHandler(Request(req, std::move(params), std::move(splats)), std::move(response));
return; return;
} }
} }
...@@ -197,28 +236,30 @@ Router::HttpHandler::onRequest( ...@@ -197,28 +236,30 @@ Router::HttpHandler::onRequest(
response.send(Http::Code::Not_Found, "Could not find a matching route"); response.send(Http::Code::Not_Found, "Could not find a matching route");
} }
} // namespace Private
void void
Router::get(std::string resource, Router::Handler handler) { Router::get(std::string resource, Route::Handler handler) {
addRoute(Http::Method::Get, std::move(resource), std::move(handler)); addRoute(Http::Method::Get, std::move(resource), std::move(handler));
} }
void void
Router::post(std::string resource, Router::Handler handler) { Router::post(std::string resource, Route::Handler handler) {
addRoute(Http::Method::Post, std::move(resource), std::move(handler)); addRoute(Http::Method::Post, std::move(resource), std::move(handler));
} }
void void
Router::put(std::string resource, Router::Handler handler) { Router::put(std::string resource, Route::Handler handler) {
addRoute(Http::Method::Put, std::move(resource), std::move(handler)); addRoute(Http::Method::Put, std::move(resource), std::move(handler));
} }
void void
Router::del(std::string resource, Router::Handler handler) { Router::del(std::string resource, Route::Handler handler) {
addRoute(Http::Method::Delete, std::move(resource), std::move(handler)); addRoute(Http::Method::Delete, std::move(resource), std::move(handler));
} }
void void
Router::addRoute(Http::Method method, std::string resource, Router::Handler handler) Router::addRoute(Http::Method method, std::string resource, Route::Handler handler)
{ {
auto& r = routes[method]; auto& r = routes[method];
r.push_back(Route(std::move(resource), method, std::move(handler))); r.push_back(Route(std::move(resource), method, std::move(handler)));
...@@ -227,19 +268,19 @@ Router::addRoute(Http::Method method, std::string resource, Router::Handler hand ...@@ -227,19 +268,19 @@ Router::addRoute(Http::Method method, std::string resource, Router::Handler hand
namespace Routes { namespace Routes {
void Get(Router& router, std::string resource, Router::Handler handler) { void Get(Router& router, std::string resource, Route::Handler handler) {
router.get(std::move(resource), std::move(handler)); router.get(std::move(resource), std::move(handler));
} }
void Post(Router& router, std::string resource, Router::Handler handler) { void Post(Router& router, std::string resource, Route::Handler handler) {
router.post(std::move(resource), std::move(handler)); router.post(std::move(resource), std::move(handler));
} }
void Put(Router& router, std::string resource, Router::Handler handler) { void Put(Router& router, std::string resource, Route::Handler handler) {
router.put(std::move(resource), std::move(handler)); router.put(std::move(resource), std::move(handler));
} }
void Delete(Router& router, std::string resource, Router::Handler handler) { void Delete(Router& router, std::string resource, Route::Handler handler) {
router.del(std::move(resource), std::move(handler)); router.del(std::move(resource), std::move(handler));
} }
......
...@@ -55,88 +55,79 @@ private: ...@@ -55,88 +55,79 @@ private:
std::string value_; std::string value_;
}; };
class Request : public Http::Request { class Request;
public:
explicit Request(
const Http::Request& request,
std::vector<TypedParam>&& params);
bool hasParam(std::string name) const;
TypedParam param(std::string name) const;
private:
std::vector<TypedParam> params_;
};
class Router { struct Route {
public:
typedef std::function<void (const Request&, Net::Http::Response)> Handler; typedef std::function<void (const Request&, Net::Http::Response)> Handler;
struct Route { Route(std::string resource, Http::Method method, Handler handler)
Route(std::string resource, Http::Method method, Handler handler) : resource_(std::move(resource))
: resource_(std::move(resource)) , method_(method)
, method_(method) , handler_(std::move(handler))
, handler_(std::move(handler)) , fragments_(Fragment::fromUrl(resource_))
, fragments_(Fragment::fromUrl(resource_)) {
{ }
}
std::pair<bool, std::vector<TypedParam>> std::tuple<bool, std::vector<TypedParam>, std::vector<TypedParam>>
match(const Http::Request& req) const; match(const Http::Request& req) const;
std::pair<bool, std::vector<TypedParam>> std::tuple<bool, std::vector<TypedParam>, std::vector<TypedParam>>
match(const std::string& req) const; match(const std::string& req) const;
template<typename... Args> template<typename... Args>
void invokeHandler(Args&& ...args) const { void invokeHandler(Args&& ...args) const {
handler_(std::forward<Args>(args)...); handler_(std::forward<Args>(args)...);
} }
private: private:
struct Fragment { struct Fragment {
explicit Fragment(std::string value); explicit Fragment(std::string value);
bool match(const std::string& raw) const; bool match(const std::string& raw) const;
bool match(const Fragment& other) const; bool match(const Fragment& other) const;
bool isParameter() const; bool isParameter() const;
bool isOptional() const; bool isSplat() const;
bool isOptional() const;
std::string value() const { std::string value() const {
return value_; return value_;
} }
static std::vector<Fragment> fromUrl(const std::string& url); static std::vector<Fragment> fromUrl(const std::string& url);
private: private:
enum class Flag { enum class Flag {
None = 0x0, None = 0x0,
Fixed = 0x1, Fixed = 0x1,
Parameter = Fixed << 1, Parameter = Fixed << 1,
Optional = Parameter << 1 Optional = Parameter << 1,
}; Splat = Optional << 1
};
void init(std::string value); void init(std::string value);
void checkInvariant() const; void checkInvariant() const;
Flags<Flag> flags; Flags<Flag> flags;
std::string value_; std::string value_;
};
std::string resource_;
Net::Http::Method method_;
Handler handler_;
/* @Performance: since we know that resource_ will live as long as the vector underneath,
* we would benefit from std::experimental::string_view to store fragments.
*
* We could use string_view instead of allocating strings everytime. However, string_view is
* only available in c++17, so I might have to come with my own lightweight implementation of
* it
*/
std::vector<Fragment> fragments_;
}; };
std::string resource_;
Net::Http::Method method_;
Handler handler_;
/* @Performance: since we know that resource_ will live as long as the vector underneath,
* we would benefit from std::experimental::string_view to store fragments.
*
* We could use string_view instead of allocating strings everytime. However, string_view is
* only available in c++17, so I might have to come with my own lightweight implementation of
* it
*/
std::vector<Fragment> fragments_;
};
namespace Private {
class HttpHandler : public Net::Http::Handler { class HttpHandler : public Net::Http::Handler {
public: public:
HttpHandler(const std::unordered_map<Http::Method, std::vector<Route>>& routes); HttpHandler(const std::unordered_map<Http::Method, std::vector<Route>>& routes);
...@@ -149,28 +140,52 @@ public: ...@@ -149,28 +140,52 @@ public:
private: private:
std::unordered_map<Http::Method, std::vector<Route>> routes; std::unordered_map<Http::Method, std::vector<Route>> routes;
}; };
}
class Request : public Http::Request {
public:
friend class Private::HttpHandler;
bool hasParam(std::string name) const;
TypedParam param(std::string name) const;
TypedParam splatAt(size_t index) const;
std::vector<TypedParam> splat() const;
private:
explicit Request(
const Http::Request& request,
std::vector<TypedParam>&& params,
std::vector<TypedParam>&& splats);
std::vector<TypedParam> params_;
std::vector<TypedParam> splats_;
};
class Router {
public:
std::shared_ptr<HttpHandler> std::shared_ptr<Private::HttpHandler>
handler() const { handler() const {
return std::make_shared<HttpHandler>(routes); return std::make_shared<Private::HttpHandler>(routes);
} }
void get(std::string resource, Handler handler); void get(std::string resource, Route::Handler handler);
void post(std::string resource, Handler handler); void post(std::string resource, Route::Handler handler);
void put(std::string resource, Handler handler); void put(std::string resource, Route::Handler handler);
void del(std::string resource, Handler handler); void del(std::string resource, Route::Handler handler);
private: private:
void addRoute(Http::Method method, std::string resource, Handler handler); void addRoute(Http::Method method, std::string resource, Route::Handler handler);
std::unordered_map<Http::Method, std::vector<Route>> routes; std::unordered_map<Http::Method, std::vector<Route>> routes;
}; };
namespace Routes { namespace Routes {
void Get(Router& router, std::string resource, Router::Handler handler); void Get(Router& router, std::string resource, Route::Handler handler);
void Post(Router& router, std::string resource, Router::Handler handler); void Post(Router& router, std::string resource, Route::Handler handler);
void Put(Router& router, std::string resource, Router::Handler handler); void Put(Router& router, std::string resource, Route::Handler handler);
void Delete(Router& router, std::string resource, Router::Handler handler); void Delete(Router& router, std::string resource, Route::Handler handler);
template<typename Handler, typename Obj> template<typename Handler, typename Obj>
void Get(Router& router, std::string resource, Handler handler, Obj obj) { void Get(Router& router, std::string resource, Handler handler, Obj obj) {
......
...@@ -9,24 +9,24 @@ ...@@ -9,24 +9,24 @@
#include "router.h" #include "router.h"
#include <algorithm> #include <algorithm>
using namespace Net::Rest; using namespace Net;
bool match(const Router::Route& route, const std::string& req) { bool match(const Rest::Route& route, const std::string& req) {
return route.match(req).first; return std::get<0>(route.match(req));
} }
bool match( bool matchParams(
const Router::Route& route, const std::string& req, const Rest::Route& route, const std::string& req,
std::initializer_list<std::pair<std::string, std::string>> list) std::initializer_list<std::pair<std::string, std::string>> list)
{ {
bool ok; bool ok;
std::vector<TypedParam> params; std::vector<Rest::TypedParam> params;
std::tie(ok, params) = route.match(req); std::tie(ok, params, std::ignore) = route.match(req);
if (!ok) return false; if (!ok) return false;
for (const auto& p: list) { for (const auto& p: list) {
auto it = std::find_if(params.begin(), params.end(), [&](const TypedParam& param) { auto it = std::find_if(params.begin(), params.end(), [&](const Rest::TypedParam& param) {
return param.name() == p.first; return param.name() == p.first;
}); });
if (it == std::end(params)) { if (it == std::end(params)) {
...@@ -43,12 +43,40 @@ bool match( ...@@ -43,12 +43,40 @@ bool match(
return true; return true;
} }
bool matchSplat(
const Rest::Route& route, const std::string& req,
std::initializer_list<std::string> list)
{
bool ok;
std::vector<Rest::TypedParam> splats;
std::tie(ok, std::ignore, splats) = route.match(req);
if (!ok) return false;
if (list.size() != splats.size()) {
std::cerr << "Size mismatch (" << list.size() << " != " << splats.size() << ")"
<< std::endl;
return false;
}
size_t i = 0;
for (const auto& s: list) {
auto splat = splats[i].as<std::string>();
if (splat != s) {
std::cerr << "Splat number " << i << " did not match ("
<< splat << " != " << s << ")" << std::endl;
return false;
}
++i;
}
return true;
}
Router::Route Rest::Route
makeRoute(std::string value) { makeRoute(std::string value) {
auto noop = [](const Net::Http::Request&, Net::Http::Response) { }; auto noop = [](const Net::Http::Request&, Net::Http::Response) { };
return Router::Route(value, Net::Http::Method::Get, noop); return Rest::Route(value, Net::Http::Method::Get, noop);
} }
TEST(router_test, test_fixed_routes) { TEST(router_test, test_fixed_routes) {
...@@ -63,12 +91,12 @@ TEST(router_test, test_fixed_routes) { ...@@ -63,12 +91,12 @@ TEST(router_test, test_fixed_routes) {
TEST(router_test, test_parameters) { TEST(router_test, test_parameters) {
auto r1 = makeRoute("/v1/hello/:name"); auto r1 = makeRoute("/v1/hello/:name");
ASSERT_TRUE(match(r1, "/v1/hello/joe", { ASSERT_TRUE(matchParams(r1, "/v1/hello/joe", {
{ ":name", "joe" } { ":name", "joe" }
})); }));
auto r2 = makeRoute("/greetings/:from/:to"); auto r2 = makeRoute("/greetings/:from/:to");
ASSERT_TRUE(match(r2, "/greetings/foo/bar", { ASSERT_TRUE(matchParams(r2, "/greetings/foo/bar", {
{ ":from", "foo" }, { ":from", "foo" },
{ ":to" , "bar" } { ":to" , "bar" }
})); }));
...@@ -78,12 +106,22 @@ TEST(router_test, test_optional) { ...@@ -78,12 +106,22 @@ TEST(router_test, test_optional) {
auto r1 = makeRoute("/get/:key?"); auto r1 = makeRoute("/get/:key?");
ASSERT_TRUE(match(r1, "/get")); ASSERT_TRUE(match(r1, "/get"));
ASSERT_TRUE(match(r1, "/get/")); ASSERT_TRUE(match(r1, "/get/"));
ASSERT_TRUE(match(r1, "/get/foo", { ASSERT_TRUE(matchParams(r1, "/get/foo", {
{ ":key", "foo" } { ":key", "foo" }
})); }));
ASSERT_TRUE(match(r1, "/get/foo/", { ASSERT_TRUE(matchParams(r1, "/get/foo/", {
{ ":key", "foo" } { ":key", "foo" }
})); }));
ASSERT_FALSE(match(r1, "/get/foo/bar")); ASSERT_FALSE(match(r1, "/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"));
ASSERT_TRUE(matchSplat(r1, "/say/hello/to/user", { "hello", "user" }));
ASSERT_TRUE(matchSplat(r1, "/say/hello/to/user/", { "hello", "user" }));
}
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