Follow up to Adding Lambda to Path
Removed RegEx fro the class.
Changed the behavior slightly. The named matching section {name}
now matches anything. But there must be at least one character between each named matching section.
PathMatcher.h
#ifndef THORSANVIL_NISSE_NISSEHTTP_PATH_MATCHER_H
#define THORSANVIL_NISSE_NISSEHTTP_PATH_MATCHER_H
#include "Util.h"
#include <map>
#include <vector>
#include <string>
#include <functional>
namespace ThorsAnvil::Nisse::HTTP
{
class Request;
class Response;
using Match = std::map<std::string, std::string>;
class PathMatcher
{
using Action = std::function<void(Match const&, Request&, Response&)>;
using NameList = std::vector<std::string>;
using MatchList = std::vector<std::string>;
struct MatchInfo
{
Method method;
MatchList matchSections;
NameList names;
Action action;
};
std::vector<MatchInfo> paths;
public:
void addPath(Method method, std::string pathMatch, Action&& action);
bool findMatch(std::string_view path, Request& request, Response& response);
private:
bool checkPathMatch(MatchInfo const& pathMatchInfo, std::string_view path, Request& request, Response& response);
};
}
#endif
PathMatcher.cpp
#include "PathMatcher.h"
#include "Request.h"
#include <ThorsLogging/ThorsLogging.h>
using namespace ThorsAnvil::Nisse::HTTP;
void PathMatcher::addPath(Method method, std::string pathMatch, Action&& action)
{
MatchList matchSections;
NameList names;
std::size_t prefix = 0;
std::size_t nameBeg = 0;
std::size_t nameEnd = 0;
std::size_t size = pathMatch.size();
bool first = true;
while (prefix != size)
{
nameBeg = std::min(size, pathMatch.find('{', prefix));
nameEnd = std::min(size, pathMatch.find('}', nameBeg));
if (!first && prefix == nameBeg) {
ThorsLogAndThrow("ThorsAnvil::Nisse::HTPP::PathMatcher", "addPath", "Invalid 'pathMatch' string. Multiple name sections with no gap");
}
matchSections.emplace_back(pathMatch.substr(prefix, nameBeg - prefix));
first = false;
if (nameBeg == size) {
break;
}
if (nameEnd == size) {
ThorsLogAndThrow("ThorsAnvil::Nisse::HTPP::PathMatcher", "addPath", "Invalid 'pathMatch' string. Badly nested braces.");
}
if (nameBeg + 1 == nameEnd) {
ThorsLogAndThrow("ThorsAnvil::Nisse::HTPP::PathMatcher", "addPath", "Invalid 'pathMatch' string. Name section with no name");
}
names.emplace_back(pathMatch.substr(nameBeg + 1, nameEnd - nameBeg - 1));
prefix = nameEnd + 1;
}
if (nameBeg != size) {
matchSections.emplace_back("");
}
paths.emplace_back(method, std::move(matchSections), std::move(names), std::move(action));
}
bool PathMatcher::checkPathMatch(MatchInfo const& pathMatchInfo, std::string_view path, Request& request, Response& response)
{
if (pathMatchInfo.method != request.getMethod()) {
return false;
}
Match result;
std::string_view prefix = path.substr(0, pathMatchInfo.matchSections[0].size());
path.remove_prefix(pathMatchInfo.matchSections[0].size());
if (pathMatchInfo.matchSections[0] != prefix) {
return false;
}
for (std::size_t loop = 1; loop < pathMatchInfo.matchSections.size(); ++loop)
{
auto find = pathMatchInfo.matchSections[loop] == "" ? path.size() : path.find(pathMatchInfo.matchSections[loop]);
if (find == std::string::npos) {
return false;
}
result.emplace(pathMatchInfo.names[loop - 1], path.substr(0, find));
path.remove_prefix(find);
path.remove_prefix(pathMatchInfo.matchSections[loop].size());
}
if (!path.empty()) {
return false;
}
pathMatchInfo.action(result, request, response);
return true;
}
bool PathMatcher::findMatch(std::string_view path, Request& request, Response& response)
{
for (auto const& pathMatchInfo: paths)
{
if (checkPathMatch(pathMatchInfo, path, request, response)) {
return true;
}
}
return false;
}