Commit d3b18572 authored by Dominik Charousset's avatar Dominik Charousset

Enable config_value to parse its to_string output

parent ab7535c7
......@@ -35,7 +35,8 @@
namespace caf {
/// Deserializes objects from sequence of bytes.
/// Deserializes objects from sequence of bytes. Does not perform run-time type
/// checks.
class CAF_CORE_EXPORT binary_deserializer
: public load_inspector_base<binary_deserializer> {
public:
......@@ -112,7 +113,7 @@ public:
bool fetch_next_object_type(type_id_t& type) noexcept;
constexpr bool begin_object(string_view) noexcept {
constexpr bool begin_object(type_id_t, string_view) noexcept {
return true;
}
......
......@@ -33,7 +33,10 @@
namespace caf {
/// Serializes objects into a sequence of bytes.
/// Serializes C++ objects into a sequence of bytes.
/// @note The binary data format may change between CAF versions and does not
/// perform any type checking at run-time. Thus the output of this
/// serializer is unsuitable for persistence layers.
class CAF_CORE_EXPORT binary_serializer
: public save_inspector_base<binary_serializer> {
public:
......@@ -95,9 +98,7 @@ public:
// -- interface functions ----------------------------------------------------
bool inject_next_object_type(type_id_t type);
constexpr bool begin_object(string_view) {
constexpr bool begin_object(type_id_t, string_view) noexcept {
return true;
}
......
......@@ -172,6 +172,9 @@ public:
// -- utility ----------------------------------------------------------------
/// @private
type_id_t type_id() const noexcept;
/// @private
error_code<sec> default_construct(type_id_t);
......@@ -207,6 +210,16 @@ public:
return {reader.move_error()};
}
/// @experimental
template <class T>
error assign(const T& x) {
config_value_writer writer{this};
if (detail::save(writer, x))
return {};
else
return {writer.move_error()};
}
private:
// -- properties -------------------------------------------------------------
......
......@@ -99,7 +99,7 @@ public:
bool fetch_next_object_type(type_id_t& type) override;
bool begin_object(string_view name) override;
bool begin_object(type_id_t type, string_view name) override;
bool end_object() override;
......
......@@ -68,9 +68,7 @@ public:
// -- interface functions ----------------------------------------------------
bool inject_next_object_type(type_id_t type) override;
bool begin_object(string_view name) override;
bool begin_object(type_id_t type, string_view name) override;
bool end_object() override;
......@@ -140,7 +138,6 @@ private:
bool push(config_value&& x);
stack_type st_;
string_view type_hint_;
};
} // namespace caf
......@@ -62,13 +62,15 @@ public:
// -- interface functions ----------------------------------------------------
/// Reads run-time-type information for the next object. Requires that the
/// @ref serializer provided this information via
/// @ref serializer::inject_next_object_type.
/// Reads run-time-type information for the next object if available.
virtual bool fetch_next_object_type(type_id_t& type) = 0;
/// Begins processing of an object.
virtual bool begin_object(string_view type) = 0;
/// Begins processing of an object, may perform a type check depending on the
/// data format.
/// @param type 16-bit ID for known types, @ref invalid_type_id otherwise.
/// @param pretty_class_name Either the output of @ref type_name_or_anonymous
/// or the optionally defined pretty name.
virtual bool begin_object(type_id_t type, string_view pretty_class_name) = 0;
/// Ends processing of an object.
virtual bool end_object() = 0;
......
......@@ -32,9 +32,7 @@ public:
size_t result = 0;
bool inject_next_object_type(type_id_t type) override;
bool begin_object(string_view) override;
bool begin_object(type_id_t, string_view) override;
bool end_object() override;
......
......@@ -58,7 +58,7 @@ public:
// -- serializer interface ---------------------------------------------------
bool begin_object(string_view name);
bool begin_object(type_id_t, string_view name);
bool end_object();
......
......@@ -53,7 +53,7 @@ public:
return false;
}
constexpr bool begin_object(string_view) {
constexpr bool begin_object(type_id_t, string_view) {
return true;
}
......
......@@ -49,7 +49,7 @@ public:
return false;
}
constexpr bool begin_object(string_view) {
constexpr bool begin_object(type_id_t, string_view) {
return true;
}
......
......@@ -275,6 +275,7 @@ public:
template <class Inspector, class LoadCallback>
struct object_with_load_callback_t {
type_id_t object_type;
string_view object_name;
Inspector* f;
LoadCallback load_callback;
......@@ -282,7 +283,7 @@ public:
template <class... Fields>
bool fields(Fields&&... fs) {
using load_callback_result = decltype(load_callback());
if (!(f->begin_object(object_name) && (fs(*f) && ...)))
if (!(f->begin_object(object_type, object_name) && (fs(*f) && ...)))
return false;
if constexpr (std::is_same<load_callback_result, bool>::value) {
if (!load_callback()) {
......@@ -299,7 +300,7 @@ public:
}
auto pretty_name(string_view name) && {
return object_t{name, f};
return object_t{object_type, name, f};
}
template <class F>
......@@ -310,16 +311,19 @@ public:
template <class Inspector>
struct object_t {
type_id_t object_type;
string_view object_name;
Inspector* f;
template <class... Fields>
bool fields(Fields&&... fs) {
return f->begin_object(object_name) && (fs(*f) && ...) && f->end_object();
return f->begin_object(object_type, object_name) //
&& (fs(*f) && ...) //
&& f->end_object();
}
auto pretty_name(string_view name) && {
return object_t{name, f};
return object_t{object_type, name, f};
}
template <class F>
......@@ -330,6 +334,7 @@ public:
template <class F>
auto on_load(F fun) && {
return object_with_load_callback_t<Inspector, F>{
object_type,
object_name,
f,
std::move(fun),
......
......@@ -38,7 +38,8 @@ public:
template <class T>
constexpr auto object(T&) noexcept {
return super::object_t<Subtype>{type_name_or_anonymous<T>(), dptr()};
return super::object_t<Subtype>{type_id_or_invalid<T>(),
type_name_or_anonymous<T>(), dptr()};
}
template <class T>
......
......@@ -171,6 +171,7 @@ public:
template <class Inspector, class SaveCallback>
struct object_with_save_callback_t {
type_id_t object_type;
string_view object_name;
Inspector* f;
SaveCallback save_callback;
......@@ -178,7 +179,7 @@ public:
template <class... Fields>
bool fields(Fields&&... fs) {
using save_callback_result = decltype(save_callback());
if (!(f->begin_object(object_name) && (fs(*f) && ...)))
if (!(f->begin_object(object_type, object_name) && (fs(*f) && ...)))
return false;
if constexpr (std::is_same<save_callback_result, bool>::value) {
if (!save_callback()) {
......@@ -206,16 +207,19 @@ public:
template <class Inspector>
struct object_t {
type_id_t object_type;
string_view object_name;
Inspector* f;
template <class... Fields>
bool fields(Fields&&... fs) {
return f->begin_object(object_name) && (fs(*f) && ...) && f->end_object();
return f->begin_object(object_type, object_name) //
&& (fs(*f) && ...) //
&& f->end_object();
}
auto pretty_name(string_view name) && {
return object_t{name, f};
return object_t{object_type, name, f};
}
template <class F>
......@@ -226,6 +230,7 @@ public:
template <class F>
auto on_save(F fun) && {
return object_with_save_callback_t<Inspector, F>{
object_type,
object_name,
f,
std::move(fun),
......
......@@ -34,7 +34,8 @@ public:
template <class T>
constexpr auto object(T&) noexcept {
return super::object_t<Subtype>{type_name_or_anonymous<T>(), dptr()};
return super::object_t<Subtype>{type_id_or_invalid<T>(),
type_name_or_anonymous<T>(), dptr()};
}
template <class T>
......
......@@ -164,6 +164,12 @@ enum class sec : uint8_t {
conversion_failed,
/// A network connection was closed by the remote side.
connection_closed,
/// An operation failed because run-time type information diverged from the
/// expected type.
type_clash,
/// An operation failed because the callee does not implement this
/// functionality.
unsupported_operation,
};
/// @relates sec
......
......@@ -66,15 +66,10 @@ public:
// -- interface functions ----------------------------------------------------
/// Injects run-time-type information for the *next* object, i.e., causes the
/// next call to `begin_object` to write additional meta information. Allows a
/// @ref deserializer to retrieve the type for the next object via
/// @ref deserializer::fetch_next_object_type.
virtual bool inject_next_object_type(type_id_t type) = 0;
/// Begins processing of an object. Saves the type information
/// to the underlying storage.
virtual bool begin_object(string_view name) = 0;
/// Begins processing of an object. May save the type information to the
/// underlying storage to allow a @ref deserializer to retrieve and check the
/// type information for data formats that provide deserialization.
virtual bool begin_object(type_id_t type, string_view name) = 0;
/// Ends processing of an object.
virtual bool end_object() = 0;
......
......@@ -108,6 +108,15 @@ string_view type_name_or_anonymous() {
return "anonymous";
}
/// Returns `type_id_v<T>` if available, `invalid_type_id` otherwise.
template <class T>
type_id_t type_id_or_invalid() {
if constexpr (detail::is_complete<type_id<T>>)
return type_id<T>::value;
else
return invalid_type_id;
}
/// Returns the type name of given `type` or an empty string if `type` is an
/// invalid ID.
CAF_CORE_EXPORT string_view query_type_name(type_id_t type);
......
......@@ -35,19 +35,23 @@ namespace {
template <class T>
bool int_value(binary_deserializer& source, T& x) {
auto tmp = std::make_unsigned_t<T>{};
if (!source.value(as_writable_bytes(make_span(&tmp, 1))))
return false;
if (source.value(as_writable_bytes(make_span(&tmp, 1)))) {
x = static_cast<T>(detail::from_network_order(tmp));
return true;
} else {
return false;
}
}
template <class T>
bool float_value(binary_deserializer& source, T& x) {
auto tmp = typename detail::ieee_754_trait<T>::packed_type{};
if (!int_value(source, tmp))
return false;
if (int_value(source, tmp)) {
x = detail::unpack754(tmp);
return true;
} else {
return false;
}
}
// Does not perform any range checks.
......@@ -67,7 +71,10 @@ binary_deserializer::binary_deserializer(actor_system& sys) noexcept
}
bool binary_deserializer::fetch_next_object_type(type_id_t& type) noexcept {
return value(type);
type = invalid_type_id;
emplace_error(sec::unsupported_operation,
"the default binary format does not embed type information");
return false;
}
bool binary_deserializer::begin_sequence(size_t& list_size) noexcept {
......
......@@ -51,10 +51,6 @@ void binary_serializer::skip(size_t num_bytes) {
write_pos_ += num_bytes;
}
bool binary_serializer::inject_next_object_type(type_id_t type) {
return value(type);
}
bool binary_serializer::begin_sequence(size_t list_size) {
// Use varbyte encoding to compress sequence size on the wire.
// For 64-bit values, the encoded representation cannot get larger than 10
......
......@@ -149,6 +149,21 @@ const char* config_value::type_name_at_index(size_t index) noexcept {
// -- utility ------------------------------------------------------------------
type_id_t config_value::type_id() const noexcept {
static constexpr type_id_t allowed_types[] = {
type_id_v<none_t>,
type_id_v<config_value::integer>,
type_id_v<config_value::boolean>,
type_id_v<config_value::real>,
type_id_v<timespan>,
type_id_v<uri>,
type_id_v<config_value::string>,
type_id_v<config_value::list>,
type_id_v<config_value::dictionary>,
};
return allowed_types[data_.index()];
}
error_code<sec> config_value::default_construct(type_id_t id) {
switch (id) {
case type_id_v<bool>:
......
......@@ -122,11 +122,12 @@ bool config_value_reader::fetch_next_object_type(type_id_t& type) {
return false;
},
[this, &type](const config_value* val) {
if (auto obj = get_if<settings>(val); obj == nullptr) {
emplace_error(sec::conversion_failed, "cannot read input as object");
return false;
auto tid = val->type_id();
if (tid != type_id_v<config_value::dictionary>) {
type = tid;
return true;
} else {
return fetch_object_type(obj, type);
return fetch_object_type(get_if<settings>(val), type);
}
},
[this](key_ptr) {
......@@ -146,11 +147,13 @@ bool config_value_reader::fetch_next_object_type(type_id_t& type) {
emplace_error(sec::runtime_error, "list index out of bounds");
return false;
}
if (auto obj = get_if<settings>(std::addressof(seq.current())); !obj) {
emplace_error(sec::conversion_failed, "cannot read input as object");
return false;
auto& val = seq.current();
auto tid = val.type_id();
if (tid != type_id_v<config_value::dictionary>) {
type = tid;
return true;
} else {
return fetch_object_type(obj, type);
return fetch_object_type(get_if<settings>(&val), type);
}
},
[this](associative_array&) {
......@@ -162,7 +165,7 @@ bool config_value_reader::fetch_next_object_type(type_id_t& type) {
}
}
bool config_value_reader::begin_object(string_view) {
bool config_value_reader::begin_object(type_id_t type, string_view) {
if (st_.empty()) {
emplace_error(sec::runtime_error,
"tried to read multiple objects from the root object");
......@@ -216,7 +219,27 @@ bool config_value_reader::begin_object(string_view) {
"fetch_next_object_type called inside associative array");
return false;
});
return visit(f, st_.top());
if (visit(f, st_.top())) {
// Perform a type check if type is a valid ID and the object contains an
// "@type" field.
if (type != invalid_type_id) {
CAF_ASSERT(holds_alternative<const settings*>(st_.top()));
auto obj = get<const settings*>(st_.top());
auto want = query_type_name(type);
if (auto i = obj->find("@type"); i != obj->end()) {
if (auto got = get_if<std::string>(std::addressof(i->second))) {
if (want != *got) {
emplace_error(sec::type_clash, "expected type: " + to_string(want),
"found type: " + *got);
return false;
}
}
}
}
return true;
} else {
return false;
}
}
bool config_value_reader::end_object() {
......@@ -563,15 +586,14 @@ bool config_value_reader::value(span<byte> bytes) {
bool config_value_reader::fetch_object_type(const settings* obj,
type_id_t& type) {
if (auto str = get_if<std::string>(obj, "@type"); str == nullptr) {
emplace_error(sec::runtime_error,
"cannot fetch object type: no '@type' entry found");
return false;
} else if (auto id = query_type_id(*str); id == invalid_type_id) {
emplace_error(sec::runtime_error, "no such type: " + *str);
return false;
} else {
type = type_id_v<config_value::dictionary>;
return true;
} else if (auto id = query_type_id(*str); id != invalid_type_id) {
type = id;
return true;
} else {
emplace_error(sec::runtime_error, "unknown type: " + *str);
return false;
}
}
......
......@@ -66,18 +66,7 @@ config_value_writer::~config_value_writer() {
// -- interface functions ------------------------------------------------------
bool config_value_writer::inject_next_object_type(type_id_t type) {
CHECK_NOT_EMPTY();
type_hint_ = query_type_name(type);
if (type_hint_.empty()) {
emplace_error(sec::runtime_error,
"query_type_name returned an empty string for type ID");
return false;
}
return true;
}
bool config_value_writer::begin_object(string_view) {
bool config_value_writer::begin_object(type_id_t type, string_view) {
CHECK_NOT_EMPTY();
auto f = detail::make_overload(
[this](config_value* x) {
......@@ -118,10 +107,8 @@ bool config_value_writer::begin_object(string_view) {
});
if (!visit(f, st_.top()))
return false;
if (!type_hint_.empty()) {
put(*get<settings*>(st_.top()), "@type", type_hint_);
type_hint_ = string_view{};
}
if (type != invalid_type_id)
put(*get<settings*>(st_.top()), "@type", query_type_name(type));
return true;
}
......
......@@ -26,11 +26,7 @@
namespace caf::detail {
bool serialized_size_inspector::inject_next_object_type(type_id_t type) {
return value(type);
}
bool serialized_size_inspector::begin_object(string_view) {
bool serialized_size_inspector::begin_object(type_id_t, string_view) {
return true;
}
......
......@@ -49,7 +49,7 @@ void escape(std::string& result, char c) {
namespace caf::detail {
bool stringification_inspector::begin_object(string_view name) {
bool stringification_inspector::begin_object(type_id_t, string_view name) {
sep();
if (name != "std::string") {
result_.insert(result_.end(), name.begin(), name.end());
......
......@@ -59,7 +59,7 @@ template <class Deserializer>
bool load_data(Deserializer& source, message::data_ptr& data) {
// For machine-to-machine data formats, we prefix the type information.
if (!source.has_human_readable_format()) {
GUARDED(source.begin_object("message"));
GUARDED(source.begin_object(type_id_v<message>, "message"));
GUARDED(source.begin_field("types"));
size_t msg_size = 0;
GUARDED(source.begin_sequence(msg_size));
......@@ -159,8 +159,6 @@ bool load_data(Deserializer& source, message::data_ptr& data) {
using unique_void_ptr = std::unique_ptr<void, free_t>;
auto msg_size = size_t{0};
std::vector<object_ptr> objects;
GUARDED(source.begin_object("message"));
GUARDED(source.begin_field("values"));
GUARDED(source.begin_sequence(msg_size));
if (msg_size > 0) {
// Deserialize message elements individually.
......@@ -185,7 +183,7 @@ bool load_data(Deserializer& source, message::data_ptr& data) {
STOP(sec::unknown_type);
}
}
GUARDED(source.end_field() && source.end_sequence());
GUARDED(source.end_sequence());
// Merge elements into a single message data object.
intrusive_ptr<detail::message_data> ptr;
if (auto vptr = malloc(sizeof(detail::message_data) + data_size)) {
......@@ -203,13 +201,11 @@ bool load_data(Deserializer& source, message::data_ptr& data) {
pos += x.meta->padded_size;
}
data.reset(ptr.release(), false);
return source.end_object();
return true;
} else {
data.reset();
return source.end_sequence();
}
return source.end_sequence() //
&& source.end_field() //
&& source.end_object();
}
} // namespace
......@@ -242,7 +238,7 @@ save_data(Serializer& sink, const message::data_ptr& data) {
if (!sink.has_human_readable_format()) {
if (data == nullptr) {
// Short-circuit empty tuples.
return sink.begin_object("message") //
return sink.begin_object(type_id_v<message>, "message") //
&& sink.begin_field("types") //
&& sink.begin_sequence(0) //
&& sink.end_sequence() //
......@@ -253,7 +249,7 @@ save_data(Serializer& sink, const message::data_ptr& data) {
&& sink.end_field() //
&& sink.end_object();
}
GUARDED(sink.begin_object("message"));
GUARDED(sink.begin_object(type_id_v<message>, "message"));
auto type_ids = data->types();
// Write type information.
GUARDED(sink.begin_field("types") && sink.begin_sequence(type_ids.size()));
......@@ -274,25 +270,17 @@ save_data(Serializer& sink, const message::data_ptr& data) {
// dynamically-typed objects.
if (data == nullptr) {
// Short-circuit empty tuples.
return sink.begin_object("message") //
&& sink.begin_field("values") //
&& sink.begin_sequence(0) //
&& sink.end_sequence() //
&& sink.end_field() //
&& sink.end_object();
return sink.begin_sequence(0) && sink.end_sequence();
}
auto type_ids = data->types();
GUARDED(sink.begin_object("message") //
&& sink.begin_field("values") //
&& sink.begin_sequence(type_ids.size()));
GUARDED(sink.begin_sequence(type_ids.size()));
auto storage = data->storage();
for (auto id : type_ids) {
auto& meta = gmos[id];
GUARDED(sink.inject_next_object_type(id) //
&& save(meta, sink, storage));
GUARDED(save(meta, sink, storage));
storage += meta.padded_size;
}
return sink.end_sequence() && sink.end_field() && sink.end_object();
return sink.end_sequence();
}
} // namespace
......
......@@ -41,12 +41,12 @@ namespace {
template <class Serializer>
bool serialize_impl(Serializer& sink, const tracing_data_ptr& x) {
if (!x) {
return sink.begin_object("tracing_data") //
return sink.begin_object(invalid_type_id, "tracing_data") //
&& sink.begin_field("value", false) //
&& sink.end_field() //
&& sink.end_object();
}
return sink.begin_object("tracing_data") //
return sink.begin_object(invalid_type_id, "tracing_data") //
&& sink.begin_field("value", true) //
&& x->serialize(sink) //
&& sink.end_field() //
......@@ -56,7 +56,7 @@ bool serialize_impl(Serializer& sink, const tracing_data_ptr& x) {
template <class Deserializer>
bool deserialize_impl(Deserializer& source, tracing_data_ptr& x) {
bool is_present = false;
if (!source.begin_object("tracing_data")
if (!source.begin_object(invalid_type_id, "tracing_data")
|| !source.begin_field("value", is_present))
return false;
if (!is_present)
......
......@@ -672,7 +672,9 @@ SCENARIO("config values can default-construct all registered types") {
auto val = from(type_id_v<my_request>);
CHECK_EQ(val.get_data().index(), 8u);
auto& dict = val.as_dictionary();
CHECK_EQ(keys(dict), std::vector<std::string>({"a", "b"}));
CHECK_EQ(keys(dict), std::vector<std::string>({"@type", "a", "b"}));
CHECK_EQ(dict["@type"].get_data().index(), 6u);
CHECK_EQ(get_as<std::string>(dict["@type"]), "my_request"s);
CHECK_EQ(dict["a"].get_data().index(), 1u);
CHECK_EQ(get_as<int32_t>(dict["a"]), 0);
CHECK_EQ(dict["b"].get_data().index(), 1u);
......@@ -682,6 +684,41 @@ SCENARIO("config values can default-construct all registered types") {
}
}
#define CHECK_ROUNDTRIP(init_val, expected_str) \
do { \
config_value x; \
if (auto assign_failed = x.assign(init_val); CHECK(!assign_failed)) { \
auto str = to_string(x); \
CHECK_EQ(str, expected_str); \
auto parsed = config_value::parse(str); \
using init_val_type = decltype(init_val); \
if (CHECK(parsed)) { \
if constexpr (!std::is_same<init_val_type, message>::value) \
CHECK_EQ(get_as<init_val_type>(*parsed), init_val); \
else \
CHECK_EQ(to_string(*parsed), str); \
} \
} \
} while (false)
SCENARIO("config values can parse their own to_string output") {
GIVEN("a config value") {
WHEN("assigning a value and then calling to_string on it") {
THEN("then config_value::parse reconstitutes the original value") {
CHECK_ROUNDTRIP(0, "0");
CHECK_ROUNDTRIP("hello world"s, "hello world");
CHECK_ROUNDTRIP(std::vector<int>({1, 2, 3}), "[1, 2, 3]");
CHECK_ROUNDTRIP(my_request(1, 2),
R"_({"@type" = "my_request", a = 1, b = 2})_");
CHECK_ROUNDTRIP(std::make_tuple(add_atom_v, 1, 2),
R"_([{"@type" = "caf::add_atom"}, 1, 2])_");
CHECK_ROUNDTRIP(make_message(add_atom_v, 1, 2),
R"_([{"@type" = "caf::add_atom"}, 1, 2])_");
}
}
}
}
CAF_TEST(default_constructed) {
config_value x;
CAF_CHECK_EQUAL(holds_alternative<none_t>(x), true);
......
......@@ -58,7 +58,7 @@ struct testee : deserializer {
return false;
}
bool begin_object(string_view object_name) override {
bool begin_object(type_id_t, string_view object_name) override {
new_line();
indent += 2;
log += "begin object ";
......
......@@ -49,15 +49,7 @@ struct testee : serializer {
log.insert(log.end(), indent, ' ');
}
bool inject_next_object_type(type_id_t type) override {
new_line();
log += "next object type: ";
auto tn = detail::global_meta_object(type)->type_name;
log.insert(log.end(), tn.begin(), tn.end());
return true;
}
bool begin_object(string_view object_name) override {
bool begin_object(type_id_t, string_view object_name) override {
new_line();
indent += 2;
log += "begin object ";
......@@ -734,18 +726,11 @@ end object)_");
f.set_has_human_readable_format(true);
CAF_CHECK(inspect(f, x));
CAF_CHECK_EQUAL(f.log, R"_(
begin object message
begin field values
begin sequence of size 3
next object type: int32_t
begin sequence of size 3
int32_t value
next object type: std::string
std::string value
next object type: double
double value
end sequence
end field
end object)_");
end sequence)_");
}
CAF_TEST_FIXTURE_SCOPE_END()
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment