This commit is contained in:
Mars 2024-06-05 19:04:53 -04:00
parent 693fa17d10
commit 500138ce67
Signed by: pupbrained
GPG key ID: 874E22DF2F9DFCB5
331 changed files with 12348 additions and 60593 deletions

13
include/rfl/bson.hpp Normal file
View file

@ -0,0 +1,13 @@
#ifndef RFL_BSON_HPP_
#define RFL_BSON_HPP_
#include "../rfl.hpp"
#include "bson/Parser.hpp"
#include "bson/Reader.hpp"
#include "bson/Writer.hpp"
#include "bson/load.hpp"
#include "bson/read.hpp"
#include "bson/save.hpp"
#include "bson/write.hpp"
#endif

View file

@ -0,0 +1,50 @@
#ifndef RFL_BSON_PARSER_HPP_
#define RFL_BSON_PARSER_HPP_
#include <bson/bson.h>
#include "../parsing/Parser.hpp"
#include "Reader.hpp"
#include "Writer.hpp"
namespace rfl::parsing {
/// bson_oid_t needs to be treated as a special case, otherwise it will be read
/// as a struct.
template <class R, class W, class ProcessorsType>
requires AreReaderAndWriter<R, W, bson_oid_t>
struct Parser<R, W, ProcessorsType, bson_oid_t> {
using InputVarType = typename R::InputVarType;
using OutputVarType = typename W::OutputVarType;
using ParentType = Parent<W>;
static Result<bson_oid_t> read(const R& _r,
const InputVarType& _var) noexcept {
return _r.template to_basic_type<bson_oid_t>(_var);
}
template <class P>
static void write(const W& _w, const bson_oid_t& _oid,
const P& _parent) noexcept {
ParentType::add_value(_w, _oid, _parent);
}
static schema::Type to_schema(
std::map<std::string, schema::Type>* _definitions) {
static_assert(rfl::always_false_v<R>,
"bson_oid_t cannot be expressed inside a JSON schema.");
return schema::Type{schema::Type::String{}};
}
};
} // namespace rfl::parsing
namespace rfl::bson {
template <class T, class ProcessorsType>
using Parser = parsing::Parser<Reader, Writer, T, ProcessorsType>;
} // namespace rfl::bson
#endif

217
include/rfl/bson/Reader.hpp Normal file
View file

@ -0,0 +1,217 @@
#ifndef RFL_BSON_READER_HPP_
#define RFL_BSON_READER_HPP_
#include <bson/bson.h>
#include <array>
#include <concepts>
#include <exception>
#include <map>
#include <memory>
#include <optional>
#include <sstream>
#include <stdexcept>
#include <string>
#include <string_view>
#include <type_traits>
#include <unordered_map>
#include <vector>
#include "../Box.hpp"
#include "../Result.hpp"
#include "../always_false.hpp"
namespace rfl {
namespace bson {
/// Please refer to https://mongoc.org/libbson/current/api.html
struct Reader {
struct BSONValue {
bson_value_t val_;
};
struct BSONInputArray {
BSONValue* val_;
};
struct BSONInputObject {
BSONValue* val_;
};
struct BSONInputVar {
BSONValue* val_;
};
using InputArrayType = BSONInputArray;
using InputObjectType = BSONInputObject;
using InputVarType = BSONInputVar;
template <class T>
static constexpr bool has_custom_constructor = (requires(InputVarType var) {
T::from_bson_obj(var);
});
rfl::Result<InputVarType> get_field(
const std::string& _name, const InputObjectType& _obj) const noexcept {
bson_t b;
bson_iter_t iter;
const auto doc = _obj.val_->val_.value.v_doc;
if (bson_init_static(&b, doc.data, doc.data_len)) {
if (bson_iter_init(&iter, &b)) {
while (bson_iter_next(&iter)) {
auto key = std::string(bson_iter_key(&iter));
if (key == _name) {
return to_input_var(&iter);
}
}
}
}
return Error("No field named '" + _name + "' was found.");
}
bool is_empty(const InputVarType& _var) const noexcept {
return _var.val_->val_.value_type == BSON_TYPE_NULL;
}
template <class T>
rfl::Result<T> to_basic_type(const InputVarType& _var) const noexcept {
const auto btype = _var.val_->val_.value_type;
const auto value = _var.val_->val_.value;
if constexpr (std::is_same<std::remove_cvref_t<T>, std::string>()) {
switch (btype) {
case BSON_TYPE_UTF8:
return std::string(value.v_utf8.str, value.v_utf8.len);
case BSON_TYPE_SYMBOL:
return std::string(value.v_symbol.symbol, value.v_symbol.len);
default:
return rfl::Error(
"Could not cast to string. The type must be UTF8 or symbol.");
}
} else if constexpr (std::is_same<std::remove_cvref_t<T>, bool>()) {
if (btype != BSON_TYPE_BOOL) {
return rfl::Error("Could not cast to boolean.");
}
return value.v_bool;
} else if constexpr (std::is_floating_point<std::remove_cvref_t<T>>() ||
std::is_integral<std::remove_cvref_t<T>>()) {
switch (btype) {
case BSON_TYPE_DOUBLE:
return static_cast<T>(value.v_double);
case BSON_TYPE_INT32:
return static_cast<T>(value.v_int32);
case BSON_TYPE_INT64:
return static_cast<T>(value.v_int64);
case BSON_TYPE_DATE_TIME:
return static_cast<T>(value.v_datetime);
default:
return rfl::Error(
"Could not cast to numeric value. The type must be double, "
"int32, int64 or date_time.");
}
} else if constexpr (std::is_same<std::remove_cvref_t<T>, bson_oid_t>()) {
if (btype != BSON_TYPE_OID) {
return rfl::Error("Could not cast to OID.");
}
return value.v_oid;
} else {
static_assert(rfl::always_false_v<T>, "Unsupported type.");
}
}
rfl::Result<InputArrayType> to_array(
const InputVarType& _var) const noexcept {
const auto btype = _var.val_->val_.value_type;
if (btype != BSON_TYPE_ARRAY && btype != BSON_TYPE_DOCUMENT) {
return Error("Could not cast to an array.");
}
return InputArrayType{_var.val_};
}
template <class ArrayReader>
std::optional<Error> read_array(const ArrayReader& _array_reader,
const InputArrayType& _arr) const noexcept {
bson_t b;
bson_iter_t iter;
const auto doc = _arr.val_->val_.value.v_doc;
if (bson_init_static(&b, doc.data, doc.data_len)) {
if (bson_iter_init(&iter, &b)) {
while (bson_iter_next(&iter)) {
const auto err = _array_reader.read(to_input_var(&iter));
if (err) {
return err;
}
}
}
}
return std::nullopt;
}
template <class ObjectReader>
std::optional<Error> read_object(const ObjectReader& _object_reader,
const InputObjectType& _obj) const noexcept {
bson_t b;
bson_iter_t iter;
const auto doc = _obj.val_->val_.value.v_doc;
if (bson_init_static(&b, doc.data, doc.data_len)) {
if (bson_iter_init(&iter, &b)) {
while (bson_iter_next(&iter)) {
const char* k = bson_iter_key(&iter);
_object_reader.read(std::string_view(k), to_input_var(&iter));
}
}
}
return std::nullopt;
}
rfl::Result<InputObjectType> to_object(
const InputVarType& _var) const noexcept {
const auto btype = _var.val_->val_.value_type;
if (btype != BSON_TYPE_DOCUMENT) {
return Error("Could not cast to a document.");
}
return InputObjectType{_var.val_};
}
template <class T>
rfl::Result<T> use_custom_constructor(
const InputVarType& _var) const noexcept {
try {
return T::from_bson_obj(_var);
} catch (std::exception& e) {
return rfl::Error(e.what());
}
}
private:
struct BSONValues {
std::vector<rfl::Box<BSONValue>> vec_;
~BSONValues() {
for (auto& v : vec_) {
bson_value_destroy(&(v->val_));
}
}
};
private:
InputVarType to_input_var(bson_iter_t* _iter) const noexcept {
values_->vec_.emplace_back(rfl::Box<BSONValue>::make());
auto* last_value = values_->vec_.back().get();
bson_value_copy(bson_iter_value(_iter), &last_value->val_);
return InputVarType{last_value};
}
private:
/// Contains the values inside the object.
rfl::Ref<BSONValues> values_;
};
} // namespace bson
} // namespace rfl
#endif // JSON_PARSER_HPP_

227
include/rfl/bson/Writer.hpp Normal file
View file

@ -0,0 +1,227 @@
#ifndef RFL_BSON_WRITER_HPP_
#define RFL_BSON_WRITER_HPP_
#include <bson/bson.h>
#include <exception>
#include <map>
#include <sstream>
#include <stdexcept>
#include <string>
#include <string_view>
#include <type_traits>
#include <variant>
#include <vector>
#include "../Box.hpp"
#include "../Ref.hpp"
#include "../Result.hpp"
#include "../always_false.hpp"
namespace rfl {
namespace bson {
/// Please refer to https://mongoc.org/libbson/current/api.html
class Writer {
struct BSONType {
bson_t val_;
};
struct IsArray {
bson_array_builder_t* ptr_;
};
struct IsObject {
bson_t* ptr_;
};
struct IsRoot {};
using ParentType = std::variant<IsArray, IsObject, IsRoot>;
public:
struct BSONOutputArray {
BSONOutputArray(bson_array_builder_t* _val, ParentType _parent)
: parent_(_parent), val_(_val) {}
ParentType parent_;
bson_array_builder_t* val_;
};
struct BSONOutputObject {
BSONOutputObject(bson_t* _val, ParentType _parent)
: parent_(_parent), val_(_val) {}
ParentType parent_;
bson_t* val_;
};
struct BSONOutputVar {};
using OutputArrayType = BSONOutputArray;
using OutputObjectType = BSONOutputObject;
using OutputVarType = BSONOutputVar;
Writer(bson_t* _doc) : doc_(_doc) {}
~Writer() = default;
OutputArrayType array_as_root(const size_t _size) const noexcept {
bson_array_builder_t* val = bson_array_builder_new();
return OutputArrayType(val, IsRoot{});
}
OutputObjectType object_as_root(const size_t _size) const noexcept {
return OutputObjectType(doc_, IsRoot{});
}
OutputVarType null_as_root() const noexcept {
// Appears to be unsupported by the BSON C API.
return OutputVarType{};
}
template <class T>
OutputVarType value_as_root(const T& _var) const noexcept {
static_assert(rfl::always_false_v<T>,
"BSON only allows arrays or objects as its root.");
return OutputVarType{};
}
OutputArrayType add_array_to_array(const size_t _size,
OutputArrayType* _parent) const noexcept {
bson_array_builder_t* val;
bson_array_builder_append_array_builder_begin(_parent->val_, &val);
return OutputArrayType(val, IsArray{_parent->val_});
}
OutputArrayType add_array_to_object(
const std::string_view& _name, const size_t _size,
OutputObjectType* _parent) const noexcept {
bson_array_builder_t* val;
bson_append_array_builder_begin(_parent->val_, _name.data(),
static_cast<int>(_name.size()), &val);
return OutputArrayType(val, IsObject{_parent->val_});
}
OutputObjectType add_object_to_array(
const size_t _size, OutputArrayType* _parent) const noexcept {
subdocs_->emplace_back(rfl::Box<BSONType>());
bson_array_builder_append_document_begin(_parent->val_,
&(subdocs_->back()->val_));
return OutputObjectType(&subdocs_->back()->val_, IsArray{_parent->val_});
}
OutputObjectType add_object_to_object(
const std::string_view& _name, const size_t _size,
OutputObjectType* _parent) const noexcept {
subdocs_->emplace_back(rfl::Box<BSONType>());
bson_append_document_begin(_parent->val_, _name.data(),
static_cast<int>(_name.size()),
&(subdocs_->back()->val_));
return OutputObjectType(&subdocs_->back()->val_, IsObject{_parent->val_});
}
template <class T>
OutputVarType add_value_to_array(const T& _var,
OutputArrayType* _parent) const noexcept {
if constexpr (std::is_same<std::remove_cvref_t<T>, std::string>()) {
bson_array_builder_append_utf8(_parent->val_, _var.c_str(),
static_cast<int>(_var.size()));
} else if constexpr (std::is_same<std::remove_cvref_t<T>, bool>()) {
bson_array_builder_append_bool(_parent->val_, _var);
} else if constexpr (std::is_floating_point<std::remove_cvref_t<T>>()) {
bson_array_builder_append_double(_parent->val_,
static_cast<double>(_var));
} else if constexpr (std::is_integral<std::remove_cvref_t<T>>()) {
bson_array_builder_append_int64(_parent->val_,
static_cast<std::int64_t>(_var));
} else if constexpr (std::is_same<std::remove_cvref_t<T>, bson_oid_t>()) {
bson_array_builder_append_oid(_parent->val_, &_var);
} else {
static_assert(rfl::always_false_v<T>, "Unsupported type.");
}
return OutputVarType{};
}
template <class T>
OutputVarType add_value_to_object(const std::string_view& _name,
const T& _var,
OutputObjectType* _parent) const noexcept {
if constexpr (std::is_same<std::remove_cvref_t<T>, std::string>()) {
bson_append_utf8(_parent->val_, _name.data(),
static_cast<int>(_name.size()), _var.c_str(),
static_cast<int>(_var.size()));
} else if constexpr (std::is_same<std::remove_cvref_t<T>, bool>()) {
bson_append_bool(_parent->val_, _name.data(),
static_cast<int>(_name.size()), _var);
} else if constexpr (std::is_floating_point<std::remove_cvref_t<T>>()) {
bson_append_double(_parent->val_, _name.data(),
static_cast<int>(_name.size()),
static_cast<double>(_var));
} else if constexpr (std::is_integral<std::remove_cvref_t<T>>()) {
bson_append_int64(_parent->val_, _name.data(),
static_cast<int>(_name.size()),
static_cast<std::int64_t>(_var));
} else if constexpr (std::is_same<std::remove_cvref_t<T>, bson_oid_t>()) {
bson_append_oid(_parent->val_, _name.data(),
static_cast<int>(_name.size()), &_var);
} else {
static_assert(rfl::always_false_v<T>, "Unsupported type.");
}
return OutputVarType{};
}
OutputVarType add_null_to_array(OutputArrayType* _parent) const noexcept {
bson_array_builder_append_null(_parent->val_);
return OutputVarType{};
}
OutputVarType add_null_to_object(const std::string_view& _name,
OutputObjectType* _parent) const noexcept {
bson_append_null(_parent->val_, _name.data(),
static_cast<int>(_name.size()));
return OutputVarType{};
}
void end_array(OutputArrayType* _arr) const noexcept {
const auto handle = [&](const auto _parent) {
using Type = std::remove_cvref_t<decltype(_parent)>;
if constexpr (std::is_same<Type, IsArray>()) {
bson_array_builder_append_array_builder_end(_parent.ptr_, _arr->val_);
} else if constexpr (std::is_same<Type, IsObject>()) {
bson_append_array_builder_end(_parent.ptr_, _arr->val_);
} else if constexpr (std::is_same<Type, IsRoot>()) {
bson_array_builder_build(_arr->val_, doc_);
} else {
static_assert(rfl::always_false_v<Type>, "Unsupported type.");
}
};
std::visit(handle, _arr->parent_);
}
void end_object(OutputObjectType* _obj) const noexcept {
const auto handle = [&](const auto _parent) {
using Type = std::remove_cvref_t<decltype(_parent)>;
if constexpr (std::is_same<Type, IsArray>()) {
bson_array_builder_append_document_end(_parent.ptr_, _obj->val_);
} else if constexpr (std::is_same<Type, IsObject>()) {
bson_append_document_end(_parent.ptr_, _obj->val_);
} else if constexpr (std::is_same<Type, IsRoot>()) {
} else {
static_assert(rfl::always_false_v<Type>, "Unsupported type.");
}
};
std::visit(handle, _obj->parent_);
}
private:
/// Pointer to the main document. In BSON, documents are what are usually
/// called objects.
bson_t* const doc_;
/// Contain all of the subdocuments.
const rfl::Ref<std::vector<rfl::Box<BSONType>>> subdocs_;
};
} // namespace bson
} // namespace rfl
#endif // BSON_PARSER_HPP_

22
include/rfl/bson/load.hpp Normal file
View file

@ -0,0 +1,22 @@
#ifndef RFL_BSON_LOAD_HPP_
#define RFL_BSON_LOAD_HPP_
#include "../Result.hpp"
#include "../io/load_bytes.hpp"
#include "read.hpp"
namespace rfl {
namespace bson {
template <class T, class... Ps>
Result<T> load(const std::string& _fname) {
const auto read_bytes = [](const auto& _bytes) {
return read<T, Ps...>(_bytes);
};
return rfl::io::load_bytes(_fname).and_then(read_bytes);
}
} // namespace bson
} // namespace rfl
#endif

61
include/rfl/bson/read.hpp Normal file
View file

