This commit is contained in:
2026-08-06 22:38:23 +08:00
parent 14b0b0e3bc
commit 114207f85d
16 changed files with 524 additions and 111 deletions
+14 -1
View File
@@ -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);
};
+24 -14
View File
@@ -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();
}
+68 -5
View File
@@ -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"));
+76 -3
View File
@@ -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)
+8 -7
View File
@@ -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_;
};
+106 -21
View File
@@ -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{};