863 lines
38 KiB
C++
863 lines
38 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 <string_view>
|
|
#include <type_traits>
|
|
#include <utility>
|
|
#include <variant>
|
|
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<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 <class T>
|
|
concept Direct_Described_Object = Described_Type<std::remove_cvref_t<T>> &&
|
|
(!Writable_Adapted_Object<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, Direct_Described_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", 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 <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 {
|
|
using Field = std::remove_cvref_t<decltype(item)>;
|
|
using Field_Value = typename Field::member_type;
|
|
if constexpr(std::is_lvalue_reference_v<decltype(item.get(candidate))> &&
|
|
!std::is_const_v<std::remove_reference_t<decltype(item.get(candidate))>>) {
|
|
assign_json_value<Json>(item.get(candidate),
|
|
Json_Adapter<Json>::at(value, item.name()),
|
|
Write_Context{mode, complete});
|
|
} else {
|
|
Field_Value field_candidate(item.get(std::as_const(candidate)));
|
|
assign_json_value<Json>(field_candidate,
|
|
Json_Adapter<Json>::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<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, Direct_Described_Object T>
|
|
Update_Result assign_json(T& target, const Json& value) {
|
|
return apply_model_json<Json>(target, value, Write_Mode::internal, true);
|
|
}
|
|
template <Json_Type Json, Direct_Described_Object T>
|
|
Update_Result apply_json_patch(T& target, const Json& patch) {
|
|
return apply_model_json<Json>(target, patch, Write_Mode::internal, false);
|
|
}
|
|
template <Json_Type Json, Direct_Described_Object T>
|
|
Update_Result apply_frontend_patch(T& target, const Json& patch) {
|
|
return apply_model_json<Json>(target, patch, Write_Mode::update, false);
|
|
}
|
|
template <Json_Type Json, Direct_Described_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;
|
|
}
|
|
}
|