@ -0,0 +1,61 @@
#ifndef RFL_BSON_READ_HPP_
#define RFL_BSON_READ_HPP_
#include <bson/bson.h>
#include <istream>
#include <string>
#include "../Processors.hpp"
#include "../internal/wrap_in_rfl_array_t.hpp"
#include "Parser.hpp"
#include "Reader.hpp"
namespace rfl {
namespace bson {
using InputObjectType = typename Reader::InputObjectType;
using InputVarType = typename Reader::InputVarType;
/// Parses an object from a BSON var.
template <class T, class... Ps>
Result<internal::wrap_in_rfl_array_t<T>> read(const InputVarType& _obj) {
const auto r = Reader();
return Parser<T, Processors<Ps...>>::read(r, _obj);
}
/// Parses an BSON object using reflection.
template <class T, class... Ps>
auto read(const uint8_t* _bytes, const size_t _size) {
Reader::BSONValue value;
value.val_.value.v_doc.data_len = static_cast<uint32_t>(_size);
value.val_.value.v_doc.data = const_cast<uint8_t*>(_bytes);
value.val_.value_type = BSON_TYPE_DOCUMENT;
auto doc = InputVarType{&value};
return read<T, Ps...>(doc);
}
/// Parses an BSON object using reflection.
template <class T, class... Ps>
auto read(const char* _bytes, const size_t _size) {
return read<T, Ps...>(reinterpret_cast<const uint8_t*>(_bytes), _size);
}
/// Parses an object from BSON using reflection.
template <class T, class... Ps>
auto read(const std::vector<char>& _bytes) {
return read<T, Ps...>(_bytes.data(), _bytes.size());
}
/// Parses an object from a stream.
template <class T, class... Ps>
auto read(std::istream& _stream) {
std::istreambuf_iterator<char> begin(_stream), end;
auto bytes = std::vector<char>(begin, end);
return read<T, Ps...>(bytes.data(), bytes.size());
}
} // namespace bson
} // namespace rfl
#endif

26
include/rfl/bson/save.hpp Normal file
View file

@ -0,0 +1,26 @@
#ifndef RFL_BSON_SAVE_HPP_
#define RFL_BSON_SAVE_HPP_
#include <fstream>
#include <iostream>
#include <string>
#include "../Result.hpp"
#include "../io/save_bytes.hpp"
#include "write.hpp"
namespace rfl {
namespace bson {
template <class... Ps>
Result<Nothing> save(const std::string& _fname, const auto& _obj) {
const auto write_func = [](const auto& _obj, auto& _stream) -> auto& {
return write<Ps...>(_obj, _stream);
};
return rfl::io::save_bytes(_fname, _obj, write_func);
}
} // namespace bson
} // namespace rfl
#endif

View file

@ -0,0 +1,61 @@
#ifndef RFL_BSON_WRITE_HPP_
#define RFL_BSON_WRITE_HPP_
#include <bson/bson.h>
#include <ostream>
#include <sstream>
#include <string>
#include <utility>
#include "../Processors.hpp"
#include "../parsing/Parent.hpp"
#include "Parser.hpp"
namespace rfl {
namespace bson {
/// Returns BSON bytes. Careful: It is the responsibility of the caller to call
/// bson_free on the returned pointer.
template <class... Ps>
std::pair<uint8_t*, size_t> to_buffer(const auto& _obj) noexcept {
using T = std::remove_cvref_t<decltype(_obj)>;
using ParentType = parsing::Parent<Writer>;
bson_t* doc = nullptr;
uint8_t* buf = nullptr;
size_t buflen = 0;
bson_writer_t* bson_writer =
bson_writer_new(&buf, &buflen, 0, bson_realloc_ctx, NULL);
bson_writer_begin(bson_writer, &doc);
const auto rfl_writer = Writer(doc);
Parser<T, Processors<Ps...>>::write(rfl_writer, _obj,
typename ParentType::Root{});
bson_writer_end(bson_writer);
const auto len = bson_writer_get_length(bson_writer);
bson_writer_destroy(bson_writer);
return std::make_pair(buf, len);
}
/// Returns BSON bytes.
template <class... Ps>
std::vector<char> write(const auto& _obj) noexcept {
auto [buf, len] = to_buffer<Ps...>(_obj);
const auto result = std::vector<char>(reinterpret_cast<char*>(buf),
reinterpret_cast<char*>(buf) + len);
bson_free(buf);
return result;
}
/// Writes a BSON into an ostream.
template <class... Ps>
std::ostream& write(const auto& _obj, std::ostream& _stream) noexcept {
auto [buf, len] = to_buffer<Ps...>(_obj);
_stream.write(reinterpret_cast<const char*>(buf), len);
bson_free(buf);
return _stream;
}
} // namespace bson
} // namespace rfl
#endif // BSON_PARSER_HPP_

13
include/rfl/cbor.hpp Normal file
View file

@ -0,0 +1,13 @@
#ifndef RFL_CBOR_HPP_
#define RFL_CBOR_HPP_
#include "../rfl.hpp"
#include "cbor/Parser.hpp"
#include "cbor/Reader.hpp"
#include "cbor/Writer.hpp"
#include "cbor/load.hpp"
#include "cbor/read.hpp"
#include "cbor/save.hpp"
#include "cbor/write.hpp"
#endif

View file

@ -0,0 +1,45 @@
#ifndef RFL_CBOR_PARSER_HPP_
#define RFL_CBOR_PARSER_HPP_
#include "../parsing/Parser.hpp"
#include "Reader.hpp"
#include "Writer.hpp"
namespace rfl {
namespace parsing {
/// CBOR requires us to explicitly set the number of fields in advance. Because
/// of that, we require all of the fields and then set them to nullptr, if
/// necessary.
template <class ProcessorsType, class... FieldTypes>
requires AreReaderAndWriter<cbor::Reader, cbor::Writer,
NamedTuple<FieldTypes...>>
struct Parser<cbor::Reader, cbor::Writer, NamedTuple<FieldTypes...>,
ProcessorsType>
: public NamedTupleParser<cbor::Reader, cbor::Writer,
/*_ignore_empty_containers=*/false,
/*_all_required=*/true, ProcessorsType,
FieldTypes...> {
};
template <class ProcessorsType, class... Ts>
requires AreReaderAndWriter<cbor::Reader, cbor::Writer, std::tuple<Ts...>>
struct Parser<cbor::Reader, cbor::Writer, std::tuple<Ts...>, ProcessorsType>
: public TupleParser<cbor::Reader, cbor::Writer,
/*_ignore_empty_containers=*/false,
/*_all_required=*/true, ProcessorsType, Ts...> {
};
} // namespace parsing
} // namespace rfl
namespace rfl {
namespace cbor {
template <class T, class ProcessorsType>
using Parser = parsing::Parser<Reader, Writer, T, ProcessorsType>;
}
} // namespace rfl
#endif

259
include/rfl/cbor/Reader.hpp Normal file
View file

@ -0,0 +1,259 @@
#ifndef RFL_CBOR_READER_HPP_
#define RFL_CBOR_READER_HPP_
#include <cbor.h>
#include <array>
#include <concepts>
#include <exception>
#include <map>
#include <memory>
#include <source_location>
#include <sstream>
#include <stdexcept>
#include <string>
#include <string_view>
#include <type_traits>
#include <unordered_map>
#include <vector>
#include "../Box.hpp"
#include "../Result.hpp"
#include "../always_false.hpp"
namespace rfl {
namespace cbor {
/// Please refer to https://intel.github.io/tinycbor/current/index.html
struct Reader {
struct CBORInputArray {
CborValue* val_;
};
struct CBORInputObject {
CborValue* val_;
};
struct CBORInputVar {
CborValue* val_;
};
using InputArrayType = CBORInputArray;
using InputObjectType = CBORInputObject;
using InputVarType = CBORInputVar;
template <class T>
static constexpr bool has_custom_constructor = (requires(InputVarType var) {
T::from_cbor_obj(var);
});
rfl::Result<InputVarType> get_field(
const std::string& _name, const InputObjectType& _obj) const noexcept {
CborValue val;
auto buffer = std::vector<char>();
auto err = cbor_value_enter_container(_obj.val_, &val);
if (err != CborNoError) {
return Error(cbor_error_string(err));
}
size_t length = 0;
err = cbor_value_get_map_length(_obj.val_, &length);
if (err != CborNoError) {
return Error(cbor_error_string(err));
}
for (size_t i = 0; i < length; ++i) {
if (!cbor_value_is_text_string(&val)) {
return Error("Expected the key to be a string value.");
}
err = get_string(&val, &buffer);
if (err != CborNoError) {
return Error(cbor_error_string(err));
}
err = cbor_value_advance(&val);
if (err != CborNoError) {
return Error(cbor_error_string(err));
}
if (_name == buffer.data()) {
return to_input_var(&val);
}
err = cbor_value_advance(&val);
if (err != CborNoError) {
return Error(cbor_error_string(err));
}
}
return Error("No field named '" + _name + "' was found.");
}
bool is_empty(const InputVarType& _var) const noexcept {
return cbor_value_is_null(_var.val_);
}
template <class T>
rfl::Result<T> to_basic_type(const InputVarType& _var) const noexcept {
if constexpr (std::is_same<std::remove_cvref_t<T>, std::string>()) {
if (!cbor_value_is_text_string(_var.val_)) {
return Error("Could not cast to string.");
}
std::vector<char> buffer;
const auto err = get_string(_var.val_, &buffer);
if (err != CborNoError) {
return Error(cbor_error_string(err));
}
return std::string(buffer.data());
} else if constexpr (std::is_same<std::remove_cvref_t<T>, bool>()) {
if (!cbor_value_is_boolean(_var.val_)) {
return rfl::Error("Could not cast to boolean.");
}
bool result = false;
const auto err = cbor_value_get_boolean(_var.val_, &result);
if (err != CborNoError) {
return Error(cbor_error_string(err));
}
return result;
} else if constexpr (std::is_floating_point<std::remove_cvref_t<T>>() ||
std::is_integral<std::remove_cvref_t<T>>()) {
if (cbor_value_is_integer(_var.val_)) {
std::int64_t result = 0;
const auto err = cbor_value_get_int64(_var.val_, &result);
if (err != CborNoError) {
return Error(cbor_error_string(err));
}
return static_cast<T>(result);
} else if (cbor_value_is_float(_var.val_)) {
float result = 0.0;
const auto err = cbor_value_get_float(_var.val_, &result);
if (err != CborNoError) {
return Error(cbor_error_string(err));
}
return static_cast<T>(result);
} else if (cbor_value_is_double(_var.val_)) {
double result = 0.0;
const auto err = cbor_value_get_double(_var.val_, &result);
if (err != CborNoError) {
return Error(cbor_error_string(err));
}
return static_cast<T>(result);
}
return rfl::Error(
"Could not cast to numeric value. The type must be integral, float "
"or double.");
} else {
static_assert(rfl::always_false_v<T>, "Unsupported type.");
}
}
rfl::Result<InputArrayType> to_array(
const InputVarType& _var) const noexcept {
if (!cbor_value_is_array(_var.val_)) {
return Error("Could not cast to an array.");
}
return InputArrayType{_var.val_};
}
rfl::Result<InputObjectType> to_object(
const InputVarType& _var) const noexcept {
if (!cbor_value_is_map(_var.val_)) {
return Error("Could not cast to an object.");
}
return InputObjectType{_var.val_};
}
template <class ArrayReader>
std::optional<Error> read_array(const ArrayReader& _array_reader,
const InputArrayType& _arr) const noexcept {
CborValue val;
auto buffer = std::vector<char>();
auto err = cbor_value_enter_container(_arr.val_, &val);
if (err != CborNoError && err != CborErrorOutOfMemory) {
return Error(cbor_error_string(err));
}
size_t length = 0;
err = cbor_value_get_array_length(_arr.val_, &length);
if (err != CborNoError && err != CborErrorOutOfMemory) {
return Error(cbor_error_string(err));
}
for (size_t i = 0; i < length; ++i) {
const auto err2 = _array_reader.read(to_input_var(&val));
if (err2) {
return err2;
}
err = cbor_value_advance(&val);
if (err != CborNoError && err != CborErrorOutOfMemory) {
return Error(cbor_error_string(err));
}
}
return std::nullopt;
}
template <class ObjectReader>
std::optional<Error> read_object(const ObjectReader& _object_reader,
const InputObjectType& _obj) const noexcept {
size_t length = 0;
auto err = cbor_value_get_map_length(_obj.val_, &length);
if (err != CborNoError) {
return Error(cbor_error_string(err));
}
CborValue val;
err = cbor_value_enter_container(_obj.val_, &val);
if (err != CborNoError) {
return Error(cbor_error_string(err));
}
auto buffer = std::vector<char>();
for (size_t i = 0; i < length; ++i) {
err = get_string(&val, &buffer);
if (err != CborNoError) {
return Error(cbor_error_string(err));
}
err = cbor_value_advance(&val);
if (err != CborNoError) {
return Error(cbor_error_string(err));
}
const auto name = std::string_view(buffer.data(), buffer.size() - 1);
_object_reader.read(name, InputVarType{&val});
cbor_value_advance(&val);
}
return std::nullopt;
}
template <class T>
rfl::Result<T> use_custom_constructor(
const InputVarType& _var) const noexcept {
try {
return T::from_cbor_obj(_var);
} catch (std::exception& e) {
return rfl::Error(e.what());
}
}
private:
CborError get_string(const CborValue* _ptr,
std::vector<char>* _buffer) const noexcept {
size_t length = 0;
auto err = cbor_value_get_string_length(_ptr, &length);
if (err != CborNoError && err != CborErrorOutOfMemory) {
return err;
}
_buffer->resize(length + 1);
(*_buffer)[length] = '\0';
return cbor_value_copy_text_string(_ptr, _buffer->data(), &length, NULL);
}
InputVarType to_input_var(CborValue* _ptr) const noexcept {
values_->emplace_back(rfl::Box<CborValue>::make(*_ptr));
auto* last_value = values_->back().get();
return InputVarType{last_value};
}
private:
/// Contains the values inside the object.
rfl::Box<std::vector<rfl::Box<CborValue>>> values_;
};
} // namespace cbor
} // namespace rfl
#endif // JSON_PARSER_HPP_

164
include/rfl/cbor/Writer.hpp Normal file
View file

@ -0,0 +1,164 @@
#ifndef RFL_CBOR_WRITER_HPP_
#define RFL_CBOR_WRITER_HPP_
#include <cbor.h>
#include <exception>
#include <map>
#include <sstream>
#include <stdexcept>
#include <string>
#include <string_view>
#include <type_traits>
#include <variant>
#include <vector>
#include "../Box.hpp"
#include "../Ref.hpp"
#include "../Result.hpp"
#include "../always_false.hpp"
namespace rfl {
namespace cbor {
class Writer {
public:
struct CBOROutputArray {
CborEncoder* encoder_;
CborEncoder* parent_;
};
struct CBOROutputObject {
CborEncoder* encoder_;
CborEncoder* parent_;
};
struct CBOROutputVar {};
using OutputArrayType = CBOROutputArray;
using OutputObjectType = CBOROutputObject;
using OutputVarType = CBOROutputVar;
Writer(CborEncoder* _encoder) : encoder_(_encoder) {}
~Writer() = default;
OutputArrayType array_as_root(const size_t _size) const noexcept {
return new_array(_size, encoder_);
}
OutputObjectType object_as_root(const size_t _size) const noexcept {
return new_object(_size, encoder_);
}
OutputVarType null_as_root() const noexcept {
cbor_encode_null(encoder_);
return OutputVarType{};
}
template <class T>
OutputVarType value_as_root(const T& _var) const noexcept {
return new_value(_var, encoder_);
}
OutputArrayType add_array_to_array(const size_t _size,
OutputArrayType* _parent) const noexcept {
return new_array(_size, _parent->encoder_);
}
OutputArrayType add_array_to_object(
const std::string_view& _name, const size_t _size,
OutputObjectType* _parent) const noexcept {
cbor_encode_text_string(_parent->encoder_, _name.data(), _name.size());
return new_array(_size, _parent->encoder_);
}
OutputObjectType add_object_to_array(
const size_t _size, OutputArrayType* _parent) const noexcept {
return new_object(_size, _parent->encoder_);
}
OutputObjectType add_object_to_object(
const std::string_view& _name, const size_t _size,
OutputObjectType* _parent) const noexcept {
cbor_encode_text_string(_parent->encoder_, _name.data(), _name.size());
return new_object(_size, _parent->encoder_);
}
template <class T>
OutputVarType add_value_to_array(const T& _var,
OutputArrayType* _parent) const noexcept {
return new_value(_var, _parent->encoder_);
}
template <class T>
OutputVarType add_value_to_object(const std::string_view& _name,
const T& _var,
OutputObjectType* _parent) const noexcept {
cbor_encode_text_string(_parent->encoder_, _name.data(), _name.size());
return new_value(_var, _parent->encoder_);
}
OutputVarType add_null_to_array(OutputArrayType* _parent) const noexcept {
cbor_encode_null(_parent->encoder_);
return OutputVarType{};
}
OutputVarType add_null_to_object(const std::string_view& _name,
OutputObjectType* _parent) const noexcept {
cbor_encode_text_string(_parent->encoder_, _name.data(), _name.size());
cbor_encode_null(_parent->encoder_);
return OutputVarType{};
}
void end_array(OutputArrayType* _arr) const noexcept {
cbor_encoder_close_container(_arr->parent_, _arr->encoder_);
}
void end_object(OutputObjectType* _obj) const noexcept {
cbor_encoder_close_container(_obj->parent_, _obj->encoder_);
}
private:
OutputArrayType new_array(const size_t _size,
CborEncoder* _parent) const noexcept {
subencoders_->emplace_back(rfl::Box<CborEncoder>::make());
cbor_encoder_create_array(_parent, subencoders_->back().get(), _size);
return OutputArrayType{subencoders_->back().get(), _parent};
}
OutputObjectType new_object(const size_t _size,
CborEncoder* _parent) const noexcept {
subencoders_->emplace_back(rfl::Box<CborEncoder>::make());
cbor_encoder_create_map(_parent, subencoders_->back().get(), _size);
return OutputObjectType{subencoders_->back().get(), _parent};
}
template <class T>
OutputVarType new_value(const T& _var, CborEncoder* _parent) const noexcept {
if constexpr (std::is_same<std::remove_cvref_t<T>, std::string>()) {
cbor_encode_text_string(_parent, _var.c_str(), _var.size());
} else if constexpr (std::is_same<std::remove_cvref_t<T>, bool>()) {
cbor_encode_boolean(_parent, _var);
} else if constexpr (std::is_floating_point<std::remove_cvref_t<T>>()) {
cbor_encode_double(_parent, static_cast<double>(_var));
} else if constexpr (std::is_integral<std::remove_cvref_t<T>>()) {
cbor_encode_int(_parent, static_cast<std::int64_t>(_var));
} else {
static_assert(rfl::always_false_v<T>, "Unsupported type.");
}
return OutputVarType{};
}
private:
/// The underlying TinyCBOR encoder.
CborEncoder* const encoder_;
/// Contain all of the subobjects and subarrays.
const rfl::Box<std::vector<rfl::Box<CborEncoder>>> subencoders_;
};
} // namespace cbor
} // namespace rfl
#endif // CBOR_PARSER_HPP_

23
include/rfl/cbor/load.hpp Normal file
View file

@ -0,0 +1,23 @@
#ifndef RFL_CBOR_LOAD_HPP_
#define RFL_CBOR_LOAD_HPP_
#include "../Processors.hpp"
#include "../Result.hpp"
#include "../io/load_bytes.hpp"
#include "read.hpp"
namespace rfl {
namespace cbor {
template <class T, class... Ps>
Result<T> load(const std::string& _fname) {
const auto read_bytes = [](const auto& _bytes) {
return read<T, Ps...>(_bytes);
};
return rfl::io::load_bytes(_fname).and_then(read_bytes);
}
} // namespace cbor
} // namespace rfl
#endif

57
include/rfl/cbor/read.hpp Normal file
View file

@ -0,0 +1,57 @@
#ifndef RFL_CBOR_READ_HPP_
#define RFL_CBOR_READ_HPP_
#include <cbor.h>
#include <istream>
#include <string>
#include "../Processors.hpp"
#include "../internal/wrap_in_rfl_array_t.hpp"
#include "Parser.hpp"
#include "Reader.hpp"
namespace rfl {
namespace cbor {
using InputObjectType = typename Reader::InputObjectType;
using InputVarType = typename Reader::InputVarType;
/// Parses an object from a CBOR var.
template <class T, class... Ps>
auto read(const InputVarType& _obj) {
const auto r = Reader();
return Parser<T, Processors<Ps...>>::read(r, _obj);
}
/// Parses an object from CBOR using reflection.
template <class T, class... Ps>
Result<internal::wrap_in_rfl_array_t<T>> read(const char* _bytes,
const size_t _size) {
CborParser parser;
CborValue value;
cbor_parser_init(reinterpret_cast<const uint8_t*>(_bytes), _size, 0, &parser,
&value);
auto doc = InputVarType{&value};
auto result = read<T, Ps...>(doc);
return result;
}
/// Parses an object from CBOR using reflection.
template <class T, class... Ps>
auto read(const std::vector<char>& _bytes) {
return read<T, Ps...>(_bytes.data(), _bytes.size());
}
/// Parses an object from a stream.
template <class T, class... Ps>
auto read(std::istream& _stream) {
std::istreambuf_iterator<char> begin(_stream), end;
auto bytes = std::vector<char>(begin, end);
return read<T, Ps...>(bytes.data(), bytes.size());
}
} // namespace cbor
} // namespace rfl
#endif

26
include/rfl/cbor/save.hpp Normal file
View file

@ -0,0 +1,26 @@
#ifndef RFL_CBOR_SAVE_HPP_
#define RFL_CBOR_SAVE_HPP_
#include <fstream>
#include <iostream>
#include <string>
#include "../Result.hpp"
#include "../io/save_bytes.hpp"
#include "write.hpp"
namespace rfl {
namespace cbor {
template <class... Ps>
Result<Nothing> save(const std::string& _fname, const auto& _obj) {
const auto write_func = [](const auto& _obj, auto& _stream) -> auto& {
return write<Ps...>(_obj, _stream);
};
return rfl::io::save_bytes(_fname, _obj, write_func);
}
} // namespace cbor
} // namespace rfl
#endif

View file

@ -0,0 +1,59 @@
#ifndef RFL_CBOR_WRITE_HPP_
#define RFL_CBOR_WRITE_HPP_
#include <cbor.h>
#include <cstdint>
#include <ostream>
#include <sstream>
#include <string>
#include <utility>
#include "../parsing/Parent.hpp"
#include "Parser.hpp"
namespace rfl {
namespace cbor {
template <class... Ps>
void write_into_buffer(const auto& _obj, CborEncoder* _encoder,
std::vector<char>* _buffer) noexcept {
using T = std::remove_cvref_t<decltype(_obj)>;
using ParentType = parsing::Parent<Writer>;
cbor_encoder_init(_encoder, reinterpret_cast<uint8_t*>(_buffer->data()),
_buffer->size(), 0);
const auto writer = Writer(_encoder);
Parser<T, Processors<Ps...>>::write(writer, _obj,
typename ParentType::Root{});
}
/// Returns CBOR bytes.
template <class... Ps>
std::vector<char> write(const auto& _obj) noexcept {
std::vector<char> buffer(4096);
CborEncoder encoder;
write_into_buffer<Ps...>(_obj, &encoder, &buffer);
const auto total_bytes_needed =
buffer.size() + cbor_encoder_get_extra_bytes_needed(&encoder);
if (total_bytes_needed != buffer.size()) {
buffer.resize(total_bytes_needed);
write_into_buffer<Ps...>(_obj, &encoder, &buffer);
}
const auto length = cbor_encoder_get_buffer_size(
&encoder, reinterpret_cast<uint8_t*>(buffer.data()));
buffer.resize(length);
return buffer;
}
/// Writes a CBOR into an ostream.
template <class... Ps>
std::ostream& write(const auto& _obj, std::ostream& _stream) noexcept {
auto buffer = write<Ps...>(_obj);
_stream.write(buffer.data(), buffer.size());
return _stream;
}
} // namespace cbor
} // namespace rfl
#endif

13
include/rfl/flexbuf.hpp Normal file
View file

@ -0,0 +1,13 @@
#ifndef RFL_FLEXBUF_HPP_
#define RFL_FLEXBUF_HPP_
#include "../rfl.hpp"
#include "flexbuf/Parser.hpp"
#include "flexbuf/Reader.hpp"
#include "flexbuf/Writer.hpp"
#include "flexbuf/load.hpp"
#include "flexbuf/read.hpp"
#include "flexbuf/save.hpp"
#include "flexbuf/write.hpp"
#endif

View file

@ -0,0 +1,17 @@
#ifndef FLEXBUF_PARSER_HPP_
#define FLEXBUF_PARSER_HPP_
#include "../parsing/Parser.hpp"
#include "Reader.hpp"
#include "Writer.hpp"
namespace rfl {
namespace flexbuf {
template <class T, class ProcessorsType>
using Parser = parsing::Parser<Reader, Writer, T, ProcessorsType>;
}
} // namespace rfl
#endif

View file

@ -0,0 +1,145 @@
#ifndef FLEXBUF_READER_HPP_
#define FLEXBUF_READER_HPP_
#include <flatbuffers/flexbuffers.h>
#include <exception>
#include <map>
#include <sstream>
#include <stdexcept>
#include <string>
#include <string_view>
#include <type_traits>
#include <vector>
#include "../Result.hpp"
#include "../always_false.hpp"
namespace rfl {
namespace flexbuf {
struct Reader {
using InputArrayType = flexbuffers::Vector;
using InputObjectType = flexbuffers::Map;
using InputVarType = flexbuffers::Reference;
template <class T, class = void>
struct has_from_flexbuf : std::false_type {};
template <class T>
struct has_from_flexbuf<
T, std::enable_if_t<std::is_invocable_r<T, decltype(T::from_flexbuf),
InputVarType>::value>>
: std::true_type {};
template <class T>
struct has_from_flexbuf<
T, std::enable_if_t<std::is_invocable_r<
rfl::Result<T>, decltype(T::from_flexbuf), InputVarType>::value>>
: std::true_type {};
template <class T>
static constexpr bool has_custom_constructor = has_from_flexbuf<T>::value;
rfl::Result<InputVarType> get_field(
const std::string& _name, const InputObjectType& _obj) const noexcept {
const auto keys = _obj.Keys();
for (size_t i = 0; i < keys.size(); ++i) {
if (_name == keys[i].AsString().c_str()) {
return _obj.Values()[i];
}
}
return rfl::Error("Map does not contain any element called '" + _name +
"'.");
}
bool is_empty(const InputVarType& _var) const noexcept {
return _var.IsNull();
}
template <class T>
rfl::Result<T> to_basic_type(const InputVarType& _var) const noexcept {
if constexpr (std::is_same<std::remove_cvref_t<T>, std::string>()) {
if (!_var.IsString()) {
return rfl::Error("Could not cast to string.");
}
return std::string(_var.AsString().c_str());
} else if constexpr (std::is_same<std::remove_cvref_t<T>, bool>()) {
if (!_var.IsBool()) {
return rfl::Error("Could not cast to boolean.");
}
return _var.AsBool();
} else if constexpr (std::is_floating_point<std::remove_cvref_t<T>>()) {
if (!_var.IsNumeric()) {
return rfl::Error("Could not cast to double.");
}
return static_cast<T>(_var.AsDouble());
} else if constexpr (std::is_integral<std::remove_cvref_t<T>>()) {
if (!_var.IsNumeric()) {
return rfl::Error("Could not cast to int.");
}
return static_cast<T>(_var.AsInt64());
} else {
static_assert(rfl::always_false_v<T>, "Unsupported type.");
}
}
template <class ArrayReader>
std::optional<Error> read_array(const ArrayReader& _array_reader,
const InputArrayType& _arr) const noexcept {
const auto size = _arr.size();
for (size_t i = 0; i < size; ++i) {
const auto err = _array_reader.read(InputVarType(_arr[i]));
if (err) {
return err;
}
}
return std::nullopt;
}
template <class ObjectReader>
std::optional<Error> read_object(const ObjectReader& _object_reader,
const InputObjectType& _obj) const noexcept {
const auto keys = _obj.Keys();
const auto values = _obj.Values();
const auto num_values = std::min(keys.size(), values.size());
for (size_t i = 0; i < num_values; ++i) {
_object_reader.read(std::string_view(keys[i].AsString().c_str()),
values[i]);
}
return std::nullopt;
}
rfl::Result<InputArrayType> to_array(
const InputVarType& _var) const noexcept {
if (!_var.IsVector()) {
return rfl::Error("Could not cast to Vector.");
}
return _var.AsVector();
}
rfl::Result<InputObjectType> to_object(
const InputVarType& _var) const noexcept {
if (!_var.IsMap()) {
return rfl::Error("Could not cast to Map!");
}
return _var.AsMap();
}
template <class T>
rfl::Result<T> use_custom_constructor(
const InputVarType& _var) const noexcept {
try {
return T::from_flexbuf(_var);
} catch (std::exception& e) {
return rfl::Error(e.what());
}
}
};
} // namespace flexbuf
} // namespace rfl
#endif

View file

@ -0,0 +1,176 @@
#ifndef FLEXBUF_WRITER_HPP_
#define FLEXBUF_WRITER_HPP_
#include <flatbuffers/flexbuffers.h>
#include <exception>
#include <functional>
#include <map>
#include <optional>
#include <sstream>
#include <stdexcept>
#include <string>
#include <string_view>
#include <type_traits>
#include <vector>
#include "../Ref.hpp"
#include "../Result.hpp"
#include "../always_false.hpp"
namespace rfl {
namespace flexbuf {
struct Writer {
struct OutputArray {
size_t start_;
};
struct OutputObject {
size_t start_;
};
struct OutputVar {};
using OutputArrayType = OutputArray;
using OutputObjectType = OutputObject;
using OutputVarType = OutputVar;
Writer(const Ref<flexbuffers::Builder>& _fbb) : fbb_(_fbb) {}
~Writer() = default;
OutputArrayType array_as_root(const size_t _size) const noexcept {
return new_array();
}
OutputObjectType object_as_root(const size_t _size) const noexcept {
return new_object();
}
OutputVarType null_as_root() const noexcept {
fbb_->Null();
return OutputVarType{};
}
template <class T>
OutputVarType value_as_root(const T& _var) const noexcept {
return insert_value(_var);
}
OutputArrayType add_array_to_array(const size_t _size,
OutputArrayType* _parent) const noexcept {
return new_array();
}
OutputArrayType add_array_to_object(
const std::string_view& _name, const size_t _size,
OutputObjectType* _parent) const noexcept {
return new_array(_name);
}
OutputObjectType add_object_to_array(
const size_t _size, OutputArrayType* _parent) const noexcept {
return new_object();
}
OutputObjectType add_object_to_object(
const std::string_view& _name, const size_t _size,
OutputObjectType* _parent) const noexcept {
return new_object(_name);
}
template <class T>
OutputVarType add_value_to_array(const T& _var,
OutputArrayType* _parent) const noexcept {
return insert_value(_var);
}
template <class T>
OutputVarType add_value_to_object(const std::string_view& _name,
const T& _var,
OutputObjectType* _parent) const noexcept {
return insert_value(_name, _var);
}
OutputVarType add_null_to_array(OutputArrayType* _parent) const noexcept {
fbb_->Null();
return OutputVarType{};
}
OutputVarType add_null_to_object(const std::string_view& _name,
OutputObjectType* _parent) const noexcept {
fbb_->Null(_name.data());
return OutputVarType{};
}
void end_array(OutputArrayType* _arr) const noexcept {
fbb_->EndVector(_arr->start_, false, false);
}
void end_object(OutputObjectType* _obj) const noexcept {
fbb_->EndMap(_obj->start_);
}
private:
template <class T>
OutputVarType insert_value(const std::string_view& _name,
const T& _var) const noexcept {
if constexpr (std::is_same<std::remove_cvref_t<T>, std::string>()) {
fbb_->String(_name.data(), _var);
} else if constexpr (std::is_same<std::remove_cvref_t<T>, bool>()) {
fbb_->Bool(_name.data(), _var);
} else if constexpr (std::is_floating_point<std::remove_cvref_t<T>>()) {
fbb_->Double(_name.data(), _var);
} else if constexpr (std::is_integral<std::remove_cvref_t<T>>()) {
fbb_->Int(_name.data(), _var);
} else {
static_assert(always_false_v<T>, "Unsupported type");
}
return OutputVarType{};
}
template <class T>
OutputVarType insert_value(const T& _var) const noexcept {
if constexpr (std::is_same<std::remove_cvref_t<T>, std::string>()) {
fbb_->String(_var);
} else if constexpr (std::is_same<std::remove_cvref_t<T>, bool>()) {
fbb_->Bool(_var);
} else if constexpr (std::is_floating_point<std::remove_cvref_t<T>>()) {
fbb_->Double(_var);
} else if constexpr (std::is_integral<std::remove_cvref_t<T>>()) {
fbb_->Int(_var);
} else {
static_assert(always_false_v<T>, "Unsupported type");
}
return OutputVarType{};
}
OutputArrayType new_array(const std::string_view& _name) const noexcept {
const auto start = fbb_->StartVector(_name.data());
return OutputArrayType{start};
}
OutputArrayType new_array() const noexcept {
const auto start = fbb_->StartVector();
return OutputArrayType{start};
}
OutputObjectType new_object(const std::string_view& _name) const noexcept {
const auto start = fbb_->StartMap(_name.data());
return OutputObjectType{start};
}
OutputObjectType new_object() const noexcept {
const auto start = fbb_->StartMap();
return OutputObjectType{start};
}
private:
Ref<flexbuffers::Builder> fbb_;
};
} // namespace flexbuf
} // namespace rfl
#endif

View file

@ -0,0 +1,22 @@
#ifndef RFL_FLEXBUF_LOAD_HPP_
#define RFL_FLEXBUF_LOAD_HPP_
#include "../Result.hpp"
#include "../io/load_bytes.hpp"
#include "read.hpp"
namespace rfl {
namespace flexbuf {
template <class T, class... Ps>
Result<T> load(const std::string& _fname) {
const auto read_bytes = [](const auto& _bytes) {
return read<T, Ps...>(_bytes);
};
return rfl::io::load_bytes(_fname).and_then(read_bytes);
}
} // namespace flexbuf
} // namespace rfl
#endif

View file

@ -0,0 +1,50 @@
#ifndef FLEXBUF_READ_HPP_
#define FLEXBUF_READ_HPP_
#include <flatbuffers/flexbuffers.h>
#include <istream>
#include <vector>
#include "../Processors.hpp"
#include "../Result.hpp"
#include "Parser.hpp"
namespace rfl {
namespace flexbuf {
using InputVarType = typename Reader::InputVarType;
/// Parses an object from flexbuf var.
template <class T, class... Ps>
auto read(const InputVarType& _obj) {
const auto r = Reader();
return Parser<T, Processors<Ps...>>::read(r, _obj);
}
/// Parses an object from flexbuf using reflection.
template <class T, class... Ps>
auto read(const char* _bytes, const size_t _size) {
const InputVarType root =
flexbuffers::GetRoot(reinterpret_cast<const uint8_t*>(_bytes), _size);
return read<T, Ps...>(root);
}
/// Parses an object from flexbuf using reflection.
template <class T, class... Ps>
auto read(const std::vector<char>& _bytes) {
return read<T, Ps...>(_bytes.data(), _bytes.size());
}
/// Parses an object directly from a stream.
template <class T, class... Ps>
auto read(std::istream& _stream) {
std::istreambuf_iterator<char> begin(_stream), end;
const auto bytes = std::vector<char>(begin, end);
return read<T, Ps...>(bytes.data(), bytes.size());
}
} // namespace flexbuf
} // namespace rfl
#endif

