344 lines
14 KiB
C++
344 lines
14 KiB
C++
#include "adminive/adminive.hpp"
|
|
#include "adminive_test.hpp"
|
|
#include <cstdint>
|
|
#include <map>
|
|
#include <optional>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <type_traits>
|
|
#include <utility>
|
|
#include <variant>
|
|
#include <vector>
|
|
namespace adapter_test {
|
|
struct Mini_Json;
|
|
using Mini_Object = std::map<std::string, Mini_Json>;
|
|
using Mini_Array = std::vector<Mini_Json>;
|
|
struct Mini_Json {
|
|
using Storage = std::variant<std::nullptr_t, bool, std::int64_t, std::uint64_t, long double, std::string, Mini_Object, Mini_Array>;
|
|
Storage value{};
|
|
};
|
|
struct External_Atomic {
|
|
int value{};
|
|
};
|
|
enum class Mode {
|
|
primary = 10,
|
|
secondary = 20
|
|
};
|
|
struct Runtime_Base {
|
|
External_Atomic worker_count{4};
|
|
};
|
|
struct Mode_Base {
|
|
Mode mode{Mode::primary};
|
|
};
|
|
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 <>
|
|
struct Json_Adapter<adapter_test::Mini_Json> {
|
|
using Json = adapter_test::Mini_Json;
|
|
static Json object() {
|
|
return Json{adapter_test::Mini_Object{}};
|
|
}
|
|
static Json array() {
|
|
return Json{adapter_test::Mini_Array{}};
|
|
}
|
|
static Json null() {
|
|
return Json{nullptr};
|
|
}
|
|
static Json parse(std::string_view) {
|
|
throw std::runtime_error("parse is not implemented by the test adapter");
|
|
}
|
|
static std::string dump(const Json&, int) {
|
|
return {};
|
|
}
|
|
static bool is_null(const Json& value) noexcept {
|
|
return std::holds_alternative<std::nullptr_t>(value.value);
|
|
}
|
|
static bool is_object(const Json& value) noexcept {
|
|
return std::holds_alternative<adapter_test::Mini_Object>(value.value);
|
|
}
|
|
static bool is_array(const Json& value) noexcept {
|
|
return std::holds_alternative<adapter_test::Mini_Array>(value.value);
|
|
}
|
|
static bool is_string(const Json& value) noexcept {
|
|
return std::holds_alternative<std::string>(value.value);
|
|
}
|
|
static bool is_signed_integer(const Json& value) noexcept {
|
|
return std::holds_alternative<std::int64_t>(value.value);
|
|
}
|
|
static bool is_unsigned_integer(const Json& value) noexcept {
|
|
return std::holds_alternative<std::uint64_t>(value.value);
|
|
}
|
|
static bool is_floating_point(const Json& value) noexcept {
|
|
return std::holds_alternative<long double>(value.value);
|
|
}
|
|
static bool is_number(const Json& value) noexcept {
|
|
return is_signed_integer(value) || is_unsigned_integer(value) || is_floating_point(value);
|
|
}
|
|
static bool is_boolean(const Json& value) noexcept {
|
|
return std::holds_alternative<bool>(value.value);
|
|
}
|
|
static std::int64_t signed_integer(const Json& value) {
|
|
return std::get<std::int64_t>(value.value);
|
|
}
|
|
static std::uint64_t unsigned_integer(const Json& value) {
|
|
return std::get<std::uint64_t>(value.value);
|
|
}
|
|
static long double number(const Json& value) {
|
|
if(is_signed_integer(value)) {
|
|
return static_cast<long double>(signed_integer(value));
|
|
}
|
|
if(is_unsigned_integer(value)) {
|
|
return static_cast<long double>(unsigned_integer(value));
|
|
}
|
|
return std::get<long double>(value.value);
|
|
}
|
|
static bool contains(const Json& value, std::string_view name) {
|
|
const auto& object_value = std::get<adapter_test::Mini_Object>(value.value);
|
|
return object_value.contains(std::string(name));
|
|
}
|
|
static std::size_t size(const Json& value) {
|
|
if(is_object(value)) {
|
|
return std::get<adapter_test::Mini_Object>(value.value).size();
|
|
}
|
|
if(is_array(value)) {
|
|
return std::get<adapter_test::Mini_Array>(value.value).size();
|
|
}
|
|
return 0;
|
|
}
|
|
static std::vector<std::string> keys(const Json& value) {
|
|
std::vector<std::string> result;
|
|
for(const auto& [name, item] : std::get<adapter_test::Mini_Object>(value.value)) {
|
|
static_cast<void>(item);
|
|
result.push_back(name);
|
|
}
|
|
return result;
|
|
}
|
|
static Json& at(Json& value, std::string_view name) {
|
|
return std::get<adapter_test::Mini_Object>(value.value).at(std::string(name));
|
|
}
|
|
static const Json& at(const Json& value, std::string_view name) {
|
|
return std::get<adapter_test::Mini_Object>(value.value).at(std::string(name));
|
|
}
|
|
static Json& at(Json& value, std::size_t index) {
|
|
return std::get<adapter_test::Mini_Array>(value.value).at(index);
|
|
}
|
|
static const Json& at(const Json& value, std::size_t index) {
|
|
return std::get<adapter_test::Mini_Array>(value.value).at(index);
|
|
}
|
|
static void set(Json& value, std::string_view name, Json item) {
|
|
std::get<adapter_test::Mini_Object>(value.value).insert_or_assign(std::string(name), std::move(item));
|
|
}
|
|
static void append(Json& value, Json item) {
|
|
std::get<adapter_test::Mini_Array>(value.value).push_back(std::move(item));
|
|
}
|
|
static void erase(Json& value, std::string_view name) {
|
|
std::get<adapter_test::Mini_Object>(value.value).erase(std::string(name));
|
|
}
|
|
template <class T>
|
|
static Json make(T&& value) {
|
|
using Value = std::remove_cvref_t<T>;
|
|
if constexpr(std::same_as<Value, bool>) {
|
|
return Json{value};
|
|
} else if constexpr(std::signed_integral<Value>) {
|
|
return Json{static_cast<std::int64_t>(value)};
|
|
} else if constexpr(std::unsigned_integral<Value>) {
|
|
return Json{static_cast<std::uint64_t>(value)};
|
|
} else if constexpr(std::floating_point<Value>) {
|
|
return Json{static_cast<long double>(value)};
|
|
} else if constexpr(std::same_as<Value, std::string>) {
|
|
return Json{std::forward<T>(value)};
|
|
} else if constexpr(std::same_as<Value, std::string_view>) {
|
|
return Json{std::string(value)};
|
|
} else if constexpr(std::is_convertible_v<T, const char*>) {
|
|
return Json{std::string(value)};
|
|
} else {
|
|
static_assert(!sizeof(T), "unsupported Mini_Json scalar");
|
|
}
|
|
}
|
|
template <class T>
|
|
static T get(const Json& value) {
|
|
if constexpr(std::same_as<T, bool>) {
|
|
return std::get<bool>(value.value);
|
|
} else if constexpr(std::signed_integral<T>) {
|
|
return static_cast<T>(std::get<std::int64_t>(value.value));
|
|
} else if constexpr(std::unsigned_integral<T>) {
|
|
return static_cast<T>(std::get<std::uint64_t>(value.value));
|
|
} else if constexpr(std::floating_point<T>) {
|
|
return static_cast<T>(number(value));
|
|
} else if constexpr(std::same_as<T, std::string>) {
|
|
return std::get<std::string>(value.value);
|
|
} else {
|
|
static_assert(!sizeof(T), "unsupported Mini_Json conversion");
|
|
}
|
|
}
|
|
};
|
|
template <class Json>
|
|
struct Value_Adapter<adapter_test::External_Atomic, Json> {
|
|
using value_type = int;
|
|
static int read(const adapter_test::External_Atomic& value) noexcept {
|
|
return value.value;
|
|
}
|
|
static void write(adapter_test::External_Atomic& target, int value) noexcept {
|
|
target.value = value;
|
|
}
|
|
};
|
|
template <>
|
|
struct Enum_Adapter<adapter_test::Mode> {
|
|
static std::vector<adapter_test::Mode> values() {
|
|
return {adapter_test::Mode::primary, adapter_test::Mode::secondary};
|
|
}
|
|
static std::string_view name(adapter_test::Mode value) {
|
|
return value == adapter_test::Mode::primary ? "primary" : "secondary";
|
|
}
|
|
static std::optional<adapter_test::Mode> cast(std::string_view value) {
|
|
if(value == "primary") {
|
|
return adapter_test::Mode::primary;
|
|
}
|
|
if(value == "secondary") {
|
|
return adapter_test::Mode::secondary;
|
|
}
|
|
return std::nullopt;
|
|
}
|
|
static std::optional<adapter_test::Mode> cast(std::underlying_type_t<adapter_test::Mode> value) {
|
|
if(value == 10) {
|
|
return adapter_test::Mode::primary;
|
|
}
|
|
if(value == 20) {
|
|
return adapter_test::Mode::secondary;
|
|
}
|
|
return std::nullopt;
|
|
}
|
|
};
|
|
template <>
|
|
struct Reflection_Adapter<adapter_test::Config> {
|
|
static constexpr std::size_t field_count = 3;
|
|
template <std::size_t Index>
|
|
static decltype(auto) get(adapter_test::Config& value) noexcept {
|
|
if constexpr(Index == 0) {
|
|
return (value.worker_count);
|
|
} else if constexpr(Index == 1) {
|
|
return (value.mode);
|
|
} else {
|
|
return (value.name);
|
|
}
|
|
}
|
|
template <std::size_t Index>
|
|
static decltype(auto) get(const adapter_test::Config& value) noexcept {
|
|
if constexpr(Index == 0) {
|
|
return (value.worker_count);
|
|
} else if constexpr(Index == 1) {
|
|
return (value.mode);
|
|
} else {
|
|
return (value.name);
|
|
}
|
|
}
|
|
template <std::size_t Index>
|
|
static constexpr std::string_view name() noexcept {
|
|
if constexpr(Index == 0) {
|
|
return "worker_count";
|
|
} else if constexpr(Index == 1) {
|
|
return "mode";
|
|
} else {
|
|
return "name";
|
|
}
|
|
}
|
|
};
|
|
template <>
|
|
struct Type_Descriptor<adapter_test::Config> {
|
|
static auto get() {
|
|
using T = adapter_test::Config;
|
|
return reflected_object_with<T>("config", "配置", []<std::size_t Index>(auto field) {
|
|
if constexpr(Index == 0) {
|
|
return field.label("线程数").editable().required();
|
|
} else if constexpr(Index == 1) {
|
|
return field.label("模式").editable().template enum_label<adapter_test::Mode::primary>("主用").template enum_label<adapter_test::Mode::secondary>("备用");
|
|
} else {
|
|
return field.label("名称").editable();
|
|
}
|
|
});
|
|
}
|
|
};
|
|
template <>
|
|
struct Type_Descriptor<adapter_test::Runtime_Control> {
|
|
static auto get() {
|
|
auto accessor = structive::trusted_callable_accessor<adapter_test::Runtime_Control>(
|
|
[](const adapter_test::Runtime_Control& value) { return value.setting(); },
|
|
[](adapter_test::Runtime_Control& target, int value) {
|
|
target.apply_setting(value);
|
|
});
|
|
return adminive::object<adapter_test::Runtime_Control>(
|
|
"runtime_control", "Runtime Control",
|
|
adminive::field("setting", "Setting", std::move(accessor))
|
|
.editable()
|
|
.unsynchronized());
|
|
}
|
|
};
|
|
template <>
|
|
struct Type_View_Descriptor<adapter_test::Config> {
|
|
static Table_View table() {
|
|
using T = adapter_test::Config;
|
|
return table_view<T>(column_index<T, 0>("线程数").sort(), column_index<T, 1>("模式").sort().filter(), column_index<T, 2>("名称").search());
|
|
}
|
|
};
|
|
}
|
|
int main() {
|
|
using Json = adapter_test::Mini_Json;
|
|
using Config = adapter_test::Config;
|
|
const Json descriptor = adminive::to_descriptor_json_with_defaults<Json, Config>();
|
|
ADMINIVE_CHECK((adminive::json_get<Json, std::string>(adminive::Json_Adapter<Json>::at(descriptor, "protocol")) == adminive::resource_protocol));
|
|
ADMINIVE_CHECK((adminive::json_get<Json, int>(adminive::Json_Adapter<Json>::at(descriptor, "protocol_version")) == adminive::resource_protocol_version));
|
|
const auto& fields = adminive::Json_Adapter<Json>::at(descriptor, "fields");
|
|
ADMINIVE_CHECK((adminive::json_get<Json, std::string>(adminive::Json_Adapter<Json>::at(adminive::Json_Adapter<Json>::at(fields, 0), "name")) == "worker_count"));
|
|
ADMINIVE_CHECK((adminive::json_get<Json, int>(adminive::Json_Adapter<Json>::at(adminive::Json_Adapter<Json>::at(fields, 0), "default")) == 4));
|
|
const auto& presentation = adminive::Json_Adapter<Json>::at(adminive::Json_Adapter<Json>::at(fields, 1), "presentation");
|
|
const auto& options = adminive::Json_Adapter<Json>::at(presentation, "options");
|
|
ADMINIVE_CHECK((adminive::json_get<Json, std::string>(adminive::Json_Adapter<Json>::at(adminive::Json_Adapter<Json>::at(options, 0), "label")) == "主用"));
|
|
Config config;
|
|
Json patch = adminive::json_object<Json>();
|
|
adminive::json_set(patch, "worker_count", 12);
|
|
adminive::json_set(patch, "mode", "secondary");
|
|
adminive::json_set(patch, "name", "updated");
|
|
const auto result = adminive::apply_frontend_patch<Json>(config, patch);
|
|
ADMINIVE_CHECK(result.success);
|
|
ADMINIVE_CHECK(config.worker_count.value == 12);
|
|
ADMINIVE_CHECK(config.mode == adapter_test::Mode::secondary);
|
|
ADMINIVE_CHECK(config.name == "updated");
|
|
const Json encoded = adminive::to_json<Json>(config);
|
|
ADMINIVE_CHECK((adminive::json_get<Json, int>(adminive::Json_Adapter<Json>::at(encoded, "worker_count")) == 12));
|
|
ADMINIVE_CHECK((adminive::json_get<Json, std::string>(adminive::Json_Adapter<Json>::at(encoded, "mode")) == "secondary"));
|
|
adapter_test::Runtime_Control runtime_control;
|
|
const Json runtime_encoded = adminive::to_json<Json>(runtime_control);
|
|
ADMINIVE_CHECK((adminive::json_get<Json, int>(
|
|
adminive::Json_Adapter<Json>::at(runtime_encoded, "setting")) == 3));
|
|
Json runtime_patch = adminive::json_object<Json>();
|
|
adminive::json_set(runtime_patch, "setting", 17);
|
|
const auto runtime_result =
|
|
adminive::apply_frontend_patch<Json>(runtime_control, runtime_patch);
|
|
ADMINIVE_CHECK(runtime_result.success);
|
|
ADMINIVE_CHECK(runtime_control.setting() == 17);
|
|
adminive::Collection_Service<Config, Json, adminive::No_Lock> collection("/configs", {Config{}});
|
|
const auto list = collection.list_response();
|
|
ADMINIVE_CHECK(list.status == 200);
|
|
const auto& data = adminive::Json_Adapter<Json>::at(list.body, "data");
|
|
ADMINIVE_CHECK((adminive::json_get<Json, std::uint64_t>(adminive::Json_Adapter<Json>::at(data, "total")) == 1));
|
|
return 0;
|
|
}
|