Files
retoor 3c4334cec9 feat: implement MoloApp plans 01-15 with integration
Full C++23 framework tree: runtime, observability, security,
data, app, realtime, supervisor, clients, CLI, example plus
enterprise tenancy, audit, operability, governance,
supplychain. Postgres via docker compose for tests.
2026-10-23 18:20:04 +02:00

124 lines
4.0 KiB
C++

#include "App.hpp"
#include <stdexcept>
#include "Caps.hpp"
namespace molo::example::fw {
HttpError::HttpError(int status, std::string code, std::string message)
: status_(status), code_(std::move(code)), message_(std::move(message)) {}
int HttpError::status() const { return status_; }
const std::string& HttpError::code() const { return code_; }
const std::string& HttpError::message() const { return message_; }
void Application::use(MiddlewareSlot slot, Middleware middleware) {
if (has_middleware_ && slot < last_slot_)
throw std::runtime_error("middleware category order violated");
last_slot_ = slot;
has_middleware_ = true;
middlewares_.emplace_back(slot, std::move(middleware));
}
void Application::include_router(const std::string& prefix,
const Router& router,
const std::string& name_prefix) {
router_.include_router(prefix, router, name_prefix);
}
void Application::mount(const std::string& path,
std::shared_ptr<Application> sub) {
mounts_.emplace_back(path, std::move(sub));
}
Response Application::dispatch(Request req) {
if (req.body.size() > caps::kMaxRequestBody)
return TextResponse("body too large", 413);
try {
return run_chain(std::move(req), 0);
} catch (const HttpError& e) {
std::string body = "{\"error\":\"" + json_escape(e.code()) +
"\",\"message\":\"" + json_escape(e.message()) + "\"}";
return JsonResponse(body, e.status());
} catch (const std::exception& e) {
return JsonResponse("{\"error\":\"internal\",\"message\":\"" +
json_escape(e.what()) + "\"}",
500);
}
}
Response Application::run_chain(Request req, int depth) {
Next next = [this, depth](Request r) { return dispatch_inner(r, depth); };
for (std::size_t i = middlewares_.size(); i > 0; --i) {
Middleware mw = middlewares_[i - 1].second;
Next inner = next;
next = [mw, inner](Request r) { return mw(std::move(r), inner); };
}
return next(std::move(req));
}
Response Application::dispatch_inner(Request req, int depth) {
if (depth > caps::kMaxMountDepth)
throw HttpError(500, "mount_depth", "sub-application nesting too deep");
std::string best;
std::shared_ptr<Application> target;
for (auto& [prefix, sub] : mounts_) {
if (req.path.size() >= prefix.size() &&
req.path.compare(0, prefix.size(), prefix) == 0 &&
prefix.size() > best.size()) {
best = prefix;
target = sub;
}
}
if (target) {
req.path = req.path.substr(best.size());
if (req.path.empty() || req.path.front() != '/') req.path = "/" + req.path;
return target->run_chain(std::move(req), depth + 1);
}
Handler handler;
if (router_.match(req, handler)) return handler(std::move(req));
bool path_exists = false;
for (const auto& r : router_.routes()) {
std::map<std::string, std::string> params;
if (match_pattern(r.pattern, req.path, params)) {
path_exists = true;
break;
}
}
if (path_exists)
throw HttpError(405, "method_not_allowed", "method not allowed");
throw HttpError(404, "not_found", "no route for " + req.path);
}
const std::vector<Route>& Application::routes() const {
return router_.routes();
}
Middleware errors_middleware() {
return [](Request req, Next next) -> Response {
try {
return next(std::move(req));
} catch (const HttpError& e) {
std::string body = "{\"error\":\"" + json_escape(e.code()) +
"\",\"message\":\"" + json_escape(e.message()) +
"\"}";
return JsonResponse(body, e.status());
}
};
}
Middleware request_id_middleware() {
return [](Request req, Next next) -> Response {
if (req.correlation_id.empty()) {
static unsigned long counter = 1;
req.correlation_id = "req-" + std::to_string(counter++);
}
Response res = next(req);
res.headers.set("X-Request-ID", req.correlation_id);
return res;
};
}
} // namespace molo::example::fw