View file

@ -0,0 +1,26 @@
#ifndef RFL_FLEXBUF_SAVE_HPP_
#define RFL_FLEXBUF_SAVE_HPP_
#include <fstream>
#include <iostream>
#include <string>
#include "../Result.hpp"
#include "../io/save_bytes.hpp"
#include "write.hpp"
namespace rfl {
namespace flexbuf {
template <class... Ps>
Result<Nothing> save(const std::string& _fname, const auto& _obj) {
const auto write_func = [](const auto& _obj, auto& _stream) -> auto& {
return write<Ps...>(_obj, _stream);
};
return rfl::io::save_bytes(_fname, _obj, write_func);
}
} // namespace flexbuf
} // namespace rfl
#endif

View file

@ -0,0 +1,50 @@
#ifndef FLEXBUF_WRITE_HPP_
#define FLEXBUF_WRITE_HPP_
#include <flatbuffers/flexbuffers.h>
#include <cstddef>
#include <ostream>
#include <sstream>
#include <vector>
#include "../Processors.hpp"
#include "../Ref.hpp"
#include "../parsing/Parent.hpp"
#include "Parser.hpp"
namespace rfl {
namespace flexbuf {
template <class... Ps>
std::vector<uint8_t> to_buffer(const auto& _obj) {
using T = std::remove_cvref_t<decltype(_obj)>;
using ParentType = parsing::Parent<Writer>;
const auto fbb = Ref<flexbuffers::Builder>::make();
auto w = Writer(fbb);
Parser<T, Processors<Ps...>>::write(w, _obj, typename ParentType::Root{});
fbb->Finish();
return fbb->GetBuffer();
}
/// Writes an object to flexbuf.
template <class... Ps>
std::vector<char> write(const auto& _obj) {
const auto buffer = to_buffer<Ps...>(_obj);
const auto data = reinterpret_cast<const char*>(buffer.data());
return std::vector<char>(data, data + buffer.size());
}
/// Writes an object to an ostream.
template <class... Ps>
std::ostream& write(const auto& _obj, std::ostream& _stream) {
const auto buffer = to_buffer<Ps...>(_obj);
const auto data = reinterpret_cast<const char*>(buffer.data());
_stream.write(data, buffer.size());
return _stream;
}
} // namespace flexbuf
} // namespace rfl
#endif

14
include/rfl/json.hpp Normal file
View file

@ -0,0 +1,14 @@
#ifndef RFL_JSON_HPP_
#define RFL_JSON_HPP_
#include "../rfl.hpp"
#include "json/Parser.hpp"
#include "json/Reader.hpp"
#include "json/Writer.hpp"
#include "json/load.hpp"
#include "json/read.hpp"
#include "json/save.hpp"
#include "json/to_schema.hpp"
#include "json/write.hpp"
#endif

View file

@ -0,0 +1,15 @@
#ifndef RFL_JSON_PARSER_HPP_
#define RFL_JSON_PARSER_HPP_
#include "../parsing/Parser.hpp"
#include "Reader.hpp"
#include "Writer.hpp"
namespace rfl::json {
template <class T, class ProcessorsType>
using Parser = parsing::Parser<Reader, Writer, T, ProcessorsType>;
} // namespace rfl::json
#endif

154
include/rfl/json/Reader.hpp Normal file
View file

@ -0,0 +1,154 @@
#ifndef RFL_JSON_READER_HPP_
#define RFL_JSON_READER_HPP_
#include <yyjson.h>
#include <array>
#include <concepts>
#include <exception>
#include <map>
#include <memory>
#include <sstream>
#include <stdexcept>
#include <string>
#include <string_view>
#include <type_traits>
#include <unordered_map>
#include <vector>
#include "../Result.hpp"
#include "../always_false.hpp"
namespace rfl {
namespace json {
struct Reader {
struct YYJSONInputArray {
YYJSONInputArray(yyjson_val* _val) : val_(_val) {}
yyjson_val* val_;
};
struct YYJSONInputObject {
YYJSONInputObject(yyjson_val* _val) : val_(_val) {}
yyjson_val* val_;
};
struct YYJSONInputVar {
YYJSONInputVar() : val_(nullptr) {}
YYJSONInputVar(yyjson_val* _val) : val_(_val) {}
yyjson_val* val_;
};
using InputArrayType = YYJSONInputArray;
using InputObjectType = YYJSONInputObject;
using InputVarType = YYJSONInputVar;
template <class T>
static constexpr bool has_custom_constructor = (requires(InputVarType var) {
T::from_json_obj(var);
});
rfl::Result<InputVarType> get_field(
const std::string& _name, const InputObjectType _obj) const noexcept {
const auto var = InputVarType(yyjson_obj_get(_obj.val_, _name.c_str()));
if (!var.val_) {
return rfl::Error("Object contains no field named '" + _name + "'.");
}
return var;
}
bool is_empty(const InputVarType _var) const noexcept {
return !_var.val_ || yyjson_is_null(_var.val_);
}
template <class ArrayReader>
std::optional<Error> read_array(const ArrayReader& _array_reader,
const InputArrayType& _arr) const noexcept {
yyjson_val* val;
yyjson_arr_iter iter;
yyjson_arr_iter_init(_arr.val_, &iter);
while ((val = yyjson_arr_iter_next(&iter))) {
const auto err = _array_reader.read(InputVarType(val));
if (err) {
return err;
}
}
return std::nullopt;
}
template <class ObjectReader>
std::optional<Error> read_object(const ObjectReader& _object_reader,
const InputObjectType& _obj) const noexcept {
yyjson_obj_iter iter;
yyjson_obj_iter_init(_obj.val_, &iter);
yyjson_val* key;
while ((key = yyjson_obj_iter_next(&iter))) {
const auto name = std::string_view(yyjson_get_str(key));
_object_reader.read(name, InputVarType(yyjson_obj_iter_get_val(key)));
}
return std::nullopt;
}
template <class T>
rfl::Result<T> to_basic_type(const InputVarType _var) const noexcept {
if constexpr (std::is_same<std::remove_cvref_t<T>, std::string>()) {
const auto r = yyjson_get_str(_var.val_);
if (r == NULL) {
return rfl::Error("Could not cast to string.");
}
return std::string(r);
} else if constexpr (std::is_same<std::remove_cvref_t<T>, bool>()) {
if (!yyjson_is_bool(_var.val_)) {
return rfl::Error("Could not cast to boolean.");
}
return yyjson_get_bool(_var.val_);
} else if constexpr (std::is_floating_point<std::remove_cvref_t<T>>()) {
if (!yyjson_is_num(_var.val_)) {
return rfl::Error("Could not cast to double.");
}
return static_cast<T>(yyjson_get_num(_var.val_));
} else if constexpr (std::is_unsigned<std::remove_cvref_t<T>>()) {
if (!yyjson_is_int(_var.val_)) {
return rfl::Error("Could not cast to int.");
}
return static_cast<T>(yyjson_get_uint(_var.val_));
} else if constexpr (std::is_integral<std::remove_cvref_t<T>>()) {
if (!yyjson_is_int(_var.val_)) {
return rfl::Error("Could not cast to int.");
}
return static_cast<T>(yyjson_get_sint(_var.val_));
} else {
static_assert(rfl::always_false_v<T>, "Unsupported type.");
}
}
rfl::Result<InputArrayType> to_array(const InputVarType _var) const noexcept {
if (!yyjson_is_arr(_var.val_)) {
return rfl::Error("Could not cast to array!");
}
return InputArrayType(_var.val_);
}
rfl::Result<InputObjectType> to_object(
const InputVarType _var) const noexcept {
if (!yyjson_is_obj(_var.val_)) {
return rfl::Error("Could not cast to object!");
}
return InputObjectType(_var.val_);
}
template <class T>
rfl::Result<T> use_custom_constructor(
const InputVarType _var) const noexcept {
try {
return T::from_json_obj(_var);
} catch (std::exception& e) {
return rfl::Error(e.what());
}
}
};
} // namespace json
} // namespace rfl
#endif // JSON_PARSER_HPP_

169
include/rfl/json/Writer.hpp Normal file
View file

@ -0,0 +1,169 @@
#ifndef RFL_JSON_WRITER_HPP_
#define RFL_JSON_WRITER_HPP_
#include <yyjson.h>
#include <exception>
#include <map>
#include <sstream>
#include <stdexcept>
#include <string>
#include <string_view>
#include <type_traits>
#include <vector>
#include "../Result.hpp"
#include "../always_false.hpp"
namespace rfl {
namespace json {
class Writer {
public:
struct YYJSONOutputArray {
YYJSONOutputArray(yyjson_mut_val* _val) : val_(_val) {}
yyjson_mut_val* val_;
};
struct YYJSONOutputObject {
YYJSONOutputObject(yyjson_mut_val* _val) : val_(_val) {}
yyjson_mut_val* val_;
};
struct YYJSONOutputVar {
YYJSONOutputVar(yyjson_mut_val* _val) : val_(_val) {}
YYJSONOutputVar(YYJSONOutputArray _arr) : val_(_arr.val_) {}
YYJSONOutputVar(YYJSONOutputObject _obj) : val_(_obj.val_) {}
yyjson_mut_val* val_;
};
using OutputArrayType = YYJSONOutputArray;
using OutputObjectType = YYJSONOutputObject;
using OutputVarType = YYJSONOutputVar;
Writer(yyjson_mut_doc* _doc) : doc_(_doc) {}
~Writer() = default;
OutputArrayType array_as_root(const size_t _size) const noexcept {
const auto arr = yyjson_mut_arr(doc_);
yyjson_mut_doc_set_root(doc_, arr);
return OutputArrayType(arr);
}
OutputObjectType object_as_root(const size_t _size) const noexcept {
const auto obj = yyjson_mut_obj(doc_);
yyjson_mut_doc_set_root(doc_, obj);
return OutputObjectType(obj);
}
OutputVarType null_as_root() const noexcept {
const auto null = yyjson_mut_null(doc_);
yyjson_mut_doc_set_root(doc_, null);
return OutputVarType(null);
}
template <class T>
OutputVarType value_as_root(const T& _var) const noexcept {
const auto val = from_basic_type(_var);
yyjson_mut_doc_set_root(doc_, val.val_);
return OutputVarType(val);
}
OutputArrayType add_array_to_array(const size_t _size,
OutputArrayType* _parent) const noexcept {
const auto arr = yyjson_mut_arr(doc_);
yyjson_mut_arr_add_val(_parent->val_, arr);
return OutputArrayType(arr);
}
OutputArrayType add_array_to_object(
const std::string_view& _name, const size_t _size,
OutputObjectType* _parent) const noexcept {
const auto arr = yyjson_mut_arr(doc_);
yyjson_mut_obj_add(_parent->val_, yyjson_mut_strcpy(doc_, _name.data()),
arr);
return OutputArrayType(arr);
}
OutputObjectType add_object_to_array(
const size_t _size, OutputArrayType* _parent) const noexcept {
const auto obj = yyjson_mut_obj(doc_);
yyjson_mut_arr_add_val(_parent->val_, obj);
return OutputObjectType(obj);
}
OutputObjectType add_object_to_object(
const std::string_view& _name, const size_t _size,
OutputObjectType* _parent) const noexcept {
const auto obj = yyjson_mut_obj(doc_);
yyjson_mut_obj_add(_parent->val_, yyjson_mut_strcpy(doc_, _name.data()),
obj);
return OutputObjectType(obj);
}
template <class T>
OutputVarType add_value_to_array(const T& _var,
OutputArrayType* _parent) const noexcept {
const auto val = from_basic_type(_var);
yyjson_mut_arr_add_val(_parent->val_, val.val_);
return OutputVarType(val);
}
template <class T>
OutputVarType add_value_to_object(const std::string_view& _name,
const T& _var,
OutputObjectType* _parent) const noexcept {
const auto val = from_basic_type(_var);
yyjson_mut_obj_add(_parent->val_, yyjson_mut_strcpy(doc_, _name.data()),
val.val_);
return OutputVarType(val);
}
OutputVarType add_null_to_array(OutputArrayType* _parent) const noexcept {
const auto null = yyjson_mut_null(doc_);
yyjson_mut_arr_add_val(_parent->val_, null);
return OutputVarType(null);
}
OutputVarType add_null_to_object(const std::string_view& _name,
OutputObjectType* _parent) const noexcept {
const auto null = yyjson_mut_null(doc_);
yyjson_mut_obj_add(_parent->val_, yyjson_mut_strcpy(doc_, _name.data()),
null);
return OutputVarType(null);
}
void end_array(OutputArrayType* _arr) const noexcept {}
void end_object(OutputObjectType* _obj) const noexcept {}
private:
template <class T>
OutputVarType from_basic_type(const T& _var) const noexcept {
if constexpr (std::is_same<std::remove_cvref_t<T>, std::string>()) {
return OutputVarType(yyjson_mut_strcpy(doc_, _var.c_str()));
} else if constexpr (std::is_same<std::remove_cvref_t<T>, bool>()) {
return OutputVarType(yyjson_mut_bool(doc_, _var));
} else if constexpr (std::is_floating_point<std::remove_cvref_t<T>>()) {
return OutputVarType(yyjson_mut_real(doc_, static_cast<double>(_var)));
} else if constexpr (std::is_unsigned<std::remove_cvref_t<T>>()) {
return OutputVarType(yyjson_mut_uint(doc_, static_cast<uint64_t>(_var)));
} else if constexpr (std::is_integral<std::remove_cvref_t<T>>()) {
return OutputVarType(yyjson_mut_int(doc_, static_cast<int64_t>(_var)));
} else {
static_assert(rfl::always_false_v<T>, "Unsupported type.");
}
}
public:
yyjson_mut_doc* doc_;
};
} // namespace json
} // namespace rfl
#endif // JSON_PARSER_HPP_

22
include/rfl/json/load.hpp Normal file
View file

@ -0,0 +1,22 @@
#ifndef RFL_JSON_LOAD_HPP_
#define RFL_JSON_LOAD_HPP_
#include "../Result.hpp"
#include "../io/load_string.hpp"
#include "read.hpp"
namespace rfl {
namespace json {
template <class T, class... Ps>
Result<T> load(const std::string& _fname) {
const auto read_string = [](const auto& _str) {
return read<T, Ps...>(_str);
};
return rfl::io::load_string(_fname).and_then(read_string);
}
} // namespace json
} // namespace rfl
#endif

52
include/rfl/json/read.hpp Normal file
View file

@ -0,0 +1,52 @@
#ifndef RFL_JSON_READ_HPP_
#define RFL_JSON_READ_HPP_
#include <yyjson.h>
#include <istream>
#include <string>
#include "../Processors.hpp"
#include "../internal/wrap_in_rfl_array_t.hpp"
#include "Parser.hpp"
#include "Reader.hpp"
namespace rfl {
namespace json {
using InputObjectType = typename Reader::InputObjectType;
using InputVarType = typename Reader::InputVarType;
/// Parses an object from a JSON var.
template <class T, class... Ps>
auto read(const InputVarType& _obj) {
const auto r = Reader();
return Parser<T, Processors<Ps...>>::read(r, _obj);
}
/// Parses an object from JSON using reflection.
template <class T, class... Ps>
Result<internal::wrap_in_rfl_array_t<T>> read(const std::string& _json_str) {
yyjson_doc* doc = yyjson_read(_json_str.c_str(), _json_str.size(), 0);
if (!doc) {
return Error("Could not parse document");
}
yyjson_val* root = yyjson_doc_get_root(doc);
const auto r = Reader();
auto res = Parser<T, Processors<Ps...>>::read(r, InputVarType(root));
yyjson_doc_free(doc);
return res;
}
/// Parses an object from a stringstream.
template <class T, class... Ps>
auto read(std::istream& _stream) {
const auto json_str = std::string(std::istreambuf_iterator<char>(_stream),
std::istreambuf_iterator<char>());
return read<T, Ps...>(json_str);
}
} // namespace json
} // namespace rfl
#endif

29
include/rfl/json/save.hpp Normal file
View file

@ -0,0 +1,29 @@
#ifndef RFL_JSON_SAVE_HPP_
#define RFL_JSON_SAVE_HPP_
#include <yyjson.h>
#include <fstream>
#include <iostream>
#include <string>
#include "../Result.hpp"
#include "../io/save_string.hpp"
#include "write.hpp"
namespace rfl {
namespace json {
template <class... Ps>
Result<Nothing> save(const std::string& _fname, const auto& _obj,
const yyjson_write_flag _flag = 0) {
const auto write_func = [_flag](const auto& _obj, auto& _stream) -> auto& {
return write<Ps...>(_obj, _stream, _flag);
};
return rfl::io::save_string(_fname, _obj, write_func);
}
} // namespace json
} // namespace rfl
#endif

View file

@ -0,0 +1,25 @@
#ifndef RFL_JSON_SCHEMA_JSONSCHEMA_HPP_
#define RFL_JSON_SCHEMA_JSONSCHEMA_HPP_
#include <map>
#include <string>
#include <variant>
#include "../../Flatten.hpp"
#include "../../Literal.hpp"
#include "../../Rename.hpp"
#include "Type.hpp"
namespace rfl::json::schema {
template <class T>
struct JSONSchema {
Rename<"$schema", Literal<"https://json-schema.org/draft/2020-12/schema">>
schema;
Flatten<T> root;
std::map<std::string, Type> definitions;
};
} // namespace rfl::json::schema
#endif

View file

