#pragma once #include "adminive/adapter.hpp" #include "adminive/descriptor.hpp" #include "adminive/managed.hpp" #include #include #include #include #include #include #include #include #include #include #include #include namespace adminive { inline constexpr std::string_view resource_protocol = "adminive.resource"; inline constexpr int resource_protocol_version = 5; struct Update_Result { bool success{}; std::string message; std::map field_errors; template Json to_json() const { Json result = json_object(); json_set(result, "success", success); json_set(result, "message", message); Json errors = json_object(); 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& errors() const noexcept { return errors_; } private: std::map 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 concept Snapshot_Adapted_Object = Object_Adapter_With_Snapshot && Described_Type>; template concept Creatable_Adapted_Object = Object_Adapter_With_Create && Described_Type>; template concept Writable_Adapted_Object = Snapshot_Adapted_Object && Object_Adapter_With_Commit; template concept Creatable_Writable_Object = Writable_Adapted_Object && Object_Adapter_With_Create; template concept Direct_Described_Object = Described_Type> && (!Writable_Adapted_Object); template requires Described_Type> Json to_descriptor_json(); template requires Creatable_Adapted_Object Json to_descriptor_json_with_defaults(); template requires (Described_Type> || Object_Adapter_With_Snapshot) && (!Managed_Value_Type) && (!Managed_Field_Type) Json to_json(const T& value); template requires (Described_Type> || Object_Adapter_With_Snapshot) && (!Managed_Value_Type) && (!Managed_Field_Type) Json to_frontend_json(const T& value); template Update_Result assign_json(T& target, const Json& value); template Update_Result assign_json(T& target, const Json& value); template Update_Result apply_object_json(T& target, const Json& value, Write_Mode mode, bool complete); template void assign_json_value(T& target, const Json& value, Write_Context context); template void assign_json_value(T& target, const Json& value) { assign_json_value(target, value, Write_Context{}); } template Json encode_json_value(const T& value) { using Storage = std::remove_cvref_t; using Value = Adapted_Value_Type; if constexpr(Value_Adapter_With_Encode) { return Value_Adapter::encode(value); } else if constexpr(!std::same_as) { return encode_json_value(read_adapted_value(value)); } else if constexpr(Optional_Type) { if(!value) { return json_null(); } return encode_json_value(*value); } else if constexpr(Polymorphic_Type) { return Polymorphic_Adapter::encode(value); } else if constexpr(std::is_enum_v) { static_assert(Enum_Type, "enum type requires an adminive::Enum_Adapter specialization"); const auto name = enum_name(value); if(!name.empty()) { return json_scalar(std::string(name)); } return json_scalar(static_cast>(value)); } else if constexpr(Described_Type || Snapshot_Adapted_Object) { return to_json(value); } else if constexpr(Container_Like_Type) { static_assert(Always_False, "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 adapter cannot encode this scalar type"); return json_scalar(value); } } template Value decode_integral_value(const Json& value) { static_assert(std::integral && !std::same_as); if(Json_Adapter::is_signed_integer(value)) { const std::int64_t source = Json_Adapter::signed_integer(value); if constexpr(std::unsigned_integral) { if(source < 0 || static_cast(source) > static_cast(std::numeric_limits::max())) { throw std::out_of_range("integer value is outside the target type range"); } } else if(source < static_cast(std::numeric_limits::min()) || source > static_cast(std::numeric_limits::max())) { throw std::out_of_range("integer value is outside the target type range"); } return static_cast(source); } if(Json_Adapter::is_unsigned_integer(value)) { const std::uint64_t source = Json_Adapter::unsigned_integer(value); if(source > static_cast(std::numeric_limits::max())) { throw std::out_of_range("integer value is outside the target type range"); } return static_cast(source); } throw std::invalid_argument("value must be an integer"); } template Value decode_floating_value(const Json& value) { static_assert(std::floating_point); if(!Json_Adapter::is_number(value)) { throw std::invalid_argument("value must be a number"); } const long double source = Json_Adapter::number(value); if(!std::isfinite(source) || source < static_cast(std::numeric_limits::lowest()) || source > static_cast(std::numeric_limits::max())) { throw std::out_of_range("number is outside the target type range"); } return static_cast(source); } template Value decode_scalar_value(const Json& value) { if constexpr(std::same_as) { if(!Json_Adapter::is_boolean(value)) { throw std::invalid_argument("value must be a boolean"); } return json_get(value); } else if constexpr(std::integral) { return decode_integral_value(value); } else if constexpr(std::floating_point) { return decode_floating_value(value); } else if constexpr(String_Type) { if(!Json_Adapter::is_string(value)) { throw std::invalid_argument("value must be a string"); } return json_get(value); } else if constexpr(String_View_Type) { static_assert(Always_False, "std::string_view is read-only storage; use std::string or provide a Value_Adapter"); } else { static_assert(Json_Scalar_Gettable, "JSON adapter cannot decode this scalar type"); return json_get(value); } } template void assign_json_value(T& target, const Json& value, Write_Context context) { using Storage = std::remove_cvref_t; using Value = Adapted_Value_Type; if constexpr(Value_Adapter_With_Decode) { Value_Adapter::decode(target, value); } else if constexpr(!std::same_as) { Value parsed{}; assign_json_value(parsed, value, context); write_adapted_value(target, std::move(parsed)); } else if constexpr(Optional_Type) { if(Json_Adapter::is_null(value)) { target.reset(); return; } Optional_Value_Type parsed = target ? *target : Optional_Value_Type{}; assign_json_value(parsed, value, context); target = std::move(parsed); } else if constexpr(Polymorphic_Type) { Polymorphic_Adapter::decode(target, value, context); } else if constexpr(std::is_enum_v) { static_assert(Enum_Type, "enum type requires an adminive::Enum_Adapter specialization"); std::optional parsed; if(Json_Adapter::is_string(value)) { parsed = enum_cast(json_get(value)); } else if constexpr(Enum_Adapter_With_Underlying_Cast) { using Underlying = std::underlying_type_t; parsed = Enum_Adapter::cast(decode_integral_value(value)); } if(!parsed) { throw std::invalid_argument("unknown enum value"); } target = *parsed; } else if constexpr(Writable_Adapted_Object) { const auto result = apply_object_json(target, value, context.mode, context.complete); if(!result.success) { throw Json_Assignment_Error(result); } } else if constexpr(Container_Like_Type) { static_assert(Always_False, "container types require an explicit adminive::Value_Adapter; Adminive does not infer vector/map serialization or UI semantics"); } else { target = decode_scalar_value(value); } } template std::string value_type_name() { using Storage = std::remove_cvref_t; using Value = Adapted_Value_Type; if constexpr(Value_Adapter_With_Type_Name) { return std::string(Value_Adapter::type_name); } else if constexpr(Optional_Type) { return value_type_name>(); } else if constexpr(Polymorphic_Type) { return "polymorphic"; } else if constexpr(std::is_enum_v) { return "enum"; } else if constexpr(std::same_as) { return "boolean"; } else if constexpr(std::integral) { return "integer"; } else if constexpr(std::floating_point) { return "number"; } else if constexpr(String_Type || String_View_Type) { return "string"; } else if constexpr(Described_Type || Snapshot_Adapted_Object) { return "object"; } else if constexpr(Container_Like_Type) { static_assert(Always_False, "container types require an explicit adminive::Value_Adapter with type_name"); } else { return "unknown"; } } template requires Polymorphic_Type Json polymorphic_descriptor_json(); template void append_constraints(Json& result) { using Storage = std::remove_cvref_t; using Value = Adapted_Value_Type; if constexpr(Value_Adapter_With_Minimum) { json_set(result, "minimum", Value_Adapter::minimum); } if constexpr(Value_Adapter_With_Maximum) { json_set(result, "maximum", Value_Adapter::maximum); } if constexpr(Value_Adapter_With_Multiple_Of) { json_set(result, "multiple_of", Value_Adapter::multiple_of); } if constexpr(Value_Adapter_With_Validation_Message) { json_set(result, "validation_message", std::string(Value_Adapter::validation_message)); } if constexpr(!std::same_as) { append_constraints(result); } else if constexpr(Optional_Type) { append_constraints>(result); } if constexpr(Polymorphic_Type, Json>) { json_set(result, "polymorphic", polymorphic_descriptor_json>()); } if constexpr(Value_Adapter_With_Schema) { Value_Adapter::append_schema(result); } } template Json enum_options(const std::map& labels) { using Value = Optional_Unwrapped_Type>; Json result = json_array(); if constexpr(std::is_enum_v) { static_assert(Enum_Type, "enum type requires an adminive::Enum_Adapter specialization"); for(const auto value : enum_values()) { const std::string name(enum_name(value)); const auto iterator = labels.find(name); Json option = json_object(); 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 make_polymorphic_variant(std::string value, std::string label, Json descriptor) { Json result = json_object(); 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 make_polymorphic_descriptor(std::string discriminator, std::string discriminator_label, Json variants) { Json options = json_array(); for(std::size_t index = 0; index < Json_Adapter::size(variants); ++index) { const auto& variant = Json_Adapter::at(variants, index); Json option = json_object(); json_set(option, "label", Json_Adapter::at(variant, "label")); json_set(option, "value", Json_Adapter::at(variant, "value")); json_append(options, std::move(option)); } Json result = json_object(); 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 requires Polymorphic_Type Json polymorphic_descriptor_json() { using Adapter = Polymorphic_Adapter, 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(); std::set values; std::apply([&](const auto&... variant) { ([&] { using Variant = typename std::remove_cvref_t::type; static_assert(Described_Type>, "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>(); 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(variant.value, variant.label, to_descriptor_json())); }(), ...); }, Adapter::variants()); return make_polymorphic_descriptor(discriminator, discriminator_label, std::move(variants)); } template requires Described_Type Json model_descriptor_json(const Model* defaults = nullptr) { const auto descriptor = describe(); Json fields = json_array(); std::apply([&](const auto&... item) { ([&] { using Field = std::remove_cvref_t; using Member = typename Field::member_type; using Value = Adapted_Value_Type; using Descriptor_Value = Optional_Unwrapped_Type; Json field_json = json_object(); json_set(field_json, "name", item.name()); json_set(field_json, "value_type", value_type_name()); json_set(field_json, "nullable", Optional_Type); 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(item.get(*defaults))); } Json presentation = json_object(); 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(item.enum_labels()); if(Json_Adapter::size(options) != 0) { json_set(presentation, "options", std::move(options)); } json_set(field_json, "presentation", std::move(presentation)); if constexpr(Described_Type || Snapshot_Adapted_Object) { json_set(field_json, "children", Json_Adapter::at(to_descriptor_json(), "fields")); } append_constraints(field_json); json_append(fields, std::move(field_json)); }(), ...); }, descriptor.fields()); Json result = json_object(); json_set(result, "protocol", resource_protocol); json_set(result, "protocol_version", resource_protocol_version); 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 requires Described_Type> Json to_descriptor_json() { using Model = Object_Model_Type; return model_descriptor_json(); } template requires Creatable_Adapted_Object Json to_descriptor_json_with_defaults() { using Object = std::remove_cvref_t; const auto defaults = Object_Adapter::create(); return model_descriptor_json(&defaults); } template Json to_descriptor_json(const Model& defaults) { return model_descriptor_json(&defaults); } template Json model_to_json(const T& value, bool frontend) { const auto descriptor = describe(); Json result = json_object(); std::apply([&](const auto&... item) { ([&] { if(!frontend || (item.is_readable() && !item.is_sensitive())) { json_set(result, item.name(), encode_json_value(item.get(value))); } }(), ...); }, descriptor.fields()); return result; } template requires (Described_Type> || Object_Adapter_With_Snapshot) && (!Managed_Value_Type) && (!Managed_Field_Type) Json to_json(const T& value) { using Object = std::remove_cvref_t; if constexpr(std::same_as>) { return model_to_json(value, false); } else { return model_to_json(Object_Adapter::snapshot(value), false); } } template requires (Described_Type> || Object_Adapter_With_Snapshot) && (!Managed_Value_Type) && (!Managed_Field_Type) Json to_frontend_json(const T& value) { using Object = std::remove_cvref_t; if constexpr(std::same_as>) { return model_to_json(value, true); } else { return model_to_json(Object_Adapter::snapshot(value), true); } } template requires (Described_Type> || Object_Adapter_With_Snapshot) && (!Managed_Value_Type) && (!Managed_Field_Type) Json to_data_json(const T& value) { return to_frontend_json(value); } template Json to_json(const T& value) { return value.read([](const auto& item) { return adminive::to_json(item); }); } template Json to_frontend_json(const T& value) { return value.read([](const auto& item) { return adminive::to_frontend_json(item); }); } template Json to_data_json(const T& value) { return to_frontend_json(value); } template Json to_json(const T& value) { return value.read([](const auto& item) { return adminive::to_json(item); }); } template Json to_frontend_json(const T& value) { return value.read([](const auto& item) { return adminive::to_frontend_json(item); }); } template Json to_data_json(const T& value) { return to_frontend_json(value); } template bool field_writable(const Field& field, Write_Mode mode) { using Descriptor = std::remove_cvref_t; 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 Update_Result apply_model_json(Model& candidate, const Json& value, Write_Mode mode, bool complete) { Update_Result result; if(!Json_Adapter::is_object(value)) { result.message = "value must be a JSON object"; return result; } const auto descriptor = describe(); for(const auto& key : Json_Adapter::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::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 { using Field = std::remove_cvref_t; using Field_Value = typename Field::member_type; if constexpr(std::is_lvalue_reference_v && !std::is_const_v>) { assign_json_value(item.get(candidate), Json_Adapter::at(value, item.name()), Write_Context{mode, complete}); } else { Field_Value field_candidate(item.get(std::as_const(candidate))); assign_json_value(field_candidate, Json_Adapter::at(value, item.name()), Write_Context{mode, complete}); item.set(candidate, std::move(field_candidate)); } } 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::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 requires Creatable_Writable_Object void decode_polymorphic_alternative(Variant& target, const Json& input, Write_Context context) { if(!Json_Adapter::is_object(input)) { throw std::invalid_argument("polymorphic value must be a JSON object"); } Json filtered = json_object(); const auto descriptor = describe>(); std::apply([&](const auto&... field) { ([&] { if(Json_Adapter::contains(input, field.name())) { json_set(filtered, field.name(), Json_Adapter::at(input, field.name())); } }(), ...); }, descriptor.fields()); if(context.mode == Write_Mode::update && std::holds_alternative(target)) { auto& current = std::get(target); auto update = apply_object_json(current, filtered, Write_Mode::update, false); if(!update.success) { throw Json_Assignment_Error(std::move(update)); } return; } Alternative created = Object_Adapter::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(created, filtered, mode, complete); if(!update.success) { throw Json_Assignment_Error(std::move(update)); } target = std::move(created); } template 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 Update_Result apply_object_json(T& target, const Json& value, Write_Mode mode, bool complete) { using Object = std::remove_cvref_t; auto candidate = [&] { if(mode == Write_Mode::create) { if constexpr(Object_Adapter_With_Create) { return Object_Adapter::create(); } else { throw std::logic_error("object adapter does not support creation"); } } return Object_Adapter::snapshot(target); }(); auto result = apply_model_json(candidate, value, mode, complete); if(!result.success) { return result; } try { Object_Adapter::commit(target, std::move(candidate)); } catch(const Json_Assignment_Error& error) { return error.result(); } catch(const Field_Validation_Error& error) { return object_commit_error(error); } catch(const std::exception& error) { result.success = false; result.message = error.what(); } return result; } template Update_Result assign_json(T& target, const Json& value) { return apply_object_json(target, value, Write_Mode::internal, true); } template Update_Result apply_json_patch(T& target, const Json& patch) { return apply_object_json(target, patch, Write_Mode::internal, false); } template Update_Result apply_frontend_create(T& target, const Json& value) { return apply_object_json(target, value, Write_Mode::create, true); } template Update_Result apply_frontend_patch(T& target, const Json& patch) { return apply_object_json(target, patch, Write_Mode::update, false); } template Update_Result apply_patch(T& target, const Json& patch) { return apply_frontend_patch(target, patch); } template Update_Result assign_json(T& target, const Json& value) { return apply_model_json(target, value, Write_Mode::internal, true); } template Update_Result apply_json_patch(T& target, const Json& patch) { return apply_model_json(target, patch, Write_Mode::internal, false); } template Update_Result apply_frontend_patch(T& target, const Json& patch) { return apply_model_json(target, patch, Write_Mode::update, false); } template Update_Result apply_patch(T& target, const Json& patch) { return apply_frontend_patch(target, patch); } template requires Writable_Adapted_Object::value_type> Update_Result assign_json(T& target, const Json& value) { return target.write([&](auto& item) { return adminive::assign_json(item, value); }); } template requires Writable_Adapted_Object::value_type> Update_Result apply_json_patch(T& target, const Json& patch) { return target.write([&](auto& item) { return adminive::apply_json_patch(item, patch); }); } template requires Creatable_Writable_Object::value_type> Update_Result apply_frontend_create(T& target, const Json& value) { return target.write([&](auto& item) { return adminive::apply_frontend_create(item, value); }); } template requires Writable_Adapted_Object::value_type> Update_Result apply_frontend_patch(T& target, const Json& patch) { return target.write([&](auto& item) { return adminive::apply_frontend_patch(item, patch); }); } template requires Writable_Adapted_Object::value_type> Update_Result apply_patch(T& target, const Json& patch) { return apply_frontend_patch(target, patch); } template requires Writable_Adapted_Object::value_type> Update_Result assign_json(T target, const Json& value) { return target.write([&](auto& item) { return adminive::assign_json(item, value); }); } template requires Writable_Adapted_Object::value_type> Update_Result apply_json_patch(T target, const Json& patch) { return target.write([&](auto& item) { return adminive::apply_json_patch(item, patch); }); } template requires Creatable_Writable_Object::value_type> Update_Result apply_frontend_create(T target, const Json& value) { return target.write([&](auto& item) { return adminive::apply_frontend_create(item, value); }); } template requires Writable_Adapted_Object::value_type> Update_Result apply_frontend_patch(T target, const Json& patch) { return target.write([&](auto& item) { return adminive::apply_frontend_patch(item, patch); }); } template requires Writable_Adapted_Object::value_type> Update_Result apply_patch(T target, const Json& patch) { return apply_frontend_patch(target, patch); } template requires std::default_initializable T from_json(const Json& value) { T result{}; auto update = assign_json(result, value); if(!update.success) { throw Json_Assignment_Error(std::move(update)); } return result; } template requires (Described_Type> || Object_Adapter_With_Snapshot) && (!Managed_Value_Type) && (!Managed_Field_Type) Update_Result validate(const T& value) { Update_Result result; try { using Object = std::remove_cvref_t; auto validate_model = [&](const auto& model) { const auto descriptor = describe>(); if constexpr(std::invocable::validator_type, const std::remove_cvref_t&, 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>) { validate_model(value); } else { const auto model = Object_Adapter::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; } }