feat(reflect-cpp): Switched from glaze -> reflect cpp

A bug was discovered in glaze which prevented valid toml output. We have
switched to toml++ and reflect-cpp. The interface has remained the same
so this should not break any code
This commit is contained in:
2025-12-06 10:55:46 -05:00
parent 2b5abeae58
commit ec13264050
365 changed files with 63946 additions and 357 deletions

View File

@@ -0,0 +1,16 @@
#ifndef RFL_PARSING_AREREADERANDWRITER_HPP_
#define RFL_PARSING_AREREADERANDWRITER_HPP_
#include "IsReader.hpp"
#include "IsWriter.hpp"
namespace rfl {
namespace parsing {
template <class R, class W, class T>
concept AreReaderAndWriter = IsReader<R, T> && IsWriter<W, T>;
} // namespace parsing
} // namespace rfl
#endif

View File

@@ -0,0 +1,86 @@
#ifndef RFL_PARSING_ARRAYREADER_HPP_
#define RFL_PARSING_ARRAYREADER_HPP_
#include <array>
#include <optional>
#include <type_traits>
#include "../Result.hpp"
#include "../internal/is_array.hpp"
#include "Parser_base.hpp"
namespace rfl::parsing {
template <class R, class W, class ProcessorsType, class T, size_t _size>
class ArrayReader {
private:
using InputVarType = typename R::InputVarType;
static constexpr size_t size_ = _size;
public:
ArrayReader(const R* _r, std::array<T, _size>* _array)
: array_(_array), num_set_(0), r_(_r) {}
~ArrayReader() = default;
std::optional<Error> check_size() const {
if (num_set_ != size_) {
return Error("Expected " + std::to_string(size_) + " elements, got " +
std::to_string(num_set_) + ".");
}
return std::nullopt;
}
size_t num_set() const { return num_set_; }
std::optional<Error> read(const InputVarType& _var) const {
if (num_set_ == size_) {
return Error("Expected " + std::to_string(size_) +
" elements, got at least " + std::to_string(size_ + 1) +
".");
}
auto res =
Parser<R, W, std::remove_cvref_t<T>, ProcessorsType>::read(*r_, _var);
if (res) {
move_to(&((*array_)[num_set_]), &(*res));
} else {
return Error("Failed to parse element " + std::to_string(num_set_) +
": " + res.error().what());
}
++num_set_;
return std::optional<Error>();
}
private:
template <class Target, class Source>
void move_to(Target* _t, Source* _s) const {
if constexpr (std::is_const_v<Target>) {
return move_to(const_cast<std::remove_const_t<Target>*>(_t), _s);
} else if constexpr (!internal::is_array_v<Source> &&
!std::is_array_v<Target>) {
::new (_t) Target(std::move(*_s));
} else if constexpr (internal::is_array_v<Source>) {
for (size_t i = 0; i < _s->arr_.size(); ++i) {
move_to(&((*_t)[i]), &(_s->arr_[i]));
}
} else {
for (size_t i = 0; i < _s->size(); ++i) {
move_to(&((*_t)[i]), &((*_s)[i]));
}
}
}
private:
/// The underlying array.
std::array<T, size_>* array_;
/// Indicates the current field
mutable size_t num_set_;
/// The underlying reader.
const R* r_;
};
} // namespace rfl::parsing
#endif

View File

@@ -0,0 +1,57 @@
#ifndef RFL_PARSING_CUSTOMPARSER_HPP_
#define RFL_PARSING_CUSTOMPARSER_HPP_
#include <exception>
#include "../Tuple.hpp"
#include "../internal/has_to_class_method_v.hpp"
#include "../internal/to_ptr_field_tuple.hpp"
#include "Parser.hpp"
#include "schema/Type.hpp"
namespace rfl {
namespace parsing {
template <class R, class W, class ProcessorsType, class OriginalClass,
class HelperStruct>
struct CustomParser {
using CustomParserHelperStruct = std::remove_cvref_t<HelperStruct>;
static Result<OriginalClass> read(const R& _r, const auto& _var) noexcept {
const auto to_class = [](auto&& _h) -> Result<OriginalClass> {
try {
if constexpr (internal::has_to_class_method_v<HelperStruct>) {
return _h.to_class();
} else {
auto ptr_field_tuple = internal::to_ptr_field_tuple(_h);
const auto class_from_ptrs = [](auto&... _ptrs) {
return OriginalClass(std::move(*_ptrs.value_)...);
};
return rfl::apply(class_from_ptrs, ptr_field_tuple);
}
} catch (std::exception& e) {
return error(e.what());
}
};
return Parser<R, W, CustomParserHelperStruct, ProcessorsType>::read(_r,
_var)
.and_then(to_class);
}
template <class P>
static auto write(const W& _w, const OriginalClass& _p, const P& _parent) {
Parser<R, W, CustomParserHelperStruct, ProcessorsType>::write(
_w, HelperStruct::from_class(_p), _parent);
}
static schema::Type to_schema(
std::map<std::string, schema::Type>* _definitions) {
return Parser<R, W, CustomParserHelperStruct, ProcessorsType>::to_schema(
_definitions);
}
};
} // namespace parsing
} // namespace rfl
#endif

View File

@@ -0,0 +1,84 @@
#ifndef RFL_PARSING_FIELD_VARIANT_PARSER_HPP_
#define RFL_PARSING_FIELD_VARIANT_PARSER_HPP_
#include <map>
#include <string>
#include <type_traits>
#include "../Result.hpp"
#include "../Tuple.hpp"
#include "../Variant.hpp"
#include "../always_false.hpp"
#include "../visit.hpp"
#include "FieldVariantReader.hpp"
#include "Parser_base.hpp"
#include "schema/Type.hpp"
namespace rfl {
namespace parsing {
/// To be used when all options of the variants are rfl::Field. Essentially,
/// this is an externally tagged union.
template <class R, class W, class ProcessorsType, class... FieldTypes>
requires AreReaderAndWriter<R, W, rfl::Variant<FieldTypes...>>
struct FieldVariantParser {
using FieldVariantType = rfl::Variant<FieldTypes...>;
using ResultType = Result<FieldVariantType>;
public:
using InputObjectType = typename R::InputObjectType;
using InputVarType = typename R::InputVarType;
static ResultType read(const R& _r, const InputVarType& _var) noexcept {
static_assert(
internal::no_duplicate_field_names<rfl::Tuple<FieldTypes...>>(),
"Externally tagged variants cannot have duplicate field "
"names.");
const auto to_result = [&](const auto _obj) -> ResultType {
auto field_variant = std::optional<Result<FieldVariantType>>();
const auto reader =
FieldVariantReader<R, W, ProcessorsType, FieldTypes...>(
&_r, &field_variant);
auto err = _r.read_object(reader, _obj);
if (err) {
return error(*err);
}
if (!field_variant) {
return error(
"Could not parse: Expected the object to have "
"exactly one field, but found more than one.");
}
return std::move(*field_variant);
};
return _r.to_object(_var).and_then(to_result);
}
template <class P>
static void write(const W& _w, const rfl::Variant<FieldTypes...>& _v,
const P& _parent) {
static_assert(
internal::no_duplicate_field_names<rfl::Tuple<FieldTypes...>>(),
"Externally tagged variants cannot have duplicate field "
"names.");
_v.visit([&](const auto& _field) {
const auto named_tuple = make_named_tuple(internal::to_ptr_field(_field));
using NamedTupleType = std::remove_cvref_t<decltype(named_tuple)>;
Parser<R, W, NamedTupleType, ProcessorsType>::write(_w, named_tuple,
_parent);
});
}
static schema::Type to_schema(
std::map<std::string, schema::Type>* _definitions,
[[maybe_unused]] std::vector<schema::Type> _types = {}) {
using VariantType = rfl::Variant<NamedTuple<FieldTypes>...>;
return Parser<R, W, VariantType, ProcessorsType>::to_schema(_definitions);
}
};
} // namespace parsing
} // namespace rfl
#endif

View File

@@ -0,0 +1,87 @@
#ifndef RFL_PARSING_FIELDVARIANTREADER_HPP_
#define RFL_PARSING_FIELDVARIANTREADER_HPP_
#include <optional>
#include <sstream>
#include <string_view>
#include <type_traits>
#include <utility>
#include "../Result.hpp"
#include "../Variant.hpp"
#include "../internal/is_array.hpp"
#include "../internal/nth_element_t.hpp"
#include "Parser_base.hpp"
namespace rfl::parsing {
template <class R, class W, class ProcessorsType, class... FieldTypes>
class FieldVariantReader {
private:
using InputVarType = typename R::InputVarType;
using FieldVariantType = rfl::Variant<FieldTypes...>;
using ResultType = Result<FieldVariantType>;
public:
FieldVariantReader(const R* _r,
std::optional<Result<FieldVariantType>>* _field_variant)
: r_(_r), field_variant_(_field_variant) {}
~FieldVariantReader() = default;
void read(const std::string_view& _disc_value,
const InputVarType& _var) const noexcept {
try_matching_fields(
_disc_value, _var,
std::make_integer_sequence<int, sizeof...(FieldTypes)>());
if (!*field_variant_) {
std::stringstream stream;
stream << "Could not parse rfl::Variant, could not match field named "
"'"
<< _disc_value << "'.";
*field_variant_ = error(stream.str());
}
}
private:
template <int... _is>
void try_matching_fields(const std::string_view& _disc_value,
const InputVarType& _var,
std::integer_sequence<int, _is...>) const noexcept {
(try_one<_is>(_disc_value, _var), ...);
}
template <int _i>
void try_one(const std::string_view& _disc_value,
const InputVarType& _var) const noexcept {
using FieldType =
std::remove_cvref_t<internal::nth_element_t<_i, FieldTypes...>>;
using ValueType = std::remove_cvref_t<typename FieldType::Type>;
constexpr auto key = FieldType::name_.string_view();
if (key == _disc_value) {
const auto to_variant = [](ValueType&& _val) {
return rfl::Variant<FieldTypes...>(FieldType(std::move(_val)));
};
const auto embellish_error = [&](const Error& _e) {
std::stringstream stream;
stream << "Could not parse rfl::Variant with field '"
<< std::string(_disc_value) << "': " << _e.what();
return Error(stream.str());
};
*field_variant_ = Parser<R, W, ValueType, ProcessorsType>::read(*r_, _var)
.transform(to_variant)
.transform_error(embellish_error);
}
}
private:
/// The underlying reader.
const R* r_;
/// The underlying field variant.
std::optional<Result<FieldVariantType>>* field_variant_;
};
} // namespace rfl::parsing
#endif

View File

@@ -0,0 +1,91 @@
#ifndef RFL_PARSING_ISREADER_HPP_
#define RFL_PARSING_ISREADER_HPP_
#include <concepts>
#include <cstdint>
#include <functional>
#include <optional>
#include <string>
#include <string_view>
#include "../Result.hpp"
#include "../internal/is_basic_type.hpp"
#include "../internal/wrap_in_rfl_array_t.hpp"
#include "SupportsTaggedUnions.hpp"
#include "schemaful/IsSchemafulReader.hpp"
namespace rfl {
namespace parsing {
template <class R>
struct MockArrayReader {
std::optional<Error> read(typename R::InputVarType&) const {
return std::nullopt;
}
};
template <class R>
struct MockObjectReader {
void read(const std::string_view&, typename R::InputVarType&) const {}
};
template <class R, class T>
concept IsReader =
(SupportsTaggedUnions<R> || schemaful::IsSchemafulReader<R>) &&
requires(R r, std::string name,
std::function<std::int16_t(std::string_view)> fct,
MockArrayReader<R> array_reader, MockObjectReader<R> object_reader,
typename R::InputArrayType arr, typename R::InputObjectType obj,
typename R::InputVarType var, size_t idx) {
/// Any Reader needs to define the following:
///
/// 1) An InputArrayType, which must be an array-like data structure.
/// 2) An InputObjectType, which must contain key-value pairs.
/// 3) An InputVarType, which must be able to represent either
/// InputArrayType, InputObjectType or a basic type (bool, integral,
/// floating point, std::string).
/// 4) A static constexpr bool has_custom_constructor, that determines
/// whether the class in question as a custom constructor, which might
/// be called something like from_json_obj(...).
/// Determines whether a variable is empty (the NULL type).
{ r.is_empty(var) } -> std::same_as<bool>;
/// Iterates through an array and writes the contained vars into
/// an array reader.
{ r.read_array(array_reader, arr) } -> std::same_as<std::optional<Error>>;
/// Iterates through an object and writes the key-value pairs into an
/// object reader. This is what we use to handle structs and named tuples,
/// making it a very important function.
{
r.read_object(object_reader, obj)
} -> std::same_as<std::optional<Error>>;
/// Transforms var to a basic type (bool, integral,
/// floating point, std::string)
{
r.template to_basic_type<internal::wrap_in_rfl_array_t<T>>(var)
} -> std::same_as<rfl::Result<internal::wrap_in_rfl_array_t<T>>>;
/// Casts var as an InputArrayType.
{
r.to_array(var)
} -> std::same_as<rfl::Result<typename R::InputArrayType>>;
/// Casts var as an InputObjectType.
{
r.to_object(var)
} -> std::same_as<rfl::Result<typename R::InputObjectType>>;
/// Uses the custom constructor, if it has been determined that T has one
/// (see above).
{
r.template use_custom_constructor<internal::wrap_in_rfl_array_t<T>>(var)
} -> std::same_as<rfl::Result<internal::wrap_in_rfl_array_t<T>>>;
};
} // namespace parsing
} // namespace rfl
#endif

View File

@@ -0,0 +1,102 @@
#ifndef RFL_PARSING_ISWRITER_HPP_
#define RFL_PARSING_ISWRITER_HPP_
#include <concepts>
#include <string>
#include <string_view>
#include "../Result.hpp"
namespace rfl {
namespace parsing {
template <class W, class T>
concept IsWriter = requires(W w, T t, std::string_view name,
std::string basic_value,
typename W::OutputArrayType arr,
typename W::OutputObjectType obj,
typename W::OutputVarType var, size_t size) {
/// Sets an empty array as the root element of the document.
/// Some serialization formats require you to pass the expected size in
/// advance. If you are not working with such a format, you can ignore the
/// parameter `size`. Returns the new array for further modification.
{ w.array_as_root(size) } -> std::same_as<typename W::OutputArrayType>;
/// Sets an empty object as the root element of the document.
/// Some serialization formats require you to pass the expected size in
/// advance. If you are not working with such a format, you can ignore the
/// parameter `size`.
/// Returns the new object for further modification.
{ w.object_as_root(size) } -> std::same_as<typename W::OutputObjectType>;
/// Sets a null as the root element of the document. Returns OutputVarType
/// containing the null value.
{ w.null_as_root() } -> std::same_as<typename W::OutputVarType>;
/// Sets a basic value (bool, numeric, string) as the root element of the
/// document. Returns an OutputVarType containing the new value.
{ w.value_as_root(basic_value) } -> std::same_as<typename W::OutputVarType>;
/// Adds an empty array to an existing array. Returns the new
/// array for further modification.
{
w.add_array_to_array(size, &arr)
} -> std::same_as<typename W::OutputArrayType>;
/// Adds an empty object to an existing array. Returns the new
/// object for further modification.
{
w.add_object_to_array(size, &arr)
} -> std::same_as<typename W::OutputObjectType>;
/// Adds an empty array to an existing object. The key or name of the field is
/// signified by `name`. Returns the new array for further modification.
{
w.add_array_to_object(name, size, &obj)
} -> std::same_as<typename W::OutputArrayType>;
/// Adds an empty object to an existing object. The key or name of the field
/// is signified by `name`. Returns the new object for further modification.
{
w.add_object_to_object(name, size, &obj)
} -> std::same_as<typename W::OutputObjectType>;
/// Adds a basic value (bool, numeric, string) to an array. Returns an
/// OutputVarType containing the new value.
{
w.add_value_to_array(basic_value, &arr)
} -> std::same_as<typename W::OutputVarType>;
/// Adds a basic value (bool, numeric, string) to an existing object. The key
/// or name of the field is signified by `name`. Returns an
/// OutputVarType containing the new value.
{
w.add_value_to_object(name, basic_value, &obj)
} -> std::same_as<typename W::OutputVarType>;
/// Adds a null value to an array. Returns an
/// OutputVarType containing the null value.
{ w.add_null_to_array(&arr) } -> std::same_as<typename W::OutputVarType>;
/// Adds a null value to an existing object. The key
/// or name of the field is signified by `name`. Returns an
/// OutputVarType containing the null value.
{
w.add_null_to_object(name, &obj)
} -> std::same_as<typename W::OutputVarType>;
/// Signifies to the writer that we do not want to add any further elements to
/// this array. Some serialization formats require this. If you are working
/// with a serialization format that doesn't, just leave the function empty.
{ w.end_array(&arr) } -> std::same_as<void>;
/// Signifies to the writer that we do not want to add any further elements to
/// this object. Some serialization formats require this. If you are working
/// with a serialization format that doesn't, just leave the function empty.
{ w.end_object(&obj) } -> std::same_as<void>;
};
} // namespace parsing
} // namespace rfl
#endif

View File

@@ -0,0 +1,162 @@
#ifndef RFL_PARSING_MAPPARSER_HPP_
#define RFL_PARSING_MAPPARSER_HPP_
#include <map>
#include <string>
#include <type_traits>
#include "../Ref.hpp"
#include "../Result.hpp"
#include "../always_false.hpp"
#include "MapReader.hpp"
#include "Parent.hpp"
#include "Parser_base.hpp"
#include "schema/Type.hpp"
#include "schemaful/IsSchemafulReader.hpp"
#include "schemaful/IsSchemafulWriter.hpp"
#include "to_single_error_message.hpp"
namespace rfl {
namespace parsing {
template <class R, class W, class MapType, class ProcessorsType>
requires AreReaderAndWriter<R, W, MapType>
struct MapParser {
public:
using InputObjectType = typename R::InputObjectType;
using InputVarType = typename R::InputVarType;
using KeyType = std::remove_cvref_t<typename MapType::value_type::first_type>;
using ValueType =
std::remove_cvref_t<typename MapType::value_type::second_type>;
using ParentType = Parent<W>;
static Result<MapType> read(const R& _r, const InputVarType& _var) noexcept {
const auto to_map = [&](auto obj) -> Result<MapType> {
return make_map(_r, obj);
};
if constexpr (schemaful::IsSchemafulReader<R>) {
return _r.to_map(_var).and_then(to_map);
} else {
return _r.to_object(_var).and_then(to_map);
}
}
template <class P>
static void write(const W& _w, const MapType& _m, const P& _parent) {
if constexpr (schemaful::IsSchemafulWriter<W>) {
write_map(_w, _m, _parent);
} else {
write_object(_w, _m, _parent);
}
}
static schema::Type to_schema(
std::map<std::string, schema::Type>* _definitions) {
return schema::Type{schema::Type::StringMap{Ref<schema::Type>::make(
Parser<R, W, ValueType, ProcessorsType>::to_schema(_definitions))}};
}
private:
static Result<MapType> make_map(const R& _r, const auto& _obj_or_map) {
MapType map;
std::vector<Error> errors;
const auto map_reader =
MapReader<R, W, MapType, ProcessorsType>(&_r, &map, &errors);
if constexpr (schemaful::IsSchemafulReader<R>) {
const auto err = _r.read_map(map_reader, _obj_or_map);
if (err) {
return error(*err);
}
} else {
const auto err = _r.read_object(map_reader, _obj_or_map);
if (err) {
return error(*err);
}
}
if (errors.size() != 0) {
return error(to_single_error_message(errors));
}
return map;
}
template <class P>
static void write_map(const W& _w, const MapType& _m, const P& _parent) {
auto m = ParentType::add_map(_w, _m.size(), _parent);
using ParentMapType =
typename ParentType::template Map<typename W::OutputMapType>;
for (const auto& [k, v] : _m) {
if constexpr (internal::has_reflection_type_v<KeyType>) {
using ReflT = typename KeyType::ReflectionType;
if constexpr (std::is_integral_v<ReflT> ||
std::is_floating_point_v<ReflT>) {
const auto name = std::to_string(k.reflection());
const auto new_parent = ParentMapType{name, &m};
Parser<R, W, std::remove_cvref_t<ValueType>, ProcessorsType>::write(
_w, v, new_parent);
} else {
const auto name = k.reflection();
const auto new_parent = ParentMapType{name, &m};
Parser<R, W, std::remove_cvref_t<ValueType>, ProcessorsType>::write(
_w, v, new_parent);
}
} else if constexpr (std::is_integral_v<KeyType> ||
std::is_floating_point_v<KeyType>) {
const auto name = std::to_string(k);
const auto new_parent = ParentMapType{name, &m};
Parser<R, W, std::remove_cvref_t<ValueType>, ProcessorsType>::write(
_w, v, new_parent);
} else {
const auto new_parent = ParentMapType{k, &m};
Parser<R, W, std::remove_cvref_t<ValueType>, ProcessorsType>::write(
_w, v, new_parent);
}
}
_w.end_map(&m);
}
template <class P>
static void write_object(const W& _w, const MapType& _m, const P& _parent) {
auto obj = ParentType::add_object(_w, _m.size(), _parent);
for (const auto& [k, v] : _m) {
if constexpr (internal::has_reflection_type_v<KeyType>) {
using ReflT = typename KeyType::ReflectionType;
if constexpr (std::is_integral_v<ReflT> ||
std::is_floating_point_v<ReflT>) {
const auto name = std::to_string(k.reflection());
const auto new_parent = typename ParentType::Object{name, &obj};
Parser<R, W, std::remove_cvref_t<ValueType>, ProcessorsType>::write(
_w, v, new_parent);
} else {
const auto name = k.reflection();
const auto new_parent = typename ParentType::Object{name, &obj};
Parser<R, W, std::remove_cvref_t<ValueType>, ProcessorsType>::write(
_w, v, new_parent);
}
} else if constexpr (std::is_integral_v<KeyType> ||
std::is_floating_point_v<KeyType>) {
const auto name = std::to_string(k);
const auto new_parent = typename ParentType::Object{name, &obj};
Parser<R, W, std::remove_cvref_t<ValueType>, ProcessorsType>::write(
_w, v, new_parent);
} else {
const auto new_parent = typename ParentType::Object{k, &obj};
Parser<R, W, std::remove_cvref_t<ValueType>, ProcessorsType>::write(
_w, v, new_parent);
}
}
_w.end_object(&obj);
}
};
} // namespace parsing
} // namespace rfl
#endif

View File

@@ -0,0 +1,117 @@
#ifndef RFL_PARSING_MAPREADER_HPP_
#define RFL_PARSING_MAPREADER_HPP_
#include <string>
#include <string_view>
#include <type_traits>
#include <utility>
#include <vector>
#include "../Result.hpp"
#include "../always_false.hpp"
#include "../internal/has_reflection_type_v.hpp"
#include "Parser_base.hpp"
namespace rfl::parsing {
template <class R, class W, class MapType, class ProcessorsType>
class MapReader {
private:
using InputVarType = typename R::InputVarType;
using KeyType = std::remove_cvref_t<typename MapType::value_type::first_type>;
using ValueType =
std::remove_cvref_t<typename MapType::value_type::second_type>;
public:
MapReader(const R* _r, MapType* _map, std::vector<Error>* _errors)
: r_(_r), map_(_map), errors_(_errors) {}
~MapReader() = default;
void read(const std::string_view& _name,
const InputVarType& _var) const noexcept {
auto res = get_pair(_name, _var);
if (res) {
map_->emplace(std::move(*res));
} else {
errors_->push_back(Error("Failed to parse field '" + std::string(_name) +
"': " + res.error().what()));
}
}
private:
template <class T>
Result<T> key_to_numeric(auto& _pair) const noexcept {
try {
if constexpr (std::is_integral_v<T> && std::is_signed_v<T>) {
return static_cast<T>(std::stoll(_pair.first));
} else if constexpr (std::is_integral_v<T> && std::is_unsigned_v<T>) {
return static_cast<T>(std::stoull(_pair.first));
} else if constexpr (std::is_floating_point_v<T>) {
return static_cast<T>(std::stod(_pair.first));
} else {
static_assert(always_false_v<T>, "Unsupported type");
}
} catch (std::exception& e) {
return error(e.what());
}
}
Result<std::pair<KeyType, ValueType>> make_key(auto&& _pair) const noexcept {
static_assert(std::is_rvalue_reference_v<decltype(_pair)>,
"Expected an rvalue");
const auto to_pair =
[&](auto&& _key) -> Result<std::pair<KeyType, ValueType>> {
try {
using K = decltype(_key);
return std::make_pair(KeyType(std::forward<K>(_key)),
std::move(_pair.second));
} catch (std::exception& e) {
return error(e.what());
}
};
if constexpr (std::is_integral_v<KeyType> ||
std::is_floating_point_v<KeyType>) {
return key_to_numeric<KeyType>(_pair).and_then(to_pair);
} else if constexpr (internal::has_reflection_type_v<KeyType>) {
using ReflT = typename KeyType::ReflectionType;
if constexpr (std::is_integral_v<ReflT> ||
std::is_floating_point_v<ReflT>) {
return key_to_numeric<ReflT>(_pair).and_then(to_pair);
} else {
return to_pair(std::move(_pair.first));
}
} else {
return std::move(_pair);
}
}
Result<std::pair<KeyType, ValueType>> get_pair(
const std::string_view& _name, const InputVarType& _var) const noexcept {
const auto to_pair = [&](ValueType&& _val) {
auto pair = std::make_pair(std::string(_name), std::move(_val));
return make_key(std::move(pair));
};
return Parser<R, W, std::remove_cvref_t<ValueType>, ProcessorsType>::read(
*r_, _var)
.and_then(to_pair);
}
private:
/// The underlying reader.
const R* r_;
/// The underlying map.
MapType* map_;
/// Collects any errors we may have come across.
std::vector<Error>* errors_;
};
} // namespace rfl::parsing
#endif

View File

@@ -0,0 +1,361 @@
#ifndef RFL_PARSING_NAMEDTUPLEPARSER_HPP_
#define RFL_PARSING_NAMEDTUPLEPARSER_HPP_
#include <array>
#include <map>
#include <sstream>
#include <type_traits>
#include <utility>
#include "../NamedTuple.hpp"
#include "../Result.hpp"
#include "../always_false.hpp"
#include "../internal/has_default_val_v.hpp"
#include "../internal/is_array.hpp"
#include "../internal/is_attribute.hpp"
#include "../internal/is_basic_type.hpp"
#include "../internal/is_default_val_v.hpp"
#include "../internal/is_extra_fields.hpp"
#include "../internal/is_skip.hpp"
#include "../internal/no_duplicate_field_names.hpp"
#include "../internal/nth_element_t.hpp"
#include "../internal/ptr_cast.hpp"
#include "../to_view.hpp"
#include "AreReaderAndWriter.hpp"
#include "Parent.hpp"
#include "Parser_base.hpp"
#include "ViewReader.hpp"
#include "ViewReaderWithDefault.hpp"
#include "ViewReaderWithDefaultAndStrippedFieldNames.hpp"
#include "ViewReaderWithStrippedFieldNames.hpp"
#include "call_destructors_where_necessary.hpp"
#include "is_empty.hpp"
#include "is_required.hpp"
#include "schema/Type.hpp"
#include "to_single_error_message.hpp"
namespace rfl {
namespace parsing {
template <class R, class W, bool _ignore_empty_containers, bool _all_required,
bool _no_field_names, class ProcessorsType, class... FieldTypes>
requires AreReaderAndWriter<R, W, NamedTuple<FieldTypes...>>
struct NamedTupleParser {
using InputVarType = typename R::InputVarType;
using ParentType = Parent<W>;
using NamedTupleType = NamedTuple<FieldTypes...>;
using ViewReaderType = std::conditional_t<
_no_field_names,
ViewReaderWithStrippedFieldNames<R, W, NamedTupleType, ProcessorsType>,
ViewReader<R, W, NamedTupleType, ProcessorsType>>;
using ViewReaderWithDefaultType = std::conditional_t<
_no_field_names,
ViewReaderWithDefaultAndStrippedFieldNames<R, W, NamedTupleType,
ProcessorsType>,
ViewReaderWithDefault<R, W, NamedTupleType, ProcessorsType>>;
using InputObjectOrArrayType =
std::conditional_t<_no_field_names, typename R::InputArrayType,
typename R::InputObjectType>;
using OutputObjectOrArrayType =
std::conditional_t<_no_field_names, typename W::OutputArrayType,
typename W::OutputObjectType>;
using SchemaType = std::conditional_t<_no_field_names, schema::Type::Tuple,
schema::Type::Object>;
static constexpr size_t size_ = NamedTupleType::size();
static_assert(NamedTupleType::pos_extra_fields() == -1 || !_no_field_names,
"You cannot use the rfl::NoFieldNames processor if you are "
"including rfl::ExtraFields.");
public:
/// The way this works is that we allocate space on the stack in this size of
/// the named tuple in which we then write the individual fields using
/// views and placement new. This is how we deal with the fact that some
/// fields might not be default-constructible.
static Result<NamedTuple<FieldTypes...>> read(
const R& _r, const InputVarType& _var) noexcept {
static_assert(
internal::no_duplicate_field_names<typename NamedTupleType::Fields>());
alignas(NamedTuple<FieldTypes...>) unsigned char
buf[sizeof(NamedTuple<FieldTypes...>)];
auto ptr = internal::ptr_cast<NamedTuple<FieldTypes...>*>(&buf);
auto view = rfl::to_view(*ptr);
using ViewType = std::remove_cvref_t<decltype(view)>;
const auto [set, err] =
Parser<R, W, ViewType, ProcessorsType>::read_view(_r, _var, &view);
if (err) [[unlikely]] {
call_destructors_where_necessary(set, &view);
return error(*err);
}
auto res = Result<NamedTuple<FieldTypes...>>(std::move(*ptr));
call_destructors_where_necessary(set, &view);
return res;
}
/// Reads the data into a view assuming no default values.
static std::pair<std::array<bool, NamedTupleType::size()>,
std::optional<Error>>
read_view(const R& _r, const InputVarType& _var,
NamedTuple<FieldTypes...>* _view) noexcept {
static_assert(
internal::no_duplicate_field_names<typename NamedTupleType::Fields>());
if constexpr (_no_field_names) {
auto arr = _r.to_array(_var);
if (!arr) [[unlikely]] {
auto set = std::array<bool, NamedTupleType::size()>{};
return std::make_pair(set, arr.error());
}
return read_object_or_array(_r, *arr, _view);
} else {
auto obj = _r.to_object(_var);
if (!obj) [[unlikely]] {
auto set = std::array<bool, NamedTupleType::size()>{};
return std::make_pair(set, obj.error());
}
return read_object_or_array(_r, *obj, _view);
}
}
/// Reads the data into a view assuming default values.
static std::optional<Error> read_view_with_default(
const R& _r, const InputVarType& _var,
NamedTuple<FieldTypes...>* _view) noexcept {
static_assert(
internal::no_duplicate_field_names<typename NamedTupleType::Fields>());
if constexpr (_no_field_names) {
auto arr = _r.to_array(_var);
if (!arr) [[unlikely]] {
return arr.error();
}
return read_object_or_array_with_default(_r, *arr, _view);
} else {
auto obj = _r.to_object(_var);
if (!obj) [[unlikely]] {
return obj.error();
}
return read_object_or_array_with_default(_r, *obj, _view);
}
}
template <class P>
static void write(const W& _w, const NamedTuple<FieldTypes...>& _tup,
const P& _parent) {
if constexpr (_no_field_names) {
auto arr = ParentType::add_array(_w, _tup.num_fields(), _parent);
build_object(_w, _tup, &arr, std::make_integer_sequence<int, size_>());
_w.end_array(&arr);
} else {
auto obj = ParentType::add_object(_w, _tup.num_fields(), _parent);
build_object(_w, _tup, &obj, std::make_integer_sequence<int, size_>());
_w.end_object(&obj);
}
}
static schema::Type to_schema(
std::map<std::string, schema::Type>* _definitions) noexcept {
SchemaType schema;
build_schema(_definitions, &schema,
std::make_integer_sequence<int, size_>());
return schema::Type{schema};
}
private:
template <int _i>
static void add_field_to_object(const W& _w,
const NamedTuple<FieldTypes...>& _tup,
OutputObjectOrArrayType* _ptr) {
using FieldType = internal::nth_element_t<_i, FieldTypes...>;
using ValueType = std::remove_cvref_t<typename FieldType::Type>;
const auto value = rfl::get<_i>(_tup);
if constexpr (internal::is_extra_fields_v<ValueType>) {
for (const auto& [k, v] : *value) {
const auto new_parent = make_parent(k, _ptr);
Parser<R, W, std::remove_cvref_t<decltype(v)>, ProcessorsType>::write(
_w, v, new_parent);
}
} else if constexpr (!_all_required && !_no_field_names &&
!is_required<ValueType, _ignore_empty_containers>()) {
constexpr auto name = FieldType::name_.string_view();
const auto new_parent = make_parent(name, _ptr);
if (!is_empty(value)) {
if constexpr (internal::is_attribute_v<ValueType>) {
Parser<R, W, ValueType, ProcessorsType>::write(
_w, value, new_parent.as_attribute());
} else {
Parser<R, W, ValueType, ProcessorsType>::write(_w, value, new_parent);
}
}
} else {
constexpr auto name = FieldType::name_.string_view();
const auto new_parent = make_parent(name, _ptr);
if constexpr (internal::is_attribute_v<ValueType>) {
Parser<R, W, ValueType, ProcessorsType>::write(
_w, value, new_parent.as_attribute());
} else {
Parser<R, W, ValueType, ProcessorsType>::write(_w, value, new_parent);
}
}
}
template <size_t _i>
static void add_field_to_schema(
std::map<std::string, schema::Type>* _definitions,
SchemaType* _schema) noexcept {
using F = internal::nth_element_t<_i, FieldTypes...>;
using U = std::remove_cvref_t<typename F::Type>;
if constexpr (!internal::is_skip_v<U> && !internal::is_extra_fields_v<U>) {
auto s = Parser<R, W, U, ProcessorsType>::to_schema(_definitions);
if constexpr (_no_field_names) {
_schema->types_.emplace_back(std::move(s));
} else {
_schema->types_[std::string(F::name())] = std::move(s);
}
}
};
template <int... _is>
static void build_object(const W& _w, const NamedTuple<FieldTypes...>& _tup,
OutputObjectOrArrayType* _ptr,
std::integer_sequence<int, _is...>) {
(add_field_to_object<_is>(_w, _tup, _ptr), ...);
}
template <int... _is>
static void build_schema(std::map<std::string, schema::Type>* _definitions,
SchemaType* _schema,
std::integer_sequence<int, _is...>) noexcept {
(add_field_to_schema<_is>(_definitions, _schema), ...);
if constexpr (NamedTupleType::pos_extra_fields() != -1) {
using F = internal::nth_element_t<NamedTupleType::pos_extra_fields(),
FieldTypes...>;
using ExtraFieldsType = std::remove_cvref_t<typename F::Type>;
using U = std::remove_cvref_t<typename ExtraFieldsType::Type>;
_schema->additional_properties_ = std::make_shared<schema::Type>(
Parser<R, W, U, ProcessorsType>::to_schema(_definitions));
}
}
/// Generates error messages for when fields are missing.
template <int _i>
static void handle_one_missing_field(const std::array<bool, size_>& _found,
const NamedTupleType& _view,
std::array<bool, size_>* _set,
std::vector<Error>* _errors) noexcept {
using FieldType = internal::nth_element_t<_i, FieldTypes...>;
using ValueType = std::remove_reference_t<
std::remove_pointer_t<typename FieldType::Type>>;
if (!std::get<_i>(_found)) {
constexpr bool is_required_field =
!internal::is_default_val_v<ValueType> &&
!internal::is_extra_fields_v<ValueType> &&
(_all_required || is_required<ValueType, _ignore_empty_containers>());
if constexpr (is_required_field) {
constexpr auto current_name =
internal::nth_element_t<_i, FieldTypes...>::name();
std::stringstream stream;
stream << "Field named '" << std::string(current_name)
<< "' not found.";
_errors->emplace_back(Error(stream.str()));
} else if constexpr (!internal::has_default_val_v<NamedTupleType>) {
if constexpr (!std::is_const_v<ValueType>) {
::new (rfl::get<_i>(_view)) ValueType();
} else {
using NonConstT = std::remove_const_t<ValueType>;
::new (const_cast<NonConstT*>(rfl::get<_i>(_view))) NonConstT();
}
std::get<_i>(*_set) = true;
}
}
}
/// Generates error messages for when fields are missing.
template <int... _is>
static void handle_missing_fields(
const std::array<bool, size_>& _found, const NamedTupleType& _view,
std::array<bool, size_>* _set, std::vector<Error>* _errors,
std::integer_sequence<int, _is...>) noexcept {
(handle_one_missing_field<_is>(_found, _view, _set, _errors), ...);
}
static auto make_parent(const std::string_view& _name,
OutputObjectOrArrayType* _ptr) {
if constexpr (_no_field_names) {
return typename ParentType::Array{_ptr};
} else {
return typename ParentType::Object{_name, _ptr};
}
}
static std::pair<std::array<bool, NamedTupleType::size()>,
std::optional<Error>>
read_object_or_array(const R& _r, const InputObjectOrArrayType& _obj_or_arr,
NamedTupleType* _view) noexcept {
auto found = std::array<bool, NamedTupleType::size()>();
found.fill(false);
auto set = std::array<bool, NamedTupleType::size()>();
set.fill(false);
std::vector<Error> errors;
const auto reader = ViewReaderType(&_r, _view, &found, &set, &errors);
if constexpr (_no_field_names) {
const auto err = _r.read_array(reader, _obj_or_arr);
if (err) {
return std::make_pair(set, err);
}
} else {
const auto err = _r.read_object(reader, _obj_or_arr);
if (err) {
return std::make_pair(set, err);
}
}
handle_missing_fields(found, *_view, &set, &errors,
std::make_integer_sequence<int, size_>());
if (errors.size() != 0) {
return std::make_pair(set, to_single_error_message(errors));
}
return std::make_pair(set, std::optional<Error>());
}
static std::optional<Error> read_object_or_array_with_default(
const R& _r, const InputObjectOrArrayType& _obj_or_arr,
NamedTupleType* _view) noexcept {
std::vector<Error> errors;
const auto reader = ViewReaderWithDefaultType(&_r, _view, &errors);
if constexpr (_no_field_names) {
const auto err = _r.read_array(reader, _obj_or_arr);
if (err) {
return err;
}
} else {
const auto err = _r.read_object(reader, _obj_or_arr);
if (err) {
return err;
}
}
if constexpr (internal::has_default_val_v<NamedTupleType> &&
!ProcessorsType::default_if_missing_) {
handle_missing_fields(reader.found(), *_view, nullptr, &errors,
std::make_integer_sequence<int, size_>());
}
if (errors.size() != 0) {
return to_single_error_message(errors);
}
return std::nullopt;
}
};
} // namespace parsing
} // namespace rfl
#endif