@ -0,0 +1,147 @@
#ifndef RFL_JSON_SCHEMA_TYPE_HPP_
#define RFL_JSON_SCHEMA_TYPE_HPP_
#include <map>
#include <memory>
#include <optional>
#include <string>
#include <variant>
#include "../../Literal.hpp"
#include "../../Rename.hpp"
namespace rfl::json::schema {
/// The JSON representation of internal::schema::Type.
struct Type {
struct Boolean {
std::optional<std::string> description;
Literal<"boolean"> type;
};
struct Integer {
Literal<"integer"> type;
std::optional<std::string> description;
};
struct Number {
Literal<"number"> type;
std::optional<std::string> description;
};
struct String {
Literal<"string"> type;
std::optional<std::string> description;
};
using NumericType = std::variant<Integer, Number>;
struct AllOf {
std::optional<std::string> description;
std::vector<Type> allOf;
};
struct AnyOf {
std::optional<std::string> description;
std::vector<Type> anyOf;
};
struct ExclusiveMaximum {
std::optional<std::string> description;
std::variant<double, int> exclusiveMaximum;
std::string type;
};
struct ExclusiveMinimum {
std::optional<std::string> description;
std::variant<double, int> exclusiveMinimum;
std::string type;
};
struct FixedSizeTypedArray {
Literal<"array"> type;
std::optional<std::string> description;
rfl::Ref<Type> items;
size_t minContains;
size_t maxContains;
};
struct Maximum {
std::optional<std::string> description;
std::variant<double, int> maximum;
std::string type;
};
struct Minimum {
std::optional<std::string> description;
std::variant<double, int> minimum;
std::string type;
};
struct Null {
Literal<"null"> type;
std::optional<std::string> description;
};
struct Object {
Literal<"object"> type;
std::optional<std::string> description;
std::map<std::string, Type> properties;
std::vector<std::string> required;
};
struct OneOf {
std::optional<std::string> description;
std::vector<Type> oneOf;
};
struct Reference {
Rename<"$ref", std::optional<std::string>> ref;
std::optional<std::string> description;
};
struct Regex {
Literal<"string"> type;
std::optional<std::string> description;
std::string pattern;
};
struct StringEnum {
Literal<"string"> type;
std::optional<std::string> description;
rfl::Rename<"enum", std::vector<std::string>> values;
};
struct StringMap {
Literal<"object"> type;
std::optional<std::string> description;
rfl::Ref<Type> additionalProperties;
};
struct Tuple {
Literal<"array"> type;
std::optional<std::string> description;
std::vector<Type> prefixItems;
bool items = false;
};
struct TypedArray {
Literal<"array"> type;
std::optional<std::string> description;
rfl::Ref<Type> items;
};
using ReflectionType =
std::variant<AllOf, AnyOf, Boolean, ExclusiveMaximum, ExclusiveMinimum,
FixedSizeTypedArray, Integer, Maximum, Minimum, Number, Null,
Object, OneOf, Reference, Regex, String, StringEnum,
StringMap, Tuple, TypedArray>;
const auto& reflection() const { return value; }
ReflectionType value;
};
} // namespace rfl::json::schema
#endif

View file

@ -0,0 +1,262 @@
#ifndef RFL_JSON_TOSCHEMA_HPP_
#define RFL_JSON_TOSCHEMA_HPP_
#include <yyjson.h>
#include <map>
#include <string>
#include <type_traits>
#include <variant>
#include "../Literal.hpp"
#include "../Processors.hpp"
#include "../parsing/schema/Type.hpp"
#include "../parsing/schema/ValidationType.hpp"
#include "../parsing/schema/make.hpp"
#include "Reader.hpp"
#include "Writer.hpp"
#include "schema/JSONSchema.hpp"
#include "schema/Type.hpp"
#include "write.hpp"
namespace rfl::json {
inline schema::Type type_to_json_schema_type(
const parsing::schema::Type& _type);
inline bool is_optional(const parsing::schema::Type& _t) {
const auto handle = [](const auto& _v) -> bool {
using T = std::remove_cvref_t<decltype(_v)>;
return std::is_same<T, parsing::schema::Type::Optional>();
};
return std::visit(handle, _t.variant_);
}
inline std::string numeric_type_to_string(const parsing::schema::Type& _type) {
const auto handle_variant = [](const auto& _t) -> std::string {
using T = std::remove_cvref_t<decltype(_t)>;
using Type = parsing::schema::Type;
if constexpr (std::is_same<T, Type::Int32>() ||
std::is_same<T, Type::Int64>() ||
std::is_same<T, Type::UInt32>() ||
std::is_same<T, Type::UInt64>() ||
std::is_same<T, Type::Integer>()) {
return schema::Type::Integer{}.type.str();
} else {
return schema::Type::Number{}.type.str();
}
};
return std::visit(handle_variant, _type.variant_);
}
inline schema::Type handle_validation_type(
const parsing::schema::Type& _type,
const parsing::schema::ValidationType& _validation_type) {
const auto handle_variant = [&](const auto& _v) -> schema::Type {
using T = std::remove_cvref_t<decltype(_v)>;
using ValidationType = parsing::schema::ValidationType;
if constexpr (std::is_same<T, ValidationType::AllOf>()) {
auto all_of = std::vector<schema::Type>();
for (const auto& t : _v.types_) {
all_of.emplace_back(handle_validation_type(_type, t));
}
return schema::Type{.value = schema::Type::AllOf{.allOf = all_of}};
} else if constexpr (std::is_same<T, ValidationType::AnyOf>()) {
auto any_of = std::vector<schema::Type>();
for (const auto& t : _v.types_) {
any_of.emplace_back(handle_validation_type(_type, t));
}
return schema::Type{.value = schema::Type::AnyOf{.anyOf = any_of}};
} else if constexpr (std::is_same<T, ValidationType::OneOf>()) {
auto one_of = std::vector<schema::Type>();
for (const auto& t : _v.types_) {
one_of.emplace_back(handle_validation_type(_type, t));
}
return schema::Type{.value = schema::Type::OneOf{.oneOf = one_of}};
} else if constexpr (std::is_same<T, ValidationType::Regex>()) {
return schema::Type{.value = schema::Type::Regex{.pattern = _v.pattern_}};
} else if constexpr (std::is_same<T, ValidationType::Size>()) {
return type_to_json_schema_type(_type);
} else if constexpr (std::is_same<T, ValidationType::ExclusiveMaximum>()) {
return schema::Type{.value = schema::Type::ExclusiveMaximum{
.exclusiveMaximum = _v.value_,
.type = numeric_type_to_string(_type)}};
} else if constexpr (std::is_same<T, ValidationType::ExclusiveMinimum>()) {
return schema::Type{.value = schema::Type::ExclusiveMinimum{
.exclusiveMinimum = _v.value_,
.type = numeric_type_to_string(_type)}};
} else if constexpr (std::is_same<T, ValidationType::Maximum>()) {
return schema::Type{
.value = schema::Type::Maximum{
.maximum = _v.value_, .type = numeric_type_to_string(_type)}};
} else if constexpr (std::is_same<T, ValidationType::Minimum>()) {
return schema::Type{
.value = schema::Type::Minimum{
.minimum = _v.value_, .type = numeric_type_to_string(_type)}};
} else if constexpr (std::is_same<T, ValidationType::EqualTo>()) {
const auto maximum = schema::Type{
.value = schema::Type::Maximum{
.maximum = _v.value_, .type = numeric_type_to_string(_type)}};
const auto minimum = schema::Type{
.value = schema::Type::Minimum{
.minimum = _v.value_, .type = numeric_type_to_string(_type)}};
return schema::Type{.value =
schema::Type::AllOf{.allOf = {maximum, minimum}}};
} else if constexpr (std::is_same<T, ValidationType::NotEqualTo>()) {
const auto excl_maximum =
schema::Type{.value = schema::Type::ExclusiveMaximum{
.exclusiveMaximum = _v.value_,
.type = numeric_type_to_string(_type)}};
const auto excl_minimum =
schema::Type{.value = schema::Type::ExclusiveMinimum{
.exclusiveMinimum = _v.value_,
.type = numeric_type_to_string(_type)}};
return schema::Type{
.value = schema::Type::AnyOf{.anyOf = {excl_maximum, excl_minimum}}};
} else {
static_assert(rfl::always_false_v<T>, "Not all cases were covered.");
}
};
return std::visit(handle_variant, _validation_type.variant_);
}
inline schema::Type type_to_json_schema_type(
const parsing::schema::Type& _type) {
const auto handle_variant = [](const auto& _t) -> schema::Type {
using T = std::remove_cvref_t<decltype(_t)>;
using Type = parsing::schema::Type;
if constexpr (std::is_same<T, Type::Boolean>()) {
return schema::Type{.value = schema::Type::Boolean{}};
} else if constexpr (std::is_same<T, Type::Int32>() ||
std::is_same<T, Type::Int64>() ||
std::is_same<T, Type::UInt32>() ||
std::is_same<T, Type::UInt64>() ||
std::is_same<T, Type::Integer>()) {
return schema::Type{.value = schema::Type::Integer{}};
} else if constexpr (std::is_same<T, Type::Float>() ||
std::is_same<T, Type::Double>()) {
return schema::Type{.value = schema::Type::Number{}};
} else if constexpr (std::is_same<T, Type::String>()) {
return schema::Type{.value = schema::Type::String{}};
} else if constexpr (std::is_same<T, Type::AnyOf>()) {
auto any_of = std::vector<schema::Type>();
for (const auto& t : _t.types_) {
any_of.emplace_back(type_to_json_schema_type(t));
}
return schema::Type{.value = schema::Type::AnyOf{.anyOf = any_of}};
} else if constexpr (std::is_same<T, Type::Description>()) {
auto res = type_to_json_schema_type(*_t.type_);
const auto update_prediction = [&](auto _v) -> schema::Type {
_v.description = _t.description_;
return schema::Type{_v};
};
return std::visit(update_prediction, res.value);
} else if constexpr (std::is_same<T, Type::FixedSizeTypedArray>()) {
return schema::Type{.value = schema::Type::FixedSizeTypedArray{
.items = Ref<schema::Type>::make(
type_to_json_schema_type(*_t.type_)),
.minContains = _t.size_,
.maxContains = _t.size_}};
} else if constexpr (std::is_same<T, Type::Literal>()) {
return schema::Type{.value =
schema::Type::StringEnum{.values = _t.values_}};
} else if constexpr (std::is_same<T, Type::Object>()) {
auto properties = std::map<std::string, schema::Type>();
auto required = std::vector<std::string>();
for (const auto& [k, v] : _t.types_) {
properties[k] = type_to_json_schema_type(v);
if (!is_optional(v)) {
required.push_back(k);
}
}
return schema::Type{.value = schema::Type::Object{
.properties = properties, .required = required}};
} else if constexpr (std::is_same<T, Type::Optional>()) {
return schema::Type{.value = schema::Type::AnyOf{
.anyOf = {type_to_json_schema_type(*_t.type_),
schema::Type{schema::Type::Null{}}}}};
} else if constexpr (std::is_same<T, Type::Reference>()) {
return schema::Type{
.value = schema::Type::Reference{.ref = "#/definitions/" + _t.name_}};
} else if constexpr (std::is_same<T, Type::StringMap>()) {
return schema::Type{.value = schema::Type::StringMap{
.additionalProperties = Ref<schema::Type>::make(
type_to_json_schema_type(*_t.value_type_))}};
} else if constexpr (std::is_same<T, Type::Tuple>()) {
auto items = std::vector<schema::Type>();
for (const auto& t : _t.types_) {
items.emplace_back(type_to_json_schema_type(t));
}
return schema::Type{.value = schema::Type::Tuple{.prefixItems = items}};
} else if constexpr (std::is_same<T, Type::TypedArray>()) {
return schema::Type{.value = schema::Type::TypedArray{
.items = Ref<schema::Type>::make(
type_to_json_schema_type(*_t.type_))}};
} else if constexpr (std::is_same<T, Type::Validated>()) {
return handle_validation_type(*_t.type_, _t.validation_);
} else {
static_assert(rfl::always_false_v<T>, "Not all cases were covered.");
}
};
return std::visit(handle_variant, _type.variant_);
}
template <class T>
struct TypeHelper {};
template <class... Ts>
struct TypeHelper<std::variant<Ts...>> {
using JSONSchemaType = std::variant<schema::JSONSchema<Ts>...>;
};
/// Returns the JSON schema for a class.
template <class T, class... Ps>
std::string to_schema(const yyjson_write_flag _flag = 0) {
const auto internal_schema =
parsing::schema::make<Reader, Writer, T, Processors<Ps...>>();
auto definitions = std::map<std::string, schema::Type>();
for (const auto& [k, v] : internal_schema.definitions_) {
definitions[k] = type_to_json_schema_type(v);
}
using JSONSchemaType =
typename TypeHelper<schema::Type::ReflectionType>::JSONSchemaType;
const auto to_schema = [&](auto&& _root) -> JSONSchemaType {
using U = std::decay_t<decltype(_root)>;
return schema::JSONSchema<U>{.root = std::move(_root),
.definitions = definitions};
};
auto root = type_to_json_schema_type(internal_schema.root_);
const auto json_schema = std::visit(to_schema, std::move(root.value));
return write(json_schema, _flag);
}
} // namespace rfl::json
#endif

View file

@ -0,0 +1,52 @@
#ifndef RFL_JSON_WRITE_HPP_
#define RFL_JSON_WRITE_HPP_
#include <yyjson.h>
#include <ostream>
#include <sstream>
#include <string>
#include "../Processors.hpp"
#include "../parsing/Parent.hpp"
#include "Parser.hpp"
namespace rfl {
namespace json {
/// Convenient alias for the YYJSON pretty flag
inline constexpr yyjson_write_flag pretty = YYJSON_WRITE_PRETTY;
/// Returns a JSON string.
template <class... Ps>
std::string write(const auto& _obj, const yyjson_write_flag _flag = 0) {
using T = std::remove_cvref_t<decltype(_obj)>;
using ParentType = parsing::Parent<Writer>;
auto w = Writer(yyjson_mut_doc_new(NULL));
Parser<T, Processors<Ps...>>::write(w, _obj, typename ParentType::Root{});
const char* json_c_str = yyjson_mut_write(w.doc_, _flag, NULL);
const auto json_str = std::string(json_c_str);
free((void*)json_c_str);
yyjson_mut_doc_free(w.doc_);
return json_str;
}
/// Writes a JSON into an ostream.
template <class... Ps>
std::ostream& write(const auto& _obj, std::ostream& _stream,
const yyjson_write_flag _flag = 0) {
using T = std::remove_cvref_t<decltype(_obj)>;
using ParentType = parsing::Parent<Writer>;
auto w = Writer(yyjson_mut_doc_new(NULL));
Parser<T, Processors<Ps...>>::write(w, _obj, typename ParentType::Root{});
const char* json_c_str = yyjson_mut_write(w.doc_, _flag, NULL);
_stream << json_c_str;
free((void*)json_c_str);
yyjson_mut_doc_free(w.doc_);
return _stream;
}
} // namespace json
} // namespace rfl
#endif // JSON_PARSER_HPP_

13
include/rfl/msgpack.hpp Normal file
View file

@ -0,0 +1,13 @@
#ifndef RFL_MSGPACK_HPP_
#define RFL_MSGPACK_HPP_
#include "../rfl.hpp"
#include "msgpack/Parser.hpp"
#include "msgpack/Reader.hpp"
#include "msgpack/Writer.hpp"
#include "msgpack/load.hpp"
#include "msgpack/read.hpp"
#include "msgpack/save.hpp"
#include "msgpack/write.hpp"
#endif

View file

@ -0,0 +1,46 @@
#ifndef RFL_MSGPACK_PARSER_HPP_
#define RFL_MSGPACK_PARSER_HPP_
#include "../parsing/Parser.hpp"
#include "Reader.hpp"
#include "Writer.hpp"
namespace rfl {
namespace parsing {
/// msgpack-c requires us to explicitly set the number of fields in advance.
/// Because of that, we require all of the fields and then set them to nullptr,
/// if necessary.
template <class ProcessorsType, class... FieldTypes>
requires AreReaderAndWriter<msgpack::Reader, msgpack::Writer,
NamedTuple<FieldTypes...>>
struct Parser<msgpack::Reader, msgpack::Writer, NamedTuple<FieldTypes...>,
ProcessorsType>
: public NamedTupleParser<msgpack::Reader, msgpack::Writer,
/*_ignore_empty_containers=*/false,
/*_all_required=*/true, ProcessorsType,
FieldTypes...> {
};
template <class ProcessorsType, class... Ts>
requires AreReaderAndWriter<msgpack::Reader, msgpack::Writer, std::tuple<Ts...>>
struct Parser<msgpack::Reader, msgpack::Writer, std::tuple<Ts...>,
ProcessorsType>
: public TupleParser<msgpack::Reader, msgpack::Writer,
/*_ignore_empty_containers=*/false,
/*_all_required=*/true, ProcessorsType, Ts...> {
};
} // namespace parsing
} // namespace rfl
namespace rfl {
namespace msgpack {
template <class T, class ProcessorsType>
using Parser = parsing::Parser<Reader, Writer, T, ProcessorsType>;
}
} // namespace rfl
#endif

View file

