Commit e8db2cb1 authored by Dominik Charousset's avatar Dominik Charousset

Add new json_builder abstraction

parent e72b48a3
......@@ -162,6 +162,7 @@ caf_add_component(
src/ipv6_endpoint.cpp
src/ipv6_subnet.cpp
src/json_array.cpp
src/json_builder.cpp
src/json_object.cpp
src/json_reader.cpp
src/json_value.cpp
......@@ -317,6 +318,7 @@ caf_add_component(
ipv6_endpoint
ipv6_subnet
json_array
json_builder
json_object
json_reader
json_value
......
......@@ -27,6 +27,18 @@
namespace caf::detail::json {
// -- utility classes ----------------------------------------------------------
// Wraps a buffer resource with a reference count.
struct CAF_CORE_EXPORT storage : public ref_counted {
/// Provides the memory for all of our parsed JSON entities.
detail::monotonic_buffer_resource buf;
};
// -- helper for modeling the JSON type system ---------------------------------
using storage_ptr = intrusive_ptr<storage>;
struct null_t {};
constexpr bool operator==(null_t, null_t) {
......@@ -287,6 +299,24 @@ bool operator!=(const linked_list<T>& lhs, const linked_list<T>& rhs) {
return !(lhs == rhs);
}
/// Re-allocates the given string at the buffer resource.
CAF_CORE_EXPORT std::string_view realloc(std::string_view str,
monotonic_buffer_resource* res);
inline std::string_view realloc(std::string_view str, const storage_ptr& ptr) {
return realloc(str, &ptr->buf);
}
/// Concatenates all strings and allocates a single new string for the result.
CAF_CORE_EXPORT std::string_view
concat(std::initializer_list<std::string_view> xs,
monotonic_buffer_resource* res);
inline std::string_view concat(std::initializer_list<std::string_view> xs,
const storage_ptr& ptr) {
return concat(xs, &ptr->buf);
}
class CAF_CORE_EXPORT value {
public:
using array = linked_list<value>;
......@@ -354,6 +384,30 @@ public:
bool is_undefined() const noexcept {
return data.index() == undefined_index;
}
void assign_string(std::string_view str, monotonic_buffer_resource* res) {
data = realloc(str, res);
}
void assign_string(std::string_view str, const storage_ptr& ptr) {
data = realloc(str, &ptr->buf);
}
void assign_object(monotonic_buffer_resource* res) {
data = object{object_allocator{res}};
}
void assign_object(const storage_ptr& ptr) {
assign_object(&ptr->buf);
}
void assign_array(monotonic_buffer_resource* res) {
data = array{array_allocator{res}};
}
void assign_array(const storage_ptr& ptr) {
assign_array(&ptr->buf);
}
};
inline bool operator==(const value& lhs, const value& rhs) {
......@@ -381,16 +435,6 @@ using member = value::member;
using object = value::object;
// -- utility classes ----------------------------------------------------------
// Wraps a buffer resource with a reference count.
struct CAF_CORE_EXPORT storage : public ref_counted {
/// Provides the memory for all of our parsed JSON entities.
detail::monotonic_buffer_resource buf;
};
using storage_ptr = intrusive_ptr<storage>;
// -- factory functions --------------------------------------------------------
value* make_value(monotonic_buffer_resource* storage);
......@@ -548,10 +592,7 @@ bool load(Deserializer& source, value& val, monotonic_buffer_resource* res) {
if (tmp.empty()) {
val.data = std::string_view{};
} else {
using alloc_t = detail::monotonic_buffer_resource::allocator<char>;
auto buf = alloc_t{res}.allocate(tmp.size());
strncpy(buf, tmp.data(), tmp.size());
val.data = std::string_view{buf, tmp.size()};
val.assign_string(tmp, res);
}
break;
}
......@@ -589,10 +630,7 @@ bool load(Deserializer& source, object& obj, monotonic_buffer_resource* res) {
if (key.empty()) {
kvp.key = std::string_view{};
} else {
using alloc_t = detail::monotonic_buffer_resource::allocator<char>;
auto buf = alloc_t{res}.allocate(key.size());
strncpy(buf, key.data(), key.size());
kvp.key = std::string_view{buf, key.size()};
kvp.key = realloc(key, res);
}
// Deserialize the value.
kvp.val = make_value(res);
......
......@@ -5,6 +5,7 @@
#pragma once
#include "caf/json_array.hpp"
#include "caf/json_builder.hpp"
#include "caf/json_object.hpp"
#include "caf/json_reader.hpp"
#include "caf/json_value.hpp"
......
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/detail/core_export.hpp"
#include "caf/detail/json.hpp"
#include "caf/json_value.hpp"
#include "caf/json_writer.hpp"
#include "caf/serializer.hpp"
namespace caf {
/// Serializes an inspectable object to a @ref json_value.
class json_builder : public serializer {
public:
// -- member types -----------------------------------------------------------
using super = serializer;
using type = json_writer::type;
// -- constructors, destructors, and assignment operators --------------------
json_builder();
explicit json_builder(actor_system& sys);
explicit json_builder(execution_unit* ctx);
json_builder(const json_builder&) = delete;
json_builder& operator=(const json_builder&) = delete;
~json_builder() override;
// -- properties -------------------------------------------------------------
/// Returns whether the writer omits empty fields entirely (true) or renders
/// empty fields as `$field: null` (false).
[[nodiscard]] bool skip_empty_fields() const noexcept {
return skip_empty_fields_;
}
/// Configures whether the writer omits empty fields.
void skip_empty_fields(bool value) noexcept {
skip_empty_fields_ = value;
}
/// Returns whether the writer omits '@type' annotations for JSON objects.
[[nodiscard]] bool skip_object_type_annotation() const noexcept {
return skip_object_type_annotation_;
}
/// Configures whether the writer omits '@type' annotations for JSON objects.
void skip_object_type_annotation(bool value) noexcept {
skip_object_type_annotation_ = value;
}
/// Returns the suffix for generating type annotation fields for variant
/// fields. For example, CAF inserts field called "@foo${field_type_suffix}"
/// for a variant field called "foo".
[[nodiscard]] std::string_view field_type_suffix() const noexcept {
return field_type_suffix_;
}
/// Configures whether the writer omits empty fields.
void field_type_suffix(std::string_view suffix) noexcept {
field_type_suffix_ = suffix;
}
// -- modifiers --------------------------------------------------------------
/// Restores the writer to its initial state.
void reset();
/// Seals the JSON value, i.e., rendering it immutable, and returns it. After
/// calling this member function, the @ref json_builder is in a moved-from
/// state and users may only call @c reset to start a new building process or
/// destroy this instance.
json_value seal();
// -- overrides --------------------------------------------------------------
bool begin_object(type_id_t type, std::string_view name) override;
bool end_object() override;
bool begin_field(std::string_view) override;
bool begin_field(std::string_view name, bool is_present) override;
bool begin_field(std::string_view name, span<const type_id_t> types,
size_t index) override;
bool begin_field(std::string_view name, bool is_present,
span<const type_id_t> types, size_t index) override;
bool end_field() override;
bool begin_tuple(size_t size) override;
bool end_tuple() override;
bool begin_key_value_pair() override;
bool end_key_value_pair() override;
bool begin_sequence(size_t size) override;
bool end_sequence() override;
bool begin_associative_array(size_t size) override;
bool end_associative_array() override;
bool value(std::byte x) override;
bool value(bool x) override;
bool value(int8_t x) override;
bool value(uint8_t x) override;
bool value(int16_t x) override;
bool value(uint16_t x) override;
bool value(int32_t x) override;
bool value(uint32_t x) override;
bool value(int64_t x) override;
bool value(uint64_t x) override;
bool value(float x) override;
bool value(double x) override;
bool value(long double x) override;
bool value(std::string_view x) override;
bool value(const std::u16string& x) override;
bool value(const std::u32string& x) override;
bool value(span<const std::byte> x) override;
private:
// -- implementation details -------------------------------------------------
template <class T>
bool number(T);
using key_type = std::string_view;
// -- state management -------------------------------------------------------
void init();
// Returns the current top of the stack or `null` if empty.
type top();
// Returns the current top of the stack or `null` if empty.
template <class T = detail::json::value>
T* top_ptr();
// Returns the current top-level object.
detail::json::object* top_obj();
// Enters a new level of nesting.
void push(detail::json::value*, type);
// Enters a new level of nesting with type member.
void push(detail::json::value::member*);
// Enters a new level of nesting with type key.
void push(key_type*);
// Backs up one level of nesting.
bool pop();
// Backs up one level of nesting but checks that current top is `t` before.
bool pop_if(type t);
// Sets an error reason that the inspector failed to write a t.
void fail(type t);
// Checks whether any element in the stack has the type `object`.
bool inside_object() const noexcept;
// -- member variables -------------------------------------------------------
// Our output.
detail::json::value* val_;
// Storage for the assembled output.
detail::json::storage_ptr storage_;
struct entry {
union {
detail::json::value* val_ptr;
detail::json::member* mem_ptr;
key_type* key_ptr;
};
type t;
entry(detail::json::value* ptr, type ptr_type) noexcept {
val_ptr = ptr;
t = ptr_type;
}
explicit entry(detail::json::member* ptr) noexcept {
mem_ptr = ptr;
t = type::member;
}
explicit entry(key_type* ptr) noexcept {
key_ptr = ptr;
t = type::key;
}
entry(const entry&) noexcept = default;
entry& operator=(const entry&) noexcept = default;
};
// Bookkeeping for where we are in the current object.
std::vector<entry> stack_;
// Configures whether we omit empty fields entirely (true) or render empty
// fields as `$field: null` (false).
bool skip_empty_fields_ = json_writer::skip_empty_fields_default;
// Configures whether we omit the top-level '@type' annotation.
bool skip_object_type_annotation_ = false;
std::string_view field_type_suffix_ = json_writer::field_type_suffix_default;
};
} // namespace caf
......@@ -210,7 +210,7 @@ private:
void init();
// Returns the current top of the stack or `null_literal` if empty.
// Returns the current top of the stack or `null` if empty.
type top();
// Enters a new level of nesting.
......@@ -300,4 +300,12 @@ private:
const type_id_mapper* mapper_ = &default_mapper_;
};
/// @relates json_writer::type
CAF_CORE_EXPORT std::string_view as_json_type_name(json_writer::type t);
/// @relates json_writer::type
constexpr bool can_morph(json_writer::type from, json_writer::type to) {
return from == json_writer::type::element && to != json_writer::type::member;
}
} // namespace caf
......@@ -7,6 +7,7 @@
#include <cstring>
#include <iterator>
#include <memory>
#include <numeric>
#include "caf/config.hpp"
#include "caf/detail/parser/chars.hpp"
......@@ -446,6 +447,27 @@ const array empty_array_instance;
} // namespace
std::string_view realloc(std::string_view str, monotonic_buffer_resource* res) {
using alloc_t = detail::monotonic_buffer_resource::allocator<char>;
auto buf = alloc_t{res}.allocate(str.size());
strncpy(buf, str.data(), str.size());
return std::string_view{buf, str.size()};
}
std::string_view concat(std::initializer_list<std::string_view> xs,
monotonic_buffer_resource* res) {
auto get_size = [](size_t x, std::string_view str) { return x + str.size(); };
auto total_size = std::accumulate(xs.begin(), xs.end(), size_t{0}, get_size);
using alloc_t = detail::monotonic_buffer_resource::allocator<char>;
auto* buf = alloc_t{res}.allocate(total_size);
auto* pos = buf;
for (auto str : xs) {
strncpy(pos, str.data(), str.size());
pos += str.size();
}
return std::string_view{buf, total_size};
}
value* make_value(monotonic_buffer_resource* storage) {
return make_impl<value>(storage);
}
......
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#include "caf/json_builder.hpp"
#include "caf/detail/append_hex.hpp"
#include "caf/detail/print.hpp"
#include "caf/json_value.hpp"
#include <type_traits>
using namespace std::literals;
namespace caf {
namespace {
static constexpr const char class_name[] = "caf::json_builder";
} // namespace
// -- implementation details ---------------------------------------------------
template <class T>
bool json_builder::number(T x) {
switch (top()) {
case type::element: {
if constexpr (std::is_floating_point_v<T>) {
top_ptr()->data = static_cast<double>(x);
} else {
top_ptr()->data = static_cast<int64_t>(x);
}
pop();
return true;
}
case type::key: {
std::string str;
detail::print(str, x);
*top_ptr<key_type>() = detail::json::realloc(str, &storage_->buf);
return true;
}
case type::array: {
auto& new_entry = top_ptr<detail::json::array>()->emplace_back();
if constexpr (std::is_floating_point_v<T>) {
new_entry.data = static_cast<double>(x);
} else {
new_entry.data = static_cast<int64_t>(x);
}
return true;
}
default:
fail(type::number);
return false;
}
}
// -- constructors, destructors, and assignment operators ----------------------
json_builder::json_builder() : json_builder(nullptr) {
init();
}
json_builder::json_builder(actor_system& sys) : super(sys) {
init();
}
json_builder::json_builder(execution_unit* ctx) : super(ctx) {
init();
}
json_builder::~json_builder() {
// nop
}
// -- modifiers ----------------------------------------------------------------
void json_builder::reset() {
stack_.clear();
if (!storage_) {
storage_ = make_counted<detail::json::storage>();
} else {
storage_->buf.reclaim();
}
val_ = detail::json::make_value(storage_);
push(val_, type::element);
}
json_value json_builder::seal() {
return json_value{val_, std::move(storage_)};
}
// -- overrides ----------------------------------------------------------------
bool json_builder::begin_object(type_id_t id, std::string_view name) {
auto add_type_annotation = [this, id, name] {
CAF_ASSERT(top() == type::key);
*top_ptr<key_type>() = "@type"sv;
pop();
CAF_ASSERT(top() == type::element);
if (auto tname = query_type_name(id); !tname.empty()) {
top_ptr()->data = tname;
} else {
top_ptr()->data = name;
}
pop();
return true;
};
if (skip_object_type_annotation_ || inside_object())
return begin_associative_array(0);
else
return begin_associative_array(0) // Put opening paren, ...
&& begin_key_value_pair() // ... add implicit @type member, ..
&& add_type_annotation() // ... write content ...
&& end_key_value_pair(); // ... and wait for next field.
}
bool json_builder::end_object() {
return end_associative_array();
}
bool json_builder::begin_field(std::string_view name) {
if (begin_key_value_pair()) {
CAF_ASSERT(top() == type::key);
*top_ptr<key_type>() = name;
pop();
CAF_ASSERT(top() == type::element);
return true;
} else {
return false;
}
}
bool json_builder::begin_field(std::string_view name, bool is_present) {
if (skip_empty_fields_ && !is_present) {
auto t = top();
switch (t) {
case type::object:
push(static_cast<detail::json::member*>(nullptr));
return true;
default: {
std::string str = "expected object, found ";
str += as_json_type_name(t);
emplace_error(sec::runtime_error, class_name, __func__, std::move(str));
return false;
}
}
} else if (begin_key_value_pair()) {
CAF_ASSERT(top() == type::key);
*top_ptr<key_type>() = name;
pop();
CAF_ASSERT(top() == type::element);
if (!is_present) {
// We don't need to assign nullptr explicitly since it's the default.
pop();
}
return true;
} else {
return false;
}
}
bool json_builder::begin_field(std::string_view name,
span<const type_id_t> types, size_t index) {
if (index >= types.size()) {
emplace_error(sec::runtime_error, "index >= types.size()");
return false;
}
if (begin_key_value_pair()) {
if (auto tname = query_type_name(types[index]); !tname.empty()) {
auto& annotation = top_obj()->emplace_back();
annotation.key = detail::json::concat({"@"sv, name, field_type_suffix_},
&storage_->buf);
annotation.val = detail::json::make_value(storage_);
annotation.val->data = tname;
} else {
emplace_error(sec::runtime_error, "query_type_name failed");
return false;
}
CAF_ASSERT(top() == type::key);
*top_ptr<key_type>() = name;
pop();
CAF_ASSERT(top() == type::element);
return true;
} else {
return false;
}
}
bool json_builder::begin_field(std::string_view name, bool is_present,
span<const type_id_t> types, size_t index) {
if (is_present)
return begin_field(name, types, index);
else
return begin_field(name, is_present);
}
bool json_builder::end_field() {
return end_key_value_pair();
}
bool json_builder::begin_tuple(size_t size) {
return begin_sequence(size);
}
bool json_builder::end_tuple() {
return end_sequence();
}
bool json_builder::begin_key_value_pair() {
auto t = top();
switch (t) {
case type::object: {
auto* obj = top_ptr<detail::json::object>();
auto& new_member = obj->emplace_back();
new_member.val = detail::json::make_value(storage_);
push(&new_member);
push(new_member.val, type::element);
push(&new_member.key);
return true;
}
default: {
std::string str = "expected object, found ";
str += as_json_type_name(t);
emplace_error(sec::runtime_error, class_name, __func__, std::move(str));
return false;
}
}
}
bool json_builder::end_key_value_pair() {
return pop_if(type::member);
}
bool json_builder::begin_sequence(size_t) {
switch (top()) {
default:
emplace_error(sec::runtime_error, "unexpected begin_sequence");
return false;
case type::element: {
auto* val = top_ptr();
val->assign_array(storage_);
push(val, type::array);
return true;
}
case type::array: {
auto* arr = top_ptr<detail::json::array>();
auto& new_val = arr->emplace_back();
new_val.assign_array(storage_);
push(&new_val, type::array);
return true;
}
}
}
bool json_builder::end_sequence() {
return pop_if(type::array);
}
bool json_builder::begin_associative_array(size_t) {
switch (top()) {
default:
emplace_error(sec::runtime_error, class_name, __func__,
"unexpected begin_object or begin_associative_array");
return false;
case type::element:
top_ptr()->assign_object(storage_);
stack_.back().t = type::object;
return true;
case type::array: {
auto& new_val = top_ptr<detail::json::array>()->emplace_back();
new_val.assign_object(storage_);
push(&new_val, type::object);
return true;
}
}
}
bool json_builder::end_associative_array() {
return pop_if(type::object);
}
bool json_builder::value(std::byte x) {
return number(std::to_integer<uint8_t>(x));
}
bool json_builder::value(bool x) {
switch (top()) {
case type::element:
top_ptr()->data = x;
pop();
return true;
case type::key:
*top_ptr<key_type>() = x ? "true"sv : "false"sv;
return true;
case type::array: {
auto* arr = top_ptr<detail::json::array>();
arr->emplace_back().data = x;
return true;
}
default:
fail(type::boolean);
return false;
}
}
bool json_builder::value(int8_t x) {
return number(x);
}
bool json_builder::value(uint8_t x) {
return number(x);
}
bool json_builder::value(int16_t x) {
return number(x);
}
bool json_builder::value(uint16_t x) {
return number(x);
}
bool json_builder::value(int32_t x) {
return number(x);
}
bool json_builder::value(uint32_t x) {
return number(x);
}
bool json_builder::value(int64_t x) {
return number(x);
}
bool json_builder::value(uint64_t x) {
return number(x);
}
bool json_builder::value(float x) {
return number(x);
}
bool json_builder::value(double x) {
return number(x);
}
bool json_builder::value(long double x) {
return number(x);
}
bool json_builder::value(std::string_view x) {
switch (top()) {
case type::element:
top_ptr()->assign_string(x, storage_);
pop();
return true;
case type::key:
*top_ptr<key_type>() = detail::json::realloc(x, storage_);
pop();
return true;
case type::array: {
auto& new_val = top_ptr<detail::json::array>()->emplace_back();
new_val.assign_string(x, storage_);
return true;
}
default:
fail(type::string);
return false;
}
}
bool json_builder::value(const std::u16string&) {
emplace_error(sec::unsupported_operation,
"u16string not supported yet by caf::json_builder");
return false;
}
bool json_builder::value(const std::u32string&) {
emplace_error(sec::unsupported_operation,
"u32string not supported yet by caf::json_builder");
return false;
}
bool json_builder::value(span<const std::byte> x) {
std::vector<char> buf;
buf.reserve(x.size() * 2);
detail::append_hex(buf, reinterpret_cast<const void*>(x.data()), x.size());
auto hex_str = std::string_view{buf.data(), buf.size()};
switch (top()) {
case type::element:
top_ptr()->assign_string(hex_str, storage_);
pop();
return true;
case type::key:
*top_ptr<key_type>() = detail::json::realloc(hex_str, storage_);
pop();
return true;
case type::array: {
auto& new_val = top_ptr<detail::json::array>()->emplace_back();
new_val.assign_string(hex_str, storage_);
return true;
}
default:
fail(type::string);
return false;
}
}
// -- state management ---------------------------------------------------------
void json_builder::init() {
has_human_readable_format_ = true;
storage_ = make_counted<detail::json::storage>();
val_ = detail::json::make_value(storage_);
stack_.reserve(32);
push(val_, type::element);
}
json_builder::type json_builder::top() {
if (!stack_.empty())
return stack_.back().t;
else
return type::null;
}
template <class T>
T* json_builder::top_ptr() {
if constexpr (std::is_same_v<T, key_type>) {
return stack_.back().key_ptr;
} else if constexpr (std::is_same_v<T, detail::json::member>) {
return stack_.back().mem_ptr;
} else if constexpr (std::is_same_v<T, detail::json::object>) {
return &std::get<detail::json::object>(stack_.back().val_ptr->data);
} else if constexpr (std::is_same_v<T, detail::json::array>) {
return &std::get<detail::json::array>(stack_.back().val_ptr->data);
} else {
static_assert(std::is_same_v<T, detail::json::value>);
return stack_.back().val_ptr;
}
}
detail::json::object* json_builder::top_obj() {
auto is_obj = [](const entry& x) { return x.t == type::object; };
auto i = std::find_if(stack_.rbegin(), stack_.rend(), is_obj);
if (i != stack_.rend())
return &std::get<detail::json::object>(i->val_ptr->data);
CAF_RAISE_ERROR("json_builder::top_obj was unable to find an object");
}
void json_builder::push(detail::json::value* ptr, type t) {
stack_.emplace_back(ptr, t);
}
void json_builder::push(detail::json::value::member* ptr) {
stack_.emplace_back(ptr);
}
void json_builder::push(key_type* ptr) {
stack_.emplace_back(ptr);
}
// Backs up one level of nesting.
bool json_builder::pop() {
if (!stack_.empty()) {
stack_.pop_back();
return true;
} else {
std::string str = "pop() called with an empty stack: begin/end mismatch";
emplace_error(sec::runtime_error, std::move(str));
return false;
}
}
bool json_builder::pop_if(type t) {
if (!stack_.empty() && stack_.back().t == t) {
stack_.pop_back();
return true;
} else {
std::string str = "pop_if failed: expected ";
str += as_json_type_name(t);
if (stack_.empty()) {
str += ", found an empty stack";
} else {
str += ", found ";
str += as_json_type_name(stack_.back().t);
}
emplace_error(sec::runtime_error, std::move(str));
return false;
}
}
void json_builder::fail(type t) {
std::string str = "failed to write a ";
str += as_json_type_name(t);
str += ": invalid position (begin/end mismatch?)";
emplace_error(sec::runtime_error, std::move(str));
}
bool json_builder::inside_object() const noexcept {
auto is_object = [](const entry& x) { return x.t == type::object; };
return std::any_of(stack_.begin(), stack_.end(), is_object);
}
} // namespace caf
......@@ -13,17 +13,9 @@ namespace {
static constexpr const char class_name[] = "caf::json_writer";
constexpr bool can_morph(json_writer::type from, json_writer::type to) {
return from == json_writer::type::element && to != json_writer::type::member;
}
constexpr const char* json_type_names[] = {"element", "object", "member",
"array", "string", "number",
"bool", "null"};
constexpr const char* json_type_name(json_writer::type t) {
return json_type_names[static_cast<uint8_t>(t)];
}
constexpr std::string_view json_type_names[] = {"element", "object", "member",
"array", "string", "number",
"bool", "null"};
char last_non_ws_char(const std::vector<char>& buf) {
auto not_ws = [](char c) { return !std::isspace(c); };
......@@ -104,7 +96,7 @@ bool json_writer::begin_object(type_id_t id, std::string_view name) {
pop();
return true;
};
if (inside_object() || skip_object_type_annotation_)
if (skip_object_type_annotation_ || inside_object())
return begin_associative_array(0);
else
return begin_associative_array(0) // Put opening paren, ...
......@@ -140,7 +132,7 @@ bool json_writer::begin_field(std::string_view name, bool is_present) {
return true;
default: {
std::string str = "expected object, found ";
str += json_type_name(t);
str += as_json_type_name(t);
emplace_error(sec::runtime_error, class_name, __func__, std::move(str));
return false;
}
......@@ -222,7 +214,7 @@ bool json_writer::begin_key_value_pair() {
return true;
default: {
std::string str = "expected object, found ";
str += json_type_name(t);
str += as_json_type_name(t);
emplace_error(sec::runtime_error, class_name, __func__, std::move(str));
return false;
}
......@@ -489,12 +481,12 @@ bool json_writer::pop_if(type t) {
return true;
} else {
std::string str = "pop_if failed: expected ";
str += json_type_name(t);
str += as_json_type_name(t);
if (stack_.empty()) {
str += ", found an empty stack";
} else {
str += ", found ";
str += json_type_name(stack_.back().t);
str += as_json_type_name(stack_.back().t);
}
emplace_error(sec::runtime_error, std::move(str));
return false;
......@@ -509,13 +501,13 @@ bool json_writer::pop_if_next(type t) {
return true;
} else {
std::string str = "pop_if_next failed: expected ";
str += json_type_name(t);
str += as_json_type_name(t);
if (stack_.size() < 2) {
str += ", found a stack of size ";
detail::print(str, stack_.size());
} else {
str += ", found ";
str += json_type_name(stack_[stack_.size() - 2].t);
str += as_json_type_name(stack_[stack_.size() - 2].t);
}
emplace_error(sec::runtime_error, std::move(str));
return false;
......@@ -536,9 +528,9 @@ bool json_writer::morph(type t, type& prev) {
return true;
} else {
std::string str = "cannot convert ";
str += json_type_name(stack_.back().t);
str += as_json_type_name(stack_.back().t);
str += " to ";
str += json_type_name(t);
str += as_json_type_name(t);
emplace_error(sec::runtime_error, std::move(str));
return false;
}
......@@ -555,7 +547,7 @@ void json_writer::unsafe_morph(type t) {
void json_writer::fail(type t) {
std::string str = "failed to write a ";
str += json_type_name(t);
str += as_json_type_name(t);
str += ": invalid position (begin/end mismatch?)";
emplace_error(sec::runtime_error, std::move(str));
}
......@@ -589,4 +581,10 @@ void json_writer::sep() {
}
}
// -- free functions -----------------------------------------------------------
std::string_view as_json_type_name(json_writer::type t) {
return json_type_names[static_cast<uint8_t>(t)];
}
} // namespace caf
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#define CAF_SUITE json_builder
#include "caf/json_builder.hpp"
#include "core-test.hpp"
#include "caf/json_value.hpp"
#include <string_view>
using namespace caf;
using namespace std::literals;
namespace {
struct fixture {
fixture() {
builder.skip_object_type_annotation(true);
}
std::string printed(const json_value& val, size_t indentation_factor = 0) {
std::string result;
val.print_to(result, indentation_factor);
return result;
}
json_builder builder;
};
} // namespace
BEGIN_FIXTURE_SCOPE(fixture)
TEST_CASE("empty JSON value") {
auto val = builder.seal();
CHECK(val.is_null());
}
TEST_CASE("integer") {
CHECK(builder.value(int32_t{42}));
auto val = builder.seal();
CHECK(val.is_integer());
CHECK_EQ(val.to_integer(), 42);
}
TEST_CASE("floating point") {
CHECK(builder.value(4.2));
auto val = builder.seal();
CHECK(val.is_double());
CHECK_EQ(val.to_double(), 4.2);
}
TEST_CASE("boolean") {
CHECK(builder.value(true));
auto val = builder.seal();
CHECK(val.is_bool());
CHECK_EQ(val.to_bool(), true);
}
TEST_CASE("string") {
CHECK(builder.value("Hello, world!"sv));
auto val = builder.seal();
CHECK(val.is_string());
CHECK_EQ(val.to_string(), "Hello, world!"sv);
}
TEST_CASE("array") {
auto xs = std::vector{1, 2, 3};
CHECK(builder.apply(xs));
auto val = builder.seal();
CHECK(val.is_array());
CHECK_EQ(printed(val), "[1, 2, 3]"sv);
}
TEST_CASE("flat object") {
auto req = my_request{10, 20};
if (!CHECK(builder.apply(req))) {
MESSAGE("builder: " << builder.get_error());
}
auto val = builder.seal();
CHECK(val.is_object());
CHECK_EQ(printed(val), R"_({"a": 10, "b": 20})_");
}
TEST_CASE("flat object with type annotation") {
builder.skip_object_type_annotation(false);
auto req = my_request{10, 20};
if (!CHECK(builder.apply(req))) {
MESSAGE("builder: " << builder.get_error());
}
auto val = builder.seal();
CHECK(val.is_object());
CHECK_EQ(printed(val), R"_({"@type": "my_request", "a": 10, "b": 20})_");
}
namespace {
constexpr std::string_view rect_str = R"_({
"top-left": {
"x": 10,
"y": 10
},
"bottom-right": {
"x": 20,
"y": 20
}
})_";
} // namespace
TEST_CASE("nested object") {
auto rect = rectangle{{10, 10}, {20, 20}};
if (!CHECK(builder.apply(rect))) {
MESSAGE("builder: " << builder.get_error());
}
auto val = builder.seal();
CHECK(val.is_object());
CHECK_EQ(printed(val, 2), rect_str);
}
namespace {
constexpr std::string_view annotated_rect_str = R"_({
"@type": "rectangle",
"top-left": {
"x": 10,
"y": 10
},
"bottom-right": {
"x": 20,
"y": 20
}
})_";
} // namespace
TEST_CASE("nested object with type annotation") {
builder.skip_object_type_annotation(false);
auto rect = rectangle{{10, 10}, {20, 20}};
if (!CHECK(builder.apply(rect))) {
MESSAGE("builder: " << builder.get_error());
}
auto val = builder.seal();
CHECK(val.is_object());
CHECK_EQ(printed(val, 2), annotated_rect_str);
}
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