View File

@@ -0,0 +1,221 @@
#ifndef RFL_PARSING_PARENT_HPP_
#define RFL_PARSING_PARENT_HPP_
#include <string_view>
#include <type_traits>
#include "../always_false.hpp"
#include "schemaful/IsSchemafulWriter.hpp"
#include "supports_attributes.hpp"
namespace rfl::parsing {
template <class W>
struct Parent {
using OutputArrayType = typename W::OutputArrayType;
using OutputObjectType = typename W::OutputObjectType;
using OutputVarType = typename W::OutputVarType;
struct Array {
OutputArrayType* arr_;
};
// For schemaful formats only.
template <class T>
struct Map {
std::string_view name_;
T* map_;
};
struct Object {
std::string_view name_;
OutputObjectType* obj_;
bool is_attribute_ = false;
Object as_attribute() const { return Object{name_, obj_, true}; }
};
// For schemaful formats only.
template <class T>
struct Union {
size_t index_;
T* union_;
};
struct Root {};
template <class ParentType>
static OutputArrayType add_array(const W& _w, const size_t _size,
const ParentType& _parent) {
using Type = std::remove_cvref_t<ParentType>;
if constexpr (std::is_same<Type, Array>()) {
return _w.add_array_to_array(_size, _parent.arr_);
} else if constexpr (std::is_same<Type, Object>()) {
return _w.add_array_to_object(_parent.name_, _size, _parent.obj_);
} else if constexpr (std::is_same<Type, Root>()) {
return _w.array_as_root(_size);
} else if constexpr (schemaful::IsSchemafulWriter<W>) {
if constexpr (std::is_same<Type, Map<typename W::OutputMapType>>()) {
return _w.add_array_to_map(_parent.name_, _size, _parent.map_);
} else if constexpr (std::is_same<Type,
Union<typename W::OutputUnionType>>()) {
return _w.add_array_to_union(_parent.index_, _size, _parent.union_);
} else {
static_assert(always_false_v<Type>, "Unsupported option.");
}
} else {
static_assert(always_false_v<Type>, "Unsupported option.");
}
}
// For schemaful formats only.
template <class ParentType>
static auto add_map(const W& _w, const size_t _size,
const ParentType& _parent) {
using Type = std::remove_cvref_t<ParentType>;
if constexpr (std::is_same<Type, Array>()) {
return _w.add_map_to_array(_size, _parent.arr_);
} else if constexpr (std::is_same<Type, Map<typename W::OutputMapType>>()) {
return _w.add_map_to_map(_parent.name_, _size, _parent.map_);
} else if constexpr (std::is_same<Type, Object>()) {
return _w.add_map_to_object(_parent.name_, _size, _parent.obj_);
} else if constexpr (std::is_same<Type,
Union<typename W::OutputUnionType>>()) {
return _w.add_map_to_union(_parent.index_, _size, _parent.union_);
} else if constexpr (std::is_same<Type, Root>()) {
return _w.map_as_root(_size);
} else {
static_assert(always_false_v<Type>, "Unsupported option.");
}
}
template <class ParentType>
static OutputObjectType add_object(const W& _w, const size_t _size,
const ParentType& _parent) {
using Type = std::remove_cvref_t<ParentType>;
if constexpr (std::is_same<Type, Array>()) {
return _w.add_object_to_array(_size, _parent.arr_);
} else if constexpr (std::is_same<Type, Object>()) {
return _w.add_object_to_object(_parent.name_, _size, _parent.obj_);
} else if constexpr (std::is_same<Type, Root>()) {
return _w.object_as_root(_size);
} else if constexpr (schemaful::IsSchemafulWriter<W>) {
if constexpr (std::is_same<Type, Map<typename W::OutputMapType>>()) {
return _w.add_object_to_map(_parent.name_, _size, _parent.map_);
} else if constexpr (std::is_same<Type,
Union<typename W::OutputUnionType>>()) {
return _w.add_object_to_union(_parent.index_, _size, _parent.union_);
} else {
static_assert(always_false_v<Type>, "Unsupported option.");
}
} else {
static_assert(always_false_v<Type>, "Unsupported option.");
}
}
template <class ParentType>
static OutputVarType add_null(const W& _w, const ParentType& _parent) {
using Type = std::remove_cvref_t<ParentType>;
if constexpr (std::is_same<Type, Array>()) {
return _w.add_null_to_array(_parent.arr_);
} else if constexpr (std::is_same<Type, Object>()) {
if constexpr (supports_attributes<std::remove_cvref_t<W>>) {
return _w.add_null_to_object(_parent.name_, _parent.obj_,
_parent.is_attribute_);
} else {
return _w.add_null_to_object(_parent.name_, _parent.obj_);
}
} else if constexpr (std::is_same<Type, Root>()) {
return _w.null_as_root();
} else if constexpr (schemaful::IsSchemafulWriter<W>) {
if constexpr (std::is_same<Type, Map<typename W::OutputMapType>>()) {
return _w.add_null_to_map(_parent.name_, _parent.map_);
} else if constexpr (std::is_same<Type,
Union<typename W::OutputUnionType>>()) {
return _w.add_null_to_union(_parent.index_, _parent.union_);
} else {
static_assert(always_false_v<Type>, "Unsupported option.");
}
} else {
static_assert(always_false_v<Type>, "Unsupported option.");
}
}
// For schemaful formats only.
template <class ParentType>
static auto add_union(const W& _w, const ParentType& _parent) {
using Type = std::remove_cvref_t<ParentType>;
if constexpr (std::is_same<Type, Array>()) {
return _w.add_union_to_array(_parent.arr_);
} else if constexpr (std::is_same<Type, Map<typename W::OutputMapType>>()) {
return _w.add_union_to_map(_parent.name_, _parent.map_);
} else if constexpr (std::is_same<Type, Object>()) {
return _w.add_union_to_object(_parent.name_, _parent.obj_);
} else if constexpr (std::is_same<Type,
Union<typename W::OutputUnionType>>()) {
return _w.add_union_to_union(_parent.index_, _parent.union_);
} else if constexpr (std::is_same<Type, Root>()) {
return _w.union_as_root();
} else {
static_assert(always_false_v<Type>, "Unsupported option.");
}
}
template <class ParentType, class T>
static OutputVarType add_value(const W& _w, const T& _var,
const ParentType& _parent) {
using Type = std::remove_cvref_t<ParentType>;
if constexpr (std::is_same<Type, Array>()) {
return _w.add_value_to_array(_var, _parent.arr_);
} else if constexpr (std::is_same<Type, Object>()) {
if constexpr (supports_attributes<std::remove_cvref_t<W>>) {
return _w.add_value_to_object(_parent.name_, _var, _parent.obj_,
_parent.is_attribute_);
} else {
return _w.add_value_to_object(_parent.name_, _var, _parent.obj_);
}
} else if constexpr (std::is_same<Type, Root>()) {
return _w.value_as_root(_var);
} else if constexpr (schemaful::IsSchemafulWriter<W>) {
if constexpr (std::is_same<Type, Map<typename W::OutputMapType>>()) {
return _w.add_value_to_map(_parent.name_, _var, _parent.map_);
} else if constexpr (std::is_same<Type,
Union<typename W::OutputUnionType>>()) {
return _w.add_value_to_union(_parent.index_, _var, _parent.union_);
} else {
static_assert(always_false_v<Type>, "Unsupported option.");
}
} else {
static_assert(always_false_v<Type>, "Unsupported option.");
}
}
};
} // namespace rfl::parsing
#endif

View File

@@ -0,0 +1,37 @@
#ifndef RFL_PARSING_PARSER_HPP_
#define RFL_PARSING_PARSER_HPP_
#include "Parser_array.hpp"
#include "Parser_base.hpp"
#include "Parser_box.hpp"
#include "Parser_bytestring.hpp"
#include "Parser_c_array.hpp"
#include "Parser_default.hpp"
#include "Parser_default_val.hpp"
#include "Parser_duration.hpp"
#include "Parser_filepath.hpp"
#include "Parser_map_like.hpp"
#include "Parser_named_tuple.hpp"
#include "Parser_optional.hpp"
#include "Parser_pair.hpp"
#include "Parser_ptr.hpp"
#include "Parser_ref.hpp"
#include "Parser_reference_wrapper.hpp"
#include "Parser_rename.hpp"
#include "Parser_result.hpp"
#include "Parser_rfl_array.hpp"
#include "Parser_rfl_tuple.hpp"
#include "Parser_rfl_variant.hpp"
#include "Parser_shared_ptr.hpp"
#include "Parser_skip.hpp"
#include "Parser_span.hpp"
#include "Parser_string_view.hpp"
#include "Parser_tagged_union.hpp"
#include "Parser_tuple.hpp"
#include "Parser_unique_ptr.hpp"
#include "Parser_variant.hpp"
#include "Parser_vector_like.hpp"
#include "Parser_vectorstring.hpp"
#include "Parser_wstring.hpp"
#endif

View File

@@ -0,0 +1,81 @@
#ifndef RFL_PARSING_PARSER_ARRAY_HPP_
#define RFL_PARSING_PARSER_ARRAY_HPP_
#include <array>
#include <map>
#include <type_traits>
#include "../Ref.hpp"
#include "../Result.hpp"
#include "../always_false.hpp"
#include "../internal/ptr_cast.hpp"
#include "ArrayReader.hpp"
#include "Parent.hpp"
#include "Parser_base.hpp"
#include "call_destructors_on_array_where_necessary.hpp"
#include "schema/Type.hpp"
namespace rfl {
namespace parsing {
template <class R, class W, class T, size_t _size, class ProcessorsType>
requires AreReaderAndWriter<R, W, std::array<T, _size>>
struct Parser<R, W, std::array<T, _size>, ProcessorsType> {
public:
using InputArrayType = typename R::InputArrayType;
using InputVarType = typename R::InputVarType;
using ParentType = Parent<W>;
static Result<std::array<T, _size>> read(const R& _r,
const InputVarType& _var) noexcept {
const auto parse =
[&](const InputArrayType& _arr) -> Result<std::array<T, _size>> {
alignas(
std::array<T, _size>) unsigned char buf[sizeof(std::array<T, _size>)];
auto ptr = internal::ptr_cast<std::array<T, _size>*>(&buf);
const auto array_reader =
ArrayReader<R, W, ProcessorsType, T, _size>(&_r, ptr);
auto err = _r.read_array(array_reader, _arr);
if (err) {
call_destructors_on_array_where_necessary(array_reader.num_set(), ptr);
return error(*err);
}
err = array_reader.check_size();
if (err) {
call_destructors_on_array_where_necessary(array_reader.num_set(), ptr);
return error(*err);
}
auto result = Result<std::array<T, _size>>(std::move(*ptr));
call_destructors_on_array_where_necessary(array_reader.num_set(), ptr);
return result;
};
return _r.to_array(_var).and_then(parse);
}
template <class P>
static void write(const W& _w, const std::array<T, _size>& _arr,
const P& _parent) {
auto arr = ParentType::add_array(_w, _size, _parent);
const auto new_parent = typename ParentType::Array{&arr};
for (const auto& e : _arr) {
Parser<R, W, std::remove_cvref_t<T>, ProcessorsType>::write(_w, e,
new_parent);
}
_w.end_array(&arr);
}
static schema::Type to_schema(
std::map<std::string, schema::Type>* _definitions) {
using U = std::remove_cvref_t<T>;
return schema::Type{schema::Type::FixedSizeTypedArray{
.size_ = _size,
.type_ = Ref<schema::Type>::make(
Parser<R, W, U, ProcessorsType>::to_schema(_definitions))}};
}
};
} // namespace parsing
} // namespace rfl
#endif

View File

@@ -0,0 +1,16 @@
#ifndef RFL_PARSING_PARSER_BASE_HPP_
#define RFL_PARSING_PARSER_BASE_HPP_
#include "AreReaderAndWriter.hpp"
namespace rfl {
namespace parsing {
template <class R, class W, class T, class ProcessorsType>
requires AreReaderAndWriter<R, W, T>
struct Parser;
} // namespace parsing
} // namespace rfl
#endif

View File

@@ -0,0 +1,45 @@
#ifndef RFL_PARSING_PARSER_BOX_HPP_
#define RFL_PARSING_PARSER_BOX_HPP_
#include <map>
#include <type_traits>
#include "../Box.hpp"
#include "../Result.hpp"
#include "Parser_base.hpp"
#include "schema/Type.hpp"
namespace rfl {
namespace parsing {
template <class R, class W, class T, Copyability C, class ProcessorsType>
requires AreReaderAndWriter<R, W, Box<T, C>>
struct Parser<R, W, Box<T, C>, ProcessorsType> {
using InputVarType = typename R::InputVarType;
static Result<Box<T, C>> read(const R& _r,
const InputVarType& _var) noexcept {
const auto to_box = [](auto&& _t) {
return Box<T, C>::make(std::move(_t));
};
return Parser<R, W, std::remove_cvref_t<T>, ProcessorsType>::read(_r, _var)
.transform(to_box);
}
template <class P>
static void write(const W& _w, const Box<T, C>& _box, const P& _parent) {
Parser<R, W, std::remove_cvref_t<T>, ProcessorsType>::write(_w, *_box,
_parent);
}
static schema::Type to_schema(
std::map<std::string, schema::Type>* _definitions) {
return Parser<R, W, std::remove_cvref_t<T>, ProcessorsType>::to_schema(
_definitions);
}
};
} // namespace parsing
} // namespace rfl
#endif

View File

@@ -0,0 +1,38 @@
#ifndef RFL_PARSING_PARSER_BYTESTRING_HPP_
#define RFL_PARSING_PARSER_BYTESTRING_HPP_
#include <map>
#include "../Bytestring.hpp"
#include "../Result.hpp"
#include "Parent.hpp"
#include "Parser_base.hpp"
#include "schema/Type.hpp"
namespace rfl::parsing {
template <class R, class W, class ProcessorsType>
requires AreReaderAndWriter<R, W, Bytestring>
struct Parser<R, W, Bytestring, ProcessorsType> {
using InputVarType = typename R::InputVarType;
using ParentType = Parent<W>;
static Result<Bytestring> read(const R& _r,
const InputVarType& _var) noexcept {
return _r.template to_basic_type<Bytestring>(_var);
}
template <class P>
static void write(const W& _w, const Bytestring& _b, const P& _parent) {
ParentType::add_value(_w, _b, _parent);
}
static schema::Type to_schema(
[[maybe_unused]] std::map<std::string, schema::Type>* _definitions) {
return schema::Type{schema::Type::Bytestring{}};
}
};
} // namespace rfl::parsing
#endif

View File

@@ -0,0 +1,57 @@
#ifndef RFL_PARSING_PARSER_C_ARRAY_HPP_
#define RFL_PARSING_PARSER_C_ARRAY_HPP_
#include <map>
#include <type_traits>
#include "../Result.hpp"
#include "../always_false.hpp"
#include "../internal/Array.hpp"
#include "../internal/to_std_array.hpp"
#include "Parent.hpp"
#include "Parser_array.hpp"
#include "Parser_base.hpp"
#include "schema/Type.hpp"
namespace rfl {
namespace parsing {
template <class R, class W, class T, size_t _size, class ProcessorsType>
requires AreReaderAndWriter<R, W, T[_size]>
struct Parser<R, W, T[_size], ProcessorsType> {
public:
using InputArrayType = typename R::InputArrayType;
using InputVarType = typename R::InputVarType;
using ParentType = Parent<W>;
using CArray = T[_size];
static Result<internal::Array<CArray>> read(
const R& _r, const InputVarType& _var) noexcept {
using StdArray = internal::to_std_array_t<CArray>;
return Parser<R, W, StdArray, ProcessorsType>::read(_r, _var);
}
template <class P>
static void write(const W& _w, const CArray& _arr, const P& _parent) {
auto arr = ParentType::add_array(_w, _size, _parent);
const auto new_parent = typename ParentType::Array{&arr};
for (const auto& e : _arr) {
Parser<R, W, std::remove_cvref_t<T>, ProcessorsType>::write(_w, e,
new_parent);
}
_w.end_array(&arr);
}
static schema::Type to_schema(
std::map<std::string, schema::Type>* _definitions) {
using StdArray = internal::to_std_array_t<CArray>;
return Parser<R, W, std::remove_cvref_t<StdArray>,
ProcessorsType>::to_schema(_definitions);
}
};
} // namespace parsing
} // namespace rfl
#endif

View File

@@ -0,0 +1,321 @@
#ifndef RFL_PARSING_PARSER_DEFAULT_HPP_
#define RFL_PARSING_PARSER_DEFAULT_HPP_
#include <map>
#include <type_traits>
#include "../Result.hpp"
#include "../always_false.hpp"
#include "../enums.hpp"
#include "../from_named_tuple.hpp"
#include "../internal/has_default_val_v.hpp"
#include "../internal/has_reflection_method_v.hpp"
#include "../internal/has_reflection_type_v.hpp"
#include "../internal/has_reflector.hpp"
#include "../internal/is_basic_type.hpp"
#include "../internal/is_description.hpp"
#include "../internal/is_literal.hpp"
#include "../internal/is_underlying_enums_v.hpp"
#include "../internal/is_validator.hpp"
#include "../internal/processed_t.hpp"
#include "../internal/ptr_cast.hpp"
#include "../internal/to_ptr_named_tuple.hpp"
#include "../thirdparty/enchantum/enchantum.hpp"
#include "../to_view.hpp"
#include "AreReaderAndWriter.hpp"
#include "Parent.hpp"
#include "Parser_base.hpp"
#include "call_destructors_where_necessary.hpp"
#include "is_tagged_union_wrapper.hpp"
#include "make_type_name.hpp"
#include "schema/Type.hpp"
#include "schemaful/IsSchemafulReader.hpp"
#include "schemaful/IsSchemafulWriter.hpp"
namespace rfl::parsing {
/// Default case - anything that cannot be explicitly matched.
template <class R, class W, class T, class ProcessorsType>
requires AreReaderAndWriter<R, W, T>
struct Parser {
public:
using InputVarType = typename R::InputVarType;
using ParentType = Parent<W>;
/// Expresses the variables as type T.
static Result<T> read(const R& _r, const InputVarType& _var) noexcept {
if constexpr (internal::has_read_reflector<T>) {
const auto wrap_in_t = [](auto&& _named_tuple) -> Result<T> {
try {
using NT = decltype(_named_tuple);
return Reflector<T>::to(std::forward<NT>(_named_tuple));
} catch (std::exception& e) {
return error(e.what());
}
};
return Parser<R, W, typename Reflector<T>::ReflType,
ProcessorsType>::read(_r, _var)
.and_then(wrap_in_t);
} else if constexpr (schemaful::IsSchemafulReader<R> &&
internal::is_literal_v<T>) {
return _r.template to_basic_type<T>(_var);
} else if constexpr (R::template has_custom_constructor<T>) {
return _r.template use_custom_constructor<T>(_var);
} else {
if constexpr (internal::has_reflection_type_v<T>) {
using ReflectionType = std::remove_cvref_t<typename T::ReflectionType>;
const auto wrap_in_t = [](auto&& _named_tuple) -> Result<T> {
try {
using NT = decltype(_named_tuple);
return T{std::forward<NT>(_named_tuple)};
} catch (std::exception& e) {
return error(e.what());
}
};
return Parser<R, W, ReflectionType, ProcessorsType>::read(_r, _var)
.and_then(wrap_in_t);
} else if constexpr (std::is_class_v<T> && std::is_aggregate_v<T>) {
if constexpr (ProcessorsType::default_if_missing_ ||
internal::has_default_val_v<T>) {
return read_struct_with_default(_r, _var);
} else {
return read_struct(_r, _var);
}
} else if constexpr (std::is_enum_v<T>) {
if constexpr (ProcessorsType::underlying_enums_ ||
schemaful::IsSchemafulReader<R>) {
static_assert(enchantum::ScopedEnum<T>,
"The enum must be a scoped enum in order to retrieve "
"the underlying value.");
return _r.template to_basic_type<std::underlying_type_t<T>>(_var)
.transform([](const auto _val) { return static_cast<T>(_val); });
} else {
return _r.template to_basic_type<std::string>(_var).and_then(
rfl::string_to_enum<T>);
}
} else {
return _r.template to_basic_type<std::remove_cvref_t<T>>(_var);
}
}
}
template <class P>
static void write(const W& _w, const T& _var, const P& _parent) {
if constexpr (internal::has_write_reflector<T>) {
Parser<R, W, typename Reflector<T>::ReflType, ProcessorsType>::write(
_w, Reflector<T>::from(_var), _parent);
} else if constexpr (schemaful::IsSchemafulWriter<W> &&
internal::is_literal_v<T>) {
ParentType::add_value(_w, _var, _parent);
} else if constexpr (internal::has_reflection_type_v<T>) {
using ReflectionType = std::remove_cvref_t<typename T::ReflectionType>;
if constexpr (internal::has_reflection_method_v<T>) {
Parser<R, W, ReflectionType, ProcessorsType>::write(
_w, _var.reflection(), _parent);
} else {
const auto& [r] = _var;
Parser<R, W, ReflectionType, ProcessorsType>::write(_w, r, _parent);
}
} else if constexpr (std::is_class_v<T> && std::is_aggregate_v<T>) {
const auto ptr_named_tuple = ProcessorsType::template process<T>(
internal::to_ptr_named_tuple(_var));
using PtrNamedTupleType = std::remove_cvref_t<decltype(ptr_named_tuple)>;
Parser<R, W, PtrNamedTupleType, ProcessorsType>::write(
_w, ptr_named_tuple, _parent);
} else if constexpr (std::is_enum_v<T>) {
if constexpr (ProcessorsType::underlying_enums_ ||
schemaful::IsSchemafulWriter<W>) {
const auto val = static_cast<std::underlying_type_t<T>>(_var);
ParentType::add_value(_w, val, _parent);
} else {
const auto str = rfl::enum_to_string(_var);
ParentType::add_value(_w, str, _parent);
}
} else {
ParentType::add_value(_w, _var, _parent);
}
}
/// Generates a schema for the underlying type.
static schema::Type to_schema(
std::map<std::string, schema::Type>* _definitions) {
using U = std::remove_cvref_t<T>;
using Type = schema::Type;
if constexpr (std::is_same<U, bool>()) {
return Type{Type::Boolean{}};
} else if constexpr (std::is_same<U, std::int32_t>()) {
return Type{Type::Int32{}};
} else if constexpr (std::is_same<U, std::int64_t>()) {
return Type{Type::Int64{}};
} else if constexpr (std::is_same<U, std::uint32_t>()) {
return Type{Type::UInt32{}};
} else if constexpr (std::is_same<U, std::uint64_t>()) {
return Type{Type::UInt64{}};
} else if constexpr (std::is_integral<U>()) {
return Type{Type::Integer{}};
} else if constexpr (std::is_same<U, float>()) {
return Type{Type::Float{}};
} else if constexpr (std::is_floating_point_v<U>) {
return Type{Type::Double{}};
} else if constexpr (std::is_same<U, std::string>()) {
return Type{Type::String{}};
} else if constexpr (rfl::internal::is_description_v<U>) {
return make_description<U>(_definitions);
} else if constexpr (std::is_enum_v<U>) {
return make_enum<U>(_definitions);
} else if constexpr (std::is_class_v<U> && std::is_aggregate_v<U>) {
return make_reference<U>(_definitions);
} else if constexpr (internal::is_literal_v<U>) {
return Type{Type::Literal{.values_ = U::strings()}};
} else if constexpr (internal::is_validator_v<U>) {
return make_validated<U>(_definitions);
} else if constexpr (internal::has_reflection_type_v<U> ||
internal::has_read_reflector<U> ||
internal::has_write_reflector<U>) {
return make_reference<U>(_definitions);
} else {
static_assert(rfl::always_false_v<U>, "Unsupported type.");
}
}
private:
template <class U>
static schema::Type make_description(
std::map<std::string, schema::Type>* _definitions) {
using Type = schema::Type;
return Type{Type::Description{
.description_ = typename U::Content().str(),
.type_ =
Ref<Type>::make(Parser<R, W, std::remove_cvref_t<typename U::Type>,
ProcessorsType>::to_schema(_definitions))}};
}
template <class U>
static schema::Type make_enum(
std::map<std::string, schema::Type>* _definitions) {
using Type = schema::Type;
if constexpr (ProcessorsType::underlying_enums_ ||
schemaful::IsSchemafulReader<R>) {
return Type{Type::Integer{}};
} else if constexpr (enchantum::is_bitflag<U>) {
return Type{Type::String{}};
} else {
return Parser<
R, W,
typename decltype(internal::enums::get_enum_names<U>())::Literal,
ProcessorsType>::to_schema(_definitions);
}
}
template <class U>
static schema::Type make_reference(
std::map<std::string, schema::Type>* _definitions) {
using Type = schema::Type;
const auto name = make_type_name<U>();
if (_definitions->find(name) == _definitions->end()) {
(*_definitions)[name] =
Type{Type::Integer{}}; // Placeholder to avoid infinite loop.
if constexpr (internal::has_reflection_type_v<U>) {
(*_definitions)[name] =
Parser<R, W, typename U::ReflectionType, ProcessorsType>::to_schema(
_definitions);
} else if constexpr (internal::has_read_reflector<U> ||
internal::has_write_reflector<U>) {
(*_definitions)[name] = Parser<R, W, typename Reflector<U>::ReflType,
ProcessorsType>::to_schema(_definitions);
} else {
using NamedTupleType = internal::processed_t<U, ProcessorsType>;
(*_definitions)[name] =
Parser<R, W, NamedTupleType, ProcessorsType>::to_schema(
_definitions);
}
}
return Type{Type::Reference{name}};
}
template <class U>
static schema::Type make_validated(
std::map<std::string, schema::Type>* _definitions) {
using Type = schema::Type;
using ReflectionType = std::remove_cvref_t<typename U::ReflectionType>;
using ValidationType = std::remove_cvref_t<typename U::ValidationType>;
return Type{Type::Validated{
.type_ = Ref<Type>::make(
Parser<R, W, ReflectionType, ProcessorsType>::to_schema(
_definitions)),
.validation_ = ValidationType::template to_schema<ReflectionType>()}};
}
/// The way this works is that we allocate space on the stack in this size of
/// the struct in which we then write the individual fields using
/// views and placement new. This is how we deal with the fact that some
/// fields might not be default-constructible.
static Result<T> read_struct(const R& _r, const InputVarType& _var) {
alignas(T) unsigned char buf[sizeof(T)]{};
auto ptr = internal::ptr_cast<T*>(&buf);
auto view = ProcessorsType::template process<T>(to_view(*ptr));
using ViewType = std::remove_cvref_t<decltype(view)>;
const auto [set, err] =
Parser<R, W, ViewType, ProcessorsType>::read_view(_r, _var, &view);
if (err) [[unlikely]] {
call_destructors_where_necessary(set, &view);
return error(err->what());
}
auto res = Result<T>(std::move(*ptr));
call_destructors_where_necessary(set, &view);
return res;
}
/// This is actually more straight-forward than the standard case - we just
/// allocate a struct and then fill it. But it is less efficient and it
/// assumes that all values on the struct have a default constructor, so we
/// only use it when the DefaultIfMissing preprocessor is added.
static Result<T> read_struct_with_default(const R& _r,
const InputVarType& _var) {
auto t = T{};
auto view = ProcessorsType::template process<T>(to_view(t));
using ViewType = decltype(view);
const auto err =
Parser<R, W, ViewType, ProcessorsType>::read_view_with_default(_r, _var,
&view);
if (err) [[unlikely]] {
return error(*err);
}
return t;
}
};
} // namespace rfl::parsing
#endif