@ -0,0 +1,148 @@
#ifndef RFL_MSGPACK_READER_HPP_
#define RFL_MSGPACK_READER_HPP_
#include <msgpack.h>
#include <array>
#include <concepts>
#include <exception>
#include <map>
#include <memory>
#include <source_location>
#include <sstream>
#include <stdexcept>
#include <string>
#include <string_view>
#include <type_traits>
#include <unordered_map>
#include <vector>
#include "../Box.hpp"
#include "../Result.hpp"
#include "../always_false.hpp"
namespace rfl {
namespace msgpack {
struct Reader {
using InputArrayType = msgpack_object_array;
using InputObjectType = msgpack_object_map;
using InputVarType = msgpack_object;
template <class T>
static constexpr bool has_custom_constructor = (requires(InputVarType var) {
T::from_msgpack_obj(var);
});
rfl::Result<InputVarType> get_field(
const std::string& _name, const InputObjectType& _obj) const noexcept {
for (uint32_t i = 0; i < _obj.size; ++i) {
const auto& key = _obj.ptr[i].key;
if (key.type != MSGPACK_OBJECT_STR) {
return Error("Key in element " + std::to_string(i) +
" was not a string.");
}
const auto current_name =
std::string_view(key.via.str.ptr, key.via.str.size);
if (_name == current_name) {
return _obj.ptr[i].val;
}
}
return Error("No field named '" + _name + "' was found.");
}
bool is_empty(const InputVarType& _var) const noexcept {
return _var.type == MSGPACK_OBJECT_NIL;
}
template <class T>
rfl::Result<T> to_basic_type(const InputVarType& _var) const noexcept {
const auto type = _var.type;
if constexpr (std::is_same<std::remove_cvref_t<T>, std::string>()) {
if (type != MSGPACK_OBJECT_STR) {
return Error("Could not cast to string.");
}
const auto str = _var.via.str;
return std::string(str.ptr, str.size);
} else if constexpr (std::is_same<std::remove_cvref_t<T>, bool>()) {
if (type != MSGPACK_OBJECT_BOOLEAN) {
return Error("Could not cast to boolean.");
}
return _var.via.boolean;
} else if constexpr (std::is_floating_point<std::remove_cvref_t<T>>() ||
std::is_integral<std::remove_cvref_t<T>>()) {
if (type == MSGPACK_OBJECT_FLOAT32 || type == MSGPACK_OBJECT_FLOAT64 ||
type == MSGPACK_OBJECT_FLOAT) {
return static_cast<T>(_var.via.f64);
} else if (type == MSGPACK_OBJECT_POSITIVE_INTEGER) {
return static_cast<T>(_var.via.u64);
} else if (type == MSGPACK_OBJECT_NEGATIVE_INTEGER) {
return static_cast<T>(_var.via.i64);
}
return rfl::Error(
"Could not cast to numeric value. The type must be integral, float "
"or double.");
} else {
static_assert(rfl::always_false_v<T>, "Unsupported type.");
}
}
rfl::Result<InputArrayType> to_array(
const InputVarType& _var) const noexcept {
if (_var.type != MSGPACK_OBJECT_ARRAY) {
return Error("Could not cast to an array.");
}
return _var.via.array;
}
rfl::Result<InputObjectType> to_object(
const InputVarType& _var) const noexcept {
if (_var.type != MSGPACK_OBJECT_MAP) {
return Error("Could not cast to a map.");
}
return _var.via.map;
}
template <class ArrayReader>
std::optional<Error> read_array(const ArrayReader& _array_reader,
const InputArrayType& _arr) const noexcept {
for (uint32_t i = 0; i < _arr.size; ++i) {
const auto err = _array_reader.read(_arr.ptr[i]);
if (err) {
return err;
}
}
return std::nullopt;
}
template <class ObjectReader>
std::optional<Error> read_object(const ObjectReader& _object_reader,
const InputObjectType& _obj) const noexcept {
for (uint32_t i = 0; i < _obj.size; ++i) {
const auto& key = _obj.ptr[i].key;
const auto& val = _obj.ptr[i].val;
if (key.type != MSGPACK_OBJECT_STR) {
return Error("Key in element " + std::to_string(i) +
" was not a string.");
}
const auto name = std::string_view(key.via.str.ptr, key.via.str.size);
_object_reader.read(name, val);
}
return std::nullopt;
}
template <class T>
rfl::Result<T> use_custom_constructor(
const InputVarType& _var) const noexcept {
try {
return T::from_msgpack_obj(_var);
} catch (std::exception& e) {
return rfl::Error(e.what());
}
}
};
} // namespace msgpack
} // namespace rfl
#endif // JSON_PARSER_HPP_

View file

@ -0,0 +1,154 @@
#ifndef RFL_MSGPACK_WRITER_HPP_
#define RFL_MSGPACK_WRITER_HPP_
#include <msgpack.h>
#include <exception>
#include <map>
#include <sstream>
#include <stdexcept>
#include <string>
#include <string_view>
#include <type_traits>
#include <variant>
#include <vector>
#include "../Box.hpp"
#include "../Ref.hpp"
#include "../Result.hpp"
#include "../always_false.hpp"
namespace rfl::msgpack {
class Writer {
public:
struct MsgpackOutputArray {};
struct MsgpackOutputObject {};
struct MsgpackOutputVar {};
using OutputArrayType = MsgpackOutputArray;
using OutputObjectType = MsgpackOutputObject;
using OutputVarType = MsgpackOutputVar;
Writer(msgpack_packer* _pk) : pk_(_pk) {}
~Writer() = default;
OutputArrayType array_as_root(const size_t _size) const noexcept {
return new_array(_size);
}
OutputObjectType object_as_root(const size_t _size) const noexcept {
return new_object(_size);
}
OutputVarType null_as_root() const noexcept {
msgpack_pack_nil(pk_);
return OutputVarType{};
}
template <class T>
OutputVarType value_as_root(const T& _var) const noexcept {
return new_value(_var);
}
OutputArrayType add_array_to_array(const size_t _size,
OutputArrayType* _parent) const noexcept {
return new_array(_size);
}
OutputArrayType add_array_to_object(
const std::string_view& _name, const size_t _size,
OutputObjectType* _parent) const noexcept {
msgpack_pack_str(pk_, _name.size());
msgpack_pack_str_body(pk_, _name.data(), _name.size());
return new_array(_size);
}
OutputObjectType add_object_to_array(
const size_t _size, OutputArrayType* _parent) const noexcept {
return new_object(_size);
}
OutputObjectType add_object_to_object(
const std::string_view& _name, const size_t _size,
OutputObjectType* _parent) const noexcept {
msgpack_pack_str(pk_, _name.size());
msgpack_pack_str_body(pk_, _name.data(), _name.size());
return new_object(_size);
}
template <class T>
OutputVarType add_value_to_array(const T& _var,
OutputArrayType* _parent) const noexcept {
return new_value(_var);
}
template <class T>
OutputVarType add_value_to_object(const std::string_view& _name,
const T& _var,
OutputObjectType* _parent) const noexcept {
msgpack_pack_str(pk_, _name.size());
msgpack_pack_str_body(pk_, _name.data(), _name.size());
return new_value(_var);
}
OutputVarType add_null_to_array(OutputArrayType* _parent) const noexcept {
msgpack_pack_nil(pk_);
return OutputVarType{};
}
OutputVarType add_null_to_object(const std::string_view& _name,
OutputObjectType* _parent) const noexcept {
msgpack_pack_str(pk_, _name.size());
msgpack_pack_str_body(pk_, _name.data(), _name.size());
msgpack_pack_nil(pk_);
return OutputVarType{};
}
void end_array(OutputArrayType* _arr) const noexcept {}
void end_object(OutputObjectType* _obj) const noexcept {}
private:
OutputArrayType new_array(const size_t _size) const noexcept {
msgpack_pack_array(pk_, _size);
return OutputArrayType{};
}
OutputObjectType new_object(const size_t _size) const noexcept {
msgpack_pack_map(pk_, _size);
return OutputObjectType{};
}
template <class T>
OutputVarType new_value(const T& _var) const noexcept {
if constexpr (std::is_same<std::remove_cvref_t<T>, std::string>()) {
msgpack_pack_str(pk_, _var.size());
msgpack_pack_str_body(pk_, _var.c_str(), _var.size());
} else if constexpr (std::is_same<std::remove_cvref_t<T>, bool>()) {
if (_var) {
msgpack_pack_true(pk_);
} else {
msgpack_pack_false(pk_);
}
} else if constexpr (std::is_floating_point<std::remove_cvref_t<T>>()) {
msgpack_pack_double(pk_, static_cast<double>(_var));
} else if constexpr (std::is_integral<std::remove_cvref_t<T>>()) {
msgpack_pack_int64(pk_, static_cast<std::int64_t>(_var));
} else {
static_assert(rfl::always_false_v<T>, "Unsupported type.");
}
return OutputVarType{};
}
private:
/// The underlying packer.
msgpack_packer* pk_;
};
} // namespace rfl::msgpack
#endif // MSGPACK_PARSER_HPP_

View file

@ -0,0 +1,22 @@
#ifndef RFL_MSGPACK_LOAD_HPP_
#define RFL_MSGPACK_LOAD_HPP_
#include "../Result.hpp"
#include "../io/load_bytes.hpp"
#include "read.hpp"
namespace rfl {
namespace msgpack {
template <class T, class... Ps>
Result<T> load(const std::string& _fname) {
const auto read_bytes = [](const auto& _bytes) {
return read<T, Ps...>(_bytes);
};
return rfl::io::load_bytes(_fname).and_then(read_bytes);
}
} // namespace msgpack
} // namespace rfl
#endif

View file

@ -0,0 +1,57 @@
#ifndef RFL_MSGPACK_READ_HPP_
#define RFL_MSGPACK_READ_HPP_
#include <msgpack.h>
#include <istream>
#include <string>
#include "../Processors.hpp"
#include "../internal/wrap_in_rfl_array_t.hpp"
#include "Parser.hpp"
#include "Reader.hpp"
namespace rfl {
namespace msgpack {
using InputObjectType = typename Reader::InputObjectType;
using InputVarType = typename Reader::InputVarType;
/// Parses an object from a MSGPACK var.
template <class T, class... Ps>
auto read(const InputVarType& _obj) {
const auto r = Reader();
return Parser<T, Processors<Ps...>>::read(r, _obj);
}
/// Parses an object from MSGPACK using reflection.
template <class T, class... Ps>
Result<internal::wrap_in_rfl_array_t<T>> read(const char* _bytes,
const size_t _size) {
msgpack_zone mempool;
msgpack_zone_init(&mempool, 2048);
msgpack_object deserialized;
msgpack_unpack(_bytes, _size, NULL, &mempool, &deserialized);
auto r = read<T, Ps...>(deserialized);
msgpack_zone_destroy(&mempool);
return r;
}
/// Parses an object from MSGPACK using reflection.
template <class T, class... Ps>
auto read(const std::vector<char>& _bytes) {
return read<T, Ps...>(_bytes.data(), _bytes.size());
}
/// Parses an object from a stream.
template <class T, class... Ps>
auto read(std::istream& _stream) {
std::istreambuf_iterator<char> begin(_stream), end;
auto bytes = std::vector<char>(begin, end);
return read<T, Ps...>(bytes.data(), bytes.size());
}
} // namespace msgpack
} // namespace rfl
#endif

View file

@ -0,0 +1,26 @@
#ifndef RFL_MSGPACK_SAVE_HPP_
#define RFL_MSGPACK_SAVE_HPP_
#include <fstream>
#include <iostream>
#include <string>
#include "../Result.hpp"
#include "../io/save_bytes.hpp"
#include "write.hpp"
namespace rfl {
namespace msgpack {
template <class... Ps>
Result<Nothing> save(const std::string& _fname, const auto& _obj) {
const auto write_func = [](const auto& _obj, auto& _stream) -> auto& {
return write<Ps...>(_obj, _stream);
};
return rfl::io::save_bytes(_fname, _obj, write_func);
}
} // namespace msgpack
} // namespace rfl
#endif

View file

@ -0,0 +1,44 @@
#ifndef RFL_MSGPACK_WRITE_HPP_
#define RFL_MSGPACK_WRITE_HPP_
#include <msgpack.h>
#include <cstdint>
#include <ostream>
#include <sstream>
#include <string>
#include <utility>
#include "../Processors.hpp"
#include "../parsing/Parent.hpp"
#include "Parser.hpp"
namespace rfl::msgpack {
/// Returns msgpack bytes.
template <class... Ps>
std::vector<char> write(const auto& _obj) noexcept {
using T = std::remove_cvref_t<decltype(_obj)>;
using ParentType = parsing::Parent<Writer>;
msgpack_sbuffer sbuf;
msgpack_sbuffer_init(&sbuf);
msgpack_packer pk;
msgpack_packer_init(&pk, &sbuf, msgpack_sbuffer_write);
auto w = Writer(&pk);
Parser<T, Processors<Ps...>>::write(w, _obj, typename ParentType::Root{});
auto bytes = std::vector<char>(sbuf.data, sbuf.data + sbuf.size);
msgpack_sbuffer_destroy(&sbuf);
return bytes;
}
/// Writes a MSGPACK into an ostream.
template <class... Ps>
std::ostream& write(const auto& _obj, std::ostream& _stream) noexcept {
auto buffer = write<Ps...>(_obj);
_stream.write(buffer.data(), buffer.size());
return _stream;
}
} // namespace rfl::msgpack
#endif

View file

