Files
Adminive/backend/tests/advanced_adapter_test.cpp
T
2026-08-07 00:01:16 +08:00

328 lines
12 KiB
C++

#include "adminive/adapters/boost_pfr.hpp"
#include "adminive/adapters/nlohmann_json.hpp"
#include "adminive/http.hpp"
#include <cassert>
#include <string>
#include <tuple>
#include <variant>
namespace advanced_test {
using Json = nlohmann::json;
struct Nested_Config {
int count{1};
};
struct Runtime_Model {
Nested_Config nested;
std::string name{"default"};
};
class Runtime_Config {
public:
Runtime_Config() = default;
Runtime_Config(const Runtime_Config&) = delete;
Runtime_Config& operator=(const Runtime_Config&) = delete;
int count() const noexcept {
return count_;
}
const std::string& name() const noexcept {
return name_;
}
void apply(const Runtime_Model& value) {
count_ = value.nested.count;
name_ = value.name;
++apply_count_;
}
int apply_count() const noexcept {
return apply_count_;
}
private:
int count_{1};
int apply_count_{};
std::string name_{"default"};
};
struct Base_A {
int alpha{3};
};
struct Base_B {
std::string beta{"base"};
};
struct Inherited_Config : Base_A, Base_B {};
struct Color_Code {
std::string value{"#112233"};
};
struct Control_Config {
Color_Code color;
};
struct Serial_Device {
std::string port{"COM1"};
int baud_rate{115200};
Color_Code color;
};
struct Network_Device {
std::string host{"127.0.0.1"};
int port{9000};
};
using Device = std::variant<Serial_Device, Network_Device>;
struct Device_Config {
Device device{Serial_Device{}};
};
struct Overview_Status {
adminive::Status_Value<std::string> state{"running", "#16a34a"};
int workers{4};
};
struct Secret_Config {
std::string secret{"token"};
std::string visible{"public"};
};
struct Duplicate_Config {
int first{};
int second{};
};
struct Invalid_Sort_Config {
int value{};
};
}
namespace adminive {
template <>
struct Boost_Pfr_Name_Adapter<advanced_test::Base_A> {
template <std::size_t Index>
static constexpr std::string_view name() noexcept {
static_assert(Index == 0);
return "alpha";
}
};
template <>
struct Boost_Pfr_Name_Adapter<advanced_test::Base_B> {
template <std::size_t Index>
static constexpr std::string_view name() noexcept {
static_assert(Index == 0);
return "beta";
}
};
template <>
struct Object_Adapter<advanced_test::Runtime_Config> {
using model_type = advanced_test::Runtime_Model;
static model_type snapshot(const advanced_test::Runtime_Config& value) {
model_type result;
result.nested.count = value.count();
result.name = value.name();
return result;
}
static void commit(advanced_test::Runtime_Config& target, model_type value) {
target.apply(value);
}
};
template <>
struct Reflection_Adapter<advanced_test::Inherited_Config> : Boost_Pfr_Base_Reflection_Adapter<advanced_test::Inherited_Config, advanced_test::Base_A, advanced_test::Base_B> {};
template <class Json>
struct Value_Adapter<advanced_test::Color_Code, Json> {
using value_type = std::string;
static const std::string& read(const advanced_test::Color_Code& value) noexcept {
return value.value;
}
static void write(advanced_test::Color_Code& target, std::string value) {
target.value = std::move(value);
}
};
template <>
struct Control_Adapter<advanced_test::Color_Code, advanced_test::Json> {
static advanced_test::Json make_control(const advanced_test::Json& field, Control_Context context) {
advanced_test::Json result;
result["type"] = "input-color";
result["name"] = make_field_name(context.prefix, field.at("name").get<std::string>());
result["label"] = field.at("label");
result["clearable"] = true;
return result;
}
static advanced_test::Json make_column(const advanced_test::Json& field) {
return advanced_test::Json{{"type", "tpl"}, {"name", field.at("name")}, {"label", field.at("list_label")}, {"tpl", "<span style=\"color:${color}\">${color}</span>"}};
}
};
template <>
struct Type_Descriptor<advanced_test::Nested_Config> {
static auto get() {
using T = advanced_test::Nested_Config;
return object<T>("nested", "Nested", ADMINIVE_FIELD(T, count).editable()).validator([](const T& value) {
if(value.count < 1) {
throw Field_Validation_Error("count", "count must be positive");
}
});
}
};
template <>
struct Type_Descriptor<advanced_test::Runtime_Model> {
static auto get() {
using T = advanced_test::Runtime_Model;
return object<T>("runtime", "Runtime", ADMINIVE_FIELD(T, nested).editable(), ADMINIVE_FIELD(T, name).editable());
}
};
template <>
struct Type_Descriptor<advanced_test::Inherited_Config> {
static auto get() {
using T = advanced_test::Inherited_Config;
return reflected_object<T>("inherited", "Inherited");
}
};
template <>
struct Type_Descriptor<advanced_test::Control_Config> {
static auto get() {
using T = advanced_test::Control_Config;
return object<T>("control", "Control", ADMINIVE_FIELD(T, color).editable().list_label("颜色"));
}
};
template <>
struct Type_Descriptor<advanced_test::Serial_Device> {
static auto get() {
using T = advanced_test::Serial_Device;
return object<T>("serial", "Serial", ADMINIVE_FIELD(T, port).editable().creatable(), ADMINIVE_FIELD(T, baud_rate).editable().creatable(), ADMINIVE_FIELD(T, color).editable().creatable());
}
};
template <>
struct Type_Descriptor<advanced_test::Network_Device> {
static auto get() {
using T = advanced_test::Network_Device;
return object<T>("network", "Network", ADMINIVE_FIELD(T, host).editable().creatable(), ADMINIVE_FIELD(T, port).editable().creatable());
}
};
template <>
struct Polymorphic_Adapter<advanced_test::Device, advanced_test::Json> {
static constexpr std::string_view discriminator() noexcept {
return "type";
}
static constexpr std::string_view discriminator_label() noexcept {
return "设备类型";
}
static auto variants() {
return std::tuple(
polymorphic_variant<advanced_test::Serial_Device>("serial", "串口设备"),
polymorphic_variant<advanced_test::Network_Device>("network", "网络设备")
);
}
static advanced_test::Json encode(const advanced_test::Device& value) {
return std::visit([](const auto& device) {
advanced_test::Json result = to_json<advanced_test::Json>(device);
using T = std::remove_cvref_t<decltype(device)>;
result["type"] = std::same_as<T, advanced_test::Serial_Device> ? "serial" : "network";
return result;
}, value);
}
static void decode(advanced_test::Device& target, const advanced_test::Json& value, Write_Context context) {
const std::string type = value.at("type").get<std::string>();
if(type == "serial") {
decode_polymorphic_alternative<advanced_test::Json, advanced_test::Serial_Device>(target, value, context);
return;
}
if(type == "network") {
decode_polymorphic_alternative<advanced_test::Json, advanced_test::Network_Device>(target, value, context);
return;
}
throw Field_Validation_Error("type", "unknown device type");
}
};
template <>
struct Type_Descriptor<advanced_test::Device_Config> {
static auto get() {
using T = advanced_test::Device_Config;
return object<T>("devices", "Devices", ADMINIVE_FIELD(T, device).editable());
}
};
template <>
struct Type_Descriptor<advanced_test::Overview_Status> {
static auto get() {
using T = advanced_test::Overview_Status;
return object<T>("overview", "运行状态", ADMINIVE_FIELD(T, state).label("服务状态"), ADMINIVE_FIELD(T, workers).label("工作线程"));
}
};
template <>
struct Type_Descriptor<advanced_test::Secret_Config> {
static auto get() {
using T = advanced_test::Secret_Config;
return object<T>("secret", "Secret", ADMINIVE_FIELD(T, secret).sensitive().editable(), ADMINIVE_FIELD(T, visible).editable());
}
};
template <>
struct Type_Descriptor<advanced_test::Duplicate_Config> {
static auto get() {
using T = advanced_test::Duplicate_Config;
return object<T>("duplicate", "Duplicate", field<&T::first>("same"), field<&T::second>("same"));
}
};
template <>
struct Type_Descriptor<advanced_test::Invalid_Sort_Config> {
static auto get() {
using T = advanced_test::Invalid_Sort_Config;
return object<T>("invalid_sort", "Invalid Sort", ADMINIVE_FIELD(T, value)).default_sort("value");
}
};
}
int main() {
using namespace advanced_test;
Runtime_Config runtime;
int transaction_count{};
adminive::Resource_Transaction<Runtime_Model> transaction;
transaction.prepare = [](const Runtime_Model& candidate, const adminive::Request_Context&) {
if(candidate.name == "blocked") {
throw adminive::Field_Validation_Error("name", "name is blocked");
}
};
transaction.commit = [&transaction_count](const Runtime_Model&, const adminive::Request_Context&) {
++transaction_count;
};
adminive::Resource_Service<Runtime_Config, Json> service(runtime, "/config", std::move(transaction));
const auto invalid = service.update_response(R"({"nested":{"count":0}})");
assert(invalid.status == 422);
assert(invalid.body.at("field_errors").contains("nested.count"));
const auto rejected_commit = service.update_response(R"({"name":"blocked"})");
assert(rejected_commit.status == 422);
assert(rejected_commit.body.at("field_errors").at("name") == "name is blocked");
assert(runtime.name() == "default");
const auto valid = service.update_response(R"({"nested":{"count":8},"name":"updated"})");
assert(valid.status == 200);
assert(runtime.count() == 8);
assert(runtime.name() == "updated");
assert(runtime.apply_count() == 1);
assert(transaction_count == 1);
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");
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");
Device_Config devices;
const auto device_form = adminive::to_amis_form_schema<Json>(devices);
assert(device_form.at("body").at(0).at("type") == "fieldset");
assert(device_form.at("body").at(0).at("body").at(0).at("type") == "select");
assert(device_form.at("body").at(0).at("body").at(1).at("body").at(2).at("type") == "input-color");
const auto polymorphic_patch = Json{{"device", Json{{"type", "network"}, {"host", "10.0.0.1"}, {"port", 7000}, {"baud_rate", 9600}}}};
const auto polymorphic_update = adminive::apply_frontend_patch<Json>(devices, polymorphic_patch);
assert(polymorphic_update.success);
assert(std::holds_alternative<Network_Device>(devices.device));
assert(std::get<Network_Device>(devices.device).host == "10.0.0.1");
const auto status_descriptor = adminive::to_status_descriptor_json<Json, Overview_Status>();
const auto status_service = adminive::make_amis_status_service<Json>(status_descriptor, "/status", 1000);
assert(status_service.at("body").at("title") == "运行状态");
assert(status_service.at("body").at("body").at("body").size() == 2);
const auto descriptor_without_defaults = adminive::to_descriptor_json<Json, Secret_Config>();
assert(!descriptor_without_defaults.at("fields").at(0).contains("default"));
const auto descriptor_with_defaults = adminive::to_descriptor_json_with_defaults<Json, Secret_Config>();
assert(!descriptor_with_defaults.at("fields").at(0).contains("default"));
assert(descriptor_with_defaults.at("fields").at(1).at("default") == "public");
Secret_Config secret;
const auto frontend_secret = adminive::to_frontend_json<Json>(secret);
assert(!frontend_secret.contains("secret"));
assert(frontend_secret.at("visible") == "public");
bool duplicate_rejected{};
try {
static_cast<void>(adminive::to_descriptor_json<Json, Duplicate_Config>());
} catch(const std::invalid_argument&) {
duplicate_rejected = true;
}
assert(duplicate_rejected);
bool invalid_sort_rejected{};
try {
static_cast<void>(adminive::to_descriptor_json<Json, Invalid_Sort_Config>());
} catch(const std::invalid_argument&) {
invalid_sort_rejected = true;
}
assert(invalid_sort_rejected);
return 0;
}