Files
Adminive/backend/library/include/adminive/json.hpp
T
2026-08-08 11:14:50 +08:00

825 lines
36 KiB
C++

#pragma once
#include "adminive/adapter.hpp"
#include "adminive/descriptor.hpp"
#include "adminive/managed.hpp"
#include <cmath>
#include <concepts>
#include <cstdint>
#include <limits>
#include <map>
#include <set>
#include <stdexcept>
#include <string>
#include <type_traits>
#include <utility>
#include <variant>
namespace adminive {
struct Update_Result {
bool success{};
std::string message;
std::map<std::string, std::string> field_errors;
template <Json_Type Json>
Json to_json() const {
Json result = json_object<Json>();
json_set(result, "success", success);
json_set(result, "message", message);
Json errors = json_object<Json>();
for(const auto& [name, error] : field_errors) {
json_set(errors, name, error);
}
json_set(result, "errors", errors);
json_set(result, "field_errors", std::move(errors));
return result;
}
};
class Json_Assignment_Error : public std::invalid_argument {
public:
explicit Json_Assignment_Error(Update_Result result) : std::invalid_argument(result.message), result_(std::move(result)) {}
const Update_Result& result() const noexcept {
return result_;
}
private:
Update_Result result_;
};
class Field_Validation_Error : public std::invalid_argument {
public:
Field_Validation_Error(std::string path, std::string message) : std::invalid_argument(message), path_(std::move(path)) {}
const std::string& path() const noexcept {
return path_;
}
private:
std::string path_;
};
class Validation_Context {
public:
void error(std::string path, std::string message) {
errors_.insert_or_assign(std::move(path), std::move(message));
}
bool empty() const noexcept {
return errors_.empty();
}
const std::map<std::string, std::string>& errors() const noexcept {
return errors_;
}
private:
std::map<std::string, std::string> errors_;
};
inline std::string join_field_path(std::string_view prefix, std::string_view child) {
if(prefix.empty()) {
return std::string(child);
}
if(child.empty()) {
return std::string(prefix);
}
if(child.front() == '[') {
return std::string(prefix) + std::string(child);
}
return std::string(prefix) + "." + std::string(child);
}
inline void merge_update_errors(Update_Result& target, const Update_Result& source, std::string_view prefix = {}) {
if(target.message.empty() && !source.message.empty()) {
target.message = source.message;
}
if(source.field_errors.empty() && !prefix.empty() && !source.message.empty()) {
target.field_errors.insert_or_assign(std::string(prefix), source.message);
return;
}
for(const auto& [path, message] : source.field_errors) {
target.field_errors.insert_or_assign(join_field_path(prefix, path), message);
}
}
template <class 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>>
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>) && (!Managed_Value_Type<T>) && (!Managed_Field_Type<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>) && (!Managed_Value_Type<T>) && (!Managed_Field_Type<T>)
Json to_frontend_json(const T& value);
template <Json_Type Json, Writable_Adapted_Object T>
Update_Result assign_json(T& target, const Json& value);
template <Json_Type Json, Writable_Adapted_Object T>
Update_Result apply_object_json(T& target, const Json& value, Write_Mode mode, bool complete);
template <Json_Type Json, class T>
void assign_json_value(T& target, const Json& value, Write_Context context);
template <Json_Type Json, class T>
void assign_json_value(T& target, const Json& value) {
assign_json_value<Json>(target, value, Write_Context{});
}
template <Json_Type Json, class T>
Json encode_json_value(const T& value) {
using Storage = std::remove_cvref_t<T>;
using Value = Adapted_Value_Type<Storage, Json>;
if constexpr(Value_Adapter_With_Encode<Storage, Json>) {
return Value_Adapter<Storage, Json>::encode(value);
} else if constexpr(!std::same_as<Storage, Value>) {
return encode_json_value<Json>(read_adapted_value<Storage, Json>(value));
} else if constexpr(Optional_Type<Value>) {
if(!value) {
return json_null<Json>();
}
return encode_json_value<Json>(*value);
} else if constexpr(Polymorphic_Type<Value, Json>) {
return Polymorphic_Adapter<Value, Json>::encode(value);
} else if constexpr(std::is_enum_v<Value>) {
static_assert(Enum_Type<Value>, "enum type requires an adminive::Enum_Adapter specialization");
const auto name = enum_name(value);
if(!name.empty()) {
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> || Snapshot_Adapted_Object<Value>) {
return to_json<Json>(value);
} else if constexpr(Container_Like_Type<Value>) {
static_assert(Always_False<Value>, "container types require an explicit adminive::Value_Adapter; Adminive does not infer vector/map serialization or UI semantics");
} else {
static_assert(Json_Scalar_Makeable<Json, Value>, "JSON adapter cannot encode this scalar type");
return json_scalar<Json>(value);
}
}
template <Json_Type Json, class Value>
Value decode_integral_value(const Json& value) {
static_assert(std::integral<Value> && !std::same_as<Value, bool>);
if(Json_Adapter<Json>::is_signed_integer(value)) {
const std::int64_t source = Json_Adapter<Json>::signed_integer(value);
if constexpr(std::unsigned_integral<Value>) {
if(source < 0 || static_cast<std::uint64_t>(source) > static_cast<std::uint64_t>(std::numeric_limits<Value>::max())) {
throw std::out_of_range("integer value is outside the target type range");
}
} else if(source < static_cast<std::int64_t>(std::numeric_limits<Value>::min()) || source > static_cast<std::int64_t>(std::numeric_limits<Value>::max())) {
throw std::out_of_range("integer value is outside the target type range");
}
return static_cast<Value>(source);
}
if(Json_Adapter<Json>::is_unsigned_integer(value)) {
const std::uint64_t source = Json_Adapter<Json>::unsigned_integer(value);
if(source > static_cast<std::uint64_t>(std::numeric_limits<Value>::max())) {
throw std::out_of_range("integer value is outside the target type range");
}
return static_cast<Value>(source);
}
throw std::invalid_argument("value must be an integer");
}
template <Json_Type Json, class Value>
Value decode_floating_value(const Json& value) {
static_assert(std::floating_point<Value>);
if(!Json_Adapter<Json>::is_number(value)) {
throw std::invalid_argument("value must be a number");
}
const long double source = Json_Adapter<Json>::number(value);
if(!std::isfinite(source) || source < static_cast<long double>(std::numeric_limits<Value>::lowest()) || source > static_cast<long double>(std::numeric_limits<Value>::max())) {
throw std::out_of_range("number is outside the target type range");
}
return static_cast<Value>(source);
}
template <Json_Type Json, class Value>
Value decode_scalar_value(const Json& value) {
if constexpr(std::same_as<Value, bool>) {
if(!Json_Adapter<Json>::is_boolean(value)) {
throw std::invalid_argument("value must be a boolean");
}
return json_get<Json, bool>(value);
} else if constexpr(std::integral<Value>) {
return decode_integral_value<Json, Value>(value);
} else if constexpr(std::floating_point<Value>) {
return decode_floating_value<Json, Value>(value);
} else if constexpr(String_Type<Value>) {
if(!Json_Adapter<Json>::is_string(value)) {
throw std::invalid_argument("value must be a string");
}
return json_get<Json, std::string>(value);
} else if constexpr(String_View_Type<Value>) {
static_assert(Always_False<Value>, "std::string_view is read-only storage; use std::string or provide a Value_Adapter");
} else {
static_assert(Json_Scalar_Gettable<Json, Value>, "JSON adapter cannot decode this scalar type");
return json_get<Json, Value>(value);
}
}
template <Json_Type Json, class T>
void assign_json_value(T& target, const Json& value, Write_Context context) {
using Storage = std::remove_cvref_t<T>;
using Value = Adapted_Value_Type<Storage, Json>;
if constexpr(Value_Adapter_With_Decode<Storage, Json>) {
Value_Adapter<Storage, Json>::decode(target, value);
} else if constexpr(!std::same_as<Storage, Value>) {
Value parsed{};
assign_json_value<Json>(parsed, value, context);
write_adapted_value<Storage, Json>(target, std::move(parsed));
} else if constexpr(Optional_Type<Value>) {
if(Json_Adapter<Json>::is_null(value)) {
target.reset();
return;
}
Optional_Value_Type<Value> parsed = target ? *target : Optional_Value_Type<Value>{};
assign_json_value<Json>(parsed, value, context);
target = std::move(parsed);
} else if constexpr(Polymorphic_Type<Value, Json>) {
Polymorphic_Adapter<Value, Json>::decode(target, value, context);
} else if constexpr(std::is_enum_v<Value>) {
static_assert(Enum_Type<Value>, "enum type requires an adminive::Enum_Adapter specialization");
std::optional<Value> parsed;
if(Json_Adapter<Json>::is_string(value)) {
parsed = enum_cast<Value>(json_get<Json, std::string>(value));
} else if constexpr(Enum_Adapter_With_Underlying_Cast<Value>) {
using Underlying = std::underlying_type_t<Value>;
parsed = Enum_Adapter<Value>::cast(decode_integral_value<Json, Underlying>(value));
}
if(!parsed) {
throw std::invalid_argument("unknown enum value");
}
target = *parsed;
} else if constexpr(Writable_Adapted_Object<Value>) {
const auto result = apply_object_json<Json>(target, value, context.mode, context.complete);
if(!result.success) {
throw Json_Assignment_Error(result);
}
} else if constexpr(Container_Like_Type<Value>) {
static_assert(Always_False<Value>, "container types require an explicit adminive::Value_Adapter; Adminive does not infer vector/map serialization or UI semantics");
} else {
target = decode_scalar_value<Json, Value>(value);
}
}
template <Json_Type Json, class T>
std::string value_type_name() {
using Storage = std::remove_cvref_t<T>;
using Value = Adapted_Value_Type<Storage, Json>;
if constexpr(Value_Adapter_With_Type_Name<Storage, Json>) {
return std::string(Value_Adapter<Storage, Json>::type_name);
} else if constexpr(Optional_Type<Value>) {
return value_type_name<Json, Optional_Value_Type<Value>>();
} else if constexpr(Polymorphic_Type<Value, Json>) {
return "polymorphic";
} else if constexpr(std::is_enum_v<Value>) {
return "enum";
} else if constexpr(std::same_as<Value, bool>) {
return "boolean";
} else if constexpr(std::integral<Value>) {
return "integer";
} else if constexpr(std::floating_point<Value>) {
return "number";
} else if constexpr(String_Type<Value> || String_View_Type<Value>) {
return "string";
} else if constexpr(Described_Type<Value> || Snapshot_Adapted_Object<Value>) {
return "object";
} else if constexpr(Container_Like_Type<Value>) {
static_assert(Always_False<Value>, "container types require an explicit adminive::Value_Adapter with type_name");
} else {
return "unknown";
}
}
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>;
if constexpr(Value_Adapter_With_Minimum<Storage, Json>) {
json_set(result, "minimum", Value_Adapter<Storage, Json>::minimum);
}
if constexpr(Value_Adapter_With_Maximum<Storage, Json>) {
json_set(result, "maximum", Value_Adapter<Storage, Json>::maximum);
}
if constexpr(Value_Adapter_With_Multiple_Of<Storage, Json>) {
json_set(result, "multiple_of", Value_Adapter<Storage, Json>::multiple_of);
}
if constexpr(Value_Adapter_With_Validation_Message<Storage, Json>) {
json_set(result, "validation_message", std::string(Value_Adapter<Storage, Json>::validation_message));
}
if constexpr(!std::same_as<Storage, Value>) {
append_constraints<Json, Value>(result);
} else if constexpr(Optional_Type<Value>) {
append_constraints<Json, Optional_Value_Type<Value>>(result);
}
if constexpr(Polymorphic_Type<Optional_Unwrapped_Type<Value>, Json>) {
json_set(result, "polymorphic", polymorphic_descriptor_json<Json, Optional_Unwrapped_Type<Value>>());
}
if constexpr(Value_Adapter_With_Schema<Storage, Json>) {
Value_Adapter<Storage, Json>::append_schema(result);
}
}
template <Json_Type Json, class T>
Json enum_options(const std::map<std::string, std::string>& labels) {
using Value = Optional_Unwrapped_Type<Adapted_Value_Type<T, Json>>;
Json result = json_array<Json>();
if constexpr(std::is_enum_v<Value>) {
static_assert(Enum_Type<Value>, "enum type requires an adminive::Enum_Adapter specialization");
for(const auto value : enum_values<Value>()) {
const std::string name(enum_name(value));
const auto iterator = labels.find(name);
Json option = json_object<Json>();
json_set(option, "label", iterator == labels.end() ? make_label(name) : iterator->second);
json_set(option, "value", name);
json_append(result, std::move(option));
}
}
return result;
}
template <Json_Type Json>
Json make_polymorphic_variant(std::string value, std::string label, Json descriptor) {
Json result = json_object<Json>();
json_set(result, "value", std::move(value));
json_set(result, "label", std::move(label));
json_set(result, "descriptor", std::move(descriptor));
return result;
}
template <Json_Type Json>
Json make_polymorphic_descriptor(std::string discriminator, std::string discriminator_label, Json variants) {
Json options = json_array<Json>();
for(std::size_t index = 0; index < Json_Adapter<Json>::size(variants); ++index) {
const auto& variant = Json_Adapter<Json>::at(variants, index);
Json option = json_object<Json>();
json_set(option, "label", Json_Adapter<Json>::at(variant, "label"));
json_set(option, "value", Json_Adapter<Json>::at(variant, "value"));
json_append(options, std::move(option));
}
Json result = json_object<Json>();
json_set(result, "discriminator", std::move(discriminator));
json_set(result, "discriminator_label", std::move(discriminator_label));
json_set(result, "options", std::move(options));
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(!valid_data_name(discriminator)) {
throw std::invalid_argument("polymorphic discriminator must match [A-Za-z_][A-Za-z0-9_]*: " + discriminator);
}
if(discriminator_label.empty()) {
throw std::invalid_argument("polymorphic discriminator label 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(variant.label.empty()) {
throw std::invalid_argument("polymorphic variant label must not be empty: " + variant.value);
}
const auto variant_descriptor = describe<Object_Model_Type<Variant>>();
std::apply([&](const auto&... field) {
if(((field.name() == discriminator) || ...)) {
throw std::invalid_argument("polymorphic discriminator conflicts with variant field: " + discriminator);
}
}, variant_descriptor.fields());
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 = nullptr) {
const auto descriptor = describe<Model>();
Json fields = json_array<Json>();
std::apply([&](const auto&... item) {
([&] {
using Field = std::remove_cvref_t<decltype(item)>;
using Member = typename Field::member_type;
using Value = Adapted_Value_Type<Member, Json>;
using Descriptor_Value = Optional_Unwrapped_Type<Value>;
Json field_json = json_object<Json>();
json_set(field_json, "name", item.name());
json_set(field_json, "value_type", value_type_name<Json, Member>());
json_set(field_json, "nullable", Optional_Type<Value>);
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, "writable", Field::managed_writable);
json_set(field_json, "synchronized", Field::managed_writable && Field::synchronized);
json_set(field_json, "creatable", item.is_creatable());
json_set(field_json, "required", item.is_required());
if(defaults && item.is_readable() && !item.is_sensitive() && item.includes_default()) {
json_set(field_json, "default", encode_json_value<Json>(item.get(*defaults)));
}
Json presentation = json_object<Json>();
json_set(presentation, "label", item.label());
json_set(presentation, "control", std::string(field_control_name(item.control())));
if(!item.description().empty()) {
json_set(presentation, "description", item.description());
}
if(!item.visible_on().empty()) {
json_set(presentation, "visible_on", item.visible_on());
}
auto options = enum_options<Json, Member>(item.enum_labels());
if(Json_Adapter<Json>::size(options) != 0) {
json_set(presentation, "options", std::move(options));
}
json_set(field_json, "presentation", std::move(presentation));
if constexpr(Described_Type<Descriptor_Value> || Snapshot_Adapted_Object<Descriptor_Value>) {
json_set(field_json, "children", Json_Adapter<Json>::at(to_descriptor_json<Json, Descriptor_Value>(), "fields"));
}
append_constraints<Json, Member>(field_json);
json_append(fields, std::move(field_json));
}(), ...);
}, descriptor.fields());
Json result = json_object<Json>();
json_set(result, "protocol", "adminive.resource");
json_set(result, "protocol_version", 5);
json_set(result, "name", descriptor.name());
json_set(result, "label", descriptor.label());
json_set(result, "value_type", "object");
json_set(result, "fields", std::move(fields));
return result;
}
template <Json_Type Json, class T>
requires Described_Type<Object_Model_Type<T>>
Json to_descriptor_json() {
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) {
const auto descriptor = describe<T>();
Json result = json_object<Json>();
std::apply([&](const auto&... item) {
([&] {
if(!frontend || (item.is_readable() && !item.is_sensitive())) {
json_set(result, item.name(), encode_json_value<Json>(item.get(value)));
}
}(), ...);
}, descriptor.fields());
return result;
}
template <Json_Type Json, class T>
requires (Described_Type<std::remove_cvref_t<T>> || Object_Adapter_With_Snapshot<T>) && (!Managed_Value_Type<T>) && (!Managed_Field_Type<T>)
Json to_json(const T& value) {
using Object = std::remove_cvref_t<T>;
if constexpr(std::same_as<Object, Object_Model_Type<Object>>) {
return model_to_json<Json>(value, false);
} else {
return model_to_json<Json>(Object_Adapter<Object>::snapshot(value), false);
}
}
template <Json_Type Json, class T>
requires (Described_Type<std::remove_cvref_t<T>> || Object_Adapter_With_Snapshot<T>) && (!Managed_Value_Type<T>) && (!Managed_Field_Type<T>)
Json to_frontend_json(const T& value) {
using Object = std::remove_cvref_t<T>;
if constexpr(std::same_as<Object, Object_Model_Type<Object>>) {
return model_to_json<Json>(value, true);
} else {
return model_to_json<Json>(Object_Adapter<Object>::snapshot(value), true);
}
}
template <Json_Type Json, class T>
requires (Described_Type<std::remove_cvref_t<T>> || Object_Adapter_With_Snapshot<T>) && (!Managed_Value_Type<T>) && (!Managed_Field_Type<T>)
Json to_data_json(const T& value) {
return to_frontend_json<Json>(value);
}
template <Json_Type Json, Managed_Value_Type T>
Json to_json(const T& value) {
return value.read([](const auto& item) {
return adminive::to_json<Json>(item);
});
}
template <Json_Type Json, Managed_Value_Type T>
Json to_frontend_json(const T& value) {
return value.read([](const auto& item) {
return adminive::to_frontend_json<Json>(item);
});
}
template <Json_Type Json, Managed_Value_Type T>
Json to_data_json(const T& value) {
return to_frontend_json<Json>(value);
}
template <Json_Type Json, Managed_Field_Type T>
Json to_json(const T& value) {
return value.read([](const auto& item) {
return adminive::to_json<Json>(item);
});
}
template <Json_Type Json, Managed_Field_Type T>
Json to_frontend_json(const T& value) {
return value.read([](const auto& item) {
return adminive::to_frontend_json<Json>(item);
});
}
template <Json_Type Json, Managed_Field_Type T>
Json to_data_json(const T& value) {
return to_frontend_json<Json>(value);
}
template <class Field>
bool field_writable(const Field& field, Write_Mode mode) {
using Descriptor = std::remove_cvref_t<Field>;
if constexpr(!Descriptor::managed_writable) {
return false;
}
if(mode == Write_Mode::internal) {
return true;
}
if(mode == Write_Mode::create) {
return field.is_creatable();
}
return field.is_editable();
}
template <Json_Type Json, Described_Type Model>
Update_Result apply_model_json(Model& candidate, const Json& value, Write_Mode mode, bool complete) {
Update_Result result;
if(!Json_Adapter<Json>::is_object(value)) {
result.message = "value must be a JSON object";
return result;
}
const auto descriptor = describe<Model>();
for(const auto& key : Json_Adapter<Json>::keys(value)) {
bool known{};
std::apply([&](const auto&... item) {
known = ((key == item.name()) || ...);
}, descriptor.fields());
if(!known) {
result.field_errors[key] = "unknown field";
}
}
std::apply([&](const auto&... item) {
([&] {
const bool present = Json_Adapter<Json>::contains(value, item.name());
const bool writable = field_writable(item, mode);
if(complete && item.is_required() && writable && !present) {
result.field_errors[item.name()] = "field is required";
return;
}
if(!present) {
return;
}
if(!writable) {
result.field_errors[item.name()] = mode == Write_Mode::create ? "field is not creatable" : "field is not editable";
return;
}
try {
assign_json_value<Json>(item.get(candidate), Json_Adapter<Json>::at(value, item.name()), Write_Context{mode, complete});
} catch(const Json_Assignment_Error& error) {
merge_update_errors(result, error.result(), item.name());
} catch(const Field_Validation_Error& error) {
result.field_errors[join_field_path(item.name(), error.path())] = error.what();
} catch(const std::exception& error) {
result.field_errors[item.name()] = error.what();
}
}(), ...);
}, descriptor.fields());
if(!result.field_errors.empty()) {
result.message = "one or more fields are invalid";
return result;
}
try {
if constexpr(std::invocable<typename std::remove_cvref_t<decltype(descriptor)>::validator_type, const Model&, Validation_Context&>) {
Validation_Context validation;
descriptor.object_validator()(candidate, validation);
for(const auto& [path, message] : validation.errors()) {
result.field_errors.insert_or_assign(path, message);
}
} else {
descriptor.object_validator()(candidate);
}
} catch(const Json_Assignment_Error& error) {
merge_update_errors(result, error.result());
} catch(const Field_Validation_Error& error) {
result.field_errors[error.path()] = error.what();
} catch(const std::exception& error) {
result.message = error.what();
return result;
}
if(!result.field_errors.empty()) {
result.message = "one or more fields are invalid";
return result;
}
result.success = true;
result.message = complete ? "assigned" : "updated";
return result;
}
template <Json_Type Json, class Alternative, class Variant>
requires Creatable_Writable_Object<Alternative>
void decode_polymorphic_alternative(Variant& target, const Json& input, Write_Context context) {
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<Alternative>>();
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());
if(context.mode == Write_Mode::update && std::holds_alternative<Alternative>(target)) {
auto& current = std::get<Alternative>(target);
auto update = apply_object_json<Json>(current, filtered, Write_Mode::update, false);
if(!update.success) {
throw Json_Assignment_Error(std::move(update));
}
return;
}
Alternative created = Object_Adapter<Alternative>::create();
const Write_Mode mode = context.mode == Write_Mode::update ? Write_Mode::create : context.mode;
const bool complete = context.mode == Write_Mode::update ? true : context.complete;
auto update = apply_object_json<Json>(created, filtered, mode, complete);
if(!update.success) {
throw Json_Assignment_Error(std::move(update));
}
target = std::move(created);
}
template <class T>
Update_Result object_commit_error(const Field_Validation_Error& error) {
Update_Result result;
result.message = "one or more fields are invalid";
result.field_errors[error.path()] = error.what();
return result;
}
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 = [&] {
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;
}
try {
Object_Adapter<Object>::commit(target, std::move(candidate));
} catch(const Json_Assignment_Error& error) {
return error.result();
} catch(const Field_Validation_Error& error) {
return object_commit_error<Object>(error);
} catch(const std::exception& error) {
result.success = false;
result.message = error.what();
}
return result;
}
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, 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, 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, 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, Writable_Adapted_Object T>
Update_Result apply_patch(T& target, const Json& patch) {
return apply_frontend_patch<Json>(target, patch);
}
template <Json_Type Json, Managed_Value_Type T>
requires Writable_Adapted_Object<typename std::remove_cvref_t<T>::value_type>
Update_Result assign_json(T& target, const Json& value) {
return target.write([&](auto& item) {
return adminive::assign_json<Json>(item, value);
});
}
template <Json_Type Json, Managed_Value_Type T>
requires Writable_Adapted_Object<typename std::remove_cvref_t<T>::value_type>
Update_Result apply_json_patch(T& target, const Json& patch) {
return target.write([&](auto& item) {
return adminive::apply_json_patch<Json>(item, patch);
});
}
template <Json_Type Json, Managed_Value_Type T>
requires Creatable_Writable_Object<typename std::remove_cvref_t<T>::value_type>
Update_Result apply_frontend_create(T& target, const Json& value) {
return target.write([&](auto& item) {
return adminive::apply_frontend_create<Json>(item, value);
});
}
template <Json_Type Json, Managed_Value_Type T>
requires Writable_Adapted_Object<typename std::remove_cvref_t<T>::value_type>
Update_Result apply_frontend_patch(T& target, const Json& patch) {
return target.write([&](auto& item) {
return adminive::apply_frontend_patch<Json>(item, patch);
});
}
template <Json_Type Json, Managed_Value_Type T>
requires Writable_Adapted_Object<typename std::remove_cvref_t<T>::value_type>
Update_Result apply_patch(T& target, const Json& patch) {
return apply_frontend_patch<Json>(target, patch);
}
template <Json_Type Json, Managed_Field_Type T>
requires Writable_Adapted_Object<typename std::remove_cvref_t<T>::value_type>
Update_Result assign_json(T target, const Json& value) {
return target.write([&](auto& item) {
return adminive::assign_json<Json>(item, value);
});
}
template <Json_Type Json, Managed_Field_Type T>
requires Writable_Adapted_Object<typename std::remove_cvref_t<T>::value_type>
Update_Result apply_json_patch(T target, const Json& patch) {
return target.write([&](auto& item) {
return adminive::apply_json_patch<Json>(item, patch);
});
}
template <Json_Type Json, Managed_Field_Type T>
requires Creatable_Writable_Object<typename std::remove_cvref_t<T>::value_type>
Update_Result apply_frontend_create(T target, const Json& value) {
return target.write([&](auto& item) {
return adminive::apply_frontend_create<Json>(item, value);
});
}
template <Json_Type Json, Managed_Field_Type T>
requires Writable_Adapted_Object<typename std::remove_cvref_t<T>::value_type>
Update_Result apply_frontend_patch(T target, const Json& patch) {
return target.write([&](auto& item) {
return adminive::apply_frontend_patch<Json>(item, patch);
});
}
template <Json_Type Json, Managed_Field_Type T>
requires Writable_Adapted_Object<typename std::remove_cvref_t<T>::value_type>
Update_Result apply_patch(T target, const Json& patch) {
return apply_frontend_patch<Json>(target, patch);
}
template <Json_Type Json, Writable_Adapted_Object T>
requires std::default_initializable<T>
T from_json(const Json& value) {
T result{};
auto update = assign_json<Json>(result, value);
if(!update.success) {
throw Json_Assignment_Error(std::move(update));
}
return result;
}
template <class T>
requires (Described_Type<std::remove_cvref_t<T>> || Object_Adapter_With_Snapshot<T>) && (!Managed_Value_Type<T>) && (!Managed_Field_Type<T>)
Update_Result validate(const T& value) {
Update_Result result;
try {
using Object = std::remove_cvref_t<T>;
auto validate_model = [&](const auto& model) {
const auto descriptor = describe<std::remove_cvref_t<decltype(model)>>();
if constexpr(std::invocable<typename std::remove_cvref_t<decltype(descriptor)>::validator_type, const std::remove_cvref_t<decltype(model)>&, Validation_Context&>) {
Validation_Context validation;
descriptor.object_validator()(model, validation);
for(const auto& [path, message] : validation.errors()) {
result.field_errors.insert_or_assign(path, message);
}
} else {
descriptor.object_validator()(model);
}
};
if constexpr(std::same_as<Object, Object_Model_Type<Object>>) {
validate_model(value);
} else {
const auto model = Object_Adapter<Object>::snapshot(value);
validate_model(model);
}
result.success = result.field_errors.empty();
if(!result.success) {
result.message = "one or more fields are invalid";
return result;
}
result.message = "valid";
} catch(const Field_Validation_Error& error) {
result.message = "one or more fields are invalid";
result.field_errors[error.path()] = error.what();
} catch(const std::exception& error) {
result.message = error.what();
}
return result;
}
}