#pragma once #include "adminive/adapter.hpp" #include "adminive/concepts.hpp" #include #include #include #include #include #include #include #include #include #include namespace adminive { template struct Type_Descriptor; template concept Described_Type = requires { { Type_Descriptor>::get() }; }; template concept Reflected_Type = requires { { Reflection_Adapter>::field_count } -> std::convertible_to; }; template struct Member_Pointer_Traits; template struct Member_Pointer_Traits { using owner_type = Owner; using member_type = Member_Type; static constexpr auto value = Member; }; template struct Member_Field_Accessor { using owner_type = typename Member_Pointer_Traits::owner_type; using member_type = typename Member_Pointer_Traits::member_type; static constexpr auto member = Member; template static decltype(auto) get(Object& object) noexcept { return object.*Member; } template static decltype(auto) get(const Object& object) noexcept { return object.*Member; } }; template struct Reflected_Field_Accessor { using owner_type = T; using reference_type = decltype(Reflection_Adapter::template get(std::declval())); using const_reference_type = decltype(Reflection_Adapter::template get(std::declval())); using member_type = std::remove_cvref_t; static constexpr std::size_t index = Index; static_assert(std::is_lvalue_reference_v); static_assert(std::is_lvalue_reference_v); static decltype(auto) get(T& object) noexcept(noexcept(Reflection_Adapter::template get(object))) { return Reflection_Adapter::template get(object); } static decltype(auto) get(const T& object) noexcept(noexcept(Reflection_Adapter::template get(object))) { return Reflection_Adapter::template get(object); } }; inline bool valid_data_name(std::string_view name) noexcept { if(name.empty()) { return false; } const auto first = static_cast(name.front()); if(!(std::isalpha(first) || name.front() == '_')) { return false; } for(const char value : name.substr(1)) { const auto character = static_cast(value); if(!(std::isalnum(character) || value == '_')) { return false; } } return true; } inline std::string make_label(std::string_view name) { std::string result; result.reserve(name.size()); bool uppercase = true; for(const char value : name) { if(value == '_') { result.push_back(' '); uppercase = true; continue; } result.push_back(uppercase ? static_cast(std::toupper(static_cast(value))) : value); uppercase = false; } return result; } struct Field_Metadata { std::string name; std::string label; std::string list_label; std::string description; std::string widget; std::string visible_on; std::map enum_labels; bool editable{}; bool creatable{}; bool readable{true}; bool sensitive{}; bool include_default{true}; bool visible{true}; bool required{}; bool list_visible{true}; int order{-1}; bool sortable{}; }; template class Field_Descriptor { public: using field_descriptor_tag = void; using accessor_type = Accessor; using owner_type = typename Accessor::owner_type; using member_type = typename Accessor::member_type; static constexpr bool managed_writable = Managed_Writable; static constexpr bool synchronized = Synchronized; explicit Field_Descriptor(std::string name) { metadata_.name = std::move(name); metadata_.label = make_label(metadata_.name); } Field_Descriptor(std::string name, std::string label) { metadata_.name = std::move(name); metadata_.label = std::move(label); } explicit Field_Descriptor(Field_Metadata metadata) : metadata_(std::move(metadata)) {} auto editable() const { auto result = rebind(); result.metadata_.editable = true; return result; } auto creatable() const { auto result = rebind(); result.metadata_.creatable = true; return result; } auto read_write() const { return rebind(); } auto unsynchronized() const { return rebind(); } Field_Descriptor readable(bool value = true) const { auto result = *this; result.metadata_.readable = value; return result; } Field_Descriptor sensitive(bool value = true) const { auto result = *this; result.metadata_.sensitive = value; if(value) { result.metadata_.readable = false; result.metadata_.include_default = false; } return result; } Field_Descriptor include_default(bool value = true) const { auto result = *this; result.metadata_.include_default = value; return result; } Field_Descriptor visible(bool value = true) const { auto result = *this; result.metadata_.visible = value; return result; } Field_Descriptor required(bool value = true) const { auto result = *this; result.metadata_.required = value; return result; } Field_Descriptor list_visible(bool value = true) const { auto result = *this; result.metadata_.list_visible = value; return result; } Field_Descriptor label(std::string value) const { auto result = *this; result.metadata_.label = std::move(value); return result; } Field_Descriptor description(std::string value) const { auto result = *this; result.metadata_.description = std::move(value); return result; } Field_Descriptor widget(std::string value) const { auto result = *this; result.metadata_.widget = std::move(value); return result; } Field_Descriptor visible_on(std::string value) const { auto result = *this; result.metadata_.visible_on = std::move(value); return result; } Field_Descriptor list_label(std::string value) const { auto result = *this; result.metadata_.list_label = std::move(value); return result; } Field_Descriptor order(int value) const { auto result = *this; result.metadata_.order = value; return result; } Field_Descriptor sortable(bool value = true) const { auto result = *this; result.metadata_.sortable = value; return result; } template requires Enum_Type> Field_Descriptor enum_label(std::string label) const { const auto name = enum_name(Value); if(name.empty()) { throw std::invalid_argument("enum adapter returned an empty value name"); } auto result = *this; result.metadata_.enum_labels.insert_or_assign(std::string(name), std::move(label)); return result; } Field_Descriptor enum_label(std::string value_name, std::string label) const { auto result = *this; result.metadata_.enum_labels.insert_or_assign(std::move(value_name), std::move(label)); return result; } template requires std::convertible_to*, owner_type*> decltype(auto) get(Object& object) const noexcept(noexcept(Accessor::get(object))) { return Accessor::get(object); } template requires std::convertible_to*, const owner_type*> decltype(auto) get(const Object& object) const noexcept(noexcept(Accessor::get(object))) { return Accessor::get(object); } const std::string& name() const noexcept { return metadata_.name; } const std::string& label() const noexcept { return metadata_.label; } const std::string& list_label() const noexcept { return metadata_.list_label.empty() ? metadata_.label : metadata_.list_label; } const std::string& description() const noexcept { return metadata_.description; } const std::string& widget() const noexcept { return metadata_.widget; } const std::string& visible_on() const noexcept { return metadata_.visible_on; } const std::map& enum_labels() const noexcept { return metadata_.enum_labels; } bool is_editable() const noexcept { return metadata_.editable; } bool is_creatable() const noexcept { return metadata_.creatable; } bool is_readable() const noexcept { return metadata_.readable; } bool is_sensitive() const noexcept { return metadata_.sensitive; } bool includes_default() const noexcept { return metadata_.include_default; } bool is_visible() const noexcept { return metadata_.visible; } bool is_required() const noexcept { return metadata_.required; } int order() const noexcept { return metadata_.order; } bool is_sortable() const noexcept { return metadata_.sortable; } bool is_list_visible() const noexcept { return metadata_.list_visible; } private: template auto rebind() const { return Field_Descriptor(metadata_); } template friend class Field_Descriptor; Field_Metadata metadata_; }; template concept Field_Descriptor_Type = requires { typename std::remove_cvref_t::field_descriptor_tag; typename std::remove_cvref_t::owner_type; typename std::remove_cvref_t::member_type; typename std::remove_cvref_t::accessor_type; { std::remove_cvref_t::managed_writable } -> std::convertible_to; { std::remove_cvref_t::synchronized } -> std::convertible_to; }; template auto field(std::string name) { return Field_Descriptor>(std::move(name)); } template auto field(std::string name, std::string label) { return Field_Descriptor>(std::move(name), std::move(label)); } template auto reflected_field() { static_assert(Index < static_cast(Reflection_Adapter::field_count)); const std::string name(Reflection_Adapter::template name()); if(name.empty()) { throw std::invalid_argument("reflected field name must not be empty"); } return Field_Descriptor>(name); } template auto reflected_field(std::string label) { static_assert(Index < static_cast(Reflection_Adapter::field_count)); const std::string name(Reflection_Adapter::template name()); if(name.empty()) { throw std::invalid_argument("reflected field name must not be empty"); } return Field_Descriptor>(name, std::move(label)); } struct No_Object_Validator { template void operator()(const T&) const noexcept {} }; struct Object_List_Options { std::string id_label{"ID"}; std::string create_label{"Create"}; std::string edit_label{"Edit"}; std::string delete_label{"Delete"}; std::string actions_label{"Actions"}; std::string confirm_label{"Confirm Changes"}; std::string delete_confirm{"Delete this record?"}; std::string view_status_label{"View status"}; std::string management_label; std::string default_order_by; std::string default_order_dir{"asc"}; bool user_reorderable_{}; bool column_reorderable_{true}; }; template class Object_Descriptor { public: using object_type = T; using validator_type = Validator; using fields_type = std::tuple; Object_Descriptor(std::string name, std::string label, Validator validator, std::tuple fields, Object_List_Options list_options = {}) : name_(std::move(name)), label_(std::move(label)), validator_(std::move(validator)), fields_(std::move(fields)), list_options_(std::move(list_options)) {} template auto validator(New_Validator value) const { return Object_Descriptor(name_, label_, std::move(value), fields_, list_options_); } Object_Descriptor label(std::string value) const { auto result = *this; result.label_ = std::move(value); return result; } Object_Descriptor id_label(std::string value) const { auto result = *this; result.list_options_.id_label = std::move(value); return result; } Object_Descriptor create_label(std::string value) const { auto result = *this; result.list_options_.create_label = std::move(value); return result; } Object_Descriptor edit_label(std::string value) const { auto result = *this; result.list_options_.edit_label = std::move(value); return result; } Object_Descriptor delete_label(std::string value) const { auto result = *this; result.list_options_.delete_label = std::move(value); return result; } Object_Descriptor actions_label(std::string value) const { auto result = *this; result.list_options_.actions_label = std::move(value); return result; } Object_Descriptor confirm_label(std::string value) const { auto result = *this; result.list_options_.confirm_label = std::move(value); return result; } Object_Descriptor delete_confirm(std::string value) const { auto result = *this; result.list_options_.delete_confirm = std::move(value); return result; } Object_Descriptor view_status_label(std::string value) const { auto result = *this; result.list_options_.view_status_label = std::move(value); return result; } Object_Descriptor management_label(std::string value) const { auto result = *this; result.list_options_.management_label = std::move(value); return result; } Object_Descriptor default_sort(std::string field_name, bool descending = false) const { auto result = *this; result.list_options_.default_order_by = std::move(field_name); result.list_options_.default_order_dir = descending ? "desc" : "asc"; return result; } Object_Descriptor user_reorderable(bool value = true) const { auto result = *this; result.list_options_.user_reorderable_ = value; return result; } Object_Descriptor column_reorderable(bool value = true) const { auto result = *this; result.list_options_.column_reorderable_ = value; return result; } const std::string& name() const noexcept { return name_; } const std::string& label() const noexcept { return label_; } const Validator& object_validator() const noexcept { return validator_; } const std::tuple& fields() const noexcept { return fields_; } const Object_List_Options& list_options() const noexcept { return list_options_; } private: std::string name_; std::string label_; Validator validator_; std::tuple fields_; Object_List_Options list_options_; }; template void validate_descriptor(const Descriptor& descriptor) { if(!valid_data_name(descriptor.name())) { throw std::invalid_argument("descriptor name must match [A-Za-z_][A-Za-z0-9_]*: " + descriptor.name()); } std::set field_names; std::apply([&](const auto&... field) { ([&] { if(!valid_data_name(field.name())) { throw std::invalid_argument("descriptor field name must match [A-Za-z_][A-Za-z0-9_]*: " + field.name()); } if(!field_names.insert(field.name()).second) { throw std::invalid_argument("duplicate descriptor field name: " + field.name()); } }(), ...); }, descriptor.fields()); const auto& options = descriptor.list_options(); if(options.default_order_dir != "asc" && options.default_order_dir != "desc") { throw std::invalid_argument("default sort direction must be 'asc' or 'desc'"); } if(options.default_order_by.empty()) { return; } bool found{}; bool sortable{}; std::apply([&](const auto&... field) { ([&] { if(field.name() == options.default_order_by) { found = true; sortable = field.is_sortable(); } }(), ...); }, descriptor.fields()); if(!found) { throw std::invalid_argument("default sort field does not exist: " + options.default_order_by); } if(!sortable) { throw std::invalid_argument("default sort field is not sortable: " + options.default_order_by); } } template auto object(std::string name, Fields... fields) { static_assert((std::convertible_to && ...)); const auto label = make_label(name); return Object_Descriptor(std::move(name), label, No_Object_Validator{}, std::tuple(std::move(fields)...)); } template auto object(std::string name, std::string label, Fields... fields) { static_assert((std::convertible_to && ...)); return Object_Descriptor(std::move(name), std::move(label), No_Object_Validator{}, std::tuple(std::move(fields)...)); } struct Identity_Field_Customizer { template auto operator()(Field value) const { return value; } }; template auto reflected_object_with_impl(std::string name, std::string label, Customizer customizer, std::index_sequence) { return object(std::move(name), std::move(label), customizer.template operator()(reflected_field())...); } template auto reflected_object_with(std::string name, Customizer customizer) { const auto label = make_label(name); return reflected_object_with_impl(std::move(name), label, std::move(customizer), std::make_index_sequence(Reflection_Adapter::field_count)>{}); } template auto reflected_object_with(std::string name, std::string label, Customizer customizer) { return reflected_object_with_impl(std::move(name), std::move(label), std::move(customizer), std::make_index_sequence(Reflection_Adapter::field_count)>{}); } template auto reflected_object(std::string name) { return reflected_object_with(std::move(name), Identity_Field_Customizer{}); } template auto reflected_object(std::string name, std::string label) { return reflected_object_with(std::move(name), std::move(label), Identity_Field_Customizer{}); } template auto describe() { auto descriptor = Type_Descriptor>::get(); validate_descriptor(descriptor); return descriptor; } template using Descriptor_Type = std::remove_cvref_t>::get())>; template using Descriptor_Fields = typename Descriptor_Type::fields_type; template using Descriptor_Field = std::tuple_element_t>; template consteval bool described_type_has_managed_writes(); template consteval bool field_has_managed_writes() { if constexpr(Field::managed_writable) { return true; } else if constexpr(Described_Type) { return described_type_has_managed_writes(); } else { return false; } } template consteval bool described_type_has_managed_writes_impl(std::index_sequence) { return (field_has_managed_writes>() || ...); } template consteval bool described_type_has_managed_writes() { return described_type_has_managed_writes_impl(std::make_index_sequence>>{}); } } #define ADMINIVE_FIELD(Type, Member) ::adminive::field<&Type::Member>(#Member) #define ADMINIVE_FIELD_LABEL(Type, Member, Label) ::adminive::field<&Type::Member>(#Member, Label) #define ADMINIVE_REFLECTED_FIELD(Type, Index) ::adminive::reflected_field() #define ADMINIVE_REFLECTED_FIELD_LABEL(Type, Index, Label) ::adminive::reflected_field(Label)