拆分核心库和服务
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
#pragma once
|
||||
#include "adminive/adapter.hpp"
|
||||
#include <boost/pfr.hpp>
|
||||
#if __has_include(<boost/pfr/core_name.hpp>)
|
||||
#include <boost/pfr/core_name.hpp>
|
||||
#define ADMINIVE_BOOST_PFR_HAS_NAMES 1
|
||||
#else
|
||||
#define ADMINIVE_BOOST_PFR_HAS_NAMES 0
|
||||
#endif
|
||||
#include <cstddef>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
namespace adminive {
|
||||
template <class T>
|
||||
struct Boost_Pfr_Name_Adapter;
|
||||
namespace detail {
|
||||
template <class T, std::size_t Index>
|
||||
constexpr std::string_view pfr_name() noexcept {
|
||||
#if ADMINIVE_BOOST_PFR_HAS_NAMES
|
||||
return boost::pfr::get_name<Index, T>();
|
||||
#else
|
||||
return Boost_Pfr_Name_Adapter<T>::template name<Index>();
|
||||
#endif
|
||||
}
|
||||
template <std::size_t Index, class Derived, class Base, class... Bases>
|
||||
decltype(auto) pfr_base_get(Derived& value) noexcept {
|
||||
constexpr std::size_t count = boost::pfr::tuple_size_v<Base>;
|
||||
if constexpr(Index < count) {
|
||||
return boost::pfr::get<Index>(static_cast<Base&>(value));
|
||||
} else {
|
||||
static_assert(sizeof...(Bases) != 0);
|
||||
return pfr_base_get<Index - count, Derived, Bases...>(value);
|
||||
}
|
||||
}
|
||||
template <std::size_t Index, class Derived, class Base, class... Bases>
|
||||
decltype(auto) pfr_base_get(const Derived& value) noexcept {
|
||||
constexpr std::size_t count = boost::pfr::tuple_size_v<Base>;
|
||||
if constexpr(Index < count) {
|
||||
return boost::pfr::get<Index>(static_cast<const Base&>(value));
|
||||
} else {
|
||||
static_assert(sizeof...(Bases) != 0);
|
||||
return pfr_base_get<Index - count, Derived, Bases...>(value);
|
||||
}
|
||||
}
|
||||
template <std::size_t Index, class Base, class... Bases>
|
||||
constexpr std::string_view pfr_base_name() noexcept {
|
||||
constexpr std::size_t count = boost::pfr::tuple_size_v<Base>;
|
||||
if constexpr(Index < count) {
|
||||
return pfr_name<Base, Index>();
|
||||
} else {
|
||||
static_assert(sizeof...(Bases) != 0);
|
||||
return pfr_base_name<Index - count, Bases...>();
|
||||
}
|
||||
}
|
||||
}
|
||||
template <class T>
|
||||
struct Boost_Pfr_Reflection_Adapter {
|
||||
static constexpr std::size_t field_count = boost::pfr::tuple_size_v<T>;
|
||||
template <std::size_t Index>
|
||||
static decltype(auto) get(T& value) noexcept {
|
||||
return boost::pfr::get<Index>(value);
|
||||
}
|
||||
template <std::size_t Index>
|
||||
static decltype(auto) get(const T& value) noexcept {
|
||||
return boost::pfr::get<Index>(value);
|
||||
}
|
||||
template <std::size_t Index>
|
||||
static constexpr std::string_view name() noexcept {
|
||||
return detail::pfr_name<T, Index>();
|
||||
}
|
||||
};
|
||||
template <class Derived, class... Bases>
|
||||
struct Boost_Pfr_Base_Reflection_Adapter {
|
||||
static_assert(sizeof...(Bases) != 0);
|
||||
static_assert((std::is_base_of_v<Bases, Derived> && ...));
|
||||
static constexpr std::size_t field_count = (boost::pfr::tuple_size_v<Bases> + ... + 0);
|
||||
template <std::size_t Index>
|
||||
static decltype(auto) get(Derived& value) noexcept {
|
||||
static_assert(Index < field_count);
|
||||
return detail::pfr_base_get<Index, Derived, Bases...>(value);
|
||||
}
|
||||
template <std::size_t Index>
|
||||
static decltype(auto) get(const Derived& value) noexcept {
|
||||
static_assert(Index < field_count);
|
||||
return detail::pfr_base_get<Index, Derived, Bases...>(value);
|
||||
}
|
||||
template <std::size_t Index>
|
||||
static constexpr std::string_view name() noexcept {
|
||||
static_assert(Index < field_count);
|
||||
return detail::pfr_base_name<Index, Bases...>();
|
||||
}
|
||||
};
|
||||
}
|
||||
#undef ADMINIVE_BOOST_PFR_HAS_NAMES
|
||||
@@ -0,0 +1,267 @@
|
||||
#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{};
|
||||
}
|
||||
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 <Basic_Lock Field_Lock>
|
||||
explicit Drogon_Resource(Synchronized_Value<std::remove_cvref_t<T>, Lock, Field_Lock>& value, std::string path, Transaction transaction = {}) : service_(std::make_shared<Service>(value, std::move(path), std::move(transaction))) {}
|
||||
template <class Root, Basic_Lock Field_Lock, auto... Members>
|
||||
requires (!std::is_const_v<Root>) && std::same_as<typename Synchronized_Field<Root, Lock, Field_Lock, Members...>::value_type, std::remove_cvref_t<T>>
|
||||
explicit Drogon_Resource(Synchronized_Field<Root, Lock, Field_Lock, Members...> value, std::string path, Transaction transaction = {}, Resource_Lock_Scope lock_scope = Resource_Lock_Scope::local) : service_(std::make_shared<Service>(value, std::move(path), std::move(transaction), lock_scope)) {}
|
||||
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() + "/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;
|
||||
try {
|
||||
context = make_drogon_request_context(request, options.context_factory);
|
||||
} catch(const std::exception& error) {
|
||||
callback(make_drogon_response(make_http_error<Json>(500, error.what())));
|
||||
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 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 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() + "/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");
|
||||
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;
|
||||
try {
|
||||
context = make_drogon_request_context(request, options.context_factory);
|
||||
} catch(const std::exception& error) {
|
||||
callback(make_drogon_response(make_http_error<Json>(500, error.what())));
|
||||
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_;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
#pragma once
|
||||
#include "adminive/collection.hpp"
|
||||
#include "adminive/http.hpp"
|
||||
#include "httplib.h"
|
||||
#include <algorithm>
|
||||
#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 {
|
||||
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 <Basic_Lock Field_Lock>
|
||||
explicit Http_Resource(Synchronized_Value<std::remove_cvref_t<T>, Lock, Field_Lock>& value, std::string path, Transaction transaction = {}) : service_(std::make_shared<Service>(value, std::move(path), std::move(transaction))) {}
|
||||
template <class Root, Basic_Lock Field_Lock, auto... Members>
|
||||
requires (!std::is_const_v<Root>) && std::same_as<typename Synchronized_Field<Root, Lock, Field_Lock, Members...>::value_type, std::remove_cvref_t<T>>
|
||||
explicit Http_Resource(Synchronized_Field<Root, Lock, Field_Lock, Members...> value, std::string path, Transaction transaction = {}, Resource_Lock_Scope lock_scope = Resource_Lock_Scope::local) : service_(std::make_shared<Service>(value, std::move(path), std::move(transaction), lock_scope)) {}
|
||||
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() + "/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](const httplib::Request& request, httplib::Response& response) {
|
||||
write_http_response(response, service->update_response(request.body));
|
||||
});
|
||||
}
|
||||
private:
|
||||
std::shared_ptr<Service> service_;
|
||||
};
|
||||
template <Described_Type T, Json_Type Json, Basic_Lock Lock = std::mutex>
|
||||
requires 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 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() + "/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";
|
||||
}
|
||||
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_;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
#include "adminive/adapter.hpp"
|
||||
#include "magic_enum/magic_enum.hpp"
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
namespace adminive {
|
||||
template <class Enum>
|
||||
requires std::is_enum_v<Enum>
|
||||
struct Enum_Adapter<Enum> {
|
||||
static std::vector<Enum> values() {
|
||||
const auto source = magic_enum::enum_values<Enum>();
|
||||
return std::vector<Enum>(source.begin(), source.end());
|
||||
}
|
||||
static std::string_view name(Enum value) {
|
||||
return magic_enum::enum_name(value);
|
||||
}
|
||||
static std::optional<Enum> cast(std::string_view value) {
|
||||
return magic_enum::enum_cast<Enum>(value);
|
||||
}
|
||||
static std::optional<Enum> cast(std::underlying_type_t<Enum> value) {
|
||||
return magic_enum::enum_cast<Enum>(value);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
#pragma once
|
||||
#include "adminive/adapter.hpp"
|
||||
#include "nlohmann/json.hpp"
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
namespace adminive {
|
||||
template <>
|
||||
struct Json_Adapter<nlohmann::json> {
|
||||
using Json = nlohmann::json;
|
||||
static Json object() {
|
||||
return Json::object();
|
||||
}
|
||||
static Json array() {
|
||||
return Json::array();
|
||||
}
|
||||
static Json null() {
|
||||
return nullptr;
|
||||
}
|
||||
static Json parse(std::string_view value) {
|
||||
return Json::parse(value.begin(), value.end());
|
||||
}
|
||||
static std::string dump(const Json& value, int indent) {
|
||||
return value.dump(indent);
|
||||
}
|
||||
static bool is_null(const Json& value) noexcept {
|
||||
return value.is_null();
|
||||
}
|
||||
static bool is_object(const Json& value) noexcept {
|
||||
return value.is_object();
|
||||
}
|
||||
static bool is_array(const Json& value) noexcept {
|
||||
return value.is_array();
|
||||
}
|
||||
static bool is_string(const Json& value) noexcept {
|
||||
return value.is_string();
|
||||
}
|
||||
static bool is_signed_integer(const Json& value) noexcept {
|
||||
return value.is_number_integer() && !value.is_number_unsigned();
|
||||
}
|
||||
static bool is_unsigned_integer(const Json& value) noexcept {
|
||||
return value.is_number_unsigned();
|
||||
}
|
||||
static bool is_floating_point(const Json& value) noexcept {
|
||||
return value.is_number_float();
|
||||
}
|
||||
static bool is_number(const Json& value) noexcept {
|
||||
return value.is_number();
|
||||
}
|
||||
static bool is_boolean(const Json& value) noexcept {
|
||||
return value.is_boolean();
|
||||
}
|
||||
static std::int64_t signed_integer(const Json& value) {
|
||||
return value.get<std::int64_t>();
|
||||
}
|
||||
static std::uint64_t unsigned_integer(const Json& value) {
|
||||
return value.get<std::uint64_t>();
|
||||
}
|
||||
static long double number(const Json& value) {
|
||||
return value.get<long double>();
|
||||
}
|
||||
static bool contains(const Json& value, std::string_view name) {
|
||||
return value.contains(std::string(name));
|
||||
}
|
||||
static std::size_t size(const Json& value) noexcept {
|
||||
return value.size();
|
||||
}
|
||||
static std::vector<std::string> keys(const Json& value) {
|
||||
std::vector<std::string> result;
|
||||
result.reserve(value.size());
|
||||
for(auto iterator = value.begin(); iterator != value.end(); ++iterator) {
|
||||
result.push_back(iterator.key());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
static Json& at(Json& value, std::string_view name) {
|
||||
return value.at(std::string(name));
|
||||
}
|
||||
static const Json& at(const Json& value, std::string_view name) {
|
||||
return value.at(std::string(name));
|
||||
}
|
||||
static Json& at(Json& value, std::size_t index) {
|
||||
return value.at(index);
|
||||
}
|
||||
static const Json& at(const Json& value, std::size_t index) {
|
||||
return value.at(index);
|
||||
}
|
||||
static void set(Json& value, std::string_view name, Json item) {
|
||||
value[std::string(name)] = std::move(item);
|
||||
}
|
||||
static void append(Json& value, Json item) {
|
||||
value.push_back(std::move(item));
|
||||
}
|
||||
static void erase(Json& value, std::string_view name) {
|
||||
value.erase(std::string(name));
|
||||
}
|
||||
template <class T>
|
||||
static Json make(T&& value) {
|
||||
if constexpr(std::same_as<std::remove_cvref_t<T>, std::string_view>) {
|
||||
return Json(std::string(value));
|
||||
} else {
|
||||
return Json(std::forward<T>(value));
|
||||
}
|
||||
}
|
||||
template <class T>
|
||||
static T get(const Json& value) {
|
||||
return value.get<T>();
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user