@ -6,6 +6,7 @@
#include <tuple>
#include <type_traits>
#include <unordered_map>
#include <utility>
#include "../NamedTuple.hpp"
#include "../Result.hpp"
@ -59,7 +60,7 @@ struct NamedTupleParser {
using ViewType = std::remove_cvref_t<decltype(view)>;
const auto err =
Parser<R, W, ViewType, ProcessorsType>::read_view(_r, _var, &view);
if (err) {
if (err) [[unlikely]] {
return *err;
}
return *ptr;
@ -69,11 +70,11 @@ struct NamedTupleParser {
static std::optional<Error> read_view(
const R& _r, const InputVarType& _var,
NamedTuple<FieldTypes...>* _view) noexcept {
const auto obj = _r.to_object(_var);
if (obj) {
return read_object(_r, *obj, _view);
auto obj = _r.to_object(_var);
if (!obj) [[unlikely]] {
return obj.error();
}
return obj.error();
return read_object(_r, *obj, _view);
}
/// For writing, we do not need to make the distinction between
@ -147,38 +148,43 @@ struct NamedTupleParser {
}
/// Generates error messages for when fields are missing.
template <size_t _i = 0>
static void handle_missing_fields(const std::array<bool, size_>& _found,
const NamedTupleType& _view,
std::array<bool, size_>* _set,
std::vector<Error>* _errors) noexcept {
if constexpr (_i < sizeof...(FieldTypes)) {
using FieldType =
std::tuple_element_t<_i, typename NamedTupleType::Fields>;
using ValueType = std::remove_reference_t<
std::remove_pointer_t<typename FieldType::Type>>;
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 = std::tuple_element_t<_i, typename NamedTupleType::Fields>;
using ValueType = std::remove_reference_t<
std::remove_pointer_t<typename FieldType::Type>>;
if (!std::get<_i>(_found)) {
if constexpr (_all_required ||
is_required<ValueType, _ignore_empty_containers>()) {
constexpr auto current_name =
std::tuple_element_t<_i, typename NamedTupleType::Fields>::name();
_errors->push_back("Field named '" + std::string(current_name) +
"' not found.");
if (!std::get<_i>(_found)) {
if constexpr (_all_required ||
is_required<ValueType, _ignore_empty_containers>()) {
constexpr auto current_name =
std::tuple_element_t<_i, typename NamedTupleType::Fields>::name();
_errors->emplace_back(Error(
"Field named '" + std::string(current_name) + "' not found."));
} else {
if constexpr (!std::is_const_v<ValueType>) {
::new (rfl::get<_i>(_view)) ValueType();
} else {
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;
using NonConstT = std::remove_const_t<ValueType>;
::new (const_cast<NonConstT*>(rfl::get<_i>(_view))) NonConstT();
}
std::get<_i>(*_set) = true;
}
handle_missing_fields<_i + 1>(_found, _view, _set, _errors);
}
}
/// 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 std::optional<Error> read_object(const R& _r,
const InputObjectType& _obj,
NamedTupleType* _view) noexcept {
@ -193,7 +199,8 @@ struct NamedTupleParser {
if (err) {
return *err;
}
handle_missing_fields(found, *_view, &set, &errors);
handle_missing_fields(found, *_view, &set, &errors,
std::make_integer_sequence<int, size_>());
if (errors.size() != 0) {
object_reader.call_destructors_where_necessary();
return to_single_error_message(errors);

View file

@ -17,11 +17,11 @@
#include "../internal/is_validator.hpp"
#include "../internal/processed_t.hpp"
#include "../internal/to_ptr_named_tuple.hpp"
#include "../to_view.hpp"
#include "../type_name_t.hpp"
#include "AreReaderAndWriter.hpp"
#include "Parent.hpp"
#include "Parser_base.hpp"
#include "StructReader.hpp"
#include "is_tagged_union_wrapper.hpp"
#include "schema/Type.hpp"
@ -55,19 +55,13 @@ struct Parser {
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>) {
return StructReader<R, W, T, ProcessorsType>::read(_r, _var);
return read_struct(_r, _var);
} else if constexpr (std::is_enum_v<T>) {
using StringConverter = internal::enums::StringConverter<T>;
return _r.template to_basic_type<std::string>(_var).and_then(
StringConverter::string_to_enum);
} else if constexpr (internal::is_basic_type_v<T>) {
return _r.template to_basic_type<std::remove_cvref_t<T>>(_var);
} else {
static_assert(
always_false_v<T>,
"Unsupported type. Please refer to the sections on custom "
"classes and custom parsers for information on how add "
"support for your own classes.");
return _r.template to_basic_type<std::remove_cvref_t<T>>(_var);
}
}
}
@ -93,13 +87,8 @@ struct Parser {
using StringConverter = internal::enums::StringConverter<T>;
const auto str = StringConverter::enum_to_string(_var);
ParentType::add_value(_w, str, _parent);
} else if constexpr (internal::is_basic_type_v<T>) {
ParentType::add_value(_w, _var, _parent);
} else {
static_assert(always_false_v<T>,
"Unsupported type. Please refer to the sections on custom "
"classes and custom parsers for information on how add "
"support for your own classes.");
ParentType::add_value(_w, _var, _parent);
}
}
@ -222,6 +211,23 @@ struct Parser {
}
}
/// 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 = reinterpret_cast<T*>(buf);
auto view = ProcessorsType::template process<T>(to_view(*ptr));
using ViewType = std::remove_cvref_t<decltype(view)>;
const auto err =
Parser<R, W, ViewType, ProcessorsType>::read_view(_r, _var, &view);
if (err) [[unlikely]] {
return *err;
}
return std::move(*ptr);
}
static std::string replace_non_alphanumeric(std::string _str) {
for (auto& ch : _str) {
ch = std::isalnum(ch) ? ch : '_';

View file

@ -1,41 +0,0 @@
#ifndef RFL_PARSING_STRUCTREADER_HPP_
#define RFL_PARSING_STRUCTREADER_HPP_
#include <array>
#include <string_view>
#include <type_traits>
#include <vector>
#include "../Result.hpp"
#include "../to_view.hpp"
#include "AreReaderAndWriter.hpp"
namespace rfl::parsing {
template <class R, class W, class StructType, class ProcessorsType>
requires AreReaderAndWriter<R, W, StructType>
struct StructReader {
public:
using InputVarType = typename R::InputVarType;
/// 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<StructType> read(const R& _r, const InputVarType& _var) {
alignas(StructType) unsigned char buf[sizeof(StructType)];
auto ptr = reinterpret_cast<StructType*>(buf);
auto view = ProcessorsType::template process<StructType>(to_view(*ptr));
using ViewType = std::remove_cvref_t<decltype(view)>;
const auto err =
Parser<R, W, ViewType, ProcessorsType>::read_view(_r, _var, &view);
if (err) {
return *err;
}
return std::move(*ptr);
}
};
} // namespace rfl::parsing
#endif

View file

@ -4,6 +4,7 @@
#include <array>
#include <string_view>
#include <type_traits>
#include <utility>
#include <vector>
#include "../Result.hpp"
@ -24,63 +25,68 @@ class ViewReader {
~ViewReader() = default;
template <size_t _i = 0>
/// 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 {
if constexpr (_i < size_) {
using FieldType = std::tuple_element_t<_i, typename ViewType::Fields>;
using OriginalType = std::remove_cvref_t<typename FieldType::Type>;
using CurrentType =
std::remove_cvref_t<std::remove_pointer_t<OriginalType>>;
constexpr auto current_name = FieldType::name();
if (!std::get<_i>(*found_) && _name == current_name) {
auto res = Parser<R, W, CurrentType, ProcessorsType>::read(*r_, _var);
if (res) {
if constexpr (std::is_pointer_v<OriginalType>) {
move_to(rfl::get<_i>(*view_), &(*res));
} else {
rfl::get<_i>(*view_) = *res;
}
std::get<_i>(*set_) = true;
} else {
errors_->push_back(Error("Failed to parse field '" +
std::string(current_name) +
"': " + res.error()->what()));
}
std::get<_i>(*found_) = true;
return;
}
read<_i + 1>(_name, _var);
}
assign_to_matching_field(*r_, _name, _var, view_, errors_, found_, set_,
std::make_integer_sequence<int, size_>());
}
/// Because of the way we have allocated the fields, we need to manually
/// trigger the destructors.
template <size_t _i = 0>
void call_destructors_where_necessary() const {
if constexpr (_i < size_) {
using FieldType = std::tuple_element_t<_i, typename ViewType::Fields>;
using OriginalType = std::remove_cvref_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);
}
}
call_destructors_where_necessary<_i + 1>();
[&]<int... is>(std::integer_sequence<int, is...>) {
(call_destructor_on_one_if_necessary<is>(), ...);
}
(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, auto* _set,
bool* _already_assigned) {
using FieldType = std::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) {
_errors->emplace_back(Error("Failed to parse field '" +
std::string(name) +
"': " + std::move(res.error()->what())));
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_matching_field(const R& _r,
const std::string_view& _current_name,
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, _var, _view, _errors,
_found, _set, &already_assigned),
...);
}
template <class T>
void call_destructor_on_array(const size_t _size, T* _ptr) const {
static 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));
@ -90,14 +96,34 @@ class ViewReader {
}
}
template <int _i>
void call_destructor_on_one_if_necessary() const {
using FieldType = std::tuple_element_t<_i, typename ViewType::Fields>;
using OriginalType = std::remove_cvref_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 Target, class Source>
void move_to(Target* _t, Source* _s) const {
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 (!internal::is_array_v<Source> &&
} else if constexpr (!rfl::internal::is_array_v<Source> &&
!std::is_array_v<Target>) {
::new (_t) Target(std::move(*_s));
} else if constexpr (internal::is_array_v<Source>) {
} 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) {

13
include/rfl/xml.hpp Normal file
View file

@ -0,0 +1,13 @@
#ifndef RFL_XML_HPP_
#define RFL_XML_HPP_
#include "../rfl.hpp"
#include "xml/Parser.hpp"
#include "xml/Reader.hpp"
#include "xml/Writer.hpp"
#include "xml/load.hpp"
#include "xml/read.hpp"
#include "xml/save.hpp"
#include "xml/write.hpp"
#endif

View file

@ -0,0 +1,40 @@
#ifndef RFL_XML_PARSER_HPP_
#define RFL_XML_PARSER_HPP_
#include <type_traits>
#include "../internal/is_attribute.hpp"
#include "../parsing/NamedTupleParser.hpp"
#include "../parsing/Parser.hpp"
#include "Reader.hpp"
#include "Writer.hpp"
namespace rfl {
namespace parsing {
/// XML is very special. It doesn't have proper support for arrays, which means
/// that we just need to ignore empty containers. Therefore, we need to a
/// template specialization for the NamedTuple parser to accommodate for it.
template <class ProcessorsType, class... FieldTypes>
requires AreReaderAndWriter<xml::Reader, xml::Writer, NamedTuple<FieldTypes...>>
struct Parser<xml::Reader, xml::Writer, NamedTuple<FieldTypes...>,
ProcessorsType>
: public NamedTupleParser<xml::Reader, xml::Writer,
/*_ignore_empty_containers=*/true,
/*_all_required=*/ProcessorsType::all_required_,
ProcessorsType, FieldTypes...> {
};
} // namespace parsing
} // namespace rfl
namespace rfl {
namespace xml {
template <class T, class ProcessorsType>
using Parser = parsing::Parser<Reader, Writer, T, ProcessorsType>;
}
} // namespace rfl
#endif

174
include/rfl/xml/Reader.hpp Normal file
View file

@ -0,0 +1,174 @@
#ifndef RFL_XML_READER_HPP_
#define RFL_XML_READER_HPP_
#include <array>
#include <exception>
#include <map>
#include <memory>
#include <optional>
#include <pugixml.hpp>
#include <sstream>
#include <stdexcept>
#include <string>
#include <string_view>
#include <type_traits>
#include <unordered_map>
#include <variant>
#include <vector>
#include "../Result.hpp"
#include "../always_false.hpp"
#include "../parsing/is_view_reader.hpp"
namespace rfl {
namespace xml {
struct Reader {
struct XMLInputArray {
XMLInputArray(pugi::xml_node _node) : node_(_node) {}
pugi::xml_node node_;
};
struct XMLInputObject {
XMLInputObject(pugi::xml_node _node) : node_(_node) {}
pugi::xml_node node_;
};
struct XMLInputVar {
XMLInputVar() : node_or_attribute_(pugi::xml_node()) {}
XMLInputVar(pugi::xml_attribute _attr) : node_or_attribute_(_attr) {}
XMLInputVar(pugi::xml_node _node) : node_or_attribute_(_node) {}
std::variant<pugi::xml_node, pugi::xml_attribute> node_or_attribute_;
};
using InputArrayType = XMLInputArray;
using InputObjectType = XMLInputObject;
using InputVarType = XMLInputVar;
// TODO
template <class T>
static constexpr bool has_custom_constructor = false;
/// XML-only helper function. This is needed because XML distinguishes between
/// nodes and attributes.
static rfl::Result<pugi::xml_node> cast_as_node(
const std::variant<pugi::xml_node, pugi::xml_attribute>&
_node_or_attribute) {
const auto cast = [](const auto& _n) -> Result<pugi::xml_node> {
using Type = std::remove_cvref_t<decltype(_n)>;
if constexpr (std::is_same<Type, pugi::xml_node>()) {
return _n;
} else {
return Error("Field '" + std::string(_n.name()) + "' is an attribute.");
}
};
return std::visit(cast, _node_or_attribute);
}
rfl::Result<InputVarType> get_field(
const std::string& _name, const InputObjectType _obj) const noexcept {
const auto node = _obj.node_.child(_name.c_str());
if (!node) {
return rfl::Error("Object contains no field named '" + _name + "'.");
}
return InputVarType(node);
}
bool is_empty(const InputVarType _var) const noexcept {
const auto wrap = [](const auto& _node) { return !_node; };
return std::visit(cast_as_node, _var.node_or_attribute_)
.transform(wrap)
.value_or(false);
}
template <class T>
rfl::Result<T> to_basic_type(const InputVarType _var) const noexcept {
const auto get_value = [](const auto& _n) -> std::string {
using Type = std::remove_cvref_t<decltype(_n)>;
if constexpr (std::is_same<Type, pugi::xml_node>()) {
return std::string(_n.child_value());
} else {
return std::string(_n.value());
}
};
if constexpr (std::is_same<std::remove_cvref_t<T>, std::string>()) {
return std::visit(get_value, _var.node_or_attribute_);
} else if constexpr (std::is_same<std::remove_cvref_t<T>, bool>()) {
return std::visit(get_value, _var.node_or_attribute_) == "true";
} else if constexpr (std::is_floating_point<std::remove_cvref_t<T>>()) {
const auto str = std::visit(get_value, _var.node_or_attribute_);
try {
return static_cast<T>(std::stod(str));
} catch (std::exception& e) {
return Error("Could not cast '" + std::string(str) +
"' to floating point value.");
}
} else if constexpr (std::is_integral<std::remove_cvref_t<T>>()) {
const auto str = std::visit(get_value, _var.node_or_attribute_);
try {
return static_cast<T>(std::stoi(str));
} catch (std::exception& e) {
return Error("Could not cast '" + std::string(str) + "' to integer.");
}
} else {
static_assert(rfl::always_false_v<T>, "Unsupported type.");
}
}
rfl::Result<InputArrayType> to_array(const InputVarType _var) const noexcept {
const auto wrap = [](const auto& _node) { return InputArrayType(_node); };
return std::visit(cast_as_node, _var.node_or_attribute_).transform(wrap);
}
template <class ArrayReader>
std::optional<Error> read_array(const ArrayReader& _array_reader,
const InputArrayType& _arr) const noexcept {
const auto name = _arr.node_.name();
for (auto node = _arr.node_; node; node = node.next_sibling(name)) {
const auto err = _array_reader.read(InputVarType(node));
if (err) {
return err;
}
}
return std::nullopt;
}
template <class ObjectReader>
std::optional<Error> read_object(const ObjectReader& _object_reader,
const InputObjectType& _obj) const noexcept {
for (auto child = _obj.node_.first_child(); child;
child = child.next_sibling()) {
_object_reader.read(std::string_view(child.name()), InputVarType(child));
}
for (auto attr = _obj.node_.first_attribute(); attr;
attr = attr.next_attribute()) {
_object_reader.read(std::string_view(attr.name()), InputVarType(attr));
}
if constexpr (parsing::is_view_reader_v<ObjectReader>) {
_object_reader.read(std::string_view("xml_content"),
InputVarType(_obj.node_));
}
return std::nullopt;
}
rfl::Result<InputObjectType> to_object(
const InputVarType _var) const noexcept {
const auto wrap = [](const auto& _node) { return InputObjectType(_node); };
return std::visit(cast_as_node, _var.node_or_attribute_).transform(wrap);
}
template <class T>
rfl::Result<T> use_custom_constructor(
const InputVarType _var) const noexcept {
return rfl::Error("TODO");
}
};
} // namespace xml
} // namespace rfl
#endif

181
include/rfl/xml/Writer.hpp Normal file
View file

@ -0,0 +1,181 @@
#ifndef RFL_XML_WRITER_HPP_
#define RFL_XML_WRITER_HPP_
#include <exception>
#include <map>
#include <pugixml.hpp>
#include <sstream>
#include <stdexcept>
#include <string>
#include <string_view>
#include <type_traits>
#include <vector>
#include "../Ref.hpp"
#include "../Result.hpp"
#include "../always_false.hpp"
namespace rfl {
namespace xml {
struct Writer {
static constexpr const char* XML_CONTENT = "xml_content";
struct XMLOutputArray {
XMLOutputArray(const std::string_view& _name,
const Ref<pugi::xml_node>& _node)
: name_(_name), node_(_node) {}
std::string_view name_;
Ref<pugi::xml_node> node_;
};
struct XMLOutputObject {
XMLOutputObject(const Ref<pugi::xml_node>& _node) : node_(_node) {}
Ref<pugi::xml_node> node_;
};
struct XMLOutputVar {
XMLOutputVar(const Ref<pugi::xml_node>& _node) : node_(_node) {}
Ref<pugi::xml_node> node_;
};
using OutputArrayType = XMLOutputArray;
using OutputObjectType = XMLOutputObject;
using OutputVarType = XMLOutputVar;
Writer(const Ref<pugi::xml_node>& _root, const std::string& _root_name)
: root_(_root), root_name_(_root_name) {}
~Writer() = default;
OutputArrayType array_as_root(const size_t _size) const noexcept {
auto node_child =
Ref<pugi::xml_node>::make(root_->append_child(root_name_.c_str()));
return OutputArrayType(root_name_, node_child);
}
OutputObjectType object_as_root(const size_t _size) const noexcept {
auto node_child =
Ref<pugi::xml_node>::make(root_->append_child(root_name_.c_str()));
return OutputObjectType(node_child);
}
OutputVarType null_as_root() const noexcept {
auto node_child =
Ref<pugi::xml_node>::make(root_->append_child(root_name_.c_str()));
return OutputVarType(node_child);
}
template <class T>
OutputVarType value_as_root(const T& _var) const noexcept {
const auto str = to_string(_var);
auto node_child =
Ref<pugi::xml_node>::make(root_->append_child(root_name_.c_str()));
node_child->append_child(pugi::node_pcdata).set_value(str.c_str());
return OutputVarType(node_child);
}
OutputArrayType add_array_to_array(const size_t _size,
OutputArrayType* _parent) const noexcept {
return *_parent;
}
OutputArrayType add_array_to_object(
const std::string_view& _name, const size_t _size,
OutputObjectType* _parent) const noexcept {
return OutputArrayType(_name, _parent->node_);
}
OutputObjectType add_object_to_array(
const size_t _size, OutputArrayType* _parent) const noexcept {
auto node_child = Ref<pugi::xml_node>::make(
_parent->node_->append_child(_parent->name_.data()));
return OutputObjectType(node_child);
}
OutputObjectType add_object_to_object(
const std::string_view& _name, const size_t _size,
OutputObjectType* _parent) const noexcept {
auto node_child =
Ref<pugi::xml_node>::make(_parent->node_->append_child(_name.data()));
return OutputObjectType(node_child);
}
template <class T>
OutputVarType add_value_to_array(const T& _var,
OutputArrayType* _parent) const noexcept {
const auto str = to_string(_var);
auto node_child = Ref<pugi::xml_node>::make(
_parent->node_->append_child(_parent->name_.data()));
node_child->append_child(pugi::node_pcdata).set_value(str.c_str());
return OutputVarType(node_child);
}
template <class T>
OutputVarType add_value_to_object(
const std::string_view& _name, const T& _var, OutputObjectType* _parent,
const bool _is_attribute = false) const noexcept {
const auto str = to_string(_var);
if (_is_attribute) {
_parent->node_->append_attribute(_name.data()) = str.c_str();
return OutputVarType(_parent->node_);
} else if (_name == XML_CONTENT) {
_parent->node_->append_child(pugi::node_pcdata).set_value(str.c_str());
return OutputVarType(_parent->node_);
} else {
auto node_child =
Ref<pugi::xml_node>::make(_parent->node_->append_child(_name.data()));
node_child->append_child(pugi::node_pcdata).set_value(str.c_str());
return OutputVarType(node_child);
}
}
OutputVarType add_null_to_array(OutputArrayType* _parent) const noexcept {
auto node_child = Ref<pugi::xml_node>::make(
_parent->node_->append_child(_parent->name_.data()));
return OutputVarType(node_child);
}
OutputVarType add_null_to_object(
const std::string_view& _name, OutputObjectType* _parent,
const bool _is_attribute = false) const noexcept {
if (_is_attribute) {
return OutputVarType(_parent->node_);
} else if (_name == XML_CONTENT) {
return OutputVarType(_parent->node_);
} else {
auto node_child =
Ref<pugi::xml_node>::make(_parent->node_->append_child(_name.data()));
return OutputVarType(node_child);
}
}
void end_array(OutputArrayType* _arr) const noexcept {}
void end_object(OutputObjectType* _obj) const noexcept {}
private:
template <class T>
std::string to_string(const T& _val) const noexcept {
if constexpr (std::is_same<std::remove_cvref_t<T>, std::string>()) {
return _val;
} else if constexpr (std::is_same<std::remove_cvref_t<T>, bool>()) {
return _val ? "true" : "false";
} else if constexpr (std::is_floating_point<std::remove_cvref_t<T>>() ||
std::is_integral<std::remove_cvref_t<T>>()) {
return std::to_string(_val);
} else {
static_assert(always_false_v<T>, "Unsupported type");
}
}
public:
Ref<pugi::xml_node> root_;
std::string root_name_;
};
} // namespace xml
} // namespace rfl
#endif // XML_PARSER_HPP_

22
include/rfl/xml/load.hpp Normal file
View file

@ -0,0 +1,22 @@
#ifndef RFL_XML_LOAD_HPP_
#define RFL_XML_LOAD_HPP_
#include "../Result.hpp"
#include "../io/load_string.hpp"
#include "read.hpp"
namespace rfl {
namespace xml {
template <class T, class... Ps>
Result<T> load(const std::string& _fname) {
const auto read_string = [](const auto& _str) {
return read<T, Ps...>(_str);
};
return rfl::io::load_string(_fname).and_then(read_string);
}
} // namespace xml
} // namespace rfl
#endif

50
include/rfl/xml/read.hpp Normal file
View file

@ -0,0 +1,50 @@
#ifndef RFL_XML_READ_HPP_
#define RFL_XML_READ_HPP_
#include <istream>
#include <pugixml.hpp>
#include <string>
#include "../Processors.hpp"
#include "../internal/get_type_name.hpp"
#include "../internal/remove_namespaces.hpp"
#include "Parser.hpp"
#include "Reader.hpp"
namespace rfl {
namespace xml {
using InputVarType = typename Reader::InputVarType;
/// Parses an object from a XML var.
template <class T, class... Ps>
auto read(const InputVarType& _var) {
const auto r = Reader();
return Parser<T, Processors<Ps...>>::read(r, _var);
}
/// Parses an object from XML using reflection.
template <class T, class... Ps>
Result<T> read(const std::string& _xml_str) {
pugi::xml_document doc;
const auto result = doc.load_string(_xml_str.c_str());
if (!result) {
return Error("XML string could not be parsed: " +
std::string(result.description()));
}
const auto var = InputVarType(doc.first_child());
return read<T, Ps...>(var);
}
/// Parses an object from a stringstream.
template <class T, class... Ps>
auto read(std::istream& _stream) {
const auto xml_str = std::string(std::istreambuf_iterator<char>(_stream),
std::istreambuf_iterator<char>());
return read<T, Ps...>(xml_str);
}
} // namespace xml
} // namespace rfl
#endif

28
include/rfl/xml/save.hpp Normal file
View file

@ -0,0 +1,28 @@
#ifndef RFL_XML_SAVE_HPP_
#define RFL_XML_SAVE_HPP_
#include <fstream>
#include <iostream>
#include <string>
#include "../Result.hpp"
#include "../internal/StringLiteral.hpp"
#include "../io/save_string.hpp"
#include "write.hpp"
namespace rfl {
namespace xml {
template <internal::StringLiteral _root = internal::StringLiteral(""),
class... Ps>
Result<Nothing> save(const std::string& _fname, const auto& _obj) {
const auto write_func = [](const auto& _obj, auto& _stream) -> auto& {
return write<_root, Ps...>(_obj, _stream);
};
return rfl::io::save_string(_fname, _obj, write_func);
}
} // namespace xml
} // namespace rfl
#endif

74
include/rfl/xml/write.hpp Normal file
View file

@ -0,0 +1,74 @@
#ifndef RFL_XML_WRITE_HPP_
#define RFL_XML_WRITE_HPP_
#include <ostream>
#include <pugixml.hpp>
#include <sstream>
#include <string>
#include <type_traits>
#include "../Processors.hpp"
#include "../internal/StringLiteral.hpp"
#include "../internal/get_type_name.hpp"
#include "../internal/remove_namespaces.hpp"
#include "../parsing/Parent.hpp"
#include "Parser.hpp"
namespace rfl {
namespace xml {
template <internal::StringLiteral _root, class T>
consteval auto get_root_name() {
if constexpr (_root != internal::StringLiteral("")) {
return _root;
} else {
return internal::remove_namespaces<
internal::get_type_name<std::remove_cvref_t<T>>()>();
}
}
/// Writes a XML into an ostream.
template <internal::StringLiteral _root = internal::StringLiteral(""),
class... Ps>
std::ostream& write(const auto& _obj, std::ostream& _stream,
const std::string& _indent = " ") {
using T = std::remove_cvref_t<decltype(_obj)>;
using ParentType = parsing::Parent<Writer>;
constexpr auto root_name = get_root_name<_root, T>();
static_assert(root_name.string_view().find("<") == std::string_view::npos &&
root_name.string_view().find(">") == std::string_view::npos,
"The name of an XML root node cannot contain '<' or '>'. "
"Please assign an "
"explicit root name to rfl::xml::write(...) like this: "
"rfl::xml::write<\"root_name\">(...).");
const auto doc = rfl::Ref<pugi::xml_document>::make();
auto declaration_node = doc->append_child(pugi::node_declaration);
declaration_node.append_attribute("version") = "1.0";
declaration_node.append_attribute("encoding") = "UTF-8";
auto w = Writer(doc, root_name.str());
Parser<T, Processors<Ps...>>::write(w, _obj, typename ParentType::Root{});
doc->save(_stream, _indent.c_str());
return _stream;
}
/// Returns a XML string.
template <internal::StringLiteral _root = internal::StringLiteral(""),
class... Ps>
std::string write(const auto& _obj, const std::string& _indent = " ") {
std::stringstream stream;
write<_root, Ps...>(_obj, stream);
return stream.str();
}
} // namespace xml
} // namespace rfl
#endif // XML_PARSER_HPP_

13
include/rfl/yaml.hpp Normal file
View file

@ -0,0 +1,13 @@
#ifndef RFL_YAML_HPP_
#define RFL_YAML_HPP_
#include "../rfl.hpp"
#include "yaml/Parser.hpp"
#include "yaml/Reader.hpp"
#include "yaml/Writer.hpp"
#include "yaml/load.hpp"
#include "yaml/read.hpp"
#include "yaml/save.hpp"
#include "yaml/write.hpp"
#endif

View file

@ -0,0 +1,17 @@
#ifndef RFL_YAML_PARSER_HPP_
#define RFL_YAML_PARSER_HPP_
#include "../parsing/Parser.hpp"
#include "Reader.hpp"
#include "Writer.hpp"
namespace rfl {
namespace yaml {
template <class T, class ProcessorsType>
using Parser = parsing::Parser<Reader, Writer, T, ProcessorsType>;
}
} // namespace rfl
#endif

137
include/rfl/yaml/Reader.hpp Normal file
View file

@ -0,0 +1,137 @@
#ifndef RFL_YAML_READER_HPP_
#define RFL_YAML_READER_HPP_
#include <yaml-cpp/yaml.h>
#include <array>
#include <exception>
#include <map>
#include <memory>
#include <sstream>
#include <stdexcept>
#include <string>
#include <string_view>
#include <type_traits>
#include <unordered_map>
#include <vector>
#include "../Result.hpp"
#include "../always_false.hpp"
namespace rfl {
namespace yaml {
struct Reader {
struct YAMLInputArray {
YAMLInputArray(const YAML::Node& _node) : node_(_node) {}
YAML::Node node_;
};
struct YAMLInputObject {
YAMLInputObject(const YAML::Node& _node) : node_(_node) {}
YAML::Node node_;
};
struct YAMLInputVar {
YAMLInputVar(const YAML::Node& _node) : node_(_node) {}
YAML::Node node_;
};
using InputArrayType = YAMLInputArray;
using InputObjectType = YAMLInputObject;
using InputVarType = YAMLInputVar;
template <class T, class = void>
struct has_from_json_obj : std::false_type {};
template <class T>
static constexpr bool has_custom_constructor = (requires(InputVarType var) {
T::from_yaml_obj(var);
});
rfl::Result<InputVarType> get_field(
const std::string& _name, const InputObjectType& _obj) const noexcept {
auto var = InputVarType(_obj.node_[_name]);
if (!var.node_) {
return rfl::Error("Object contains no field named '" + _name + "'.");
}
return var;
}
bool is_empty(const InputVarType& _var) const noexcept {
return !_var.node_ && true;
}
template <class T>
rfl::Result<T> to_basic_type(const InputVarType& _var) const noexcept {
try {
if constexpr (std::is_same<std::remove_cvref_t<T>, std::string>() ||
std::is_same<std::remove_cvref_t<T>, bool>() ||
std::is_floating_point<std::remove_cvref_t<T>>() ||
std::is_integral<std::remove_cvref_t<T>>()) {
return _var.node_.as<std::remove_cvref_t<T>>();
} else {
static_assert(rfl::always_false_v<T>, "Unsupported type.");
}
} catch (std::exception& e) {
return rfl::Error(e.what());
}
}
rfl::Result<InputArrayType> to_array(
const InputVarType& _var) const noexcept {
if (!_var.node_.IsSequence()) {
return rfl::Error("Could not cast to sequence!");
}
return InputArrayType(_var.node_);
}
template <class ArrayReader>
std::optional<Error> read_array(const ArrayReader& _array_reader,
const InputArrayType& _arr) const noexcept {
for (size_t i = 0; i < _arr.node_.size(); ++i) {
const auto err = _array_reader.read(_arr.node_[i]);
if (err) {
return err;
}
}
return std::nullopt;
}
template <class ObjectReader>
std::optional<Error> read_object(const ObjectReader& _object_reader,
const InputObjectType& _obj) const noexcept {
for (const auto& p : _obj.node_) {
try {
const auto k = p.first.as<std::string>();
_object_reader.read(std::string_view(k), InputVarType(p.second));
} catch (std::exception& e) {
continue;
}
}
return std::nullopt;
}
rfl::Result<InputObjectType> to_object(
const InputVarType& _var) const noexcept {
if (!_var.node_.IsMap()) {
return rfl::Error("Could not cast to map!");
}
return InputObjectType(_var.node_);
}
template <class T>
rfl::Result<T> use_custom_constructor(
const InputVarType _var) const noexcept {
try {
return T::from_yaml_obj(_var);
} catch (std::exception& e) {
return rfl::Error(e.what());
}
}
};
} // namespace yaml
} // namespace rfl
#endif

148
include/rfl/yaml/Writer.hpp Normal file
View file

@ -0,0 +1,148 @@
#ifndef RFL_YAML_WRITER_HPP_
#define RFL_YAML_WRITER_HPP_
#include <yaml-cpp/yaml.h>
#include <exception>
#include <map>
#include <sstream>
#include <stdexcept>
#include <string>
#include <string_view>
#include <type_traits>
#include <vector>
#include "../Ref.hpp"
#include "../Result.hpp"
#include "../always_false.hpp"
namespace rfl {
namespace yaml {
class Writer {
public:
struct YAMLArray {};
struct YAMLObject {};
struct YAMLVar {};
using OutputArrayType = YAMLArray;
using OutputObjectType = YAMLObject;
using OutputVarType = YAMLVar;
Writer(const Ref<YAML::Emitter>& _out) : out_(_out) {}
~Writer() = default;
OutputArrayType array_as_root(const size_t _size) const noexcept {
return new_array();
}
OutputObjectType object_as_root(const size_t _size) const noexcept {
return new_object();
}
OutputVarType null_as_root() const noexcept {
return insert_value(YAML::Null);
}
template <class T>
OutputVarType value_as_root(const T& _var) const noexcept {
return insert_value(_var);
}
OutputArrayType add_array_to_array(const size_t _size,
OutputArrayType* _parent) const noexcept {
return new_array();
}
OutputArrayType add_array_to_object(
const std::string_view& _name, const size_t _size,
OutputObjectType* _parent) const noexcept {
return new_array(_name);
}
OutputObjectType add_object_to_array(
const size_t _size, OutputArrayType* _parent) const noexcept {
return new_object();
}
OutputObjectType add_object_to_object(
const std::string_view& _name, const size_t _size,
OutputObjectType* _parent) const noexcept {
return new_object(_name);
}
template <class T>
OutputVarType add_value_to_array(const T& _var,
OutputArrayType* _parent) const noexcept {
return insert_value(_var);
}
template <class T>
OutputVarType add_value_to_object(const std::string_view& _name,
const T& _var,
OutputObjectType* _parent) const noexcept {
return insert_value(_name, _var);
}
OutputVarType add_null_to_array(OutputArrayType* _parent) const noexcept {
return insert_value(YAML::Null);
}
OutputVarType add_null_to_object(const std::string_view& _name,
OutputObjectType* _parent) const noexcept {
return insert_value(_name, YAML::Null);
}
void end_array(OutputArrayType* _arr) const noexcept {
(*out_) << YAML::EndSeq;
}
void end_object(OutputObjectType* _obj) const noexcept {
(*out_) << YAML::EndMap;
}
private:
template <class T>
OutputVarType insert_value(const std::string_view& _name,
const T& _var) const noexcept {
(*out_) << YAML::Key << _name.data() << YAML::Value << _var;
return OutputVarType{};
}
template <class T>
OutputVarType insert_value(const T& _var) const noexcept {
(*out_) << _var;
return OutputVarType{};
}
OutputArrayType new_array(const std::string_view& _name) const noexcept {
(*out_) << YAML::Key << _name.data() << YAML::Value << YAML::BeginSeq;
return OutputArrayType{};
}
OutputArrayType new_array() const noexcept {
(*out_) << YAML::BeginSeq;
return OutputArrayType{};
}
OutputObjectType new_object(const std::string_view& _name) const noexcept {
(*out_) << YAML::Key << _name.data() << YAML::Value << YAML::BeginMap;
return OutputObjectType{};
}
OutputObjectType new_object() const noexcept {
(*out_) << YAML::BeginMap;
return OutputObjectType{};
}
public:
const Ref<YAML::Emitter> out_;
};
} // namespace yaml
} // namespace rfl
#endif // JSON_PARSER_HPP_

22
include/rfl/yaml/load.hpp Normal file
View file

@ -0,0 +1,22 @@
#ifndef RFL_YAML_LOAD_HPP_
#define RFL_YAML_LOAD_HPP_
#include "../Result.hpp"
#include "../io/load_string.hpp"
#include "read.hpp"
namespace rfl {
namespace yaml {
template <class T, class... Ps>
Result<T> load(const std::string& _fname) {
const auto read_string = [](const auto& _str) {
return read<T, Ps...>(_str);
};
return rfl::io::load_string(_fname).and_then(read_string);
}
} // namespace yaml
} // namespace rfl
#endif

47
include/rfl/yaml/read.hpp Normal file
View file

@ -0,0 +1,47 @@
#ifndef RFL_YAML_READ_HPP_
#define RFL_YAML_READ_HPP_
#include <yaml-cpp/yaml.h>
#include <istream>
#include <string>
#include "../Processors.hpp"
#include "../internal/wrap_in_rfl_array_t.hpp"
#include "Parser.hpp"
#include "Reader.hpp"
namespace rfl {
namespace yaml {
using InputVarType = typename Reader::InputVarType;
/// Parses an object from a YAML var.
template <class T, class... Ps>
auto read(const InputVarType& _var) {
const auto r = Reader();
return Parser<T, Processors<Ps...>>::read(r, _var);
}
/// Parses an object from YAML using reflection.
template <class T, class... Ps>
Result<internal::wrap_in_rfl_array_t<T>> read(const std::string& _yaml_str) {
try {
const auto var = InputVarType(YAML::Load(_yaml_str));
return read<T, Ps...>(var);
} catch (std::exception& e) {
return Error(e.what());
}
}
/// Parses an object from a stringstream.
template <class T, class... Ps>
auto read(std::istream& _stream) {
const auto yaml_str = std::string(std::istreambuf_iterator<char>(_stream),
std::istreambuf_iterator<char>());
return read<T, Ps...>(yaml_str);
}
} // namespace yaml
} // namespace rfl
#endif

27
include/rfl/yaml/save.hpp Normal file
View file

@ -0,0 +1,27 @@
#ifndef RFL_YAML_SAVE_HPP_
#define RFL_YAML_SAVE_HPP_
#include <fstream>
#include <iostream>
#include <string>
#include "../Processors.hpp"
#include "../Result.hpp"
#include "../io/save_string.hpp"
#include "write.hpp"
namespace rfl {
namespace yaml {
template <class... Ps>
Result<Nothing> save(const std::string& _fname, const auto& _obj) {
const auto write_func = [](const auto& _obj, auto& _stream) -> auto& {
return write<Ps...>(_obj, _stream);
};
return rfl::io::save_string(_fname, _obj, write_func);
}
} // namespace yaml
} // namespace rfl
#endif

View file

@ -0,0 +1,44 @@
#ifndef RFL_YAML_WRITE_HPP_
#define RFL_YAML_WRITE_HPP_
#include <yaml-cpp/yaml.h>
#include <ostream>
#include <sstream>
#include <string>
#include <type_traits>
#include "../Processors.hpp"
#include "../parsing/Parent.hpp"
#include "Parser.hpp"
namespace rfl {
namespace yaml {
/// Writes a YAML into an ostream.
template <class... Ps>
std::ostream& write(const auto& _obj, std::ostream& _stream) {
using T = std::remove_cvref_t<decltype(_obj)>;
using ParentType = parsing::Parent<Writer>;
const auto out = Ref<YAML::Emitter>::make();
auto w = Writer(out);
Parser<T, Processors<Ps...>>::write(w, _obj, typename ParentType::Root{});
_stream << out->c_str();
return _stream;
}
/// Returns a YAML string.
template <class... Ps>
std::string write(const auto& _obj) {
using T = std::remove_cvref_t<decltype(_obj)>;
using ParentType = parsing::Parent<Writer>;
const auto out = Ref<YAML::Emitter>::make();
auto w = Writer(out);
Parser<T, Processors<Ps...>>::write(w, _obj, typename ParentType::Root{});
return out->c_str();
}
} // namespace yaml
} // namespace rfl
#endif