570 lines
21 KiB
C++
570 lines
21 KiB
C++
#pragma once
|
|
#include "adminive/adapter.hpp"
|
|
#include "adminive/concepts.hpp"
|
|
#include <cctype>
|
|
#include <concepts>
|
|
#include <map>
|
|
#include <set>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <tuple>
|
|
#include <type_traits>
|
|
#include <utility>
|
|
namespace adminive {
|
|
template <class T>
|
|
struct Type_Descriptor;
|
|
template <class T>
|
|
concept Described_Type = requires {
|
|
{ Type_Descriptor<std::remove_cvref_t<T>>::get() };
|
|
};
|
|
template <class T>
|
|
concept Reflected_Type = requires {
|
|
{ Reflection_Adapter<std::remove_cvref_t<T>>::field_count } -> std::convertible_to<std::size_t>;
|
|
};
|
|
template <auto Member>
|
|
struct Member_Pointer_Traits;
|
|
template <class Owner, class Member_Type, Member_Type Owner::*Member>
|
|
struct Member_Pointer_Traits<Member> {
|
|
using owner_type = Owner;
|
|
using member_type = Member_Type;
|
|
static constexpr auto value = Member;
|
|
};
|
|
template <auto Member>
|
|
struct Member_Field_Accessor {
|
|
using owner_type = typename Member_Pointer_Traits<Member>::owner_type;
|
|
using member_type = typename Member_Pointer_Traits<Member>::member_type;
|
|
static constexpr auto member = Member;
|
|
template <class Object>
|
|
static decltype(auto) get(Object& object) noexcept {
|
|
return object.*Member;
|
|
}
|
|
template <class Object>
|
|
static decltype(auto) get(const Object& object) noexcept {
|
|
return object.*Member;
|
|
}
|
|
};
|
|
template <class T, std::size_t Index>
|
|
struct Reflected_Field_Accessor {
|
|
using owner_type = T;
|
|
using reference_type = decltype(Reflection_Adapter<T>::template get<Index>(std::declval<T&>()));
|
|
using const_reference_type = decltype(Reflection_Adapter<T>::template get<Index>(std::declval<const T&>()));
|
|
using member_type = std::remove_cvref_t<reference_type>;
|
|
static constexpr std::size_t index = Index;
|
|
static_assert(std::is_lvalue_reference_v<reference_type>);
|
|
static_assert(std::is_lvalue_reference_v<const_reference_type>);
|
|
static decltype(auto) get(T& object) noexcept(noexcept(Reflection_Adapter<T>::template get<Index>(object))) {
|
|
return Reflection_Adapter<T>::template get<Index>(object);
|
|
}
|
|
static decltype(auto) get(const T& object) noexcept(noexcept(Reflection_Adapter<T>::template get<Index>(object))) {
|
|
return Reflection_Adapter<T>::template get<Index>(object);
|
|
}
|
|
};
|
|
inline bool valid_data_name(std::string_view name) noexcept {
|
|
if(name.empty()) {
|
|
return false;
|
|
}
|
|
const auto first = static_cast<unsigned char>(name.front());
|
|
if(!(std::isalpha(first) || name.front() == '_')) {
|
|
return false;
|
|
}
|
|
for(const char value : name.substr(1)) {
|
|
const auto character = static_cast<unsigned char>(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<char>(std::toupper(static_cast<unsigned char>(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<std::string, std::string> 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 Accessor, bool Managed_Writable = false, bool Synchronized = true>
|
|
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<true, Synchronized>();
|
|
result.metadata_.editable = true;
|
|
return result;
|
|
}
|
|
auto creatable() const {
|
|
auto result = rebind<true, Synchronized>();
|
|
result.metadata_.creatable = true;
|
|
return result;
|
|
}
|
|
auto read_write() const {
|
|
return rebind<true, Synchronized>();
|
|
}
|
|
auto unsynchronized() const {
|
|
return rebind<Managed_Writable, false>();
|
|
}
|
|
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 <auto Value>
|
|
requires Enum_Type<std::remove_cv_t<decltype(Value)>>
|
|
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 <class Object>
|
|
requires std::convertible_to<std::remove_reference_t<Object>*, owner_type*>
|
|
decltype(auto) get(Object& object) const noexcept(noexcept(Accessor::get(object))) {
|
|
return Accessor::get(object);
|
|
}
|
|
template <class Object>
|
|
requires std::convertible_to<const std::remove_reference_t<Object>*, 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<std::string, std::string>& 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 <bool New_Writable, bool New_Synchronized>
|
|
auto rebind() const {
|
|
return Field_Descriptor<Accessor, New_Writable, New_Synchronized>(metadata_);
|
|
}
|
|
template <class, bool, bool>
|
|
friend class Field_Descriptor;
|
|
Field_Metadata metadata_;
|
|
};
|
|
template <class T>
|
|
concept Field_Descriptor_Type = requires {
|
|
typename std::remove_cvref_t<T>::field_descriptor_tag;
|
|
typename std::remove_cvref_t<T>::owner_type;
|
|
typename std::remove_cvref_t<T>::member_type;
|
|
typename std::remove_cvref_t<T>::accessor_type;
|
|
{ std::remove_cvref_t<T>::managed_writable } -> std::convertible_to<bool>;
|
|
{ std::remove_cvref_t<T>::synchronized } -> std::convertible_to<bool>;
|
|
};
|
|
template <auto Member>
|
|
auto field(std::string name) {
|
|
return Field_Descriptor<Member_Field_Accessor<Member>>(std::move(name));
|
|
}
|
|
template <auto Member>
|
|
auto field(std::string name, std::string label) {
|
|
return Field_Descriptor<Member_Field_Accessor<Member>>(std::move(name), std::move(label));
|
|
}
|
|
template <Reflected_Type T, std::size_t Index>
|
|
auto reflected_field() {
|
|
static_assert(Index < static_cast<std::size_t>(Reflection_Adapter<T>::field_count));
|
|
const std::string name(Reflection_Adapter<T>::template name<Index>());
|
|
if(name.empty()) {
|
|
throw std::invalid_argument("reflected field name must not be empty");
|
|
}
|
|
return Field_Descriptor<Reflected_Field_Accessor<T, Index>>(name);
|
|
}
|
|
template <Reflected_Type T, std::size_t Index>
|
|
auto reflected_field(std::string label) {
|
|
static_assert(Index < static_cast<std::size_t>(Reflection_Adapter<T>::field_count));
|
|
const std::string name(Reflection_Adapter<T>::template name<Index>());
|
|
if(name.empty()) {
|
|
throw std::invalid_argument("reflected field name must not be empty");
|
|
}
|
|
return Field_Descriptor<Reflected_Field_Accessor<T, Index>>(name, std::move(label));
|
|
}
|
|
struct No_Object_Validator {
|
|
template <class T>
|
|
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 T, class Validator, Field_Descriptor_Type... Fields>
|
|
class Object_Descriptor {
|
|
public:
|
|
using object_type = T;
|
|
using validator_type = Validator;
|
|
using fields_type = std::tuple<Fields...>;
|
|
Object_Descriptor(std::string name, std::string label, Validator validator, std::tuple<Fields...> 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 <class New_Validator>
|
|
auto validator(New_Validator value) const {
|
|
return Object_Descriptor<T, New_Validator, Fields...>(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...>& 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...> fields_;
|
|
Object_List_Options list_options_;
|
|
};
|
|
template <class Descriptor>
|
|
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<std::string> 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 <class T, Field_Descriptor_Type... Fields>
|
|
auto object(std::string name, Fields... fields) {
|
|
static_assert((std::convertible_to<T*, typename Fields::owner_type*> && ...));
|
|
const auto label = make_label(name);
|
|
return Object_Descriptor<T, No_Object_Validator, Fields...>(std::move(name), label, No_Object_Validator{}, std::tuple<Fields...>(std::move(fields)...));
|
|
}
|
|
template <class T, Field_Descriptor_Type... Fields>
|
|
auto object(std::string name, std::string label, Fields... fields) {
|
|
static_assert((std::convertible_to<T*, typename Fields::owner_type*> && ...));
|
|
return Object_Descriptor<T, No_Object_Validator, Fields...>(std::move(name), std::move(label), No_Object_Validator{}, std::tuple<Fields...>(std::move(fields)...));
|
|
}
|
|
struct Identity_Field_Customizer {
|
|
template <std::size_t Index, Field_Descriptor_Type Field>
|
|
auto operator()(Field value) const {
|
|
return value;
|
|
}
|
|
};
|
|
template <Reflected_Type T, class Customizer, std::size_t... Indexes>
|
|
auto reflected_object_with_impl(std::string name, std::string label, Customizer customizer, std::index_sequence<Indexes...>) {
|
|
return object<T>(std::move(name), std::move(label), customizer.template operator()<Indexes>(reflected_field<T, Indexes>())...);
|
|
}
|
|
template <Reflected_Type T, class Customizer>
|
|
auto reflected_object_with(std::string name, Customizer customizer) {
|
|
const auto label = make_label(name);
|
|
return reflected_object_with_impl<T>(std::move(name), label, std::move(customizer), std::make_index_sequence<static_cast<std::size_t>(Reflection_Adapter<T>::field_count)>{});
|
|
}
|
|
template <Reflected_Type T, class Customizer>
|
|
auto reflected_object_with(std::string name, std::string label, Customizer customizer) {
|
|
return reflected_object_with_impl<T>(std::move(name), std::move(label), std::move(customizer), std::make_index_sequence<static_cast<std::size_t>(Reflection_Adapter<T>::field_count)>{});
|
|
}
|
|
template <Reflected_Type T>
|
|
auto reflected_object(std::string name) {
|
|
return reflected_object_with<T>(std::move(name), Identity_Field_Customizer{});
|
|
}
|
|
template <Reflected_Type T>
|
|
auto reflected_object(std::string name, std::string label) {
|
|
return reflected_object_with<T>(std::move(name), std::move(label), Identity_Field_Customizer{});
|
|
}
|
|
template <Described_Type T>
|
|
auto describe() {
|
|
auto descriptor = Type_Descriptor<std::remove_cvref_t<T>>::get();
|
|
validate_descriptor(descriptor);
|
|
return descriptor;
|
|
}
|
|
template <class T>
|
|
using Descriptor_Type = std::remove_cvref_t<decltype(Type_Descriptor<std::remove_cvref_t<T>>::get())>;
|
|
template <class T>
|
|
using Descriptor_Fields = typename Descriptor_Type<T>::fields_type;
|
|
template <class T, std::size_t Index>
|
|
using Descriptor_Field = std::tuple_element_t<Index, Descriptor_Fields<T>>;
|
|
template <Described_Type T>
|
|
consteval bool described_type_has_managed_writes();
|
|
template <class Field>
|
|
consteval bool field_has_managed_writes() {
|
|
if constexpr(Field::managed_writable) {
|
|
return true;
|
|
} else if constexpr(Described_Type<typename Field::member_type>) {
|
|
return described_type_has_managed_writes<typename Field::member_type>();
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
template <Described_Type T, std::size_t... Indexes>
|
|
consteval bool described_type_has_managed_writes_impl(std::index_sequence<Indexes...>) {
|
|
return (field_has_managed_writes<Descriptor_Field<T, Indexes>>() || ...);
|
|
}
|
|
template <Described_Type T>
|
|
consteval bool described_type_has_managed_writes() {
|
|
return described_type_has_managed_writes_impl<T>(std::make_index_sequence<std::tuple_size_v<Descriptor_Fields<T>>>{});
|
|
}
|
|
}
|
|
#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<Type, Index>()
|
|
#define ADMINIVE_REFLECTED_FIELD_LABEL(Type, Index, Label) ::adminive::reflected_field<Type, Index>(Label)
|