更新
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
@@ -168,11 +169,23 @@ template <class T, class Json>
|
||||
concept Control_Adapter_With_Column = requires(const Json& field) {
|
||||
{ Control_Adapter<std::remove_cvref_t<T>, Json>::make_column(field) } -> std::same_as<Json>;
|
||||
};
|
||||
template <class T>
|
||||
struct Polymorphic_Variant {
|
||||
using type = T;
|
||||
std::string value;
|
||||
std::string label;
|
||||
};
|
||||
template <class T>
|
||||
auto polymorphic_variant(std::string value, std::string label) {
|
||||
return Polymorphic_Variant<T>{std::move(value), std::move(label)};
|
||||
}
|
||||
template <class T, class Json>
|
||||
struct Polymorphic_Adapter;
|
||||
template <class T, class Json>
|
||||
concept Polymorphic_Type = requires(const std::remove_cvref_t<T>& value, std::remove_cvref_t<T>& target, const Json& input) {
|
||||
{ Polymorphic_Adapter<std::remove_cvref_t<T>, Json>::descriptor() } -> std::same_as<Json>;
|
||||
{ Polymorphic_Adapter<std::remove_cvref_t<T>, Json>::discriminator() } -> std::convertible_to<std::string_view>;
|
||||
{ Polymorphic_Adapter<std::remove_cvref_t<T>, Json>::discriminator_label() } -> std::convertible_to<std::string_view>;
|
||||
{ Polymorphic_Adapter<std::remove_cvref_t<T>, Json>::variants() };
|
||||
{ Polymorphic_Adapter<std::remove_cvref_t<T>, Json>::encode(value) } -> std::same_as<Json>;
|
||||
Polymorphic_Adapter<std::remove_cvref_t<T>, Json>::decode(target, input);
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "adminive/http.hpp"
|
||||
#include <drogon/drogon.h>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
@@ -14,31 +15,40 @@ drogon::HttpResponsePtr make_drogon_response(Http_Response<Json> value) {
|
||||
response->setBody(dump_json(value.body, 2));
|
||||
return response;
|
||||
}
|
||||
using Drogon_Executor = std::function<void(std::function<void()>)>;
|
||||
template <class T, Json_Type Json>
|
||||
requires Adapted_Object_Type<T>
|
||||
requires Writable_Adapted_Object<T>
|
||||
class Drogon_Resource {
|
||||
public:
|
||||
using Service = Resource_Service<T, Json>;
|
||||
using Commit_Function = typename Service::Commit_Function;
|
||||
Drogon_Resource(T& value, std::string path, Commit_Function commit = {}, std::mutex* shared_mutex = nullptr) : service_(value, std::move(path), std::move(commit), shared_mutex) {}
|
||||
using Transaction_Function = typename Service::Transaction_Function;
|
||||
Drogon_Resource(T& value, std::string path, Transaction_Function transaction = {}, std::mutex* shared_mutex = nullptr) : service_(std::make_shared<Service>(value, std::move(path), std::move(transaction), shared_mutex)) {}
|
||||
Json amis_schema() const {
|
||||
return service_.amis_schema();
|
||||
return service_->amis_schema();
|
||||
}
|
||||
void bind(drogon::HttpAppFramework& app) {
|
||||
app.registerHandler(service_.path() + "/descriptor", [this](const drogon::HttpRequestPtr&, std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
|
||||
callback(make_drogon_response(service_.descriptor_response()));
|
||||
void bind(drogon::HttpAppFramework& app, Drogon_Executor executor = {}) const {
|
||||
const auto service = service_;
|
||||
app.registerHandler(service->path() + "/descriptor", [service](const drogon::HttpRequestPtr&, std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
|
||||
callback(make_drogon_response(service->descriptor_response()));
|
||||
}, {drogon::Get});
|
||||
app.registerHandler(service_.path() + "/data", [this](const drogon::HttpRequestPtr&, std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
|
||||
callback(make_drogon_response(service_.data_response()));
|
||||
app.registerHandler(service->path() + "/data", [service](const drogon::HttpRequestPtr&, std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
|
||||
callback(make_drogon_response(service->data_response()));
|
||||
}, {drogon::Get});
|
||||
app.registerHandler(service_.path() + "/amis", [this](const drogon::HttpRequestPtr&, std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
|
||||
callback(make_drogon_response(service_.amis_response()));
|
||||
app.registerHandler(service->path() + "/amis", [service](const drogon::HttpRequestPtr&, std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
|
||||
callback(make_drogon_response(service->amis_response()));
|
||||
}, {drogon::Get});
|
||||
app.registerHandler(service_.path() + "/data", [this](const drogon::HttpRequestPtr& request, std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
|
||||
callback(make_drogon_response(service_.update_response(request->getBody())));
|
||||
app.registerHandler(service->path() + "/data", [service, executor = std::move(executor)](const drogon::HttpRequestPtr& request, std::function<void(const drogon::HttpResponsePtr&)>&& callback) mutable {
|
||||
auto task = [service, body = std::string(request->getBody()), callback = std::move(callback)]() mutable {
|
||||
callback(make_drogon_response(service->update_response(body)));
|
||||
};
|
||||
if(executor) {
|
||||
executor(std::move(task));
|
||||
} else {
|
||||
task();
|
||||
}
|
||||
}, {drogon::Post});
|
||||
}
|
||||
private:
|
||||
Service service_;
|
||||
std::shared_ptr<Service> service_;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -27,12 +27,12 @@ void write_http_error(httplib::Response& response, int status, const Update_Resu
|
||||
write_http_response(response, make_http_error<Json>(status, update));
|
||||
}
|
||||
template <class T, Json_Type Json>
|
||||
requires Adapted_Object_Type<T>
|
||||
requires Writable_Adapted_Object<T>
|
||||
class Http_Resource {
|
||||
public:
|
||||
using Service = Resource_Service<T, Json>;
|
||||
using Commit_Function = typename Service::Commit_Function;
|
||||
Http_Resource(T& value, std::string path, Commit_Function commit = {}, std::mutex* shared_mutex = nullptr) : service_(value, std::move(path), std::move(commit), shared_mutex) {}
|
||||
using Transaction_Function = typename Service::Transaction_Function;
|
||||
Http_Resource(T& value, std::string path, Transaction_Function transaction = {}, std::mutex* shared_mutex = nullptr) : service_(value, std::move(path), std::move(transaction), shared_mutex) {}
|
||||
Json amis_schema() const {
|
||||
return service_.amis_schema();
|
||||
}
|
||||
|
||||
@@ -14,6 +14,23 @@ inline std::string make_field_name(std::string_view prefix, std::string_view nam
|
||||
}
|
||||
return std::string(prefix) + "." + std::string(name);
|
||||
}
|
||||
inline std::string escape_amis_string_literal(std::string_view value) {
|
||||
std::string result;
|
||||
result.reserve(value.size());
|
||||
for(const char character : value) {
|
||||
if(character == '\\' || character == '\'') {
|
||||
result.push_back('\\');
|
||||
}
|
||||
if(character == '\n') {
|
||||
result += "\\n";
|
||||
} else if(character == '\r') {
|
||||
result += "\\r";
|
||||
} else {
|
||||
result.push_back(character);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
template <Json_Type Json>
|
||||
Json make_amis_form_actions(std::string submit_label = "Apply") {
|
||||
Json reset = json_object<Json>();
|
||||
@@ -32,6 +49,9 @@ template <Json_Type Json>
|
||||
Json make_amis_form_body(const Json& descriptor, std::string_view permission, std::string_view prefix = {});
|
||||
template <Json_Type Json, Described_Type T>
|
||||
Json make_typed_amis_form_body(const Json& descriptor, std::string_view permission, std::string_view prefix = {});
|
||||
template <Json_Type Json, class T>
|
||||
requires Polymorphic_Type<T, Json>
|
||||
Json make_typed_amis_polymorphic_control(const Json& field, std::string_view permission, std::string_view prefix);
|
||||
template <Json_Type Json>
|
||||
Json make_amis_polymorphic_control(const Json& field, std::string_view permission, std::string_view prefix) {
|
||||
const std::string name = json_get<Json, std::string>(Json_Adapter<Json>::at(field, "name"));
|
||||
@@ -52,7 +72,7 @@ Json make_amis_polymorphic_control(const Json& field, std::string_view permissio
|
||||
const std::string value = json_get<Json, std::string>(Json_Adapter<Json>::at(variant, "value"));
|
||||
Json group = json_object<Json>();
|
||||
json_set(group, "type", "container");
|
||||
json_set(group, "visibleOn", "${" + make_field_name(full_name, discriminator) + " == '" + value + "'}");
|
||||
json_set(group, "visibleOn", "${" + make_field_name(full_name, discriminator) + " == '" + escape_amis_string_literal(value) + "'}");
|
||||
json_set(group, "body", make_amis_form_body<Json>(Json_Adapter<Json>::at(variant, "descriptor"), permission, full_name));
|
||||
json_append(body, std::move(group));
|
||||
}
|
||||
@@ -70,6 +90,47 @@ Json make_amis_polymorphic_control(const Json& field, std::string_view permissio
|
||||
}
|
||||
return result;
|
||||
}
|
||||
template <Json_Type Json, class T>
|
||||
requires Polymorphic_Type<T, Json>
|
||||
Json make_typed_amis_polymorphic_control(const Json& field, std::string_view permission, std::string_view prefix) {
|
||||
using Adapter = Polymorphic_Adapter<std::remove_cvref_t<T>, Json>;
|
||||
const std::string name = json_get<Json, std::string>(Json_Adapter<Json>::at(field, "name"));
|
||||
const std::string full_name = make_field_name(prefix, name);
|
||||
const auto& metadata = Json_Adapter<Json>::at(field, "polymorphic");
|
||||
const std::string discriminator(Adapter::discriminator());
|
||||
Json selector = json_object<Json>();
|
||||
json_set(selector, "type", "select");
|
||||
json_set(selector, "name", make_field_name(full_name, discriminator));
|
||||
json_set(selector, "label", Json_Adapter<Json>::at(metadata, "discriminator_label"));
|
||||
json_set(selector, "options", Json_Adapter<Json>::at(metadata, "options"));
|
||||
json_set(selector, "required", true);
|
||||
Json body = json_array<Json>();
|
||||
json_append(body, std::move(selector));
|
||||
std::apply([&](const auto&... variant) {
|
||||
([&] {
|
||||
using Variant = typename std::remove_cvref_t<decltype(variant)>::type;
|
||||
const Json descriptor = to_descriptor_json<Json, Variant>();
|
||||
Json group = json_object<Json>();
|
||||
json_set(group, "type", "container");
|
||||
json_set(group, "visibleOn", "${" + make_field_name(full_name, discriminator) + " == '" + escape_amis_string_literal(variant.value) + "'}");
|
||||
json_set(group, "body", make_typed_amis_form_body<Json, Object_Model_Type<Variant>>(descriptor, permission, full_name));
|
||||
json_append(body, std::move(group));
|
||||
}(), ...);
|
||||
}, Adapter::variants());
|
||||
Json result = json_object<Json>();
|
||||
json_set(result, "type", "fieldset");
|
||||
json_set(result, "title", Json_Adapter<Json>::at(field, "label"));
|
||||
json_set(result, "body", std::move(body));
|
||||
json_set(result, "collapsable", true);
|
||||
json_set(result, "collapsed", false);
|
||||
if(Json_Adapter<Json>::contains(field, "description")) {
|
||||
json_set(result, "description", Json_Adapter<Json>::at(field, "description"));
|
||||
}
|
||||
if(Json_Adapter<Json>::contains(field, "visible_on")) {
|
||||
json_set(result, "visibleOn", Json_Adapter<Json>::at(field, "visible_on"));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
template <Json_Type Json>
|
||||
Json make_default_amis_control(const Json& field, std::string_view permission, std::string_view prefix = {}) {
|
||||
const std::string name = json_get<Json, std::string>(Json_Adapter<Json>::at(field, "name"));
|
||||
@@ -164,7 +225,9 @@ Json make_typed_amis_control(const Json& field, std::string_view permission, std
|
||||
return Control_Adapter<Storage, Json>::make_control(field, context);
|
||||
} else if constexpr(!std::same_as<Storage, Value> && Control_Adapter_With_Control<Value, Json>) {
|
||||
return Control_Adapter<Value, Json>::make_control(field, context);
|
||||
} else if constexpr(Adapted_Object_Type<Value>) {
|
||||
} else if constexpr(Polymorphic_Type<Value, Json>) {
|
||||
return make_typed_amis_polymorphic_control<Json, Value>(field, permission, prefix);
|
||||
} else if constexpr(Described_Type<Value> || Snapshot_Adapted_Object<Value>) {
|
||||
using Model = Object_Model_Type<Value>;
|
||||
const std::string name = json_get<Json, std::string>(Json_Adapter<Json>::at(field, "name"));
|
||||
const std::string full_name = make_field_name(prefix, name);
|
||||
@@ -356,7 +419,7 @@ Json make_amis_object_status_column(const Json& descriptor, std::string status_a
|
||||
return result;
|
||||
}
|
||||
template <Json_Type Json, class T>
|
||||
requires Adapted_Object_Type<T>
|
||||
requires Described_Type<std::remove_cvref_t<T>> || Snapshot_Adapted_Object<T>
|
||||
Json to_amis_form_schema(const T& value, std::string submit_api = {}, std::string submit_label = "Apply") {
|
||||
using Model = Object_Model_Type<T>;
|
||||
const Json descriptor = to_descriptor_json<Json, T>();
|
||||
@@ -376,7 +439,7 @@ Json to_amis_form_schema(const T& value, std::string submit_api = {}, std::strin
|
||||
return result;
|
||||
}
|
||||
template <Json_Type Json, class T>
|
||||
requires Adapted_Object_Type<T>
|
||||
requires Described_Type<Object_Model_Type<T>>
|
||||
Json to_amis_crud_schema(std::string base_api, const Json* status_descriptor = nullptr, std::uint64_t status_interval = 2000) {
|
||||
using Model = Object_Model_Type<T>;
|
||||
const Json descriptor = to_descriptor_json<Json, T>();
|
||||
@@ -498,7 +561,7 @@ Json to_amis_crud_schema(std::string base_api, const Json* status_descriptor = n
|
||||
return result;
|
||||
}
|
||||
template <Json_Type Json, class T>
|
||||
requires Adapted_Object_Type<T>
|
||||
requires Described_Type<Object_Model_Type<T>>
|
||||
Json to_amis_crud_status_schema(std::string base_api, std::string status_api, const Json& status_descriptor, std::uint64_t status_interval = 2000, const Json* object_status_descriptor = nullptr, std::uint64_t object_status_interval = 2000) {
|
||||
Json result = to_amis_crud_schema<Json, T>(std::move(base_api), object_status_descriptor, object_status_interval);
|
||||
Json crud = std::move(Json_Adapter<Json>::at(result, "body"));
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <cctype>
|
||||
#include <concepts>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
@@ -94,6 +95,20 @@ public:
|
||||
result.readable_ = value;
|
||||
return result;
|
||||
}
|
||||
Derived sensitive(bool value = true) const {
|
||||
auto result = derived();
|
||||
result.sensitive_ = value;
|
||||
if(value) {
|
||||
result.readable_ = false;
|
||||
result.include_default_ = false;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
Derived include_default(bool value = true) const {
|
||||
auto result = derived();
|
||||
result.include_default_ = value;
|
||||
return result;
|
||||
}
|
||||
Derived visible(bool value = true) const {
|
||||
auto result = derived();
|
||||
result.visible_ = value;
|
||||
@@ -200,6 +215,12 @@ public:
|
||||
bool is_readable() const noexcept {
|
||||
return readable_;
|
||||
}
|
||||
bool is_sensitive() const noexcept {
|
||||
return sensitive_;
|
||||
}
|
||||
bool includes_default() const noexcept {
|
||||
return include_default_;
|
||||
}
|
||||
bool is_visible() const noexcept {
|
||||
return visible_;
|
||||
}
|
||||
@@ -230,6 +251,8 @@ private:
|
||||
bool editable_{};
|
||||
bool creatable_{};
|
||||
bool readable_{true};
|
||||
bool sensitive_{};
|
||||
bool include_default_{true};
|
||||
bool visible_{true};
|
||||
bool required_{};
|
||||
bool list_visible_{true};
|
||||
@@ -269,12 +292,20 @@ auto field(std::string name, std::string label) {
|
||||
template <Reflected_Type T, std::size_t Index>
|
||||
auto reflected_field() {
|
||||
static_assert(Index < static_cast<std::size_t>(Reflection_Adapter<T>::field_count));
|
||||
return Reflected_Field_Descriptor<T, Index>(std::string(Reflection_Adapter<T>::template name<Index>()));
|
||||
const std::string name(Reflection_Adapter<T>::template name<Index>());
|
||||
if(name.empty()) {
|
||||
throw std::invalid_argument("reflected field name must not be empty");
|
||||
}
|
||||
return Reflected_Field_Descriptor<T, Index>(name);
|
||||
}
|
||||
template <Reflected_Type T, std::size_t Index>
|
||||
auto reflected_field(std::string label) {
|
||||
static_assert(Index < static_cast<std::size_t>(Reflection_Adapter<T>::field_count));
|
||||
return Reflected_Field_Descriptor<T, Index>(std::string(Reflection_Adapter<T>::template name<Index>()), std::move(label));
|
||||
const std::string name(Reflection_Adapter<T>::template name<Index>());
|
||||
if(name.empty()) {
|
||||
throw std::invalid_argument("reflected field name must not be empty");
|
||||
}
|
||||
return Reflected_Field_Descriptor<T, Index>(name, std::move(label));
|
||||
}
|
||||
struct No_Object_Validator {
|
||||
template <class T>
|
||||
@@ -393,6 +424,46 @@ private:
|
||||
std::tuple<Fields...> fields_;
|
||||
Object_List_Options list_options_;
|
||||
};
|
||||
template <class Descriptor>
|
||||
void validate_descriptor(const Descriptor& descriptor) {
|
||||
if(descriptor.name().empty()) {
|
||||
throw std::invalid_argument("descriptor name must not be empty");
|
||||
}
|
||||
std::set<std::string> field_names;
|
||||
std::apply([&](const auto&... field) {
|
||||
([&] {
|
||||
if(field.name().empty()) {
|
||||
throw std::invalid_argument("descriptor field name must not be empty");
|
||||
}
|
||||
if(!field_names.insert(field.name()).second) {
|
||||
throw std::invalid_argument("duplicate descriptor field name: " + field.name());
|
||||
}
|
||||
}(), ...);
|
||||
}, descriptor.fields());
|
||||
const auto& options = descriptor.list_options();
|
||||
if(options.default_order_dir != "asc" && options.default_order_dir != "desc") {
|
||||
throw std::invalid_argument("default sort direction must be 'asc' or 'desc'");
|
||||
}
|
||||
if(options.default_order_by.empty()) {
|
||||
return;
|
||||
}
|
||||
bool found{};
|
||||
bool sortable{};
|
||||
std::apply([&](const auto&... field) {
|
||||
([&] {
|
||||
if(field.name() == options.default_order_by) {
|
||||
found = true;
|
||||
sortable = field.is_sortable();
|
||||
}
|
||||
}(), ...);
|
||||
}, descriptor.fields());
|
||||
if(!found) {
|
||||
throw std::invalid_argument("default sort field does not exist: " + options.default_order_by);
|
||||
}
|
||||
if(!sortable) {
|
||||
throw std::invalid_argument("default sort field is not sortable: " + options.default_order_by);
|
||||
}
|
||||
}
|
||||
template <class T, Field_Descriptor_Type... Fields>
|
||||
auto object(std::string name, Fields... fields) {
|
||||
static_assert((std::convertible_to<T*, typename Fields::owner_type*> && ...));
|
||||
@@ -433,7 +504,9 @@ auto reflected_object(std::string name, std::string label) {
|
||||
}
|
||||
template <Described_Type T>
|
||||
auto describe() {
|
||||
return Type_Descriptor<std::remove_cvref_t<T>>::get();
|
||||
auto descriptor = Type_Descriptor<std::remove_cvref_t<T>>::get();
|
||||
validate_descriptor(descriptor);
|
||||
return descriptor;
|
||||
}
|
||||
}
|
||||
#define ADMINIVE_FIELD(Type, Member) ::adminive::field<&Type::Member>(#Member)
|
||||
|
||||
@@ -40,13 +40,13 @@ Http_Response<Json> make_http_error(int status, std::string message) {
|
||||
return Http_Response<Json>{status, make_http_result<Json>(status, std::move(message), json_object<Json>())};
|
||||
}
|
||||
template <class T, Json_Type Json>
|
||||
requires Adapted_Object_Type<T>
|
||||
requires Writable_Adapted_Object<T>
|
||||
class Resource_Service {
|
||||
public:
|
||||
using Object = std::remove_cvref_t<T>;
|
||||
using Model = Object_Model_Type<Object>;
|
||||
using Commit_Function = std::function<void(const Model&)>;
|
||||
Resource_Service(T& value, std::string path, Commit_Function commit = {}, std::mutex* shared_mutex = nullptr) : value_(value), path_(std::move(path)), commit_(std::move(commit)), shared_mutex_(shared_mutex) {}
|
||||
using Transaction_Function = std::function<void(Object&, Model)>;
|
||||
Resource_Service(T& value, std::string path, Transaction_Function transaction = {}, std::mutex* shared_mutex = nullptr) : value_(value), path_(std::move(path)), transaction_(std::move(transaction)), shared_mutex_(shared_mutex) {}
|
||||
const std::string& path() const noexcept {
|
||||
return path_;
|
||||
}
|
||||
@@ -78,10 +78,11 @@ public:
|
||||
return make_http_error<Json>(422, result);
|
||||
}
|
||||
try {
|
||||
if(commit_) {
|
||||
commit_(candidate);
|
||||
if(transaction_) {
|
||||
transaction_(value_, std::move(candidate));
|
||||
} else {
|
||||
Object_Adapter<Object>::commit(value_, std::move(candidate));
|
||||
}
|
||||
Object_Adapter<Object>::commit(value_, std::move(candidate));
|
||||
} catch(const Json_Assignment_Error& error) {
|
||||
return make_http_error<Json>(422, error.result());
|
||||
} catch(const Field_Validation_Error& error) {
|
||||
@@ -97,7 +98,7 @@ private:
|
||||
}
|
||||
T& value_;
|
||||
std::string path_;
|
||||
Commit_Function commit_;
|
||||
Transaction_Function transaction_;
|
||||
std::mutex* shared_mutex_{};
|
||||
mutable std::mutex mutex_;
|
||||
};
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "adminive/descriptor.hpp"
|
||||
#include <concepts>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
@@ -92,17 +93,26 @@ inline void merge_update_errors(Update_Result& target, const Update_Result& sour
|
||||
}
|
||||
}
|
||||
template <class T>
|
||||
concept Adapted_Object_Type = Object_Adapter_With_Snapshot<T> && Object_Adapter_With_Create<T> && Object_Adapter_With_Commit<T> && Described_Type<Object_Model_Type<T>>;
|
||||
concept Snapshot_Adapted_Object = Object_Adapter_With_Snapshot<T> && Described_Type<Object_Model_Type<T>>;
|
||||
template <class T>
|
||||
concept Creatable_Adapted_Object = Object_Adapter_With_Create<T> && Described_Type<Object_Model_Type<T>>;
|
||||
template <class T>
|
||||
concept Writable_Adapted_Object = Snapshot_Adapted_Object<T> && Object_Adapter_With_Commit<T>;
|
||||
template <class T>
|
||||
concept Creatable_Writable_Object = Writable_Adapted_Object<T> && Object_Adapter_With_Create<T>;
|
||||
template <Json_Type Json, class T>
|
||||
requires Described_Type<Object_Model_Type<T>> && Object_Adapter_With_Create<T>
|
||||
requires Described_Type<Object_Model_Type<T>>
|
||||
Json to_descriptor_json();
|
||||
template <Json_Type Json, class T>
|
||||
requires Creatable_Adapted_Object<T>
|
||||
Json to_descriptor_json_with_defaults();
|
||||
template <Json_Type Json, class T>
|
||||
requires Described_Type<std::remove_cvref_t<T>> || Object_Adapter_With_Snapshot<T>
|
||||
Json to_json(const T& value);
|
||||
template <Json_Type Json, class T>
|
||||
requires Described_Type<std::remove_cvref_t<T>> || Object_Adapter_With_Snapshot<T>
|
||||
Json to_frontend_json(const T& value);
|
||||
template <Json_Type Json, Adapted_Object_Type T>
|
||||
template <Json_Type Json, Writable_Adapted_Object T>
|
||||
Update_Result assign_json(T& target, const Json& value);
|
||||
template <Json_Type Json, class T>
|
||||
void assign_json_value(T& target, const Json& value);
|
||||
@@ -123,7 +133,7 @@ Json encode_json_value(const T& value) {
|
||||
return json_scalar<Json>(std::string(name));
|
||||
}
|
||||
return json_scalar<Json>(static_cast<std::underlying_type_t<Value>>(value));
|
||||
} else if constexpr(Described_Type<Value> || (Object_Adapter_With_Snapshot<Value> && Described_Type<Object_Model_Type<Value>>)) {
|
||||
} else if constexpr(Described_Type<Value> || Snapshot_Adapted_Object<Value>) {
|
||||
return to_json<Json>(value);
|
||||
} else if constexpr(String_Key_Map_Type<Value>) {
|
||||
Json result = json_object<Json>();
|
||||
@@ -165,7 +175,7 @@ void assign_json_value(T& target, const Json& value) {
|
||||
throw std::invalid_argument("unknown enum value");
|
||||
}
|
||||
target = *parsed;
|
||||
} else if constexpr(Adapted_Object_Type<Value>) {
|
||||
} else if constexpr(Writable_Adapted_Object<Value>) {
|
||||
const auto result = assign_json<Json>(target, value);
|
||||
if(!result.success) {
|
||||
throw Json_Assignment_Error(result);
|
||||
@@ -243,7 +253,7 @@ std::string value_type_name() {
|
||||
return "number";
|
||||
} else if constexpr(String_Type<Value>) {
|
||||
return "string";
|
||||
} else if constexpr(Described_Type<Value> || (Object_Adapter_With_Snapshot<Value> && Described_Type<Object_Model_Type<Value>>)) {
|
||||
} else if constexpr(Described_Type<Value> || Snapshot_Adapted_Object<Value>) {
|
||||
return "object";
|
||||
} else if constexpr(String_Key_Map_Type<Value>) {
|
||||
return "map";
|
||||
@@ -254,6 +264,9 @@ std::string value_type_name() {
|
||||
}
|
||||
}
|
||||
template <Json_Type Json, class T>
|
||||
requires Polymorphic_Type<T, Json>
|
||||
Json polymorphic_descriptor_json();
|
||||
template <Json_Type Json, class T>
|
||||
void append_constraints(Json& result) {
|
||||
using Storage = std::remove_cvref_t<T>;
|
||||
using Value = Adapted_Value_Type<Storage, Json>;
|
||||
@@ -273,7 +286,7 @@ void append_constraints(Json& result) {
|
||||
append_constraints<Json, Value>(result);
|
||||
}
|
||||
if constexpr(Polymorphic_Type<Value, Json>) {
|
||||
json_set(result, "polymorphic", Polymorphic_Adapter<Value, Json>::descriptor());
|
||||
json_set(result, "polymorphic", polymorphic_descriptor_json<Json, Value>());
|
||||
}
|
||||
if constexpr(Value_Adapter_With_Schema<Storage, Json>) {
|
||||
Value_Adapter<Storage, Json>::append_schema(result);
|
||||
@@ -321,9 +334,35 @@ Json make_polymorphic_descriptor(std::string discriminator, std::string discrimi
|
||||
json_set(result, "variants", std::move(variants));
|
||||
return result;
|
||||
}
|
||||
template <Json_Type Json, class T>
|
||||
requires Polymorphic_Type<T, Json>
|
||||
Json polymorphic_descriptor_json() {
|
||||
using Adapter = Polymorphic_Adapter<std::remove_cvref_t<T>, Json>;
|
||||
const std::string discriminator(Adapter::discriminator());
|
||||
const std::string discriminator_label(Adapter::discriminator_label());
|
||||
if(discriminator.empty()) {
|
||||
throw std::invalid_argument("polymorphic discriminator must not be empty");
|
||||
}
|
||||
Json variants = json_array<Json>();
|
||||
std::set<std::string> values;
|
||||
std::apply([&](const auto&... variant) {
|
||||
([&] {
|
||||
using Variant = typename std::remove_cvref_t<decltype(variant)>::type;
|
||||
static_assert(Described_Type<Object_Model_Type<Variant>>, "polymorphic variant requires a type descriptor");
|
||||
if(variant.value.empty()) {
|
||||
throw std::invalid_argument("polymorphic variant value must not be empty");
|
||||
}
|
||||
if(!values.insert(variant.value).second) {
|
||||
throw std::invalid_argument("duplicate polymorphic variant value: " + variant.value);
|
||||
}
|
||||
json_append(variants, make_polymorphic_variant<Json>(variant.value, variant.label, to_descriptor_json<Json, Variant>()));
|
||||
}(), ...);
|
||||
}, Adapter::variants());
|
||||
return make_polymorphic_descriptor<Json>(discriminator, discriminator_label, std::move(variants));
|
||||
}
|
||||
template <Json_Type Json, class Model>
|
||||
requires Described_Type<Model>
|
||||
Json model_descriptor_json(const Model& defaults) {
|
||||
Json model_descriptor_json(const Model* defaults = nullptr) {
|
||||
const auto descriptor = describe<Model>();
|
||||
Json fields = json_array<Json>();
|
||||
int field_index{};
|
||||
@@ -340,6 +379,7 @@ Json model_descriptor_json(const Model& defaults) {
|
||||
json_set(field_json, "list_label", item.list_label());
|
||||
json_set(field_json, "value_type", value_type_name<Json, Member>());
|
||||
json_set(field_json, "readable", item.is_readable());
|
||||
json_set(field_json, "sensitive", item.is_sensitive());
|
||||
json_set(field_json, "editable", item.is_editable());
|
||||
json_set(field_json, "creatable", item.is_creatable());
|
||||
json_set(field_json, "visible", item.is_visible());
|
||||
@@ -347,7 +387,9 @@ Json model_descriptor_json(const Model& defaults) {
|
||||
json_set(field_json, "list_visible", item.is_list_visible());
|
||||
json_set(field_json, "order", order);
|
||||
json_set(field_json, "sortable", item.is_sortable());
|
||||
json_set(field_json, "default", encode_json_value<Json>(item.get(defaults)));
|
||||
if(defaults && item.is_readable() && !item.is_sensitive() && item.includes_default()) {
|
||||
json_set(field_json, "default", encode_json_value<Json>(item.get(*defaults)));
|
||||
}
|
||||
if(!item.description().empty()) {
|
||||
json_set(field_json, "description", item.description());
|
||||
}
|
||||
@@ -357,7 +399,7 @@ Json model_descriptor_json(const Model& defaults) {
|
||||
if(!item.visible_on().empty()) {
|
||||
json_set(field_json, "visible_on", item.visible_on());
|
||||
}
|
||||
if constexpr(Adapted_Object_Type<Value>) {
|
||||
if constexpr(Described_Type<Value> || Snapshot_Adapted_Object<Value>) {
|
||||
json_set(field_json, "children", Json_Adapter<Json>::at(to_descriptor_json<Json, Value>(), "fields"));
|
||||
}
|
||||
append_constraints<Json, Member>(field_json);
|
||||
@@ -394,9 +436,21 @@ Json model_descriptor_json(const Model& defaults) {
|
||||
return result;
|
||||
}
|
||||
template <Json_Type Json, class T>
|
||||
requires Described_Type<Object_Model_Type<T>> && Object_Adapter_With_Create<T>
|
||||
requires Described_Type<Object_Model_Type<T>>
|
||||
Json to_descriptor_json() {
|
||||
return model_descriptor_json<Json>(Object_Adapter<std::remove_cvref_t<T>>::create());
|
||||
using Model = Object_Model_Type<T>;
|
||||
return model_descriptor_json<Json, Model>();
|
||||
}
|
||||
template <Json_Type Json, class T>
|
||||
requires Creatable_Adapted_Object<T>
|
||||
Json to_descriptor_json_with_defaults() {
|
||||
using Object = std::remove_cvref_t<T>;
|
||||
const auto defaults = Object_Adapter<Object>::create();
|
||||
return model_descriptor_json<Json>(&defaults);
|
||||
}
|
||||
template <Json_Type Json, Described_Type Model>
|
||||
Json to_descriptor_json(const Model& defaults) {
|
||||
return model_descriptor_json<Json>(&defaults);
|
||||
}
|
||||
template <Json_Type Json, Described_Type T>
|
||||
Json model_to_json(const T& value, bool frontend) {
|
||||
@@ -404,7 +458,7 @@ Json model_to_json(const T& value, bool frontend) {
|
||||
Json result = json_object<Json>();
|
||||
std::apply([&](const auto&... item) {
|
||||
([&] {
|
||||
if(!frontend || item.is_readable()) {
|
||||
if(!frontend || (item.is_readable() && !item.is_sensitive())) {
|
||||
json_set(result, item.name(), encode_json_value<Json>(item.get(value)));
|
||||
}
|
||||
}(), ...);
|
||||
@@ -511,6 +565,28 @@ Update_Result apply_model_json(Model& candidate, const Json& value, Write_Mode m
|
||||
result.message = complete ? "assigned" : "updated";
|
||||
return result;
|
||||
}
|
||||
template <Json_Type Json, class Variant>
|
||||
requires std::default_initializable<Variant> && Writable_Adapted_Object<Variant>
|
||||
Variant decode_polymorphic_variant(const Json& input) {
|
||||
if(!Json_Adapter<Json>::is_object(input)) {
|
||||
throw std::invalid_argument("polymorphic value must be a JSON object");
|
||||
}
|
||||
Json filtered = json_object<Json>();
|
||||
const auto descriptor = describe<Object_Model_Type<Variant>>();
|
||||
std::apply([&](const auto&... field) {
|
||||
([&] {
|
||||
if(Json_Adapter<Json>::contains(input, field.name())) {
|
||||
json_set(filtered, field.name(), Json_Adapter<Json>::at(input, field.name()));
|
||||
}
|
||||
}(), ...);
|
||||
}, descriptor.fields());
|
||||
Variant result{};
|
||||
auto update = assign_json<Json>(result, filtered);
|
||||
if(!update.success) {
|
||||
throw Json_Assignment_Error(std::move(update));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
template <class T>
|
||||
Update_Result object_commit_error(const Field_Validation_Error& error) {
|
||||
Update_Result result;
|
||||
@@ -518,10 +594,19 @@ Update_Result object_commit_error(const Field_Validation_Error& error) {
|
||||
result.field_errors[error.path()] = error.what();
|
||||
return result;
|
||||
}
|
||||
template <Json_Type Json, Adapted_Object_Type T>
|
||||
template <Json_Type Json, Writable_Adapted_Object T>
|
||||
Update_Result apply_object_json(T& target, const Json& value, Write_Mode mode, bool complete) {
|
||||
using Object = std::remove_cvref_t<T>;
|
||||
auto candidate = mode == Write_Mode::create ? Object_Adapter<Object>::create() : Object_Adapter<Object>::snapshot(target);
|
||||
auto candidate = [&] {
|
||||
if(mode == Write_Mode::create) {
|
||||
if constexpr(Object_Adapter_With_Create<Object>) {
|
||||
return Object_Adapter<Object>::create();
|
||||
} else {
|
||||
throw std::logic_error("object adapter does not support creation");
|
||||
}
|
||||
}
|
||||
return Object_Adapter<Object>::snapshot(target);
|
||||
}();
|
||||
auto result = apply_model_json<Json>(candidate, value, mode, complete);
|
||||
if(!result.success) {
|
||||
return result;
|
||||
@@ -538,27 +623,27 @@ Update_Result apply_object_json(T& target, const Json& value, Write_Mode mode, b
|
||||
}
|
||||
return result;
|
||||
}
|
||||
template <Json_Type Json, Adapted_Object_Type T>
|
||||
template <Json_Type Json, Writable_Adapted_Object T>
|
||||
Update_Result assign_json(T& target, const Json& value) {
|
||||
return apply_object_json<Json>(target, value, Write_Mode::internal, true);
|
||||
}
|
||||
template <Json_Type Json, Adapted_Object_Type T>
|
||||
template <Json_Type Json, Writable_Adapted_Object T>
|
||||
Update_Result apply_json_patch(T& target, const Json& patch) {
|
||||
return apply_object_json<Json>(target, patch, Write_Mode::internal, false);
|
||||
}
|
||||
template <Json_Type Json, Adapted_Object_Type T>
|
||||
template <Json_Type Json, Creatable_Writable_Object T>
|
||||
Update_Result apply_frontend_create(T& target, const Json& value) {
|
||||
return apply_object_json<Json>(target, value, Write_Mode::create, true);
|
||||
}
|
||||
template <Json_Type Json, Adapted_Object_Type T>
|
||||
template <Json_Type Json, Writable_Adapted_Object T>
|
||||
Update_Result apply_frontend_patch(T& target, const Json& patch) {
|
||||
return apply_object_json<Json>(target, patch, Write_Mode::update, false);
|
||||
}
|
||||
template <Json_Type Json, Adapted_Object_Type T>
|
||||
template <Json_Type Json, Writable_Adapted_Object T>
|
||||
Update_Result apply_patch(T& target, const Json& patch) {
|
||||
return apply_frontend_patch<Json>(target, patch);
|
||||
}
|
||||
template <Json_Type Json, Adapted_Object_Type T>
|
||||
template <Json_Type Json, Writable_Adapted_Object T>
|
||||
requires std::default_initializable<T>
|
||||
T from_json(const Json& value) {
|
||||
T result{};
|
||||
|
||||
Reference in New Issue
Block a user