Commit 0facd386 authored by Dominik Charousset's avatar Dominik Charousset

Add new API for customizing type names in JSON

parent a6583d46
......@@ -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<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
......@@ -251,6 +261,11 @@ private:
// followed by a newline.
void sep();
// Closes a nested structure like lists or objects. Traces back to see if only
// whitespaces were added between the open character and the current position.
// If so, compress the output to produce "[]" instead of "[\n \n]".
void close_nested(char open, char close);
// -- member variables -------------------------------------------------------
// The current level of indentation.
......@@ -280,7 +295,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.
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(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 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(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 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()(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:
string_view operator()(type_id_t type) const override;
type_id_t operator()(string_view name) const override;
};
} // namespace caf
// -- CAF_BEGIN_TYPE_ID_BLOCK --------------------------------------------------
......
......@@ -180,7 +180,7 @@ void json_reader::reset() {
bool json_reader::fetch_next_object_type(type_id_t& type) {
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 {
......@@ -308,7 +308,7 @@ bool json_reader::begin_field(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));
......
......@@ -7,6 +7,8 @@
#include "caf/detail/append_hex.hpp"
#include "caf/detail/print.hpp"
#include <cctype>
namespace caf {
namespace {
......@@ -85,7 +87,7 @@ bool json_writer::begin_object(type_id_t id, 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('"');
......@@ -170,12 +172,12 @@ bool json_writer::begin_field(string_view name, span<const type_id_t> types,
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);
......@@ -247,8 +249,7 @@ bool json_writer::begin_sequence(size_t) {
bool json_writer::end_sequence() {
if (pop_if(type::array)) {
--indentation_level_;
nl();
add(']');
close_nested('[', ']');
return true;
} else {
return false;
......@@ -278,8 +279,7 @@ bool json_writer::begin_associative_array(size_t) {
bool json_writer::end_associative_array() {
if (pop_if(type::object)) {
--indentation_level_;
nl();
add('}');
close_nested('{', '}');
if (!stack_.empty())
stack_.back().filled = true;
return true;
......@@ -570,4 +570,16 @@ void json_writer::sep() {
}
}
void json_writer::close_nested(char open, char close) {
auto not_ws = [](char c) { return !std::isspace(c); };
auto i = std::find_if(buf_.rbegin(), buf_.rend(), not_ws);
if (*i == open) {
while (std::isspace(buf_.back()))
buf_.pop_back();
} else {
nl();
}
add(close);
}
} // namespace caf
......@@ -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(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(string_view name) {
return invalid_type_id;
}
type_id_mapper::~type_id_mapper() {
// nop
}
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()(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 {
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()(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());
}
}
}
}
}
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