View File

@@ -0,0 +1,46 @@
#ifndef RFL_PARSING_PARSER_DEFAULTVAL_HPP_
#define RFL_PARSING_PARSER_DEFAULTVAL_HPP_
#include <map>
#include <type_traits>
#include "../DefaultVal.hpp"
#include "AreReaderAndWriter.hpp"
#include "Parent.hpp"
#include "Parser_base.hpp"
#include "schema/Type.hpp"
namespace rfl::parsing {
template <class R, class W, class T, class ProcessorsType>
requires AreReaderAndWriter<R, W, DefaultVal<T>>
struct Parser<R, W, DefaultVal<T>, ProcessorsType> {
using InputVarType = typename R::InputVarType;
using ParentType = Parent<W>;
static Result<DefaultVal<T>> read(const R& _r,
const InputVarType& _var) noexcept {
return Parser<R, W, std::remove_cvref_t<T>, ProcessorsType>::read(_r, _var)
.transform([](auto&& _t) {
return DefaultVal<T>(std::forward<decltype(_t)>(_t));
});
}
template <class P>
static void write(const W& _w, const DefaultVal<T>& _d, const P& _parent) {
Parser<R, W, std::remove_cvref_t<T>, ProcessorsType>::write(_w, _d.value(),
_parent);
}
static schema::Type to_schema(
std::map<std::string, schema::Type>* _definitions) {
using U = std::remove_cvref_t<T>;
return schema::Type{
Parser<R, W, U, ProcessorsType>::to_schema(_definitions)};
}
};
} // namespace rfl::parsing
#endif

View File

@@ -0,0 +1,143 @@
#ifndef RFL_PARSING_PARSER_DURATION_HPP_
#define RFL_PARSING_PARSER_DURATION_HPP_
#include <chrono>
#include <map>
#include <type_traits>
#include "../Literal.hpp"
#include "../Variant.hpp"
#include "../always_false.hpp"
#include "Parent.hpp"
#include "Parser_base.hpp"
#include "schema/Type.hpp"
namespace rfl ::parsing {
template <class R, class W, class Rep, class Period, class ProcessorsType>
requires AreReaderAndWriter<R, W, std::chrono::duration<Rep, Period>>
struct Parser<R, W, std::chrono::duration<Rep, Period>, ProcessorsType> {
public:
using InputVarType = typename R::InputVarType;
using ParentType = Parent<W>;
using DurationType = std::chrono::duration<Rep, Period>;
using Unit = Literal<"nanoseconds", "microseconds", "milliseconds", "seconds",
"minutes", "hours", "days", "weeks", "months", "years">;
using SupportedTypes =
Variant<std::chrono::nanoseconds, std::chrono::microseconds,
std::chrono::milliseconds, std::chrono::seconds,
std::chrono::minutes, std::chrono::hours, std::chrono::days,
std::chrono::weeks, std::chrono::months, std::chrono::years>;
struct RType {
int64_t count;
Unit unit;
};
static Result<DurationType> read(const R& _r,
const InputVarType& _var) noexcept {
return Parser<R, W, RType, ProcessorsType>::read(_r, _var)
.and_then(to_duration)
.transform([](auto&& _duration) {
return _duration.visit([](auto&& _d) -> DurationType {
return std::chrono::duration_cast<DurationType>(std::move(_d));
});
});
}
template <class P>
static void write(const W& _w, const DurationType& _d, const P& _parent) {
const auto r =
RType{.count = static_cast<int64_t>(_d.count()), .unit = make_unit()};
return Parser<R, W, RType, ProcessorsType>::write(_w, r, _parent);
}
static schema::Type to_schema(
std::map<std::string, schema::Type>* _definitions) {
return Parser<R, W, RType, ProcessorsType>::to_schema(_definitions);
}
private:
static Result<SupportedTypes> to_duration(const RType& _r) {
switch (_r.unit.value()) {
case Unit::value_of<"nanoseconds">():
return SupportedTypes(std::chrono::nanoseconds(_r.count));
case Unit::value_of<"microseconds">():
return SupportedTypes(std::chrono::microseconds(_r.count));
case Unit::value_of<"milliseconds">():
return SupportedTypes(std::chrono::milliseconds(_r.count));
case Unit::value_of<"seconds">():
return SupportedTypes(std::chrono::seconds(_r.count));
case Unit::value_of<"minutes">():
return SupportedTypes(std::chrono::minutes(_r.count));
case Unit::value_of<"hours">():
return SupportedTypes(std::chrono::hours(_r.count));
case Unit::value_of<"days">():
return SupportedTypes(std::chrono::days(_r.count));
case Unit::value_of<"weeks">():
return SupportedTypes(std::chrono::weeks(_r.count));
case Unit::value_of<"months">():
return SupportedTypes(std::chrono::months(_r.count));
case Unit::value_of<"years">():
return SupportedTypes(std::chrono::years(_r.count));
default:
return error("Unsupported unit.");
}
}
static auto make_unit() noexcept {
if constexpr (std::is_same_v<DurationType, std::chrono::nanoseconds>) {
return Unit::make<"nanoseconds">();
} else if constexpr (std::is_same_v<DurationType,
std::chrono::microseconds>) {
return Unit::make<"microseconds">();
} else if constexpr (std::is_same_v<DurationType,
std::chrono::milliseconds>) {
return Unit::make<"milliseconds">();
} else if constexpr (std::is_same_v<DurationType, std::chrono::seconds>) {
return Unit::make<"seconds">();
} else if constexpr (std::is_same_v<DurationType, std::chrono::minutes>) {
return Unit::make<"minutes">();
} else if constexpr (std::is_same_v<DurationType, std::chrono::hours>) {
return Unit::make<"hours">();
} else if constexpr (std::is_same_v<DurationType, std::chrono::days>) {
return Unit::make<"days">();
} else if constexpr (std::is_same_v<DurationType, std::chrono::weeks>) {
return Unit::make<"weeks">();
} else if constexpr (std::is_same_v<DurationType, std::chrono::months>) {
return Unit::make<"months">();
} else if constexpr (std::is_same_v<DurationType, std::chrono::years>) {
return Unit::make<"years">();
} else {
static_assert(always_false_v<DurationType>, "Unsupported type.");
}
};
};
} // namespace rfl::parsing
#endif

View File

@@ -0,0 +1,50 @@
#ifndef RFL_PARSING_PARSER_FILEPATH_HPP_
#define RFL_PARSING_PARSER_FILEPATH_HPP_
#include <filesystem>
#include <map>
#include "../Result.hpp"
#include "Parser_base.hpp"
#include "schema/Type.hpp"
namespace rfl {
namespace parsing {
template <class R, class W, class ProcessorsType>
requires AreReaderAndWriter<R, W, std::filesystem::path>
struct Parser<R, W, std::filesystem::path, ProcessorsType> {
using InputVarType = typename R::InputVarType;
/// Expresses the variables as type T.
static Result<std::filesystem::path> read(const R& _r,
const InputVarType& _var) noexcept {
const auto to_path =
[&](std::string&& _str) -> Result<std::filesystem::path> {
try {
return std::filesystem::path(_str);
} catch (std::exception& e) {
return error(e.what());
}
};
return Parser<R, W, std::string, ProcessorsType>::read(_r, _var).and_then(
to_path);
}
template <class P>
static void write(const W& _w, const std::filesystem::path& _p,
const P& _parent) {
return Parser<R, W, std::string, ProcessorsType>::write(_w, _p.string(),
_parent);
}
static schema::Type to_schema(
std::map<std::string, schema::Type>* _definitions) {
return Parser<R, W, std::string, ProcessorsType>::to_schema(_definitions);
}
};
} // namespace parsing
} // namespace rfl
#endif

View File

@@ -0,0 +1,47 @@
#ifndef RFL_PARSING_PARSER_MAP_LIKE_HPP_
#define RFL_PARSING_PARSER_MAP_LIKE_HPP_
#include <map>
#include <string>
#include <unordered_map>
#include "../ExtraFields.hpp"
#include "../Object.hpp"
#include "../Result.hpp"
#include "../always_false.hpp"
#include "MapParser.hpp"
#include "Parser_base.hpp"
namespace rfl {
namespace parsing {
template <class R, class W, class T, class ProcessorsType>
requires AreReaderAndWriter<R, W, std::map<std::string, T>>
struct Parser<R, W, std::map<std::string, T>, ProcessorsType>
: public MapParser<R, W, std::map<std::string, T>, ProcessorsType> {};
template <class R, class W, class T, class Hash, class KeyEqual,
class Allocator, class ProcessorsType>
requires AreReaderAndWriter<
R, W, std::unordered_map<std::string, T, Hash, KeyEqual, Allocator>>
struct Parser<R, W,
std::unordered_map<std::string, T, Hash, KeyEqual, Allocator>,
ProcessorsType>
: public MapParser<
R, W, std::unordered_map<std::string, T, Hash, KeyEqual, Allocator>,
ProcessorsType> {};
template <class R, class W, class T, class ProcessorsType>
requires AreReaderAndWriter<R, W, Object<T>>
struct Parser<R, W, Object<T>, ProcessorsType>
: public MapParser<R, W, Object<T>, ProcessorsType> {};
template <class R, class W, class T, class ProcessorsType>
requires AreReaderAndWriter<R, W, ExtraFields<T>>
struct Parser<R, W, ExtraFields<T>, ProcessorsType>
: public MapParser<R, W, ExtraFields<T>, ProcessorsType> {};
} // namespace parsing
} // namespace rfl
#endif

View File

@@ -0,0 +1,23 @@
#ifndef RFL_PARSING_PARSER_NAMED_TUPLE_HPP_
#define RFL_PARSING_PARSER_NAMED_TUPLE_HPP_
#include "NamedTupleParser.hpp"
#include "Parser_base.hpp"
namespace rfl {
namespace parsing {
template <class R, class W, class... FieldTypes, class ProcessorsType>
requires AreReaderAndWriter<R, W, NamedTuple<FieldTypes...>>
struct Parser<R, W, NamedTuple<FieldTypes...>, ProcessorsType>
: public NamedTupleParser<
R, W, /*_ignore_empty_containers=*/false,
/*_all_required=*/ProcessorsType::all_required_,
/*_no_field_names=*/ProcessorsType::no_field_names_, ProcessorsType,
FieldTypes...> {
};
} // namespace parsing
} // namespace rfl
#endif

View File

@@ -0,0 +1,83 @@
#ifndef RFL_PARSING_PARSER_OPTIONAL_HPP_
#define RFL_PARSING_PARSER_OPTIONAL_HPP_
#include <map>
#include <optional>
#include <type_traits>
#include "../Ref.hpp"
#include "../Result.hpp"
#include "../always_false.hpp"
#include "Parent.hpp"
#include "Parser_base.hpp"
#include "schema/Type.hpp"
#include "schemaful/IsSchemafulReader.hpp"
#include "schemaful/IsSchemafulWriter.hpp"
#include "schemaful/OptionalReader.hpp"
namespace rfl {
namespace parsing {
template <class R, class W, class T, class ProcessorsType>
requires AreReaderAndWriter<R, W, std::optional<T>>
struct Parser<R, W, std::optional<T>, ProcessorsType> {
using InputVarType = typename R::InputVarType;
using ParentType = Parent<W>;
static Result<std::optional<T>> read(const R& _r,
const InputVarType& _var) noexcept {
if constexpr (schemaful::IsSchemafulReader<R>) {
using O = schemaful::OptionalReader<R, W, std::remove_cvref_t<T>,
ProcessorsType>;
const auto to_optional = [&](const auto& _u) -> Result<std::optional<T>> {
return _r.template read_union<std::optional<T>, O>(_u);
};
return _r.to_union(_var).and_then(to_optional);
} else {
if (_r.is_empty(_var)) {
return std::optional<T>();
}
const auto to_opt = [](auto&& _t) {
return std::make_optional<T>(std::forward<decltype(_t)>(_t));
};
return Parser<R, W, std::remove_cvref_t<T>, ProcessorsType>::read(_r,
_var)
.transform(to_opt);
}
}
template <class P>
static void write(const W& _w, const std::optional<T>& _o, const P& _parent) {
if constexpr (schemaful::IsSchemafulWriter<W>) {
auto u = ParentType::add_union(_w, _parent);
using UnionType = typename ParentType::template Union<decltype(u)>;
auto p =
UnionType{.index_ = static_cast<size_t>(_o ? 0 : 1), .union_ = &u};
if (_o) {
Parser<R, W, std::remove_cvref_t<T>, ProcessorsType>::write(_w, *_o, p);
} else {
ParentType::add_null(_w, p);
}
} else {
if (_o) {
Parser<R, W, std::remove_cvref_t<T>, ProcessorsType>::write(_w, *_o,
_parent);
} else {
ParentType::add_null(_w, _parent);
}
}
}
static schema::Type to_schema(
std::map<std::string, schema::Type>* _definitions) {
using U = std::remove_cvref_t<T>;
return schema::Type{schema::Type::Optional{Ref<schema::Type>::make(
Parser<R, W, U, ProcessorsType>::to_schema(_definitions))}};
}
};
} // namespace parsing
} // namespace rfl
#endif

View File

@@ -0,0 +1,52 @@
#ifndef RFL_PARSING_PARSER_PAIR_HPP_
#define RFL_PARSING_PARSER_PAIR_HPP_
#include <map>
#include <utility>
#include "../Ref.hpp"
#include "../Result.hpp"
#include "../always_false.hpp"
#include "Parser_base.hpp"
#include "schema/Type.hpp"
namespace rfl {
namespace parsing {
template <class R, class W, class FirstType, class SecondType,
class ProcessorsType>
requires AreReaderAndWriter<R, W, std::pair<FirstType, SecondType>>
struct Parser<R, W, std::pair<FirstType, SecondType>, ProcessorsType> {
using InputVarType = typename R::InputVarType;
/// Expresses the variables as type T.
static Result<std::pair<FirstType, SecondType>> read(
const R& _r, const InputVarType& _var) noexcept {
const auto to_pair = [&](auto&& _t) {
return std::make_pair(std::move(std::get<0>(_t)),
std::move(std::get<1>(_t)));
};
return Parser<R, W, std::tuple<FirstType, SecondType>,
ProcessorsType>::read(_r, _var)
.transform(to_pair);
}
template <class P>
static void write(const W& _w, const std::pair<FirstType, SecondType>& _p,
const P& _parent) {
const auto tup = std::make_tuple(&_p.first, &_p.second);
Parser<R, W, std::tuple<const FirstType*, const SecondType*>,
ProcessorsType>::write(_w, tup, _parent);
}
static schema::Type to_schema(
std::map<std::string, schema::Type>* _definitions) {
return Parser<R, W, std::tuple<FirstType, SecondType>,
ProcessorsType>::to_schema(_definitions);
}
};
} // namespace parsing
} // namespace rfl
#endif

View File

@@ -0,0 +1,69 @@
#ifndef RFL_PARSING_PARSER_PTR_HPP_
#define RFL_PARSING_PARSER_PTR_HPP_
#include <map>
#include <type_traits>
#include "../Ref.hpp"
#include "../Result.hpp"
#include "../always_false.hpp"
#include "Parent.hpp"
#include "Parser_base.hpp"
#include "schema/Type.hpp"
namespace rfl {
namespace parsing {
template <class R, class W, class T, class ProcessorsType>
requires AreReaderAndWriter<R, W, T*>
struct Parser<R, W, T*, ProcessorsType> {
using InputVarType = typename R::InputVarType;
using ParentType = Parent<W>;
/// Expresses the variables as type T.
static Result<T*> read(const R& _r, const InputVarType& _var) noexcept {
if constexpr (!ProcessorsType::allow_raw_ptrs_) {
static_assert(
always_false_v<T>,
"Reading into raw pointers is dangerous and "
"therefore unsupported by default. "
"Please consider using std::unique_ptr, rfl::Box, "
"std::shared_ptr, "
"rfl::Ref or std::optional instead. "
"If you absolutely must use raw pointers, "
"you can pass the rfl::AllowRawPtrs processor. "
"Please note that it is then YOUR responsibility "
"to delete the allocated memory. Please also refer "
"to the related documentation (in the section on processors).");
return error("Unsupported.");
} else {
if (_r.is_empty(_var)) {
return nullptr;
}
return Parser<R, W, T, ProcessorsType>::read(_r, _var).transform(
[](T&& _t) { return new T(std::move(_t)); });
}
}
template <class P>
static void write(const W& _w, const T* _ptr, const P& _parent) {
if (!_ptr) {
ParentType::add_null(_w, _parent);
return;
}
Parser<R, W, std::remove_cvref_t<T>, ProcessorsType>::write(_w, *_ptr,
_parent);
}
static schema::Type to_schema(
std::map<std::string, schema::Type>* _definitions) {
return Parser<R, W, std::remove_cvref_t<T>, ProcessorsType>::to_schema(
_definitions);
}
};
} // namespace parsing
} // namespace rfl
#endif

View File

@@ -0,0 +1,43 @@
#ifndef RFL_PARSING_PARSER_REF_HPP_
#define RFL_PARSING_PARSER_REF_HPP_
#include <map>
#include <type_traits>
#include "../Ref.hpp"
#include "../Result.hpp"
#include "../always_false.hpp"
#include "Parser_base.hpp"
#include "schema/Type.hpp"
namespace rfl {
namespace parsing {
template <class R, class W, class T, class ProcessorsType>
requires AreReaderAndWriter<R, W, Ref<T>>
struct Parser<R, W, Ref<T>, ProcessorsType> {
using InputVarType = typename R::InputVarType;
static Result<Ref<T>> read(const R& _r, const InputVarType& _var) noexcept {
const auto to_ref = [&](auto&& _t) { return Ref<T>::make(std::move(_t)); };
return Parser<R, W, std::remove_cvref_t<T>, ProcessorsType>::read(_r, _var)
.transform(to_ref);
}
template <class P>
static void write(const W& _w, const Ref<T>& _ref, const P& _parent) {
Parser<R, W, std::remove_cvref_t<T>, ProcessorsType>::write(_w, *_ref,
_parent);
}
static schema::Type to_schema(
std::map<std::string, schema::Type>* _definitions) {
return Parser<R, W, std::remove_cvref_t<T>, ProcessorsType>::to_schema(
_definitions);
}
};
} // namespace parsing
} // namespace rfl
#endif

View File

@@ -0,0 +1,49 @@
#ifndef RFL_PARSING_PARSER_REFERENCE_WRAPPER_HPP_
#define RFL_PARSING_PARSER_REFERENCE_WRAPPER_HPP_
#include <functional>
#include <map>
#include <type_traits>
#include "../Result.hpp"
#include "../always_false.hpp"
#include "Parser_base.hpp"
#include "schema/Type.hpp"
namespace rfl {
namespace parsing {
template <class R, class W, class T, class ProcessorsType>
requires AreReaderAndWriter<R, W, std::reference_wrapper<T>>
struct Parser<R, W, std::reference_wrapper<T>, ProcessorsType> {
using InputVarType = typename R::InputVarType;
static Result<std::reference_wrapper<T>> read(const R&,
const InputVarType&) noexcept {
static_assert(always_false_v<T>,
"Reading into std::reference_wrapper is dangerous and "
"therefore unsupported. "
"Please consider using std::unique_ptr, rfl::Box, "
"std::shared_ptr, or"
"rfl::Ref instead.");
return error("Unsupported.");
}
template <class P>
static void write(const W& _w, const std::reference_wrapper<T> _ref,
const P& _p) {
Parser<R, W, std::remove_cvref_t<T>, ProcessorsType>::write(_w, _ref.get(),
_p);
}
static schema::Type to_schema(
std::map<std::string, schema::Type>* _definitions) {
return Parser<R, W, std::remove_cvref_t<T>, ProcessorsType>::to_schema(
_definitions);
}
};
} // namespace parsing
} // namespace rfl
#endif

View File

@@ -0,0 +1,49 @@
#ifndef RFL_PARSING_PARSER_RENAME_HPP_
#define RFL_PARSING_PARSER_RENAME_HPP_
#include <map>
#include <type_traits>
#include "../Rename.hpp"
#include "../Result.hpp"
#include "../always_false.hpp"
#include "../internal/StringLiteral.hpp"
#include "Parser_base.hpp"
#include "schema/Type.hpp"
namespace rfl {
namespace parsing {
template <class R, class W, class T, internal::StringLiteral _name,
class ProcessorsType>
requires AreReaderAndWriter<R, W, Rename<_name, T>>
struct Parser<R, W, Rename<_name, T>, ProcessorsType> {
using InputVarType = typename R::InputVarType;
static Result<Rename<_name, T>> read(const R& _r,
const InputVarType& _var) noexcept {
const auto to_rename = [](auto&& _t) {
return Rename<_name, T>(std::move(_t));
};
return Parser<R, W, std::remove_cvref_t<T>, ProcessorsType>::read(_r, _var)
.transform(to_rename);
}
template <class P>
static void write(const W& _w, const Rename<_name, T>& _rename,
const P& _parent) {
Parser<R, W, std::remove_cvref_t<T>, ProcessorsType>::write(
_w, _rename.value(), _parent);
}
static schema::Type to_schema(
std::map<std::string, schema::Type>* _definitions) {
return Parser<R, W, std::remove_cvref_t<T>, ProcessorsType>::to_schema(
_definitions);
}
};
} // namespace parsing
} // namespace rfl
#endif

View File

@@ -0,0 +1,68 @@
#ifndef RFL_PARSING_PARSER_RESULT_HPP_
#define RFL_PARSING_PARSER_RESULT_HPP_
#include <map>
#include <type_traits>
#include "../Field.hpp"
#include "../NamedTuple.hpp"
#include "../Result.hpp"
#include "../always_false.hpp"
#include "../internal/StringLiteral.hpp"
#include "Parent.hpp"
#include "Parser_base.hpp"
#include "schema/Type.hpp"
namespace rfl {
namespace parsing {
template <class R, class W, class T, class ProcessorsType>
requires AreReaderAndWriter<R, W, Result<T>>
struct Parser<R, W, Result<T>, ProcessorsType> {
using InputVarType = typename R::InputVarType;
using ErrorType = NamedTuple<Field<"error", std::string>>;
using VariantType = std::variant<std::remove_cvref_t<T>, ErrorType>;
static Result<Result<T>> read(const R& _r,
const InputVarType& _var) noexcept {
const auto handle = [](auto&& _t) -> Result<T> {
using Type = std::remove_cvref_t<decltype(_t)>;
if constexpr (std::is_same<Type, ErrorType>()) {
return error(_t.template get<"error">());
} else {
return std::forward<std::remove_cvref_t<T>>(_t);
}
};
const auto to_res = [&](VariantType&& _v) -> Result<T> {
return std::visit(handle, std::forward<VariantType>(_v));
};
return Result<Result<T>>(
Parser<R, W, VariantType, ProcessorsType>::read(_r, _var).transform(
to_res));
}
template <class P>
static void write(const W& _w, const Result<T>& _r, const P& _parent) {
if (_r) {
Parser<R, W, std::remove_cvref_t<T>, ProcessorsType>::write(
_w, _r.value(), _parent);
} else {
Parser<R, W, ErrorType, ProcessorsType>::write(
_w, ErrorType(make_field<"error">(_r.error().what())), _parent);
}
}
static schema::Type to_schema(
std::map<std::string, schema::Type>* _definitions) {
return Parser<R, W, std::remove_cvref_t<T>, ProcessorsType>::to_schema(
_definitions);
}
};
} // namespace parsing
} // namespace rfl
#endif

View File

@@ -0,0 +1,46 @@
#ifndef RFL_PARSING_PARSER_RFL_ARRAY_HPP_
#define RFL_PARSING_PARSER_RFL_ARRAY_HPP_
#include <map>
#include "../Result.hpp"
#include "../always_false.hpp"
#include "../internal/Array.hpp"
#include "../internal/to_std_array.hpp"
#include "Parser_array.hpp"
#include "Parser_base.hpp"
#include "schema/Type.hpp"
namespace rfl {
namespace parsing {
template <class R, class W, class T, class ProcessorsType>
requires AreReaderAndWriter<R, W, internal::Array<T>>
struct Parser<R, W, internal::Array<T>, ProcessorsType> {
public:
using InputArrayType = typename R::InputArrayType;
using InputVarType = typename R::InputVarType;
using StdArray = internal::to_std_array_t<T>;
static Result<internal::Array<T>> read(const R& _r,
const InputVarType& _var) noexcept {
return Parser<R, W, StdArray, ProcessorsType>::read(_r, _var);
}
template <class P>
static void write(const W& _w, const internal::Array<T>& _arr,
const P& _parent) {
Parser<R, W, StdArray, ProcessorsType>::write(_w, _arr.arr_, _parent);
}
static schema::Type to_schema(
std::map<std::string, schema::Type>* _definitions) {
return Parser<R, W, StdArray, ProcessorsType>::to_schema(_definitions);
}
};
} // namespace parsing
} // namespace rfl
#endif

View File

@@ -0,0 +1,22 @@
#ifndef RFL_PARSING_PARSER_RFL_TUPLE_HPP_
#define RFL_PARSING_PARSER_RFL_TUPLE_HPP_
#include "../Tuple.hpp"
#include "Parser_base.hpp"
#include "TupleParser.hpp"
namespace rfl {
namespace parsing {
template <class R, class W, class... Ts, class ProcessorsType>
requires AreReaderAndWriter<R, W, rfl::Tuple<Ts...>>
struct Parser<R, W, rfl::Tuple<Ts...>, ProcessorsType>
: public TupleParser<R, W, /*_ignore_empty_containers=*/false,
/*_all_required=*/ProcessorsType::all_required_,
ProcessorsType, rfl::Tuple<Ts...>> {
};
} // namespace parsing
} // namespace rfl
#endif

View File

@@ -0,0 +1,217 @@
#ifndef RFL_PARSING_PARSER_RFL_VARIANT_HPP_
#define RFL_PARSING_PARSER_RFL_VARIANT_HPP_
#include <map>
#include <type_traits>
#include "../Result.hpp"
#include "../Variant.hpp"
#include "../always_false.hpp"
#include "../internal/all_fields.hpp"
#include "../internal/nth_element_t.hpp"
#include "FieldVariantParser.hpp"
#include "Parent.hpp"
#include "Parser_base.hpp"
#include "VariantAlternativeWrapper.hpp"
#include "schema/Type.hpp"
#include "schemaful/IsSchemafulReader.hpp"
#include "schemaful/IsSchemafulWriter.hpp"
#include "schemaful/VariantReader.hpp"
#include "to_single_error_message.hpp"
namespace rfl::parsing {
template <class R, class W, class... AlternativeTypes, class ProcessorsType>
requires AreReaderAndWriter<R, W, rfl::Variant<AlternativeTypes...>>
class Parser<R, W, rfl::Variant<AlternativeTypes...>, ProcessorsType> {
using ParentType = Parent<W>;
public:
using InputVarType = typename R::InputVarType;
static Result<rfl::Variant<AlternativeTypes...>> read(
const R& _r, const InputVarType& _var) noexcept {
if constexpr (internal::all_fields<std::tuple<AlternativeTypes...>>()) {
if constexpr (schemaful::IsSchemafulReader<R>) {
using WrappedType = rfl::Variant<NamedTuple<AlternativeTypes>...>;
return Parser<R, W, WrappedType, ProcessorsType>::read(_r, _var)
.transform(
[](auto&& _variant) -> rfl::Variant<AlternativeTypes...> {
return std::move(_variant).visit([](auto&& _named_tuple) {
return rfl::Variant<AlternativeTypes...>(std::move(
std::move(_named_tuple).fields().template get<0>()));
});
});
} else {
return FieldVariantParser<R, W, ProcessorsType,
AlternativeTypes...>::read(_r, _var);
}
} else if constexpr (schemaful::IsSchemafulReader<R>) {
using V = schemaful::VariantReader<R, W, Variant<AlternativeTypes...>,
ProcessorsType, AlternativeTypes...>;
return _r.to_union(_var).and_then([&](const auto& _u) {
return _r.template read_union<Variant<AlternativeTypes...>, V>(_u);
});
} else if constexpr (ProcessorsType::add_tags_to_variants_ ||
ProcessorsType::add_namespaced_tags_to_variants_) {
constexpr bool remove_namespaces = ProcessorsType::add_tags_to_variants_;
using FieldVariantType = rfl::Variant<
VariantAlternativeWrapper<AlternativeTypes, remove_namespaces>...>;
const auto from_field_variant =
[](auto&& _field) -> rfl::Variant<AlternativeTypes...> {
return std::move(_field.value());
};
return Parser<R, W, FieldVariantType, ProcessorsType>::read(_r, _var)
.transform([&](FieldVariantType&& _f) {
return _f.visit(from_field_variant);
});
} else {
std::optional<rfl::Variant<AlternativeTypes...>> result;
std::vector<Error> errors;
errors.reserve(sizeof...(AlternativeTypes));
read_variant(
_r, _var, &result, &errors,
std::make_integer_sequence<int, sizeof...(AlternativeTypes)>());
if (result) {
return std::move(*result);
} else {
return error(
to_single_error_message(errors,
"Could not parse the variant. Each of the "
"possible alternatives failed "
"for the following reasons: ",
100000));
}
}
}
template <class P>
static void write(const W& _w,
const rfl::Variant<AlternativeTypes...>& _variant,
const P& _parent) {
if constexpr (internal::all_fields<std::tuple<AlternativeTypes...>>()) {
if constexpr (schemaful::IsSchemafulWriter<W>) {
using WrappedType = rfl::Variant<
NamedTuple<Field<AlternativeTypes::name_,
const typename AlternativeTypes::Type*>>...>;
const auto to_wrapped = [](const auto& _variant) -> WrappedType {
return _variant.visit([](const auto& _field) -> WrappedType {
return make_named_tuple(internal::to_ptr_field(_field));
});
};
Parser<R, W, WrappedType, ProcessorsType>::write(
_w, to_wrapped(_variant), _parent);
} else {
FieldVariantParser<R, W, ProcessorsType, AlternativeTypes...>::write(
_w, _variant, _parent);
}
} else if constexpr (schemaful::IsSchemafulWriter<W>) {
return rfl::visit(
[&](const auto& _v) {
using Type = std::remove_cvref_t<decltype(_v)>;
auto u = ParentType::add_union(_w, _parent);
using UnionType = typename ParentType::template Union<decltype(u)>;
auto p = UnionType{.index_ = static_cast<size_t>(_variant.index()),
.union_ = &u};
Parser<R, W, Type, ProcessorsType>::write(_w, _v, p);
},
_variant);
} else if constexpr (ProcessorsType::add_tags_to_variants_ ||
ProcessorsType::add_namespaced_tags_to_variants_) {
constexpr bool remove_namespaces = ProcessorsType::add_tags_to_variants_;
using FieldVariantType =
rfl::Variant<VariantAlternativeWrapper<const AlternativeTypes*,
remove_namespaces>...>;
const auto to_field_variant =
[]<class T>(const T& _t) -> FieldVariantType {
return VariantAlternativeWrapper<const T*, remove_namespaces>(&_t);
};
Parser<R, W, FieldVariantType, ProcessorsType>::write(
_w, _variant.visit(to_field_variant), _parent);
} else {
const auto handle = [&](const auto& _v) {
using Type = std::remove_cvref_t<decltype(_v)>;
Parser<R, W, Type, ProcessorsType>::write(_w, _v, _parent);
};
return rfl::visit(handle, _variant);
}
}
static schema::Type to_schema(
std::map<std::string, schema::Type>* _definitions) {
if constexpr (internal::all_fields<std::tuple<AlternativeTypes...>>()) {
return FieldVariantParser<R, W, ProcessorsType,
AlternativeTypes...>::to_schema(_definitions);
} else if constexpr (ProcessorsType::add_tags_to_variants_ ||
ProcessorsType::add_namespaced_tags_to_variants_) {
constexpr bool remove_namespaces = ProcessorsType::add_tags_to_variants_;
using FieldVariantType = rfl::Variant<
VariantAlternativeWrapper<AlternativeTypes, remove_namespaces>...>;
return Parser<R, W, FieldVariantType, ProcessorsType>::to_schema(
_definitions);
} else {
std::vector<schema::Type> types;
build_schema(
_definitions, &types,
std::make_integer_sequence<int, sizeof...(AlternativeTypes)>());
return schema::Type{schema::Type::AnyOf{.types_ = std::move(types)}};
}
}
private:
template <size_t _i>
static void add_to_schema(std::map<std::string, schema::Type>* _definitions,
std::vector<schema::Type>* _types) noexcept {
using AltType =
std::remove_cvref_t<internal::nth_element_t<_i, AlternativeTypes...>>;
_types->push_back(
Parser<R, W, AltType, ProcessorsType>::to_schema(_definitions));
}
template <int... _is>
static void build_schema(std::map<std::string, schema::Type>* _definitions,
std::vector<schema::Type>* _types,
std::integer_sequence<int, _is...>) noexcept {
(add_to_schema<_is>(_definitions, _types), ...);
}
template <int _i>
static void read_one_alternative(
const R& _r, const InputVarType& _var,
std::optional<rfl::Variant<AlternativeTypes...>>* _result,
std::vector<Error>* _errors) noexcept {
if (!*_result) {
using AltType =
std::remove_cvref_t<internal::nth_element_t<_i, AlternativeTypes...>>;
auto res = Parser<R, W, AltType, ProcessorsType>::read(_r, _var);
if (res) {
*_result = std::move(*res);
} else {
_errors->emplace_back(std::move(res.error()));
}
}
}
template <int... _is>
static void read_variant(
const R& _r, const InputVarType& _var,
std::optional<rfl::Variant<AlternativeTypes...>>* _result,
std::vector<Error>* _errors,
std::integer_sequence<int, _is...>) noexcept {
(read_one_alternative<_is>(_r, _var, _result, _errors), ...);
}
};
} // namespace rfl::parsing
#endif

