diff --git a/README.md b/README.md index 7bdb2cc..4a59cd1 100644 --- a/README.md +++ b/README.md @@ -263,6 +263,28 @@ struct adminive::Object_Adapter { }; ``` +如果运行时对象已经拥有权威状态和并发策略,不要为了 Adminive 再造一份完整配置快照。`field(name, label, accessor)` 直接接受 Structive `Property_Accessor`;默认数据成员仍使用 `structive::Member_Accessor`,方法型或子控件属性可使用 Structive 的 getter/setter accessor: + +```cpp +template <> +struct adminive::Type_Descriptor { + static auto get() { + auto accessor = structive::trusted_callable_accessor( + [](const Runtime_Control& value) { return value.setting(); }, + [](Runtime_Control& value, int setting) { + value.apply_setting(setting); + }); + return adminive::object( + "runtime_control", + adminive::field("setting", "Setting", std::move(accessor)) + .editable() + .unsynchronized()); + } +}; +``` + +这种直接描述不要求对象可复制,也不经过 `Object_Adapter`。`.unsynchronized()` 只表示 Adminive/Structive 不增加同步域;访问器调用的业务方法及对象自身同步仍是唯一权威实现。 + `Resource_Service` 始终通过 `Object_Adapter::commit()`提交运行时模型。外部持久化和系统副作用通过 `Resource_Transaction` 分成准备、提交和回滚三个阶段: ```cpp diff --git a/backend/library/include/adminive/descriptor.hpp b/backend/library/include/adminive/descriptor.hpp index 0a0a62f..5fde2fe 100644 --- a/backend/library/include/adminive/descriptor.hpp +++ b/backend/library/include/adminive/descriptor.hpp @@ -2,6 +2,7 @@ #include "adminive/adapter.hpp" #include "adminive/concepts.hpp" #include "adminive/presentation.hpp" +#include #include #include #include @@ -23,43 +24,33 @@ template concept Reflected_Type = requires { { Reflection_Adapter>::field_count } -> std::convertible_to; }; -template -struct Member_Pointer_Traits; -template -struct Member_Pointer_Traits { - using owner_type = Owner; - using member_type = Member_Type; - static constexpr auto value = Member; -}; -template -struct Member_Field_Accessor { - using owner_type = typename Member_Pointer_Traits::owner_type; - using member_type = typename Member_Pointer_Traits::member_type; - static constexpr auto member = Member; - template - static decltype(auto) get(Object& object) noexcept { - return object.*Member; - } - template - static decltype(auto) get(const Object& object) noexcept { - return object.*Member; - } -}; template struct Reflected_Field_Accessor { - using owner_type = T; + using object_type = T; using reference_type = decltype(Reflection_Adapter::template get(std::declval())); using const_reference_type = decltype(Reflection_Adapter::template get(std::declval())); - using member_type = std::remove_cvref_t; + using value_type = std::remove_cvref_t; + struct storage_identity {}; + using dependency_spec = structive::No_Property_Dependencies; + static constexpr bool readable = true; + static constexpr bool writable = + !std::is_const_v>; + static constexpr bool synchronized_view_read = false; + static constexpr bool trusted_object_access = true; static constexpr std::size_t index = Index; static_assert(std::is_lvalue_reference_v); static_assert(std::is_lvalue_reference_v); - static decltype(auto) get(T& object) noexcept(noexcept(Reflection_Adapter::template get(object))) { + decltype(auto) read(T& object) const noexcept(noexcept(Reflection_Adapter::template get(object))) { return Reflection_Adapter::template get(object); } - static decltype(auto) get(const T& object) noexcept(noexcept(Reflection_Adapter::template get(object))) { + decltype(auto) read(const T& object) const noexcept(noexcept(Reflection_Adapter::template get(object))) { return Reflection_Adapter::template get(object); } + template + void write(T& object, Value&& value) const + requires std::assignable_from { + Reflection_Adapter::template get(object) = std::forward(value); + } }; inline bool valid_data_name(std::string_view name) noexcept { if(name.empty()) { @@ -102,35 +93,39 @@ struct Field_Metadata { bool include_default{true}; bool required{}; }; -template +template class Field_Descriptor { public: using field_descriptor_tag = void; using accessor_type = Accessor; - using owner_type = typename Accessor::owner_type; - using member_type = typename Accessor::member_type; + using owner_type = typename Accessor::object_type; + using member_type = typename Accessor::value_type; static constexpr bool managed_writable = Managed_Writable; static constexpr bool synchronized = Synchronized; - explicit Field_Descriptor(std::string name) { + Field_Descriptor(std::string name, Accessor accessor = {}) + : accessor_(std::move(accessor)) { metadata_.name = std::move(name); metadata_.presentation.label = make_label(metadata_.name); } - Field_Descriptor(std::string name, std::string label) { + Field_Descriptor(std::string name, std::string label, Accessor accessor = {}) + : accessor_(std::move(accessor)) { metadata_.name = std::move(name); metadata_.presentation.label = std::move(label); } - explicit Field_Descriptor(Field_Metadata metadata) : metadata_(std::move(metadata)) {} - auto editable() const { + Field_Descriptor(Field_Metadata metadata, Accessor accessor) + : accessor_(std::move(accessor)), metadata_(std::move(metadata)) {} + auto editable() const requires Accessor::writable { auto result = rebind(); result.metadata_.editable = true; return result; } - auto creatable() const { + auto creatable() const requires Accessor::writable { auto result = rebind(); result.metadata_.creatable = true; return result; } - auto read_write() const { + auto read_write() const requires Accessor::writable { return rebind(); } auto unsynchronized() const { @@ -219,13 +214,24 @@ public: } template requires std::convertible_to*, owner_type*> - decltype(auto) get(Object& object) const noexcept(noexcept(Accessor::get(object))) { - return Accessor::get(object); + decltype(auto) get(Object& object) const + noexcept(noexcept(accessor_.read(object))) { + return accessor_.read(object); } template requires std::convertible_to*, const owner_type*> - decltype(auto) get(const Object& object) const noexcept(noexcept(Accessor::get(object))) { - return Accessor::get(object); + decltype(auto) get(const Object& object) const + noexcept(noexcept(accessor_.read(object))) { + return accessor_.read(object); + } + template + requires std::convertible_to*, owner_type*> && + requires(const Accessor& accessor, Object& object, Value&& value) { + accessor.write(object, std::forward(value)); + } + void set(Object& object, Value&& value) const + noexcept(noexcept(accessor_.write(object, std::forward(value)))) { + accessor_.write(object, std::forward(value)); } const std::string& name() const noexcept { return metadata_.name; @@ -269,10 +275,11 @@ public: private: template auto rebind() const { - return Field_Descriptor(metadata_); + return Field_Descriptor(metadata_, accessor_); } - template + template friend class Field_Descriptor; + [[no_unique_address]] Accessor accessor_; Field_Metadata metadata_; }; template @@ -286,11 +293,19 @@ concept Field_Descriptor_Type = requires { }; template auto field(std::string name) { - return Field_Descriptor>(std::move(name)); + return Field_Descriptor>(std::move(name)); } template auto field(std::string name, std::string label) { - return Field_Descriptor>(std::move(name), std::move(label)); + return Field_Descriptor>(std::move(name), std::move(label)); +} +template +auto field(std::string name, Accessor accessor) { + return Field_Descriptor(std::move(name), std::move(accessor)); +} +template +auto field(std::string name, std::string label, Accessor accessor) { + return Field_Descriptor(std::move(name), std::move(label), std::move(accessor)); } template auto reflected_field() { @@ -384,7 +399,7 @@ struct Identity_Field_Customizer { }; template auto reflected_object_with_impl(std::string name, std::string label, Customizer customizer, std::index_sequence) { - return object(std::move(name), std::move(label), customizer.template operator()(reflected_field())...); + return adminive::object(std::move(name), std::move(label), customizer.template operator()(reflected_field())...); } template auto reflected_object_with(std::string name, Customizer customizer) { diff --git a/backend/library/include/adminive/json.hpp b/backend/library/include/adminive/json.hpp index 4b17848..fc20d33 100644 --- a/backend/library/include/adminive/json.hpp +++ b/backend/library/include/adminive/json.hpp @@ -100,6 +100,9 @@ template concept Writable_Adapted_Object = Snapshot_Adapted_Object && Object_Adapter_With_Commit; template concept Creatable_Writable_Object = Writable_Adapted_Object && Object_Adapter_With_Create; +template +concept Direct_Described_Object = Described_Type> && + (!Writable_Adapted_Object); template requires Described_Type> Json to_descriptor_json(); @@ -114,6 +117,8 @@ requires (Described_Type> || Object_Adapter_With_Snapshot Json to_frontend_json(const T& value); template Update_Result assign_json(T& target, const Json& value); +template +Update_Result assign_json(T& target, const Json& value); template Update_Result apply_object_json(T& target, const Json& value, Write_Mode mode, bool complete); template @@ -583,7 +588,20 @@ Update_Result apply_model_json(Model& candidate, const Json& value, Write_Mode m return; } try { - assign_json_value(item.get(candidate), Json_Adapter::at(value, item.name()), Write_Context{mode, complete}); + using Field = std::remove_cvref_t; + using Field_Value = typename Field::member_type; + if constexpr(std::is_lvalue_reference_v && + !std::is_const_v>) { + assign_json_value(item.get(candidate), + Json_Adapter::at(value, item.name()), + Write_Context{mode, complete}); + } else { + Field_Value field_candidate(item.get(std::as_const(candidate))); + assign_json_value(field_candidate, + Json_Adapter::at(value, item.name()), + Write_Context{mode, complete}); + item.set(candidate, std::move(field_candidate)); + } } catch(const Json_Assignment_Error& error) { merge_update_errors(result, error.result(), item.name()); } catch(const Field_Validation_Error& error) { @@ -711,6 +729,22 @@ template Update_Result apply_patch(T& target, const Json& patch) { return apply_frontend_patch(target, patch); } +template +Update_Result assign_json(T& target, const Json& value) { + return apply_model_json(target, value, Write_Mode::internal, true); +} +template +Update_Result apply_json_patch(T& target, const Json& patch) { + return apply_model_json(target, patch, Write_Mode::internal, false); +} +template +Update_Result apply_frontend_patch(T& target, const Json& patch) { + return apply_model_json(target, patch, Write_Mode::update, false); +} +template +Update_Result apply_patch(T& target, const Json& patch) { + return apply_frontend_patch(target, patch); +} template requires Writable_Adapted_Object::value_type> Update_Result assign_json(T& target, const Json& value) { diff --git a/backend/library/include/adminive/managed.hpp b/backend/library/include/adminive/managed.hpp index 986e476..25d5230 100644 --- a/backend/library/include/adminive/managed.hpp +++ b/backend/library/include/adminive/managed.hpp @@ -97,6 +97,11 @@ consteval bool field_requires_synchronization() { } return member_type_requires_synchronization(); } +template +const auto& managed_descriptor_field() { + static const auto descriptor = describe(); + return std::get(descriptor.fields()); +} template struct Managed_Field_Accessor { using object_type = Managed; @@ -107,15 +112,26 @@ struct Managed_Field_Accessor { static constexpr bool writable = managed_field_writable_v; 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()); + decltype(auto) read(const object_type& object) const + noexcept(noexcept(managed_descriptor_field().get( + object.unsafe_value()))) { + return managed_descriptor_field().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()); + decltype(auto) read(object_type& object) const + noexcept(noexcept(managed_descriptor_field().get( + object.unsafe_value()))) { + return managed_descriptor_field().get( + object.unsafe_value()); } template - void write(object_type& object, Value&& value) const requires writable && std::assignable_from { - Field::accessor_type::get(object.unsafe_value()) = std::forward(value); + void write(object_type& object, Value&& value) const + requires writable && requires { + managed_descriptor_field().set( + object.unsafe_value(), std::forward(value)); + } { + managed_descriptor_field().set( + object.unsafe_value(), std::forward(value)); } }; template @@ -227,7 +243,7 @@ decltype(auto) path_value(const Object& object) { template decltype(auto) managed_path_value(T& object) { using Field = Descriptor_Field; - auto&& root = Field::accessor_type::get(object); + auto&& root = managed_descriptor_field().get(object); if constexpr(sizeof...(Members) == 0) { return std::forward(root); } else { @@ -237,7 +253,7 @@ decltype(auto) managed_path_value(T& object) { template decltype(auto) managed_path_value(const T& object) { using Field = Descriptor_Field; - auto&& root = Field::accessor_type::get(object); + auto&& root = managed_descriptor_field().get(object); if constexpr(sizeof...(Members) == 0) { return std::forward(root); } else { diff --git a/backend/library/include/adminive/view.hpp b/backend/library/include/adminive/view.hpp index 2c43506..2133c5c 100644 --- a/backend/library/include/adminive/view.hpp +++ b/backend/library/include/adminive/view.hpp @@ -68,7 +68,7 @@ inline View_Node view_field(std::string name) { } template std::string described_member_name() { - using Owner = typename Member_Pointer_Traits::owner_type; + using Owner = typename structive::Member_Pointer_Traits::object_type; const auto descriptor = describe(); std::string result; std::apply([&](const auto&... item) { diff --git a/backend/library/tests/core_adapter_test.cpp b/backend/library/tests/core_adapter_test.cpp index afc3fa1..8c91e36 100644 --- a/backend/library/tests/core_adapter_test.cpp +++ b/backend/library/tests/core_adapter_test.cpp @@ -34,6 +34,20 @@ struct Mode_Base { struct Config : Runtime_Base, Mode_Base { std::string name{"default"}; }; + +class Runtime_Control final { +public: + Runtime_Control() = default; + Runtime_Control(const Runtime_Control&) = delete; + Runtime_Control& operator=(const Runtime_Control&) = delete; + + [[nodiscard]] int setting() const noexcept { return setting_; } + void apply_setting(int value) noexcept { setting_ = value; } + +private: + int setting_{3}; +}; + } namespace adminive { template <> @@ -263,6 +277,21 @@ struct Type_Descriptor { } }; template <> +struct Type_Descriptor { + static auto get() { + auto accessor = structive::trusted_callable_accessor( + [](const adapter_test::Runtime_Control& value) { return value.setting(); }, + [](adapter_test::Runtime_Control& target, int value) { + target.apply_setting(value); + }); + return adminive::object( + "runtime_control", "Runtime Control", + adminive::field("setting", "Setting", std::move(accessor)) + .editable() + .unsynchronized()); + } +}; +template <> struct Type_View_Descriptor { static Table_View table() { using T = adapter_test::Config; @@ -295,6 +324,16 @@ int main() { const Json encoded = adminive::to_json(config); ADMINIVE_CHECK((adminive::json_get(adminive::Json_Adapter::at(encoded, "worker_count")) == 12)); ADMINIVE_CHECK((adminive::json_get(adminive::Json_Adapter::at(encoded, "mode")) == "secondary")); + adapter_test::Runtime_Control runtime_control; + const Json runtime_encoded = adminive::to_json(runtime_control); + ADMINIVE_CHECK((adminive::json_get( + adminive::Json_Adapter::at(runtime_encoded, "setting")) == 3)); + Json runtime_patch = adminive::json_object(); + adminive::json_set(runtime_patch, "setting", 17); + const auto runtime_result = + adminive::apply_frontend_patch(runtime_control, runtime_patch); + ADMINIVE_CHECK(runtime_result.success); + ADMINIVE_CHECK(runtime_control.setting() == 17); adminive::Collection_Service collection("/configs", {Config{}}); const auto list = collection.list_response(); ADMINIVE_CHECK(list.status == 200);