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).
## [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
- Passing a response promise to a run-delayed continuation could result in a
......
......@@ -112,6 +112,16 @@ public:
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 --------------------------------------------------------------
/// Parses @p json_text into an internal representation. After loading the
......@@ -247,6 +257,12 @@ private:
/// Keeps track of the current field for better debugging output.
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
......@@ -115,6 +115,16 @@ public:
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 --------------------------------------------------------------
/// Removes all characters from the buffer and restores the writer to its
......@@ -280,7 +290,14 @@ private:
// Configures whether we omit the top-level '@type' annotation.
bool skip_object_type_annotation_ = false;
// Configures how we generate type annotations for fields.
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
......@@ -4,13 +4,14 @@
#pragma once
#include "caf/detail/core_export.hpp"
#include "caf/fwd.hpp"
#include "caf/timespan.hpp"
#include <chrono>
#include <cstdint>
#include <string>
#include "caf/detail/core_export.hpp"
#include "caf/timespan.hpp"
namespace caf {
/// A portable timestamp with nanosecond resolution anchored at the UNIX epoch.
......@@ -23,6 +24,9 @@ CAF_CORE_EXPORT timestamp make_timestamp();
/// Prints `x` in ISO 8601 format, e.g., `2018-11-15T06:25:01.462`.
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.,
/// `2018-11-15T06:25:01.462`, to `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() {
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.
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);
/// 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
// -- CAF_BEGIN_TYPE_ID_BLOCK --------------------------------------------------
......
......@@ -182,7 +182,7 @@ void json_reader::reset() {
bool json_reader::fetch_next_object_type(type_id_t& type) {
std::string_view 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;
return true;
} else {
......@@ -311,7 +311,7 @@ bool json_reader::begin_field(std::string_view name, bool& is_present,
member != nullptr
&& member->val->data.index() != detail::json::value::null_index) {
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);
i != types.end()) {
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) {
add(R"_("@type": )_");
pop();
CAF_ASSERT(top() == type::element);
if (auto tname = query_type_name(id); !tname.empty()) {
if (auto tname = (*mapper_)(id); !tname.empty()) {
add('"');
add(tname);
add('"');
......@@ -177,12 +177,12 @@ bool json_writer::begin_field(std::string_view name,
pop();
CAF_ASSERT(top() == type::element);
pop();
if (auto tname = query_type_name(types[index]); !tname.empty()) {
if (auto tname = (*mapper_)(types[index]); !tname.empty()) {
add('"');
add(tname);
add('"');
} else {
emplace_error(sec::runtime_error, "query_type_name failed");
emplace_error(sec::runtime_error, "failed to retrieve type name");
return false;
}
return end_key_value_pair() && begin_field(name);
......
......@@ -2,8 +2,10 @@
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#include "caf/deep_to_string.hpp"
#include "caf/timestamp.hpp"
#include "caf/deep_to_string.hpp"
#include "caf/detail/parse.hpp"
#include "caf/expected.hpp"
namespace caf {
......@@ -15,6 +17,14 @@ std::string timestamp_to_string(timestamp x) {
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) {
x += timestamp_to_string(y);
}
......
......@@ -22,4 +22,16 @@ type_id_t query_type_id(std::string_view name) {
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
......@@ -12,6 +12,8 @@
using namespace caf;
using namespace std::literals;
namespace {
struct fixture {
......@@ -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()
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