Skip to content

Commit

Permalink
feat(schema): io visitors, ref #149
Browse files Browse the repository at this point in the history
  • Loading branch information
iboB committed Nov 29, 2024
1 parent 3e65810 commit 2058d9b
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 20 deletions.
56 changes: 36 additions & 20 deletions schema/code/ac/schema/IOVisitors.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// SPDX-License-Identifier: MIT
//
#pragma once
#include "Field.hpp"
#include <ac/Dict.hpp>
#include <astl/throw_stdex.hpp>
#include <astl/move.hpp>
Expand All @@ -13,13 +14,13 @@ namespace ac::local::schema {
namespace impl {
struct DummyVisitor {
template <typename T>
void field(T& value, std::string_view name, std::string_view desc, bool required);
void operator()(Field<T>& field, std::string_view name, std::string_view desc);
};
} // namespace impl

template <typename T>
concept Visitable = requires(T t) {
t.visitFields(std::declval<impl::DummyVisitor>());
t.visitFields(std::declval<impl::DummyVisitor&>());
};

struct ToDictVisitor {
Expand All @@ -41,41 +42,45 @@ struct ToDictVisitor {
static void writeToDict(Dict& out, std::vector<T>& value) {
out = Dict::array();
for (auto& v : value) {
vriteToDict(out.emplace_back(), v);
writeToDict(out.emplace_back(), v);
}
}

template <typename T>
void field(T& value, std::string_view name, std::string_view, bool) {
writeToDict(out[name], value);
}

template <typename T>
void field(std::optional<T>& value, std::string_view name, std::string_view desc, bool required) {
if (!value) {
if (required) {
void operator()(Field<T>& 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 <typename T>
Dict Struct_toDict(T&& s) {
Dict ret;
ToDictVisitor v(ret);
s.visitFields(v);
return ret;
}

struct FromDictVisitor {
Dict& in;
FromDictVisitor(Dict& in) : in(in) {};

template <Visitable T>
static void readFromDict(Dict& in, T& value) {
FromDictVisitor sub(in);
value.visitFields(value);
value.visitFields(sub);
}

template <typename T>
static void readFromDict(Dict& in, T& value) {
value = in.get<value>();
value = in.get<T>();
}

static void readFromDict(Dict& in, std::string& value) {
Expand All @@ -85,21 +90,32 @@ struct FromDictVisitor {
template <typename T>
static void readFromDict(Dict& in, std::vector<T>& 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 <typename T>
void operator()(Field<T>& 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 <typename T>
T Struct_fromDict(Dict&& d) {
T ret;
FromDictVisitor v(d);
ret.visitFields(v);
return ret;
}

} // namespace ac::local::schema
1 change: 1 addition & 0 deletions schema/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ macro(schema_test test)
endmacro()

schema_test(TupleIndexByItemId)
schema_test(Field)
schema_test(DispatchHelpers)
schema_test(visitors)
77 changes: 77 additions & 0 deletions schema/test/t-visitors.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,80 @@
// SPDX-License-Identifier: MIT
//
#include <ac/schema/IOVisitors.hpp>
#include <doctest/doctest.h>

using namespace ac::local::schema;

struct Person {
Field<std::string> name;
Field<int> age = Default(0);

template <typename V>
void visitFields(V& v) {
v(name, "name", "Name of the person");
v(age, "age", "Age of the person");
}
};

struct Company {
Field<std::string> name;
Field<std::string> mission = std::nullopt;
Field<float> revenue = Default(0.f);
Field<Person> ceo;
Field<std::vector<Person>> employees;
Field<std::vector<std::string>> products = std::nullopt;

template <typename V>
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<std::string> {"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<Company>(std::move(dict));
checkAc(cc);
}

0 comments on commit 2058d9b

Please sign in to comment.