Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -616,6 +616,7 @@ reflect-cpp supports the following containers from the C++ standard library:
- `std::array`
- `std::atomic`
- `std::atomic_flag`
- `std::bitset`
- `std::deque`
- `std::expected`
- `std::chrono::duration`
Expand Down
9 changes: 8 additions & 1 deletion docs/content/standard_containers.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,19 @@ This will also be represented as follows:
```

All other supported standard containers
(other than `std::variant`, `std::optional`, `std::unique_ptr`, `std::shared_ptr` and `std::expected`)
(other than `std::bitset`, `std::variant`, `std::optional`, `std::unique_ptr`, `std::shared_ptr` and `std::expected`)
will be represented as arrays. Containers for which the `value_type`
is a key-value-pair will be represented as arrays of pairs.

`std::expected` is an exception to this: it is serialized as its value type, or as an object with a single `error` field. Refer to the [std::expected](../expected) section for details.

`std::bitset<N>` uses the native representation in Cereal, YAS, CBOR, and
Boost.Serialization (packed in the first three, a string in Boost). Other
binary formats use `ceil(N / 8)` packed bytes: bit zero is the high bit of the
first byte, and unused low bits are zero. Other formats use exactly `N` binary
characters in `to_string()` order. The shared parsers reject incorrect lengths,
invalid characters, and nonzero padding; native adapters retain native validation.

## Typed map keys in binary formats

MessagePack, Cereal, YAS, and Boost.Serialization serialize the keys of the
Expand Down
5 changes: 5 additions & 0 deletions include/rfl/avro/Parser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@
namespace rfl {
namespace parsing {

template <std::size_t N, class ProcessorsType>
struct Parser<avro::Reader, avro::Writer, std::bitset<N>, ProcessorsType>
: ParserBitset<avro::Reader, avro::Writer, N, ProcessorsType,
Bytestring> {};

/// AVRO requires us to explicitly set all fields. Because
/// of that, we require all of the fields and then set them to nullptr, if
/// necessary.
Expand Down
3 changes: 3 additions & 0 deletions include/rfl/avro/Reader.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ struct Reader {
if (err) {
return error("Could not cast to bytestring.");
}
if (size == 0) {
return VectorType{};
}
const auto data = internal::ptr_cast<const ValueType*>(ptr);
return VectorType(data, data + size);
} else if constexpr (std::is_same<std::remove_cvref_t<T>, bool>()) {
Expand Down
8 changes: 4 additions & 4 deletions include/rfl/avro/Writer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -329,10 +329,10 @@ class RFL_API Writer {

} else if constexpr (concepts::MutableContiguousByteContainer<Type>) {
auto var = _var;
if (!var.data()) {
return;
}
int result = avro_value_set_bytes(_val, var.data(), var.size());
// Avro requires a non-null pointer even for an empty bytes value.
typename Type::value_type empty{};
auto* data = var.size() == 0 ? &empty : var.data();
int result = avro_value_set_bytes(_val, data, var.size());
if (result != 0) {
throw std::runtime_error(
"Error setting bytestring value: " + std::to_string(result) + ": " +
Expand Down
1 change: 1 addition & 0 deletions include/rfl/avro/to_schema.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include "../Processors.hpp"
#include "../common.hpp"
#include "../parsing/schema/make.hpp"
#include "Parser.hpp"
#include "Reader.hpp"
#include "Schema.hpp"
#include "Writer.hpp"
Expand Down
10 changes: 10 additions & 0 deletions include/rfl/boost_serialization/Parser.hpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
#ifndef RFL_BOOST_SERIALIZATION_PARSER_HPP_
#define RFL_BOOST_SERIALIZATION_PARSER_HPP_

#include <bitset>
#include <boost/archive/binary_iarchive.hpp>
#include <boost/archive/binary_oarchive.hpp>
#include <boost/serialization/bitset.hpp>
#include <cstddef>
#include <cstdint>
#include <exception>
Expand All @@ -22,6 +24,14 @@
namespace rfl {
namespace parsing {

template <class IArchive, class OArchive, std::size_t N, class ProcessorsType>
struct Parser<boost_serialization::Reader<IArchive>,
boost_serialization::Writer<OArchive>, std::bitset<N>,
ProcessorsType>
: ParserBasicType<boost_serialization::Reader<IArchive>,
boost_serialization::Writer<OArchive>, std::bitset<N>,
ProcessorsType> {};

template <class T, class ProcessorsType>
requires(std::is_arithmetic_v<T> && !std::is_same_v<T, bool> &&
!std::is_same_v<T, char>)
Expand Down
9 changes: 9 additions & 0 deletions include/rfl/bson/Parser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,15 @@

namespace rfl::parsing {

template <std::size_t N, class ProcessorsType>
struct Parser<bson::Reader, bson::Writer, std::bitset<N>, ProcessorsType>
: ParserBitset<bson::Reader, bson::Writer, N, ProcessorsType,
Bytestring> {};

} // namespace rfl::parsing

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>
Expand Down
5 changes: 5 additions & 0 deletions include/rfl/capnproto/Parser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@
namespace rfl {
namespace parsing {

template <std::size_t N, class ProcessorsType>
struct Parser<capnproto::Reader, capnproto::Writer, std::bitset<N>, ProcessorsType>
: ParserBitset<capnproto::Reader, capnproto::Writer, N, ProcessorsType,
Bytestring> {};

/// Cap'n proto requires us to explicitly set all fields. Because
/// of that, we require all of the fields and then set them to nullptr, if
/// necessary.
Expand Down
1 change: 1 addition & 0 deletions include/rfl/capnproto/to_schema.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include "../parsing/schema/Type.hpp"
#include "../parsing/schema/ValidationType.hpp"
#include "../parsing/schema/make.hpp"
#include "Parser.hpp"
#include "Reader.hpp"
#include "Schema.hpp"
#include "Writer.hpp"
Expand Down
9 changes: 9 additions & 0 deletions include/rfl/cbor/Parser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@
#include "Reader.hpp"
#include "Writer.hpp"

namespace rfl::parsing {

template <std::size_t N, class ProcessorsType>
struct Parser<cbor::Reader, cbor::Writer, std::bitset<N>, ProcessorsType>
: ParserBasicType<cbor::Reader, cbor::Writer, std::bitset<N>,
ProcessorsType> {};

} // namespace rfl::parsing

namespace rfl::cbor {

template <class T, class ProcessorsType>
Expand Down
8 changes: 8 additions & 0 deletions include/rfl/cbor/Reader.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include "../Result.hpp"
#include "../Vectorstring.hpp"
#include "../always_false.hpp"
#include "../internal/is_bitset.hpp"
#include "../internal/ptr_cast.hpp"

namespace rfl::cbor {
Expand Down Expand Up @@ -122,6 +123,13 @@ class Reader {
}
return _var.val_->as<std::string>();

} else if constexpr (internal::is_bitset_v<T>) {
try {
return _var.val_->template as<T>();
} catch (const std::exception& e) {
return error(e.what());
}

} else if constexpr (std::is_same<std::remove_cvref_t<T>,
rfl::Bytestring>() ||
std::is_same<std::remove_cvref_t<T>,
Expand Down
6 changes: 6 additions & 0 deletions include/rfl/cbor/Writer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
#endif
#endif
#include <jsoncons/json.hpp>
#include <jsoncons_ext/cbor/cbor_encoder.hpp>
#ifdef __GNUC__
#ifndef __clang__
Expand All @@ -24,6 +25,7 @@
#include "../Vectorstring.hpp"
#include "../always_false.hpp"
#include "../common.hpp"
#include "../internal/is_bitset.hpp"

namespace rfl::cbor {

Expand Down Expand Up @@ -179,6 +181,10 @@ class RFL_API Writer {
if constexpr (std::is_same<std::remove_cvref_t<T>, std::string>()) {
encoder_->string_value(_var);

} else if constexpr (internal::is_bitset_v<T>) {
// Native jsoncons conversion retains the CBOR base16 semantic tag.
jsoncons::json(_var).dump(*encoder_);

} else if constexpr (std::is_same<std::remove_cvref_t<T>,
rfl::Bytestring>() ||
std::is_same<std::remove_cvref_t<T>,
Expand Down
9 changes: 9 additions & 0 deletions include/rfl/cereal/Parser.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#ifndef RFL_CEREAL_PARSER_HPP_
#define RFL_CEREAL_PARSER_HPP_

#include <bitset>
#include <cereal/types/bitset.hpp>
#include <cstddef>
#include <exception>
#include <string>
Expand All @@ -15,6 +17,13 @@
namespace rfl {
namespace parsing {

// Delegate to Cereal's tagged, packed encoding through the reader and writer,
// retaining normal error handling and parent metadata.
template <std::size_t N, class ProcessorsType>
struct Parser<cereal::Reader, cereal::Writer, std::bitset<N>, ProcessorsType>
: ParserBasicType<cereal::Reader, cereal::Writer, std::bitset<N>,
ProcessorsType> {};

template <class T, class ProcessorsType>
requires(std::is_arithmetic_v<T> && !std::is_same_v<T, bool> &&
!std::is_same_v<T, char>)
Expand Down
12 changes: 12 additions & 0 deletions include/rfl/csv/read.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
//#include "../concepts.hpp"
#include "../internal/wrap_in_rfl_array_t.hpp"
#include "../parsing/tabular/ArrowReader.hpp"
#include "../parsing/tabular/make_arrow_schema.hpp"
#include "Settings.hpp"

namespace rfl::csv {
Expand Down Expand Up @@ -58,6 +59,17 @@ Result<internal::wrap_in_rfl_array_t<T>> read(
std::vector<std::string>({_settings.null_string});
convert_options.strings_can_be_null = true;

// Preserve textual columns before Arrow's inference can discard leading
// zeroes (in particular, the fixed-length text representation of bitsets).
using Row = typename T::value_type;
const auto schema = parsing::tabular::make_arrow_schema<
named_tuple_t<Row, Ps...>, parsing::tabular::SerializationType::csv>();
for (const auto& field : schema->fields()) {
if (field->type()->Equals(arrow::utf8())) {
convert_options.column_types[field->name()] = field->type();
}
}

auto parse_options = arrow::csv::ParseOptions::Defaults();
parse_options.delimiter = _settings.delimiter;
parse_options.quoting = _settings.quoting;
Expand Down
9 changes: 9 additions & 0 deletions include/rfl/flexbuf/Parser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@
#include "Reader.hpp"
#include "Writer.hpp"

namespace rfl::parsing {

template <std::size_t N, class ProcessorsType>
struct Parser<flexbuf::Reader, flexbuf::Writer, std::bitset<N>, ProcessorsType>
: ParserBitset<flexbuf::Reader, flexbuf::Writer, N, ProcessorsType,
Bytestring> {};

} // namespace rfl::parsing

namespace rfl {
namespace flexbuf {

Expand Down
77 changes: 77 additions & 0 deletions include/rfl/internal/bitset.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#ifndef RFL_INTERNAL_BITSET_HPP_
#define RFL_INTERNAL_BITSET_HPP_

#include <bitset>
#include <cstddef>
#include <string>
#include <string_view>
#include <utility>

#include "../Result.hpp"

namespace rfl::internal {

template <std::size_t N>
Result<std::bitset<N>> bitset_from_string(const std::string_view _str) {
if (_str.size() != N) {
std::string message = "Expected a bitset string of length ";
message += std::to_string(N);
message += ", got ";
message += std::to_string(_str.size());
message += ".";
return error(std::move(message));
}
std::bitset<N> bits;
for (std::size_t i = 0; i < N; ++i) {
if (_str[i] != '0' && _str[i] != '1') {
return error("Bitset strings may contain only '0' and '1'.");
}
bits[N - 1 - i] = _str[i] == '1';
}
return bits;
}

// Bit zero occupies the most significant bit of the first byte, matching
// Cereal's packed payload. Never access the implementation's bitset storage.
template <class Bytes, std::size_t N>
Bytes bitset_to_bytes(const std::bitset<N>& _bits) {
Bytes bytes{};
if constexpr (requires { bytes.resize(0); }) {
bytes.resize(N / 8 + (N % 8 != 0));
}
for (std::size_t i = 0; i < N; ++i) {
if (_bits[i]) {
bytes[i / 8] |= static_cast<typename Bytes::value_type>(0x80u >> (i % 8));
}
}
return bytes;
}

template <std::size_t N, class Bytes>
Result<std::bitset<N>> bitset_from_bytes(const Bytes& _bytes) {
constexpr auto size = N / 8 + (N % 8 != 0);
if (_bytes.size() != size) {
std::string message = "Expected ";
message += std::to_string(size);
message += " packed bitset bytes, got ";
message += std::to_string(_bytes.size());
message += ".";
return error(std::move(message));
}
if constexpr (N % 8 != 0) {
constexpr auto mask = (1u << (8 - N % 8)) - 1;
if ((static_cast<unsigned char>(_bytes.back()) & mask) != 0) {
return error("Packed bitset padding bits must be zero.");
}
}
std::bitset<N> bits;
for (std::size_t i = 0; i < N; ++i) {
bits[i] =
(static_cast<unsigned char>(_bytes[i / 8]) & (0x80u >> (i % 8))) != 0;
}
return bits;
}

} // namespace rfl::internal

#endif
21 changes: 21 additions & 0 deletions include/rfl/internal/is_bitset.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#ifndef RFL_INTERNAL_IS_BITSET_HPP_
#define RFL_INTERNAL_IS_BITSET_HPP_

#include <bitset>
#include <cstddef>
#include <type_traits>

namespace rfl::internal {

template <class T>
struct is_bitset : std::false_type {};

template <std::size_t N>
struct is_bitset<std::bitset<N>> : std::true_type {};

template <class T>
inline constexpr bool is_bitset_v = is_bitset<std::remove_cvref_t<T>>::value;

} // namespace rfl::internal

#endif
5 changes: 5 additions & 0 deletions include/rfl/msgpack/Parser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@
namespace rfl {
namespace parsing {

template <std::size_t N, class ProcessorsType>
struct Parser<msgpack::Reader, msgpack::Writer, std::bitset<N>, ProcessorsType>
: ParserBitset<msgpack::Reader, msgpack::Writer, N, ProcessorsType,
Bytestring> {};

/// 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.
Expand Down
1 change: 1 addition & 0 deletions include/rfl/parsing/Parser.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#ifndef RFL_PARSING_PARSER_HPP_
#define RFL_PARSING_PARSER_HPP_

#include "ParserBitset.hpp"
#include "Parser_base.hpp"
#include "Parser_default.hpp"
#include "Parser_named_tuple.hpp"
Expand Down
Loading
Loading