View File

@@ -0,0 +1,79 @@
#ifndef RFL_PARSING_PARSER_SHARED_PTR_HPP_
#define RFL_PARSING_PARSER_SHARED_PTR_HPP_
#include <map>
#include <memory>
#include <type_traits>
#include "../Ref.hpp"
#include "../Result.hpp"
#include "../always_false.hpp"
#include "Parent.hpp"
#include "Parser_base.hpp"
#include "schema/Type.hpp"
#include "schemaful/IsSchemafulReader.hpp"
#include "schemaful/IsSchemafulWriter.hpp"
#include "schemaful/SharedPtrReader.hpp"
namespace rfl::parsing {
template <class R, class W, class T, class ProcessorsType>
requires AreReaderAndWriter<R, W, std::shared_ptr<T>>
struct Parser<R, W, std::shared_ptr<T>, ProcessorsType> {
using InputVarType = typename R::InputVarType;
using ParentType = Parent<W>;
static Result<std::shared_ptr<T>> read(const R& _r,
const InputVarType& _var) noexcept {
if constexpr (schemaful::IsSchemafulReader<R>) {
using S = schemaful::SharedPtrReader<R, W, std::remove_cvref_t<T>,
ProcessorsType>;
const auto to_shared = [&](const auto& _u) -> Result<std::shared_ptr<T>> {
return _r.template read_union<std::shared_ptr<T>, S>(_u);
};
return _r.to_union(_var).and_then(to_shared);
} else {
if (_r.is_empty(_var)) {
return std::shared_ptr<T>();
}
return Parser<R, W, std::remove_cvref_t<T>, ProcessorsType>::read(_r,
_var)
.transform([](T&& _t) { return std::make_shared<T>(std::move(_t)); });
}
}
template <class P>
static void write(const W& _w, const std::shared_ptr<T>& _s,
const P& _parent) noexcept {
if constexpr (schemaful::IsSchemafulWriter<W>) {
auto u = ParentType::add_union(_w, _parent);
using UnionType = typename ParentType::template Union<decltype(u)>;
auto p =
UnionType{.index_ = static_cast<size_t>(_s ? 0 : 1), .union_ = &u};
if (_s) {
Parser<R, W, std::remove_cvref_t<T>, ProcessorsType>::write(_w, *_s, p);
} else {
ParentType::add_null(_w, p);
}
} else {
if (_s) {
Parser<R, W, std::remove_cvref_t<T>, ProcessorsType>::write(_w, *_s,
_parent);
} else {
ParentType::add_null(_w, _parent);
}
}
}
static schema::Type to_schema(
std::map<std::string, schema::Type>* _definitions) {
using U = std::remove_cvref_t<T>;
return schema::Type{schema::Type::Optional{Ref<schema::Type>::make(
Parser<R, W, U, ProcessorsType>::to_schema(_definitions))}};
}
};
} // namespace rfl::parsing
#endif

View File

@@ -0,0 +1,66 @@
#ifndef RFL_PARSING_PARSER_SKIP_HPP_
#define RFL_PARSING_PARSER_SKIP_HPP_
#include <map>
#include <type_traits>
#include "../Result.hpp"
#include "../always_false.hpp"
#include "../internal/Skip.hpp"
#include "Parent.hpp"
#include "Parser_base.hpp"
#include "schema/Type.hpp"
namespace rfl {
namespace parsing {
template <class R, class W, class T, bool _skip_serialization,
bool _skip_deserialization, class ProcessorsType>
requires AreReaderAndWriter<
R, W, internal::Skip<T, _skip_serialization, _skip_deserialization>>
struct Parser<R, W,
internal::Skip<T, _skip_serialization, _skip_deserialization>,
ProcessorsType> {
using InputVarType = typename R::InputVarType;
using ParentType = Parent<W>;
static Result<internal::Skip<T, _skip_serialization, _skip_deserialization>>
read(const R& _r, const InputVarType& _var) noexcept {
if constexpr (_skip_deserialization) {
return internal::Skip<T, _skip_serialization, _skip_deserialization>(
std::remove_cvref_t<T>());
} else {
const auto to_skip = [&](auto&& _t) {
return internal::Skip<T, _skip_serialization, _skip_deserialization>(
std::move(_t));
};
return Parser<R, W, std::remove_cvref_t<T>, ProcessorsType>::read(_r,
_var)
.transform(to_skip);
}
}
template <class P>
static void write(const W& _w,
const internal::Skip<T, _skip_serialization,
_skip_deserialization>& _skip,
const P& _parent) {
if constexpr (_skip_serialization) {
ParentType::add_null(_w, _parent);
} else {
Parser<R, W, std::remove_cvref_t<T>, ProcessorsType>::write(
_w, _skip.value(), _parent);
}
}
static schema::Type to_schema(
std::map<std::string, schema::Type>* _definitions) {
return Parser<R, W, std::remove_cvref_t<T>, ProcessorsType>::to_schema(
_definitions);
}
};
} // namespace parsing
} // namespace rfl
#endif

View File

@@ -0,0 +1,77 @@
#ifndef RFL_PARSING_PARSER_SPAN_HPP_
#define RFL_PARSING_PARSER_SPAN_HPP_
#include <cstring>
#include <map>
#include <span>
#include <string>
#include <type_traits>
#include <vector>
#include "../Result.hpp"
#include "../always_false.hpp"
#include "Parent.hpp"
#include "Parser_base.hpp"
#include "schema/Type.hpp"
namespace rfl::parsing {
template <class R, class W, class T, class ProcessorsType>
requires AreReaderAndWriter<R, W, std::span<T>>
struct Parser<R, W, std::span<T>, ProcessorsType> {
using InputVarType = typename R::InputVarType;
using ParentType = Parent<W>;
static Result<std::span<T>> read(const R& _r,
const InputVarType& _var) noexcept {
if constexpr (!ProcessorsType::allow_raw_ptrs_) {
static_assert(
always_false_v<R>,
"Reading into std::span is dangerous and "
"therefore unsupported. "
"Please consider using std::vector instead or wrapping "
"std::vector in rfl::Box or rfl::Ref."
"If you absolutely must use std::span, "
"you can pass the rfl::AllowRawPtrs processor. "
"Please note that it is then YOUR responsibility "
"to delete the allocated memory. Please also refer "
"to the related documentation (in the section on processors).");
return error("Unsupported.");
} else {
return Parser<R, W, std::vector<std::remove_cvref_t<T>>,
ProcessorsType>::read(_r, _var)
.and_then([](std::vector<T>&& _vec) -> Result<std::span<T>> {
using Type = std::remove_cvref_t<T>;
Type* data = new (std::nothrow) Type[_vec.size()];
if (!data) {
return error("Failed to allocate memory for std::span.");
}
for (size_t i = 0; i < _vec.size(); ++i) {
data[i] = std::move(_vec[i]);
}
return std::span<T>(data, data + _vec.size());
});
}
}
template <class P>
static void write(const W& _w, const std::span<T>& _span, const P& _parent) {
auto arr = ParentType::add_array(_w, _span.size(), _parent);
const auto new_parent = typename ParentType::Array{&arr};
for (const auto& v : _span) {
Parser<R, W, std::remove_cvref_t<T>, ProcessorsType>::write(_w, v,
new_parent);
}
_w.end_array(&arr);
}
static schema::Type to_schema(
std::map<std::string, schema::Type>* _definitions) {
return Parser<R, W, std::vector<std::remove_cvref_t<T>>,
ProcessorsType>::to_schema(_definitions);
}
};
} // namespace rfl::parsing
#endif

View File

@@ -0,0 +1,62 @@
#ifndef RFL_PARSING_PARSER_STRING_VIEW_HPP_
#define RFL_PARSING_PARSER_STRING_VIEW_HPP_
#include <cstring>
#include <map>
#include <string>
#include <string_view>
#include "../Result.hpp"
#include "../always_false.hpp"
#include "Parser_base.hpp"
#include "schema/Type.hpp"
namespace rfl {
namespace parsing {
template <class R, class W, class ProcessorsType>
requires AreReaderAndWriter<R, W, std::string_view>
struct Parser<R, W, std::string_view, ProcessorsType> {
using InputVarType = typename R::InputVarType;
static Result<std::string_view> read(const R& _r,
const InputVarType& _var) noexcept {
if constexpr (!ProcessorsType::allow_raw_ptrs_) {
static_assert(
always_false_v<R>,
"Reading into std::string_view is dangerous and "
"therefore unsupported. "
"Please consider using std::string instead or wrapping "
"std::string in rfl::Box or rfl::Ref."
"If you absolutely must use std::string_view, "
"you can pass the rfl::AllowRawPtrs processor. "
"Please note that it is then YOUR responsibility "
"to delete the allocated memory. Please also refer "
"to the related documentation (in the section on processors).");
return error("Unsupported.");
} else {
return Parser<R, W, std::string, ProcessorsType>::read(_r, _var)
.transform([](std::string&& str) {
char* data =
new char[str.size() + 1]; // +1 for the null terminator
std::memcpy(data, str.data(), str.size() + 1);
return std::string_view(data, str.size());
});
}
}
template <class P>
static void write(const W& _w, const std::string_view& _str, const P& _p) {
Parser<R, W, std::string, ProcessorsType>::write(_w, std::string(_str), _p);
}
static schema::Type to_schema(
std::map<std::string, schema::Type>* _definitions) {
return Parser<R, W, std::string, ProcessorsType>::to_schema(_definitions);
}
};
} // namespace parsing
} // namespace rfl
#endif

View File

@@ -0,0 +1,240 @@
#ifndef RFL_PARSING_PARSER_TAGGED_UNION_HPP_
#define RFL_PARSING_PARSER_TAGGED_UNION_HPP_
#include <map>
#include <sstream>
#include <type_traits>
#include "../Result.hpp"
#include "../TaggedUnion.hpp"
#include "../always_false.hpp"
#include "../internal/strings/strings.hpp"
#include "../named_tuple_t.hpp"
#include "../visit.hpp"
#include "Parser_base.hpp"
#include "TaggedUnionWrapper.hpp"
#include "is_tagged_union_wrapper.hpp"
#include "schema/Type.hpp"
#include "schemaful/IsSchemafulReader.hpp"
#include "schemaful/IsSchemafulWriter.hpp"
#include "tagged_union_wrapper_no_ptr.hpp"
namespace rfl::parsing {
template <class R, class W, internal::StringLiteral _discriminator,
class... AlternativeTypes, class ProcessorsType>
requires AreReaderAndWriter<R, W,
TaggedUnion<_discriminator, AlternativeTypes...>>
struct Parser<R, W, TaggedUnion<_discriminator, AlternativeTypes...>,
ProcessorsType> {
using ResultType = Result<TaggedUnion<_discriminator, AlternativeTypes...>>;
public:
using InputObjectType = typename R::InputObjectType;
using InputVarType = typename R::InputVarType;
constexpr static bool no_field_names_ = ProcessorsType::no_field_names_;
using InputObjectOrArrayType =
std::conditional_t<no_field_names_, typename R::InputArrayType,
typename R::InputObjectType>;
static ResultType read(const R& _r, const InputVarType& _var) noexcept {
if constexpr (schemaful::IsSchemafulReader<R>) {
return Parser<R, W, Variant<AlternativeTypes...>, ProcessorsType>::read(
_r, _var)
.transform([](auto&& _variant) {
return TaggedUnion<_discriminator, AlternativeTypes...>(
std::move(_variant));
});
} else {
const auto get_disc =
[&_r](InputObjectOrArrayType _obj_or_arr) -> Result<std::string> {
return get_discriminator(_r, _obj_or_arr);
};
const auto to_result =
[&_r, _var](const std::string& _disc_value) -> ResultType {
return find_matching_alternative(
_r, _disc_value, _var,
std::make_integer_sequence<int, sizeof...(AlternativeTypes)>());
};
if constexpr (no_field_names_) {
return _r.to_array(_var).and_then(get_disc).and_then(to_result);
} else {
return _r.to_object(_var).and_then(get_disc).and_then(to_result);
}
}
}
template <class P>
static void write(
const W& _w,
const TaggedUnion<_discriminator, AlternativeTypes...>& _tagged_union,
const P& _parent) {
if constexpr (schemaful::IsSchemafulWriter<W>) {
Parser<R, W, Variant<AlternativeTypes...>, ProcessorsType>::write(
_w, _tagged_union.variant(), _parent);
} else {
rfl::visit([&](const auto& _val) { write_wrapped(_w, _val, _parent); },
_tagged_union.variant());
}
}
static schema::Type to_schema(
std::map<std::string, schema::Type>* _definitions) noexcept {
if constexpr (schemaful::IsSchemafulReader<R> &&
schemaful::IsSchemafulWriter<W>) {
return Parser<R, W, Variant<AlternativeTypes...>,
ProcessorsType>::to_schema(_definitions);
} else {
using VariantType = std::variant<std::invoke_result_t<
decltype(wrap_if_necessary<AlternativeTypes>), AlternativeTypes>...>;
return Parser<R, W, VariantType, ProcessorsType>::to_schema(_definitions);
}
}
private:
template <int... _is>
static ResultType find_matching_alternative(
const R& _r, const std::string& _disc_value, const InputVarType& _var,
std::integer_sequence<int, _is...>) noexcept {
using PossibleTags =
possible_tags_t<TaggedUnion<_discriminator, AlternativeTypes...>>;
static_assert(!PossibleTags::has_duplicates(),
"Duplicate tags are not allowed inside tagged unions.");
ResultType res = error("");
bool match_found = false;
(set_if_disc_value_matches<_is>(_r, _disc_value, _var, &res, &match_found),
...);
if (match_found) [[likely]] {
return res;
} else {
const auto names = PossibleTags::names();
std::stringstream stream;
stream << "Could not parse tagged union, could not match "
<< _discriminator.str() << " '" << _disc_value
<< "'. The following tags are allowed: "
<< internal::strings::join(", ", names);
return error(stream.str());
}
}
template <int _i>
static void set_if_disc_value_matches(const R& _r,
const std::string& _disc_value,
const InputVarType& _var,
ResultType* _result,
bool* _match_found) {
using AlternativeType = std::remove_cvref_t<
std::variant_alternative_t<_i, std::variant<AlternativeTypes...>>>;
if (!*_match_found && contains_disc_value<AlternativeType>(_disc_value)) {
const auto get_fields = [](auto&& _val) -> AlternativeType {
if constexpr (is_tagged_union_wrapper_v<decltype(_val)>) {
return std::move(_val.fields());
} else {
return std::move(_val);
}
};
const auto to_tagged_union = [](auto&& _val) {
return TaggedUnion<_discriminator, AlternativeTypes...>(
std::move(_val));
};
const auto embellish_error = [&](auto&& _e) {
std::stringstream stream;
stream << "Could not parse tagged union with "
"discrimininator "
<< _discriminator.str() << " '" << _disc_value
<< "': " << _e.what();
return Error(stream.str());
};
if constexpr (no_field_names_) {
using T = tagged_union_wrapper_no_ptr_t<std::invoke_result_t<
decltype(wrap_if_necessary<AlternativeType>), AlternativeType>>;
*_result = Parser<R, W, T, ProcessorsType>::read(_r, _var)
.transform(get_fields)
.transform(to_tagged_union)
.transform_error(embellish_error);
} else {
*_result = Parser<R, W, AlternativeType, ProcessorsType>::read(_r, _var)
.transform(to_tagged_union)
.transform_error(embellish_error);
}
*_match_found = true;
}
}
/// Retrieves the discriminator from an object
static Result<std::string> get_discriminator(
const R& _r, const InputObjectOrArrayType& _obj_or_arr) noexcept {
const auto to_type = [&_r](auto _var) {
return _r.template to_basic_type<std::string>(_var);
};
const auto embellish_error = [](const auto&) {
std::stringstream stream;
stream << "Could not parse tagged union: Could not find field '"
<< _discriminator.str() << "' or type of field was not a string.";
return Error(stream.str());
};
if constexpr (no_field_names_) {
return _r.get_field_from_array(0, _obj_or_arr)
.and_then(to_type)
.transform_error(embellish_error);
} else {
return _r.get_field_from_object(_discriminator.str(), _obj_or_arr)
.and_then(to_type)
.transform_error(embellish_error);
}
}
/// Determines whether the discriminating literal contains the value
/// retrieved from the object.
template <class T>
static inline bool contains_disc_value(
const std::string& _disc_value) noexcept {
return internal::tag_t<_discriminator, T>::contains(_disc_value);
}
/// Writes a wrapped version of the original object, which contains the tag.
template <class T, class P>
static void write_wrapped(const W& _w, const T& _val, const P& _parent) {
const auto wrapped = wrap_if_necessary<T>(_val);
Parser<R, W, std::remove_cvref_t<decltype(wrapped)>, ProcessorsType>::write(
_w, wrapped, _parent);
}
/// Generates a wrapped version of the original object, which contains the
/// tag, if the object doesn't already contain the wrap.
template <class T>
static auto wrap_if_necessary(const T& _val) {
if constexpr (named_tuple_t<T>::Names::template contains<
_discriminator>()) {
return _val;
} else {
const auto tag = internal::make_tag<_discriminator, T>(_val);
using TagType = std::remove_cvref_t<decltype(tag)>;
if constexpr (internal::has_fields<std::remove_cvref_t<T>>()) {
using WrapperType =
TaggedUnionWrapperWithFields<T, TagType, _discriminator>;
return WrapperType{.tag = tag, .fields = &_val};
} else {
using WrapperType =
TaggedUnionWrapperNoFields<T, TagType, _discriminator>;
return WrapperType{.tag = tag, .fields = &_val};
}
}
}
};
} // namespace rfl::parsing
#endif

View File

@@ -0,0 +1,23 @@
#ifndef RFL_PARSING_PARSER_TUPLE_HPP_
#define RFL_PARSING_PARSER_TUPLE_HPP_
#include <tuple>
#include "Parser_base.hpp"
#include "TupleParser.hpp"
namespace rfl {
namespace parsing {
template <class R, class W, class... Ts, class ProcessorsType>
requires AreReaderAndWriter<R, W, std::tuple<Ts...>>
struct Parser<R, W, std::tuple<Ts...>, ProcessorsType>
: public TupleParser<R, W, /*_ignore_empty_containers=*/false,
/*_all_required=*/ProcessorsType::all_required_,
ProcessorsType, std::tuple<Ts...>> {
};
} // namespace parsing
} // namespace rfl
#endif

View File

@@ -0,0 +1,81 @@
#ifndef RFL_PARSING_PARSER_UNIQUE_PTR_HPP_
#define RFL_PARSING_PARSER_UNIQUE_PTR_HPP_
#include <map>
#include <memory>
#include <type_traits>
#include "../Ref.hpp"
#include "../Result.hpp"
#include "../always_false.hpp"
#include "Parent.hpp"
#include "Parser_base.hpp"
#include "schema/Type.hpp"
#include "schemaful/IsSchemafulReader.hpp"
#include "schemaful/IsSchemafulWriter.hpp"
#include "schemaful/UniquePtrReader.hpp"
namespace rfl {
namespace parsing {
template <class R, class W, class T, class ProcessorsType>
requires AreReaderAndWriter<R, W, std::unique_ptr<T>>
struct Parser<R, W, std::unique_ptr<T>, ProcessorsType> {
using InputVarType = typename R::InputVarType;
using ParentType = Parent<W>;
static Result<std::unique_ptr<T>> read(const R& _r,
const InputVarType& _var) noexcept {
if constexpr (schemaful::IsSchemafulReader<R>) {
using S = schemaful::UniquePtrReader<R, W, std::remove_cvref_t<T>,
ProcessorsType>;
const auto to_unique = [&](const auto& _u) -> Result<std::unique_ptr<T>> {
return _r.template read_union<std::unique_ptr<T>, S>(_u);
};
return _r.to_union(_var).and_then(to_unique);
} else {
if (_r.is_empty(_var)) {
return std::unique_ptr<T>();
}
return Parser<R, W, std::remove_cvref_t<T>, ProcessorsType>::read(_r,
_var)
.transform([](T&& _t) { return std::make_unique<T>(std::move(_t)); });
}
}
template <class P>
static void write(const W& _w, const std::unique_ptr<T>& _s,
const P& _parent) {
if constexpr (schemaful::IsSchemafulWriter<W>) {
auto u = ParentType::add_union(_w, _parent);
using UnionType = typename ParentType::template Union<decltype(u)>;
auto p =
UnionType{.index_ = static_cast<size_t>(_s ? 0 : 1), .union_ = &u};
if (_s) {
Parser<R, W, std::remove_cvref_t<T>, ProcessorsType>::write(_w, *_s, p);
} else {
ParentType::add_null(_w, p);
}
} else {
if (_s) {
Parser<R, W, std::remove_cvref_t<T>, ProcessorsType>::write(_w, *_s,
_parent);
} else {
ParentType::add_null(_w, _parent);
}
}
}
static schema::Type to_schema(
std::map<std::string, schema::Type>* _definitions) {
using U = std::remove_cvref_t<T>;
return schema::Type{schema::Type::Optional{Ref<schema::Type>::make(
Parser<R, W, U, ProcessorsType>::to_schema(_definitions))}};
}
};
} // namespace parsing
} // namespace rfl
#endif

View File

@@ -0,0 +1,244 @@
#ifndef RFL_PARSING_PARSER_VARIANT_HPP_
#define RFL_PARSING_PARSER_VARIANT_HPP_
#include <map>
#include <optional>
#include <type_traits>
#include <variant>
#include "../NamedTuple.hpp"
#include "../Ref.hpp"
#include "../Result.hpp"
#include "../Variant.hpp"
#include "../always_false.hpp"
#include "../internal/all_fields.hpp"
#include "../internal/to_ptr_field.hpp"
#include "FieldVariantParser.hpp"
#include "Parent.hpp"
#include "Parser_base.hpp"
#include "VariantAlternativeWrapper.hpp"
#include "schema/Type.hpp"
#include "schemaful/IsSchemafulReader.hpp"
#include "schemaful/IsSchemafulWriter.hpp"
#include "schemaful/VariantReader.hpp"
#include "to_single_error_message.hpp"
namespace rfl::parsing {
template <class R, class W, class... AlternativeTypes, class ProcessorsType>
requires AreReaderAndWriter<R, W, std::variant<AlternativeTypes...>>
class Parser<R, W, std::variant<AlternativeTypes...>, ProcessorsType> {
template <class T>
using ptr_field_t =
decltype(internal::to_ptr_field(std::declval<const T&>()));
using ParentType = Parent<W>;
public:
using InputVarType = typename R::InputVarType;
static Result<std::variant<AlternativeTypes...>> read(
const R& _r, const InputVarType& _var) noexcept {
if constexpr (internal::all_fields<std::tuple<AlternativeTypes...>>()) {
if constexpr (schemaful::IsSchemafulReader<R>) {
using WrappedType = rfl::Variant<NamedTuple<AlternativeTypes>...>;
return Parser<R, W, WrappedType, ProcessorsType>::read(_r, _var)
.transform(
[](auto&& _variant) -> std::variant<AlternativeTypes...> {
return std::move(_variant).visit([](auto&& _named_tuple) {
return std::variant<AlternativeTypes...>(std::move(
std::move(_named_tuple).fields().template get<0>()));
});
});
} else {
const auto wrap = [](auto&& _v) {
return std::variant<AlternativeTypes...>(std::move(_v));
};
const auto to_std_variant = [&](auto&& _v) {
return rfl::visit(wrap, std::move(_v));
};
return FieldVariantParser<R, W, ProcessorsType,
AlternativeTypes...>::read(_r, _var)
.transform(to_std_variant);
}
} else if constexpr (schemaful::IsSchemafulReader<R>) {
using V =
schemaful::VariantReader<R, W, std::variant<AlternativeTypes...>,
ProcessorsType, AlternativeTypes...>;
return _r.to_union(_var).and_then([&](const auto& _u) {
return _r.template read_union<std::variant<AlternativeTypes...>, V>(_u);
});
} else if constexpr (ProcessorsType::add_tags_to_variants_ ||
ProcessorsType::add_namespaced_tags_to_variants_) {
constexpr bool remove_namespaces = ProcessorsType::add_tags_to_variants_;
using FieldVariantType = rfl::Variant<
VariantAlternativeWrapper<AlternativeTypes, remove_namespaces>...>;
const auto from_field_variant =
[](auto&& _field) -> std::variant<AlternativeTypes...> {
return std::move(_field.value());
};
return Parser<R, W, FieldVariantType, ProcessorsType>::read(_r, _var)
.transform([&](FieldVariantType&& _f) {
return _f.visit(from_field_variant);
});
} else {
std::optional<std::variant<AlternativeTypes...>> result;
std::vector<Error> errors;
errors.reserve(sizeof...(AlternativeTypes));
read_variant(
_r, _var, &result, &errors,
std::make_integer_sequence<int, sizeof...(AlternativeTypes)>());
if (result) {
return std::move(*result);
} else {
return error(
to_single_error_message(errors,
"Could not parse the variant. Each of the "
"possible alternatives failed "
"for the following reasons: ",
100000));
}
}
}
template <class P>
static void write(const W& _w,
const std::variant<AlternativeTypes...>& _variant,
const P& _parent) {
if constexpr (internal::all_fields<std::tuple<AlternativeTypes...>>()) {
if constexpr (schemaful::IsSchemafulWriter<W>) {
using WrappedType = rfl::Variant<
NamedTuple<Field<AlternativeTypes::name_,
const typename AlternativeTypes::Type*>>...>;
const auto to_wrapped = [](const auto& _variant) -> WrappedType {
return std::visit(
[](const auto& _field) -> WrappedType {
return make_named_tuple(internal::to_ptr_field(_field));
},
_variant);
};
Parser<R, W, WrappedType, ProcessorsType>::write(
_w, to_wrapped(_variant), _parent);
} else {
const auto wrap = [](const auto& _v) {
return rfl::Variant<ptr_field_t<AlternativeTypes>...>(
internal::to_ptr_field(_v));
};
const auto to_rfl_variant = [&](const auto& _v) {
return std::visit(wrap, _v);
};
FieldVariantParser<
R, W, ProcessorsType,
ptr_field_t<AlternativeTypes>...>::write(_w,
to_rfl_variant(_variant),
_parent);
}
} else if constexpr (schemaful::IsSchemafulWriter<W>) {
return std::visit(
[&](const auto& _v) {
using Type = std::remove_cvref_t<decltype(_v)>;
auto u = ParentType::add_union(_w, _parent);
using UnionType = typename ParentType::template Union<decltype(u)>;
auto p = UnionType{.index_ = static_cast<size_t>(_variant.index()),
.union_ = &u};
Parser<R, W, Type, ProcessorsType>::write(_w, _v, p);
},
_variant);
} else if constexpr (ProcessorsType::add_tags_to_variants_ ||
ProcessorsType::add_namespaced_tags_to_variants_) {
constexpr bool remove_namespaces = ProcessorsType::add_tags_to_variants_;
using FieldVariantType =
rfl::Variant<VariantAlternativeWrapper<const AlternativeTypes*,
remove_namespaces>...>;
const auto to_field_variant =
[]<class T>(const T& _t) -> FieldVariantType {
return VariantAlternativeWrapper<const T*, remove_namespaces>(&_t);
};
Parser<R, W, FieldVariantType, ProcessorsType>::write(
_w, std::visit(to_field_variant, _variant), _parent);
} else {
const auto handle = [&](const auto& _v) {
using Type = std::remove_cvref_t<decltype(_v)>;
Parser<R, W, Type, ProcessorsType>::write(_w, _v, _parent);
};
return std::visit(handle, _variant);
}
}
static schema::Type to_schema(
std::map<std::string, schema::Type>* _definitions) {
if constexpr (internal::all_fields<std::tuple<AlternativeTypes...>>()) {
return FieldVariantParser<R, W, ProcessorsType,
AlternativeTypes...>::to_schema(_definitions);
} else if constexpr (ProcessorsType::add_tags_to_variants_ ||
ProcessorsType::add_namespaced_tags_to_variants_) {
constexpr bool remove_namespaces = ProcessorsType::add_tags_to_variants_;
using FieldVariantType = rfl::Variant<
VariantAlternativeWrapper<AlternativeTypes, remove_namespaces>...>;
return Parser<R, W, FieldVariantType, ProcessorsType>::to_schema(
_definitions);
} else {
std::vector<schema::Type> types;
build_schema(
_definitions, &types,
std::make_integer_sequence<int, sizeof...(AlternativeTypes)>());
return schema::Type{schema::Type::AnyOf{.types_ = std::move(types)}};
}
}
private:
template <size_t _i>
static void add_to_schema(std::map<std::string, schema::Type>* _definitions,
std::vector<schema::Type>* _types) noexcept {
using U = std::remove_cvref_t<
std::variant_alternative_t<_i, std::variant<AlternativeTypes...>>>;
_types->push_back(Parser<R, W, U, ProcessorsType>::to_schema(_definitions));
}
template <int... _is>
static void build_schema(std::map<std::string, schema::Type>* _definitions,
std::vector<schema::Type>* _types,
std::integer_sequence<int, _is...>) noexcept {
(add_to_schema<_is>(_definitions, _types), ...);
}
template <int _i>
static void read_one_alternative(
const R& _r, const InputVarType& _var,
std::optional<std::variant<AlternativeTypes...>>* _result,
std::vector<Error>* _errors) noexcept {
if (!*_result) {
using AltType =
std::remove_cvref_t<internal::nth_element_t<_i, AlternativeTypes...>>;
auto res = Parser<R, W, AltType, ProcessorsType>::read(_r, _var);
if (res) {
_result->emplace(std::move(*res));
} else {
_errors->emplace_back(std::move(res.error()));
}
}
}
template <int... _is>
static void read_variant(
const R& _r, const InputVarType& _var,
std::optional<std::variant<AlternativeTypes...>>* _result,
std::vector<Error>* _errors,
std::integer_sequence<int, _is...>) noexcept {
(read_one_alternative<_is>(_r, _var, _result, _errors), ...);
}
};
} // namespace rfl::parsing
#endif

View File

