Files
Adminive/backend/service/include/adminive/adapters/httplib.hpp
T
2026-08-08 21:45:29 +08:00

238 lines
12 KiB
C++

#pragma once
#include "adminive/collection.hpp"
#include "adminive/http.hpp"
#include "httplib.h"
#include <charconv>
#include <concepts>
#include <cstdint>
#include <functional>
#include <memory>
#include <mutex>
#include <shared_mutex>
#include <string>
#include <string_view>
#include <type_traits>
#include <utility>
#include <vector>
namespace adminive {
using Httplib_Request_Context_Factory = std::function<Request_Context(const httplib::Request&)>;
struct Httplib_Bind_Options {
Httplib_Request_Context_Factory context_factory;
};
template <Json_Type Json>
void write_http_json(httplib::Response& response, const Json& value) {
response.set_content(dump_json(value, 2), "application/json; charset=utf-8");
}
template <Json_Type Json>
void write_http_response(httplib::Response& response, Http_Response<Json> value) {
response.status = value.status;
write_http_json(response, value.body);
}
template <Json_Type Json>
void write_http_error(httplib::Response& response, int status, const Update_Result& update) {
write_http_response(response, make_http_error<Json>(status, update));
}
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 Http_Resource {
public:
using Service = Resource_Service<T, Json, Lock>;
using Transaction = typename Service::Transaction;
Http_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 Http_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 Http_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(httplib::Server& server) const {
bind(server, {});
}
void bind(httplib::Server& server, Httplib_Bind_Options options) const {
const auto service = service_;
const auto context_factory = std::move(options.context_factory);
server.Get(service->path() + "/descriptor", [service](const httplib::Request&, httplib::Response& response) {
write_http_response(response, service->descriptor_response());
});
server.Get(service->path() + "/view", [service](const httplib::Request&, httplib::Response& response) {
write_http_response(response, service->view_response());
});
server.Get(service->path() + "/data", [service](const httplib::Request&, httplib::Response& response) {
write_http_response(response, service->data_response());
});
server.Get(service->path() + "/amis", [service](const httplib::Request&, httplib::Response& response) {
write_http_response(response, service->amis_response());
});
server.Post(service->path() + "/data", [service, context_factory](const httplib::Request& request, httplib::Response& response) {
try {
const Request_Context context = context_factory ? context_factory(request) : Request_Context{};
write_http_response(response, service->update_response(request.body, context));
} catch(const std::exception& error) {
write_http_response(response, make_http_error<Json>(500, error.what()));
} catch(...) {
write_http_response(response, make_http_error<Json>(500, "unknown server error"));
}
});
}
private:
std::shared_ptr<Service> service_;
};
template <Described_Type Status, Json_Type Json>
class Http_Status_Resource {
public:
using Reader = std::function<Status(const Request_Context&)>;
Http_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(httplib::Server& server) const {
bind(server, {});
}
void bind(httplib::Server& server, Httplib_Bind_Options options) const {
const auto state = state_;
const auto context_factory = std::move(options.context_factory);
server.Get(state->path + "/descriptor", [state](const httplib::Request&, httplib::Response& response) {
write_http_response(response, make_http_success<Json>(state->descriptor));
});
server.Get(state->path + "/amis", [state](const httplib::Request&, httplib::Response& response) {
write_http_response(response, make_http_success<Json>(make_amis_status_service<Json>(state->descriptor, state->path + "/data", state->interval)));
});
server.Get(state->path + "/data", [state, context_factory](const httplib::Request& request, httplib::Response& response) {
try {
const Request_Context context = context_factory ? context_factory(request) : Request_Context{};
write_http_response(response, make_http_success<Json>(to_status_json<Json>(state->reader(context))));
} catch(const std::exception& error) {
write_http_response(response, make_http_error<Json>(500, error.what()));
} catch(...) {
write_http_response(response, make_http_error<Json>(500, "unknown server error"));
}
});
}
private:
struct State {
std::string path;
Reader reader;
std::uint64_t interval;
Json descriptor;
};
std::shared_ptr<State> state_;
};
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 Http_Collection_Resource {
public:
using Service = Collection_Service<T, Json, Lock>;
explicit Http_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(httplib::Server& server) const {
const auto service = service_;
server.Get(service->path() + "/descriptor", [service](const httplib::Request&, httplib::Response& response) {
write_http_response(response, service->descriptor_response());
});
server.Get(service->path() + "/view", [service](const httplib::Request&, httplib::Response& response) {
write_http_response(response, service->view_response());
});
server.Get(service->path() + "/amis", [service](const httplib::Request&, httplib::Response& response) {
write_http_response(response, service->amis_response());
});
if(service->has_status()) {
server.Get(service->path() + "/status/descriptor", [service](const httplib::Request&, httplib::Response& response) {
write_http_response(response, service->status_descriptor_response());
});
server.Get(status_pattern(service->path()), [service](const httplib::Request& request, httplib::Response& response) {
write_http_response(response, service->status_response(read_route_id(request)));
});
}
server.Get(service->path(), [service](const httplib::Request& request, httplib::Response& response) {
Collection_Query query;
query.page = read_size_parameter(request, "page", 1);
query.per_page = read_size_parameter(request, "perPage", 20);
if(request.has_param("orderBy")) {
query.order_by = request.get_param_value("orderBy");
query.order_dir = request.has_param("orderDir") ? request.get_param_value("orderDir") : "asc";
}
for(const auto& [name, value] : request.params) {
if(name == "page" || name == "perPage" || name == "orderBy" || name == "orderDir") {
continue;
}
query.fields.insert_or_assign(name, value);
}
write_http_response(response, service->list_response(std::move(query)));
});
server.Get(item_pattern(service->path()), [service](const httplib::Request& request, httplib::Response& response) {
write_http_response(response, service->item_response(read_route_id(request)));
});
server.Post(service->path(), [service](const httplib::Request& request, httplib::Response& response) {
write_http_response(response, service->create_response(request.body));
});
if(service->user_reorderable()) {
server.Post(service->path() + "/order", [service](const httplib::Request& request, httplib::Response& response) {
write_http_response(response, service->reorder_response(request.body));
});
}
server.Put(item_pattern(service->path()), [service](const httplib::Request& request, httplib::Response& response) {
write_http_response(response, service->update_response(read_route_id(request), request.body));
});
server.Patch(item_pattern(service->path()), [service](const httplib::Request& request, httplib::Response& response) {
write_http_response(response, service->update_response(read_route_id(request), request.body));
});
server.Delete(item_pattern(service->path()), [service](const httplib::Request& request, httplib::Response& response) {
write_http_response(response, service->delete_response(read_route_id(request)));
});
}
private:
static std::size_t read_size_parameter(const httplib::Request& request, const char* name, std::size_t fallback) {
if(!request.has_param(name)) {
return fallback;
}
const auto text = request.get_param_value(name);
std::size_t value{};
const auto [end, error] = std::from_chars(text.data(), text.data() + text.size(), value);
if(error != std::errc{} || end != text.data() + text.size() || value == 0) {
return fallback;
}
return value;
}
static std::uint64_t read_route_id(const httplib::Request& request) {
const std::string text = request.matches[1].str();
std::uint64_t id{};
const auto [end, error] = std::from_chars(text.data(), text.data() + text.size(), id);
if(error != std::errc{} || end != text.data() + text.size()) {
return 0;
}
return id;
}
static std::string item_pattern(const std::string& path) {
return path + R"(/(\d+))";
}
static std::string status_pattern(const std::string& path) {
return path + R"(/(\d+)/status)";
}
std::shared_ptr<Service> service_;
};
}