Files
Adminive/backend/service/src/gallery.cpp
T
2026-08-08 21:45:29 +08:00

438 lines
40 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "gallery.hpp"
#include <map>
#include <set>
#include <stdexcept>
#include <string>
#include <string_view>
#include <type_traits>
#include <utility>
#include <vector>
namespace adminive::example {
namespace {
Json make_gallery_note(std::string title, std::string text) {
return Json{{"type", "tpl"}, {"tpl", "<strong>" + std::move(title) + "</strong><div class=\"text-muted\" style=\"margin-top:6px\">" + std::move(text) + "</div>"}};
}
Composition_View make_backend_layout_view() {
return composition_view("component_gallery", compose::tabs(
compose::tab("字段表单", compose::slot("form")),
compose::tab("后端组合定位", compose::vertical(
compose::heading("后端 Composition 决定组件位置").accent("#2563eb"),
compose::group("横向 horizontal", compose::horizontal(compose::card("横向 A", compose::slot("horizontal_a")), compose::card("横向 B", compose::slot("horizontal_b")), compose::card("横向 C", compose::slot("horizontal_c")))),
compose::group("竖向 vertical", compose::vertical(compose::card("第一项", compose::slot("vertical_a")), compose::card("第二项", compose::slot("vertical_b")), compose::card("第三项", compose::slot("vertical_c")))),
compose::group("列表 list", compose::list(compose::slot("list_a"), compose::slot("list_b"), compose::slot("list_c"))),
compose::group("卡片 + 网格", compose::grid(3, compose::card("Card A", compose::slot("card_a")), compose::card("Card B", compose::slot("card_b")), compose::card("Card C", compose::slot("card_c")))),
compose::group("Flow 自动换行", compose::flow(compose::card("Flow A", compose::slot("flow_a")), compose::card("Flow B", compose::slot("flow_b")), compose::card("Flow C", compose::slot("flow_c")), compose::card("Flow D", compose::slot("flow_d")))).collapsible_when(),
compose::group("Tabs 嵌套", compose::tabs(compose::tab("说明", compose::slot("tabs_a")), compose::tab("嵌套卡片", compose::card("Tab Card", compose::slot("tabs_b"))))))),
compose::tab("列表 / 卡片 / 表格", compose::vertical(compose::heading("同一 Collection View 的三种展示"), compose::tabs(compose::tab("列表", compose::slot("collection_list")), compose::tab("卡片", compose::slot("collection_cards")), compose::tab("表格", compose::slot("collection_table"))))),
compose::tab("高级 Adapter", compose::grid(4, compose::card("Value / Control / Sensitive", compose::slot("advanced_adapter")), compose::card("Reflection", compose::slot("advanced_reflection")), compose::card("Polymorphic", compose::slot("advanced_polymorphic")), compose::card("Object Adapter", compose::slot("advanced_object")))),
compose::tab("前端决定定位", compose::frontend(compose::slot("frontend_a"), compose::slot("frontend_b"), compose::slot("frontend_c")).described("frontend 节点只声明组件成员与顺序,具体横排、竖排、列表、卡片或表格由前端决定。"))
)).titled("Adminive 组件画廊");
}
Composition_View make_frontend_layout_view() {
return composition_view("frontend_owned_layout", compose::frontend(compose::slot("alpha"), compose::slot("beta"), compose::slot("gamma"))).titled("前端定位演示");
}
Collection_Item_View make_gallery_item_view() {
using T = Gallery_Row;
return collection_item<&T::name>().subtitle<&T::category>().accent<&T::color>().body<&T::score, &T::enabled, &T::color>();
}
Collection_View make_gallery_collection_view(Collection_Mode mode) {
auto result = describe_collection_view<Gallery_Row>();
if(mode == Collection_Mode::list) {
return result.as_list(make_gallery_item_view());
}
if(mode == Collection_Mode::cards) {
return result.as_cards(make_gallery_item_view(), 3);
}
return result.as_table();
}
Json string_array(std::initializer_list<const char*> values) {
Json result = Json::array();
for(const char* value : values) {
result.push_back(value);
}
return result;
}
struct Capability_Case {
std::string id;
std::string group;
std::string title;
std::string summary;
std::string cpp;
Json view;
Json amis;
Json preview;
Json tags;
Json endpoints;
};
Capability_Case make_doc_case(std::string id, std::string group, std::string title, std::string summary, std::string cpp, Json view, Json amis, Json preview, Json tags, Json endpoints = Json::array()) {
return Capability_Case{std::move(id), std::move(group), std::move(title), std::move(summary), std::move(cpp), std::move(view), std::move(amis), std::move(preview), std::move(tags), std::move(endpoints)};
}
Json capability_case_json(const Capability_Case& item) {
return Json{{"id", item.id}, {"group", item.group}, {"title", item.title}, {"summary", item.summary}, {"cpp", item.cpp}, {"view", item.view}, {"amis", item.amis}, {"preview", item.preview}, {"tags", item.tags}, {"endpoints", item.endpoints}};
}
Json capability_cases_json(const std::vector<Capability_Case>& cases) {
Json result = Json::array();
for(const auto& item : cases) {
result.push_back(capability_case_json(item));
}
return result;
}
Request_Context gallery_request_context(const httplib::Request& request) {
Request_Context context;
context.user = request.get_header_value("X-Adminive-User");
context.request_id = request.get_header_value("X-Request-Id");
context.remote_address = request.remote_addr;
context.attributes.emplace("transport", "httplib");
return context;
}
template <Described_Type T>
Json make_field_policy_snapshot() {
const auto descriptor = describe<T>();
Json fields = Json::array();
std::apply([&](const auto&... item) {
([&] {
using Field = std::remove_cvref_t<decltype(item)>;
fields.push_back(Json{{"name", item.name()}, {"readable", item.is_readable()}, {"writable", Field::managed_writable}, {"editable", item.is_editable()}, {"creatable", item.is_creatable()}, {"sensitive", item.is_sensitive()}, {"required", item.is_required()}, {"include_default", item.includes_default()}, {"synchronized", Field::managed_writable && Field::synchronized}});
}(), ...);
}, descriptor.fields());
return fields;
}
Json make_managed_snapshot(const Managed_Value<Gallery_Config>& value) {
const Json descriptor = to_descriptor_json<Json, Gallery_Config>();
const auto resolved = value.resolved_synchronization();
Json fields = Json::array();
for(std::size_t index = 0; index < descriptor.at("fields").size(); ++index) {
const auto& field = descriptor.at("fields").at(index);
Json item{{"name", field.at("name")}, {"writable", field.at("writable")}, {"editable", field.at("editable")}, {"creatable", field.at("creatable")}, {"synchronized", field.at("synchronized")}};
const auto slot = resolved.slot(index);
if(slot == structive::Resolved_Synchronization_View::unsynchronized_slot) {
item["synchronization_slot"] = "unsynchronized";
} else {
item["synchronization_slot"] = slot;
}
fields.push_back(std::move(item));
}
return Json{{"lock_count", resolved.lock_count}, {"fields", std::move(fields)}, {"note", "managed writable 是后端写能力,editable/creatable 是前端提交能力;两者不是同一概念。"}};
}
template <class Function>
Json rejected_case(std::string id, std::string cpp, Function&& function) {
try {
std::forward<Function>(function)();
return Json{{"id", std::move(id)}, {"cpp", std::move(cpp)}, {"rejected", false}, {"error", "no error"}};
} catch(const std::exception& error) {
return Json{{"id", std::move(id)}, {"cpp", std::move(cpp)}, {"rejected", true}, {"error", error.what()}};
}
}
Json protocol_boundaries() {
Json result = Json::array();
result.push_back(rejected_case("grid-zero", "compose::grid(0, compose::slot(\"a\"))", [] {
to_view_json<Json>(composition_view("invalid_grid", compose::grid(0, compose::slot("a"))));
}));
result.push_back(rejected_case("tab-outside-tabs", "composition_view(\"invalid_tab\", compose::tab(\"x\", compose::slot(\"a\")))", [] {
to_view_json<Json>(composition_view("invalid_tab", compose::tab("x", compose::slot("a"))));
}));
result.push_back(rejected_case("manifest-missing-contract", "to_composition_manifest_json(view, {}, {})", [] {
const auto view = composition_view("missing_contract", compose::slot("a"));
to_composition_manifest_json<Json>(view, {}, {});
}));
result.push_back(rejected_case("manifest-duplicate-contract", "contracts = {a, a}", [] {
const auto view = composition_view("duplicate_contract", compose::slot("a"));
const std::vector<Composition_Slot_Contract> contracts{{"a", "amis_schema", {}, {}, {}, {}}, {"a", "amis_schema", {}, {}, {}, {}}};
const std::map<std::string, Json> schemas{{"a", make_gallery_note("a", "a")}};
to_composition_manifest_json<Json>(view, contracts, schemas);
}));
result.push_back(rejected_case("manifest-duplicate-slot", "compose::frontend(compose::slot(\"a\"), compose::slot(\"a\"))", [] {
const auto view = composition_view("duplicate_slot", compose::frontend(compose::slot("a"), compose::slot("a")));
const std::vector<Composition_Slot_Contract> contracts{{"a", "amis_schema", {}, {}, {}, {}}};
const std::map<std::string, Json> schemas{{"a", make_gallery_note("a", "a")}};
to_composition_manifest_json<Json>(view, contracts, schemas);
}));
Gallery_Config readonly_value;
const auto readonly_update = apply_frontend_patch(readonly_value, Json{{"read_only_text", "forbidden"}});
result.push_back(Json{{"id", "readonly-submit"}, {"request", Json{{"read_only_text", "forbidden"}}}, {"status", 422}, {"response", make_http_error<Json>(422, readonly_update).body}});
Gallery_Config invalid_value;
const auto multi_update = apply_frontend_patch(invalid_value, Json{{"even_number", 3}, {"number_value", 101}, {"nested", Json{{"port", 70000}}}});
result.push_back(Json{{"id", "multi-field-validation"}, {"request", Json{{"even_number", 3}, {"number_value", 101}, {"nested", Json{{"port", 70000}}}}}, {"status", 422}, {"response", make_http_error<Json>(422, multi_update).body}});
Radio_State invalid_radio;
const auto object_update = apply_frontend_patch(invalid_radio, Json{{"low_watermark", 20}, {"high_watermark", 10}});
result.push_back(Json{{"id", "object-validator"}, {"request", Json{{"low_watermark", 20}, {"high_watermark", 10}}}, {"status", object_update.success ? 200 : 422}, {"response", object_update.success ? make_http_success<Json>(to_frontend_json<Json>(invalid_radio)).body : make_http_error<Json>(422, object_update).body}});
Gallery_Polymorphic_Config invalid_polymorphic;
const auto polymorphic_update = apply_frontend_patch(invalid_polymorphic, Json{{"target", Json{{"type", "unknown"}}}});
result.push_back(Json{{"id", "polymorphic-discriminator"}, {"request", Json{{"target", Json{{"type", "unknown"}}}}}, {"status", 422}, {"response", make_http_error<Json>(422, polymorphic_update).body}});
Collection_Service<Gallery_Row, Json> collection("/boundary", {Gallery_Row{"Alpha", Gallery_Category::primary, Range_Value<int, 0, 100>{92}, true, "#2563eb"}});
Collection_Query invalid_filter;
invalid_filter.fields.emplace("score", "92");
const auto filter_response = collection.list_response(std::move(invalid_filter));
result.push_back(Json{{"id", "collection-invalid-filter"}, {"status", filter_response.status}, {"response", filter_response.body}});
Collection_Query invalid_sort;
invalid_sort.order_by = "color";
invalid_sort.order_dir = "asc";
const auto sort_response = collection.list_response(std::move(invalid_sort));
result.push_back(Json{{"id", "collection-invalid-sort"}, {"status", sort_response.status}, {"response", sort_response.body}});
return result;
}
Http_Response<Json> transaction_scenario(std::string_view scenario, const httplib::Request& request) {
Gallery_Runtime_Config runtime;
std::vector<std::string> events;
Resource_Transaction<Gallery_Runtime_Model> transaction;
transaction.prepare = [&](const Gallery_Runtime_Model&, const Request_Context&) {
events.push_back("prepare");
if(scenario == "prepare-reject") {
throw std::runtime_error("prepare rejected by demo transaction");
}
};
transaction.commit = [&](const Gallery_Runtime_Model&, const Request_Context&) {
events.push_back("commit");
if(scenario == "commit-fail" || scenario == "rollback-fail") {
throw std::runtime_error("external commit failed");
}
};
transaction.rollback = [&](const Gallery_Runtime_Model&, const Request_Context&) {
events.push_back("rollback");
if(scenario == "rollback-fail") {
throw std::runtime_error("external rollback failed");
}
};
Resource_Service<Gallery_Runtime_Config, Json> service(runtime, "/transaction", std::move(transaction));
const std::string body = request.body.empty() ? R"({"name":"Transaction Scenario"})" : request.body;
const auto context = gallery_request_context(request);
auto response = service.update_response(body, context);
response.body["demo"] = Json{{"scenario", scenario}, {"events", events}, {"runtime", to_frontend_json<Json>(runtime)}, {"context", Json{{"user", context.user}, {"request_id", context.request_id}, {"remote_address", context.remote_address}, {"attributes", context.attributes}}}};
return response;
}
Json derive_capabilities(const std::vector<Capability_Case>& cases) {
Json capabilities = Json::object();
std::map<std::string, std::set<std::string>> values;
for(const auto& item : cases) {
for(const auto& tag : item.tags) {
values[item.group].insert(tag.get<std::string>());
}
}
for(const auto& [group, tags] : values) {
Json array = Json::array();
for(const auto& tag : tags) {
array.push_back(tag);
}
capabilities[group] = std::move(array);
}
return capabilities;
}
Json derive_endpoints(const std::vector<Capability_Case>& cases) {
std::set<std::string> values;
values.insert("/admin/amis");
values.insert("/admin/status");
values.insert("/admin/gallery/docs");
for(const auto& item : cases) {
for(const auto& endpoint : item.endpoints) {
values.insert(endpoint.get<std::string>());
}
}
Json result = Json::array();
for(const auto& value : values) {
result.push_back(value);
}
return result;
}
}
Component_Gallery::Component_Gallery() : config_resource_(config_, "/admin/gallery/config"), rows_("/admin/gallery/items", make_rows()), adapter_resource_(adapter_config_, "/admin/gallery/advanced/adapter"), reflected_resource_(reflected_config_, "/admin/gallery/advanced/reflection"), polymorphic_resource_(polymorphic_config_, "/admin/gallery/advanced/polymorphic"), runtime_resource_(runtime_config_, "/admin/gallery/advanced/object", make_resource_transaction<Gallery_Runtime_Model>([this](const Gallery_Runtime_Model&, const Request_Context&) { runtime_config_.transaction_committed(); })) {
rows_.register_overview_status<Server_Status>("/admin/status", 2000);
rows_.register_status<&Gallery_Row::status>(2000);
}
Json Component_Gallery::amis_schema() const {
const Json form = config_resource_.amis_schema();
const auto composition = make_backend_layout_view();
std::map<std::string, Json> slots;
slots.emplace("form", form);
slots.emplace("horizontal_a", make_gallery_note("horizontal", "后端明确声明横向组合,Adapter 翻译为 AMIS grid。"));
slots.emplace("horizontal_b", make_gallery_note("slot", "Slot 可以放 Form、Collection、Status 或任意已生成组件。"));
slots.emplace("horizontal_c", make_gallery_note("responsive", "后端声明组合语义,Adapter 负责断点和具体 renderer。"));
slots.emplace("vertical_a", make_gallery_note("vertical", "竖向只表达先后顺序。"));
slots.emplace("vertical_b", make_gallery_note("group", "Group 支持标题、折叠和继续嵌套。"));
slots.emplace("vertical_c", make_gallery_note("heading", "Heading 可以带强调色。"));
slots.emplace("list_a", make_gallery_note("list / 1", "列表节点把多个业务组件表达为线性条目。"));
slots.emplace("list_b", make_gallery_note("list / 2", "列表与 vertical 不同,它保留条目语义。"));
slots.emplace("list_c", make_gallery_note("list / 3", "AMIS Adapter 给条目添加明确的视觉分隔。"));
slots.emplace("card_a", make_gallery_note("card", "Card 是组合容器。"));
slots.emplace("card_b", make_gallery_note("grid", "Grid 由后端声明逻辑列数。"));
slots.emplace("card_c", make_gallery_note("nest", "Card、Grid、Group、Tabs 可以互相嵌套。"));
slots.emplace("flow_a", make_gallery_note("flow", "Flow 使用可换行 Flex。"));
slots.emplace("flow_b", make_gallery_note("flow", "适合宽度不固定的组件。"));
slots.emplace("flow_c", make_gallery_note("flow", "每个子项都是合法 renderer。"));
slots.emplace("flow_d", make_gallery_note("flow", "窗口变窄时自动换行。"));
slots.emplace("tabs_a", make_gallery_note("tabs", "Tabs 是一等组合节点。"));
slots.emplace("tabs_b", make_gallery_note("tab", "Tab 只能作为 Tabs 的直接子节点,验证阶段会拒绝非法位置。"));
slots.emplace("collection_list", collection_schema(Collection_Mode::list).at("body"));
slots.emplace("collection_cards", collection_schema(Collection_Mode::cards).at("body"));
slots.emplace("collection_table", collection_schema(Collection_Mode::table).at("body"));
slots.emplace("advanced_adapter", adapter_resource_.amis_schema());
slots.emplace("advanced_reflection", reflected_resource_.amis_schema());
slots.emplace("advanced_polymorphic", polymorphic_resource_.amis_schema());
slots.emplace("advanced_object", runtime_resource_.amis_schema());
slots.emplace("frontend_a", make_gallery_note("frontend", "后端只声明成员和顺序,不声明横向、竖向、列表、卡片或表格。"));
slots.emplace("frontend_b", make_gallery_note("slot registry", "manifest 明确给出每个 slot 的类型、来源接口和 renderer schema。"));
slots.emplace("frontend_c", make_gallery_note("fallback", "AMIS Adapter 对 frontend 节点提供中性的竖向 fallback,不偷做业务布局决策。"));
return to_amis_composition_schema<Json>(composition, slots);
}
Json Component_Gallery::backend_layout_view_json() const {
return to_view_json<Json>(make_backend_layout_view());
}
Json Component_Gallery::frontend_layout_manifest() const {
const auto view = make_frontend_layout_view();
const std::vector<Composition_Slot_Contract> contracts{
{"alpha", "amis_schema", "/admin/gallery/config/descriptor", "/admin/gallery/config/view", "/admin/gallery/config/data", "/admin/gallery/config/amis"},
{"beta", "amis_schema", "/admin/gallery/items/descriptor", "/admin/gallery/items/list/view", "/admin/gallery/items", "/admin/gallery/items/list/amis"},
{"gamma", "amis_schema", "/admin/gallery/items/descriptor", "/admin/gallery/items/cards/view", "/admin/gallery/items", "/admin/gallery/items/cards/amis"}
};
const std::map<std::string, Json> schemas{{"alpha", config_resource_.amis_schema()}, {"beta", collection_schema(Collection_Mode::list)}, {"gamma", collection_schema(Collection_Mode::cards)}};
return to_composition_manifest_json<Json>(view, contracts, schemas);
}
Json Component_Gallery::collection_view_json(Collection_Mode mode) const {
return to_view_json<Json, Gallery_Row>(make_gallery_collection_view(mode));
}
Json Component_Gallery::collection_schema(Collection_Mode mode) const {
const Json item_status = to_status_descriptor_json<Json, Gallery_Row_Status>();
const Json overview_status = to_status_descriptor_json<Json, Server_Status>();
return to_amis_collection_schema<Json, Gallery_Row>("/admin/gallery/items", make_gallery_collection_view(mode), &item_status, 2000, &overview_status, "/admin/status", 2000);
}
Json Component_Gallery::documentation() const {
Gallery_Config config_value;
config_.read([&](const Gallery_Config& value) {
config_value = value;
});
const Form_View display_view = detail_view<Gallery_Config>();
const Form_View create_view = create_form<Gallery_Config>();
const Form_View edit_view = describe_edit_view<Gallery_Config>();
Gallery_Config invalid_config = config_value;
const Update_Result invalid_update = apply_frontend_patch(invalid_config, Json{{"even_number", 3}});
const Json validation_error = make_http_error<Json>(422, invalid_update).body;
const Json status_descriptor = to_status_descriptor_json<Json, Server_Status>();
const Json status_amis = make_amis_status_service<Json>(status_descriptor, "/admin/status", 2000);
Json examples = Json::object();
examples["descriptor"] = to_descriptor_json<Json, Gallery_Config>();
examples["field_policies"] = make_field_policy_snapshot<Gallery_Config>();
examples["form_view"] = config_resource_.view_schema();
examples["form_display_view"] = to_view_json<Json, Gallery_Config>(display_view);
examples["form_create_view"] = to_view_json<Json, Gallery_Config>(create_view);
examples["form_edit_view"] = to_view_json<Json, Gallery_Config>(edit_view);
examples["form_display_amis"] = to_amis_form_schema<Json>(config_value, display_view);
examples["form_create_amis"] = to_amis_form_schema<Json>(Gallery_Config{}, create_view);
examples["form_edit_amis"] = config_resource_.amis_schema();
examples["validation_422"] = validation_error;
examples["collection_list_view"] = collection_view_json(Collection_Mode::list);
examples["collection_cards_view"] = collection_view_json(Collection_Mode::cards);
examples["collection_table_view"] = collection_view_json(Collection_Mode::table);
examples["backend_composition_view"] = backend_layout_view_json();
examples["frontend_manifest"] = frontend_layout_manifest();
examples["backend_amis"] = amis_schema();
examples["list_amis"] = collection_schema(Collection_Mode::list);
examples["cards_amis"] = collection_schema(Collection_Mode::cards);
examples["table_amis"] = collection_schema(Collection_Mode::table);
examples["adapter_descriptor"] = to_descriptor_json<Json, Gallery_Adapter_Config>();
examples["adapter_view"] = adapter_resource_.view_schema();
examples["adapter_data"] = to_frontend_json<Json>(adapter_config_);
examples["adapter_amis"] = adapter_resource_.amis_schema();
examples["reflection_descriptor"] = to_descriptor_json<Json, Gallery_Reflected_Config>();
examples["reflection_view"] = reflected_resource_.view_schema();
examples["reflection_data"] = to_frontend_json<Json>(reflected_config_);
examples["reflection_amis"] = reflected_resource_.amis_schema();
examples["enum_values"] = Json{{"gallery_category", string_array({"primary", "secondary", "experimental"})}, {"encoded_row", to_frontend_json<Json>(make_rows().front())}};
examples["json_adapter_roundtrip"] = parse_json<Json>(dump_json(Json{{"adapter", "nlohmann_json"}, {"value", 7}}));
examples["polymorphic_descriptor"] = to_descriptor_json<Json, Gallery_Polymorphic_Config>();
examples["polymorphic_view"] = polymorphic_resource_.view_schema();
examples["polymorphic_data"] = to_frontend_json<Json>(polymorphic_config_);
examples["polymorphic_amis"] = polymorphic_resource_.amis_schema();
examples["object_adapter_descriptor"] = to_descriptor_json<Json, Gallery_Runtime_Config>();
examples["object_adapter_view"] = runtime_resource_.view_schema();
examples["object_adapter_data"] = to_frontend_json<Json>(runtime_config_);
examples["object_adapter_amis"] = runtime_resource_.amis_schema();
examples["managed_synchronization"] = make_managed_snapshot(config_);
examples["protocol_boundaries"] = protocol_boundaries();
examples["status_descriptor"] = status_descriptor;
examples["status_amis"] = status_amis;
examples["data"] = to_frontend_json<Json>(config_value);
examples["items"] = rows_.items_json();
std::vector<Capability_Case> cases;
cases.push_back(make_doc_case("form-layout", "fields", "Form View:字段、权限与组合布局", "字段事实来自 DescriptorForm View 只负责字段选择、顺序与组合;readable、read_write、editable、creatable、required、include_default 和 sensitive 各自保持独立语义。", "field(...).read_write().readable(false).include_default(false); edit_form<Gallery_Config>(vertical(group(...), grid(...), flow(...), tabs(...)))", Json{{"form_view", config_resource_.view_schema()}, {"field_policies", examples["field_policies"]}}, config_resource_.amis_schema(), config_resource_.amis_schema(), string_array({"automatic", "text", "multiline", "number", "range", "validated", "boolean", "select", "date", "color", "optional", "readonly", "sensitive", "visible_on", "nested", "readable", "read_write", "editable", "creatable", "required", "include_default", "enum_label"}), string_array({"/admin/gallery/config/descriptor", "/admin/gallery/config/view", "/admin/gallery/config/data", "/admin/gallery/config/amis"})));
cases.push_back(make_doc_case("form-modes", "fields", "Form Modedisplay / create / edit", "三种模式使用同一 Descriptor,但根据字段权限和 mode 生成不同 View、提交字段和 AMIS。", "detail_view<T>(); create_form<T>(); describe_edit_view<T>();", Json{{"display", examples["form_display_view"]}, {"create", examples["form_create_view"]}, {"edit", examples["form_edit_view"]}}, Json{{"display", examples["form_display_amis"]}, {"create", examples["form_create_amis"]}, {"edit", examples["form_edit_amis"]}}, examples["form_edit_amis"], string_array({"display", "create", "edit", "writable_submit_filter"}), string_array({"/admin/gallery/config/data"})));
cases.push_back(make_doc_case("backend-composition", "composition", "Composition:后端决定定位", "horizontal / vertical / list / grid / card / flow / tabs 都由后端 Composition JSON 明确表达。", "composition_view(\"component_gallery\", compose::tabs(...))", backend_layout_view_json(), amis_schema(), amis_schema(), string_array({"slot", "heading", "vertical", "horizontal", "flow", "grid", "list", "group", "card", "tabs", "tab"}), string_array({"/admin/gallery/backend/view", "/admin/gallery/backend/amis"})));
cases.push_back(make_doc_case("frontend-composition", "composition", "Composition Manifest:前端决定定位", "frontend 节点只保留成员与顺序;slot registry 明确组件类型和来源,前端用同一批真实组件做五种布局。", "to_composition_manifest_json(view, contracts, schemas)", frontend_layout_manifest(), Json{{"renderer_registry", "component_kind -> renderer"}}, make_gallery_note("前端定位 Preview", "同一 manifest 的五种真实布局在前端定位页由通用 renderer 展示。"), string_array({"frontend", "slot_contract", "composition_manifest", "renderer_registry"}), string_array({"/admin/gallery/frontend/manifest"})));
cases.push_back(make_doc_case("collection-modes", "collection", "Collection Viewtable / list / cards", "三种展示是 adminive.view 的一等 Collection mode,复用同一 Descriptor、查询、CRUD、重排和状态事实。", "describe_collection_view<Gallery_Row>().as_table(); .as_list(item); .as_cards(item, 3);", Json{{"list", examples["collection_list_view"]}, {"cards", examples["collection_cards_view"]}, {"table", examples["collection_table_view"]}}, Json{{"list", examples["list_amis"]}, {"cards", examples["cards_amis"]}, {"table", examples["table_amis"]}}, examples["table_amis"], string_array({"collection_view", "table", "list", "cards", "server_paging", "sort", "search", "filter", "create", "edit", "delete", "row_reorder", "column_reorder", "overview_status", "item_status"}), string_array({"/admin/gallery/items", "/admin/gallery/items/{id}", "/admin/gallery/items/order", "/admin/gallery/items/{id}/status", "/admin/status"})));
cases.push_back(make_doc_case("value-control-sensitive", "adapters", "Value / Control Adapter + Sensitive", "自定义值类型通过 Value_Adapter 映射数据,Control_Adapter 决定控件;sensitive 字段不会进入 frontend data。", "Value_Adapter<Gallery_Color_Code, Json>; Control_Adapter<Gallery_Color_Code, Json>; field.sensitive()", Json{{"descriptor", examples["adapter_descriptor"]}, {"frontend_data", examples["adapter_data"]}}, examples["adapter_amis"], examples["adapter_amis"], string_array({"value_adapter", "control_adapter", "sensitive_output_filter"}), string_array({"/admin/gallery/advanced/adapter/descriptor", "/admin/gallery/advanced/adapter/data", "/admin/gallery/advanced/adapter/amis"})));
cases.push_back(make_doc_case("json-enum-reflection", "adapters", "Json / Enum / Reflection Adapter", "JSON、枚举和无成员指针反射都使用正式 Adapter 契约;Reflection 示例由字段索引和名字驱动 Descriptor。", "Json_Adapter<Json>; Enum_Adapter<Gallery_Category>; Reflection_Adapter<Gallery_Reflected_Config>", Json{{"reflection", examples["reflection_descriptor"]}, {"enum", examples["enum_values"]}, {"json", examples["json_adapter_roundtrip"]}}, examples["reflection_amis"], examples["reflection_amis"], string_array({"json_adapter", "enum_adapter", "reflection_adapter"}), string_array({"/admin/gallery/advanced/reflection/descriptor", "/admin/gallery/advanced/reflection/data", "/admin/gallery/advanced/reflection/amis"})));
cases.push_back(make_doc_case("polymorphic", "adapters", "Polymorphic Adapter", "variant 的 discriminator、可选类型和每个子类型 Descriptor 都进入协议,AMIS 自动生成类型切换与子表单。", "Polymorphic_Adapter<Gallery_Target, Json>::variants()", examples["polymorphic_descriptor"], examples["polymorphic_amis"], examples["polymorphic_amis"], string_array({"polymorphic_adapter", "discriminator", "variant"}), string_array({"/admin/gallery/advanced/polymorphic/descriptor", "/admin/gallery/advanced/polymorphic/data", "/admin/gallery/advanced/polymorphic/amis"})));
cases.push_back(make_doc_case("object-transaction", "runtime", "Object Adapter + Resource Transaction", "运行时对象通过 Object_Adapter snapshot/commit 映射到可描述 Model;事务实验台覆盖 success、prepare reject、commit fail、rollback fail。", "Object_Adapter<Gallery_Runtime_Config>; Resource_Transaction<Gallery_Runtime_Model>", examples["object_adapter_view"], examples["object_adapter_amis"], examples["object_adapter_amis"], string_array({"object_adapter", "resource_transaction", "prepare", "commit", "rollback", "request_context"}), string_array({"/admin/gallery/advanced/object/data", "/admin/gallery/transaction/{scenario}"})));
cases.push_back(make_doc_case("managed-synchronization", "runtime", "Managed_Value 与同步拓扑", "Managed_Value 使用 Structive 的同步拓扑;后端 writable、前端 editable/creatable 和 synchronized 分别展示,避免把权限和锁混为一谈。", "Managed_Value<Gallery_Config>; value.resolved_synchronization(); value.member<&T::field>().write(...)", examples["managed_synchronization"], Json{{"transport", "Resource_Service<Managed_Value<T>>"}}, make_gallery_note("Managed / Synchronization", "同步拓扑和字段能力来自运行中的 Managed_Value,而不是第二套前端字段表。"), string_array({"managed_value", "managed_update", "synchronization_topology", "unsynchronized_field", "backend_writable", "frontend_editable"}), string_array({"/admin/gallery/config/data", "/admin/gallery/managed"})));
cases.push_back(make_doc_case("validation-boundaries", "safety", "Validation 与协议边界", "字段校验、nested path、object validator、readonly 提交、多态 discriminator、非法 Composition/Manifest 和非法 Collection query 都有可重复的标准错误。", "apply_frontend_patch(...); validate_composition_view(...); validate_composition_slot_contracts(...)", examples["protocol_boundaries"], examples["validation_422"], make_gallery_note("协议边界", "在“协议边界”页面可以逐项执行真实请求并查看标准错误。"), string_array({"validation_422", "field_errors", "nested_path", "object_validator", "readonly_reject", "invalid_grid", "invalid_tab", "missing_slot_contract", "duplicate_slot", "invalid_sort", "invalid_filter"}), string_array({"/admin/gallery/boundaries", "/admin/gallery/items?orderBy=color&orderDir=asc", "/admin/gallery/items?score=92"})));
Json status_endpoints = Json::array({"/admin/status", std::string(radio_states_api) + "/{id}/status"});
cases.push_back(make_doc_case("status", "runtime", "Status Pollingoverview + item", "状态 Descriptor 与轮询 renderer 分离;完整运行时同时展示 overview status 和 Collection item status。", "make_amis_status_service<Json>(descriptor, api, interval); collection.register_status<&T::status>()", status_descriptor, status_amis, status_amis, string_array({"status_polling", "overview_status", "item_status"}), std::move(status_endpoints)));
cases.push_back(make_doc_case("http-adapters", "adapters", "HTTP AdapterHttplib + Drogon", "Httplib 与 Drogon 都从 transport 构造 Request_Context,并把非保留 Collection query 完整交给同一 Service 校验;Httplib 也提供独立 Http_Status_Resource。真实黑盒测试覆盖 CRUD、严格查询、状态、异常边界和上下文传递。", "resource.bind(server, Httplib_Bind_Options{.context_factory = ...}); Http_Status_Resource<Status, Json>(...); Drogon_Bind_Options{...}", Json{{"httplib", "context_factory + strict query + standalone status"}, {"drogon", "context_factory + strict query + executor/filter"}}, Json{{"tests", string_array({"Adminive_Httplib_Adapter_Test", "Adminive_Drogon_Adapter_Test"})}}, make_gallery_note("Transport Adapter", "当前 Gallery 本身运行在 Httplib;两种 transport 对共享 Service 的 query 与错误语义保持一致,Drogon 由独立 adapter test 验证。"), string_array({"httplib", "drogon", "request_context", "strict_query", "standalone_status", "real_http_test"}), string_array({"/admin/gallery/config/data"})));
const Json verification_view = Json{{"ctest_labels", string_array({"unit", "protocol", "http", "install", "package", "header", "concurrency"})}, {"sanitizer_matrix", Json{{"clang_gnu", string_array({"ASan", "UBSan", "TSan"})}, {"msvc", string_array({"ASan"})}, {"unsupported_configuration", "configure_error"}}}, {"configurations", string_array({"Debug", "Release"})}};
const Json verification_runtime = Json{{"headers", "all public Adminive headers compile standalone"}, {"install_consumers", string_array({"Adminive::Core", "Adminive::Default + Httplib"})}, {"package_zip", "exact root files + no stale nested project"}, {"frontend", string_array({"node_test", "playwright_chromium", "playwright_msedge"})}};
cases.push_back(make_doc_case("verification", "engineering", "工程验证 Gate", "Release-safe test checks、公共头独立编译、源码包结构、Core + Default/Httplib 安装消费、真实 HTTP、Debug/Release 和平台真实支持的 Sanitizer 都有正式入口。", "ctest -L header; ctest -L package; ctest -L install; python scripts/verify.py", verification_view, verification_runtime, make_gallery_note("Verification Gate", "测试断言在 Release 下不会被 NDEBUG 清除;不支持的 sanitizer 配置会明确失败或 SKIP,不会假通过。"), string_array({"release_safe_checks", "public_header_compile", "package_zip", "install_consumer", "debug", "release", "asan", "ubsan", "tsan", "frontend_unit", "playwright", "edge"})));
Json layers = Json::array();
layers.push_back(Json{{"name", "Object Schema"}, {"purpose", "字段结构、读写能力、约束与同步事实"}});
layers.push_back(Json{{"name", "Field Presentation"}, {"purpose", "单字段 label、description、control、options、visible_on"}});
layers.push_back(Json{{"name", "View Schema"}, {"purpose", "Form/Collection 字段选择、顺序、查询能力与字段组合"}});
layers.push_back(Json{{"name", "Composition Manifest"}, {"purpose", "跨组件组合、slot contract 以及 frontend 布局授权"}});
layers.push_back(Json{{"name", "Adapter / Runtime"}, {"purpose", "AMIS、HTTP、Managed、Transaction、Status 和浏览器 renderer"}});
return Json{{"title", "Adminive 协议驱动能力画廊"}, {"summary", "Capability Case Registry 是唯一能力清单:每一项都绑定真实 C++ 声明、协议 JSON、renderer/HTTP 入口或自动测试,不再靠静态 capability 字符串宣称支持。"}, {"layers", std::move(layers)}, {"capabilities", derive_capabilities(cases)}, {"endpoints", derive_endpoints(cases)}, {"examples", std::move(examples)}, {"cases", capability_cases_json(cases)}};
}
void Component_Gallery::bind(httplib::Server& server) {
config_resource_.bind(server, Httplib_Bind_Options{gallery_request_context});
rows_.bind(server);
adapter_resource_.bind(server, Httplib_Bind_Options{gallery_request_context});
reflected_resource_.bind(server, Httplib_Bind_Options{gallery_request_context});
polymorphic_resource_.bind(server, Httplib_Bind_Options{gallery_request_context});
runtime_resource_.bind(server, Httplib_Bind_Options{gallery_request_context});
server.Get("/admin/gallery/backend/view", [this](const httplib::Request&, httplib::Response& response) {
write_http_response(response, make_http_success<Json>(backend_layout_view_json()));
});
server.Get("/admin/gallery/backend/amis", [this](const httplib::Request&, httplib::Response& response) {
write_http_response(response, make_http_success<Json>(amis_schema()));
});
server.Get("/admin/gallery/frontend/manifest", [this](const httplib::Request&, httplib::Response& response) {
write_http_response(response, make_http_success<Json>(frontend_layout_manifest()));
});
server.Get("/admin/gallery/items/list/view", [this](const httplib::Request&, httplib::Response& response) {
write_http_response(response, make_http_success<Json>(collection_view_json(Collection_Mode::list)));
});
server.Get("/admin/gallery/items/cards/view", [this](const httplib::Request&, httplib::Response& response) {
write_http_response(response, make_http_success<Json>(collection_view_json(Collection_Mode::cards)));
});
server.Get("/admin/gallery/items/table/view", [this](const httplib::Request&, httplib::Response& response) {
write_http_response(response, make_http_success<Json>(collection_view_json(Collection_Mode::table)));
});
server.Get("/admin/gallery/items/list/amis", [this](const httplib::Request&, httplib::Response& response) {
write_http_response(response, make_http_success<Json>(collection_schema(Collection_Mode::list)));
});
server.Get("/admin/gallery/items/cards/amis", [this](const httplib::Request&, httplib::Response& response) {
write_http_response(response, make_http_success<Json>(collection_schema(Collection_Mode::cards)));
});
server.Get("/admin/gallery/items/table/amis", [this](const httplib::Request&, httplib::Response& response) {
write_http_response(response, make_http_success<Json>(collection_schema(Collection_Mode::table)));
});
server.Get("/admin/gallery/managed", [this](const httplib::Request&, httplib::Response& response) {
write_http_response(response, make_http_success<Json>(make_managed_snapshot(config_)));
});
server.Get("/admin/gallery/boundaries", [](const httplib::Request&, httplib::Response& response) {
write_http_response(response, make_http_success<Json>(protocol_boundaries()));
});
server.Post(R"(/admin/gallery/transaction/(success|prepare-reject|commit-fail|rollback-fail))", [](const httplib::Request& request, httplib::Response& response) {
write_http_response(response, transaction_scenario(request.matches[1].str(), request));
});
server.Get("/admin/gallery/docs", [this](const httplib::Request&, httplib::Response& response) {
write_http_response(response, make_http_success<Json>(documentation()));
});
}
std::vector<Gallery_Row> Component_Gallery::make_rows() {
return {
Gallery_Row{"Alpha", Gallery_Category::primary, Range_Value<int, 0, 100>{92}, true, "#2563eb"},
Gallery_Row{"Beta", Gallery_Category::secondary, Range_Value<int, 0, 100>{76}, true, "#16a34a"},
Gallery_Row{"Gamma", Gallery_Category::experimental, Range_Value<int, 0, 100>{58}, false, "#d97706"},
Gallery_Row{"Delta", Gallery_Category::primary, Range_Value<int, 0, 100>{84}, true, "#7c3aed"},
Gallery_Row{"Epsilon", Gallery_Category::secondary, Range_Value<int, 0, 100>{67}, false, "#0891b2"},
Gallery_Row{"Zeta", Gallery_Category::experimental, Range_Value<int, 0, 100>{49}, true, "#dc2626"}
};
}
}