@@ -0,0 +1,103 @@
#ifndef RFL_PARSING_PARSER_VECTOR_LIKE_HPP_
#define RFL_PARSING_PARSER_VECTOR_LIKE_HPP_
#include <deque>
#include <forward_list>
#include <list>
#include <map>
#include <set>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include "../Result.hpp"
#include "../always_false.hpp"
#include "Parser_base.hpp"
#include "VectorParser.hpp"
namespace rfl {
namespace parsing {
template <class R, class W, class T, class ProcessorsType>
requires AreReaderAndWriter<R, W, std::deque<T>>
struct Parser<R, W, std::deque<T>, ProcessorsType>
: public VectorParser<R, W, std::deque<T>, ProcessorsType> {};
template <class R, class W, class T, class ProcessorsType>
requires AreReaderAndWriter<R, W, std::forward_list<T>>
struct Parser<R, W, std::forward_list<T>, ProcessorsType>
: public VectorParser<R, W, std::forward_list<T>, ProcessorsType> {};
template <class R, class W, class T, class ProcessorsType>
requires AreReaderAndWriter<R, W, std::list<T>>
struct Parser<R, W, std::list<T>, ProcessorsType>
: public VectorParser<R, W, std::list<T>, ProcessorsType> {};
template <class R, class W, class K, class V, class ProcessorsType>
requires AreReaderAndWriter<R, W, std::map<K, V>>
struct Parser<R, W, std::map<K, V>, ProcessorsType>
: public VectorParser<R, W, std::map<K, V>, ProcessorsType> {};
template <class R, class W, class K, class V, class ProcessorsType>
requires AreReaderAndWriter<R, W, std::multimap<K, V>>
struct Parser<R, W, std::multimap<K, V>, ProcessorsType>
: public VectorParser<R, W, std::multimap<K, V>, ProcessorsType> {};
template <class R, class W, class T, class ProcessorsType>
requires AreReaderAndWriter<R, W, std::multiset<T>>
struct Parser<R, W, std::multiset<T>, ProcessorsType>
: public VectorParser<R, W, std::multiset<T>, ProcessorsType> {};
template <class R, class W, class T, class ProcessorsType>
requires AreReaderAndWriter<R, W, std::set<T>>
struct Parser<R, W, std::set<T>, ProcessorsType>
: public VectorParser<R, W, std::set<T>, ProcessorsType> {};
template <class R, class W, class K, class V, class Hash, class KeyEqual,
class Allocator, class ProcessorsType>
requires AreReaderAndWriter<
R, W, std::unordered_map<K, V, Hash, KeyEqual, Allocator>>
struct Parser<R, W, std::unordered_map<K, V, Hash, KeyEqual, Allocator>,
ProcessorsType>
: public VectorParser<R, W,
std::unordered_map<K, V, Hash, KeyEqual, Allocator>,
ProcessorsType> {};
template <class R, class W, class T, class Hash, class KeyEqual,
class Allocator, class ProcessorsType>
requires AreReaderAndWriter<
R, W, std::unordered_multiset<T, Hash, KeyEqual, Allocator>>
struct Parser<R, W, std::unordered_multiset<T, Hash, KeyEqual, Allocator>,
ProcessorsType>
: public VectorParser<R, W,
std::unordered_multiset<T, Hash, KeyEqual, Allocator>,
ProcessorsType> {};
template <class R, class W, class K, class V, class Hash, class KeyEqual,
class Allocator, class ProcessorsType>
requires AreReaderAndWriter<
R, W, std::unordered_multimap<K, V, Hash, KeyEqual, Allocator>>
struct Parser<R, W, std::unordered_multimap<K, V, Hash, KeyEqual, Allocator>,
ProcessorsType>
: public VectorParser<R, W, std::unordered_multimap<K, V>, ProcessorsType> {
};
template <class R, class W, class T, class Hash, class KeyEqual,
class Allocator, class ProcessorsType>
requires AreReaderAndWriter<R, W,
std::unordered_set<T, Hash, KeyEqual, Allocator>>
struct Parser<R, W, std::unordered_set<T, Hash, KeyEqual, Allocator>,
ProcessorsType>
: public VectorParser<R, W,
std::unordered_set<T, Hash, KeyEqual, Allocator>,
ProcessorsType> {};
template <class R, class W, class T, class ProcessorsType>
requires AreReaderAndWriter<R, W, std::vector<T>>
struct Parser<R, W, std::vector<T>, ProcessorsType>
: public VectorParser<R, W, std::vector<T>, ProcessorsType> {};
} // namespace parsing
} // namespace rfl
#endif

View File

@@ -0,0 +1,38 @@
#ifndef RFL_PARSING_PARSER_VECTORSRING_HPP_
#define RFL_PARSING_PARSER_VECTORSRING_HPP_
#include <map>
#include "../Result.hpp"
#include "../Vectorstring.hpp"
#include "Parent.hpp"
#include "Parser_base.hpp"
#include "schema/Type.hpp"
namespace rfl::parsing {
template <class R, class W, class ProcessorsType>
requires AreReaderAndWriter<R, W, Vectorstring>
struct Parser<R, W, Vectorstring, ProcessorsType> {
using InputVarType = typename R::InputVarType;
using ParentType = Parent<W>;
static Result<Vectorstring> read(const R& _r,
const InputVarType& _var) noexcept {
return _r.template to_basic_type<Vectorstring>(_var);
}
template <class P>
static void write(const W& _w, const Vectorstring& _b, const P& _parent) {
ParentType::add_value(_w, _b, _parent);
}
static schema::Type to_schema(
[[maybe_unused]] std::map<std::string, schema::Type>* _definitions) {
return schema::Type{schema::Type::Vectorstring{}};
}
};
} // namespace rfl::parsing
#endif

View File

@@ -0,0 +1,81 @@
#ifndef RFL_PARSING_PARSER_WSTRING_HPP_
#define RFL_PARSING_PARSER_WSTRING_HPP_
#include <map>
#include "../Result.hpp"
#include "../always_false.hpp"
#include "Parent.hpp"
#include "Parser_base.hpp"
#include "schema/Type.hpp"
namespace rfl {
namespace parsing {
template <class R, class W, class ProcessorsType>
requires AreReaderAndWriter<R, W, std::wstring>
struct Parser<R, W, std::wstring, ProcessorsType> {
public:
using InputVarType = typename R::InputVarType;
using ParentType = Parent<W>;
static Result<std::wstring> read(const R& _r,
const InputVarType& _var) noexcept {
if (_r.is_empty(_var)) {
return std::wstring();
}
auto inStr = Parser<R, W, std::string, ProcessorsType>::read(_r, _var);
if (!inStr) {
return Result<std::wstring>(error(inStr.error()));
}
// if (auto err = inStr.error(); err.has_value()) {
// return Result<std::wstring>(err.value());
// }
std::mbstate_t state = std::mbstate_t();
auto val = inStr.value();
std::wstring outStr(val.size() * 2, L'\0');
// Explicitly set the size so we don't empty it when we truncate
outStr.resize(val.size() * 2);
auto* ptr = val.c_str();
// Add 1 for null terminator
auto len = std::mbsrtowcs(outStr.data(), &ptr, val.size(), &state);
outStr.resize(len); // Truncate the extra bytes
return Result<std::wstring>(outStr);
}
template <class P>
static void write(const W& _w, const std::wstring& _str, const P& _parent) {
if (_str.empty()) {
ParentType::add_value(_w, std::string(), _parent);
return;
}
std::mbstate_t state = std::mbstate_t();
std::string outStr(_str.size(), '\0');
outStr.resize(_str.size());
auto* ptr = _str.c_str();
auto len = std::wcsrtombs(outStr.data(), &ptr, _str.size(), &state);
outStr.resize(len);
ParentType::add_value(_w, outStr, _parent);
}
static schema::Type to_schema(
std::map<std::string, schema::Type>* _definitions) {
return Parser<R, W, std::string, ProcessorsType>::to_schema(_definitions);
}
};
} // namespace parsing
} // namespace rfl
#endif

View File

@@ -0,0 +1,30 @@
#ifndef RFL_PARSING_SUPPORTSTAGGEDUNIONS_HPP_
#define RFL_PARSING_SUPPORTSTAGGEDUNIONS_HPP_
#include <concepts>
#include <string>
#include "../Result.hpp"
#include "../internal/is_basic_type.hpp"
#include "../internal/wrap_in_rfl_array_t.hpp"
namespace rfl::parsing {
template <class R>
concept SupportsTaggedUnions = requires(
R r, std::string name, typename R::InputArrayType arr,
typename R::InputObjectType obj, typename R::InputVarType var, size_t idx) {
/// Retrieves a particular field from an array.
{
r.get_field_from_array(idx, arr)
} -> std::same_as<rfl::Result<typename R::InputVarType>>;
/// Retrieves a particular field from an object.
{
r.get_field_from_object(name, obj)
} -> std::same_as<rfl::Result<typename R::InputVarType>>;
};
} // namespace rfl::parsing
#endif

View File

@@ -0,0 +1,37 @@
#ifndef RFL_PARSING_TAGGEDUNIONWRAPPER_HPP_
#define RFL_PARSING_TAGGEDUNIONWRAPPER_HPP_
#include <type_traits>
#include "../Field.hpp"
#include "../Flatten.hpp"
#include "../Rename.hpp"
#include "../internal/StringLiteral.hpp"
namespace rfl {
namespace parsing {
template <class T, class TagType, internal::StringLiteral _discriminator,
bool _as_const_pointer = true>
struct TaggedUnionWrapperNoFields {
using Type = std::remove_cvref_t<T>;
using FlattenedType =
std::conditional_t<_as_const_pointer, const Type*, Type>;
rfl::Rename<_discriminator, TagType> tag;
rfl::Flatten<FlattenedType> fields;
};
template <class T, class TagType, internal::StringLiteral _discriminator,
bool _as_const_pointer = true>
struct TaggedUnionWrapperWithFields {
using Type = T;
using FlattenedType =
std::conditional_t<_as_const_pointer, const Type*, Type>;
rfl::Field<_discriminator, TagType> tag;
rfl::Flatten<FlattenedType> fields;
};
} // namespace parsing
} // namespace rfl
#endif

View File

@@ -0,0 +1,133 @@
#ifndef RFL_PARSING_TUPLEPARSER_HPP_
#define RFL_PARSING_TUPLEPARSER_HPP_
#include <cstddef>
#include <map>
#include <type_traits>
#include <utility>
#include "../Result.hpp"
#include "../Tuple.hpp"
#include "../always_false.hpp"
#include "../internal/nth_element_t.hpp"
#include "../internal/ptr_cast.hpp"
#include "Parent.hpp"
#include "TupleReader.hpp"
#include "call_destructors_on_tuple_where_necessary.hpp"
#include "schema/Type.hpp"
#include "schemaful/IsSchemafulReader.hpp"
#include "schemaful/IsSchemafulWriter.hpp"
#include "schemaful/tuple_to_named_tuple.hpp"
#include "schemaful/tuple_to_named_tuple_t.hpp"
namespace rfl::parsing {
template <class R, class W, bool _ignore_empty_containers, bool _all_required,
class ProcessorsType, class TupleType>
requires AreReaderAndWriter<R, W, TupleType>
struct TupleParser {
public:
using InputArrayType = typename R::InputArrayType;
using InputVarType = typename R::InputVarType;
using ParentType = Parent<W>;
static Result<TupleType> read(const R& _r,
const InputVarType& _var) noexcept {
if constexpr (schemaful::IsSchemafulReader<R>) {
using NamedTupleType = schemaful::tuple_to_named_tuple_t<TupleType>;
const auto to_tuple = [](auto&& _named_tuple) {
return [&]<int... _is>(std::integer_sequence<int, _is...>) {
return TupleType(std::move(rfl::get<_is>(_named_tuple))...);
}(std::make_integer_sequence<int, NamedTupleType::size()>());
};
return Parser<R, W, NamedTupleType, ProcessorsType>::read(_r, _var)
.transform(to_tuple);
} else {
const auto parse = [&](const InputArrayType& _arr) -> Result<TupleType> {
alignas(TupleType) unsigned char buf[sizeof(TupleType)]{};
auto ptr = internal::ptr_cast<TupleType*>(&buf);
const auto tuple_reader =
TupleReader<R, W, TupleType, _ignore_empty_containers,
_all_required, ProcessorsType>(&_r, ptr);
auto err = _r.read_array(tuple_reader, _arr);
if (err) {
call_destructors_on_tuple_where_necessary(tuple_reader.num_set(),
ptr);
return error(*err);
}
err = tuple_reader.handle_missing_fields();
if (err) {
call_destructors_on_tuple_where_necessary(tuple_reader.num_set(),
ptr);
return error(*err);
}
auto res = Result<TupleType>(std::move(*ptr));
call_destructors_on_tuple_where_necessary(tuple_reader.num_set(), ptr);
return res;
};
return _r.to_array(_var).and_then(parse);
}
}
template <class P>
static void write(const W& _w, const TupleType& _tup, const P& _parent) {
if constexpr (schemaful::IsSchemafulWriter<W>) {
const auto named_tuple = schemaful::tuple_to_named_tuple(_tup);
Parser<R, W, std::remove_cvref_t<decltype(named_tuple)>,
ProcessorsType>::write(_w, named_tuple, _parent);
} else {
constexpr auto size = rfl::tuple_size_v<TupleType>;
auto arr = ParentType::add_array(_w, size, _parent);
const auto new_parent = typename ParentType::Array{&arr};
to_array(_w, _tup, new_parent, std::make_integer_sequence<int, size>());
_w.end_array(&arr);
}
}
static schema::Type to_schema(
std::map<std::string, schema::Type>* _definitions) {
std::vector<schema::Type> types;
build_schema(
_definitions, &types,
std::make_integer_sequence<int, rfl::tuple_size_v<TupleType>>());
return schema::Type{schema::Type::Tuple{.types_ = std::move(types)}};
}
private:
template <size_t _i>
static void add_to_schema(std::map<std::string, schema::Type>* _definitions,
std::vector<schema::Type>* _types) noexcept {
using U = std::remove_cvref_t<rfl::tuple_element_t<_i, TupleType>>;
_types->push_back(Parser<R, W, U, ProcessorsType>::to_schema(_definitions));
}
template <int... _is>
static void build_schema(std::map<std::string, schema::Type>* _definitions,
std::vector<schema::Type>* _types,
std::integer_sequence<int, _is...>) noexcept {
(add_to_schema<_is>(_definitions, _types), ...);
}
template <int _i, class P>
static void add_to_array(const W& _w, const TupleType& _tup,
const P& _parent) {
using NewFieldType =
std::remove_cvref_t<rfl::tuple_element_t<_i, TupleType>>;
Parser<R, W, NewFieldType, ProcessorsType>::write(_w, rfl::get<_i>(_tup),
_parent);
}
template <int... _is, class P>
static void to_array(const W& _w, const TupleType& _tup, const P& _parent,
std::integer_sequence<int, _is...>) {
(add_to_array<_is>(_w, _tup, _parent), ...);
}
};
} // namespace rfl::parsing
#endif

View File

@@ -0,0 +1,127 @@
#ifndef RFL_PARSING_TUPLEREADER_HPP_
#define RFL_PARSING_TUPLEREADER_HPP_
#include <sstream>
#include <type_traits>
#include "../Result.hpp"
#include "../Tuple.hpp"
#include "../internal/is_array.hpp"
#include "AreReaderAndWriter.hpp"
#include "Parser_base.hpp"
#include "is_required.hpp"
namespace rfl::parsing {
template <class R, class W, class TupleType, bool _ignore_empty_containers,
bool _all_required, class ProcessorsType>
class TupleReader {
private:
using InputVarType = typename R::InputVarType;
static constexpr size_t size_ = rfl::tuple_size_v<TupleType>;
public:
TupleReader(const R* _r, TupleType* _tuple)
: num_set_(0), r_(_r), tuple_(_tuple) {}
~TupleReader() = default;
std::optional<Error> handle_missing_fields() const {
std::optional<Error> err;
if (num_set_ < size_) {
handle_missing_fields_impl(&err);
}
return err;
}
size_t num_set() const { return num_set_; }
std::optional<Error> read(const InputVarType& _var) const {
std::optional<Error> err;
read_impl(_var, &err);
return err;
}
private:
template <int _i = 0>
void handle_missing_fields_impl(std::optional<Error>* _err) const noexcept {
if constexpr (_i < size_) {
if (num_set_ == _i) {
using CurrentType =
std::remove_cvref_t<rfl::tuple_element_t<_i, TupleType>>;
if constexpr (_all_required ||
is_required<CurrentType, _ignore_empty_containers>()) {
*_err = Error("Field " + std::to_string(_i) +
" was required, but missing.");
return;
} else {
::new (&(rfl::get<_i>(*tuple_))) CurrentType();
++num_set_;
}
}
handle_missing_fields_impl<_i + 1>(_err);
}
}
template <size_t _i = 0>
void read_impl(const InputVarType& _var, std::optional<Error>* _err) const {
if constexpr (_i < size_) {
if (num_set_ == _i) {
using CurrentType =
std::remove_cvref_t<rfl::tuple_element_t<_i, TupleType>>;
auto res = Parser<R, W, CurrentType, ProcessorsType>::read(*r_, _var);
if (res) {
move_to(&(rfl::get<_i>(*tuple_)), &(*res));
++num_set_;
} else {
std::stringstream stream;
stream << "Failed to parse field " << _i << ": "
<< res.error().what();
*_err = Error(stream.str());
}
return;
}
read_impl<_i + 1>(_var, _err);
} else {
std::stringstream stream;
stream << "Expected " << size_ << " fields, but got at least one more.";
*_err = Error(stream.str());
return;
}
}
template <class Target, class Source>
void move_to(Target* _t, Source* _s) const {
if constexpr (std::is_const_v<Target>) {
return move_to(const_cast<std::remove_const_t<Target>*>(_t), _s);
} else if constexpr (!internal::is_array_v<Source> &&
!std::is_array_v<Target>) {
::new (_t) Target(std::move(*_s));
} else if constexpr (internal::is_array_v<Source>) {
for (size_t i = 0; i < _s->arr_.size(); ++i) {
move_to(&((*_t)[i]), &(_s->arr_[i]));
}
} else {
for (size_t i = 0; i < _s->size(); ++i) {
move_to(&((*_t)[i]), &((*_s)[i]));
}
}
}
private:
/// Indicates the last field that was set.
mutable size_t num_set_;
/// The underlying reader.
const R* r_;
/// The underlying tuple.
TupleType* tuple_;
};
} // namespace rfl::parsing
#endif

View File

@@ -0,0 +1,50 @@
#ifndef RFL_PARSING_VARIANTALTERNATIVEWRAPPER_HPP_
#define RFL_PARSING_VARIANTALTERNATIVEWRAPPER_HPP_
#include <type_traits>
#include "../Field.hpp"
#include "../Literal.hpp"
#include "../internal/StringLiteral.hpp"
#include "../internal/get_type_name.hpp"
#include "../internal/has_tag_v.hpp"
#include "../internal/remove_namespaces.hpp"
namespace rfl::parsing {
namespace vaw {
template <class T>
struct GetName {};
template <internal::StringLiteral _name>
struct GetName<Literal<_name>> {
constexpr static internal::StringLiteral name_ = _name;
};
template <class T, bool _remove_namespaces = true>
consteval auto make_tag() {
if constexpr (internal::has_tag_v<T>) {
return typename T::Tag();
} else if constexpr (std::is_same_v<std::remove_cvref_t<T>, std::string>) {
return Literal<"std::string">();
} else if constexpr (_remove_namespaces) {
return Literal<
internal::remove_namespaces<internal::get_type_name<T>()>()>();
} else {
return Literal<internal::get_type_name<T>()>();
}
}
template <class T, bool _remove_namespaces = true>
using tag_t = std::invoke_result_t<
decltype(make_tag<std::remove_cvref_t<std::remove_pointer_t<T>>, _remove_namespaces>)>;
} // namespace vaw
template <class T, bool _remove_namespaces = true>
using VariantAlternativeWrapper = Field<vaw::GetName<vaw::tag_t<T, _remove_namespaces>>::name_, T>;
} // namespace rfl::parsing
#endif

View File

@@ -0,0 +1,128 @@
#ifndef RFL_PARSING_VECTORPARSER_HPP_
#define RFL_PARSING_VECTORPARSER_HPP_
#include <iterator>
#include <map>
#include <string>
#include <type_traits>
#include "../Bytestring.hpp"
#include "../Result.hpp"
#include "../Vectorstring.hpp"
#include "../always_false.hpp"
#include "MapParser.hpp"
#include "Parent.hpp"
#include "Parser_base.hpp"
#include "VectorReader.hpp"
#include "is_forward_list.hpp"
#include "is_map_like.hpp"
#include "is_map_like_not_multimap.hpp"
#include "is_set_like.hpp"
#include "schema/Type.hpp"
namespace rfl {
namespace parsing {
/// This can be used for data structures that would be expressed as array in
/// serialized format (std::vector, std::set, std::deque, ...),
/// but also includes map-like types, when the key is not of type
/// std::string.
template <class R, class W, class VecType, class ProcessorsType>
requires AreReaderAndWriter<R, W, VecType>
struct VectorParser {
public:
using InputArrayType = typename R::InputArrayType;
using InputVarType = typename R::InputVarType;
using ParentType = Parent<W>;
using T = typename VecType::value_type;
static_assert(!std::is_same_v<std::remove_cvref_t<VecType>, Bytestring>,
"Cannot be a bytestring.");
static_assert(!std::is_same_v<std::remove_cvref_t<VecType>, Vectorstring>,
"Cannot be a vectorstring.");
static Result<VecType> read(const R& _r, const InputVarType& _var) noexcept {
if constexpr (treat_as_map()) {
return MapParser<R, W, VecType, ProcessorsType>::read(_r, _var);
} else if constexpr (is_forward_list<VecType>()) {
const auto to_forward_list = [](auto&& vec) -> std::forward_list<T> {
std::forward_list<T> list;
for (auto it = vec.rbegin(); it != vec.rend(); ++it) {
list.emplace_front(std::move(*it));
}
return list;
};
return Parser<R, W, std::vector<T>, ProcessorsType>::read(_r, _var)
.transform(to_forward_list);
} else {
const auto parse = [&](const InputArrayType& _arr) -> Result<VecType> {
VecType vec;
auto vector_reader =
VectorReader<R, W, VecType, ProcessorsType>(&_r, &vec);
const auto err = _r.read_array(vector_reader, _arr);
if (err) {
return error(*err);
}
return vec;
};
return _r.to_array(_var).and_then(parse);
}
}
template <class P>
static void write(const W& _w, const VecType& _vec, const P& _parent) {
if constexpr (treat_as_map()) {
MapParser<R, W, VecType, ProcessorsType>::write(_w, _vec, _parent);
} else {
auto arr = ParentType::add_array(
_w, std::distance(_vec.begin(), _vec.end()), _parent);
const auto new_parent = typename ParentType::Array{&arr};
for (const auto& v : _vec) {
Parser<R, W, std::remove_cvref_t<T>, ProcessorsType>::write(_w, v,
new_parent);
}
_w.end_array(&arr);
}
}
/// Generates a schema for the underlying type.
static schema::Type to_schema(
std::map<std::string, schema::Type>* _definitions) {
if constexpr (treat_as_map()) {
return MapParser<R, W, VecType, ProcessorsType>::to_schema(_definitions);
} else {
using Type = schema::Type;
return Type{Type::TypedArray{
.type_ = Ref<Type>::make(
Parser<R, W, T, ProcessorsType>::to_schema(_definitions))}};
}
}
private:
static constexpr bool treat_as_map() {
if constexpr (is_map_like_not_multimap<VecType>()) {
if constexpr (internal::has_reflection_type_v<typename T::first_type>) {
using U = std::remove_cvref_t<typename T::first_type::ReflectionType>;
return std::is_same<U, std::string>() || std::is_integral_v<U> ||
std::is_floating_point_v<U>;
// We do not need std::string here, it is already caught by the template
// specialization.
} else if constexpr (std::is_integral_v<typename T::first_type> ||
std::is_floating_point_v<typename T::first_type>) {
return true;
} else {
return false;
}
} else {
return false;
}
}
};
} // namespace parsing
} // namespace rfl
#endif

View File

@@ -0,0 +1,70 @@
#ifndef RFL_PARSING_VECTORREADER_HPP_
#define RFL_PARSING_VECTORREADER_HPP_
#include <optional>
#include <type_traits>
#include "../Result.hpp"
#include "Parser_base.hpp"
#include "is_map_like.hpp"
#include "is_set_like.hpp"
namespace rfl::parsing {
template <class R, class W, class VecType, class ProcessorsType>
class VectorReader {
private:
using InputVarType = typename R::InputVarType;
using T = typename VecType::value_type;
public:
VectorReader(const R* _r, VecType* _vec) : r_(_r), vec_(_vec) {}
~VectorReader() = default;
std::optional<Error> read(const InputVarType& _var) const {
const auto parse = [this](const InputVarType& _var) {
if constexpr (is_map_like_v<VecType>) {
return get_pair(_var);
} else {
return Parser<R, W, std::remove_cvref_t<T>, ProcessorsType>::read(*r_,
_var);
}
};
const auto insert = [this](auto&& _var) -> std::optional<Error> {
if constexpr (is_map_like_v<VecType> || is_set_like_v<VecType>) {
vec_->insert(std::move(_var));
} else {
vec_->emplace_back(std::move(_var));
}
return std::nullopt;
};
auto res = parse(_var).transform(insert);
if (res) {
return std::nullopt;
} else {
return res.error();
}
// return parse(_var).transform(insert).error();
}
private:
auto get_pair(const auto& _var) const {
using K = std::remove_cvref_t<typename T::first_type>;
using V = std::remove_cvref_t<typename T::second_type>;
return Parser<R, W, std::remove_cvref_t<std::pair<K, V>>,
ProcessorsType>::read(*r_, _var);
}
private:
/// The underlying reader.
const R* r_;
/// The underlying vector.
VecType* vec_;
};
} // namespace rfl::parsing
#endif

View File

@@ -0,0 +1,192 @@
#ifndef RFL_PARSING_VIEWREADER_HPP_
#define RFL_PARSING_VIEWREADER_HPP_
#include <array>
#include <sstream>
#include <string_view>
#include <type_traits>
#include <utility>
#include <vector>
#include "../Result.hpp"
#include "../Tuple.hpp"
#include "../internal/is_array.hpp"
#include "Parser_base.hpp"
#include "schemaful/IsSchemafulReader.hpp"
namespace rfl::parsing {
template <class R, class W, class ViewType, class ProcessorsType>
class ViewReader {
private:
using InputVarType = typename R::InputVarType;
static constexpr size_t size_ = ViewType::size();
public:
ViewReader(const R* _r, ViewType* _view, std::array<bool, size_>* _found,
std::array<bool, size_>* _set, std::vector<Error>* _errors)
: r_(_r), view_(_view), found_(_found), set_(_set), errors_(_errors) {}
~ViewReader() = default;
/// Assigns the parsed version of _var to the field signified by _name, if
/// such a field exists in the underlying view.
void read(const std::string_view& _name, const InputVarType& _var) const {
assign_to_matching_field(*r_, _name, _var, view_, errors_, found_, set_,
std::make_integer_sequence<int, size_>());
}
/// Assigns the parsed version of _var to the field signified by _index, if
/// such a field exists in the underlying view.
/// Note that schemaful formats can assign by index.
void read(const int _index, const InputVarType& _var) const {
assign_to_matching_field(*r_, _index, _var, view_, errors_, found_, set_,
std::make_integer_sequence<int, size_>());
}
private:
template <int i, class FieldType>
static bool is_matching(const int _current_index) {
return _current_index == i;
}
template <int i, class FieldType>
static bool is_matching(const std::string_view& _current_name) {
return _current_name == FieldType::name();
}
template <int i>
static void assign_if_field_matches(const R& _r,
const auto _current_name_or_index,
const auto& _var, auto* _view,
auto* _errors, auto* _found, auto* _set,
bool* _already_assigned) {
using FieldType = tuple_element_t<i, typename ViewType::Fields>;
using OriginalType = typename FieldType::Type;
using T =
std::remove_cvref_t<std::remove_pointer_t<typename FieldType::Type>>;
constexpr auto name = FieldType::name();
if (!(*_already_assigned) && !std::get<i>(*_found) &&
is_matching<i, FieldType>(_current_name_or_index)) {
std::get<i>(*_found) = true;
*_already_assigned = true;
auto res = Parser<R, W, T, ProcessorsType>::read(_r, _var);
if (!res) {
std::stringstream stream;
stream << "Failed to parse field '" << std::string(name)
<< "': " << res.error().what();
_errors->emplace_back(Error(stream.str()));
return;
}
if constexpr (std::is_pointer_v<OriginalType>) {
move_to(rfl::get<i>(*_view), &(*res));
} else {
rfl::get<i>(*_view) = std::move(*res);
}
std::get<i>(*_set) = true;
}
}
template <int _pos>
static void assign_to_extra_fields(const R& _r,
const std::string_view& _current_name,
const auto& _var, auto* _view,
auto* _errors, auto* _found, auto* _set) {
auto* extra_fields = _view->template get<_pos>();
using ExtraFieldsType =
std::remove_cvref_t<std::remove_pointer_t<decltype(extra_fields)>>;
using T = std::remove_cvref_t<
std::remove_pointer_t<typename ExtraFieldsType::Type>>;
if (!std::get<_pos>(*_set)) {
::new (extra_fields) ExtraFieldsType();
std::get<_pos>(*_set) = true;
std::get<_pos>(*_found) = true;
}
auto res = Parser<R, W, T, ProcessorsType>::read(_r, _var);
if (!res) {
std::stringstream stream;
stream << "Failed to parse field '" << _current_name
<< "': " << res.error().what();
_errors->emplace_back(Error(stream.str()));
return;
}
extra_fields->emplace(std::string(_current_name), std::move(*res));
}
template <int... is>
static void assign_to_matching_field(const R& _r,
const auto _current_name_or_index,
const auto& _var, auto* _view,
auto* _errors, auto* _found, auto* _set,
std::integer_sequence<int, is...>) {
bool already_assigned = false;
(assign_if_field_matches<is>(_r, _current_name_or_index, _var, _view,
_errors, _found, _set, &already_assigned),
...);
if constexpr (ViewType::pos_extra_fields() != -1) {
static_assert(!schemaful::IsSchemafulReader<R>,
"rfl::ExtraFields are not supported for schemaful formats, "
"because schemaful formats cannot have extra fields.");
constexpr int pos = ViewType::pos_extra_fields();
if (!already_assigned) {
assign_to_extra_fields<pos>(_r, _current_name_or_index, _var, _view,
_errors, _found, _set);
}
} else if constexpr (ProcessorsType::no_extra_fields_) {
static_assert(
!schemaful::IsSchemafulReader<R>,
"Passing rfl::NoExtraFields to a schemaful format does not make "
"sense, because schemaful formats cannot have extra fields.");
if (!already_assigned) {
std::stringstream stream;
stream << "Value named '" << _current_name_or_index
<< "' not used. Remove the rfl::NoExtraFields processor or add "
"rfl::ExtraFields to avoid this error message.";
_errors->emplace_back(Error(stream.str()));
}
}
}
template <class Target, class Source>
static void move_to(Target* _t, Source* _s) {
if constexpr (std::is_const_v<Target>) {
return move_to(const_cast<std::remove_const_t<Target>*>(_t), _s);
} else if constexpr (!rfl::internal::is_array_v<Source> &&
!std::is_array_v<Target>) {
::new (_t) Target(std::move(*_s));
} else if constexpr (rfl::internal::is_array_v<Source>) {
static_assert(std::is_array_v<Target>,
"Expected target to be a c-array.");
for (size_t i = 0; i < _s->arr_.size(); ++i) {
move_to(&((*_t)[i]), &(_s->arr_[i]));
}
} else {
for (size_t i = 0; i < _s->size(); ++i) {
move_to(&((*_t)[i]), &((*_s)[i]));
}
}
}
private:
/// The underlying reader.
const R* r_;
/// The underlying view.
ViewType* view_;
/// Indicates that a certain field has been found.
std::array<bool, size_>* found_;
/// Indicates that a certain field has been successfully set - necessary,
/// because we have to trigger the destructors manually.
std::array<bool, size_>* set_;
/// Collects any errors we may have come across.
std::vector<Error>* errors_;
};
} // namespace rfl::parsing
#endif

View File

