Commit 56d43a75 authored by Dominik Charousset's avatar Dominik Charousset

Merge topic/neverlord/type-id-mapper

parents 4b8d5abf 251b128f
...@@ -5,6 +5,11 @@ is based on [Keep a Changelog](https://keepachangelog.com). ...@@ -5,6 +5,11 @@ is based on [Keep a Changelog](https://keepachangelog.com).
## [Unreleased] ## [Unreleased]
### Added
- JSON inspectors now allow users to use a custom `type_id_mapper` to generate
and parse JSON text that uses different names for the types than the C++ API.
### Fixed ### Fixed
- Passing a response promise to a run-delayed continuation could result in a - Passing a response promise to a run-delayed continuation could result in a
......
...@@ -112,6 +112,16 @@ public: ...@@ -112,6 +112,16 @@ public:
field_type_suffix_ = suffix; field_type_suffix_ = suffix;
} }
/// Returns the type ID mapper used by the writer.
[[nodiscard]] const type_id_mapper* mapper() const noexcept {
return mapper_;
}
/// Changes the type ID mapper for the writer.
void mapper(const type_id_mapper* ptr) noexcept {
mapper_ = ptr;
}
// -- modifiers -------------------------------------------------------------- // -- modifiers --------------------------------------------------------------
/// Parses @p json_text into an internal representation. After loading the /// Parses @p json_text into an internal representation. After loading the
...@@ -247,6 +257,12 @@ private: ...@@ -247,6 +257,12 @@ private:
/// Keeps track of the current field for better debugging output. /// Keeps track of the current field for better debugging output.
std::vector<std::string_view> field_; std::vector<std::string_view> field_;
/// The mapper implementation we use by default.
default_type_id_mapper default_mapper_;
/// Configures which ID mapper we use to translate between type IDs and names.
const type_id_mapper* mapper_ = &default_mapper_;
}; };
} // namespace caf } // namespace caf
...@@ -115,6 +115,16 @@ public: ...@@ -115,6 +115,16 @@ public:
field_type_suffix_ = suffix; field_type_suffix_ = suffix;
} }
/// Returns the type ID mapper used by the writer.
[[nodiscard]] const type_id_mapper* mapper() const noexcept {
return mapper_;
}
/// Changes the type ID mapper for the writer.
void mapper(const type_id_mapper* ptr) noexcept {
mapper_ = ptr;
}
// -- modifiers -------------------------------------------------------------- // -- modifiers --------------------------------------------------------------
/// Removes all characters from the buffer and restores the writer to its /// Removes all characters from the buffer and restores the writer to its
...@@ -280,7 +290,14 @@ private: ...@@ -280,7 +290,14 @@ private:
// Configures whether we omit the top-level '@type' annotation. // Configures whether we omit the top-level '@type' annotation.
bool skip_object_type_annotation_ = false; bool skip_object_type_annotation_ = false;
// Configures how we generate type annotations for fields.
std::string_view field_type_suffix_ = field_type_suffix_default; std::string_view field_type_suffix_ = field_type_suffix_default;
// The mapper implementation we use by default.
default_type_id_mapper default_mapper_;
// Configures which ID mapper we use to translate between type IDs and names.
const type_id_mapper* mapper_ = &default_mapper_;
}; };
} // namespace caf } // namespace caf
...@@ -4,13 +4,14 @@ ...@@ -4,13 +4,14 @@
#pragma once #pragma once
#include "caf/detail/core_export.hpp"
#include "caf/fwd.hpp"
#include "caf/timespan.hpp"
#include <chrono> #include <chrono>
#include <cstdint> #include <cstdint>
#include <string> #include <string>
#include "caf/detail/core_export.hpp"
#include "caf/timespan.hpp"
namespace caf { namespace caf {
/// A portable timestamp with nanosecond resolution anchored at the UNIX epoch. /// A portable timestamp with nanosecond resolution anchored at the UNIX epoch.
...@@ -23,6 +24,9 @@ CAF_CORE_EXPORT timestamp make_timestamp(); ...@@ -23,6 +24,9 @@ CAF_CORE_EXPORT timestamp make_timestamp();
/// Prints `x` in ISO 8601 format, e.g., `2018-11-15T06:25:01.462`. /// Prints `x` in ISO 8601 format, e.g., `2018-11-15T06:25:01.462`.
CAF_CORE_EXPORT std::string timestamp_to_string(timestamp x); CAF_CORE_EXPORT std::string timestamp_to_string(timestamp x);
/// Converts an ISO 8601 formatted timestamp into its native representation.
CAF_CORE_EXPORT expected<timestamp> timestamp_from_string(std::string_view str);
/// Appends the timestamp `x` in ISO 8601 format, e.g., /// Appends the timestamp `x` in ISO 8601 format, e.g.,
/// `2018-11-15T06:25:01.462`, to `y`. /// `2018-11-15T06:25:01.462`, to `y`.
CAF_CORE_EXPORT void append_timestamp_to_string(std::string& x, timestamp y); CAF_CORE_EXPORT void append_timestamp_to_string(std::string& x, timestamp y);
......
...@@ -96,13 +96,34 @@ type_id_t type_id_or_invalid() { ...@@ -96,13 +96,34 @@ type_id_t type_id_or_invalid() {
return invalid_type_id; return invalid_type_id;
} }
/// Returns the type name of given `type` or an empty string if `type` is an /// Returns the type name for @p type or an empty string if @p type is an
/// invalid ID. /// invalid ID.
CAF_CORE_EXPORT std::string_view query_type_name(type_id_t type); CAF_CORE_EXPORT std::string_view query_type_name(type_id_t type);
/// Returns the type of given `name` or `invalid_type_id` if no type matches. /// Returns the type for @p name or `invalid_type_id` if @p name is unknown.
CAF_CORE_EXPORT type_id_t query_type_id(std::string_view name); CAF_CORE_EXPORT type_id_t query_type_id(std::string_view name);
/// Translates between human-readable type names and type IDs.
class CAF_CORE_EXPORT type_id_mapper {
public:
virtual ~type_id_mapper();
/// Returns the type name for @p type or an empty string if @p type is an
/// invalid ID.
virtual std::string_view operator()(type_id_t type) const = 0;
/// Returns the type for @p name or `invalid_type_id` if @p name is unknown.
virtual type_id_t operator()(std::string_view name) const = 0;
};
/// Dispatches to @ref query_type_name and @ref query_type_id.
class default_type_id_mapper : public type_id_mapper {
public:
std::string_view operator()(type_id_t type) const override;
type_id_t operator()(std::string_view name) const override;
};
} // namespace caf } // namespace caf
// -- CAF_BEGIN_TYPE_ID_BLOCK -------------------------------------------------- // -- CAF_BEGIN_TYPE_ID_BLOCK --------------------------------------------------
......
...@@ -182,7 +182,7 @@ void json_reader::reset() { ...@@ -182,7 +182,7 @@ void json_reader::reset() {
bool json_reader::fetch_next_object_type(type_id_t& type) { bool json_reader::fetch_next_object_type(type_id_t& type) {
std::string_view type_name; std::string_view type_name;
if (fetch_next_object_name(type_name)) { if (fetch_next_object_name(type_name)) {
if (auto id = query_type_id(type_name); id != invalid_type_id) { if (auto id = (*mapper_)(type_name); id != invalid_type_id) {
type = id; type = id;
return true; return true;
} else { } else {
...@@ -311,7 +311,7 @@ bool json_reader::begin_field(std::string_view name, bool& is_present, ...@@ -311,7 +311,7 @@ bool json_reader::begin_field(std::string_view name, bool& is_present,
member != nullptr member != nullptr
&& member->val->data.index() != detail::json::value::null_index) { && member->val->data.index() != detail::json::value::null_index) {
auto ft = field_type(top<position::object>(), name, field_type_suffix_); auto ft = field_type(top<position::object>(), name, field_type_suffix_);
if (auto id = query_type_id(ft); id != invalid_type_id) { if (auto id = (*mapper_)(ft); id != invalid_type_id) {
if (auto i = std::find(types.begin(), types.end(), id); if (auto i = std::find(types.begin(), types.end(), id);
i != types.end()) { i != types.end()) {
index = static_cast<size_t>(std::distance(types.begin(), i)); index = static_cast<size_t>(std::distance(types.begin(), i));
......
...@@ -92,7 +92,7 @@ bool json_writer::begin_object(type_id_t id, std::string_view name) { ...@@ -92,7 +92,7 @@ bool json_writer::begin_object(type_id_t id, std::string_view name) {
add(R"_("@type": )_"); add(R"_("@type": )_");
pop(); pop();
CAF_ASSERT(top() == type::element); CAF_ASSERT(top() == type::element);
if (auto tname = query_type_name(id); !tname.empty()) { if (auto tname = (*mapper_)(id); !tname.empty()) {
add('"'); add('"');
add(tname); add(tname);
add('"'); add('"');
...@@ -177,12 +177,12 @@ bool json_writer::begin_field(std::string_view name, ...@@ -177,12 +177,12 @@ bool json_writer::begin_field(std::string_view name,
pop(); pop();
CAF_ASSERT(top() == type::element); CAF_ASSERT(top() == type::element);
pop(); pop();
if (auto tname = query_type_name(types[index]); !tname.empty()) { if (auto tname = (*mapper_)(types[index]); !tname.empty()) {
add('"'); add('"');
add(tname); add(tname);
add('"'); add('"');
} else { } else {
emplace_error(sec::runtime_error, "query_type_name failed"); emplace_error(sec::runtime_error, "failed to retrieve type name");
return false; return false;
} }
return end_key_value_pair() && begin_field(name); return end_key_value_pair() && begin_field(name);
......
...@@ -2,8 +2,10 @@ ...@@ -2,8 +2,10 @@
// the main distribution directory for license terms and copyright or visit // the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE. // https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#include "caf/deep_to_string.hpp"
#include "caf/timestamp.hpp" #include "caf/timestamp.hpp"
#include "caf/deep_to_string.hpp"
#include "caf/detail/parse.hpp"
#include "caf/expected.hpp"
namespace caf { namespace caf {
...@@ -15,6 +17,14 @@ std::string timestamp_to_string(timestamp x) { ...@@ -15,6 +17,14 @@ std::string timestamp_to_string(timestamp x) {
return deep_to_string(x.time_since_epoch().count()); return deep_to_string(x.time_since_epoch().count());
} }
expected<timestamp> timestamp_from_string(std::string_view str) {
timestamp result;
if (auto err = detail::parse(str, result); !err)
return result;
else
return err;
}
void append_timestamp_to_string(std::string& x, timestamp y) { void append_timestamp_to_string(std::string& x, timestamp y) {
x += timestamp_to_string(y); x += timestamp_to_string(y);
} }
......
...@@ -22,4 +22,16 @@ type_id_t query_type_id(std::string_view name) { ...@@ -22,4 +22,16 @@ type_id_t query_type_id(std::string_view name) {
return invalid_type_id; return invalid_type_id;
} }
type_id_mapper::~type_id_mapper() {
// nop
}
std::string_view default_type_id_mapper::operator()(type_id_t type) const {
return query_type_name(type);
}
type_id_t default_type_id_mapper::operator()(std::string_view name) const {
return query_type_id(name);
}
} // namespace caf } // namespace caf
...@@ -12,6 +12,8 @@ ...@@ -12,6 +12,8 @@
using namespace caf; using namespace caf;
using namespace std::literals;
namespace { namespace {
struct fixture { struct fixture {
...@@ -124,4 +126,57 @@ CAF_TEST(json baselines) { ...@@ -124,4 +126,57 @@ CAF_TEST(json baselines) {
} }
} }
SCENARIO("mappers enable custom type names in JSON input") {
GIVEN("a custom mapper") {
class custom_mapper : public type_id_mapper {
std::string_view operator()(type_id_t type) const override {
switch (type) {
case type_id_v<std::string>:
return "String";
case type_id_v<int32_t>:
return "Int";
default:
return query_type_name(type);
}
}
type_id_t operator()(std::string_view name) const override {
if (name == "String")
return type_id_v<std::string>;
else if (name == "Int")
return type_id_v<int32_t>;
else
return query_type_id(name);
}
};
custom_mapper mapper_instance;
WHEN("reading a variant from JSON") {
using value_type = std::variant<int32_t, std::string>;
THEN("the custom mapper translates between external and internal names") {
json_reader reader;
reader.mapper(&mapper_instance);
auto value = value_type{};
auto input1 = R"_({"@value-type": "String", "value": "hello world"})_"s;
if (CHECK(reader.load(input1))) {
if (!CHECK(reader.apply(value)))
MESSAGE("reader reported error: " << reader.get_error());
if (CHECK(std::holds_alternative<std::string>(value)))
CHECK_EQ(std::get<std::string>(value), "hello world"s);
} else {
MESSAGE("reader reported error: " << reader.get_error());
}
reader.reset();
auto input2 = R"_({"@value-type": "Int", "value": 42})_"sv;
if (CHECK(reader.load(input2))) {
if (!CHECK(reader.apply(value)))
MESSAGE("reader reported error: " << reader.get_error());
if (CHECK(std::holds_alternative<int32_t>(value)))
CHECK_EQ(std::get<int32_t>(value), 42);
} else {
MESSAGE("reader reported error: " << reader.get_error());
}
}
}
}
}
END_FIXTURE_SCOPE() END_FIXTURE_SCOPE()
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