更新接口
This commit is contained in:
@@ -6,6 +6,7 @@ add_library(Adminive::Core ALIAS Adminive)
|
||||
add_library(Adminive::Http ALIAS Adminive)
|
||||
target_include_directories(Adminive INTERFACE "$<BUILD_INTERFACE:${adminive_library_include_dir}>" "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>")
|
||||
target_compile_features(Adminive INTERFACE cxx_std_20)
|
||||
target_link_libraries(Adminive INTERFACE "$<BUILD_INTERFACE:structive::property_core>")
|
||||
if(MSVC)
|
||||
target_compile_options(Adminive INTERFACE /utf-8 /Zc:__cplusplus)
|
||||
if(ADMINIVE_PROPAGATE_MSVC_STRICT_MODE)
|
||||
@@ -22,3 +23,4 @@ if(BUILD_TESTING)
|
||||
endif()
|
||||
install(TARGETS Adminive EXPORT AdminiveCoreTargets)
|
||||
install(DIRECTORY "${adminive_library_include_dir}/" DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}")
|
||||
install(DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/../../third_party/Structive/core/include/" DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}")
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
#include "adminive/http.hpp"
|
||||
#include "adminive/json.hpp"
|
||||
#include "adminive/status.hpp"
|
||||
#include "adminive/synchronized.hpp"
|
||||
#include "adminive/managed.hpp"
|
||||
#include "adminive/value.hpp"
|
||||
|
||||
@@ -459,15 +459,15 @@ Json to_amis_form_schema(const T& value, std::string submit_api = {}, std::strin
|
||||
}
|
||||
return result;
|
||||
}
|
||||
template <Json_Type Json, class T, Basic_Lock Lock, Basic_Lock Field_Lock>
|
||||
Json to_amis_form_schema(const Synchronized_Value<T, Lock, Field_Lock>& value, std::string submit_api = {}, std::string submit_label = "Apply") {
|
||||
return value.read([&](const T& item) {
|
||||
template <Json_Type Json, Managed_Value_Type T>
|
||||
Json to_amis_form_schema(const T& value, std::string submit_api = {}, std::string submit_label = "Apply") {
|
||||
return value.read([&](const auto& item) {
|
||||
return adminive::to_amis_form_schema<Json>(item, std::move(submit_api), std::move(submit_label));
|
||||
});
|
||||
}
|
||||
template <Json_Type Json, class Root, Basic_Lock Lock, Basic_Lock Field_Lock, auto... Members>
|
||||
Json to_amis_form_schema(const Synchronized_Field<Root, Lock, Field_Lock, Members...>& value, std::string submit_api = {}, std::string submit_label = "Apply") {
|
||||
return value.object_read([&](const auto& item) {
|
||||
template <Json_Type Json, Managed_Field_Type T>
|
||||
Json to_amis_form_schema(const T& value, std::string submit_api = {}, std::string submit_label = "Apply") {
|
||||
return value.read([&](const auto& item) {
|
||||
return adminive::to_amis_form_schema<Json>(item, std::move(submit_api), std::move(submit_label));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -12,11 +12,6 @@
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
namespace adminive {
|
||||
enum class Field_Lock_Mode {
|
||||
automatic,
|
||||
enabled,
|
||||
disabled
|
||||
};
|
||||
template <class T>
|
||||
struct Type_Descriptor;
|
||||
template <class T>
|
||||
@@ -39,6 +34,7 @@ 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;
|
||||
@@ -54,6 +50,7 @@ struct Reflected_Field_Accessor {
|
||||
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))) {
|
||||
@@ -83,8 +80,8 @@ 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 == '_') {
|
||||
for(const char value : name) {
|
||||
if(value == '_') {
|
||||
result.push_back(' ');
|
||||
uppercase = true;
|
||||
continue;
|
||||
@@ -94,111 +91,142 @@ inline std::string make_label(std::string_view name) {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
template <class Derived, class Accessor>
|
||||
class Field_Descriptor_Base {
|
||||
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;
|
||||
explicit Field_Descriptor_Base(std::string name) : name_(std::move(name)), label_(make_label(name_)) {}
|
||||
Field_Descriptor_Base(std::string name, std::string label) : name_(std::move(name)), label_(std::move(label)) {}
|
||||
Derived editable(bool value = true) const {
|
||||
auto result = derived();
|
||||
result.editable_ = value;
|
||||
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;
|
||||
}
|
||||
Derived locked(bool value = true) const {
|
||||
auto result = derived();
|
||||
result.lock_mode_ = value ? Field_Lock_Mode::enabled : Field_Lock_Mode::disabled;
|
||||
auto creatable() const {
|
||||
auto result = rebind<true, Synchronized>();
|
||||
result.metadata_.creatable = true;
|
||||
return result;
|
||||
}
|
||||
Derived creatable(bool value = true) const {
|
||||
auto result = derived();
|
||||
result.creatable_ = value;
|
||||
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;
|
||||
}
|
||||
Derived readable(bool value = true) const {
|
||||
auto result = derived();
|
||||
result.readable_ = value;
|
||||
return result;
|
||||
}
|
||||
Derived sensitive(bool value = true) const {
|
||||
auto result = derived();
|
||||
result.sensitive_ = value;
|
||||
Field_Descriptor sensitive(bool value = true) const {
|
||||
auto result = *this;
|
||||
result.metadata_.sensitive = value;
|
||||
if(value) {
|
||||
result.readable_ = false;
|
||||
result.include_default_ = false;
|
||||
result.metadata_.readable = false;
|
||||
result.metadata_.include_default = false;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
Derived include_default(bool value = true) const {
|
||||
auto result = derived();
|
||||
result.include_default_ = value;
|
||||
Field_Descriptor include_default(bool value = true) const {
|
||||
auto result = *this;
|
||||
result.metadata_.include_default = value;
|
||||
return result;
|
||||
}
|
||||
Derived visible(bool value = true) const {
|
||||
auto result = derived();
|
||||
result.visible_ = value;
|
||||
Field_Descriptor visible(bool value = true) const {
|
||||
auto result = *this;
|
||||
result.metadata_.visible = value;
|
||||
return result;
|
||||
}
|
||||
Derived required(bool value = true) const {
|
||||
auto result = derived();
|
||||
result.required_ = value;
|
||||
Field_Descriptor required(bool value = true) const {
|
||||
auto result = *this;
|
||||
result.metadata_.required = value;
|
||||
return result;
|
||||
}
|
||||
Derived list_visible(bool value = true) const {
|
||||
auto result = derived();
|
||||
result.list_visible_ = value;
|
||||
Field_Descriptor list_visible(bool value = true) const {
|
||||
auto result = *this;
|
||||
result.metadata_.list_visible = value;
|
||||
return result;
|
||||
}
|
||||
Derived label(std::string value) const {
|
||||
auto result = derived();
|
||||
result.label_ = std::move(value);
|
||||
Field_Descriptor label(std::string value) const {
|
||||
auto result = *this;
|
||||
result.metadata_.label = std::move(value);
|
||||
return result;
|
||||
}
|
||||
Derived description(std::string value) const {
|
||||
auto result = derived();
|
||||
result.description_ = std::move(value);
|
||||
Field_Descriptor description(std::string value) const {
|
||||
auto result = *this;
|
||||
result.metadata_.description = std::move(value);
|
||||
return result;
|
||||
}
|
||||
Derived widget(std::string value) const {
|
||||
auto result = derived();
|
||||
result.widget_ = std::move(value);
|
||||
Field_Descriptor widget(std::string value) const {
|
||||
auto result = *this;
|
||||
result.metadata_.widget = std::move(value);
|
||||
return result;
|
||||
}
|
||||
Derived visible_on(std::string value) const {
|
||||
auto result = derived();
|
||||
result.visible_on_ = std::move(value);
|
||||
Field_Descriptor visible_on(std::string value) const {
|
||||
auto result = *this;
|
||||
result.metadata_.visible_on = std::move(value);
|
||||
return result;
|
||||
}
|
||||
Derived list_label(std::string value) const {
|
||||
auto result = derived();
|
||||
result.list_label_ = std::move(value);
|
||||
Field_Descriptor list_label(std::string value) const {
|
||||
auto result = *this;
|
||||
result.metadata_.list_label = std::move(value);
|
||||
return result;
|
||||
}
|
||||
Derived order(int value) const {
|
||||
auto result = derived();
|
||||
result.order_ = value;
|
||||
Field_Descriptor order(int value) const {
|
||||
auto result = *this;
|
||||
result.metadata_.order = value;
|
||||
return result;
|
||||
}
|
||||
Derived sortable(bool value = true) const {
|
||||
auto result = derived();
|
||||
result.sortable_ = value;
|
||||
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)>>
|
||||
Derived enum_label(std::string label) const {
|
||||
Field_Descriptor enum_label(std::string label) const {
|
||||
const auto name = enum_name(Value);
|
||||
if (name.empty()) {
|
||||
if(name.empty()) {
|
||||
throw std::invalid_argument("enum adapter returned an empty value name");
|
||||
}
|
||||
auto result = derived();
|
||||
result.enum_labels_.insert_or_assign(std::string(name), std::move(label));
|
||||
auto result = *this;
|
||||
result.metadata_.enum_labels.insert_or_assign(std::string(name), std::move(label));
|
||||
return result;
|
||||
}
|
||||
Derived enum_label(std::string value_name, std::string label) const {
|
||||
auto result = derived();
|
||||
result.enum_labels_.insert_or_assign(std::move(value_name), std::move(label));
|
||||
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>
|
||||
@@ -212,112 +240,81 @@ public:
|
||||
return Accessor::get(object);
|
||||
}
|
||||
const std::string& name() const noexcept {
|
||||
return name_;
|
||||
return metadata_.name;
|
||||
}
|
||||
const std::string& label() const noexcept {
|
||||
return label_;
|
||||
return metadata_.label;
|
||||
}
|
||||
const std::string& list_label() const noexcept {
|
||||
return list_label_.empty() ? label_ : list_label_;
|
||||
return metadata_.list_label.empty() ? metadata_.label : metadata_.list_label;
|
||||
}
|
||||
const std::string& description() const noexcept {
|
||||
return description_;
|
||||
return metadata_.description;
|
||||
}
|
||||
const std::string& widget() const noexcept {
|
||||
return widget_;
|
||||
return metadata_.widget;
|
||||
}
|
||||
const std::string& visible_on() const noexcept {
|
||||
return visible_on_;
|
||||
return metadata_.visible_on;
|
||||
}
|
||||
const std::map<std::string, std::string>& enum_labels() const noexcept {
|
||||
return enum_labels_;
|
||||
return metadata_.enum_labels;
|
||||
}
|
||||
bool is_editable() const noexcept {
|
||||
return editable_;
|
||||
}
|
||||
Field_Lock_Mode lock_mode() const noexcept {
|
||||
return lock_mode_;
|
||||
return metadata_.editable;
|
||||
}
|
||||
bool is_creatable() const noexcept {
|
||||
return creatable_;
|
||||
return metadata_.creatable;
|
||||
}
|
||||
bool is_readable() const noexcept {
|
||||
return readable_;
|
||||
return metadata_.readable;
|
||||
}
|
||||
bool is_sensitive() const noexcept {
|
||||
return sensitive_;
|
||||
return metadata_.sensitive;
|
||||
}
|
||||
bool includes_default() const noexcept {
|
||||
return include_default_;
|
||||
return metadata_.include_default;
|
||||
}
|
||||
bool is_visible() const noexcept {
|
||||
return visible_;
|
||||
return metadata_.visible;
|
||||
}
|
||||
bool is_required() const noexcept {
|
||||
return required_;
|
||||
return metadata_.required;
|
||||
}
|
||||
int order() const noexcept {
|
||||
return order_;
|
||||
return metadata_.order;
|
||||
}
|
||||
bool is_sortable() const noexcept {
|
||||
return sortable_;
|
||||
return metadata_.sortable;
|
||||
}
|
||||
bool is_list_visible() const noexcept {
|
||||
return list_visible_;
|
||||
}
|
||||
protected:
|
||||
const Derived& derived() const noexcept {
|
||||
return static_cast<const Derived&>(*this);
|
||||
return metadata_.list_visible;
|
||||
}
|
||||
private:
|
||||
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_{};
|
||||
Field_Lock_Mode lock_mode_{Field_Lock_Mode::automatic};
|
||||
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 <auto Member>
|
||||
class Field_Descriptor : public Field_Descriptor_Base<Field_Descriptor<Member>, Member_Field_Accessor<Member>> {
|
||||
public:
|
||||
using Base = Field_Descriptor_Base<Field_Descriptor<Member>, Member_Field_Accessor<Member>>;
|
||||
using Base::Base;
|
||||
static constexpr auto member = Member;
|
||||
};
|
||||
template <class T, std::size_t Index>
|
||||
class Reflected_Field_Descriptor : public Field_Descriptor_Base<Reflected_Field_Descriptor<T, Index>, Reflected_Field_Accessor<T, Index>> {
|
||||
public:
|
||||
using Base = Field_Descriptor_Base<Reflected_Field_Descriptor<T, Index>, Reflected_Field_Accessor<T, Index>>;
|
||||
using Base::Base;
|
||||
static constexpr std::size_t index = Index;
|
||||
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>
|
||||
struct Is_Field_Descriptor : std::false_type {};
|
||||
template <auto Member>
|
||||
struct Is_Field_Descriptor<Field_Descriptor<Member>> : std::true_type {};
|
||||
template <class T, std::size_t Index>
|
||||
struct Is_Field_Descriptor<Reflected_Field_Descriptor<T, Index>> : std::true_type {};
|
||||
template <class T>
|
||||
concept Field_Descriptor_Type = Is_Field_Descriptor<std::remove_cvref_t<T>>::value;
|
||||
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>(std::move(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>(std::move(name), std::move(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() {
|
||||
@@ -326,7 +323,7 @@ auto reflected_field() {
|
||||
if(name.empty()) {
|
||||
throw std::invalid_argument("reflected field name must not be empty");
|
||||
}
|
||||
return Reflected_Field_Descriptor<T, Index>(name);
|
||||
return Field_Descriptor<Reflected_Field_Accessor<T, Index>>(name);
|
||||
}
|
||||
template <Reflected_Type T, std::size_t Index>
|
||||
auto reflected_field(std::string label) {
|
||||
@@ -335,7 +332,7 @@ auto reflected_field(std::string label) {
|
||||
if(name.empty()) {
|
||||
throw std::invalid_argument("reflected field name must not be empty");
|
||||
}
|
||||
return Reflected_Field_Descriptor<T, Index>(name, std::move(label));
|
||||
return Field_Descriptor<Reflected_Field_Accessor<T, Index>>(name, std::move(label));
|
||||
}
|
||||
struct No_Object_Validator {
|
||||
template <class T>
|
||||
@@ -361,6 +358,7 @@ 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 {
|
||||
@@ -538,6 +536,32 @@ auto describe() {
|
||||
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)
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
#pragma once
|
||||
#include "adminive/amis.hpp"
|
||||
#include "adminive/synchronized.hpp"
|
||||
#include "adminive/lock.hpp"
|
||||
#include "adminive/managed.hpp"
|
||||
#include <exception>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <shared_mutex>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
namespace adminive {
|
||||
@@ -50,10 +50,6 @@ template <Json_Type Json>
|
||||
Http_Response<Json> make_http_error(int status, std::string message) {
|
||||
return Http_Response<Json>{status, make_http_result<Json>(status, std::move(message), json_object<Json>())};
|
||||
}
|
||||
enum class Resource_Lock_Scope {
|
||||
local,
|
||||
root
|
||||
};
|
||||
template <class Model>
|
||||
struct Resource_Transaction {
|
||||
using Prepare_Function = std::function<void(const Model&, const Request_Context&)>;
|
||||
@@ -69,7 +65,7 @@ Resource_Transaction<Model> make_resource_transaction(Function&& function) {
|
||||
result.commit = std::forward<Function>(function);
|
||||
return result;
|
||||
}
|
||||
template <class T, Json_Type Json, Basic_Lock Lock = std::shared_mutex>
|
||||
template <class T, Json_Type Json, Basic_Lock Lock = std::mutex>
|
||||
requires Writable_Adapted_Object<T> && std::copy_constructible<Object_Model_Type<T>>
|
||||
class Resource_Service {
|
||||
public:
|
||||
@@ -88,8 +84,8 @@ public:
|
||||
configure_raw_access(value, *owned_lock_);
|
||||
}
|
||||
}
|
||||
template <Basic_Lock Field_Lock>
|
||||
explicit Resource_Service(Synchronized_Value<Object, Lock, Field_Lock>& value, std::string path, Transaction transaction = {}) : path_(std::move(path)), transaction_(std::move(transaction)) {
|
||||
template <structive::Synchronization_Policy Policy>
|
||||
explicit Resource_Service(Managed_Value<Object, Policy>& value, std::string path, Transaction transaction = {}) : path_(std::move(path)), transaction_(std::move(transaction)) {
|
||||
read_access_ = [&value](const Read_Function& function) {
|
||||
value.read([&](const Object& object) {
|
||||
function(object);
|
||||
@@ -101,20 +97,16 @@ public:
|
||||
});
|
||||
};
|
||||
}
|
||||
template <class Root, Basic_Lock Field_Lock, auto... Members>
|
||||
requires (!std::is_const_v<Root>) && std::same_as<typename Synchronized_Field<Root, Lock, Field_Lock, Members...>::value_type, Object>
|
||||
explicit Resource_Service(Synchronized_Field<Root, Lock, Field_Lock, Members...> value, std::string path, Transaction transaction = {}, Resource_Lock_Scope lock_scope = Resource_Lock_Scope::local) : path_(std::move(path)), transaction_(std::move(transaction)) {
|
||||
if(lock_scope == Resource_Lock_Scope::root) {
|
||||
configure_raw_access(detail::Synchronized_Access::value(value), detail::Synchronized_Access::mutex(value));
|
||||
return;
|
||||
}
|
||||
template <class Managed, std::size_t Root_Index, auto... Members>
|
||||
requires std::same_as<typename Managed_Field<Managed, Root_Index, Members...>::value_type, Object> && (!std::is_const_v<Managed>)
|
||||
explicit Resource_Service(Managed_Field<Managed, Root_Index, Members...> value, std::string path, Transaction transaction = {}) : path_(std::move(path)), transaction_(std::move(transaction)) {
|
||||
read_access_ = [value](const Read_Function& function) {
|
||||
value.object_read([&](const Object& object) {
|
||||
value.read([&](const Object& object) {
|
||||
function(object);
|
||||
});
|
||||
};
|
||||
write_access_ = [value](const Write_Function& function) mutable {
|
||||
value.object_write([&](Object& object) {
|
||||
value.write([&](Object& object) {
|
||||
function(object);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
#include "adminive/adapter.hpp"
|
||||
#include "adminive/descriptor.hpp"
|
||||
#include "adminive/synchronized.hpp"
|
||||
#include "adminive/managed.hpp"
|
||||
#include <cmath>
|
||||
#include <concepts>
|
||||
#include <cstdint>
|
||||
@@ -103,10 +103,10 @@ 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>
|
||||
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>
|
||||
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);
|
||||
@@ -413,14 +413,9 @@ Json model_descriptor_json(const Model* defaults = nullptr) {
|
||||
json_set(field_json, "readable", item.is_readable());
|
||||
json_set(field_json, "sensitive", item.is_sensitive());
|
||||
json_set(field_json, "editable", item.is_editable());
|
||||
const char* lock_mode = "automatic";
|
||||
if(item.lock_mode() == Field_Lock_Mode::enabled) {
|
||||
lock_mode = "enabled";
|
||||
} else if(item.lock_mode() == Field_Lock_Mode::disabled) {
|
||||
lock_mode = "disabled";
|
||||
}
|
||||
json_set(field_json, "lock_mode", lock_mode);
|
||||
json_set(field_json, "synchronized", descriptor_field_requires_lock(item));
|
||||
using Field = std::remove_cvref_t<decltype(item)>;
|
||||
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, "visible", item.is_visible());
|
||||
json_set(field_json, "required", item.is_required());
|
||||
@@ -467,7 +462,7 @@ Json model_descriptor_json(const Model* defaults = nullptr) {
|
||||
json_set(list, "column_reorderable", options.column_reorderable_);
|
||||
Json result = json_object<Json>();
|
||||
json_set(result, "protocol", "adminive.resource");
|
||||
json_set(result, "protocol_version", 3);
|
||||
json_set(result, "protocol_version", 4);
|
||||
json_set(result, "name", descriptor.name());
|
||||
json_set(result, "label", descriptor.label());
|
||||
json_set(result, "value_type", "object");
|
||||
@@ -506,7 +501,7 @@ Json model_to_json(const T& value, bool frontend) {
|
||||
return result;
|
||||
}
|
||||
template <Json_Type Json, class T>
|
||||
requires Described_Type<std::remove_cvref_t<T>> || Object_Adapter_With_Snapshot<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>>) {
|
||||
@@ -516,7 +511,7 @@ 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>
|
||||
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>>) {
|
||||
@@ -526,44 +521,48 @@ Json to_frontend_json(const T& value) {
|
||||
}
|
||||
}
|
||||
template <Json_Type Json, class T>
|
||||
requires Described_Type<std::remove_cvref_t<T>> || Object_Adapter_With_Snapshot<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, class T, Basic_Lock Lock, Basic_Lock Field_Lock>
|
||||
Json to_json(const Synchronized_Value<T, Lock, Field_Lock>& value) {
|
||||
return value.read([](const T& item) {
|
||||
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, class T, Basic_Lock Lock, Basic_Lock Field_Lock>
|
||||
Json to_frontend_json(const Synchronized_Value<T, Lock, Field_Lock>& value) {
|
||||
return value.read([](const T& 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, class T, Basic_Lock Lock, Basic_Lock Field_Lock>
|
||||
Json to_data_json(const Synchronized_Value<T, Lock, Field_Lock>& value) {
|
||||
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, class Root, Basic_Lock Lock, Basic_Lock Field_Lock, auto... Members>
|
||||
Json to_json(const Synchronized_Field<Root, Lock, Field_Lock, Members...>& value) {
|
||||
return value.object_read([](const auto& item) {
|
||||
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, class Root, Basic_Lock Lock, Basic_Lock Field_Lock, auto... Members>
|
||||
Json to_frontend_json(const Synchronized_Field<Root, Lock, Field_Lock, Members...>& value) {
|
||||
return value.object_read([](const auto& 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, class Root, Basic_Lock Lock, Basic_Lock Field_Lock, auto... Members>
|
||||
Json to_data_json(const Synchronized_Field<Root, Lock, Field_Lock, Members...>& value) {
|
||||
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;
|
||||
}
|
||||
@@ -733,70 +732,70 @@ 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, class T, Basic_Lock Lock, Basic_Lock Field_Lock>
|
||||
requires Writable_Adapted_Object<T>
|
||||
Update_Result assign_json(Synchronized_Value<T, Lock, Field_Lock>& target, const Json& value) {
|
||||
return target.write([&](T& item) {
|
||||
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, class T, Basic_Lock Lock, Basic_Lock Field_Lock>
|
||||
requires Writable_Adapted_Object<T>
|
||||
Update_Result apply_json_patch(Synchronized_Value<T, Lock, Field_Lock>& target, const Json& patch) {
|
||||
return target.write([&](T& item) {
|
||||
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, class T, Basic_Lock Lock, Basic_Lock Field_Lock>
|
||||
requires Creatable_Writable_Object<T>
|
||||
Update_Result apply_frontend_create(Synchronized_Value<T, Lock, Field_Lock>& target, const Json& value) {
|
||||
return target.write([&](T& item) {
|
||||
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, class T, Basic_Lock Lock, Basic_Lock Field_Lock>
|
||||
requires Writable_Adapted_Object<T>
|
||||
Update_Result apply_frontend_patch(Synchronized_Value<T, Lock, Field_Lock>& target, const Json& patch) {
|
||||
return target.write([&](T& item) {
|
||||
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, class T, Basic_Lock Lock, Basic_Lock Field_Lock>
|
||||
requires Writable_Adapted_Object<T>
|
||||
Update_Result apply_patch(Synchronized_Value<T, Lock, Field_Lock>& target, const Json& 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, class Root, Basic_Lock Lock, Basic_Lock Field_Lock, auto... Members>
|
||||
requires (!std::is_const_v<Root>) && Writable_Adapted_Object<typename Synchronized_Field<Root, Lock, Field_Lock, Members...>::value_type>
|
||||
Update_Result assign_json(Synchronized_Field<Root, Lock, Field_Lock, Members...> target, const Json& value) {
|
||||
return target.object_write([&](auto& item) {
|
||||
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, class Root, Basic_Lock Lock, Basic_Lock Field_Lock, auto... Members>
|
||||
requires (!std::is_const_v<Root>) && Writable_Adapted_Object<typename Synchronized_Field<Root, Lock, Field_Lock, Members...>::value_type>
|
||||
Update_Result apply_json_patch(Synchronized_Field<Root, Lock, Field_Lock, Members...> target, const Json& patch) {
|
||||
return target.object_write([&](auto& item) {
|
||||
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, class Root, Basic_Lock Lock, Basic_Lock Field_Lock, auto... Members>
|
||||
requires (!std::is_const_v<Root>) && Creatable_Writable_Object<typename Synchronized_Field<Root, Lock, Field_Lock, Members...>::value_type>
|
||||
Update_Result apply_frontend_create(Synchronized_Field<Root, Lock, Field_Lock, Members...> target, const Json& value) {
|
||||
return target.object_write([&](auto& item) {
|
||||
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, class Root, Basic_Lock Lock, Basic_Lock Field_Lock, auto... Members>
|
||||
requires (!std::is_const_v<Root>) && Writable_Adapted_Object<typename Synchronized_Field<Root, Lock, Field_Lock, Members...>::value_type>
|
||||
Update_Result apply_frontend_patch(Synchronized_Field<Root, Lock, Field_Lock, Members...> target, const Json& patch) {
|
||||
return target.object_write([&](auto& item) {
|
||||
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, class Root, Basic_Lock Lock, Basic_Lock Field_Lock, auto... Members>
|
||||
requires (!std::is_const_v<Root>) && Writable_Adapted_Object<typename Synchronized_Field<Root, Lock, Field_Lock, Members...>::value_type>
|
||||
Update_Result apply_patch(Synchronized_Field<Root, Lock, Field_Lock, Members...> target, const Json& 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>
|
||||
@@ -810,7 +809,7 @@ T from_json(const Json& value) {
|
||||
return result;
|
||||
}
|
||||
template <class T>
|
||||
requires Described_Type<std::remove_cvref_t<T>> || Object_Adapter_With_Snapshot<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 {
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
#include <concepts>
|
||||
namespace adminive {
|
||||
struct No_Lock {
|
||||
void lock() noexcept {}
|
||||
bool try_lock() noexcept {
|
||||
return true;
|
||||
}
|
||||
void unlock() noexcept {}
|
||||
};
|
||||
template <class T>
|
||||
concept Basic_Lock = std::default_initializable<T> && requires(T& value) {
|
||||
value.lock();
|
||||
value.unlock();
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,400 @@
|
||||
#pragma once
|
||||
#include "adminive/descriptor.hpp"
|
||||
#include <structive/property/property.hpp>
|
||||
#include <array>
|
||||
#include <concepts>
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <ranges>
|
||||
#include <span>
|
||||
#include <string_view>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
namespace adminive {
|
||||
template <class Mutex>
|
||||
struct Mutex_Policy {
|
||||
using mutex_type = Mutex;
|
||||
};
|
||||
template <class T, structive::Synchronization_Policy Policy = structive::Shared_Mutex_Policy>
|
||||
class Managed_Value;
|
||||
template <class Managed, std::size_t Root_Index, auto... Members>
|
||||
class Managed_Field;
|
||||
namespace detail {
|
||||
template <class T>
|
||||
struct Is_Reference_Wrapper : std::false_type {};
|
||||
template <class T>
|
||||
struct Is_Reference_Wrapper<std::reference_wrapper<T>> : std::true_type {};
|
||||
template <class Result>
|
||||
concept Managed_Callback_Result = std::is_void_v<Result> || (!std::is_reference_v<Result> && !std::is_pointer_v<Result> && !Is_Reference_Wrapper<std::remove_cvref_t<Result>>::value && !std::ranges::view<std::remove_cvref_t<Result>>);
|
||||
template <std::size_t Value>
|
||||
consteval std::size_t decimal_digits() {
|
||||
std::size_t result = 1;
|
||||
std::size_t current = Value;
|
||||
while(current >= 10) {
|
||||
current /= 10;
|
||||
++result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
template <std::size_t Index>
|
||||
consteval auto make_managed_key_text() {
|
||||
constexpr std::string_view prefix = "__adminive_";
|
||||
constexpr std::size_t digits = decimal_digits<Index>();
|
||||
std::array<char, prefix.size() + digits> result{};
|
||||
for(std::size_t position = 0; position < prefix.size(); ++position) {
|
||||
result[position] = prefix[position];
|
||||
}
|
||||
std::size_t value = Index;
|
||||
for(std::size_t position = 0; position < digits; ++position) {
|
||||
result[prefix.size() + digits - position - 1] = static_cast<char>('0' + value % 10);
|
||||
value /= 10;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
template <std::size_t Index>
|
||||
struct Managed_Key_Value {
|
||||
static constexpr auto text = make_managed_key_text<Index>();
|
||||
constexpr std::string_view view() const noexcept {
|
||||
return {text.data(), text.size()};
|
||||
}
|
||||
};
|
||||
template <std::size_t Index>
|
||||
struct Managed_Key_Attribute {
|
||||
using attribute_category = structive::Key_Category;
|
||||
static constexpr bool single_valued = true;
|
||||
static constexpr bool inheritable = false;
|
||||
static constexpr Managed_Key_Value<Index> value{};
|
||||
};
|
||||
template <class T>
|
||||
consteval bool member_type_has_managed_writes() {
|
||||
using Value = std::remove_cvref_t<T>;
|
||||
if constexpr(Described_Type<Value>) {
|
||||
return described_type_has_managed_writes<Value>();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
template <class Field>
|
||||
inline constexpr bool managed_field_writable_v = Field::managed_writable || member_type_has_managed_writes<typename Field::member_type>();
|
||||
template <class Field>
|
||||
consteval bool field_requires_synchronization();
|
||||
template <class T>
|
||||
consteval bool member_type_requires_synchronization() {
|
||||
using Value = std::remove_cvref_t<T>;
|
||||
if constexpr(Described_Type<Value>) {
|
||||
using Fields = Descriptor_Fields<Value>;
|
||||
return []<std::size_t... Indexes>(std::index_sequence<Indexes...>) {
|
||||
return (field_requires_synchronization<std::tuple_element_t<Indexes, Fields>>() || ...);
|
||||
}(std::make_index_sequence<std::tuple_size_v<Fields>>{});
|
||||
}
|
||||
return false;
|
||||
}
|
||||
template <class Field>
|
||||
consteval bool field_requires_synchronization() {
|
||||
if constexpr(Field::managed_writable) {
|
||||
return Field::synchronized;
|
||||
}
|
||||
return member_type_requires_synchronization<typename Field::member_type>();
|
||||
}
|
||||
template <class Managed, class Field, std::size_t Index>
|
||||
struct Managed_Field_Accessor {
|
||||
using object_type = Managed;
|
||||
using value_type = typename Field::member_type;
|
||||
struct storage_identity {};
|
||||
static constexpr bool readable = true;
|
||||
static constexpr bool writable = managed_field_writable_v<Field>;
|
||||
static constexpr bool synchronized_view_read = false;
|
||||
static constexpr bool trusted_object_access = false;
|
||||
const value_type& read(const object_type& object) const noexcept(noexcept(Field::accessor_type::get(object.unsafe_value()))) {
|
||||
return Field::accessor_type::get(object.unsafe_value());
|
||||
}
|
||||
value_type& read(object_type& object) const noexcept(noexcept(Field::accessor_type::get(object.unsafe_value()))) {
|
||||
return Field::accessor_type::get(object.unsafe_value());
|
||||
}
|
||||
template <class Value>
|
||||
void write(object_type& object, Value&& value) const requires writable && std::assignable_from<value_type&, Value> {
|
||||
Field::accessor_type::get(object.unsafe_value()) = std::forward<Value>(value);
|
||||
}
|
||||
};
|
||||
template <class Managed, class Field, std::size_t Index>
|
||||
auto make_structive_property() {
|
||||
using Accessor = Managed_Field_Accessor<Managed, Field, Index>;
|
||||
using Key = Managed_Key_Attribute<Index>;
|
||||
if constexpr(managed_field_writable_v<Field>) {
|
||||
using Capability = structive::Property_Capability_Attribute<structive::Property_Capability::read_write>;
|
||||
return structive::Property_Descriptor<Accessor, Key, Capability>{{}, {Key{}, Capability{}}};
|
||||
} else {
|
||||
using Capability = structive::Property_Capability_Attribute<structive::Property_Capability::read>;
|
||||
return structive::Property_Descriptor<Accessor, Key, Capability>{{}, {Key{}, Capability{}}};
|
||||
}
|
||||
}
|
||||
template <class Managed, class T, std::size_t... Indexes>
|
||||
auto make_managed_schema_impl(std::index_sequence<Indexes...>) {
|
||||
using Fields = Descriptor_Fields<T>;
|
||||
structive::Synchronization_Plan plan;
|
||||
([&] {
|
||||
using Field = std::tuple_element_t<Indexes, Fields>;
|
||||
if constexpr(managed_field_writable_v<Field> && !field_requires_synchronization<Field>()) {
|
||||
plan.unsynchronized(Managed_Key_Attribute<Indexes>::value.view());
|
||||
}
|
||||
}(), ...);
|
||||
return structive::object<Managed>(std::move(plan), make_structive_property<Managed, std::tuple_element_t<Indexes, Fields>, Indexes>()...);
|
||||
}
|
||||
template <class Managed, Described_Type T>
|
||||
auto make_managed_schema() {
|
||||
return make_managed_schema_impl<Managed, T>(std::make_index_sequence<std::tuple_size_v<Descriptor_Fields<T>>>{});
|
||||
}
|
||||
template <Described_Type T, auto Member, std::size_t Index = 0>
|
||||
consteval std::size_t described_member_index() {
|
||||
using Fields = Descriptor_Fields<T>;
|
||||
if constexpr(Index == std::tuple_size_v<Fields>) {
|
||||
return Index;
|
||||
} else {
|
||||
using Field = std::tuple_element_t<Index, Fields>;
|
||||
using Accessor = typename Field::accessor_type;
|
||||
if constexpr(requires { Accessor::member; } && std::same_as<std::remove_cv_t<decltype(Accessor::member)>, decltype(Member)>) {
|
||||
if constexpr(Accessor::member == Member) {
|
||||
return Index;
|
||||
}
|
||||
}
|
||||
return described_member_index<T, Member, Index + 1>();
|
||||
}
|
||||
}
|
||||
template <class Current, auto Member, auto... Rest>
|
||||
consteval bool managed_member_path_writable() {
|
||||
static_assert(Described_Type<std::remove_cvref_t<Current>>);
|
||||
constexpr auto index = described_member_index<std::remove_cvref_t<Current>, Member>();
|
||||
using Field = Descriptor_Field<std::remove_cvref_t<Current>, index>;
|
||||
if constexpr(sizeof...(Rest) == 0) {
|
||||
return Field::managed_writable;
|
||||
} else {
|
||||
return managed_member_path_writable<typename Field::member_type, Rest...>();
|
||||
}
|
||||
}
|
||||
template <class Current, auto Member, auto... Rest>
|
||||
consteval bool managed_member_path_requires_synchronization() {
|
||||
static_assert(Described_Type<std::remove_cvref_t<Current>>);
|
||||
constexpr auto index = described_member_index<std::remove_cvref_t<Current>, Member>();
|
||||
using Field = Descriptor_Field<std::remove_cvref_t<Current>, index>;
|
||||
if constexpr(Field::managed_writable) {
|
||||
return Field::synchronized;
|
||||
} else if constexpr(sizeof...(Rest) == 0) {
|
||||
return false;
|
||||
} else {
|
||||
return managed_member_path_requires_synchronization<typename Field::member_type, Rest...>();
|
||||
}
|
||||
}
|
||||
template <class Root, std::size_t Root_Index, auto... Members>
|
||||
consteval bool managed_path_writable() {
|
||||
using Field = Descriptor_Field<Root, Root_Index>;
|
||||
if constexpr(sizeof...(Members) == 0) {
|
||||
return Field::managed_writable;
|
||||
} else {
|
||||
return managed_member_path_writable<typename Field::member_type, Members...>();
|
||||
}
|
||||
}
|
||||
template <class Root, std::size_t Root_Index, auto... Members>
|
||||
consteval bool managed_path_requires_synchronization() {
|
||||
using Field = Descriptor_Field<Root, Root_Index>;
|
||||
if constexpr(Field::managed_writable) {
|
||||
return Field::synchronized;
|
||||
} else if constexpr(sizeof...(Members) == 0) {
|
||||
return false;
|
||||
} else {
|
||||
return managed_member_path_requires_synchronization<typename Field::member_type, Members...>();
|
||||
}
|
||||
}
|
||||
template <class Object, auto Member, auto... Rest>
|
||||
decltype(auto) path_value(Object& object) {
|
||||
auto&& next = object.*Member;
|
||||
if constexpr(sizeof...(Rest) == 0) {
|
||||
return std::forward<decltype(next)>(next);
|
||||
} else {
|
||||
return path_value<decltype(next), Rest...>(next);
|
||||
}
|
||||
}
|
||||
template <class Object, auto Member, auto... Rest>
|
||||
decltype(auto) path_value(const Object& object) {
|
||||
auto&& next = object.*Member;
|
||||
if constexpr(sizeof...(Rest) == 0) {
|
||||
return std::forward<decltype(next)>(next);
|
||||
} else {
|
||||
return path_value<decltype(next), Rest...>(next);
|
||||
}
|
||||
}
|
||||
template <class T, std::size_t Root_Index, auto... Members>
|
||||
decltype(auto) managed_path_value(T& object) {
|
||||
using Field = Descriptor_Field<T, Root_Index>;
|
||||
auto&& root = Field::accessor_type::get(object);
|
||||
if constexpr(sizeof...(Members) == 0) {
|
||||
return std::forward<decltype(root)>(root);
|
||||
} else {
|
||||
return path_value<decltype(root), Members...>(root);
|
||||
}
|
||||
}
|
||||
template <class T, std::size_t Root_Index, auto... Members>
|
||||
decltype(auto) managed_path_value(const T& object) {
|
||||
using Field = Descriptor_Field<T, Root_Index>;
|
||||
auto&& root = Field::accessor_type::get(object);
|
||||
if constexpr(sizeof...(Members) == 0) {
|
||||
return std::forward<decltype(root)>(root);
|
||||
} else {
|
||||
return path_value<decltype(root), Members...>(root);
|
||||
}
|
||||
}
|
||||
template <class T, std::size_t... Indexes>
|
||||
auto managed_key_array_impl(std::index_sequence<Indexes...>) {
|
||||
return std::array<std::string_view, sizeof...(Indexes)>{Managed_Key_Attribute<Indexes>::value.view()...};
|
||||
}
|
||||
template <Described_Type T>
|
||||
const auto& managed_key_array() {
|
||||
static const auto value = managed_key_array_impl<T>(std::make_index_sequence<std::tuple_size_v<Descriptor_Fields<T>>>{});
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
namespace structive {
|
||||
template <class T, Synchronization_Policy Policy>
|
||||
struct Type_Descriptor<adminive::Managed_Value<T, Policy>> {
|
||||
static auto get() {
|
||||
return adminive::detail::make_managed_schema<adminive::Managed_Value<T, Policy>, T>();
|
||||
}
|
||||
};
|
||||
}
|
||||
namespace adminive {
|
||||
template <class T, structive::Synchronization_Policy Policy>
|
||||
class Managed_Value : public structive::Property_Object<Managed_Value<T, Policy>, Policy> {
|
||||
using Base = structive::Property_Object<Managed_Value<T, Policy>, Policy>;
|
||||
public:
|
||||
using value_type = T;
|
||||
using policy_type = Policy;
|
||||
Managed_Value() requires std::default_initializable<T> = default;
|
||||
explicit Managed_Value(T value) : value_(std::move(value)) {}
|
||||
explicit Managed_Value(structive::Property_Synchronization synchronization) : Base(std::move(synchronization)) {}
|
||||
Managed_Value(T value, structive::Property_Synchronization synchronization) : Base(std::move(synchronization)), value_(std::move(value)) {}
|
||||
Managed_Value(const Managed_Value&) requires std::copy_constructible<T> = default;
|
||||
Managed_Value(Managed_Value&&) noexcept(std::is_nothrow_move_constructible_v<T>) requires std::move_constructible<T> = default;
|
||||
Managed_Value& operator=(const Managed_Value&) requires std::is_copy_assignable_v<T> = default;
|
||||
Managed_Value& operator=(Managed_Value&&) noexcept(std::is_nothrow_move_assignable_v<T>) requires std::is_move_assignable_v<T> = default;
|
||||
T& unsafe_value() noexcept {
|
||||
return value_;
|
||||
}
|
||||
const T& unsafe_value() const noexcept {
|
||||
return value_;
|
||||
}
|
||||
template <class Function>
|
||||
decltype(auto) read(Function&& function) const requires std::invocable<Function, const T&> && detail::Managed_Callback_Result<std::invoke_result_t<Function, const T&>> {
|
||||
const auto& keys = detail::managed_key_array<T>();
|
||||
auto guard = this->lock_shared(std::span<const std::string_view>{keys});
|
||||
return std::invoke(std::forward<Function>(function), std::as_const(value_));
|
||||
}
|
||||
template <class Function>
|
||||
decltype(auto) write(Function&& function) requires std::invocable<Function, T&> && detail::Managed_Callback_Result<std::invoke_result_t<Function, T&>> {
|
||||
using Result = std::invoke_result_t<Function, T&>;
|
||||
if constexpr(std::is_void_v<Result>) {
|
||||
this->with_all_writable_locked([&](auto&) {
|
||||
std::invoke(std::forward<Function>(function), value_);
|
||||
});
|
||||
} else {
|
||||
std::optional<std::remove_cvref_t<Result>> result;
|
||||
this->with_all_writable_locked([&](auto&) {
|
||||
result.emplace(std::invoke(std::forward<Function>(function), value_));
|
||||
});
|
||||
return std::move(*result);
|
||||
}
|
||||
}
|
||||
T snapshot() const requires std::copy_constructible<T> {
|
||||
return read([](const T& value) {
|
||||
return value;
|
||||
});
|
||||
}
|
||||
template <auto Member>
|
||||
auto member() {
|
||||
constexpr auto index = detail::described_member_index<T, Member>();
|
||||
static_assert(index < std::tuple_size_v<Descriptor_Fields<T>>);
|
||||
return Managed_Field<Managed_Value, index>{*this};
|
||||
}
|
||||
template <auto Member>
|
||||
auto member() const {
|
||||
constexpr auto index = detail::described_member_index<T, Member>();
|
||||
static_assert(index < std::tuple_size_v<Descriptor_Fields<T>>);
|
||||
return Managed_Field<const Managed_Value, index>{*this};
|
||||
}
|
||||
template <std::size_t Index>
|
||||
auto field() {
|
||||
static_assert(Index < std::tuple_size_v<Descriptor_Fields<T>>);
|
||||
return Managed_Field<Managed_Value, Index>{*this};
|
||||
}
|
||||
template <std::size_t Index>
|
||||
auto field() const {
|
||||
static_assert(Index < std::tuple_size_v<Descriptor_Fields<T>>);
|
||||
return Managed_Field<const Managed_Value, Index>{*this};
|
||||
}
|
||||
private:
|
||||
T value_{};
|
||||
};
|
||||
template <class Managed, std::size_t Root_Index, auto... Members>
|
||||
class Managed_Field {
|
||||
using Owner = std::remove_const_t<Managed>;
|
||||
using Root = typename Owner::value_type;
|
||||
public:
|
||||
using owner_type = Managed;
|
||||
using value_type = std::remove_cvref_t<decltype(detail::managed_path_value<Root, Root_Index, Members...>(std::declval<Root&>()))>;
|
||||
explicit Managed_Field(Managed& owner) : owner_(&owner) {}
|
||||
template <class Function>
|
||||
decltype(auto) read(Function&& function) const requires std::invocable<Function, const value_type&> && detail::Managed_Callback_Result<std::invoke_result_t<Function, const value_type&>> {
|
||||
if constexpr(detail::managed_path_requires_synchronization<Root, Root_Index, Members...>()) {
|
||||
const auto key = detail::Managed_Key_Attribute<Root_Index>::value.view();
|
||||
auto guard = owner_->lock_shared({key});
|
||||
const auto& value = detail::managed_path_value<Root, Root_Index, Members...>(owner_->unsafe_value());
|
||||
return std::invoke(std::forward<Function>(function), value);
|
||||
} else {
|
||||
const auto& value = detail::managed_path_value<Root, Root_Index, Members...>(owner_->unsafe_value());
|
||||
return std::invoke(std::forward<Function>(function), value);
|
||||
}
|
||||
}
|
||||
template <class Function>
|
||||
decltype(auto) write(Function&& function) requires (!std::is_const_v<Managed>) && (detail::managed_path_writable<Root, Root_Index, Members...>()) && std::invocable<Function, value_type&> && detail::Managed_Callback_Result<std::invoke_result_t<Function, value_type&>> {
|
||||
if constexpr(detail::managed_path_requires_synchronization<Root, Root_Index, Members...>()) {
|
||||
const auto key = detail::Managed_Key_Attribute<Root_Index>::value.view();
|
||||
auto guard = owner_->lock_unique({key});
|
||||
auto& value = detail::managed_path_value<Root, Root_Index, Members...>(owner_->unsafe_value());
|
||||
return std::invoke(std::forward<Function>(function), value);
|
||||
} else {
|
||||
auto& value = detail::managed_path_value<Root, Root_Index, Members...>(owner_->unsafe_value());
|
||||
return std::invoke(std::forward<Function>(function), value);
|
||||
}
|
||||
}
|
||||
value_type snapshot() const requires std::copy_constructible<value_type> {
|
||||
return read([](const value_type& value) {
|
||||
return value;
|
||||
});
|
||||
}
|
||||
template <auto Member>
|
||||
auto member() requires (!std::is_const_v<Managed>) {
|
||||
return Managed_Field<Managed, Root_Index, Members..., Member>{*owner_};
|
||||
}
|
||||
template <auto Member>
|
||||
auto member() const {
|
||||
return Managed_Field<const Owner, Root_Index, Members..., Member>{*owner_};
|
||||
}
|
||||
Managed& owner() const noexcept {
|
||||
return *owner_;
|
||||
}
|
||||
private:
|
||||
Managed* owner_;
|
||||
};
|
||||
template <class T>
|
||||
struct Is_Managed_Value : std::false_type {};
|
||||
template <class T, structive::Synchronization_Policy Policy>
|
||||
struct Is_Managed_Value<Managed_Value<T, Policy>> : std::true_type {};
|
||||
template <class T>
|
||||
concept Managed_Value_Type = Is_Managed_Value<std::remove_cvref_t<T>>::value;
|
||||
template <class T>
|
||||
struct Is_Managed_Field : std::false_type {};
|
||||
template <class Managed, std::size_t Root_Index, auto... Members>
|
||||
struct Is_Managed_Field<Managed_Field<Managed, Root_Index, Members...>> : std::true_type {};
|
||||
template <class T>
|
||||
concept Managed_Field_Type = Is_Managed_Field<std::remove_cvref_t<T>>::value;
|
||||
}
|
||||
@@ -285,7 +285,7 @@ int main() {
|
||||
const Json encoded = adminive::to_json<Json>(config);
|
||||
assert((adminive::json_get<Json, int>(adminive::Json_Adapter<Json>::at(encoded, "worker_count")) == 12));
|
||||
assert((adminive::json_get<Json, std::string>(adminive::Json_Adapter<Json>::at(encoded, "mode")) == "secondary"));
|
||||
adminive::Collection_Service<Config, Json, adminive::Empty_Lock> collection("/configs", {Config{}});
|
||||
adminive::Collection_Service<Config, Json, adminive::No_Lock> collection("/configs", {Config{}});
|
||||
const auto list = collection.list_response();
|
||||
assert(list.status == 200);
|
||||
const auto& data = adminive::Json_Adapter<Json>::at(list.body, "data");
|
||||
|
||||
@@ -65,9 +65,9 @@ if(BUILD_TESTING)
|
||||
target_include_directories(Adminive_Config_Store_Test PRIVATE "${CMAKE_CURRENT_LIST_DIR}/src")
|
||||
target_link_libraries(Adminive_Config_Store_Test PRIVATE Adminive_Example)
|
||||
add_test(NAME Adminive_Config_Store_Test COMMAND Adminive_Config_Store_Test)
|
||||
add_executable(Adminive_Synchronized_Test "${CMAKE_CURRENT_LIST_DIR}/tests/synchronized_test.cpp")
|
||||
target_link_libraries(Adminive_Synchronized_Test PRIVATE Adminive::Default)
|
||||
add_test(NAME Adminive_Synchronized_Test COMMAND Adminive_Synchronized_Test)
|
||||
add_executable(Adminive_Managed_Test "${CMAKE_CURRENT_LIST_DIR}/tests/managed_test.cpp")
|
||||
target_link_libraries(Adminive_Managed_Test PRIVATE Adminive::Default)
|
||||
add_test(NAME Adminive_Managed_Test COMMAND Adminive_Managed_Test)
|
||||
add_executable(Adminive_Drogon_Adapter_Test "${CMAKE_CURRENT_LIST_DIR}/tests/drogon_adapter_test.cpp")
|
||||
target_include_directories(Adminive_Drogon_Adapter_Test BEFORE PRIVATE "${CMAKE_CURRENT_LIST_DIR}/tests/fake_drogon")
|
||||
target_link_libraries(Adminive_Drogon_Adapter_Test PRIVATE Adminive::Default)
|
||||
@@ -78,7 +78,7 @@ if(BUILD_TESTING)
|
||||
target_compile_options(Adminive_Drogon_Adapter_Test PRIVATE /permissive-)
|
||||
target_compile_options(Adminive_Safety_Test PRIVATE /permissive-)
|
||||
target_compile_options(Adminive_Config_Store_Test PRIVATE /permissive-)
|
||||
target_compile_options(Adminive_Synchronized_Test PRIVATE /permissive-)
|
||||
target_compile_options(Adminive_Managed_Test PRIVATE /permissive-)
|
||||
endif()
|
||||
endif()
|
||||
install(TARGETS Adminive_Service Adminive_Nlohmann Adminive_MagicEnum Adminive_BoostPfr Adminive_Httplib Adminive_Default EXPORT AdminiveServiceTargets)
|
||||
|
||||
@@ -82,11 +82,11 @@ public:
|
||||
using Service = Resource_Service<T, Json, Lock>;
|
||||
using Transaction = typename Service::Transaction;
|
||||
Drogon_Resource(T& value, std::string path, Transaction transaction = {}, Lock* shared_lock = nullptr) : service_(std::make_shared<Service>(value, std::move(path), std::move(transaction), shared_lock)) {}
|
||||
template <Basic_Lock Field_Lock>
|
||||
explicit Drogon_Resource(Synchronized_Value<std::remove_cvref_t<T>, Lock, Field_Lock>& value, std::string path, Transaction transaction = {}) : service_(std::make_shared<Service>(value, std::move(path), std::move(transaction))) {}
|
||||
template <class Root, Basic_Lock Field_Lock, auto... Members>
|
||||
requires (!std::is_const_v<Root>) && std::same_as<typename Synchronized_Field<Root, Lock, Field_Lock, Members...>::value_type, std::remove_cvref_t<T>>
|
||||
explicit Drogon_Resource(Synchronized_Field<Root, Lock, Field_Lock, Members...> value, std::string path, Transaction transaction = {}, Resource_Lock_Scope lock_scope = Resource_Lock_Scope::local) : service_(std::make_shared<Service>(value, std::move(path), std::move(transaction), lock_scope)) {}
|
||||
template <structive::Synchronization_Policy Policy>
|
||||
explicit Drogon_Resource(Managed_Value<std::remove_cvref_t<T>, Policy>& value, std::string path, Transaction transaction = {}) : service_(std::make_shared<Service>(value, std::move(path), std::move(transaction))) {}
|
||||
template <class Managed, std::size_t Root_Index, auto... Members>
|
||||
requires (!std::is_const_v<Managed>) && std::same_as<typename Managed_Field<Managed, Root_Index, Members...>::value_type, std::remove_cvref_t<T>>
|
||||
explicit Drogon_Resource(Managed_Field<Managed, Root_Index, Members...> value, std::string path, Transaction transaction = {}) : service_(std::make_shared<Service>(value, std::move(path), std::move(transaction))) {}
|
||||
Json amis_schema() const {
|
||||
return service_->amis_schema();
|
||||
}
|
||||
|
||||
@@ -36,11 +36,11 @@ public:
|
||||
using Service = Resource_Service<T, Json, Lock>;
|
||||
using Transaction = typename Service::Transaction;
|
||||
Http_Resource(T& value, std::string path, Transaction transaction = {}, Lock* shared_lock = nullptr) : service_(std::make_shared<Service>(value, std::move(path), std::move(transaction), shared_lock)) {}
|
||||
template <Basic_Lock Field_Lock>
|
||||
explicit Http_Resource(Synchronized_Value<std::remove_cvref_t<T>, Lock, Field_Lock>& value, std::string path, Transaction transaction = {}) : service_(std::make_shared<Service>(value, std::move(path), std::move(transaction))) {}
|
||||
template <class Root, Basic_Lock Field_Lock, auto... Members>
|
||||
requires (!std::is_const_v<Root>) && std::same_as<typename Synchronized_Field<Root, Lock, Field_Lock, Members...>::value_type, std::remove_cvref_t<T>>
|
||||
explicit Http_Resource(Synchronized_Field<Root, Lock, Field_Lock, Members...> value, std::string path, Transaction transaction = {}, Resource_Lock_Scope lock_scope = Resource_Lock_Scope::local) : service_(std::make_shared<Service>(value, std::move(path), std::move(transaction), lock_scope)) {}
|
||||
template <structive::Synchronization_Policy Policy>
|
||||
explicit Http_Resource(Managed_Value<std::remove_cvref_t<T>, Policy>& value, std::string path, Transaction transaction = {}) : service_(std::make_shared<Service>(value, std::move(path), std::move(transaction))) {}
|
||||
template <class Managed, std::size_t Root_Index, auto... Members>
|
||||
requires (!std::is_const_v<Managed>) && std::same_as<typename Managed_Field<Managed, Root_Index, Members...>::value_type, std::remove_cvref_t<T>>
|
||||
explicit Http_Resource(Managed_Field<Managed, Root_Index, Members...> value, std::string path, Transaction transaction = {}) : service_(std::make_shared<Service>(value, std::move(path), std::move(transaction))) {}
|
||||
Json amis_schema() const {
|
||||
return service_->amis_schema();
|
||||
}
|
||||
|
||||
@@ -31,8 +31,11 @@ void replace_file(const std::filesystem::path& temporary, const std::filesystem:
|
||||
}
|
||||
#endif
|
||||
}
|
||||
structive::Property_Synchronization shared_config_synchronization() {
|
||||
return structive::property_synchronization(structive::Synchronization_Plan{structive::Synchronization_Default::shared});
|
||||
}
|
||||
Config_Store::Config_Store(std::filesystem::path path) : path_(std::move(path)) {
|
||||
}
|
||||
Config_Store::Config_Store(std::filesystem::path path) : path_(std::move(path)), data_(shared_config_synchronization()) {
|
||||
load_or_create();
|
||||
}
|
||||
Config_Store::Radio_Service_Access Config_Store::radio_service() noexcept {
|
||||
@@ -79,48 +82,48 @@ Resource_Transaction<Network_Table_Config> Config_Store::network_table_transacti
|
||||
});
|
||||
}
|
||||
void Config_Store::persist_radio_service(const Radio_Service_Config& value) {
|
||||
data_.write([&](Application_Config& data) {
|
||||
Application_Config candidate = data;
|
||||
data_.member<&Application_Config::radio_service>().write([&](Radio_Service_Config& target) {
|
||||
Application_Config candidate = data_.unsafe_value();
|
||||
candidate.radio_service = value;
|
||||
persist_candidate(candidate);
|
||||
data = std::move(candidate);
|
||||
target = value;
|
||||
});
|
||||
}
|
||||
void Config_Store::persist_alerts(const Alert_Config& value) {
|
||||
data_.write([&](Application_Config& data) {
|
||||
Application_Config candidate = data;
|
||||
data_.member<&Application_Config::alerts>().write([&](Alert_Config& target) {
|
||||
Application_Config candidate = data_.unsafe_value();
|
||||
candidate.alerts = value;
|
||||
persist_candidate(candidate);
|
||||
data = std::move(candidate);
|
||||
target = value;
|
||||
});
|
||||
}
|
||||
void Config_Store::persist_serial_table(const Serial_Table_Config& value) {
|
||||
data_.write([&](Application_Config& data) {
|
||||
Application_Config candidate = data;
|
||||
data_.member<&Application_Config::serial_table>().write([&](Serial_Table_Config& target) {
|
||||
Application_Config candidate = data_.unsafe_value();
|
||||
candidate.serial_table = value;
|
||||
persist_candidate(candidate);
|
||||
data = std::move(candidate);
|
||||
target = value;
|
||||
});
|
||||
}
|
||||
void Config_Store::persist_network_table(const Network_Table_Config& value) {
|
||||
data_.write([&](Application_Config& data) {
|
||||
Application_Config candidate = data;
|
||||
data_.member<&Application_Config::network_table>().write([&](Network_Table_Config& target) {
|
||||
Application_Config candidate = data_.unsafe_value();
|
||||
candidate.network_table = value;
|
||||
persist_candidate(candidate);
|
||||
data = std::move(candidate);
|
||||
target = value;
|
||||
});
|
||||
}
|
||||
void Config_Store::persist_default_device_type(Device_Table_Type value) {
|
||||
data_.write([&](Application_Config& data) {
|
||||
Application_Config candidate = data;
|
||||
data_.member<&Application_Config::default_device_type>().write([&](Device_Table_Type& target) {
|
||||
Application_Config candidate = data_.unsafe_value();
|
||||
candidate.default_device_type = value;
|
||||
persist_candidate(candidate);
|
||||
data = std::move(candidate);
|
||||
target = value;
|
||||
});
|
||||
}
|
||||
void Config_Store::load_or_create() {
|
||||
if(!std::filesystem::exists(path_)) {
|
||||
persist_candidate(detail::Synchronized_Access::value(data_));
|
||||
persist_candidate(data_.unsafe_value());
|
||||
return;
|
||||
}
|
||||
std::ifstream input(path_);
|
||||
@@ -133,7 +136,7 @@ void Config_Store::load_or_create() {
|
||||
if(!result.success) {
|
||||
throw std::runtime_error("invalid configuration file: " + result.message);
|
||||
}
|
||||
detail::Synchronized_Access::value(data_) = std::move(candidate);
|
||||
data_.unsafe_value() = std::move(candidate);
|
||||
}
|
||||
void Config_Store::persist_candidate(const Application_Config& value) {
|
||||
const auto parent = path_.parent_path();
|
||||
@@ -157,24 +160,16 @@ void Config_Store::persist_candidate(const Application_Config& value) {
|
||||
}
|
||||
replace_file(temporary, path_);
|
||||
}
|
||||
void Config_Store::persist_radio_service_locked(const Radio_Service_Config& value) {
|
||||
Application_Config candidate = detail::Synchronized_Access::value(data_);
|
||||
candidate.radio_service = value;
|
||||
persist_candidate(candidate);
|
||||
void Config_Store::persist_radio_service_locked(const Radio_Service_Config&) {
|
||||
persist_candidate(data_.unsafe_value());
|
||||
}
|
||||
void Config_Store::persist_alerts_locked(const Alert_Config& value) {
|
||||
Application_Config candidate = detail::Synchronized_Access::value(data_);
|
||||
candidate.alerts = value;
|
||||
persist_candidate(candidate);
|
||||
void Config_Store::persist_alerts_locked(const Alert_Config&) {
|
||||
persist_candidate(data_.unsafe_value());
|
||||
}
|
||||
void Config_Store::persist_serial_table_locked(const Serial_Table_Config& value) {
|
||||
Application_Config candidate = detail::Synchronized_Access::value(data_);
|
||||
candidate.serial_table = value;
|
||||
persist_candidate(candidate);
|
||||
void Config_Store::persist_serial_table_locked(const Serial_Table_Config&) {
|
||||
persist_candidate(data_.unsafe_value());
|
||||
}
|
||||
void Config_Store::persist_network_table_locked(const Network_Table_Config& value) {
|
||||
Application_Config candidate = detail::Synchronized_Access::value(data_);
|
||||
candidate.network_table = value;
|
||||
persist_candidate(candidate);
|
||||
void Config_Store::persist_network_table_locked(const Network_Table_Config&) {
|
||||
persist_candidate(data_.unsafe_value());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
namespace adminive::example {
|
||||
class Config_Store {
|
||||
public:
|
||||
using Storage = Synchronized_Value<Application_Config>;
|
||||
using Storage = Managed_Value<Application_Config>;
|
||||
using Radio_Service_Access = decltype(std::declval<Storage&>().member<&Application_Config::radio_service>());
|
||||
using Alert_Access = decltype(std::declval<Storage&>().member<&Application_Config::alerts>());
|
||||
using Serial_Table_Access = decltype(std::declval<Storage&>().member<&Application_Config::serial_table>());
|
||||
|
||||
@@ -22,7 +22,7 @@ Json make_table_page(std::string_view title, std::string_view color, Json config
|
||||
return Json{{"type", "container"}, {"body", Json::array({std::move(heading), std::move(config_form), std::move(table_page.at("body"))})}};
|
||||
}
|
||||
}
|
||||
Serial_Device_Table::Serial_Device_Table(Config_Store& store) : store_(store), config_resource_(store.serial_table(), "/admin/device_tables/serial/config", store.serial_table_transaction(), Resource_Lock_Scope::root), rows_("/admin/device_tables/serial/items", make_serial_rows()) {}
|
||||
Serial_Device_Table::Serial_Device_Table(Config_Store& store) : store_(store), config_resource_(store.serial_table(), "/admin/device_tables/serial/config", store.serial_table_transaction()), rows_("/admin/device_tables/serial/items", make_serial_rows()) {}
|
||||
Device_Table_Type Serial_Device_Table::type() const noexcept {
|
||||
return Device_Table_Type::serial;
|
||||
}
|
||||
@@ -40,7 +40,7 @@ void Serial_Device_Table::bind(httplib::Server& server) {
|
||||
config_resource_.bind(server);
|
||||
rows_.bind(server);
|
||||
}
|
||||
Network_Device_Table::Network_Device_Table(Config_Store& store) : store_(store), config_resource_(store.network_table(), "/admin/device_tables/network/config", store.network_table_transaction(), Resource_Lock_Scope::root), rows_("/admin/device_tables/network/items", make_network_rows()) {}
|
||||
Network_Device_Table::Network_Device_Table(Config_Store& store) : store_(store), config_resource_(store.network_table(), "/admin/device_tables/network/config", store.network_table_transaction()), rows_("/admin/device_tables/network/items", make_network_rows()) {}
|
||||
Device_Table_Type Network_Device_Table::type() const noexcept {
|
||||
return Device_Table_Type::network;
|
||||
}
|
||||
|
||||
@@ -105,11 +105,11 @@ struct Type_Descriptor<example::Application_Config> {
|
||||
using T = example::Application_Config;
|
||||
return object<T>(
|
||||
"application_config",
|
||||
ADMINIVE_FIELD_LABEL(T, default_device_type, "默认设备类型").locked(),
|
||||
ADMINIVE_FIELD_LABEL(T, radio_service, "无线电服务"),
|
||||
ADMINIVE_FIELD_LABEL(T, alerts, "告警"),
|
||||
ADMINIVE_FIELD_LABEL(T, serial_table, "串口表格"),
|
||||
ADMINIVE_FIELD_LABEL(T, network_table, "网络表格")
|
||||
ADMINIVE_FIELD_LABEL(T, default_device_type, "默认设备类型").read_write(),
|
||||
ADMINIVE_FIELD_LABEL(T, radio_service, "无线电服务").read_write(),
|
||||
ADMINIVE_FIELD_LABEL(T, alerts, "告警").read_write(),
|
||||
ADMINIVE_FIELD_LABEL(T, serial_table, "串口表格").read_write(),
|
||||
ADMINIVE_FIELD_LABEL(T, network_table, "网络表格").read_write()
|
||||
).label("Adminive 配置");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -12,7 +12,7 @@ Radio_State make_radio_state(Radio_Mode mode, int port, int buffer_count, int lo
|
||||
return result;
|
||||
}
|
||||
}
|
||||
Server_App::Server_App(int port, std::filesystem::path frontend_dist, std::filesystem::path config_file) : port_(port), frontend_dist_(std::move(frontend_dist)), config_store_(std::move(config_file)), state_resource_("/admin/radio_states", make_radio_states()), service_config_resource_(config_store_.radio_service(), "/admin/config/radio", config_store_.radio_service_transaction(), Resource_Lock_Scope::root), alert_config_resource_(config_store_.alerts(), "/admin/config/alerts", config_store_.alerts_transaction(), Resource_Lock_Scope::root), device_tables_(config_store_) {
|
||||
Server_App::Server_App(int port, std::filesystem::path frontend_dist, std::filesystem::path config_file) : port_(port), frontend_dist_(std::move(frontend_dist)), config_store_(std::move(config_file)), state_resource_("/admin/radio_states", make_radio_states()), service_config_resource_(config_store_.radio_service(), "/admin/config/radio", config_store_.radio_service_transaction()), alert_config_resource_(config_store_.alerts(), "/admin/config/alerts", config_store_.alerts_transaction()), device_tables_(config_store_) {
|
||||
state_resource_.register_overview_status<Server_Status>("/admin/status", 2000);
|
||||
state_resource_.register_status<&Radio_State::status>(2000);
|
||||
bind_routes();
|
||||
|
||||
@@ -289,15 +289,15 @@ int main() {
|
||||
const auto inherited = adminive::to_descriptor_json<Json, Inherited_Config>();
|
||||
assert(inherited.at("fields").at(0).at("name") == "alpha");
|
||||
assert(inherited.at("fields").at(1).at("name") == "beta");
|
||||
adminive::Synchronized_Value<Inherited_Config> synchronized_inherited;
|
||||
synchronized_inherited.field<0>().write([](int& value) {
|
||||
adminive::Managed_Value<Inherited_Config> managed_inherited;
|
||||
managed_inherited.field<0>().write([](int& value) {
|
||||
value = 12;
|
||||
});
|
||||
synchronized_inherited.field<1>().write([](std::string& value) {
|
||||
managed_inherited.field<1>().write([](std::string& value) {
|
||||
value = "changed";
|
||||
});
|
||||
assert(synchronized_inherited.field<0>().snapshot() == 12);
|
||||
assert(synchronized_inherited.field<1>().snapshot() == "changed");
|
||||
assert(managed_inherited.field<0>().snapshot() == 12);
|
||||
assert(managed_inherited.field<1>().snapshot() == "changed");
|
||||
Control_Config controls;
|
||||
const auto control_form = adminive::to_amis_form_schema<Json>(controls);
|
||||
assert(control_form.at("body").at(0).at("type") == "input-color");
|
||||
|
||||
@@ -0,0 +1,190 @@
|
||||
#include "adminive/adapters/nlohmann_json.hpp"
|
||||
#include "adminive/http.hpp"
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
namespace managed_test {
|
||||
using Json = nlohmann::json;
|
||||
struct Section_Config {
|
||||
int editable_value{1};
|
||||
int immutable_value{2};
|
||||
int backend_value{3};
|
||||
int unsynchronized_value{4};
|
||||
};
|
||||
struct Root_Config {
|
||||
Section_Config section;
|
||||
int immutable_value{5};
|
||||
};
|
||||
struct Concurrent_Config {
|
||||
int first{};
|
||||
int second{};
|
||||
int immutable_value{7};
|
||||
};
|
||||
struct Probe_Lock {
|
||||
static inline std::atomic<int> exclusive_locks{};
|
||||
static inline std::atomic<int> shared_locks{};
|
||||
void lock() noexcept {
|
||||
exclusive_locks.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
void unlock() noexcept {}
|
||||
void lock_shared() noexcept {
|
||||
shared_locks.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
void unlock_shared() noexcept {}
|
||||
static void reset() noexcept {
|
||||
exclusive_locks.store(0, std::memory_order_relaxed);
|
||||
shared_locks.store(0, std::memory_order_relaxed);
|
||||
}
|
||||
};
|
||||
template <class Field>
|
||||
concept Writable_Field = requires(Field field) {
|
||||
field.write([](auto& value) {
|
||||
++value;
|
||||
});
|
||||
};
|
||||
template <class Field>
|
||||
concept Reference_Escaping_Read = requires(Field field) {
|
||||
field.read([](const auto& value) -> const auto& {
|
||||
return value;
|
||||
});
|
||||
};
|
||||
}
|
||||
namespace adminive {
|
||||
template <>
|
||||
struct Type_Descriptor<managed_test::Section_Config> {
|
||||
static auto get() {
|
||||
using T = managed_test::Section_Config;
|
||||
return object<T>("section", "Section", ADMINIVE_FIELD(T, editable_value).editable(), ADMINIVE_FIELD(T, immutable_value), ADMINIVE_FIELD(T, backend_value).read_write(), ADMINIVE_FIELD(T, unsynchronized_value).editable().unsynchronized());
|
||||
}
|
||||
};
|
||||
template <>
|
||||
struct Type_Descriptor<managed_test::Root_Config> {
|
||||
static auto get() {
|
||||
using T = managed_test::Root_Config;
|
||||
return object<T>("root", "Root", ADMINIVE_FIELD(T, section), ADMINIVE_FIELD(T, immutable_value));
|
||||
}
|
||||
};
|
||||
template <>
|
||||
struct Type_Descriptor<managed_test::Concurrent_Config> {
|
||||
static auto get() {
|
||||
using T = managed_test::Concurrent_Config;
|
||||
return object<T>("concurrent", "Concurrent", ADMINIVE_FIELD(T, first).editable(), ADMINIVE_FIELD(T, second).editable(), ADMINIVE_FIELD(T, immutable_value));
|
||||
}
|
||||
};
|
||||
}
|
||||
int main() {
|
||||
using namespace managed_test;
|
||||
using Guarded = adminive::Managed_Value<Section_Config, adminive::Mutex_Policy<Probe_Lock>>;
|
||||
Guarded guarded;
|
||||
auto immutable = guarded.member<&Section_Config::immutable_value>();
|
||||
auto editable = guarded.member<&Section_Config::editable_value>();
|
||||
auto backend = guarded.member<&Section_Config::backend_value>();
|
||||
auto unsynchronized = guarded.member<&Section_Config::unsynchronized_value>();
|
||||
static_assert(!Writable_Field<decltype(immutable)>);
|
||||
static_assert(Writable_Field<decltype(editable)>);
|
||||
static_assert(Writable_Field<decltype(backend)>);
|
||||
static_assert(Writable_Field<decltype(unsynchronized)>);
|
||||
static_assert(!Reference_Escaping_Read<decltype(immutable)>);
|
||||
const auto resolved = guarded.resolved_synchronization();
|
||||
assert(resolved.lock_count == 2);
|
||||
assert(resolved.slot(0) != structive::Resolved_Synchronization_View::unsynchronized_slot);
|
||||
assert(resolved.slot(1) == structive::Resolved_Synchronization_View::unsynchronized_slot);
|
||||
assert(resolved.slot(2) != structive::Resolved_Synchronization_View::unsynchronized_slot);
|
||||
assert(resolved.slot(0) != resolved.slot(2));
|
||||
assert(resolved.slot(3) == structive::Resolved_Synchronization_View::unsynchronized_slot);
|
||||
Probe_Lock::reset();
|
||||
assert(immutable.snapshot() == 2);
|
||||
assert(Probe_Lock::exclusive_locks.load(std::memory_order_relaxed) == 0);
|
||||
assert(Probe_Lock::shared_locks.load(std::memory_order_relaxed) == 0);
|
||||
Probe_Lock::reset();
|
||||
editable.write([](int& value) {
|
||||
value = 11;
|
||||
});
|
||||
assert(Probe_Lock::exclusive_locks.load(std::memory_order_relaxed) == 1);
|
||||
assert(Probe_Lock::shared_locks.load(std::memory_order_relaxed) == 0);
|
||||
Probe_Lock::reset();
|
||||
backend.write([](int& value) {
|
||||
value = 12;
|
||||
});
|
||||
assert(Probe_Lock::exclusive_locks.load(std::memory_order_relaxed) == 1);
|
||||
Probe_Lock::reset();
|
||||
unsynchronized.write([](int& value) {
|
||||
value = 13;
|
||||
});
|
||||
assert(Probe_Lock::exclusive_locks.load(std::memory_order_relaxed) == 0);
|
||||
assert(Probe_Lock::shared_locks.load(std::memory_order_relaxed) == 0);
|
||||
const auto descriptor = adminive::to_descriptor_json<Json, Section_Config>();
|
||||
assert(descriptor.at("protocol_version") == 4);
|
||||
assert(!descriptor.at("fields").at(0).contains("lock_mode"));
|
||||
assert(descriptor.at("fields").at(0).at("writable") == true);
|
||||
assert(descriptor.at("fields").at(0).at("synchronized") == true);
|
||||
assert(descriptor.at("fields").at(1).at("writable") == false);
|
||||
assert(descriptor.at("fields").at(1).at("synchronized") == false);
|
||||
assert(descriptor.at("fields").at(2).at("writable") == true);
|
||||
assert(descriptor.at("fields").at(2).at("editable") == false);
|
||||
assert(descriptor.at("fields").at(2).at("synchronized") == true);
|
||||
assert(descriptor.at("fields").at(3).at("writable") == true);
|
||||
assert(descriptor.at("fields").at(3).at("synchronized") == false);
|
||||
adminive::Managed_Value<Root_Config, adminive::Mutex_Policy<Probe_Lock>> nested;
|
||||
auto section = nested.member<&Root_Config::section>();
|
||||
static_assert(!Writable_Field<decltype(section)>);
|
||||
Probe_Lock::reset();
|
||||
assert(section.member<&Section_Config::immutable_value>().snapshot() == 2);
|
||||
assert(Probe_Lock::shared_locks.load(std::memory_order_relaxed) == 0);
|
||||
Probe_Lock::reset();
|
||||
section.member<&Section_Config::editable_value>().write([](int& value) {
|
||||
value = 31;
|
||||
});
|
||||
assert(Probe_Lock::exclusive_locks.load(std::memory_order_relaxed) == 1);
|
||||
Probe_Lock::reset();
|
||||
section.member<&Section_Config::unsynchronized_value>().write([](int& value) {
|
||||
value = 32;
|
||||
});
|
||||
assert(Probe_Lock::exclusive_locks.load(std::memory_order_relaxed) == 0);
|
||||
adminive::Managed_Value<Section_Config, structive::No_Lock_Policy> no_lock;
|
||||
adminive::Resource_Service<Section_Config, Json> service(no_lock, "/section");
|
||||
const auto rejected = service.update_response(R"({"backend_value":99,"immutable_value":88})");
|
||||
assert(rejected.status == 422);
|
||||
const auto update = service.update_response(R"({"editable_value":21,"unsynchronized_value":22})");
|
||||
assert(update.status == 200);
|
||||
const auto no_lock_snapshot = no_lock.snapshot();
|
||||
assert(no_lock_snapshot.editable_value == 21);
|
||||
assert(no_lock_snapshot.backend_value == 3);
|
||||
assert(no_lock_snapshot.immutable_value == 2);
|
||||
assert(no_lock_snapshot.unsynchronized_value == 22);
|
||||
adminive::Managed_Value<Concurrent_Config> concurrent;
|
||||
std::atomic<bool> start{};
|
||||
auto first_writer = std::thread([&] {
|
||||
while(!start.load(std::memory_order_acquire)) {}
|
||||
for(int index = 0; index < 5000; ++index) {
|
||||
concurrent.member<&Concurrent_Config::first>().write([index](int& value) {
|
||||
value = index;
|
||||
});
|
||||
}
|
||||
});
|
||||
auto second_writer = std::thread([&] {
|
||||
while(!start.load(std::memory_order_acquire)) {}
|
||||
for(int index = 0; index < 5000; ++index) {
|
||||
concurrent.member<&Concurrent_Config::second>().write([index](int& value) {
|
||||
value = index;
|
||||
});
|
||||
}
|
||||
});
|
||||
auto serializer = std::thread([&] {
|
||||
while(!start.load(std::memory_order_acquire)) {}
|
||||
for(int index = 0; index < 1000; ++index) {
|
||||
const Json value = adminive::to_json<Json>(concurrent);
|
||||
assert(value.at("immutable_value") == 7);
|
||||
}
|
||||
});
|
||||
start.store(true, std::memory_order_release);
|
||||
first_writer.join();
|
||||
second_writer.join();
|
||||
serializer.join();
|
||||
const auto concurrent_snapshot = concurrent.snapshot();
|
||||
assert(concurrent_snapshot.first == 4999);
|
||||
assert(concurrent_snapshot.second == 4999);
|
||||
assert(concurrent_snapshot.immutable_value == 7);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user