@@ -0,0 +1,160 @@
#ifndef RFL_PARSING_VIEWREADERWITHDEFAULT_HPP_
#define RFL_PARSING_VIEWREADERWITHDEFAULT_HPP_
#include <array>
#include <sstream>
#include <string_view>
#include <type_traits>
#include <utility>
#include <vector>
#include "../Ref.hpp"
#include "../Result.hpp"
#include "../Tuple.hpp"
#include "../internal/is_array.hpp"
#include "Parser_base.hpp"
namespace rfl::parsing {
template <class R, class W, class ViewType, class ProcessorsType>
class ViewReaderWithDefault {
private:
using InputVarType = typename R::InputVarType;
static constexpr size_t size_ = ViewType::size();
public:
ViewReaderWithDefault(const R* _r, ViewType* _view,
std::vector<Error>* _errors)
: r_(_r), view_(_view), errors_(_errors) {
found_->fill(false);
}
~ViewReaderWithDefault() = default;
const std::array<bool, size_>& found() const { return *found_; }
/// Assigns the parsed version of _var to the field signified by _name, if
/// such a field exists in the underlying view.
void read(const std::string_view& _name, const InputVarType& _var) const {
assign_to_matching_field(*r_, _name, _var, view_, errors_, found_.get(),
std::make_integer_sequence<int, size_>());
}
private:
template <int i>
static void assign_if_field_matches(const R& _r,
const std::string_view& _current_name,
const auto& _var, auto* _view,
auto* _errors, auto* _found,
bool* _already_assigned) {
using FieldType = tuple_element_t<i, typename ViewType::Fields>;
using OriginalType = typename FieldType::Type;
using T =
std::remove_cvref_t<std::remove_pointer_t<typename FieldType::Type>>;
constexpr auto name = FieldType::name();
if (!(*_already_assigned) && !std::get<i>(*_found) &&
_current_name == name) {
std::get<i>(*_found) = true;
*_already_assigned = true;
auto res = Parser<R, W, T, ProcessorsType>::read(_r, _var);
if (!res) {
std::stringstream stream;
stream << "Failed to parse field '" << std::string(name)
<< "': " << res.error().what();
_errors->emplace_back(Error(stream.str()));
return;
}
if constexpr (std::is_pointer_v<OriginalType>) {
move_to(rfl::get<i>(*_view), &(*res));
} else {
rfl::get<i>(*_view) = std::move(*res);
}
}
}
template <int _pos>
static void assign_to_extra_fields(const R& _r,
const std::string_view& _current_name,
const auto& _var, auto* _view,
auto* _errors) {
auto* extra_fields = _view->template get<_pos>();
using ExtraFieldsType =
std::remove_cvref_t<std::remove_pointer_t<decltype(extra_fields)>>;
using T = std::remove_cvref_t<
std::remove_pointer_t<typename ExtraFieldsType::Type>>;
auto res = Parser<R, W, T, ProcessorsType>::read(_r, _var);
if (!res) {
std::stringstream stream;
stream << "Failed to parse field '" << _current_name
<< "': " << res.error().what();
_errors->emplace_back(Error(stream.str()));
return;
}
extra_fields->emplace(std::string(_current_name), std::move(*res));
}
template <int... is>
static void assign_to_matching_field(const R& _r,
const std::string_view& _current_name,
const auto& _var, auto* _view,
auto* _errors, auto* _found,
std::integer_sequence<int, is...>) {
bool already_assigned = false;
(assign_if_field_matches<is>(_r, _current_name, _var, _view, _errors,
_found, &already_assigned),
...);
if constexpr (ViewType::pos_extra_fields() != -1) {
constexpr int pos = ViewType::pos_extra_fields();
if (!already_assigned) {
assign_to_extra_fields<pos>(_r, _current_name, _var, _view, _errors);
}
} else if constexpr (ProcessorsType::no_extra_fields_) {
if (!already_assigned) {
std::stringstream stream;
stream << "Value named '" << std::string(_current_name)
<< "' not used. Remove the rfl::NoExtraFields processor or add "
"rfl::ExtraFields to avoid this error message.";
_errors->emplace_back(Error(stream.str()));
}
}
}
template <class Target, class Source>
static void move_to(Target* _t, Source* _s) {
if constexpr (std::is_const_v<Target>) {
return move_to(const_cast<std::remove_const_t<Target>*>(_t), _s);
} else if constexpr (!rfl::internal::is_array_v<Source> &&
!std::is_array_v<Target>) {
*_t = Target(std::move(*_s));
} else if constexpr (rfl::internal::is_array_v<Source>) {
static_assert(std::is_array_v<Target>,
"Expected target to be a c-array.");
for (size_t i = 0; i < _s->arr_.size(); ++i) {
move_to(&((*_t)[i]), &(_s->arr_[i]));
}
} else {
for (size_t i = 0; i < _s->size(); ++i) {
move_to(&((*_t)[i]), &((*_s)[i]));
}
}
}
private:
/// The underlying reader.
const R* r_;
/// The underlying view.
ViewType* view_;
/// Indicates that a certain field has been found.
rfl::Ref<std::array<bool, size_>> found_;
/// Collects any errors we may have come across.
std::vector<Error>* errors_;
};
} // namespace rfl::parsing
#endif

View File

@@ -0,0 +1,122 @@
#ifndef RFL_PARSING_VIEWREADERWITHDEFAULTANDSTRIPPEDFIELDNAMES_HPP_
#define RFL_PARSING_VIEWREADERWITHDEFAULTANDSTRIPPEDFIELDNAMES_HPP_
#include <optional>
#include <sstream>
#include <type_traits>
#include <utility>
#include <vector>
#include "../Result.hpp"
#include "../Tuple.hpp"
#include "../internal/is_array.hpp"
#include "Parser_base.hpp"
namespace rfl::parsing {
template <class R, class W, class ViewType, class ProcessorsType>
class ViewReaderWithDefaultAndStrippedFieldNames {
private:
using InputVarType = typename R::InputVarType;
static constexpr size_t size_ = ViewType::size();
public:
ViewReaderWithDefaultAndStrippedFieldNames(const R* _r, ViewType* _view,
std::vector<Error>* _errors)
: i_(0), r_(_r), view_(_view), errors_(_errors) {}
~ViewReaderWithDefaultAndStrippedFieldNames() = default;
std::array<bool, size_> found() const {
std::array<bool, size_> f;
std::fill(f.begin(), f.begin() + i_, true);
std::fill(f.begin() + i_, f.end(), false);
return f;
}
/// Assigns the parsed version of _var to the field signified by i_, to be
/// used when the field names are stripped.
std::optional<Error> read(const InputVarType& _var) const {
if (i_ == size_) {
std::stringstream stream;
stream << "Expected a maximum of " << std::to_string(size_)
<< " fields, but got at least one more.";
return Error(stream.str());
}
assign_to_field_i(*r_, _var, view_, errors_, i_,
std::make_integer_sequence<int, size_>());
++i_;
return std::nullopt;
}
private:
template <int i>
static void assign_if_field_is_field_i(const R& _r, const auto& _var,
auto* _view, auto* _errors, int _i) {
using FieldType = tuple_element_t<i, typename ViewType::Fields>;
using OriginalType = typename FieldType::Type;
using T =
std::remove_cvref_t<std::remove_pointer_t<typename FieldType::Type>>;
constexpr auto name = FieldType::name();
if (_i == i) {
auto res = Parser<R, W, T, ProcessorsType>::read(_r, _var);
if (!res) {
std::stringstream stream;
stream << "Failed to parse field '" << std::string(name)
<< "': " << res.error().what();
_errors->emplace_back(Error(stream.str()));
return;
}
if constexpr (std::is_pointer_v<OriginalType>) {
move_to(rfl::get<i>(*_view), &(*res));
} else {
rfl::get<i>(*_view) = std::move(*res);
}
}
}
template <int... is>
static void assign_to_field_i(const R& _r, const auto& _var, auto* _view,
auto* _errors, int _i,
std::integer_sequence<int, is...>) {
(assign_if_field_is_field_i<is>(_r, _var, _view, _errors, _i), ...);
}
// TODO: Unnecessary code duplication.
template <class Target, class Source>
static void move_to(Target* _t, Source* _s) {
if constexpr (std::is_const_v<Target>) {
return move_to(const_cast<std::remove_const_t<Target>*>(_t), _s);
} else if constexpr (!rfl::internal::is_array_v<Source> &&
!std::is_array_v<Target>) {
*_t = Target(std::move(*_s));
} else if constexpr (rfl::internal::is_array_v<Source>) {
static_assert(std::is_array_v<Target>,
"Expected target to be a c-array.");
for (size_t i = 0; i < _s->arr_.size(); ++i) {
move_to(&((*_t)[i]), &(_s->arr_[i]));
}
} else {
for (size_t i = 0; i < _s->size(); ++i) {
move_to(&((*_t)[i]), &((*_s)[i]));
}
}
}
private:
/// Indicates the current field.
mutable int i_;
/// The underlying reader.
const R* r_;
/// The underlying view.
ViewType* view_;
/// Collects any errors we may have come across.
std::vector<Error>* errors_;
};
} // namespace rfl::parsing
#endif

View File

@@ -0,0 +1,134 @@
#ifndef RFL_PARSING_VIEWREADERWITHSTRIPPEDFIELDNAMES_HPP_
#define RFL_PARSING_VIEWREADERWITHSTRIPPEDFIELDNAMES_HPP_
#include <array>
#include <optional>
#include <sstream>
#include <type_traits>
#include <utility>
#include <vector>
#include "../Result.hpp"
#include "../Tuple.hpp"
#include "../internal/is_array.hpp"
#include "Parser_base.hpp"
namespace rfl::parsing {
template <class R, class W, class ViewType, class ProcessorsType>
class ViewReaderWithStrippedFieldNames {
private:
using InputVarType = typename R::InputVarType;
static constexpr size_t size_ = ViewType::size();
public:
ViewReaderWithStrippedFieldNames(const R* _r, ViewType* _view,
std::array<bool, size_>* _found,
std::array<bool, size_>* _set,
std::vector<Error>* _errors)
: i_(0),
r_(_r),
view_(_view),
found_(_found),
set_(_set),
errors_(_errors) {}
~ViewReaderWithStrippedFieldNames() = default;
/// Assigns the parsed version of _var to the field signified by i_, to be
/// used when the field names are stripped.
std::optional<Error> read(const InputVarType& _var) const {
if (i_ == size_) {
std::stringstream stream;
stream << "Expected a maximum of " << std::to_string(size_)
<< " fields, but got at least one more.";
return Error(stream.str());
}
assign_to_field_i(*r_, _var, view_, errors_, found_, set_, i_,
std::make_integer_sequence<int, size_>());
++i_;
return std::nullopt;
}
private:
template <int i>
static void assign_if_field_is_field_i(const R& _r, const auto& _var,
auto* _view, auto* _errors,
auto* _found, auto* _set, int _i) {
using FieldType = tuple_element_t<i, typename ViewType::Fields>;
using OriginalType = typename FieldType::Type;
using T =
std::remove_cvref_t<std::remove_pointer_t<typename FieldType::Type>>;
constexpr auto name = FieldType::name();
if (_i == i) {
std::get<i>(*_found) = true;
auto res = Parser<R, W, T, ProcessorsType>::read(_r, _var);
if (!res) {
std::stringstream stream;
stream << "Failed to parse field '" << std::string(name)
<< "': " << res.error().what();
_errors->emplace_back(Error(stream.str()));
return;
}
if constexpr (std::is_pointer_v<OriginalType>) {
move_to(rfl::get<i>(*_view), &(*res));
} else {
rfl::get<i>(*_view) = std::move(*res);
}
std::get<i>(*_set) = true;
}
}
template <int... is>
static void assign_to_field_i(const R& _r, const auto& _var, auto* _view,
auto* _errors, auto* _found, auto* _set, int _i,
std::integer_sequence<int, is...>) {
(assign_if_field_is_field_i<is>(_r, _var, _view, _errors, _found, _set, _i),
...);
}
// TODO: Unnecessary code duplication.
template <class Target, class Source>
static void move_to(Target* _t, Source* _s) {
if constexpr (std::is_const_v<Target>) {
return move_to(const_cast<std::remove_const_t<Target>*>(_t), _s);
} else if constexpr (!rfl::internal::is_array_v<Source> &&
!std::is_array_v<Target>) {
::new (_t) Target(std::move(*_s));
} else if constexpr (rfl::internal::is_array_v<Source>) {
static_assert(std::is_array_v<Target>,
"Expected target to be a c-array.");
for (size_t i = 0; i < _s->arr_.size(); ++i) {
move_to(&((*_t)[i]), &(_s->arr_[i]));
}
} else {
for (size_t i = 0; i < _s->size(); ++i) {
move_to(&((*_t)[i]), &((*_s)[i]));
}
}
}
private:
/// Indicates the current field.
mutable int i_;
/// The underlying reader.
const R* r_;
/// The underlying view.
ViewType* view_;
/// Indicates that a certain field has been found.
std::array<bool, size_>* found_;
/// Indicates that a certain field has been successfully set - necessary,
/// because we have to trigger the destructors manually.
std::array<bool, size_>* set_;
/// Collects any errors we may have come across.
std::vector<Error>* errors_;
};
} // namespace rfl::parsing
#endif

View File

@@ -0,0 +1,30 @@
#ifndef RFL_PARSING_CALL_DESTRUCTORS_ON_ARRAY_WHERE_NECESSARY_HPP_
#define RFL_PARSING_CALL_DESTRUCTORS_ON_ARRAY_WHERE_NECESSARY_HPP_
#include <array>
#include <type_traits>
#include "call_destructors_where_necessary.hpp"
namespace rfl::parsing {
/// Because of the way we have allocated the fields, we need to manually
/// trigger the destructors.
template <class T, size_t _size>
void call_destructors_on_array_where_necessary(const size_t _num_set,
std::array<T, _size>* _array) {
for (size_t i = 0; i < std::min(_num_set, _size); ++i) {
if constexpr (!std::is_array_v<T> &&
std::is_destructible_v<std::remove_cv_t<T>>) {
(*_array)[i].~T();
} else if constexpr (std::is_array_v<T>) {
auto ptr = (*_array)[i];
call_destructor_on_array(sizeof(*ptr) / sizeof(**ptr), *ptr);
}
}
}
} // namespace rfl::parsing
#endif

View File

@@ -0,0 +1,45 @@
#ifndef RFL_PARSING_CALL_DESTRUCTORS_ON_TUPLE_WHERE_NECESSARY_HPP_
#define RFL_PARSING_CALL_DESTRUCTORS_ON_TUPLE_WHERE_NECESSARY_HPP_
#include <type_traits>
#include "../Tuple.hpp"
#include "call_destructors_where_necessary.hpp"
namespace rfl::parsing {
/// Because of the way we have allocated the fields, we need to manually
/// trigger the destructors.
template <class TupleType, unsigned long _size, int _i>
void call_destructor_on_one_tuple_element_if_necessary(const size_t _num_set,
TupleType* _tup) {
using FieldType = tuple_element_t<_i, TupleType>;
using ValueType = std::remove_cv_t<std::remove_pointer_t<FieldType>>;
if constexpr (!std::is_array_v<ValueType> &&
std::is_destructible_v<ValueType>) {
if (_i < _num_set) {
rfl::get<_i>(*_tup).~ValueType();
}
} else if constexpr (std::is_array_v<ValueType>) {
if (_i < _num_set) {
auto ptr = rfl::get<_i>(*_tup);
call_destructor_on_array(sizeof(*ptr) / sizeof(**ptr), *ptr);
}
}
}
template <class TupleType>
void call_destructors_on_tuple_where_necessary(const size_t _num_set,
TupleType* _tup) {
[&]<int... is>(std::integer_sequence<int, is...>) {
(call_destructor_on_one_tuple_element_if_necessary<
TupleType, rfl::tuple_size_v<TupleType>, is>(_num_set, _tup),
...);
}
(std::make_integer_sequence<int, rfl::tuple_size_v<TupleType>>());
}
} // namespace rfl::parsing
#endif

View File

@@ -0,0 +1,57 @@
#ifndef RFL_PARSING_CALL_DESTRUCTORS_WHERE_NECESSARY_HPP_
#define RFL_PARSING_CALL_DESTRUCTORS_WHERE_NECESSARY_HPP_
#include <array>
#include <type_traits>
#include "../get.hpp"
namespace rfl::parsing {
/// Because of the way we have allocated the fields, we need to manually
/// trigger the destructors.
template <class T>
void call_destructor_on_array(const size_t _size, T* _ptr) {
for (size_t i = 0; i < _size; ++i) {
if constexpr (std::is_array_v<T>) {
call_destructor_on_array(sizeof(*_ptr) / sizeof(**_ptr), *(_ptr + i));
} else if constexpr (std::is_destructible_v<std::remove_cv_t<T>>) {
(_ptr + i)->~T();
}
}
}
template <class ViewType, unsigned long _size, int _i>
void call_destructor_on_one_if_necessary(const std::array<bool, _size>& _set,
ViewType* _view) {
using FieldType = tuple_element_t<_i, typename ViewType::Fields>;
using OriginalType = std::remove_cv_t<typename FieldType::Type>;
using ValueType =
std::remove_cvref_t<std::remove_pointer_t<typename FieldType::Type>>;
if constexpr (!std::is_array_v<ValueType> &&
std::is_pointer_v<OriginalType> &&
std::is_destructible_v<ValueType>) {
if (std::get<_i>(_set)) {
rfl::get<_i>(*_view)->~ValueType();
}
} else if constexpr (std::is_array_v<ValueType>) {
if (std::get<_i>(_set)) {
auto ptr = rfl::get<_i>(*_view);
call_destructor_on_array(sizeof(*ptr) / sizeof(**ptr), *ptr);
}
}
}
template <class ViewType, auto _size>
void call_destructors_where_necessary(const std::array<bool, _size>& _set,
ViewType* _view) {
[&]<int... is>(std::integer_sequence<int, is...>) {
(call_destructor_on_one_if_necessary<ViewType, _size, is>(_set, _view),
...);
}(std::make_integer_sequence<int, _size>());
}
} // namespace rfl::parsing
#endif

View File

@@ -0,0 +1,42 @@
#ifndef RFL_PARSING_IS_EMPTY_HPP_
#define RFL_PARSING_IS_EMPTY_HPP_
#include <type_traits>
#include "../internal/has_reflection_method_v.hpp"
#include "../internal/has_reflection_type_v.hpp"
#include "../internal/is_rename.hpp"
#include "../internal/is_skip.hpp"
#include "is_map_like.hpp"
#include "is_vector_like.hpp"
namespace rfl {
namespace parsing {
template <class T>
static bool is_empty(const T& _var) {
using Type = std::remove_cvref_t<T>;
if constexpr (std::is_pointer_v<Type>) {
return !_var || is_empty(*_var);
} else if constexpr (internal::is_skip_v<Type>) {
return Type::skip_serialization_;
} else if constexpr (internal::has_reflection_type_v<Type>) {
if constexpr (internal::has_reflection_method_v<Type>) {
return is_empty(_var.reflection());
} else {
const auto& [r] = _var;
return is_empty(r);
}
} else if constexpr (internal::is_rename_v<Type>) {
return is_empty(_var.value());
} else if constexpr (is_map_like_v<Type> || is_vector_like_v<Type>) {
return _var.begin() == _var.end();
} else {
return !_var;
}
}
} // namespace parsing
} // namespace rfl
#endif

View File

@@ -0,0 +1,22 @@
#ifndef RFL_PARSING_IS_FORWARD_LIST_HPP_
#define RFL_PARSING_IS_FORWARD_LIST_HPP_
#include <forward_list>
#include <type_traits>
namespace rfl {
namespace parsing {
template <class T>
class is_forward_list;
template <class T>
class is_forward_list : public std::false_type {};
template <class T>
class is_forward_list<std::forward_list<T>> : public std::true_type {};
} // namespace parsing
} // namespace rfl
#endif

View File

@@ -0,0 +1,38 @@
#ifndef RFL_PARSING_IS_MAP_LIKE_HPP_
#define RFL_PARSING_IS_MAP_LIKE_HPP_
#include <map>
#include <type_traits>
#include <unordered_map>
namespace rfl {
namespace parsing {
template <class T>
class is_map_like;
template <class T>
class is_map_like : public std::false_type {};
template <class K, class V>
class is_map_like<std::map<K, V>> : public std::true_type {};
template <class K, class V>
class is_map_like<std::multimap<K, V>> : public std::true_type {};
template <class K, class V, class Hash, class KeyEqual, class Allocator>
class is_map_like<std::unordered_map<K, V, Hash, KeyEqual, Allocator>>
: public std::true_type {};
template <class K, class V, class Hash, class KeyEqual, class Allocator>
class is_map_like<std::unordered_multimap<K, V, Hash, KeyEqual, Allocator>>
: public std::true_type {};
template <class T>
constexpr bool is_map_like_v =
is_map_like<std::remove_cvref_t<std::remove_pointer_t<T>>>::value;
} // namespace parsing
} // namespace rfl
#endif

View File

@@ -0,0 +1,28 @@
#ifndef RFL_PARSING_IS_MAP_LIKE_NOT_MULTIMAP_HPP_
#define RFL_PARSING_IS_MAP_LIKE_NOT_MULTIMAP_HPP_
#include <map>
#include <type_traits>
#include <unordered_map>
namespace rfl {
namespace parsing {
template <class T>
class is_map_like_not_multimap;
template <class T>
class is_map_like_not_multimap : public std::false_type {};
template <class K, class V>
class is_map_like_not_multimap<std::map<K, V>> : public std::true_type {};
template <class K, class V, class Hash, class KeyEqual, class Allocator>
class is_map_like_not_multimap<
std::unordered_map<K, V, Hash, KeyEqual, Allocator>>
: public std::true_type {};
} // namespace parsing
} // namespace rfl
#endif

View File

@@ -0,0 +1,58 @@
#ifndef RFL_PARSING_IS_REQUIRED_HPP_
#define RFL_PARSING_IS_REQUIRED_HPP_
#include <memory>
#include <optional>
#include <type_traits>
#include "../Generic.hpp"
#include "../Rename.hpp"
#include "../internal/StringLiteral.hpp"
#include "../internal/has_reflection_type_v.hpp"
#include "../internal/is_rename.hpp"
#include "is_map_like.hpp"
#include "is_vector_like.hpp"
namespace rfl {
namespace parsing {
/// Determines whether a field in a named tuple is required.
/// General case - most fields are required.
template <class T>
class is_never_required;
template <class T>
class is_never_required : public std::false_type {};
template <class T>
class is_never_required<std::optional<T>> : public std::true_type {};
template <class T>
class is_never_required<std::shared_ptr<T>> : public std::true_type {};
template <class T>
class is_never_required<std::unique_ptr<T>> : public std::true_type {};
template <class T>
constexpr bool is_never_required_v = is_never_required<T>::value;
template <class T, bool _ignore_empty_containers>
consteval bool is_required() {
using Type = std::remove_cvref_t<std::remove_pointer_t<T>>;
if constexpr (internal::has_reflection_type_v<Type> &&
!std::is_same_v<Type, rfl::Generic>) {
return is_required<typename Type::ReflectionType,
_ignore_empty_containers>();
} else if constexpr (internal::is_rename_v<Type>) {
return is_required<typename Type::Type, _ignore_empty_containers>();
} else {
return !(is_never_required_v<Type> ||
(_ignore_empty_containers &&
(is_vector_like_v<Type> || is_map_like_v<Type>)));
}
}
} // namespace parsing
} // namespace rfl
#endif

View File

@@ -0,0 +1,37 @@
#ifndef RFL_PARSING_IS_SET_LIKE_HPP_
#define RFL_PARSING_IS_SET_LIKE_HPP_
#include <set>
#include <type_traits>
#include <unordered_set>
namespace rfl {
namespace parsing {
template <class T>
class is_set_like;
template <class T>
class is_set_like : public std::false_type {};
template <class T>
class is_set_like<std::set<T>> : public std::true_type {};
template <class T>
class is_set_like<std::unordered_set<T>> : public std::true_type {};
template <class T>
class is_set_like<std::multiset<T>> : public std::true_type {};
template <class T>
class is_set_like<std::unordered_multiset<T>> : public std::true_type {};
template <class T>
constexpr bool is_set_like_v =
is_set_like<std::remove_cvref_t<std::remove_pointer_t<T>>>::value;
} // namespace parsing
} // namespace rfl
#endif

View File

@@ -0,0 +1,37 @@
#ifndef RFL_PARSING_ISTAGGEDUNIONWRAPPER_HPP_
#define RFL_PARSING_ISTAGGEDUNIONWRAPPER_HPP_
#include <type_traits>
#include "../internal/StringLiteral.hpp"
#include "TaggedUnionWrapper.hpp"
namespace rfl {
namespace parsing {
template <class T>
class is_tagged_union_wrapper;
template <class T>
class is_tagged_union_wrapper : public std::false_type {};
template <class T, class TagType, internal::StringLiteral _discriminator,
bool _as_const_pointer>
class is_tagged_union_wrapper<
TaggedUnionWrapperNoFields<T, TagType, _discriminator, _as_const_pointer>>
: public std::true_type {};
template <class T, class TagType, internal::StringLiteral _discriminator,
bool _as_const_pointer>
class is_tagged_union_wrapper<
TaggedUnionWrapperWithFields<T, TagType, _discriminator, _as_const_pointer>>
: public std::true_type {};
template <class T>
constexpr bool is_tagged_union_wrapper_v =
is_tagged_union_wrapper<std::remove_cvref_t<T>>::value;
} // namespace parsing
} // namespace rfl
#endif

View File

@@ -0,0 +1,51 @@
#ifndef RFL_PARSING_IS_VECTOR_LIKE_HPP_
#define RFL_PARSING_IS_VECTOR_LIKE_HPP_
#include <deque>
#include <forward_list>
#include <list>
#include <set>
#include <unordered_set>
#include <vector>
namespace rfl {
namespace parsing {
template <class T>
class is_vector_like;
template <class T>
class is_vector_like : public std::false_type {};
template <class T>
class is_vector_like<std::deque<T>> : public std::true_type {};
template <class T>
class is_vector_like<std::forward_list<T>> : public std::true_type {};
template <class T>
class is_vector_like<std::list<T>> : public std::true_type {};
template <class T>
class is_vector_like<std::multiset<T>> : public std::true_type {};
template <class T>
class is_vector_like<std::set<T>> : public std::true_type {};
template <class T>
class is_vector_like<std::unordered_multiset<T>> : public std::true_type {};
template <class T>
class is_vector_like<std::unordered_set<T>> : public std::true_type {};
template <class T>
class is_vector_like<std::vector<T>> : public std::true_type {};
template <class T>
constexpr bool is_vector_like_v =
is_vector_like<std::remove_cvref_t<std::remove_pointer_t<T>>>::value;
} // namespace parsing
} // namespace rfl
#endif

View File

@@ -0,0 +1,24 @@
#ifndef RFL_PARSING_ISVIEWREADER_HPP_
#define RFL_PARSING_ISVIEWREADER_HPP_
#include "ViewReader.hpp"
namespace rfl ::parsing {
template <class T>
class is_view_reader;
template <class T>
class is_view_reader : public std::false_type {};
template <class R, class W, class NamedTupleType, class ProcessorsType>
class is_view_reader<ViewReader<R, W, NamedTupleType, ProcessorsType>>
: public std::true_type {};
template <class T>
constexpr bool is_view_reader_v =
is_view_reader<std::remove_cvref_t<std::remove_pointer_t<T>>>::value;
} // namespace rfl::parsing
#endif

View File

@@ -0,0 +1,28 @@
#ifndef RFL_PARSING_MAKETYPENAME_HPP_
#define RFL_PARSING_MAKETYPENAME_HPP_
#include "../type_name_t.hpp"
#include "is_tagged_union_wrapper.hpp"
namespace rfl::parsing {
inline std::string replace_non_alphanumeric(std::string _str) {
for (auto& ch : _str) {
ch = std::isalnum(ch) ? ch : '_';
}
return _str;
}
template <class U>
static std::string make_type_name() {
if constexpr (is_tagged_union_wrapper_v<U>) {
return replace_non_alphanumeric(type_name_t<typename U::Type>().str() +
"__tagged");
} else {
return replace_non_alphanumeric(type_name_t<U>().str());
}
}
} // namespace rfl::parsing
#endif

View File

@@ -0,0 +1,21 @@
#ifndef RFL_PARSING_SCHEMA_DEFINITION_HPP_
#define RFL_PARSING_SCHEMA_DEFINITION_HPP_
#include <map>
#include <string>
#include "Type.hpp"
namespace rfl::parsing::schema {
struct Definition {
/// Contains the root element of the schema definition.
Type root_;
/// Contains the definitions to be referenced by Type::Reference.
std::map<std::string, Type> definitions_;
};
} // namespace rfl::parsing::schema
#endif

View File

@@ -0,0 +1,111 @@
#ifndef RFL_PARSING_SCHEMA_TYPE_HPP_
#define RFL_PARSING_SCHEMA_TYPE_HPP_
#include <cstddef>
#include <memory>
#include <string>
#include <vector>
#include "../../Object.hpp"
#include "../../Ref.hpp"
#include "../../Variant.hpp"
#include "ValidationType.hpp"
#include "../../common.hpp"
namespace rfl::parsing::schema {
struct RFL_API Type {
struct Boolean {};
struct Bytestring {};
struct Vectorstring {};
struct Int32 {};
struct Int64 {};
struct UInt32 {};
struct UInt64 {};
struct Integer {};
struct Float {};
struct Double {};
struct String {};
struct AnyOf {
std::vector<Type> types_;
};
struct Description {
std::string description_;
Ref<Type> type_;
};
struct FixedSizeTypedArray {
size_t size_;
Ref<Type> type_;
};
struct Literal {
std::vector<std::string> values_;
};
struct Object {
rfl::Object<Type> types_;
std::shared_ptr<Type> additional_properties_;
};
/// All values are assumed to be required unless explicitly stated otherwise
/// using this wrapper.
struct Optional {
Ref<Type> type_;
};
/// The is necessary to resolve circular definitions. Refers to something in
/// Definitions.
struct Reference {
std::string name_;
};
// A map with key type string.
struct StringMap {
Ref<Type> value_type_;
};
struct Tuple {
std::vector<Type> types_;
};
struct TypedArray {
Ref<Type> type_;
};
struct Validated {
Ref<Type> type_;
ValidationType validation_;
};
using VariantType =
rfl::Variant<Boolean, Bytestring, Vectorstring, Int32, Int64, UInt32, UInt64, Integer,
Float, Double, String, AnyOf, Description,
FixedSizeTypedArray, Literal, Object, Optional, Reference,
StringMap, Tuple, TypedArray, Validated>;
Type();
Type(const VariantType& _variant);
~Type();
/// A type can be determined to be any of the above.
VariantType variant_;
};
} // namespace rfl::parsing::schema
#endif

View File

@@ -0,0 +1,67 @@
#ifndef RFL_PARSING_SCHEMA_VALIDATIONTYPE_HPP_
#define RFL_PARSING_SCHEMA_VALIDATIONTYPE_HPP_
#include <string>
#include <vector>
#include "../../Ref.hpp"
#include "../../Variant.hpp"
namespace rfl::parsing::schema {
struct ValidationType {
struct AllOf {
std::vector<ValidationType> types_;
};
struct AnyOf {
std::vector<ValidationType> types_;
};
struct EqualTo {
rfl::Variant<double, int> value_;
};
struct ExclusiveMaximum {
rfl::Variant<double, int> value_;
};
struct ExclusiveMinimum {
rfl::Variant<double, int> value_;
};
struct Maximum {
rfl::Variant<double, int> value_;
};
struct Minimum {
rfl::Variant<double, int> value_;
};
struct NotEqualTo {
rfl::Variant<double, int> value_;
};
struct OneOf {
std::vector<ValidationType> types_;
};
struct Regex {
std::string pattern_;
};
struct Size {
Ref<ValidationType> size_limit_;
};
using VariantType =
rfl::Variant<AllOf, AnyOf, EqualTo, ExclusiveMaximum, ExclusiveMinimum,
Maximum, Minimum, OneOf, NotEqualTo, Regex, Size>;
/// A type can be determined to be any of the above.
VariantType variant_;
};
} // namespace rfl::parsing::schema
#endif

View File

@@ -0,0 +1,21 @@
#ifndef RFL_PARSING_SCHEMA_MAKE_HPP_
#define RFL_PARSING_SCHEMA_MAKE_HPP_
#include <map>
#include "../Parser.hpp"
#include "Definition.hpp"
#include "Type.hpp"
namespace rfl::parsing::schema {
template <class R, class W, class T, class ProcessorsType>
Definition make() {
std::map<std::string, Type> definitions;
auto root = Parser<R, W, T, ProcessorsType>::to_schema(&definitions);
return Definition{.root_ = root, .definitions_ = definitions};
}
} // namespace rfl::parsing::schema
#endif

View File

@@ -0,0 +1,66 @@
#ifndef RFL_PARSING_SCHEMAFUL_ISSCHEMAFULREADER_HPP_
#define RFL_PARSING_SCHEMAFUL_ISSCHEMAFULREADER_HPP_
#include <concepts>
#include <optional>
#include <string>
#include <variant>
#include "../../Result.hpp"
namespace rfl::parsing::schemaful {
using MockVariantType = std::variant<std::string, int>;
template <class R>
struct MockMapReader {
void read(const std::string_view&, typename R::InputVarType&) const {}
};
template <class R>
struct MockObjectReader {
void read(const int, typename R::InputVarType&) const {}
};
template <class R>
struct MockUnionReader {
static rfl::Result<MockVariantType> read(const R&, const size_t,
typename R::InputVarType&) {
return error("This is a mock type.");
}
};
template <class R>
concept IsSchemafulReader =
requires(R r, typename R::InputVarType var, typename R::InputObjectType obj,
typename R::InputMapType m, typename R::InputUnionType u,
MockMapReader<R> map_reader, MockObjectReader<R> object_reader) {
/// A schemaful reader needs to differentiate between objects, for which
/// the field names are known at compile time and maps, for which the
/// field names are not known at compile time.
{ r.read_map(map_reader, m) } -> std::same_as<std::optional<Error>>;
/// A schemaful reader can read fields by order and does not have to
/// compare strings - the correct order of the fields is guaranteed by the
/// schema.
{
r.read_object(object_reader, obj)
} -> std::same_as<std::optional<Error>>;
/// A schemaful reader needs an explicit union type.
{
r.template read_union<MockVariantType, MockUnionReader<R>>(u)
} -> std::same_as<rfl::Result<MockVariantType>>;
/// It needs to be possible to transform variables to maps.
{ r.to_map(var) } -> std::same_as<rfl::Result<typename R::InputMapType>>;
/// It needs to be possible to transform variables to unions.
{
r.to_union(var)
} -> std::same_as<rfl::Result<typename R::InputUnionType>>;
};
} // namespace rfl::parsing::schemaful
#endif

