Files
Adminive/backend/service/include/adminive/adapters/drogon.hpp
T
2026-08-08 22:39:18 +08:00

296 lines
16 KiB
C++

#pragma once
#include "adminive/collection.hpp"
#include "adminive/http.hpp"
#include <drogon/drogon.h>
#include <charconv>
#include <cstdint>
#include <functional>
#include <memory>
#include <shared_mutex>
#include <string>
#include <utility>
#include <vector>
namespace adminive {
template <Json_Type Json>
drogon::HttpResponsePtr make_drogon_response(Http_Response<Json> value) {
auto response = drogon::HttpResponse::newHttpResponse();
response->setStatusCode(static_cast<drogon::HttpStatusCode>(value.status));
response->setContentTypeCode(drogon::CT_APPLICATION_JSON);
response->setBody(dump_json(value.body, 2));
return response;
}
using Drogon_Executor = std::function<void(std::function<void()>)>;
using Drogon_Request_Context_Factory = std::function<Request_Context(const drogon::HttpRequestPtr&)>;
struct Drogon_Bind_Options {
Drogon_Executor executor;
Drogon_Request_Context_Factory context_factory;
std::vector<std::string> filters;
};
inline Request_Context make_drogon_request_context(const drogon::HttpRequestPtr& request, const Drogon_Request_Context_Factory& factory) {
return factory ? factory(request) : Request_Context{};
}
template <Json_Type Json>
bool resolve_drogon_request_context(const drogon::HttpRequestPtr& request, const Drogon_Request_Context_Factory& factory, Request_Context& context, std::function<void(const drogon::HttpResponsePtr&)>& callback) noexcept {
try {
context = make_drogon_request_context(request, factory);
return true;
} catch(const std::exception& error) {
callback(make_drogon_response(make_http_error<Json>(500, error.what())));
} catch(...) {
callback(make_drogon_response(make_http_error<Json>(500, "unknown server error")));
}
return false;
}
inline std::vector<drogon::internal::HttpConstraint> make_drogon_constraints(drogon::HttpMethod method, const std::vector<std::string>& filters) {
std::vector<drogon::internal::HttpConstraint> result;
result.reserve(filters.size() + 1);
result.emplace_back(method);
for(const auto& filter : filters) {
result.emplace_back(filter);
}
return result;
}
inline std::size_t read_drogon_size_parameter(const drogon::HttpRequestPtr& request, const char* name, std::size_t fallback) {
const std::string& text = request->getParameter(name);
if(text.empty()) {
return fallback;
}
std::size_t value{};
const auto [end, error] = std::from_chars(text.data(), text.data() + text.size(), value);
return error == std::errc{} && end == text.data() + text.size() && value != 0 ? value : fallback;
}
template <Json_Type Json, class Function>
void complete_drogon_request(std::function<void(const drogon::HttpResponsePtr&)>& callback, Function&& function) noexcept {
try {
callback(make_drogon_response(std::forward<Function>(function)()));
} catch(const std::exception& error) {
callback(make_drogon_response(make_http_error<Json>(500, error.what())));
} catch(...) {
callback(make_drogon_response(make_http_error<Json>(500, "unknown server error")));
}
}
template <Json_Type Json, class Function>
void schedule_drogon_request(const Drogon_Executor& executor, std::function<void(const drogon::HttpResponsePtr&)>&& callback, Function&& function) noexcept {
auto response_callback = std::make_shared<std::function<void(const drogon::HttpResponsePtr&)>>(std::move(callback));
std::function<void()> task = [response_callback, function = std::forward<Function>(function)]() mutable {
complete_drogon_request<Json>(*response_callback, std::move(function));
};
try {
if(executor) {
executor(task);
} else {
task();
}
} catch(const std::exception& error) {
(*response_callback)(make_drogon_response(make_http_error<Json>(500, error.what())));
} catch(...) {
(*response_callback)(make_drogon_response(make_http_error<Json>(500, "failed to schedule request")));
}
}
template <class T, Json_Type Json, Basic_Lock Lock = std::shared_mutex>
requires Writable_Adapted_Object<T> && std::copy_constructible<Object_Model_Type<T>>
class Drogon_Resource {
public:
using Service = Resource_Service<T, Json, Lock>;
using Transaction = typename Service::Transaction;
Drogon_Resource(T& value, std::string path, Transaction transaction = {}, Lock* shared_lock = nullptr) : service_(std::make_shared<Service>(value, std::move(path), std::move(transaction), shared_lock)) {}
template <structive::Synchronization_Policy Policy>
explicit Drogon_Resource(Managed_Value<std::remove_cvref_t<T>, Policy>& value, std::string path, Transaction transaction = {}) : service_(std::make_shared<Service>(value, std::move(path), std::move(transaction))) {}
template <class Managed, std::size_t Root_Index, auto... Members>
requires (!std::is_const_v<Managed>) && std::same_as<typename Managed_Field<Managed, Root_Index, Members...>::value_type, std::remove_cvref_t<T>>
explicit Drogon_Resource(Managed_Field<Managed, Root_Index, Members...> value, std::string path, Transaction transaction = {}) : service_(std::make_shared<Service>(value, std::move(path), std::move(transaction))) {}
Json view_schema() const {
return service_->view_schema();
}
Json amis_schema() const {
return service_->amis_schema();
}
void bind(drogon::HttpAppFramework& app, Drogon_Bind_Options options = {}) const {
const auto service = service_;
const auto get_constraints = make_drogon_constraints(drogon::Get, options.filters);
const auto post_constraints = make_drogon_constraints(drogon::Post, options.filters);
app.registerHandler(service->path() + "/descriptor", [service](const drogon::HttpRequestPtr&, std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
complete_drogon_request<Json>(callback, [service] {
return service->descriptor_response();
});
}, get_constraints);
app.registerHandler(service->path() + "/view", [service](const drogon::HttpRequestPtr&, std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
complete_drogon_request<Json>(callback, [service] {
return service->view_response();
});
}, get_constraints);
app.registerHandler(service->path() + "/data", [service](const drogon::HttpRequestPtr&, std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
complete_drogon_request<Json>(callback, [service] {
return service->data_response();
});
}, get_constraints);
app.registerHandler(service->path() + "/amis", [service](const drogon::HttpRequestPtr&, std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
complete_drogon_request<Json>(callback, [service] {
return service->amis_response();
});
}, get_constraints);
app.registerHandler(service->path() + "/data", [service, options](const drogon::HttpRequestPtr& request, std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
Request_Context context;
if(!resolve_drogon_request_context<Json>(request, options.context_factory, context, callback)) {
return;
}
schedule_drogon_request<Json>(options.executor, std::move(callback), [service, body = std::string(request->getBody()), context] {
return service->update_response(body, context);
});
}, post_constraints);
}
private:
std::shared_ptr<Service> service_;
};
template <Described_Type T, Json_Type Json, Basic_Lock Lock = std::mutex>
requires Table_View_Described_Type<T> && std::default_initializable<T> && std::copy_constructible<T> && std::assignable_from<T&, T>
class Drogon_Collection_Resource {
public:
using Service = Collection_Service<T, Json, Lock>;
explicit Drogon_Collection_Resource(std::string path, std::vector<T> initial = {}) : service_(std::make_shared<Service>(std::move(path), std::move(initial))) {}
template <Described_Type Status>
void register_overview_status(std::string api, std::uint64_t interval = 2000) {
service_->template register_overview_status<Status>(std::move(api), interval);
}
template <auto Function>
void register_status(std::uint64_t interval = 2000) {
service_->template register_status<Function>(interval);
}
std::size_t size() const {
return service_->size();
}
Json items_json() const {
return service_->items_json();
}
Json view_schema() const {
return service_->view_schema();
}
Json amis_schema() const {
return service_->amis_schema();
}
void bind(drogon::HttpAppFramework& app, Drogon_Bind_Options options = {}) const {
const auto service = service_;
const auto get_constraints = make_drogon_constraints(drogon::Get, options.filters);
const auto post_constraints = make_drogon_constraints(drogon::Post, options.filters);
const auto put_constraints = make_drogon_constraints(drogon::Put, options.filters);
const auto patch_constraints = make_drogon_constraints(drogon::Patch, options.filters);
const auto delete_constraints = make_drogon_constraints(drogon::Delete, options.filters);
app.registerHandler(service->path() + "/descriptor", [service](const drogon::HttpRequestPtr&, std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
complete_drogon_request<Json>(callback, [service] {
return service->descriptor_response();
});
}, get_constraints);
app.registerHandler(service->path() + "/view", [service](const drogon::HttpRequestPtr&, std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
complete_drogon_request<Json>(callback, [service] {
return service->view_response();
});
}, get_constraints);
app.registerHandler(service->path() + "/amis", [service](const drogon::HttpRequestPtr&, std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
complete_drogon_request<Json>(callback, [service] {
return service->amis_response();
});
}, get_constraints);
if(service->has_status()) {
app.registerHandler(service->path() + "/status/descriptor", [service](const drogon::HttpRequestPtr&, std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
complete_drogon_request<Json>(callback, [service] {
return service->status_descriptor_response();
});
}, get_constraints);
app.registerHandler(service->path() + "/{1:id}/status", [service, options](const drogon::HttpRequestPtr&, std::function<void(const drogon::HttpResponsePtr&)>&& callback, std::uint64_t id) {
schedule_drogon_request<Json>(options.executor, std::move(callback), [service, id] {
return service->status_response(id);
});
}, get_constraints);
}
app.registerHandler(service->path(), [service](const drogon::HttpRequestPtr& request, std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
Collection_Query query;
query.page = read_drogon_size_parameter(request, "page", 1);
query.per_page = read_drogon_size_parameter(request, "perPage", 20);
query.order_by = request->getParameter("orderBy");
query.order_dir = request->getParameter("orderDir");
for(const auto& [name, value] : request->getParameters()) {
if(name == "page" || name == "perPage" || name == "orderBy" || name == "orderDir") {
continue;
}
query.fields.insert_or_assign(name, value);
}
complete_drogon_request<Json>(callback, [service, query = std::move(query)]() mutable {
return service->list_response(std::move(query));
});
}, get_constraints);
app.registerHandler(service->path() + "/{1:id}", [service](const drogon::HttpRequestPtr&, std::function<void(const drogon::HttpResponsePtr&)>&& callback, std::uint64_t id) {
complete_drogon_request<Json>(callback, [service, id] {
return service->item_response(id);
});
}, get_constraints);
app.registerHandler(service->path(), [service, options](const drogon::HttpRequestPtr& request, std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
schedule_drogon_request<Json>(options.executor, std::move(callback), [service, body = std::string(request->getBody())] {
return service->create_response(body);
});
}, post_constraints);
if(service->user_reorderable()) {
app.registerHandler(service->path() + "/order", [service, options](const drogon::HttpRequestPtr& request, std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
schedule_drogon_request<Json>(options.executor, std::move(callback), [service, body = std::string(request->getBody())] {
return service->reorder_response(body);
});
}, post_constraints);
}
const auto bind_update = [service, options](const drogon::HttpRequestPtr& request, std::function<void(const drogon::HttpResponsePtr&)>&& callback, std::uint64_t id) {
schedule_drogon_request<Json>(options.executor, std::move(callback), [service, id, body = std::string(request->getBody())] {
return service->update_response(id, body);
});
};
app.registerHandler(service->path() + "/{1:id}", bind_update, put_constraints);
app.registerHandler(service->path() + "/{1:id}", bind_update, patch_constraints);
app.registerHandler(service->path() + "/{1:id}", [service, options](const drogon::HttpRequestPtr&, std::function<void(const drogon::HttpResponsePtr&)>&& callback, std::uint64_t id) {
schedule_drogon_request<Json>(options.executor, std::move(callback), [service, id] {
return service->delete_response(id);
});
}, delete_constraints);
}
private:
std::shared_ptr<Service> service_;
};
template <Described_Type Status, Json_Type Json>
class Drogon_Status_Resource {
public:
using Reader = std::function<Status(const Request_Context&)>;
Drogon_Status_Resource(std::string path, Reader reader, std::uint64_t interval = 2000) : state_(std::make_shared<State>(State{std::move(path), std::move(reader), interval, to_status_descriptor_json<Json, Status>()})) {}
Json amis_schema() const {
return make_amis_status_service<Json>(state_->descriptor, state_->path + "/data", state_->interval);
}
void bind(drogon::HttpAppFramework& app, Drogon_Bind_Options options = {}) const {
const auto state = state_;
const auto constraints = make_drogon_constraints(drogon::Get, options.filters);
app.registerHandler(state->path + "/descriptor", [state](const drogon::HttpRequestPtr&, std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
complete_drogon_request<Json>(callback, [state] {
return make_http_success<Json>(state->descriptor);
});
}, constraints);
app.registerHandler(state->path + "/amis", [state](const drogon::HttpRequestPtr&, std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
complete_drogon_request<Json>(callback, [state] {
return make_http_success<Json>(make_amis_status_service<Json>(state->descriptor, state->path + "/data", state->interval));
});
}, constraints);
app.registerHandler(state->path + "/data", [state, options](const drogon::HttpRequestPtr& request, std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
Request_Context context;
if(!resolve_drogon_request_context<Json>(request, options.context_factory, context, callback)) {
return;
}
schedule_drogon_request<Json>(options.executor, std::move(callback), [state, context] {
return make_http_success<Json>(to_status_json<Json>(state->reader(context)));
});
}, constraints);
}
private:
struct State {
std::string path;
Reader reader;
std::uint64_t interval;
Json descriptor;
};
std::shared_ptr<State> state_;
};
}