From 2058d9ba0825c19f082fab13b39c007e491b83fe Mon Sep 17 00:00:00 2001 From: Borislav Stanimirov Date: Fri, 29 Nov 2024 14:51:40 +0200 Subject: [PATCH] feat(schema): io visitors, ref #149 --- schema/code/ac/schema/IOVisitors.hpp | 56 ++++++++++++-------- schema/test/CMakeLists.txt | 1 + schema/test/t-visitors.cpp | 77 ++++++++++++++++++++++++++++ 3 files changed, 114 insertions(+), 20 deletions(-) diff --git a/schema/code/ac/schema/IOVisitors.hpp b/schema/code/ac/schema/IOVisitors.hpp index 17f9fccc..b478f3a0 100644 --- a/schema/code/ac/schema/IOVisitors.hpp +++ b/schema/code/ac/schema/IOVisitors.hpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: MIT // #pragma once +#include "Field.hpp" #include #include #include @@ -13,13 +14,13 @@ namespace ac::local::schema { namespace impl { struct DummyVisitor { template - void field(T& value, std::string_view name, std::string_view desc, bool required); + void operator()(Field& field, std::string_view name, std::string_view desc); }; } // namespace impl template concept Visitable = requires(T t) { - t.visitFields(std::declval()); + t.visitFields(std::declval()); }; struct ToDictVisitor { @@ -41,28 +42,32 @@ struct ToDictVisitor { static void writeToDict(Dict& out, std::vector& value) { out = Dict::array(); for (auto& v : value) { - vriteToDict(out.emplace_back(), v); + writeToDict(out.emplace_back(), v); } } template - void field(T& value, std::string_view name, std::string_view, bool) { - writeToDict(out[name], value); - } - - template - void field(std::optional& value, std::string_view name, std::string_view desc, bool required) { - if (!value) { - if (required) { + void operator()(Field& field, std::string_view name, std::string_view) { + if (field.defaultSet()) return; // don't be redundant + if (!field.hasValue()) { + if (field.required()) { throw_ex{} << "Required field " << name << " is not set"; } + // nothing to write for nullopt return; } - - field(*value, name, {}, true); + writeToDict(out[name], field.value()); } }; +template +Dict Struct_toDict(T&& s) { + Dict ret; + ToDictVisitor v(ret); + s.visitFields(v); + return ret; +} + struct FromDictVisitor { Dict& in; FromDictVisitor(Dict& in) : in(in) {}; @@ -70,12 +75,12 @@ struct FromDictVisitor { template static void readFromDict(Dict& in, T& value) { FromDictVisitor sub(in); - value.visitFields(value); + value.visitFields(sub); } template static void readFromDict(Dict& in, T& value) { - value = in.get(); + value = in.get(); } static void readFromDict(Dict& in, std::string& value) { @@ -85,21 +90,32 @@ struct FromDictVisitor { template static void readFromDict(Dict& in, std::vector& value) { value.clear(); - for (auto& v : *in) { + for (auto& v : in) { readFromDict(v, value.emplace_back()); } } - Dict* find(std::string_view name, bool required) { + template + void operator()(Field& field, std::string_view name, std::string_view) { auto it = in.find(name); if (it == in.end()) { - if (required) { + if (field.required()) { throw_ex{} << "Required field " << name << " is not set"; } - return nullptr; + // otherwise leave the field as is + return; } - return &*it; + + readFromDict(*it, field.materialize()); } }; +template +T Struct_fromDict(Dict&& d) { + T ret; + FromDictVisitor v(d); + ret.visitFields(v); + return ret; +} + } // namespace ac::local::schema diff --git a/schema/test/CMakeLists.txt b/schema/test/CMakeLists.txt index d35a5d97..60b77a50 100644 --- a/schema/test/CMakeLists.txt +++ b/schema/test/CMakeLists.txt @@ -6,5 +6,6 @@ macro(schema_test test) endmacro() schema_test(TupleIndexByItemId) +schema_test(Field) schema_test(DispatchHelpers) schema_test(visitors) diff --git a/schema/test/t-visitors.cpp b/schema/test/t-visitors.cpp index 896092cf..17a52a28 100644 --- a/schema/test/t-visitors.cpp +++ b/schema/test/t-visitors.cpp @@ -2,3 +2,80 @@ // SPDX-License-Identifier: MIT // #include +#include + +using namespace ac::local::schema; + +struct Person { + Field name; + Field age = Default(0); + + template + void visitFields(V& v) { + v(name, "name", "Name of the person"); + v(age, "age", "Age of the person"); + } +}; + +struct Company { + Field name; + Field mission = std::nullopt; + Field revenue = Default(0.f); + Field ceo; + Field> employees; + Field> products = std::nullopt; + + template + void visitFields(V& v) { + v(name, "name", "Company name"); + v(mission, "mission", "Mission statement"); + v(revenue, "revenue", "Yearly revenue"); + v(ceo, "ceo", "CEO"); + v(employees, "employees", "List of employees"); + v(products, "products", "List of products"); + } +}; + +Company makeAc() { + return { + .name = "Alpaca Core", + .ceo = Person {.name = "John Doe", .age = 42 }, + .employees = std::vector { + Person {.name = "Alice", .age = 25 }, + Person {.name = "Bob", .age = 30 }, + Person {.name = "Charlie" } + }, + .products = std::vector {"ac-local", "acord", "ilib-foo"}, + }; +} + +void checkAc(const Company& c) { + CHECK(c.name == "Alpaca Core"); + CHECK_FALSE(c.mission.hasValue()); + CHECK(c.revenue == 0.f); + CHECK(c.ceo->name == "John Doe"); + CHECK(c.ceo->age == 42); + CHECK(c.employees->size() == 3); + CHECK(c.employees->at(0).name == "Alice"); + CHECK(c.employees->at(0).age == 25); + CHECK(c.employees->at(1).name == "Bob"); + CHECK(c.employees->at(1).age == 30); + CHECK(c.employees->at(2).name == "Charlie"); + CHECK(c.employees->at(2).age == 0); + CHECK(c.products->size() == 3); + CHECK(c.products->at(0) == "ac-local"); + CHECK(c.products->at(1) == "acord"); + CHECK(c.products->at(2) == "ilib-foo"); +} + +TEST_CASE("io visitors") { + ac::Dict dict; + { + auto c = makeAc(); + checkAc(c); + dict = Struct_toDict(std::move(c)); + } + + auto cc = Struct_fromDict(std::move(dict)); + checkAc(cc); +} \ No newline at end of file