View File

@@ -0,0 +1,129 @@
#ifndef RFL_PARSING_SCHEMAFUL_ISSCHEMAFULWRITER_HPP_
#define RFL_PARSING_SCHEMAFUL_ISSCHEMAFULWRITER_HPP_
#include <concepts>
#include <string>
#include <string_view>
namespace rfl::parsing::schemaful {
template <class W>
concept IsSchemafulWriter = requires(
W w, typename W::OutputVarType var, typename W::OutputArrayType arr,
typename W::OutputMapType m, typename W::OutputObjectType obj,
typename W::OutputUnionType u, size_t index, size_t size,
std::string_view name, std::string val) {
/// A schemaful writer needs to know two additional types:
/// 1) Maps - unlike objects, their field names are not known at compile time.
/// 2) Unions - schemaful formats need explicit union types.
/// Sets an empty map as the root element of the document.
/// Some serialization formats require you to pass the expected size in
/// advance. If you are not working with such a format, you can ignore the
/// parameter `size`. Returns the new array for further modification.
{ w.map_as_root(size) } -> std::same_as<typename W::OutputMapType>;
/// Sets an empty union as the root element of the document.
{ w.union_as_root() } -> std::same_as<typename W::OutputUnionType>;
/// Adds an empty array to an existing map. Returns the new
/// array for further modification.
{
w.add_array_to_map(name, size, &m)
} -> std::same_as<typename W::OutputArrayType>;
/// Adds an empty array to an existing union.
/// The index refers to the index of the element in the union.
/// Returns the new array for further modification.
{
w.add_array_to_union(index, size, &u)
} -> std::same_as<typename W::OutputArrayType>;
/// Adds an empty map to an existing array. Returns the new
/// map for further modification.
{ w.add_map_to_array(size, &arr) } -> std::same_as<typename W::OutputMapType>;
/// Adds an empty map to an existing map. The key or name of the field
/// is signified by `name`. Returns the new map for further modification.
{
w.add_map_to_map(name, size, &m)
} -> std::same_as<typename W::OutputMapType>;
/// Adds an empty map to an existing object. The key or name of the field
/// is signified by `name`. Returns the new map for further modification.
{
w.add_map_to_object(name, size, &obj)
} -> std::same_as<typename W::OutputMapType>;
/// Adds an empty map to an existing union.
/// The index refers to the index of the element in the union.
/// Returns the new map for further modification.
{
w.add_map_to_union(index, size, &u)
} -> std::same_as<typename W::OutputMapType>;
/// Adds an empty object to an existing map. The key or name of the field
/// is signified by `name`. Returns the new object for further modification.
{
w.add_object_to_map(name, size, &m)
} -> std::same_as<typename W::OutputObjectType>;
/// Adds an empty object to an existing union.
/// The index refers to the index of the element in the union.
/// Returns the new object for further modification.
{
w.add_object_to_union(index, size, &u)
} -> std::same_as<typename W::OutputObjectType>;
/// Adds an empty union to an existing array. Returns the new
/// union for further modification.
{ w.add_union_to_array(&arr) } -> std::same_as<typename W::OutputUnionType>;
/// Adds an empty union to an existing map. The key or name of the field
/// is signified by `name`. Returns the new union for further modification.
{ w.add_union_to_map(name, &m) } -> std::same_as<typename W::OutputUnionType>;
/// Adds an empty union to an existing object. The key or name of the field
/// is signified by `name`. Returns the new union for further modification.
{
w.add_union_to_object(name, &obj)
} -> std::same_as<typename W::OutputUnionType>;
/// Adds an empty union to an existing union.
/// The index refers to the index of the element in the union.
/// Returns the new union for further modification.
{
w.add_union_to_union(index, &u)
} -> std::same_as<typename W::OutputUnionType>;
/// Adds a null value to a map. Returns an
/// OutputVarType containing the null value.
{ w.add_null_to_map(name, &m) } -> std::same_as<typename W::OutputVarType>;
/// Adds a basic value (bool, numeric, string) to an existing map. The key
/// or name of the field is signified by `name`. Returns an
/// OutputVarType containing the new value.
{
w.add_value_to_map(name, val, &m)
} -> std::same_as<typename W::OutputVarType>;
/// Adds a null value to a union. Returns an
/// OutputVarType containing the null value.
{ w.add_null_to_union(index, &u) } -> std::same_as<typename W::OutputVarType>;
/// Adds a basic value (bool, numeric, string) to an existing union. The key
/// or name of the field is signified by `name`. Returns an
/// OutputVarType containing the new value.
{
w.add_value_to_union(index, val, &u)
} -> std::same_as<typename W::OutputVarType>;
/// Signifies to the writer that we do not want to add any further elements to
/// this map. Some serialization formats require this. If you are working
/// with a serialization format that doesn't, just leave the function empty.
{ w.end_map(&m) } -> std::same_as<void>;
};
} // namespace rfl::parsing::schemaful
#endif

View File

@@ -0,0 +1,26 @@
#ifndef RFL_PARSING_SCHEMAFUL_OPTIONALREADER_HPP_
#define RFL_PARSING_SCHEMAFUL_OPTIONALREADER_HPP_
#include <optional>
#include <type_traits>
#include "../../Result.hpp"
#include "../Parser_base.hpp"
namespace rfl::parsing::schemaful {
template <class R, class W, class T, class ProcessorsType>
struct OptionalReader {
static Result<std::optional<T>> read(
const R& _r, const size_t _index,
const typename R::InputVarType& _var) noexcept {
if (_index == 1) {
return std::optional<T>();
}
return Parser<R, W, std::remove_cvref_t<T>, ProcessorsType>::read(_r, _var);
}
};
} // namespace rfl::parsing::schemaful
#endif

View File

@@ -0,0 +1,27 @@
#ifndef RFL_PARSING_SCHEMAFUL_SHAREDPTRREADER_HPP_
#define RFL_PARSING_SCHEMAFUL_SHAREDPTRREADER_HPP_
#include <memory>
#include <type_traits>
#include "../../Result.hpp"
#include "../Parser_base.hpp"
namespace rfl::parsing::schemaful {
template <class R, class W, class T, class ProcessorsType>
struct SharedPtrReader {
static Result<std::shared_ptr<T>> read(
const R& _r, const size_t _index,
const typename R::InputVarType& _var) noexcept {
if (_index == 1) {
return std::shared_ptr<T>();
}
return Parser<R, W, std::remove_cvref_t<T>, ProcessorsType>::read(_r, _var)
.transform([](T&& _t) { return std::make_shared<T>(std::move(_t)); });
}
};
} // namespace rfl::parsing::schemaful
#endif

View File

@@ -0,0 +1,27 @@
#ifndef RFL_PARSING_SCHEMAFUL_UNIQUEPTRREADER_HPP_
#define RFL_PARSING_SCHEMAFUL_UNIQUEPTRREADER_HPP_
#include <memory>
#include <type_traits>
#include "../../Result.hpp"
#include "../Parser_base.hpp"
namespace rfl::parsing::schemaful {
template <class R, class W, class T, class ProcessorsType>
struct UniquePtrReader {
static Result<std::unique_ptr<T>> read(
const R& _r, const size_t _index,
const typename R::InputVarType& _var) noexcept {
if (_index == 1) {
return std::unique_ptr<T>();
}
return Parser<R, W, std::remove_cvref_t<T>, ProcessorsType>::read(_r, _var)
.transform([](T&& _t) { return std::make_unique<T>(std::move(_t)); });
}
};
} // namespace rfl::parsing::schemaful
#endif

View File

@@ -0,0 +1,45 @@
#ifndef RFL_PARSING_SCHEMAFUL_VARIANTREADER_HPP_
#define RFL_PARSING_SCHEMAFUL_VARIANTREADER_HPP_
#include <type_traits>
#include <utility>
#include "../../Result.hpp"
#include "../../internal/nth_element_t.hpp"
#include "../Parser_base.hpp"
namespace rfl::parsing::schemaful {
template <class R, class W, class VariantType, class ProcessorsType,
class... AlternativeTypes>
class VariantReader {
public:
static Result<VariantType> read(
const R& _r, const size_t _index,
const typename R::InputVarType& _var) noexcept {
return [&]<size_t... _is>(std::integer_sequence<size_t, _is...>) {
Result<VariantType> result =
error("Could not parse union: Index out of bounds.");
(try_one_type<_is>(_r, _index, _var, &result), ...);
return result;
}(std::make_integer_sequence<size_t, sizeof...(AlternativeTypes)>());
}
private:
template <size_t _i>
static void try_one_type(const R& _r, const size_t _index,
const typename R::InputVarType& _var,
Result<VariantType>* _result) noexcept {
if (_index == _i) {
using T = internal::nth_element_t<_i, AlternativeTypes...>;
*_result =
Parser<R, W, std::remove_cvref_t<T>, ProcessorsType>::read(_r, _var)
.transform(
[](auto&& _val) -> VariantType { return std::move(_val); });
}
}
};
} // namespace rfl::parsing::schemaful
#endif

View File

@@ -0,0 +1,53 @@
#ifndef RFL_PARSING_SCHEMAFUL_TUPLETONAMEDTUPLE_HPP_
#define RFL_PARSING_SCHEMAFUL_TUPLETONAMEDTUPLE_HPP_
#include <tuple>
#include <type_traits>
#include <utility>
#include "../../Field.hpp"
#include "../../Tuple.hpp"
#include "../../internal/StringLiteral.hpp"
#include "../../make_named_tuple.hpp"
namespace rfl::parsing::schemaful {
template <int _i>
inline consteval auto to_field_name() {
return internal::StringLiteral<5>('f',
static_cast<char>('0' + ((_i / 100) % 10)),
static_cast<char>('0' + ((_i / 10) % 10)),
static_cast<char>('0' + (_i % 10)));
}
template <int _i>
inline auto to_field(const auto& _t) {
using T = std::remove_cvref_t<decltype(_t)>;
return rfl::Field<to_field_name<_i>(), const T*>(&_t);
}
/// Schemaful formats often don't have an explicit tuple representation.
/// This is the required workaround.
template <class... Ts>
auto tuple_to_named_tuple(const Tuple<Ts...>& _tup) {
static_assert(sizeof...(Ts) <= 1000,
"The tuple cannot contain more than 1000 elements.");
return [&]<int... _is>(std::integer_sequence<int, _is...>) {
return rfl::make_named_tuple(to_field<_is>(rfl::get<_is>(_tup))...);
}(std::make_integer_sequence<int, sizeof...(Ts)>());
}
/// Schemaful formats often don't have an explicit tuple representation.
/// This is the required workaround.
template <class... Ts>
auto tuple_to_named_tuple(const std::tuple<Ts...>& _tup) {
static_assert(sizeof...(Ts) <= 1000,
"The tuple cannot contain more than 1000 elements.");
return [&]<int... _is>(std::integer_sequence<int, _is...>) {
return rfl::make_named_tuple(to_field<_is>(std::get<_is>(_tup))...);
}(std::make_integer_sequence<int, sizeof...(Ts)>());
}
} // namespace rfl::parsing::schemaful
#endif

View File

@@ -0,0 +1,47 @@
#ifndef RFL_PARSING_SCHEMAFUL_TUPLETONAMEDTUPLE_T_HPP_
#define RFL_PARSING_SCHEMAFUL_TUPLETONAMEDTUPLE_T_HPP_
#include <tuple>
#include <utility>
#include "../../NamedTuple.hpp"
#include "../../Tuple.hpp"
#include "tuple_to_named_tuple.hpp"
namespace rfl::parsing::schemaful {
template <class IntegerSequence, class... Ts>
struct ToNamedTuple;
template <int... _is, class... Ts>
struct ToNamedTuple<std::integer_sequence<int, _is...>, Ts...> {
using Type = NamedTuple<Field<to_field_name<_is>(), Ts>...>;
};
template <class TupleType>
struct TupleToNamedTuple;
template <class... Ts>
struct TupleToNamedTuple<Tuple<Ts...>> {
using Type =
typename ToNamedTuple<std::make_integer_sequence<int, sizeof...(Ts)>,
Ts...>::Type;
};
template <class... Ts>
struct TupleToNamedTuple<std::tuple<Ts...>> {
using Type =
typename ToNamedTuple<std::make_integer_sequence<int, sizeof...(Ts)>,
Ts...>::Type;
};
/// Given
/// std::tuple<T1, T2, T3, ...> or rfl::Tuple<T1, T2, T3, ...>
/// return
/// NamedTuple<Field<"f000", T1>, Field<"f001", T2>, Field<"f002", T3>, ...>
template <class T>
using tuple_to_named_tuple_t = typename TupleToNamedTuple<T>::Type;
} // namespace rfl::parsing::schemaful
#endif

View File

@@ -0,0 +1,15 @@
#ifndef RFL_PARSING_SCHEMAFUL_TUPLETOOBJECT_HPP_
#define RFL_PARSING_SCHEMAFUL_TUPLETOOBJECT_HPP_
#include "../schema/Type.hpp"
#include "../../common.hpp"
namespace rfl::parsing::schemaful {
/// Schemaful formats often don't have an explicit tuple representation.
/// This is the required workaround.
RFL_API schema::Type::Object tuple_to_object(const schema::Type::Tuple& _tup);
} // namespace rfl::parsing::schemaful
#endif

View File

@@ -0,0 +1,29 @@
#ifndef RFL_PARSING_SUPPORTSATTRIBUTES_HPP_
#define RFL_PARSING_SUPPORTSATTRIBUTES_HPP_
#include <concepts>
#include <string_view>
#include "../Result.hpp"
namespace rfl {
namespace parsing {
/// Determines whether a writer supports attributes.
template <class W>
concept supports_attributes = requires(W w, std::string_view name,
typename W::OutputObjectType obj,
bool is_attribute) {
{
w.add_value_to_object(name, name, &obj, is_attribute)
} -> std::same_as<typename W::OutputVarType>;
{
w.add_null_to_object(name, &obj, is_attribute)
} -> std::same_as<typename W::OutputVarType>;
};
} // namespace parsing
} // namespace rfl
#endif

View File

@@ -0,0 +1,133 @@
#ifndef RFL_PARSING_TABULAR_ARROWREADER_HPP_
#define RFL_PARSING_TABULAR_ARROWREADER_HPP_
#include <arrow/api.h>
#include <array>
#include <stdexcept>
#include <string>
#include <type_traits>
#include <utility>
#include "../../Processors.hpp"
#include "../../Result.hpp"
#include "../../Tuple.hpp"
#include "../../apply.hpp"
#include "../../get.hpp"
#include "../../named_tuple_t.hpp"
#include "../../to_view.hpp"
#include "../../view_t.hpp"
#include "../call_destructors_where_necessary.hpp"
#include "make_chunked_array_iterators.hpp"
namespace rfl::parsing::tabular {
template <class VecType, SerializationType _s, class... Ps>
class ArrowReader {
static_assert(!Processors<Ps...>::add_tags_to_variants_,
"rfl::AddTagsToVariants cannot be used for tabular data.");
static_assert(!Processors<Ps...>::add_namespaced_tags_to_variants_,
"rfl::AddNamespacedTagsToVariants cannot be used for tabular data.");
static_assert(!Processors<Ps...>::all_required_,
"rfl::NoOptionals cannot be used for tabular data.");
static_assert(!Processors<Ps...>::default_if_missing_,
"rfl::DefaultIfMissing cannot be used for tabular data.");
static_assert(!Processors<Ps...>::no_extra_fields_,
"rfl::NoExtraFields cannot be used for tabular data (neither "
"can rfl::ExtraFields).");
static_assert(!Processors<Ps...>::no_field_names_,
"rfl::NoFieldNames cannot be used for tabular data.");
public:
using ValueType = typename std::remove_cvref_t<typename VecType::value_type>;
static Result<ArrowReader> make(const std::shared_ptr<arrow::Table>& _table) {
try {
return ArrowReader(_table);
} catch (const std::exception& e) {
return error(std::string("Could not create ArrowReader: ") + e.what());
}
}
~ArrowReader() = default;
Result<VecType> read() const noexcept {
return make_chunked_array_iterators<named_tuple_t<ValueType, Ps...>, _s>(
table_)
.and_then([&](auto chunked_array_iterators) -> Result<VecType> {
VecType result;
while (!end(chunked_array_iterators)) {
auto value = new_value(&chunked_array_iterators);
if (!value) {
return error(value.error().what());
}
result.emplace_back(std::move(*value));
}
return result;
});
}
private:
ArrowReader(const std::shared_ptr<arrow::Table>& _table)
: table_(Ref<arrow::Table>::make(_table).value()) {}
bool end(const auto& _chunked_array_iterators) const {
return apply(
[](const auto&... _its) { return (false || ... || _its.end()); },
_chunked_array_iterators);
}
Result<ValueType> new_value(auto* _chunked_array_iterators) const noexcept {
alignas(ValueType) unsigned char buf[sizeof(ValueType)]{};
auto ptr = internal::ptr_cast<ValueType*>(&buf);
auto view = to_view(*ptr);
using ViewType = std::remove_cvref_t<decltype(view)>;
try {
const auto set_one = [&]<size_t _i>(std::integral_constant<size_t, _i>) {
using FieldType = tuple_element_t<_i, typename ViewType::Fields>;
using T = std::remove_cvref_t<
std::remove_pointer_t<typename FieldType::Type>>;
auto res = *_chunked_array_iterators->template get<_i>();
if (!res) {
destroy_value<_i>(&view);
throw std::runtime_error(
std::string("Field '") + typename FieldType::Name().str() +
std::string("' could not be set: ") + res.error().what());
}
::new (view.template get<_i>()) T(std::move(*res));
++_chunked_array_iterators->template get<_i>();
};
[&]<size_t... _is>(std::integer_sequence<size_t, _is...>) {
(set_one(std::integral_constant<size_t, _is>{}), ...);
}(std::make_integer_sequence<size_t, view.size()>());
} catch (const std::exception& e) {
return error(e.what());
}
return std::move(*ptr);
}
template <size_t _i, class ViewType>
void destroy_value(ViewType* _view) const {
static_assert(_i < ViewType::size(), "_i out of bounds.");
auto set = std::array<bool, ViewType::size()>();
for (size_t i = 0; i < _i; ++i) {
set[i] = true;
}
for (size_t i = _i; i < ViewType::size(); ++i) {
set[i] = false;
}
call_destructors_where_necessary(set, _view);
}
private:
Ref<arrow::Table> table_;
};
} // namespace rfl::parsing::tabular
#endif

View File

