* Add named path parameters parsing * Select match mode based on pattern * Add examples and comments to README * Add documentation to matchers
This commit is contained in:
parent
f1daa5b88b
commit
17fc522b75
@ -94,11 +94,20 @@ int main(void)
|
|||||||
res.set_content("Hello World!", "text/plain");
|
res.set_content("Hello World!", "text/plain");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Match the request path against a regular expression
|
||||||
|
// and extract its captures
|
||||||
svr.Get(R"(/numbers/(\d+))", [&](const Request& req, Response& res) {
|
svr.Get(R"(/numbers/(\d+))", [&](const Request& req, Response& res) {
|
||||||
auto numbers = req.matches[1];
|
auto numbers = req.matches[1];
|
||||||
res.set_content(numbers, "text/plain");
|
res.set_content(numbers, "text/plain");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Capture the second segment of the request path as "id" path param
|
||||||
|
svr.Get("/users/:id", [&](const Request& req, Response& res) {
|
||||||
|
auto user_id = req.path_params.at("id");
|
||||||
|
res.set_content(user_id, "text/plain");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Extract values from HTTP headers and URL query params
|
||||||
svr.Get("/body-header-param", [](const Request& req, Response& res) {
|
svr.Get("/body-header-param", [](const Request& req, Response& res) {
|
||||||
if (req.has_header("Content-Length")) {
|
if (req.has_header("Content-Length")) {
|
||||||
auto val = req.get_header_value("Content-Length");
|
auto val = req.get_header_value("Content-Length");
|
||||||
|
212
httplib.h
212
httplib.h
@ -229,6 +229,8 @@ using socket_t = int;
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <unordered_set>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||||
@ -472,6 +474,7 @@ struct Request {
|
|||||||
MultipartFormDataMap files;
|
MultipartFormDataMap files;
|
||||||
Ranges ranges;
|
Ranges ranges;
|
||||||
Match matches;
|
Match matches;
|
||||||
|
std::unordered_map<std::string, std::string> path_params;
|
||||||
|
|
||||||
// for client
|
// for client
|
||||||
ResponseHandler response_handler;
|
ResponseHandler response_handler;
|
||||||
@ -665,6 +668,76 @@ using SocketOptions = std::function<void(socket_t sock)>;
|
|||||||
|
|
||||||
void default_socket_options(socket_t sock);
|
void default_socket_options(socket_t sock);
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
|
||||||
|
class MatcherBase {
|
||||||
|
public:
|
||||||
|
virtual ~MatcherBase() = default;
|
||||||
|
|
||||||
|
// Match request path and populate its matches and
|
||||||
|
virtual bool match(Request &request) const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Captures parameters in request path and stores them in Request::path_params
|
||||||
|
*
|
||||||
|
* Capture name is a substring of a pattern from : to /.
|
||||||
|
* The rest of the pattern is matched agains the request path directly
|
||||||
|
* Parameters are captured starting from the next character after
|
||||||
|
* the end of the last matched static pattern fragment until the next /.
|
||||||
|
*
|
||||||
|
* Example pattern:
|
||||||
|
* "/path/fragments/:capture/more/fragments/:second_capture"
|
||||||
|
* Static fragments:
|
||||||
|
* "/path/fragments/", "more/fragments/"
|
||||||
|
*
|
||||||
|
* Given the following request path:
|
||||||
|
* "/path/fragments/:1/more/fragments/:2"
|
||||||
|
* the resulting capture will be
|
||||||
|
* {{"capture", "1"}, {"second_capture", "2"}}
|
||||||
|
*/
|
||||||
|
class PathParamsMatcher : public MatcherBase {
|
||||||
|
public:
|
||||||
|
PathParamsMatcher(const std::string &pattern);
|
||||||
|
|
||||||
|
bool match(Request &request) const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
static constexpr char marker = ':';
|
||||||
|
// Treat segment separators as the end of path parameter capture
|
||||||
|
// Does not need to handle query parameters as they are parsed before path
|
||||||
|
// matching
|
||||||
|
static constexpr char separator = '/';
|
||||||
|
|
||||||
|
// Contains static path fragments to match against, excluding the '/' after
|
||||||
|
// path params
|
||||||
|
// Fragments are separated by path params
|
||||||
|
std::vector<std::string> static_fragments_;
|
||||||
|
// Stores the names of the path parameters to be used as keys in the
|
||||||
|
// Request::path_params map
|
||||||
|
std::vector<std::string> param_names_;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs std::regex_match on request path
|
||||||
|
* and stores the result in Request::matches
|
||||||
|
*
|
||||||
|
* Note that regex match is performed directly on the whole request.
|
||||||
|
* This means that wildcard patterns may match multiple path segments with /:
|
||||||
|
* "/begin/(.*)/end" will match both "/begin/middle/end" and "/begin/1/2/end".
|
||||||
|
*/
|
||||||
|
class RegexMatcher : public MatcherBase {
|
||||||
|
public:
|
||||||
|
RegexMatcher(const std::string &pattern) : regex_(pattern) {}
|
||||||
|
|
||||||
|
bool match(Request &request) const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::regex regex_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
class Server {
|
class Server {
|
||||||
public:
|
public:
|
||||||
using Handler = std::function<void(const Request &, Response &)>;
|
using Handler = std::function<void(const Request &, Response &)>;
|
||||||
@ -772,9 +845,14 @@ protected:
|
|||||||
size_t payload_max_length_ = CPPHTTPLIB_PAYLOAD_MAX_LENGTH;
|
size_t payload_max_length_ = CPPHTTPLIB_PAYLOAD_MAX_LENGTH;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
using Handlers = std::vector<std::pair<std::regex, Handler>>;
|
using Handlers =
|
||||||
|
std::vector<std::pair<std::unique_ptr<detail::MatcherBase>, Handler>>;
|
||||||
using HandlersForContentReader =
|
using HandlersForContentReader =
|
||||||
std::vector<std::pair<std::regex, HandlerWithContentReader>>;
|
std::vector<std::pair<std::unique_ptr<detail::MatcherBase>,
|
||||||
|
HandlerWithContentReader>>;
|
||||||
|
|
||||||
|
static std::unique_ptr<detail::MatcherBase>
|
||||||
|
make_matcher(const std::string &pattern);
|
||||||
|
|
||||||
socket_t create_server_socket(const std::string &host, int port,
|
socket_t create_server_socket(const std::string &host, int port,
|
||||||
int socket_flags,
|
int socket_flags,
|
||||||
@ -5147,6 +5225,99 @@ inline socket_t BufferStream::socket() const { return 0; }
|
|||||||
|
|
||||||
inline const std::string &BufferStream::get_buffer() const { return buffer; }
|
inline const std::string &BufferStream::get_buffer() const { return buffer; }
|
||||||
|
|
||||||
|
inline PathParamsMatcher::PathParamsMatcher(const std::string &pattern) {
|
||||||
|
// One past the last ending position of a path param substring
|
||||||
|
std::size_t last_param_end = 0;
|
||||||
|
|
||||||
|
#ifndef CPPHTTPLIB_NO_EXCEPTIONS
|
||||||
|
// Needed to ensure that parameter names are unique during matcher
|
||||||
|
// construction
|
||||||
|
// If exceptions are disabled, only last duplicate path
|
||||||
|
// parameter will be set
|
||||||
|
std::unordered_set<std::string> param_name_set;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const auto marker_pos = pattern.find(marker, last_param_end);
|
||||||
|
if (marker_pos == std::string::npos) { break; }
|
||||||
|
|
||||||
|
static_fragments_.push_back(
|
||||||
|
pattern.substr(last_param_end, marker_pos - last_param_end));
|
||||||
|
|
||||||
|
const auto param_name_start = marker_pos + 1;
|
||||||
|
|
||||||
|
auto sep_pos = pattern.find(separator, param_name_start);
|
||||||
|
if (sep_pos == std::string::npos) { sep_pos = pattern.length(); }
|
||||||
|
|
||||||
|
auto param_name =
|
||||||
|
pattern.substr(param_name_start, sep_pos - param_name_start);
|
||||||
|
|
||||||
|
#ifndef CPPHTTPLIB_NO_EXCEPTIONS
|
||||||
|
if (param_name_set.find(param_name) != param_name_set.cend()) {
|
||||||
|
std::string msg = "Encountered path parameter '" + param_name +
|
||||||
|
"' multiple times in route pattern '" + pattern + "'.";
|
||||||
|
throw std::invalid_argument(msg);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
param_names_.push_back(std::move(param_name));
|
||||||
|
|
||||||
|
last_param_end = sep_pos + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (last_param_end < pattern.length()) {
|
||||||
|
static_fragments_.push_back(pattern.substr(last_param_end));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool PathParamsMatcher::match(Request &request) const {
|
||||||
|
request.matches = {};
|
||||||
|
request.path_params.clear();
|
||||||
|
request.path_params.reserve(param_names_.size());
|
||||||
|
|
||||||
|
// One past the position at which the path matched the pattern last time
|
||||||
|
std::size_t starting_pos = 0;
|
||||||
|
for (size_t i = 0; i < static_fragments_.size(); ++i) {
|
||||||
|
const auto &fragment = static_fragments_[i];
|
||||||
|
|
||||||
|
if (starting_pos + fragment.length() > request.path.length()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Avoid unnecessary allocation by using strncmp instead of substr +
|
||||||
|
// comparison
|
||||||
|
if (std::strncmp(request.path.c_str() + starting_pos, fragment.c_str(),
|
||||||
|
fragment.length()) != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
starting_pos += fragment.length();
|
||||||
|
|
||||||
|
// Should only happen when we have a static fragment after a param
|
||||||
|
// Example: '/users/:id/subscriptions'
|
||||||
|
// The 'subscriptions' fragment here does not have a corresponding param
|
||||||
|
if (i >= param_names_.size()) { continue; }
|
||||||
|
|
||||||
|
auto sep_pos = request.path.find(separator, starting_pos);
|
||||||
|
if (sep_pos == std::string::npos) { sep_pos = request.path.length(); }
|
||||||
|
|
||||||
|
const auto ¶m_name = param_names_[i];
|
||||||
|
|
||||||
|
request.path_params.emplace(
|
||||||
|
param_name, request.path.substr(starting_pos, sep_pos - starting_pos));
|
||||||
|
|
||||||
|
// Mark everythin up to '/' as matched
|
||||||
|
starting_pos = sep_pos + 1;
|
||||||
|
}
|
||||||
|
// Returns false if the path is longer than the pattern
|
||||||
|
return starting_pos >= request.path.length();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool RegexMatcher::match(Request &request) const {
|
||||||
|
request.path_params.clear();
|
||||||
|
return std::regex_match(request.path, request.matches, regex_);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|
||||||
// HTTP server implementation
|
// HTTP server implementation
|
||||||
@ -5160,67 +5331,76 @@ inline Server::Server()
|
|||||||
|
|
||||||
inline Server::~Server() {}
|
inline Server::~Server() {}
|
||||||
|
|
||||||
|
inline std::unique_ptr<detail::MatcherBase>
|
||||||
|
Server::make_matcher(const std::string &pattern) {
|
||||||
|
if (pattern.find("/:") != std::string::npos) {
|
||||||
|
return detail::make_unique<detail::PathParamsMatcher>(pattern);
|
||||||
|
} else {
|
||||||
|
return detail::make_unique<detail::RegexMatcher>(pattern);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
inline Server &Server::Get(const std::string &pattern, Handler handler) {
|
inline Server &Server::Get(const std::string &pattern, Handler handler) {
|
||||||
get_handlers_.push_back(
|
get_handlers_.push_back(
|
||||||
std::make_pair(std::regex(pattern), std::move(handler)));
|
std::make_pair(make_matcher(pattern), std::move(handler)));
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline Server &Server::Post(const std::string &pattern, Handler handler) {
|
inline Server &Server::Post(const std::string &pattern, Handler handler) {
|
||||||
post_handlers_.push_back(
|
post_handlers_.push_back(
|
||||||
std::make_pair(std::regex(pattern), std::move(handler)));
|
std::make_pair(make_matcher(pattern), std::move(handler)));
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline Server &Server::Post(const std::string &pattern,
|
inline Server &Server::Post(const std::string &pattern,
|
||||||
HandlerWithContentReader handler) {
|
HandlerWithContentReader handler) {
|
||||||
post_handlers_for_content_reader_.push_back(
|
post_handlers_for_content_reader_.push_back(
|
||||||
std::make_pair(std::regex(pattern), std::move(handler)));
|
std::make_pair(make_matcher(pattern), std::move(handler)));
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline Server &Server::Put(const std::string &pattern, Handler handler) {
|
inline Server &Server::Put(const std::string &pattern, Handler handler) {
|
||||||
put_handlers_.push_back(
|
put_handlers_.push_back(
|
||||||
std::make_pair(std::regex(pattern), std::move(handler)));
|
std::make_pair(make_matcher(pattern), std::move(handler)));
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline Server &Server::Put(const std::string &pattern,
|
inline Server &Server::Put(const std::string &pattern,
|
||||||
HandlerWithContentReader handler) {
|
HandlerWithContentReader handler) {
|
||||||
put_handlers_for_content_reader_.push_back(
|
put_handlers_for_content_reader_.push_back(
|
||||||
std::make_pair(std::regex(pattern), std::move(handler)));
|
std::make_pair(make_matcher(pattern), std::move(handler)));
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline Server &Server::Patch(const std::string &pattern, Handler handler) {
|
inline Server &Server::Patch(const std::string &pattern, Handler handler) {
|
||||||
patch_handlers_.push_back(
|
patch_handlers_.push_back(
|
||||||
std::make_pair(std::regex(pattern), std::move(handler)));
|
std::make_pair(make_matcher(pattern), std::move(handler)));
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline Server &Server::Patch(const std::string &pattern,
|
inline Server &Server::Patch(const std::string &pattern,
|
||||||
HandlerWithContentReader handler) {
|
HandlerWithContentReader handler) {
|
||||||
patch_handlers_for_content_reader_.push_back(
|
patch_handlers_for_content_reader_.push_back(
|
||||||
std::make_pair(std::regex(pattern), std::move(handler)));
|
std::make_pair(make_matcher(pattern), std::move(handler)));
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline Server &Server::Delete(const std::string &pattern, Handler handler) {
|
inline Server &Server::Delete(const std::string &pattern, Handler handler) {
|
||||||
delete_handlers_.push_back(
|
delete_handlers_.push_back(
|
||||||
std::make_pair(std::regex(pattern), std::move(handler)));
|
std::make_pair(make_matcher(pattern), std::move(handler)));
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline Server &Server::Delete(const std::string &pattern,
|
inline Server &Server::Delete(const std::string &pattern,
|
||||||
HandlerWithContentReader handler) {
|
HandlerWithContentReader handler) {
|
||||||
delete_handlers_for_content_reader_.push_back(
|
delete_handlers_for_content_reader_.push_back(
|
||||||
std::make_pair(std::regex(pattern), std::move(handler)));
|
std::make_pair(make_matcher(pattern), std::move(handler)));
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline Server &Server::Options(const std::string &pattern, Handler handler) {
|
inline Server &Server::Options(const std::string &pattern, Handler handler) {
|
||||||
options_handlers_.push_back(
|
options_handlers_.push_back(
|
||||||
std::make_pair(std::regex(pattern), std::move(handler)));
|
std::make_pair(make_matcher(pattern), std::move(handler)));
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -5930,10 +6110,10 @@ inline bool Server::routing(Request &req, Response &res, Stream &strm) {
|
|||||||
inline bool Server::dispatch_request(Request &req, Response &res,
|
inline bool Server::dispatch_request(Request &req, Response &res,
|
||||||
const Handlers &handlers) {
|
const Handlers &handlers) {
|
||||||
for (const auto &x : handlers) {
|
for (const auto &x : handlers) {
|
||||||
const auto &pattern = x.first;
|
const auto &matcher = x.first;
|
||||||
const auto &handler = x.second;
|
const auto &handler = x.second;
|
||||||
|
|
||||||
if (std::regex_match(req.path, req.matches, pattern)) {
|
if (matcher->match(req)) {
|
||||||
handler(req, res);
|
handler(req, res);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -6055,10 +6235,10 @@ inline bool Server::dispatch_request_for_content_reader(
|
|||||||
Request &req, Response &res, ContentReader content_reader,
|
Request &req, Response &res, ContentReader content_reader,
|
||||||
const HandlersForContentReader &handlers) {
|
const HandlersForContentReader &handlers) {
|
||||||
for (const auto &x : handlers) {
|
for (const auto &x : handlers) {
|
||||||
const auto &pattern = x.first;
|
const auto &matcher = x.first;
|
||||||
const auto &handler = x.second;
|
const auto &handler = x.second;
|
||||||
|
|
||||||
if (std::regex_match(req.path, req.matches, pattern)) {
|
if (matcher->match(req)) {
|
||||||
handler(req, res, content_reader);
|
handler(req, res, content_reader);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
152
test/test.cc
152
test/test.cc
@ -4379,6 +4379,12 @@ TEST(GetWithParametersTest, GetWithParameters) {
|
|||||||
EXPECT_EQ("bar", req.get_param_value("param2"));
|
EXPECT_EQ("bar", req.get_param_value("param2"));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
svr.Get("/users/:id", [&](const Request &req, Response &) {
|
||||||
|
EXPECT_EQ("user-id", req.path_params.at("id"));
|
||||||
|
EXPECT_EQ("foo", req.get_param_value("param1"));
|
||||||
|
EXPECT_EQ("bar", req.get_param_value("param2"));
|
||||||
|
});
|
||||||
|
|
||||||
auto listen_thread = std::thread([&svr]() { svr.listen(HOST, PORT); });
|
auto listen_thread = std::thread([&svr]() { svr.listen(HOST, PORT); });
|
||||||
auto se = detail::scope_exit([&] {
|
auto se = detail::scope_exit([&] {
|
||||||
svr.stop();
|
svr.stop();
|
||||||
@ -4419,6 +4425,15 @@ TEST(GetWithParametersTest, GetWithParameters) {
|
|||||||
ASSERT_TRUE(res);
|
ASSERT_TRUE(res);
|
||||||
EXPECT_EQ(200, res->status);
|
EXPECT_EQ(200, res->status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Client cli(HOST, PORT);
|
||||||
|
|
||||||
|
auto res = cli.Get("/users/user-id?param1=foo¶m2=bar");
|
||||||
|
|
||||||
|
ASSERT_TRUE(res);
|
||||||
|
EXPECT_EQ(200, res->status);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(GetWithParametersTest, GetWithParameters2) {
|
TEST(GetWithParametersTest, GetWithParameters2) {
|
||||||
@ -6290,3 +6305,140 @@ TEST(VulnerabilityTest, CRLFInjection) {
|
|||||||
cli.Patch("/test4", "content", "text/plain\r\nevil: hello4");
|
cli.Patch("/test4", "content", "text/plain\r\nevil: hello4");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(PathParamsTest, StaticMatch) {
|
||||||
|
const auto pattern = "/users/all";
|
||||||
|
detail::PathParamsMatcher matcher(pattern);
|
||||||
|
|
||||||
|
Request request;
|
||||||
|
request.path = "/users/all";
|
||||||
|
ASSERT_TRUE(matcher.match(request));
|
||||||
|
|
||||||
|
std::unordered_map<std::string, std::string> expected_params = {};
|
||||||
|
|
||||||
|
EXPECT_EQ(request.path_params, expected_params);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(PathParamsTest, StaticMismatch) {
|
||||||
|
const auto pattern = "/users/all";
|
||||||
|
detail::PathParamsMatcher matcher(pattern);
|
||||||
|
|
||||||
|
Request request;
|
||||||
|
request.path = "/users/1";
|
||||||
|
ASSERT_FALSE(matcher.match(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(PathParamsTest, SingleParamInTheMiddle) {
|
||||||
|
const auto pattern = "/users/:id/subscriptions";
|
||||||
|
detail::PathParamsMatcher matcher(pattern);
|
||||||
|
|
||||||
|
Request request;
|
||||||
|
request.path = "/users/42/subscriptions";
|
||||||
|
ASSERT_TRUE(matcher.match(request));
|
||||||
|
|
||||||
|
std::unordered_map<std::string, std::string> expected_params = {{"id", "42"}};
|
||||||
|
|
||||||
|
EXPECT_EQ(request.path_params, expected_params);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(PathParamsTest, SingleParamInTheEnd) {
|
||||||
|
const auto pattern = "/users/:id";
|
||||||
|
detail::PathParamsMatcher matcher(pattern);
|
||||||
|
|
||||||
|
Request request;
|
||||||
|
request.path = "/users/24";
|
||||||
|
ASSERT_TRUE(matcher.match(request));
|
||||||
|
|
||||||
|
std::unordered_map<std::string, std::string> expected_params = {{"id", "24"}};
|
||||||
|
|
||||||
|
EXPECT_EQ(request.path_params, expected_params);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(PathParamsTest, SingleParamInTheEndTrailingSlash) {
|
||||||
|
const auto pattern = "/users/:id/";
|
||||||
|
detail::PathParamsMatcher matcher(pattern);
|
||||||
|
|
||||||
|
Request request;
|
||||||
|
request.path = "/users/42/";
|
||||||
|
ASSERT_TRUE(matcher.match(request));
|
||||||
|
std::unordered_map<std::string, std::string> expected_params = {{"id", "42"}};
|
||||||
|
|
||||||
|
EXPECT_EQ(request.path_params, expected_params);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(PathParamsTest, EmptyParam) {
|
||||||
|
const auto pattern = "/users/:id/";
|
||||||
|
detail::PathParamsMatcher matcher(pattern);
|
||||||
|
|
||||||
|
Request request;
|
||||||
|
request.path = "/users//";
|
||||||
|
ASSERT_TRUE(matcher.match(request));
|
||||||
|
|
||||||
|
std::unordered_map<std::string, std::string> expected_params = {{"id", ""}};
|
||||||
|
|
||||||
|
EXPECT_EQ(request.path_params, expected_params);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(PathParamsTest, FragmentMismatch) {
|
||||||
|
const auto pattern = "/users/:id/";
|
||||||
|
detail::PathParamsMatcher matcher(pattern);
|
||||||
|
|
||||||
|
Request request;
|
||||||
|
request.path = "/admins/24/";
|
||||||
|
ASSERT_FALSE(matcher.match(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(PathParamsTest, ExtraFragments) {
|
||||||
|
const auto pattern = "/users/:id";
|
||||||
|
detail::PathParamsMatcher matcher(pattern);
|
||||||
|
|
||||||
|
Request request;
|
||||||
|
request.path = "/users/42/subscriptions";
|
||||||
|
ASSERT_FALSE(matcher.match(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(PathParamsTest, MissingTrailingParam) {
|
||||||
|
const auto pattern = "/users/:id";
|
||||||
|
detail::PathParamsMatcher matcher(pattern);
|
||||||
|
|
||||||
|
Request request;
|
||||||
|
request.path = "/users";
|
||||||
|
ASSERT_FALSE(matcher.match(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(PathParamsTest, MissingParamInTheMiddle) {
|
||||||
|
const auto pattern = "/users/:id/subscriptions";
|
||||||
|
detail::PathParamsMatcher matcher(pattern);
|
||||||
|
|
||||||
|
Request request;
|
||||||
|
request.path = "/users/subscriptions";
|
||||||
|
ASSERT_FALSE(matcher.match(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(PathParamsTest, MultipleParams) {
|
||||||
|
const auto pattern = "/users/:userid/subscriptions/:subid";
|
||||||
|
detail::PathParamsMatcher matcher(pattern);
|
||||||
|
|
||||||
|
Request request;
|
||||||
|
request.path = "/users/42/subscriptions/2";
|
||||||
|
ASSERT_TRUE(matcher.match(request));
|
||||||
|
|
||||||
|
std::unordered_map<std::string, std::string> expected_params = {
|
||||||
|
{"userid", "42"}, {"subid", "2"}};
|
||||||
|
|
||||||
|
EXPECT_EQ(request.path_params, expected_params);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(PathParamsTest, SequenceOfParams) {
|
||||||
|
const auto pattern = "/values/:x/:y/:z";
|
||||||
|
detail::PathParamsMatcher matcher(pattern);
|
||||||
|
|
||||||
|
Request request;
|
||||||
|
request.path = "/values/1/2/3";
|
||||||
|
ASSERT_TRUE(matcher.match(request));
|
||||||
|
|
||||||
|
std::unordered_map<std::string, std::string> expected_params = {
|
||||||
|
{"x", "1"}, {"y", "2"}, {"z", "3"}};
|
||||||
|
|
||||||
|
EXPECT_EQ(request.path_params, expected_params);
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user