@@ -0,0 +1,956 @@
#ifndef RFL_PARSING_TABULAR_ARROWTYPES_HPP_
#define RFL_PARSING_TABULAR_ARROWTYPES_HPP_
#include <arrow/api.h>
#include <memory>
#include <optional>
#include <stdexcept>
#include <string>
#include "../../Box.hpp"
#include "../../NamedTuple.hpp"
#include "../../Ref.hpp"
#include "../../Rename.hpp"
#include "../../Timestamp.hpp"
#include "../../Tuple.hpp"
#include "../../concepts.hpp"
#include "../../enums.hpp"
#include "../../internal/StringLiteral.hpp"
#include "../../internal/has_reflection_type_v.hpp"
#include "../../internal/ptr_cast.hpp"
#include "../../named_tuple_t.hpp"
namespace rfl::parsing::tabular {
enum class SerializationType { csv, parquet };
template <class T, SerializationType _s>
struct ArrowTypes;
template <class T, SerializationType _s>
Result<Ref<typename ArrowTypes<T, _s>::ArrayType>> transform_numerical_array(
const std::shared_ptr<arrow::Array>& _arr) noexcept;
template <SerializationType _s>
struct ArrowTypes<bool, _s> {
using ArrayType = arrow::BooleanArray;
using BuilderType = arrow::BooleanBuilder;
static auto data_type() { return arrow::boolean(); }
static void add_to_builder(const bool _val, BuilderType* _builder) {
const auto status = _builder->Append(_val);
if (!status.ok()) {
throw std::runtime_error(status.message());
}
}
static Result<Ref<ArrayType>> get_array(
const std::shared_ptr<arrow::Array>& _arr) {
if (_arr->type()->Equals(data_type())) {
return Ref<ArrayType>::make(std::static_pointer_cast<ArrayType>(_arr));
} else {
return error("Expected boolean array, got " + _arr->type()->ToString() +
".");
}
}
static Result<bool> get_value(const Ref<ArrayType>& _chunk,
const int64_t _ix) {
return _chunk->Value(_ix);
}
static auto make_builder() { return BuilderType(); }
};
template <SerializationType _s>
struct ArrowTypes<uint8_t, _s> {
using ArrayType = arrow::UInt8Array;
using BuilderType = arrow::UInt8Builder;
using T = uint8_t;
static auto data_type() { return arrow::uint8(); }
static void add_to_builder(const auto _val, BuilderType* _builder) {
const auto status = _builder->Append(_val);
if (!status.ok()) {
throw std::runtime_error(status.message());
}
}
static Result<Ref<ArrayType>> get_array(
const std::shared_ptr<arrow::Array>& _arr) {
return transform_numerical_array<T, _s>(_arr);
}
static Result<uint8_t> get_value(const Ref<ArrayType>& _chunk,
const int64_t _ix) {
return _chunk->Value(_ix);
}
static auto make_builder() { return BuilderType(); }
};
template <SerializationType _s>
struct ArrowTypes<uint16_t, _s> {
using ArrayType = arrow::UInt16Array;
using BuilderType = arrow::UInt16Builder;
using T = uint16_t;
static auto data_type() { return arrow::uint16(); }
static void add_to_builder(const auto _val, BuilderType* _builder) {
const auto status = _builder->Append(_val);
if (!status.ok()) {
throw std::runtime_error(status.message());
}
}
static Result<Ref<ArrayType>> get_array(
const std::shared_ptr<arrow::Array>& _arr) {
return transform_numerical_array<T, _s>(_arr);
}
static Result<uint16_t> get_value(const Ref<ArrayType>& _chunk,
const int64_t _ix) {
return _chunk->Value(_ix);
}
static auto make_builder() { return BuilderType(); }
};
template <SerializationType _s>
struct ArrowTypes<uint32_t, _s> {
using ArrayType = arrow::UInt32Array;
using BuilderType = arrow::UInt32Builder;
using T = uint32_t;
static auto data_type() { return arrow::uint32(); }
static void add_to_builder(const auto _val, BuilderType* _builder) {
const auto status = _builder->Append(_val);
if (!status.ok()) {
throw std::runtime_error(status.message());
}
}
static Result<Ref<ArrayType>> get_array(
const std::shared_ptr<arrow::Array>& _arr) {
return transform_numerical_array<T, _s>(_arr);
}
static Result<uint32_t> get_value(const Ref<ArrayType>& _chunk,
const int64_t _ix) {
return _chunk->Value(_ix);
}
static auto make_builder() { return BuilderType(); }
};
template <SerializationType _s>
struct ArrowTypes<uint64_t, _s> {
using ArrayType = arrow::UInt64Array;
using BuilderType = arrow::UInt64Builder;
using T = uint64_t;
static auto data_type() { return arrow::uint64(); }
static void add_to_builder(const auto _val, BuilderType* _builder) {
const auto status = _builder->Append(_val);
if (!status.ok()) {
throw std::runtime_error(status.message());
}
}
static Result<Ref<ArrayType>> get_array(
const std::shared_ptr<arrow::Array>& _arr) {
return transform_numerical_array<T, _s>(_arr);
}
static Result<uint64_t> get_value(const Ref<ArrayType>& _chunk,
const int64_t _ix) {
return _chunk->Value(_ix);
}
static auto make_builder() { return BuilderType(); }
};
template <SerializationType _s>
struct ArrowTypes<int8_t, _s> {
using ArrayType = arrow::Int8Array;
using BuilderType = arrow::Int8Builder;
using T = int8_t;
static auto data_type() { return arrow::int8(); }
static void add_to_builder(const auto _val, BuilderType* _builder) {
const auto status = _builder->Append(_val);
if (!status.ok()) {
throw std::runtime_error(status.message());
}
}
static Result<Ref<ArrayType>> get_array(
const std::shared_ptr<arrow::Array>& _arr) {
return transform_numerical_array<T, _s>(_arr);
}
static Result<int8_t> get_value(const Ref<ArrayType>& _chunk,
const int64_t _ix) {
return _chunk->Value(_ix);
}
static auto make_builder() { return BuilderType(); }
};
template <SerializationType _s>
struct ArrowTypes<int16_t, _s> {
using ArrayType = arrow::Int16Array;
using BuilderType = arrow::Int16Builder;
using T = int16_t;
static auto data_type() { return arrow::int16(); }
static void add_to_builder(const auto _val, BuilderType* _builder) {
const auto status = _builder->Append(_val);
if (!status.ok()) {
throw std::runtime_error(status.message());
}
}
static Result<Ref<ArrayType>> get_array(
const std::shared_ptr<arrow::Array>& _arr) {
return transform_numerical_array<T, _s>(_arr);
}
static Result<int16_t> get_value(const Ref<ArrayType>& _chunk,
const int64_t _ix) {
return _chunk->Value(_ix);
}
static auto make_builder() { return BuilderType(); }
};
template <SerializationType _s>
struct ArrowTypes<int32_t, _s> {
using ArrayType = arrow::Int32Array;
using BuilderType = arrow::Int32Builder;
using T = int32_t;
static auto data_type() { return arrow::int32(); }
static void add_to_builder(const auto _val, BuilderType* _builder) {
const auto status = _builder->Append(_val);
if (!status.ok()) {
throw std::runtime_error(status.message());
}
}
static Result<Ref<ArrayType>> get_array(
const std::shared_ptr<arrow::Array>& _arr) {
return transform_numerical_array<T, _s>(_arr);
}
static Result<int32_t> get_value(const Ref<ArrayType>& _chunk,
const int64_t _ix) {
return _chunk->Value(_ix);
}
static auto make_builder() { return BuilderType(); }
};
template <SerializationType _s>
struct ArrowTypes<int64_t, _s> {
using ArrayType = arrow::Int64Array;
using BuilderType = arrow::Int64Builder;
using T = int64_t;
static auto data_type() { return arrow::int64(); }
static void add_to_builder(const auto _val, BuilderType* _builder) {
const auto status = _builder->Append(_val);
if (!status.ok()) {
throw std::runtime_error(status.message());
}
}
static Result<Ref<ArrayType>> get_array(
const std::shared_ptr<arrow::Array>& _arr) {
return transform_numerical_array<T, _s>(_arr);
}
static Result<int64_t> get_value(const Ref<ArrayType>& _chunk,
const int64_t _ix) {
return _chunk->Value(_ix);
}
static auto make_builder() { return BuilderType(); }
};
template <SerializationType _s>
struct ArrowTypes<float, _s> {
using ArrayType = arrow::FloatArray;
using BuilderType = arrow::FloatBuilder;
using T = float;
static auto data_type() { return arrow::float32(); }
static void add_to_builder(const auto _val, BuilderType* _builder) {
const auto status = _builder->Append(_val);
if (!status.ok()) {
throw std::runtime_error(status.message());
}
}
static Result<Ref<ArrayType>> get_array(
const std::shared_ptr<arrow::Array>& _arr) {
return transform_numerical_array<T, _s>(_arr);
}
static Result<float> get_value(const Ref<ArrayType>& _chunk,
const int64_t _ix) {
return _chunk->Value(_ix);
}
static auto make_builder() { return BuilderType(); }
};
template <SerializationType _s>
struct ArrowTypes<double, _s> {
using ArrayType = arrow::DoubleArray;
using BuilderType = arrow::DoubleBuilder;
using T = double;
static auto data_type() { return arrow::float64(); }
static void add_to_builder(const auto _val, BuilderType* _builder) {
const auto status = _builder->Append(_val);
if (!status.ok()) {
throw std::runtime_error(status.message());
}
}
static Result<Ref<ArrayType>> get_array(
const std::shared_ptr<arrow::Array>& _arr) {
return transform_numerical_array<T, _s>(_arr);
}
static Result<double> get_value(const Ref<ArrayType>& _chunk,
const int64_t _ix) {
return _chunk->Value(_ix);
}
static auto make_builder() { return BuilderType(); }
};
template <SerializationType _s>
struct ArrowTypes<std::string, _s> {
using ArrayType = arrow::StringArray;
using BuilderType = arrow::StringBuilder;
static auto data_type() { return arrow::utf8(); }
static void add_to_builder(const auto& _val, BuilderType* _builder) {
const auto status = _builder->Append(_val);
if (!status.ok()) {
throw std::runtime_error(status.message());
}
}
static Result<Ref<ArrayType>> get_array(
const std::shared_ptr<arrow::Array>& _arr) {
if (_arr->type()->Equals(data_type())) {
return Ref<ArrayType>::make(std::static_pointer_cast<ArrayType>(_arr));
} else {
return error("Expected string array, got " + _arr->type()->ToString() +
".");
}
}
static Result<std::string> get_value(const Ref<ArrayType>& _chunk,
const int64_t _ix) {
return std::string(_chunk->Value(_ix));
}
static auto make_builder() { return BuilderType(); }
};
template <class T, SerializationType _s>
requires enchantum::Enum<T>
struct ArrowTypes<T, _s> {
using ArrayType = arrow::StringArray;
using BuilderType = arrow::StringBuilder;
static auto data_type() { return arrow::utf8(); }
static void add_to_builder(const T _val, BuilderType* _builder) {
const auto status = _builder->Append(enum_to_string(_val));
if (!status.ok()) {
throw std::runtime_error(status.message());
}
}
static Result<Ref<ArrayType>> get_array(
const std::shared_ptr<arrow::Array>& _arr) {
return ArrowTypes<std::string, _s>::get_array(_arr);
}
static Result<T> get_value(const Ref<ArrayType>& _chunk, const int64_t _ix) {
return string_to_enum<T>(std::string(_chunk->Value(_ix)));
}
static auto make_builder() { return BuilderType(); }
};
template <class T, SerializationType _s>
requires concepts::ContiguousByteContainer<T>
struct ArrowTypes<T, _s> {
using ArrayType = arrow::BinaryArray;
using BuilderType = arrow::BinaryBuilder;
static auto data_type() { return arrow::binary(); }
static void add_to_builder(const auto& _val, BuilderType* _builder) {
const auto status = _builder->Append(
internal::ptr_cast<const uint8_t*>(_val.data()), _val.size());
if (!status.ok()) {
throw std::runtime_error(status.message());
}
}
static Result<Ref<ArrayType>> get_array(
const std::shared_ptr<arrow::Array>& _arr) {
if (_arr->type()->Equals(data_type())) {
return Ref<ArrayType>::make(std::static_pointer_cast<ArrayType>(_arr));
} else if (_arr->type()->Equals(arrow::utf8())) {
return transform_string(
std::static_pointer_cast<arrow::StringArray>(_arr));
} else {
return error("Expected binary or string array, got " +
_arr->type()->ToString() + ".");
}
}
static Result<T> get_value(const Ref<ArrayType>& _chunk, const int64_t _ix) {
const auto begin = internal::ptr_cast<const typename T::value_type*>(
_chunk->Value(_ix).data());
return T(begin, begin + _chunk->Value(_ix).size());
}
static auto make_builder() { return BuilderType(); }
static Result<Ref<arrow::BinaryArray>> transform_string(
const std::shared_ptr<arrow::StringArray>& _arr) noexcept {
if (!_arr) {
return error(
"transform_string: std::shared_ptr not set. This is a "
"bug, please report.");
}
auto builder = arrow::BinaryBuilder();
for (int64_t i = 0; i < _arr->length(); ++i) {
if (_arr->IsNull(i)) {
const auto status = builder.AppendNull();
if (!status.ok()) {
return error(status.message());
}
} else {
const std::string_view s = _arr->Value(i);
const auto status = builder.Append(
internal::ptr_cast<const uint8_t*>(s.data()), s.size());
if (!status.ok()) {
return error(status.message());
}
}
}
std::shared_ptr<arrow::Array> res;
const auto status = builder.Finish(&res);
return Ref<arrow::BinaryArray>::make(
std::static_pointer_cast<arrow::BinaryArray>(res));
}
};
template <internal::StringLiteral _format, SerializationType _s>
struct ArrowTypes<Timestamp<_format>, _s> {
enum class TimeUnit { day, second, milli, micro, nano, string };
using ArrayType = arrow::TimestampArray;
using BuilderType = arrow::TimestampBuilder;
static auto data_type() { return arrow::timestamp(arrow::TimeUnit::MILLI); }
static void add_to_builder(const auto& _val, BuilderType* _builder) {
const auto status = _builder->Append(_val.to_time_t() * 1000);
if (!status.ok()) {
throw std::runtime_error(status.message());
}
}
static Result<Ref<ArrayType>> get_array(
const std::shared_ptr<arrow::Array>& _arr) {
if (_arr->type()->Equals(data_type())) {
return Ref<ArrayType>::make(std::static_pointer_cast<ArrayType>(_arr));
} else if (_arr->type()->Equals(
arrow::timestamp(arrow::TimeUnit::SECOND))) {
return transform_time_stamp<TimeUnit::second>(
std::static_pointer_cast<arrow::TimestampArray>(_arr));
} else if (_arr->type()->Equals(arrow::timestamp(arrow::TimeUnit::MICRO))) {
return transform_time_stamp<TimeUnit::micro>(
std::static_pointer_cast<arrow::TimestampArray>(_arr));
} else if (_arr->type()->Equals(arrow::timestamp(arrow::TimeUnit::NANO))) {
return transform_time_stamp<TimeUnit::nano>(
std::static_pointer_cast<arrow::TimestampArray>(_arr));
} else if (_arr->type()->Equals(arrow::date32())) {
return transform_time_stamp<TimeUnit::day>(
std::static_pointer_cast<arrow::Date32Array>(_arr));
} else if (_arr->type()->Equals(arrow::date64())) {
return transform_time_stamp<TimeUnit::milli>(
std::static_pointer_cast<arrow::Date64Array>(_arr));
} else if (_arr->type()->Equals(arrow::utf8())) {
return transform_time_stamp<TimeUnit::string>(
std::static_pointer_cast<arrow::StringArray>(_arr));
} else {
return error("Expected timestamp, date32, date64 or string array, got " +
_arr->type()->ToString() + ".");
}
}
static Result<Timestamp<_format>> get_value(const Ref<ArrayType>& _chunk,
const int64_t _ix) {
return Timestamp<_format>(_chunk->Value(_ix) / 1000);
}
static auto make_builder() {
return BuilderType(data_type(), arrow::default_memory_pool());
}
template <TimeUnit _unit, class SourceArrayType>
static Result<Ref<arrow::TimestampArray>> transform_time_stamp(
const std::shared_ptr<SourceArrayType>& _arr) noexcept {
if (!_arr) {
return error(
"transform_time_stamp: std::shared_ptr not set. This is a "
"bug, please report.");
}
auto builder =
arrow::TimestampBuilder(data_type(), arrow::default_memory_pool());
for (int64_t i = 0; i < _arr->length(); ++i) {
if (_arr->IsNull(i)) {
const auto status = builder.AppendNull();
if (!status.ok()) {
return error(status.message());
}
} else {
if constexpr (_unit == TimeUnit::day) {
const auto status = builder.Append(
static_cast<int64_t>(_arr->Value(i)) * 1000 * 24 * 60 * 60);
if (!status.ok()) {
return error(status.message());
}
} else if constexpr (_unit == TimeUnit::second) {
const auto status =
builder.Append(static_cast<int64_t>(_arr->Value(i) * 1000));
if (!status.ok()) {
return error(status.message());
}
} else if constexpr (_unit == TimeUnit::milli) {
const auto status =
builder.Append(static_cast<int64_t>(_arr->Value(i)));
if (!status.ok()) {
return error(status.message());
}
} else if constexpr (_unit == TimeUnit::micro) {
const auto status =
builder.Append(static_cast<int64_t>(_arr->Value(i) / 1000));
if (!status.ok()) {
return error(status.message());
}
} else if constexpr (_unit == TimeUnit::nano) {
const auto status =
builder.Append(static_cast<int64_t>(_arr->Value(i) / 1000000));
if (!status.ok()) {
return error(status.message());
}
} else if constexpr (_unit == TimeUnit::string) {
const auto ts = Timestamp<_format>::make(std::string(_arr->Value(i)));
if (!ts) {
return error(ts.error().what());
}
const auto status = builder.Append(ts->to_time_t() * 1000);
if (!status.ok()) {
return error(status.message());
}
} else {
static_assert(rfl::always_false_v<SourceArrayType>,
"Unsupported time unit.");
}
}
}
std::shared_ptr<arrow::Array> res;
const auto status = builder.Finish(&res);
return Ref<arrow::TimestampArray>::make(
std::static_pointer_cast<arrow::TimestampArray>(res));
}
};
template <internal::StringLiteral _format>
struct ArrowTypes<Timestamp<_format>, SerializationType::csv> {
using ArrayType = arrow::TimestampArray;
using BuilderType = arrow::StringBuilder;
static auto data_type() { return arrow::timestamp(arrow::TimeUnit::MILLI); }
static void add_to_builder(const Timestamp<_format>& _val,
BuilderType* _builder) {
const auto status = _builder->Append(_val.str());
if (!status.ok()) {
throw std::runtime_error(status.message());
}
}
static Result<Ref<ArrayType>> get_array(
const std::shared_ptr<arrow::Array>& _arr) {
return ArrowTypes<Timestamp<_format>,
SerializationType::parquet>::get_array(_arr);
}
static Result<Timestamp<_format>> get_value(const Ref<ArrayType>& _chunk,
const int64_t _ix) {
return ArrowTypes<Timestamp<_format>,
SerializationType::parquet>::get_value(_chunk, _ix);
}
static auto make_builder() { return BuilderType(); }
};
template <class T, SerializationType _s>
requires internal::has_reflection_type_v<T>
struct ArrowTypes<T, _s> {
using ArrayType =
typename ArrowTypes<typename T::ReflectionType, _s>::ArrayType;
using BuilderType =
typename ArrowTypes<typename T::ReflectionType, _s>::BuilderType;
static auto data_type() {
return ArrowTypes<typename T::ReflectionType, _s>::data_type();
}
static void add_to_builder(const auto& _val, BuilderType* _builder) {
ArrowTypes<typename T::ReflectionType, _s>::add_to_builder(
_val.reflection(), _builder);
}
static Result<Ref<ArrayType>> get_array(
const std::shared_ptr<arrow::Array>& _arr) {
return ArrowTypes<typename T::ReflectionType, _s>::get_array(_arr);
}
static Result<T> get_value(const Ref<ArrayType>& _chunk, const int64_t _ix) {
return ArrowTypes<std::remove_cvref_t<typename T::ReflectionType>,
_s>::get_value(_chunk, _ix)
.and_then([](const auto& _v) -> Result<T> {
try {
return T(_v);
} catch (const std::exception& e) {
return error(e.what());
}
});
}
static auto make_builder() {
return ArrowTypes<typename T::ReflectionType, _s>::make_builder();
}
};
template <class T, SerializationType _s>
struct ArrowTypes<std::optional<T>, _s> {
using ArrayType = typename ArrowTypes<std::remove_cvref_t<T>, _s>::ArrayType;
using BuilderType =
typename ArrowTypes<std::remove_cvref_t<T>, _s>::BuilderType;
static auto data_type() { return ArrowTypes<T, _s>::data_type(); }
static void add_to_builder(const auto& _val, BuilderType* _builder) {
if (_val) {
ArrowTypes<T, _s>::add_to_builder(*_val, _builder);
} else {
const auto status = _builder->AppendNull();
if (!status.ok()) {
throw std::runtime_error(status.message());
}
}
}
static Result<Ref<ArrayType>> get_array(
const std::shared_ptr<arrow::Array>& _arr) {
return ArrowTypes<T, _s>::get_array(_arr);
}
static auto get_value(const Ref<ArrayType>& _chunk, const int64_t _ix) {
return ArrowTypes<std::remove_cvref_t<T>, _s>::get_value(_chunk, _ix)
.transform([](const auto& _v) { return std::make_optional<T>(_v); });
}
static auto make_builder() { return ArrowTypes<T, _s>::make_builder(); }
};
template <class T, SerializationType _s>
struct ArrowTypes<std::shared_ptr<T>, _s> {
using ArrayType = typename ArrowTypes<std::remove_cvref_t<T>, _s>::ArrayType;
using BuilderType =
typename ArrowTypes<std::remove_cvref_t<T>, _s>::BuilderType;
static auto data_type() { return ArrowTypes<T, _s>::data_type(); }
static void add_to_builder(const auto& _val, BuilderType* _builder) {
if (_val) {
ArrowTypes<T, _s>::add_to_builder(*_val, _builder);
} else {
const auto status = _builder->AppendNull();
if (!status.ok()) {
throw std::runtime_error(status.message());
}
}
}
static Result<Ref<ArrayType>> get_array(
const std::shared_ptr<arrow::Array>& _arr) {
return ArrowTypes<T, _s>::get_array(_arr);
}
static auto get_value(const Ref<ArrayType>& _chunk, const int64_t _ix) {
return ArrowTypes<std::remove_cvref_t<T>, _s>::get_value(_chunk, _ix)
.transform([](const auto& _v) { return std::make_shared<T>(_v); });
}
static auto make_builder() { return ArrowTypes<T, _s>::make_builder(); }
};
template <class T, SerializationType _s>
struct ArrowTypes<std::unique_ptr<T>, _s> {
using ArrayType = typename ArrowTypes<std::remove_cvref_t<T>, _s>::ArrayType;
using BuilderType =
typename ArrowTypes<std::remove_cvref_t<T>, _s>::BuilderType;
static auto data_type() { return ArrowTypes<T, _s>::data_type(); }
static void add_to_builder(const auto& _val, BuilderType* _builder) {
if (_val) {
ArrowTypes<T, _s>::add_to_builder(*_val, _builder);
} else {
const auto status = _builder->AppendNull();
if (!status.ok()) {
throw std::runtime_error(status.message());
}
}
}
static Result<Ref<ArrayType>> get_array(
const std::shared_ptr<arrow::Array>& _arr) {
return ArrowTypes<T, _s>::get_array(_arr);
}
static auto get_value(const Ref<ArrayType>& _chunk, const int64_t _ix) {
return ArrowTypes<std::remove_cvref_t<T>, _s>::get_value(_chunk, _ix)
.transform([](const auto& _v) { return std::make_unique<T>(_v); });
}
static auto make_builder() { return ArrowTypes<T, _s>::make_builder(); }
};
template <class T, SerializationType _s>
struct ArrowTypes<Box<T>, _s> {
using ArrayType = typename ArrowTypes<std::remove_cvref_t<T>, _s>::ArrayType;
using BuilderType =
typename ArrowTypes<std::remove_cvref_t<T>, _s>::BuilderType;
static auto data_type() { return ArrowTypes<T, _s>::data_type(); }
static void add_to_builder(const auto& _val, BuilderType* _builder) {
ArrowTypes<T, _s>::add_to_builder(*_val, _builder);
}
static Result<Ref<ArrayType>> get_array(
const std::shared_ptr<arrow::Array>& _arr) {
return ArrowTypes<T, _s>::get_array(_arr);
}
static auto get_value(const Ref<ArrayType>& _chunk, const int64_t _ix) {
return ArrowTypes<std::remove_cvref_t<T>, _s>::get_value(_chunk, _ix)
.transform([](const auto& _v) { return Box<T>::make(_v); });
}
static auto make_builder() { return ArrowTypes<T, _s>::make_builder(); }
};
template <class T, SerializationType _s>
struct ArrowTypes<Ref<T>, _s> {
using ArrayType = typename ArrowTypes<std::remove_cvref_t<T>, _s>::ArrayType;
using BuilderType =
typename ArrowTypes<std::remove_cvref_t<T>, _s>::BuilderType;
static auto data_type() { return ArrowTypes<T, _s>::data_type(); }
static void add_to_builder(const auto& _val, BuilderType* _builder) {
ArrowTypes<T, _s>::add_to_builder(*_val, _builder);
}
static Result<Ref<ArrayType>> get_array(
const std::shared_ptr<arrow::Array>& _arr) {
return ArrowTypes<T, _s>::get_array(_arr);
}
static auto get_value(const Ref<ArrayType>& _chunk, const int64_t _ix) {
return ArrowTypes<std::remove_cvref_t<T>, _s>::get_value(_chunk, _ix)
.transform([](const auto& _v) { return Ref<T>::make(_v); });
}
static auto make_builder() { return ArrowTypes<T, _s>::make_builder(); }
};
template <internal::StringLiteral _name, class T, SerializationType _s>
struct ArrowTypes<Rename<_name, T>, _s> {
using ArrayType = typename ArrowTypes<std::remove_cvref_t<T>, _s>::ArrayType;
using BuilderType =
typename ArrowTypes<std::remove_cvref_t<T>, _s>::BuilderType;
static auto data_type() { return ArrowTypes<T, _s>::data_type(); }
static void add_to_builder(const auto& _val, BuilderType* _builder) {
ArrowTypes<T, _s>::add_to_builder(_val.value(), _builder);
}
static Result<Ref<ArrayType>> get_array(
const std::shared_ptr<arrow::Array>& _arr) {
return ArrowTypes<T, _s>::get_array(_arr);
}
static auto get_value(const Ref<ArrayType>& _chunk, const int64_t _ix) {
return ArrowTypes<std::remove_cvref_t<T>, _s>::get_value(_chunk, _ix)
.transform([](const auto& _v) { return Rename<_name, T>(_v); });
}
static auto make_builder() { return ArrowTypes<T, _s>::make_builder(); }
};
template <class T, SerializationType _s, class SourceArrayType>
Result<Ref<typename ArrowTypes<T, _s>::ArrayType>>
transform_numerical_array_impl(
const std::shared_ptr<SourceArrayType>& _arr) noexcept {
if (!_arr) {
return error(
"transform_numerical_array_impl: std::shared_ptr not set. This is a "
"bug, please report.");
}
auto builder = ArrowTypes<T, _s>::make_builder();
for (int64_t i = 0; i < _arr->length(); ++i) {
if (_arr->IsNull(i)) {
const auto status = builder.AppendNull();
if (!status.ok()) {
return error(status.message());
}
} else {
const auto status = builder.Append(static_cast<T>(_arr->Value(i)));
if (!status.ok()) {
return error(status.message());
}
}
}
using TargetArrayType = typename ArrowTypes<T, _s>::ArrayType;
std::shared_ptr<arrow::Array> res;
const auto status = builder.Finish(&res);
return Ref<TargetArrayType>::make(
std::static_pointer_cast<TargetArrayType>(res));
}
template <class T, SerializationType _s>
Result<Ref<typename ArrowTypes<T, _s>::ArrayType>> transform_numerical_array(
const std::shared_ptr<arrow::Array>& _arr) noexcept {
if (!_arr) {
return error(
"Could not transform the numerical array. std::shared_ptr not set.");
}
using ArrayType = typename ArrowTypes<T, _s>::ArrayType;
if (_arr->type()->Equals(ArrowTypes<T, _s>::data_type())) {
return Ref<ArrayType>::make(std::static_pointer_cast<ArrayType>(_arr));
} else if (_arr->type()->Equals(ArrowTypes<uint8_t, _s>::data_type())) {
return transform_numerical_array_impl<T, _s>(
std::static_pointer_cast<typename ArrowTypes<uint8_t, _s>::ArrayType>(
_arr));
} else if (_arr->type()->Equals(ArrowTypes<uint16_t, _s>::data_type())) {
return transform_numerical_array_impl<T, _s>(
std::static_pointer_cast<typename ArrowTypes<uint16_t, _s>::ArrayType>(
_arr));
} else if (_arr->type()->Equals(ArrowTypes<uint32_t, _s>::data_type())) {
return transform_numerical_array_impl<T, _s>(
std::static_pointer_cast<typename ArrowTypes<uint32_t, _s>::ArrayType>(
_arr));
} else if (_arr->type()->Equals(ArrowTypes<uint64_t, _s>::data_type())) {
return transform_numerical_array_impl<T, _s>(
std::static_pointer_cast<typename ArrowTypes<uint64_t, _s>::ArrayType>(
_arr));
} else if (_arr->type()->Equals(ArrowTypes<int8_t, _s>::data_type())) {
return transform_numerical_array_impl<T, _s>(
std::static_pointer_cast<typename ArrowTypes<int8_t, _s>::ArrayType>(
_arr));
} else if (_arr->type()->Equals(ArrowTypes<int16_t, _s>::data_type())) {
return transform_numerical_array_impl<T, _s>(
std::static_pointer_cast<typename ArrowTypes<int16_t, _s>::ArrayType>(
_arr));
} else if (_arr->type()->Equals(ArrowTypes<int32_t, _s>::data_type())) {
return transform_numerical_array_impl<T, _s>(
std::static_pointer_cast<typename ArrowTypes<int32_t, _s>::ArrayType>(
_arr));
} else if (_arr->type()->Equals(ArrowTypes<int64_t, _s>::data_type())) {
return transform_numerical_array_impl<T, _s>(
std::static_pointer_cast<typename ArrowTypes<int64_t, _s>::ArrayType>(
_arr));
} else if (_arr->type()->Equals(ArrowTypes<float, _s>::data_type())) {
return transform_numerical_array_impl<T, _s>(
std::static_pointer_cast<typename ArrowTypes<float, _s>::ArrayType>(
_arr));
} else if (_arr->type()->Equals(ArrowTypes<double, _s>::data_type())) {
return transform_numerical_array_impl<T, _s>(
std::static_pointer_cast<typename ArrowTypes<double, _s>::ArrayType>(
_arr));
} else {
return error("Expected numerical array, got " + _arr->type()->ToString() +
".");
}
}
} // namespace rfl::parsing::tabular
#endif

View File

@@ -0,0 +1,118 @@
#ifndef RFL_PARSING_TABULAR_ARROWWRITER_HPP_
#define RFL_PARSING_TABULAR_ARROWWRITER_HPP_
#include <arrow/api.h>
#include <stdexcept>
#include <type_traits>
#include <utility>
#include "../../Processors.hpp"
#include "../../Tuple.hpp"
#include "../../get.hpp"
#include "../../named_tuple_t.hpp"
#include "../../to_view.hpp"
#include "add_to_builder.hpp"
#include "make_arrow_builders.hpp"
#include "make_arrow_data_types.hpp"
#include "make_arrow_schema.hpp"
namespace rfl::parsing::tabular {
template <class VecType, SerializationType _s, class... Ps>
class ArrowWriter {
static_assert(!Processors<Ps...>::add_tags_to_variants_,
"rfl::AddTagsToVariants cannot be used for tabular data.");
static_assert(!Processors<Ps...>::add_namespaced_tags_to_variants_,
"rfl::AddNamespacedTagsToVariants cannot be used for tabular data.");
static_assert(!Processors<Ps...>::all_required_,
"rfl::NoOptionals cannot be used for tabular data.");
static_assert(!Processors<Ps...>::default_if_missing_,
"rfl::DefaultIfMissing cannot be used for tabular data.");
static_assert(!Processors<Ps...>::no_extra_fields_,
"rfl::NoExtraFields cannot be used for tabular data (neither "
"can rfl::ExtraFields).");
static_assert(!Processors<Ps...>::no_field_names_,
"rfl::NoFieldNames cannot be used for tabular data.");
public:
using ValueType = typename std::remove_cvref_t<typename VecType::value_type>;
ArrowWriter(const size_t _chunksize) : chunksize_(_chunksize) {}
~ArrowWriter() = default;
std::shared_ptr<arrow::Table> to_table(const VecType& _data) const {
return arrow::Table::Make(
make_arrow_schema<named_tuple_t<ValueType, Ps...>, _s>(),
to_chunked_arrays(_data));
}
private:
std::vector<std::shared_ptr<arrow::ChunkedArray>> to_chunked_arrays(
const VecType& _data) const;
private:
size_t chunksize_;
};
template <class VecType, SerializationType _s, class... Ps>
std::vector<std::shared_ptr<arrow::ChunkedArray>>
ArrowWriter<VecType, _s, Ps...>::to_chunked_arrays(const VecType& _data) const {
using ValueType = typename VecType::value_type;
auto builders =
make_arrow_builders<named_tuple_t<typename VecType::value_type>, _s>();
constexpr size_t size = tuple_size_v<decltype(builders)>;
std::vector<std::vector<std::shared_ptr<arrow::Array>>> array_chunks(size);
auto it = _data.begin();
while (it != _data.end()) {
size_t i = 0;
for (; it != _data.end() && (i < chunksize_ || chunksize_ == 0);
++i, ++it) {
const auto view = to_view(*it);
[&]<int... _is>(const auto& _v, auto* _b,
std::integer_sequence<int, _is...>) {
(add_to_builder<_s>(*get<_is>(_v), &(_b->template get<_is>())), ...);
}(view, &builders, std::make_integer_sequence<int, size>());
}
if (i != 0) {
std::vector<std::shared_ptr<arrow::Array>> chunks(size);
const auto finish_builder = [](auto* _b, auto* _c) {
const auto status = _b->Finish(_c);
if (!status.ok()) {
throw std::runtime_error(status.message());
}
};
[&]<size_t... _is>(auto* _b, auto* _c,
std::integer_sequence<size_t, _is...>) {
(finish_builder(&_b->template get<_is>(), &_c->at(_is)), ...);
}(&builders, &chunks, std::make_integer_sequence<size_t, size>());
for (size_t j = 0; j < size; ++j) {
array_chunks.at(j).emplace_back(std::move(chunks.at(j)));
}
}
}
const auto data_types = make_arrow_data_types<ValueType, _s>();
return [&]<size_t... _is>(std::integer_sequence<size_t, _is...>) {
return std::vector<std::shared_ptr<arrow::ChunkedArray>>(
{std::make_shared<arrow::ChunkedArray>(array_chunks.at(_is),
std::get<_is>(data_types))...});
}(std::make_integer_sequence<size_t, size>());
}
} // namespace rfl::parsing::tabular
#endif

View File

@@ -0,0 +1,88 @@
#ifndef RFL_PARSING_TABULAR_CHUNKEDARRAYITERATOR_HPP_
#define RFL_PARSING_TABULAR_CHUNKEDARRAYITERATOR_HPP_
#include <arrow/api.h>
#include "../../Ref.hpp"
#include "../../Result.hpp"
#include "../../internal/ptr_cast.hpp"
#include "../is_required.hpp"
#include "array_t.hpp"
namespace rfl::parsing::tabular {
template <class T, SerializationType _s>
class ChunkedArrayIterator {
public:
using difference_type = std::ptrdiff_t;
using value_type = Result<T>;
using ArrayType = array_t<T, _s>;
static ChunkedArrayIterator make(const Ref<arrow::ChunkedArray>& _arr) {
return ChunkedArrayIterator(_arr);
}
ChunkedArrayIterator(const Ref<arrow::ChunkedArray>& _arr)
: arr_(_arr), chunk_ix_(0), current_chunk_(get_chunk(arr_, 0)), ix_(0) {}
~ChunkedArrayIterator() = default;
Result<T> operator*() const noexcept {
const bool is_null =
current_chunk_
.transform([&](const auto& _c) { return _c->IsNull(ix_); })
.value_or(false);
if (is_null) {
if constexpr (is_required<T, false>()) {
return error("Value cannot be null.");
} else {
return T();
}
}
return current_chunk_.and_then(
[&](const auto& _c) { return ArrowTypes<T, _s>::get_value(_c, ix_); });
}
bool end() const noexcept { return chunk_ix_ >= arr_->num_chunks(); }
ChunkedArrayIterator& operator++() noexcept {
if (!current_chunk_) {
return *this;
}
++ix_;
if (ix_ >= (*current_chunk_)->length()) {
++chunk_ix_;
current_chunk_ = get_chunk(arr_, chunk_ix_);
ix_ = 0;
}
return *this;
}
void operator++(int) noexcept { ++*this; }
private:
static Result<Ref<ArrayType>> get_chunk(const Ref<arrow::ChunkedArray>& _arr,
const int _chunk_ix) noexcept {
if (_chunk_ix < _arr->num_chunks()) {
return ArrowTypes<T, _s>::get_array(_arr->chunk(_chunk_ix));
} else {
return error("chunk_ix out of bounds.");
}
}
private:
Ref<arrow::ChunkedArray> arr_;
int chunk_ix_;
Result<Ref<ArrayType>> current_chunk_;
int64_t ix_;
};
} // namespace rfl::parsing::tabular
#endif

View File

@@ -0,0 +1,19 @@
#ifndef RFL_PARSING_TABULAR_ADD_TO_BUILDER_HPP_
#define RFL_PARSING_TABULAR_ADD_TO_BUILDER_HPP_
#include <type_traits>
#include "../../named_tuple_t.hpp"
#include "ArrowTypes.hpp"
namespace rfl::parsing::tabular {
template <SerializationType _s, class ValueType, class BuilderType>
inline void add_to_builder(const ValueType& _val, BuilderType* _builder) {
ArrowTypes<std::remove_cvref_t<ValueType>, _s>::add_to_builder(_val,
_builder);
}
} // namespace rfl::parsing::tabular
#endif

View File

@@ -0,0 +1,13 @@
#ifndef RFL_PARSING_TABULAR_ARRAYT_HPP_
#define RFL_PARSING_TABULAR_ARRAYT_HPP_
#include "ArrowTypes.hpp"
namespace rfl::parsing::tabular {
template <class T, SerializationType _s>
using array_t = typename ArrowTypes<std::remove_cvref_t<T>, _s>::ArrayType;
} // namespace rfl::parsing::tabular
#endif

View File

@@ -0,0 +1,53 @@
#ifndef RFL_PARSING_TABULAR_MAKEARROWBUILDERS_HPP_
#define RFL_PARSING_TABULAR_MAKEARROWBUILDERS_HPP_
#include <arrow/api.h>
#include <type_traits>
#include "../../named_tuple_t.hpp"
#include "ArrowTypes.hpp"
namespace rfl::parsing::tabular {
template <class T, SerializationType _s>
using arrow_builder_t =
typename ArrowTypes<std::remove_cvref_t<std::remove_pointer_t<T>>,
_s>::BuilderType;
template <class T, SerializationType _s>
struct ArrowBuildersType;
template <SerializationType _s, class... FieldTypes>
struct ArrowBuildersType<NamedTuple<FieldTypes...>, _s> {
using Type = Tuple<arrow_builder_t<typename FieldTypes::Type, _s>...>;
static auto data_types() {
return [&]<size_t... _is>(std::integer_sequence<size_t, _is...>) {
return std::array<std::shared_ptr<arrow::DataType>,
sizeof...(FieldTypes)>(
{ArrowTypes<typename FieldTypes::Type, _s>::data_type()...});
}(std::make_integer_sequence<size_t, sizeof...(FieldTypes)>());
}
static Type make_builders() {
return Type(ArrowTypes<typename FieldTypes::Type, _s>::make_builder()...);
}
static auto schema() {
const auto fields =
std::vector<std::shared_ptr<arrow::Field>>({arrow::field(
typename FieldTypes::Name().str(),
ArrowTypes<typename FieldTypes::Type, _s>::data_type())...});
return arrow::schema(fields);
}
};
template <class T, SerializationType _s>
auto make_arrow_builders() {
return ArrowBuildersType<std::remove_cvref_t<T>, _s>::make_builders();
}
} // namespace rfl::parsing::tabular
#endif

View File

@@ -0,0 +1,19 @@
#ifndef RFL_PARSING_TABULAR_MAKE_ARROW_DATA_TYPES_HPP_
#define RFL_PARSING_TABULAR_MAKE_ARROW_DATA_TYPES_HPP_
#include <type_traits>
#include "../../named_tuple_t.hpp"
#include "make_arrow_builders.hpp"
namespace rfl::parsing::tabular {
template <class T, SerializationType _s>
inline auto make_arrow_data_types() {
return ArrowBuildersType<named_tuple_t<std::remove_cvref_t<T>>,
_s>::data_types();
}
} // namespace rfl::parsing::tabular
#endif

View File

@@ -0,0 +1,18 @@
#ifndef RFL_PARSING_TABULAR_MAKE_ARROW_SCHEMA_HPP_
#define RFL_PARSING_TABULAR_MAKE_ARROW_SCHEMA_HPP_
#include <type_traits>
#include "../../named_tuple_t.hpp"
#include "make_arrow_builders.hpp"
namespace rfl::parsing::tabular {
template <class T, SerializationType _s>
inline auto make_arrow_schema() {
return ArrowBuildersType<named_tuple_t<std::remove_cvref_t<T>>, _s>::schema();
}
} // namespace rfl::parsing::tabular
#endif

View File

@@ -0,0 +1,52 @@
#ifndef RFL_PARSING_TABULAR_MAKECHUNKEDARRAYITERATORS_HPP_
#define RFL_PARSING_TABULAR_MAKECHUNKEDARRAYITERATORS_HPP_
#include <arrow/api.h>
#include <string>
#include "../../NamedTuple.hpp"
#include "../../Ref.hpp"
#include "../../Result.hpp"
#include "../../Tuple.hpp"
#include "ChunkedArrayIterator.hpp"
namespace rfl::parsing::tabular {
template <class NamedTupleType, SerializationType _s>
struct MakeChunkedArrayIterators;
template <SerializationType _s, class... FieldTypes>
struct MakeChunkedArrayIterators<NamedTuple<FieldTypes...>, _s> {
using TupleType =
Tuple<ChunkedArrayIterator<typename FieldTypes::Type, _s>...>;
Result<TupleType> operator()(const Ref<arrow::Table>& _table) const {
const auto get_column =
[&](const std::string& _colname) -> Result<Ref<arrow::ChunkedArray>> {
const auto col = _table->GetColumnByName(_colname);
if (!col) {
return error("Column named '" + _colname + "' not found.");
}
return Ref<arrow::ChunkedArray>::make(col);
};
try {
return TupleType(
get_column(typename FieldTypes::Name().str())
.transform(
ChunkedArrayIterator<typename FieldTypes::Type, _s>::make)
.value()...);
} catch (const std::exception& e) {
return error(e.what());
}
}
};
template <class NamedTupleType, SerializationType _s>
const auto make_chunked_array_iterators =
MakeChunkedArrayIterators<NamedTupleType, _s>{};
} // namespace rfl::parsing::tabular
#endif

View File

@@ -0,0 +1,41 @@
#ifndef RFL_PARSING_TAGGEDUNIONWRAPPERNOPTR_HPP_
#define RFL_PARSING_TAGGEDUNIONWRAPPERNOPTR_HPP_
#include <type_traits>
#include "../internal/StringLiteral.hpp"
#include "TaggedUnionWrapper.hpp"
namespace rfl {
namespace parsing {
template <class T>
struct tagged_union_wrapper_no_ptr;
template <class T>
struct tagged_union_wrapper_no_ptr {
using Type = T;
};
template <class T, class TagType, internal::StringLiteral _discriminator,
bool _as_const_pointer>
struct tagged_union_wrapper_no_ptr<
TaggedUnionWrapperNoFields<T, TagType, _discriminator, _as_const_pointer>> {
using Type = TaggedUnionWrapperNoFields<T, TagType, _discriminator, false>;
};
template <class T, class TagType, internal::StringLiteral _discriminator,
bool _as_const_pointer>
struct tagged_union_wrapper_no_ptr<TaggedUnionWrapperWithFields<
T, TagType, _discriminator, _as_const_pointer>> {
using Type = TaggedUnionWrapperWithFields<T, TagType, _discriminator, false>;
};
template <class T>
using tagged_union_wrapper_no_ptr_t =
typename tagged_union_wrapper_no_ptr<std::remove_cvref_t<T>>::Type;
} // namespace parsing
} // namespace rfl
#endif

View File

@@ -0,0 +1,44 @@
#ifndef RFL_PARSING_TOSINGLEERRORMESSAGE_HPP_
#define RFL_PARSING_TOSINGLEERRORMESSAGE_HPP_
#include <optional>
#include <sstream>
#include <string>
#include <vector>
#include "../Result.hpp"
#include "../internal/strings/strings.hpp"
namespace rfl::parsing {
/// Combines a set of errors to a single, readable error message.
inline std::string to_single_error_message(
std::vector<Error> _errors,
std::optional<std::string> _msg_prefix = std::nullopt,
size_t _err_limit = 10) {
if (_errors.size() == 1) {
return _errors[0].what();
} else {
std::stringstream stream;
stream << (_msg_prefix
? *_msg_prefix
: "Found " + std::to_string(_errors.size()) + " errors:");
for (size_t i = 0; i < _errors.size() && i < _err_limit; ++i) {
stream << "\n"
<< i + 1 << ") "
<< internal::strings::replace_all(_errors.at(i).what(), "\n",
"\n ");
}
if (_errors.size() > _err_limit) {
stream << "\n...\nMore than " << _err_limit
<< " errors occurred, but I am only showing the "
"first "
<< _err_limit << ".";
}
return stream.str();
}
}
} // namespace rfl::parsing
#endif