Commit a3b6ddc5 authored by Dominik Charousset's avatar Dominik Charousset

Merge branch 'topic/neverlord/json-api'

parents 7168be7a c5bb6a65
...@@ -3,6 +3,13 @@ ...@@ -3,6 +3,13 @@
All notable changes to this project will be documented in this file. The format All notable changes to this project will be documented in this file. The format
is based on [Keep a Changelog](https://keepachangelog.com). is based on [Keep a Changelog](https://keepachangelog.com).
## [Unreleased]
### Added
- The new classes `json_value`, `json_array` and `json_object` allow working
with JSON inputs directly. Actors can also pass around JSON values safely.
## [0.19.0-rc.1] - 2020-10-31 ## [0.19.0-rc.1] - 2020-10-31
### Added ### Added
......
...@@ -161,7 +161,11 @@ caf_add_component( ...@@ -161,7 +161,11 @@ caf_add_component(
src/ipv6_address.cpp src/ipv6_address.cpp
src/ipv6_endpoint.cpp src/ipv6_endpoint.cpp
src/ipv6_subnet.cpp src/ipv6_subnet.cpp
src/json_array.cpp
src/json_builder.cpp
src/json_object.cpp
src/json_reader.cpp src/json_reader.cpp
src/json_value.cpp
src/json_writer.cpp src/json_writer.cpp
src/load_inspector.cpp src/load_inspector.cpp
src/local_actor.cpp src/local_actor.cpp
...@@ -313,7 +317,11 @@ caf_add_component( ...@@ -313,7 +317,11 @@ caf_add_component(
ipv6_address ipv6_address
ipv6_endpoint ipv6_endpoint
ipv6_subnet ipv6_subnet
json_array
json_builder
json_object
json_reader json_reader
json_value
json_writer json_writer
load_inspector load_inspector
logger logger
......
...@@ -4,13 +4,20 @@ ...@@ -4,13 +4,20 @@
#pragma once #pragma once
#include "caf/detail/monotonic_buffer_resource.hpp"
#include "caf/detail/print.hpp"
#include "caf/intrusive_ptr.hpp"
#include "caf/parser_state.hpp"
#include "caf/ref_counted.hpp"
#include "caf/span.hpp"
#include "caf/type_id.hpp"
#include <cstdint> #include <cstdint>
#include <cstring>
#include <iterator>
#include <new>
#include <string_view> #include <string_view>
#include <variant> #include <variant>
#include <vector>
#include "caf/detail/monotonic_buffer_resource.hpp"
#include "caf/parser_state.hpp"
// This JSON abstraction is designed to allocate its entire state in a monotonic // This JSON abstraction is designed to allocate its entire state in a monotonic
// buffer resource. This minimizes memory allocations and also enables us to // buffer resource. This minimizes memory allocations and also enables us to
...@@ -20,25 +27,313 @@ ...@@ -20,25 +27,313 @@
namespace caf::detail::json { 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 {}; struct null_t {};
class value { constexpr bool operator==(null_t, null_t) {
return true;
}
constexpr bool operator!=(null_t, null_t) {
return false;
}
struct undefined_t {};
constexpr bool operator==(undefined_t, undefined_t) {
return true;
}
constexpr bool operator!=(undefined_t, undefined_t) {
return false;
}
template <class T>
struct linked_list_node {
T value;
linked_list_node* next;
};
template <class T>
class linked_list_iterator {
public:
using difference_type = ptrdiff_t;
using value_type = T;
using pointer = value_type*;
using reference = value_type&;
using iterator_category = std::forward_iterator_tag;
using node_pointer
= std::conditional_t<std::is_const_v<T>,
const linked_list_node<std::remove_const_t<T>>*,
linked_list_node<value_type>*>;
constexpr linked_list_iterator() noexcept = default;
constexpr explicit linked_list_iterator(node_pointer ptr) noexcept
: ptr_(ptr) {
// nop
}
constexpr linked_list_iterator(
const linked_list_iterator&) noexcept = default;
constexpr linked_list_iterator&
operator=(const linked_list_iterator&) noexcept = default;
constexpr node_pointer get() const noexcept {
return ptr_;
}
linked_list_iterator& operator++() noexcept {
ptr_ = ptr_->next;
return *this;
}
linked_list_iterator operator++(int) noexcept {
return linked_list_iterator{ptr_->next};
}
T& operator*() const noexcept {
return ptr_->value;
}
T* operator->() const noexcept {
return std::addressof(ptr_->value);
}
private:
node_pointer ptr_ = nullptr;
};
template <class T>
constexpr bool
operator==(linked_list_iterator<T> lhs, linked_list_iterator<T> rhs) {
return lhs.get() == rhs.get();
}
template <class T>
constexpr bool
operator!=(linked_list_iterator<T> lhs, linked_list_iterator<T> rhs) {
return !(lhs == rhs);
}
// A minimal version of a linked list that has constexpr constructor and an
// iterator type where the default-constructed iterator is the past-the-end
// iterator. Properties that std::list unfortunately lacks.
//
// The default-constructed list object is an empty list that does not allow
// push_back.
template <class T>
class linked_list {
public:
using value_type = T;
using node_type = linked_list_node<value_type>;
using allocator_type = monotonic_buffer_resource::allocator<node_type>;
using reference = value_type&;
using const_reference = const value_type&;
using pointer = value_type*;
using const_pointer = const value_type*;
using node_pointer = node_type*;
using iterator = linked_list_iterator<value_type>;
using const_iterator = linked_list_iterator<const value_type>;
linked_list() noexcept {
// nop
}
~linked_list() {
auto* ptr = head_;
while (ptr != nullptr) {
auto* next = ptr->next;
ptr->~node_type();
allocator_.deallocate(ptr, 1);
ptr = next;
}
}
explicit linked_list(allocator_type allocator) noexcept
: allocator_(allocator) {
// nop
}
linked_list(const linked_list&) = delete;
linked_list(linked_list&& other)
: size_(other.size_),
head_(other.head_),
tail_(other.tail_),
allocator_(other.allocator_) {
other.size_ = 0;
other.head_ = nullptr;
other.tail_ = nullptr;
}
linked_list& operator=(const linked_list&) = delete;
linked_list& operator=(linked_list&& other) {
using std::swap;
swap(size_, other.size_);
swap(head_, other.head_);
swap(tail_, other.tail_);
swap(allocator_, other.allocator_);
return *this;
}
[[nodiscard]] bool empty() const noexcept {
return size_ == 0;
}
[[nodiscard]] size_t size() const noexcept {
return size_;
}
[[nodiscard]] iterator begin() noexcept {
return iterator{head_};
}
[[nodiscard]] const_iterator begin() const noexcept {
return const_iterator{head_};
}
[[nodiscard]] const_iterator cbegin() const noexcept {
return begin();
}
[[nodiscard]] iterator end() noexcept {
return {};
}
[[nodiscard]] const_iterator end() const noexcept {
return {};
}
[[nodiscard]] const_iterator cend() const noexcept {
return {};
}
[[nodiscard]] reference front() noexcept {
return head_->value;
}
[[nodiscard]] const_reference front() const noexcept {
return head_->value;
}
[[nodiscard]] reference back() noexcept {
return tail_->value;
}
[[nodiscard]] const_reference back() const noexcept {
return tail_->value;
}
[[nodiscard]] allocator_type get_allocator() const noexcept {
return allocator_;
}
void push_back(T value) {
++size_;
auto new_node = allocator_->allocate(1);
new (new_node) node_type{std::move(value), nullptr};
if (head_ == nullptr) {
head_ = tail_ = new_node;
} else {
tail_->next = new_node;
tail_ = new_node;
}
}
template <class... Ts>
reference emplace_back(Ts&&... args) {
++size_;
auto new_node = allocator_.allocate(1);
new (new_node) node_type{T{std::forward<Ts>(args)...}, nullptr};
if (head_ == nullptr) {
head_ = tail_ = new_node;
} else {
tail_->next = new_node;
tail_ = new_node;
}
return new_node->value;
}
private:
size_t size_ = 0;
node_pointer head_ = nullptr;
node_pointer tail_ = nullptr;
allocator_type allocator_;
};
template <class T>
bool operator==(const linked_list<T>& lhs, const linked_list<T>& rhs) {
return std::equal(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
}
template <class T>
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: public:
using array_allocator = monotonic_buffer_resource::allocator<value>; using array = linked_list<value>;
using array = std::vector<value, array_allocator>; using array_allocator = array::allocator_type;
struct member { struct member {
std::string_view key; std::string_view key;
value* val = nullptr; value* val = nullptr;
}; };
using member_allocator = monotonic_buffer_resource::allocator<member>; using object = linked_list<member>;
using object = std::vector<member, member_allocator>; using object_allocator = object::allocator_type;
using data_type = std::variant<null_t, int64_t, double, bool, using data_type = std::variant<null_t, int64_t, double, bool,
std::string_view, array, object>; std::string_view, array, object, undefined_t>;
static constexpr size_t null_index = 0; static constexpr size_t null_index = 0;
...@@ -54,21 +349,482 @@ public: ...@@ -54,21 +349,482 @@ public:
static constexpr size_t object_index = 6; static constexpr size_t object_index = 6;
static constexpr size_t undefined_index = 7;
data_type data; data_type data;
bool is_null() const noexcept {
return data.index() == null_index;
}
bool is_integer() const noexcept {
return data.index() == integer_index;
}
bool is_double() const noexcept {
return data.index() == double_index;
}
bool is_bool() const noexcept {
return data.index() == bool_index;
}
bool is_string() const noexcept {
return data.index() == string_index;
}
bool is_array() const noexcept {
return data.index() == array_index;
}
bool is_object() const noexcept {
return data.index() == object_index;
}
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) {
return lhs.data == rhs.data;
}
inline bool operator!=(const value& lhs, const value& rhs) {
return !(lhs == rhs);
}
inline bool operator==(const value::member& lhs, const value::member& rhs) {
if (lhs.key == rhs.key && lhs.val != nullptr && rhs.val != nullptr) {
return *lhs.val == *rhs.val;
}
return false;
}
inline bool operator!=(const value::member& lhs, const value::member& rhs) {
return !(lhs == rhs);
}
using array = value::array; using array = value::array;
using member = value::member; using member = value::member;
using object = value::object; using object = value::object;
// -- factory functions --------------------------------------------------------
value* make_value(monotonic_buffer_resource* storage); value* make_value(monotonic_buffer_resource* storage);
inline value* make_value(const storage_ptr& ptr) {
return make_value(&ptr->buf);
}
array* make_array(monotonic_buffer_resource* storage); array* make_array(monotonic_buffer_resource* storage);
inline array* make_array(const storage_ptr& ptr) {
return make_array(&ptr->buf);
}
object* make_object(monotonic_buffer_resource* storage); object* make_object(monotonic_buffer_resource* storage);
inline object* make_object(const storage_ptr& ptr) {
return make_object(&ptr->buf);
}
// -- saving -------------------------------------------------------------------
template <class Serializer>
bool save(Serializer& sink, const object& obj);
template <class Serializer>
bool save(Serializer& sink, const array& arr);
template <class Serializer>
bool save(Serializer& sink, const value& val) {
// On the "wire", we only use the public types.
if (!sink.begin_object(type_id_v<json_value>, type_name_v<json_value>))
return false;
// Maps our type indexes to their public API counterpart.
type_id_t mapping[] = {type_id_v<unit_t>, type_id_v<int64_t>,
type_id_v<double>, type_id_v<bool>,
type_id_v<std::string>, type_id_v<json_array>,
type_id_v<json_object>, type_id_v<none_t>};
// Act as-if this type is a variant of the mapped types.
auto type_index = val.data.index();
if (!sink.begin_field("value", make_span(mapping), type_index))
return false;
// Dispatch on the run-time type of this value.
switch (type_index) {
case value::integer_index:
if (!sink.apply(std::get<int64_t>(val.data)))
return false;
break;
case value::double_index:
if (!sink.apply(std::get<double>(val.data)))
return false;
break;
case value::bool_index:
if (!sink.apply(std::get<bool>(val.data)))
return false;
break;
case value::string_index:
if (!sink.apply(std::get<std::string_view>(val.data)))
return false;
break;
case value::array_index:
if (!save(sink, std::get<array>(val.data)))
return false;
break;
case value::object_index:
if (!save(sink, std::get<object>(val.data)))
return false;
break;
default:
// null and undefined both have no data
break;
}
// Wrap up.
return sink.end_field() && sink.end_object();
}
template <class Serializer>
bool save(Serializer& sink, const object& obj) {
if (!sink.begin_associative_array(obj.size()))
return false;
for (const auto& kvp : obj) {
if (kvp.val != nullptr) {
if (!sink.begin_key_value_pair() // <key-value-pair>
|| !sink.value(kvp.key) // <key ...>
|| !save(sink, *kvp.val) // <value ...>
|| !sink.end_key_value_pair()) // </key-value-pair>
return false;
}
}
return sink.end_associative_array();
}
template <class Serializer>
bool save(Serializer& sink, const array& arr) {
if (!sink.begin_sequence(arr.size()))
return false;
for (const auto& val : arr)
if (!save(sink, val))
return false;
return sink.end_sequence();
}
// -- loading ------------------------------------------------------------------
template <class Deserializer>
bool load(Deserializer& source, object& obj, monotonic_buffer_resource* res);
template <class Deserializer>
bool load(Deserializer& source, array& arr, monotonic_buffer_resource* res);
template <class Deserializer>
bool load(Deserializer& source, value& val, monotonic_buffer_resource* res) {
// On the "wire", we only use the public types.
if (!source.begin_object(type_id_v<json_value>, type_name_v<json_value>))
return false;
// Maps our type indexes to their public API counterpart.
type_id_t mapping[] = {type_id_v<unit_t>, type_id_v<int64_t>,
type_id_v<double>, type_id_v<bool>,
type_id_v<std::string>, type_id_v<json_array>,
type_id_v<json_object>, type_id_v<none_t>};
// Act as-if this type is a variant of the mapped types.
auto type_index = size_t{0};
if (!source.begin_field("value", make_span(mapping), type_index))
return false;
// Dispatch on the run-time type of this value.
switch (type_index) {
case value::null_index:
val.data = null_t{};
break;
case value::integer_index: {
auto tmp = int64_t{0};
if (!source.apply(tmp))
return false;
val.data = tmp;
break;
}
case value::double_index: {
auto tmp = double{0};
if (!source.apply(tmp))
return false;
val.data = tmp;
break;
}
case value::bool_index: {
auto tmp = false;
if (!source.apply(tmp))
return false;
val.data = tmp;
break;
}
case value::string_index: {
auto tmp = std::string{};
if (!source.apply(tmp))
return false;
if (tmp.empty()) {
val.data = std::string_view{};
} else {
val.assign_string(tmp, res);
}
break;
}
case value::array_index:
val.data = array{array::allocator_type{res}};
if (!load(source, std::get<array>(val.data), res))
return false;
break;
case value::object_index:
val.data = object{object::allocator_type{res}};
if (!load(source, std::get<object>(val.data), res))
return false;
break;
default: // undefined
val.data = undefined_t{};
break;
}
// Wrap up.
return source.end_field() && source.end_object();
}
template <class Deserializer>
bool load(Deserializer& source, object& obj, monotonic_buffer_resource* res) {
auto size = size_t{0};
if (!source.begin_associative_array(size))
return false;
for (size_t i = 0; i < size; ++i) {
if (!source.begin_key_value_pair())
return false;
auto& kvp = obj.emplace_back();
// Deserialize the key.
auto key = std::string{};
if (!source.apply(key))
return false;
if (key.empty()) {
kvp.key = std::string_view{};
} else {
kvp.key = realloc(key, res);
}
// Deserialize the value.
kvp.val = make_value(res);
if (!load(source, *kvp.val, res))
return false;
if (!source.end_key_value_pair())
return false;
}
return source.end_associative_array();
}
template <class Deserializer>
bool load(Deserializer& source, array& arr, monotonic_buffer_resource* res) {
auto size = size_t{0};
if (!source.begin_sequence(size))
return false;
for (size_t i = 0; i < size; ++i) {
auto& val = arr.emplace_back();
if (!load(source, val, res))
return false;
}
return source.end_sequence();
}
template <class Deserializer>
bool load(Deserializer& source, object& obj, const storage_ptr& ptr) {
return load(source, obj, std::addressof(ptr->buf));
}
template <class Deserializer>
bool load(Deserializer& source, array& arr, const storage_ptr& ptr) {
return load(source, arr, std::addressof(ptr->buf));
}
template <class Deserializer>
bool load(Deserializer& source, value& val, const storage_ptr& ptr) {
return load(source, val, std::addressof(ptr->buf));
}
// -- singletons ---------------------------------------------------------------
const value* null_value() noexcept;
const value* undefined_value() noexcept;
const object* empty_object() noexcept;
const array* empty_array() noexcept;
// -- parsing ------------------------------------------------------------------
// Specialization for parsers operating on mutable character sequences.
using mutable_string_parser_state = parser_state<char*>;
// Specialization for parsers operating on files.
using file_parser_state = parser_state<std::istreambuf_iterator<char>>;
// Parses the input string and makes a deep copy of all strings.
value* parse(string_parser_state& ps, monotonic_buffer_resource* storage); value* parse(string_parser_state& ps, monotonic_buffer_resource* storage);
// Parses the input string and makes a deep copy of all strings.
value* parse(file_parser_state& ps, monotonic_buffer_resource* storage);
// Parses the input file and makes a deep copy of all strings.
value* parse(string_parser_state& ps, monotonic_buffer_resource* storage);
// Parses the input and makes a shallow copy of strings whenever possible.
// Strings that do not have escaped characters are not copied, other strings
// will be copied.
value* parse_shallow(string_parser_state& ps,
monotonic_buffer_resource* storage);
// Parses the input and makes a shallow copy of all strings. Strings with
// escaped characters are decoded in place.
value* parse_in_situ(mutable_string_parser_state& ps,
monotonic_buffer_resource* storage);
// -- printing -----------------------------------------------------------------
template <class Buffer>
static void print_to(Buffer& buf, std::string_view str) {
buf.insert(buf.end(), str.begin(), str.end());
}
template <class Buffer>
static void print_nl_to(Buffer& buf, size_t indentation) {
buf.push_back('\n');
buf.insert(buf.end(), indentation, ' ');
}
template <class Buffer>
void print_to(Buffer& buf, const array& arr, size_t indentation_factor,
size_t offset = 0);
template <class Buffer>
void print_to(Buffer& buf, const object& obj, size_t indentation_factor,
size_t offset = 0);
template <class Buffer>
void print_to(Buffer& buf, const value& val, size_t indentation_factor,
size_t offset = 0) {
using namespace std::literals;
switch (val.data.index()) {
case value::integer_index:
print(buf, std::get<int64_t>(val.data));
break;
case value::double_index:
print(buf, std::get<double>(val.data));
break;
case value::bool_index:
print(buf, std::get<bool>(val.data));
break;
case value::string_index:
print_escaped(buf, std::get<std::string_view>(val.data));
break;
case value::array_index:
print_to(buf, std::get<array>(val.data), indentation_factor, offset);
break;
case value::object_index:
print_to(buf, std::get<object>(val.data), indentation_factor, offset);
break;
default:
print_to(buf, "null"sv);
}
}
template <class Buffer>
void print_to(Buffer& buf, const array& arr, size_t indentation_factor,
size_t offset) {
using namespace std::literals;
if (arr.empty()) {
print_to(buf, "[]"sv);
} else if (indentation_factor == 0) {
buf.push_back('[');
auto i = arr.begin();
print_to(buf, *i, 0);
auto e = arr.end();
while (++i != e) {
print_to(buf, ", "sv);
print_to(buf, *i, 0);
}
buf.push_back(']');
} else {
buf.push_back('[');
auto new_offset = indentation_factor + offset;
print_nl_to(buf, new_offset);
auto i = arr.begin();
print_to(buf, *i, indentation_factor, new_offset);
auto e = arr.end();
while (++i != e) {
buf.push_back(',');
print_nl_to(buf, new_offset);
print_to(buf, *i, indentation_factor, new_offset);
}
print_nl_to(buf, offset);
buf.push_back(']');
}
}
template <class Buffer>
void print_to(Buffer& buf, const object& obj, size_t indentation_factor,
size_t offset) {
using namespace std::literals;
auto print_member = [&](const value::member& kvp, size_t new_offset) {
print_escaped(buf, kvp.key);
print_to(buf, ": "sv);
print_to(buf, *kvp.val, indentation_factor, new_offset);
};
if (obj.empty()) {
print_to(buf, "{}"sv);
} else if (indentation_factor == 0) {
buf.push_back('{');
auto i = obj.begin();
print_member(*i, offset);
auto e = obj.end();
while (++i != e) {
print_to(buf, ", "sv);
print_member(*i, offset);
}
buf.push_back('}');
} else {
buf.push_back('{');
auto new_offset = indentation_factor + offset;
print_nl_to(buf, new_offset);
auto i = obj.begin();
print_member(*i, new_offset);
auto e = obj.end();
while (++i != e) {
buf.push_back(',');
print_nl_to(buf, new_offset);
print_member(*i, new_offset);
}
print_nl_to(buf, offset);
buf.push_back('}');
}
}
} // namespace caf::detail::json } // namespace caf::detail::json
...@@ -55,20 +55,20 @@ public: ...@@ -55,20 +55,20 @@ public:
using other = allocator<U>; using other = allocator<U>;
}; };
explicit allocator(monotonic_buffer_resource* mbr) : mbr_(mbr) { constexpr explicit allocator(monotonic_buffer_resource* mbr) : mbr_(mbr) {
// nop // nop
} }
allocator() : mbr_(nullptr) { constexpr allocator() : mbr_(nullptr) {
// nop // nop
} }
allocator(const allocator&) = default; constexpr allocator(const allocator&) = default;
allocator& operator=(const allocator&) = default; constexpr allocator& operator=(const allocator&) = default;
template <class U> template <class U>
allocator(const allocator<U>& other) : mbr_(other.resource()) { constexpr allocator(const allocator<U>& other) : mbr_(other.resource()) {
// nop // nop
} }
......
...@@ -110,6 +110,11 @@ class ipv4_subnet; ...@@ -110,6 +110,11 @@ class ipv4_subnet;
class ipv6_address; class ipv6_address;
class ipv6_endpoint; class ipv6_endpoint;
class ipv6_subnet; class ipv6_subnet;
class json_array;
class json_object;
class json_reader;
class json_value;
class json_writer;
class local_actor; class local_actor;
class mailbox_element; class mailbox_element;
class message; class message;
......
// 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/json_array.hpp"
#include "caf/json_builder.hpp"
#include "caf/json_object.hpp"
#include "caf/json_reader.hpp"
#include "caf/json_value.hpp"
#include "caf/json_writer.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/fwd.hpp"
#include "caf/json_value.hpp"
#include <iterator>
#include <memory>
namespace caf {
/// Represents a JSON array.
class CAF_CORE_EXPORT json_array {
public:
// -- friends ----------------------------------------------------------------
friend class json_value;
// -- member types -----------------------------------------------------------
class const_iterator {
public:
using difference_type = ptrdiff_t;
using value_type = json_value;
using pointer = value_type*;
using reference = value_type&;
using iterator_category = std::forward_iterator_tag;
const_iterator(detail::json::value::array::const_iterator iter,
detail::json::storage* storage)
: iter_(iter), storage_(storage) {
// nop
}
const_iterator() noexcept : storage_(nullptr) {
// nop
}
const_iterator(const const_iterator&) = default;
const_iterator& operator=(const const_iterator&) = default;
json_value value() const noexcept {
return json_value{std::addressof(*iter_), storage_};
}
json_value operator*() const noexcept {
return value();
}
const_iterator& operator++() noexcept {
++iter_;
return *this;
}
const_iterator operator++(int) noexcept {
return {iter_++, storage_};
}
bool equal_to(const const_iterator& other) const noexcept {
return iter_ == other.iter_;
}
private:
detail::json::value::array::const_iterator iter_;
detail::json::storage* storage_;
};
// -- constructors, destructors, and assignment operators --------------------
json_array() noexcept : arr_(detail::json::empty_array()) {
// nop
}
json_array(json_array&&) noexcept = default;
json_array(const json_array&) noexcept = default;
json_array& operator=(json_array&&) noexcept = default;
json_array& operator=(const json_array&) noexcept = default;
// -- properties -------------------------------------------------------------
/// Checks whether the array has no members.
bool empty() const noexcept {
return arr_->empty();
}
/// Alias for @c empty.
bool is_empty() const noexcept {
return empty();
}
/// Returns the number of key-value pairs in this array.
size_t size() const noexcept {
return arr_->size();
}
const_iterator begin() const noexcept {
return {arr_->begin(), storage_.get()};
}
const_iterator end() const noexcept {
return {arr_->end(), storage_.get()};
}
// -- printing ---------------------------------------------------------------
template <class Buffer>
void print_to(Buffer& buf, size_t indentation_factor = 0) const {
detail::json::print_to(buf, *arr_, indentation_factor);
}
// -- serialization ----------------------------------------------------------
template <class Inspector>
friend bool inspect(Inspector& inspector, json_array& arr) {
if constexpr (Inspector::is_loading) {
auto storage = make_counted<detail::json::storage>();
auto* internal_arr = detail::json::make_array(storage);
if (!detail::json::load(inspector, *internal_arr, storage))
return false;
arr = json_array{internal_arr, std::move(storage)};
return true;
} else {
return detail::json::save(inspector, *arr.arr_);
}
}
private:
json_array(const detail::json::value::array* obj,
detail::json::storage_ptr sptr) noexcept
: arr_(obj), storage_(sptr) {
// nop
}
const detail::json::value::array* arr_ = nullptr;
detail::json::storage_ptr storage_;
};
// -- free functions -----------------------------------------------------------
inline bool operator==(const json_array::const_iterator& lhs,
const json_array::const_iterator& rhs) noexcept {
return lhs.equal_to(rhs);
}
inline bool operator!=(const json_array::const_iterator& lhs,
const json_array::const_iterator& rhs) noexcept {
return !lhs.equal_to(rhs);
}
inline bool operator==(const json_array& lhs, const json_array& rhs) noexcept {
return std::equal(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
}
inline bool operator!=(const json_array& lhs, const json_array& rhs) noexcept {
return !(lhs == rhs);
}
/// @relates json_array
CAF_CORE_EXPORT std::string to_string(const json_array& arr);
} // 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.
#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
// 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/fwd.hpp"
#include "caf/json_value.hpp"
#include <iterator>
namespace caf {
/// Represents a JSON object.
class CAF_CORE_EXPORT json_object {
public:
// -- friends ----------------------------------------------------------------
friend class json_value;
// -- member types -----------------------------------------------------------
class const_iterator {
public:
using difference_type = ptrdiff_t;
using value_type = std::pair<std::string_view, json_value>;
using pointer = value_type*;
using reference = value_type&;
using iterator_category = std::forward_iterator_tag;
const_iterator(detail::json::value::object::const_iterator iter,
detail::json::storage* storage)
: iter_(iter), storage_(storage) {
// nop
}
const_iterator() noexcept = default;
const_iterator(const const_iterator&) = default;
const_iterator& operator=(const const_iterator&) = default;
std::string_view key() const noexcept {
return iter_->key;
}
json_value value() const noexcept {
return json_value{iter_->val, storage_};
}
value_type operator*() const noexcept {
return {key(), value()};
}
const_iterator& operator++() noexcept {
++iter_;
return *this;
}
const_iterator operator++(int) noexcept {
return {iter_++, storage_};
}
bool equal_to(const const_iterator& other) const noexcept {
return iter_ == other.iter_;
}
private:
detail::json::value::object::const_iterator iter_;
detail::json::storage* storage_ = nullptr;
};
// -- constructors, destructors, and assignment operators --------------------
json_object() noexcept : obj_(detail::json::empty_object()) {
// nop
}
json_object(json_object&&) noexcept = default;
json_object(const json_object&) noexcept = default;
json_object& operator=(json_object&&) noexcept = default;
json_object& operator=(const json_object&) noexcept = default;
// -- properties -------------------------------------------------------------
/// Checks whether the object has no members.
bool empty() const noexcept {
return !obj_ || obj_->empty();
}
/// Alias for @c empty.
bool is_empty() const noexcept {
return empty();
}
/// Returns the number of key-value pairs in this object.
size_t size() const noexcept {
return obj_ ? obj_->size() : 0u;
}
/// Returns the value for @p key or an @c undefined value if the object does
/// not contain a value for @p key.
json_value value(std::string_view key) const;
const_iterator begin() const noexcept {
return {obj_->begin(), storage_.get()};
}
const_iterator end() const noexcept {
return {obj_->end(), storage_.get()};
}
// -- printing ---------------------------------------------------------------
template <class Buffer>
void print_to(Buffer& buf, size_t indentation_factor = 0) const {
detail::json::print_to(buf, *obj_, indentation_factor);
}
// -- serialization ----------------------------------------------------------
template <class Inspector>
friend bool inspect(Inspector& inspector, json_object& obj) {
if constexpr (Inspector::is_loading) {
auto storage = make_counted<detail::json::storage>();
auto* internal_obj = detail::json::make_object(storage);
if (!detail::json::load(inspector, *internal_obj, storage))
return false;
obj = json_object{internal_obj, std::move(storage)};
return true;
} else {
return detail::json::save(inspector, *obj.obj_);
}
}
private:
json_object(const detail::json::value::object* obj,
detail::json::storage_ptr sptr) noexcept
: obj_(obj), storage_(sptr) {
// nop
}
const detail::json::value::object* obj_ = nullptr;
detail::json::storage_ptr storage_;
};
inline bool operator==(const json_object::const_iterator& lhs,
const json_object::const_iterator& rhs) noexcept {
return lhs.equal_to(rhs);
}
inline bool operator!=(const json_object::const_iterator& lhs,
const json_object::const_iterator& rhs) noexcept {
return !lhs.equal_to(rhs);
}
inline bool operator==(const json_object& lhs,
const json_object& rhs) noexcept {
return std::equal(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
}
inline bool operator!=(const json_object& lhs,
const json_object& rhs) noexcept {
return !(lhs == rhs);
}
/// @relates json_object
CAF_CORE_EXPORT std::string to_string(const json_object& val);
} // namespace caf
...@@ -133,6 +133,17 @@ public: ...@@ -133,6 +133,17 @@ public:
/// @note Implicitly calls `reset`. /// @note Implicitly calls `reset`.
bool load(std::string_view json_text); bool load(std::string_view json_text);
/// Parses the content of the file under the given @p path. After loading the
/// content of the JSON file, the reader is ready for attempting to
/// deserialize inspectable objects.
/// @note Implicitly calls `reset`.
bool load_file(const char* path);
/// @copydoc load_file
bool load_file(const std::string& path) {
return load_file(path.c_str());
}
/// Reverts the state of the reader back to where it was after calling `load`. /// Reverts the state of the reader back to where it was after calling `load`.
/// @post The reader is ready for attempting to deserialize another /// @post The reader is ready for attempting to deserialize another
/// inspectable object. /// inspectable object.
......
// 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/fwd.hpp"
#include "caf/make_counted.hpp"
#include <string>
#include <string_view>
namespace caf {
/// Represents an immutable JSON value.
class CAF_CORE_EXPORT json_value {
public:
// -- constructors, destructors, and assignment operators --------------------
json_value() noexcept : val_(detail::json::null_value()) {
// nop
}
json_value(const detail::json::value* val,
detail::json::storage_ptr sptr) noexcept
: val_(val), storage_(sptr) {
// nop
}
json_value(json_value&&) noexcept = default;
json_value(const json_value&) noexcept = default;
json_value& operator=(json_value&&) noexcept = default;
json_value& operator=(const json_value&) noexcept = default;
// -- factories --------------------------------------------------------------
static json_value undefined() noexcept {
return json_value{detail::json::undefined_value(), nullptr};
}
// -- properties -------------------------------------------------------------
/// Checks whether the value is @c null.
bool is_null() const noexcept {
return val_->is_null();
}
/// Checks whether the value is undefined. This special state indicates that a
/// previous key lookup failed.
bool is_undefined() const noexcept {
return val_->is_undefined();
}
/// Checks whether the value is an @c int64_t.
bool is_integer() const noexcept {
return val_->is_integer();
}
/// Checks whether the value is a @c double.
bool is_double() const noexcept {
return val_->is_double();
}
/// Checks whether the value is a number, i.e., an @c int64_t or a @c double.
bool is_number() const noexcept {
return is_integer() || is_double();
}
/// Checks whether the value is a @c bool.
bool is_bool() const noexcept {
return val_->is_bool();
}
/// Checks whether the value is a JSON string (@c std::string_view).
bool is_string() const noexcept {
return val_->is_string();
}
/// Checks whether the value is an JSON array.
bool is_array() const noexcept {
return val_->is_array();
}
/// Checks whether the value is a JSON object.
bool is_object() const noexcept {
return val_->is_object();
}
// -- conversion -------------------------------------------------------------
int64_t to_integer(int64_t fallback = 0) const;
double to_double(double fallback = 0.0) const;
bool to_bool(bool fallback = false) const;
std::string_view to_string() const;
std::string_view to_string(std::string_view fallback) const;
json_array to_array() const;
json_array to_array(json_array fallback) const;
json_object to_object() const;
json_object to_object(json_object fallback) const;
// -- comparison -------------------------------------------------------------
bool equal_to(const json_value& other) const noexcept;
// -- parsing ----------------------------------------------------------------
/// Attempts to parse @p str as JSON input into a self-contained value.
static expected<json_value> parse(std::string_view str);
/// Attempts to parse @p str as JSON input into a value that avoids copies
/// where possible by pointing into @p str.
/// @warning The returned @ref json_value may hold pointers into @p str. Thus,
/// the input *must* outlive the @ref json_value and any other JSON
/// objects created from that value.
static expected<json_value> parse_shallow(std::string_view str);
/// Attempts to parse @p str as JSON input. Decodes JSON in place and points
/// back into the @p str for all strings in the JSON input.
/// @warning The returned @ref json_value may hold pointers into @p str. Thus,
/// the input *must* outlive the @ref json_value and any other JSON
/// objects created from that value.
static expected<json_value> parse_in_situ(std::string& str);
/// Attempts to parse the content of the file at @p path as JSON input into a
/// self-contained value.
static expected<json_value> parse_file(const char* path);
/// @copydoc parse_file
static expected<json_value> parse_file(const std::string& path);
// -- printing ---------------------------------------------------------------
template <class Buffer>
void print_to(Buffer& buf, size_t indentation_factor = 0) const {
detail::json::print_to(buf, *val_, indentation_factor);
}
// -- serialization ----------------------------------------------------------
template <class Inspector>
friend bool inspect(Inspector& inspector, json_value& val) {
if constexpr (Inspector::is_loading) {
auto storage = make_counted<detail::json::storage>();
auto* internal_val = detail::json::make_value(storage);
if (!detail::json::load(inspector, *internal_val, storage))
return false;
val = json_value{internal_val, std::move(storage)};
return true;
} else {
return detail::json::save(inspector, *val.val_);
}
}
private:
const detail::json::value* val_;
detail::json::storage_ptr storage_;
};
// -- free functions -----------------------------------------------------------
inline bool operator==(const json_value& lhs, const json_value& rhs) {
return lhs.equal_to(rhs);
}
inline bool operator!=(const json_value& lhs, const json_value& rhs) {
return !(lhs == rhs);
}
/// @relates json_value
CAF_CORE_EXPORT std::string to_string(const json_value& val);
} // namespace caf
...@@ -210,7 +210,7 @@ private: ...@@ -210,7 +210,7 @@ private:
void init(); 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(); type top();
// Enters a new level of nesting. // Enters a new level of nesting.
...@@ -300,4 +300,12 @@ private: ...@@ -300,4 +300,12 @@ private:
const type_id_mapper* mapper_ = &default_mapper_; 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 } // namespace caf
...@@ -16,6 +16,8 @@ namespace caf { ...@@ -16,6 +16,8 @@ namespace caf {
/// Stores all information necessary for implementing an FSM-based parser. /// Stores all information necessary for implementing an FSM-based parser.
template <class Iterator, class Sentinel> template <class Iterator, class Sentinel>
struct parser_state { struct parser_state {
using iterator_type = Iterator;
/// Current position of the parser. /// Current position of the parser.
Iterator i; Iterator i;
......
...@@ -405,7 +405,6 @@ CAF_BEGIN_TYPE_ID_BLOCK(core_module, 0) ...@@ -405,7 +405,6 @@ CAF_BEGIN_TYPE_ID_BLOCK(core_module, 0)
CAF_ADD_TYPE_ID(core_module, (caf::cow_string)) CAF_ADD_TYPE_ID(core_module, (caf::cow_string))
CAF_ADD_TYPE_ID(core_module, (caf::cow_u16string)) CAF_ADD_TYPE_ID(core_module, (caf::cow_u16string))
CAF_ADD_TYPE_ID(core_module, (caf::cow_u32string)) CAF_ADD_TYPE_ID(core_module, (caf::cow_u32string))
CAF_ADD_TYPE_ID(core_module, (caf::dictionary<caf::config_value>) )
CAF_ADD_TYPE_ID(core_module, (caf::down_msg)) CAF_ADD_TYPE_ID(core_module, (caf::down_msg))
CAF_ADD_TYPE_ID(core_module, (caf::error)) CAF_ADD_TYPE_ID(core_module, (caf::error))
CAF_ADD_TYPE_ID(core_module, (caf::exit_msg)) CAF_ADD_TYPE_ID(core_module, (caf::exit_msg))
...@@ -419,6 +418,9 @@ CAF_BEGIN_TYPE_ID_BLOCK(core_module, 0) ...@@ -419,6 +418,9 @@ CAF_BEGIN_TYPE_ID_BLOCK(core_module, 0)
CAF_ADD_TYPE_ID(core_module, (caf::ipv6_address)) CAF_ADD_TYPE_ID(core_module, (caf::ipv6_address))
CAF_ADD_TYPE_ID(core_module, (caf::ipv6_endpoint)) CAF_ADD_TYPE_ID(core_module, (caf::ipv6_endpoint))
CAF_ADD_TYPE_ID(core_module, (caf::ipv6_subnet)) CAF_ADD_TYPE_ID(core_module, (caf::ipv6_subnet))
CAF_ADD_TYPE_ID(core_module, (caf::json_array))
CAF_ADD_TYPE_ID(core_module, (caf::json_object))
CAF_ADD_TYPE_ID(core_module, (caf::json_value))
CAF_ADD_TYPE_ID(core_module, (caf::message)) CAF_ADD_TYPE_ID(core_module, (caf::message))
CAF_ADD_TYPE_ID(core_module, (caf::message_id)) CAF_ADD_TYPE_ID(core_module, (caf::message_id))
CAF_ADD_TYPE_ID(core_module, (caf::node_down_msg)) CAF_ADD_TYPE_ID(core_module, (caf::node_down_msg))
...@@ -426,6 +428,7 @@ CAF_BEGIN_TYPE_ID_BLOCK(core_module, 0) ...@@ -426,6 +428,7 @@ CAF_BEGIN_TYPE_ID_BLOCK(core_module, 0)
CAF_ADD_TYPE_ID(core_module, (caf::none_t)) CAF_ADD_TYPE_ID(core_module, (caf::none_t))
CAF_ADD_TYPE_ID(core_module, (caf::pec)) CAF_ADD_TYPE_ID(core_module, (caf::pec))
CAF_ADD_TYPE_ID(core_module, (caf::sec)) CAF_ADD_TYPE_ID(core_module, (caf::sec))
CAF_ADD_TYPE_ID(core_module, (caf::settings))
CAF_ADD_TYPE_ID(core_module, (caf::shared_action_ptr)) CAF_ADD_TYPE_ID(core_module, (caf::shared_action_ptr))
CAF_ADD_TYPE_ID(core_module, (caf::stream)) CAF_ADD_TYPE_ID(core_module, (caf::stream))
CAF_ADD_TYPE_ID(core_module, (caf::stream_abort_msg)) CAF_ADD_TYPE_ID(core_module, (caf::stream_abort_msg))
......
...@@ -4,8 +4,11 @@ ...@@ -4,8 +4,11 @@
#include "caf/detail/json.hpp" #include "caf/detail/json.hpp"
#include <cstring>
#include <iterator> #include <iterator>
#include <memory> #include <memory>
#include <numeric>
#include <streambuf>
#include "caf/config.hpp" #include "caf/config.hpp"
#include "caf/detail/parser/chars.hpp" #include "caf/detail/parser/chars.hpp"
...@@ -14,6 +17,7 @@ ...@@ -14,6 +17,7 @@
#include "caf/detail/parser/read_number.hpp" #include "caf/detail/parser/read_number.hpp"
#include "caf/detail/scope_guard.hpp" #include "caf/detail/scope_guard.hpp"
#include "caf/pec.hpp" #include "caf/pec.hpp"
#include "caf/span.hpp"
CAF_PUSH_UNUSED_LABEL_WARNING CAF_PUSH_UNUSED_LABEL_WARNING
...@@ -23,6 +27,115 @@ namespace { ...@@ -23,6 +27,115 @@ namespace {
constexpr size_t max_nesting_level = 128; constexpr size_t max_nesting_level = 128;
size_t do_unescape(const char* i, const char* e, char* out) {
size_t new_size = 0;
while (i != e) {
switch (*i) {
default:
if (i != out) {
*out++ = *i++;
} else {
++out;
++i;
}
++new_size;
break;
case '\\':
if (++i != e) {
switch (*i) {
case '"':
*out++ = '"';
break;
case '\\':
*out++ = '\\';
break;
case 'b':
*out++ = '\b';
break;
case 'f':
*out++ = '\f';
break;
case 'n':
*out++ = '\n';
break;
case 'r':
*out++ = '\r';
break;
case 't':
*out++ = '\t';
break;
case 'v':
*out++ = '\v';
break;
default:
// TODO: support control characters in \uXXXX notation.
*out++ = '?';
}
++i;
++new_size;
}
}
}
return new_size;
}
std::string_view as_str_view(const char* first, const char* last) {
return {first, static_cast<size_t>(last - first)};
}
struct regular_unescaper {
std::string_view operator()(caf::detail::monotonic_buffer_resource* storage,
const char* first, const char* last,
bool is_escaped) const {
auto len = static_cast<size_t>(last - first);
caf::detail::monotonic_buffer_resource::allocator<char> alloc{storage};
auto* str_buf = alloc.allocate(len);
if (!is_escaped) {
strncpy(str_buf, first, len);
return std::string_view{str_buf, len};
}
auto unescaped_size = do_unescape(first, last, str_buf);
return std::string_view{str_buf, unescaped_size};
}
};
struct shallow_unescaper {
std::string_view operator()(caf::detail::monotonic_buffer_resource* storage,
const char* first, const char* last,
bool is_escaped) const {
if (!is_escaped)
return as_str_view(first, last);
caf::detail::monotonic_buffer_resource::allocator<char> alloc{storage};
auto* str_buf = alloc.allocate(static_cast<size_t>(last - first));
auto unescaped_size = do_unescape(first, last, str_buf);
return std::string_view{str_buf, unescaped_size};
}
};
struct in_situ_unescaper {
std::string_view operator()(caf::detail::monotonic_buffer_resource*,
char* first, char* last, bool is_escaped) const {
if (!is_escaped)
return as_str_view(first, last);
auto unescaped_size = do_unescape(first, last, first);
return std::string_view{first, unescaped_size};
}
};
template <class Escaper, class Consumer, class Iterator>
void assign_value(Escaper escaper, Consumer& consumer, Iterator first,
Iterator last, bool is_escaped) {
auto iter2ptr = [](Iterator iter) {
if constexpr (std::is_pointer_v<Iterator>)
return iter;
else
return std::addressof(*iter);
};
auto str = escaper(consumer.storage, iter2ptr(first), iter2ptr(last),
is_escaped);
consumer.value(str);
}
} // namespace } // namespace
namespace caf::detail::parser { namespace caf::detail::parser {
...@@ -46,6 +159,7 @@ struct val_consumer { ...@@ -46,6 +159,7 @@ struct val_consumer {
}; };
struct key_consumer { struct key_consumer {
monotonic_buffer_resource* storage;
std::string_view* ptr; std::string_view* ptr;
void value(std::string_view str) { void value(std::string_view str) {
...@@ -58,7 +172,7 @@ struct member_consumer { ...@@ -58,7 +172,7 @@ struct member_consumer {
json::member* ptr; json::member* ptr;
key_consumer begin_key() { key_consumer begin_key() {
return {std::addressof(ptr->key)}; return {storage, std::addressof(ptr->key)};
} }
val_consumer begin_val() { val_consumer begin_val() {
...@@ -71,8 +185,8 @@ struct obj_consumer { ...@@ -71,8 +185,8 @@ struct obj_consumer {
json::object* ptr; json::object* ptr;
member_consumer begin_member() { member_consumer begin_member() {
ptr->emplace_back(); auto& new_member = ptr->emplace_back();
return {ptr->get_allocator().resource(), std::addressof(ptr->back())}; return {ptr->get_allocator().resource(), &new_member};
} }
}; };
...@@ -80,30 +194,25 @@ struct arr_consumer { ...@@ -80,30 +194,25 @@ struct arr_consumer {
json::array* ptr; json::array* ptr;
val_consumer begin_value() { val_consumer begin_value() {
ptr->emplace_back(); auto& new_element = ptr->emplace_back();
return {ptr->get_allocator().resource(), std::addressof(ptr->back())}; return {ptr->get_allocator().resource(), &new_element};
} }
}; };
arr_consumer val_consumer::begin_array() { arr_consumer val_consumer::begin_array() {
ptr->data = json::array(json::value::array_allocator{storage}); ptr->data = json::array(json::value::array_allocator{storage});
auto& arr = std::get<json::array>(ptr->data); auto& arr = std::get<json::array>(ptr->data);
arr.reserve(16);
return {&arr}; return {&arr};
} }
obj_consumer val_consumer::begin_object() { obj_consumer val_consumer::begin_object() {
ptr->data = json::object(json::value::member_allocator{storage}); ptr->data = json::object(json::value::object_allocator{storage});
auto& obj = std::get<json::object>(ptr->data); auto& obj = std::get<json::object>(ptr->data);
obj.reserve(16);
return {&obj}; return {&obj};
} }
void read_value(string_parser_state& ps, size_t nesting_level, template <class ParserState, class Consumer>
val_consumer consumer); void read_json_null_or_nan(ParserState& ps, Consumer consumer) {
template <class Consumer>
void read_json_null_or_nan(string_parser_state& ps, Consumer consumer) {
enum { nil, is_null, is_nan }; enum { nil, is_null, is_nan };
auto res_type = nil; auto res_type = nil;
auto g = make_scope_guard([&] { auto g = make_scope_guard([&] {
...@@ -141,9 +250,16 @@ void read_json_null_or_nan(string_parser_state& ps, Consumer consumer) { ...@@ -141,9 +250,16 @@ void read_json_null_or_nan(string_parser_state& ps, Consumer consumer) {
// clang-format on // clang-format on
} }
template <class Consumer> // If we have an iterator into a contiguous memory block, we simply store the
void read_json_string(string_parser_state& ps, Consumer consumer) { // iterator position and use the escaper to decide whether we make regular,
std::string_view::iterator first; // shallow or in-situ copies. Otherwise, we use the scratch-space and decode the
// string while parsing.
template <class ParserState, class Unescaper, class Consumer>
void read_json_string(ParserState& ps, unit_t, Unescaper escaper,
Consumer consumer) {
using iterator_t = typename ParserState::iterator_type;
iterator_t first;
// clang-format off // clang-format off
start(); start();
state(init) { state(init) {
...@@ -152,14 +268,17 @@ void read_json_string(string_parser_state& ps, Consumer consumer) { ...@@ -152,14 +268,17 @@ void read_json_string(string_parser_state& ps, Consumer consumer) {
} }
state(read_chars) { state(read_chars) {
transition(escape, '\\') transition(escape, '\\')
transition(done, '"', transition(done, '"', assign_value(escaper, consumer, first, ps.i, false))
consumer.value(std::string_view{
std::addressof(*first), static_cast<size_t>(ps.i - first)}))
transition(read_chars, any_char) transition(read_chars, any_char)
} }
state(read_chars_after_escape) {
transition(escape, '\\')
transition(done, '"', assign_value(escaper, consumer, first, ps.i, true))
transition(read_chars_after_escape, any_char)
}
state(escape) { state(escape) {
// TODO: Add support for JSON's \uXXXX escaping. // TODO: Add support for JSON's \uXXXX escaping.
transition(read_chars, "\"\\/bfnrt") transition(read_chars_after_escape, "\"\\/bfnrtv")
} }
term_state(done) { term_state(done) {
transition(done, " \t\n") transition(done, " \t\n")
...@@ -168,17 +287,57 @@ void read_json_string(string_parser_state& ps, Consumer consumer) { ...@@ -168,17 +287,57 @@ void read_json_string(string_parser_state& ps, Consumer consumer) {
// clang-format on // clang-format on
} }
void read_member(string_parser_state& ps, size_t nesting_level, template <class ParserState, class Unescaper, class Consumer>
void read_json_string(ParserState& ps, std::vector<char>& scratch_space,
Unescaper escaper, Consumer consumer) {
scratch_space.clear();
// clang-format off
start();
state(init) {
transition(init, " \t\n")
transition(read_chars, '"')
}
state(read_chars) {
transition(escape, '\\')
transition(done, '"',
assign_value(escaper, consumer, scratch_space.begin(),
scratch_space.end(), false))
transition(read_chars, any_char, scratch_space.push_back(ch))
}
state(escape) {
// TODO: Add support for JSON's \uXXXX escaping.
transition(read_chars, '"', scratch_space.push_back('"'))
transition(read_chars, '\\', scratch_space.push_back('\\'))
transition(read_chars, 'b', scratch_space.push_back('\b'))
transition(read_chars, 'f', scratch_space.push_back('\f'))
transition(read_chars, 'n', scratch_space.push_back('\n'))
transition(read_chars, 'r', scratch_space.push_back('\r'))
transition(read_chars, 't', scratch_space.push_back('\t'))
transition(read_chars, 'v', scratch_space.push_back('\v'))
}
term_state(done) {
transition(done, " \t\n")
}
fin();
// clang-format on
}
template <class ParserState, class ScratchSpace, class Unescaper>
void read_member(ParserState& ps, ScratchSpace& scratch_space,
Unescaper unescaper, size_t nesting_level,
member_consumer consumer) { member_consumer consumer) {
// clang-format off // clang-format off
start(); start();
state(init) { state(init) {
transition(init, " \t\n") transition(init, " \t\n")
fsm_epsilon(read_json_string(ps, consumer.begin_key()), after_key, '"') fsm_epsilon(read_json_string(ps, scratch_space, unescaper,
consumer.begin_key()),
after_key, '"')
} }
state(after_key) { state(after_key) {
transition(after_key, " \t\n") transition(after_key, " \t\n")
fsm_transition(read_value(ps, nesting_level, consumer.begin_val()), fsm_transition(read_value(ps, scratch_space, unescaper, nesting_level,
consumer.begin_val()),
done, ':') done, ':')
} }
term_state(done) { term_state(done) {
...@@ -188,7 +347,9 @@ void read_member(string_parser_state& ps, size_t nesting_level, ...@@ -188,7 +347,9 @@ void read_member(string_parser_state& ps, size_t nesting_level,
// clang-format on // clang-format on
} }
void read_json_object(string_parser_state& ps, size_t nesting_level, template <class ParserState, class ScratchSpace, class Unescaper>
void read_json_object(ParserState& ps, ScratchSpace& scratch_space,
Unescaper unescaper, size_t nesting_level,
obj_consumer consumer) { obj_consumer consumer) {
if (nesting_level >= max_nesting_level) { if (nesting_level >= max_nesting_level) {
ps.code = pec::nested_too_deeply; ps.code = pec::nested_too_deeply;
...@@ -202,7 +363,8 @@ void read_json_object(string_parser_state& ps, size_t nesting_level, ...@@ -202,7 +363,8 @@ void read_json_object(string_parser_state& ps, size_t nesting_level,
} }
state(has_open_brace) { state(has_open_brace) {
transition(has_open_brace, " \t\n") transition(has_open_brace, " \t\n")
fsm_epsilon(read_member(ps, nesting_level + 1, consumer.begin_member()), fsm_epsilon(read_member(ps, scratch_space, unescaper, nesting_level + 1,
consumer.begin_member()),
after_member, '"') after_member, '"')
transition(done, '}') transition(done, '}')
} }
...@@ -213,7 +375,8 @@ void read_json_object(string_parser_state& ps, size_t nesting_level, ...@@ -213,7 +375,8 @@ void read_json_object(string_parser_state& ps, size_t nesting_level,
} }
state(after_comma) { state(after_comma) {
transition(after_comma, " \t\n") transition(after_comma, " \t\n")
fsm_epsilon(read_member(ps, nesting_level + 1, consumer.begin_member()), fsm_epsilon(read_member(ps, scratch_space, unescaper, nesting_level + 1,
consumer.begin_member()),
after_member, '"') after_member, '"')
} }
term_state(done) { term_state(done) {
...@@ -223,7 +386,9 @@ void read_json_object(string_parser_state& ps, size_t nesting_level, ...@@ -223,7 +386,9 @@ void read_json_object(string_parser_state& ps, size_t nesting_level,
// clang-format on // clang-format on
} }
void read_json_array(string_parser_state& ps, size_t nesting_level, template <class ParserState, class ScratchSpace, class Unescaper>
void read_json_array(ParserState& ps, ScratchSpace& scratch_space,
Unescaper unescaper, size_t nesting_level,
arr_consumer consumer) { arr_consumer consumer) {
if (nesting_level >= max_nesting_level) { if (nesting_level >= max_nesting_level) {
ps.code = pec::nested_too_deeply; ps.code = pec::nested_too_deeply;
...@@ -238,7 +403,8 @@ void read_json_array(string_parser_state& ps, size_t nesting_level, ...@@ -238,7 +403,8 @@ void read_json_array(string_parser_state& ps, size_t nesting_level,
state(has_open_brace) { state(has_open_brace) {
transition(has_open_brace, " \t\n") transition(has_open_brace, " \t\n")
transition(done, ']') transition(done, ']')
fsm_epsilon(read_value(ps, nesting_level + 1, consumer.begin_value()), fsm_epsilon(read_value(ps, scratch_space, unescaper, nesting_level + 1,
consumer.begin_value()),
after_value) after_value)
} }
state(after_value) { state(after_value) {
...@@ -248,7 +414,8 @@ void read_json_array(string_parser_state& ps, size_t nesting_level, ...@@ -248,7 +414,8 @@ void read_json_array(string_parser_state& ps, size_t nesting_level,
} }
state(after_comma) { state(after_comma) {
transition(after_comma, " \t\n") transition(after_comma, " \t\n")
fsm_epsilon(read_value(ps, nesting_level + 1, consumer.begin_value()), fsm_epsilon(read_value(ps, scratch_space, unescaper, nesting_level + 1,
consumer.begin_value()),
after_value) after_value)
} }
term_state(done) { term_state(done) {
...@@ -258,19 +425,24 @@ void read_json_array(string_parser_state& ps, size_t nesting_level, ...@@ -258,19 +425,24 @@ void read_json_array(string_parser_state& ps, size_t nesting_level,
// clang-format on // clang-format on
} }
void read_value(string_parser_state& ps, size_t nesting_level, template <class ParserState, class ScratchSpace, class Unescaper>
void read_value(ParserState& ps, ScratchSpace& scratch_space,
Unescaper unescaper, size_t nesting_level,
val_consumer consumer) { val_consumer consumer) {
// clang-format off // clang-format off
start(); start();
state(init) { state(init) {
transition(init, " \t\n") transition(init, " \t\n")
fsm_epsilon(read_json_string(ps, consumer), done, '"') fsm_epsilon(read_json_string(ps, scratch_space, unescaper, consumer),
done, '"')
fsm_epsilon(read_bool(ps, consumer), done, "ft") fsm_epsilon(read_bool(ps, consumer), done, "ft")
fsm_epsilon(read_json_null_or_nan(ps, consumer), done, "n") fsm_epsilon(read_json_null_or_nan(ps, consumer), done, "n")
fsm_epsilon(read_number(ps, consumer), done, "+-.0123456789") fsm_epsilon(read_number(ps, consumer), done, "+-.0123456789")
fsm_epsilon(read_json_object(ps, nesting_level, consumer.begin_object()), fsm_epsilon(read_json_object(ps, scratch_space, unescaper, nesting_level,
consumer.begin_object()),
done, '{') done, '{')
fsm_epsilon(read_json_array(ps, nesting_level, consumer.begin_array()), fsm_epsilon(read_json_array(ps, scratch_space, unescaper, nesting_level,
consumer.begin_array()),
done, '[') done, '[')
} }
term_state(done) { term_state(done) {
...@@ -288,9 +460,10 @@ namespace caf::detail::json { ...@@ -288,9 +460,10 @@ namespace caf::detail::json {
namespace { namespace {
template <class T, class Allocator> template <class T>
void init(std::vector<T, Allocator>* ptr, monotonic_buffer_resource* storage) { void init(linked_list<T>* ptr, monotonic_buffer_resource* storage) {
new (ptr) std::vector<T, Allocator>(Allocator{storage}); using allocator_type = typename linked_list<T>::allocator_type;
new (ptr) linked_list<T>(allocator_type{storage});
} }
void init(value* ptr, monotonic_buffer_resource*) { void init(value* ptr, monotonic_buffer_resource*) {
...@@ -305,28 +478,103 @@ T* make_impl(monotonic_buffer_resource* storage) { ...@@ -305,28 +478,103 @@ T* make_impl(monotonic_buffer_resource* storage) {
return result; return result;
} }
const value null_value_instance;
const value undefined_value_instance = value{undefined_t{}};
const object empty_object_instance;
const array empty_array_instance;
} // namespace } // 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) { value* make_value(monotonic_buffer_resource* storage) {
return make_impl<value>(storage); return make_impl<value>(storage);
} }
array* make_array(monotonic_buffer_resource* storage) { array* make_array(monotonic_buffer_resource* storage) {
auto result = make_impl<array>(storage); auto result = make_impl<array>(storage);
result->reserve(16);
return result; return result;
} }
object* make_object(monotonic_buffer_resource* storage) { object* make_object(monotonic_buffer_resource* storage) {
auto result = make_impl<object>(storage); auto result = make_impl<object>(storage);
result->reserve(16);
return result; return result;
} }
const value* null_value() noexcept {
return &null_value_instance;
}
const value* undefined_value() noexcept {
return &undefined_value_instance;
}
const object* empty_object() noexcept {
return &empty_object_instance;
}
const array* empty_array() noexcept {
return &empty_array_instance;
}
value* parse(string_parser_state& ps, monotonic_buffer_resource* storage) { value* parse(string_parser_state& ps, monotonic_buffer_resource* storage) {
unit_t scratch_space;
regular_unescaper unescaper;
monotonic_buffer_resource::allocator<value> alloc{storage};
auto result = new (alloc.allocate(1)) value();
parser::read_value(ps, scratch_space, unescaper, 0, {storage, result});
return result;
}
value* parse(file_parser_state& ps, monotonic_buffer_resource* storage) {
std::vector<char> scratch_space;
scratch_space.reserve(64);
regular_unescaper unescaper;
monotonic_buffer_resource::allocator<value> alloc{storage};
auto result = new (alloc.allocate(1)) value();
parser::read_value(ps, scratch_space, unescaper, 0, {storage, result});
return result;
}
value* parse_shallow(string_parser_state& ps,
monotonic_buffer_resource* storage) {
unit_t scratch_space;
shallow_unescaper unescaper;
monotonic_buffer_resource::allocator<value> alloc{storage};
auto result = new (alloc.allocate(1)) value();
parser::read_value(ps, scratch_space, unescaper, 0, {storage, result});
return result;
}
value* parse_in_situ(mutable_string_parser_state& ps,
monotonic_buffer_resource* storage) {
unit_t scratch_space;
in_situ_unescaper unescaper;
monotonic_buffer_resource::allocator<value> alloc{storage}; monotonic_buffer_resource::allocator<value> alloc{storage};
auto result = new (alloc.allocate(1)) value(); auto result = new (alloc.allocate(1)) value();
parser::read_value(ps, 0, {storage, result}); parser::read_value(ps, scratch_space, unescaper, 0, {storage, result});
return result; return result;
} }
......
...@@ -21,6 +21,9 @@ ...@@ -21,6 +21,9 @@
#include "caf/ipv6_address.hpp" #include "caf/ipv6_address.hpp"
#include "caf/ipv6_endpoint.hpp" #include "caf/ipv6_endpoint.hpp"
#include "caf/ipv6_subnet.hpp" #include "caf/ipv6_subnet.hpp"
#include "caf/json_array.hpp"
#include "caf/json_object.hpp"
#include "caf/json_value.hpp"
#include "caf/message.hpp" #include "caf/message.hpp"
#include "caf/message_id.hpp" #include "caf/message_id.hpp"
#include "caf/node_id.hpp" #include "caf/node_id.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.
#include "caf/json_array.hpp"
namespace caf {
std::string to_string(const json_array& arr) {
std::string result;
arr.print_to(result);
return result;
}
} // 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.
#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
// 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_object.hpp"
namespace caf {
// -- properties ---------------------------------------------------------------
json_value json_object::value(std::string_view key) const {
auto pred = [key](const auto& member) { return member.key == key; };
auto i = std::find_if(obj_->begin(), obj_->end(), pred);
if (i != obj_->end()) {
return {i->val, storage_};
}
return json_value::undefined();
}
std::string to_string(const json_object& obj) {
std::string result;
obj.print_to(result);
return result;
}
} // namespace caf
...@@ -8,6 +8,8 @@ ...@@ -8,6 +8,8 @@
#include "caf/detail/print.hpp" #include "caf/detail/print.hpp"
#include "caf/string_algorithms.hpp" #include "caf/string_algorithms.hpp"
#include <fstream>
namespace { namespace {
static constexpr const char class_name[] = "caf::json_reader"; static constexpr const char class_name[] = "caf::json_reader";
...@@ -145,19 +147,41 @@ json_reader::~json_reader() { ...@@ -145,19 +147,41 @@ json_reader::~json_reader() {
bool json_reader::load(std::string_view json_text) { bool json_reader::load(std::string_view json_text) {
reset(); reset();
string_parser_state ps{json_text.begin(), json_text.end()}; string_parser_state ps{json_text.begin(), json_text.end()};
root_ = detail::json::parse_shallow(ps, &buf_);
if (ps.code != pec::success) {
set_error(make_error(ps));
st_ = nullptr;
return false;
}
err_.reset();
detail::monotonic_buffer_resource::allocator<stack_type> alloc{&buf_};
st_ = new (alloc.allocate(1)) stack_type(stack_allocator{&buf_});
st_->reserve(16);
st_->emplace_back(root_);
return true;
}
bool json_reader::load_file(const char* path) {
using iterator_t = std::istreambuf_iterator<char>;
reset();
std::ifstream input{path};
if (!input.is_open()) {
emplace_error(sec::cannot_open_file);
return false;
}
detail::json::file_parser_state ps{iterator_t{input}, iterator_t{}};
root_ = detail::json::parse(ps, &buf_); root_ = detail::json::parse(ps, &buf_);
if (ps.code != pec::success) { if (ps.code != pec::success) {
set_error(make_error(ps)); set_error(make_error(ps));
st_ = nullptr; st_ = nullptr;
return false; return false;
} else {
err_.reset();
detail::monotonic_buffer_resource::allocator<stack_type> alloc{&buf_};
st_ = new (alloc.allocate(1)) stack_type(stack_allocator{&buf_});
st_->reserve(16);
st_->emplace_back(root_);
return true;
} }
err_.reset();
detail::monotonic_buffer_resource::allocator<stack_type> alloc{&buf_};
st_ = new (alloc.allocate(1)) stack_type(stack_allocator{&buf_});
st_->reserve(16);
st_->emplace_back(root_);
return true;
} }
void json_reader::revert() { void json_reader::revert() {
......
// 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_value.hpp"
#include "caf/expected.hpp"
#include "caf/json_array.hpp"
#include "caf/json_object.hpp"
#include "caf/make_counted.hpp"
#include "caf/parser_state.hpp"
#include <fstream>
namespace caf {
// -- conversion ---------------------------------------------------------------
int64_t json_value::to_integer(int64_t fallback) const {
if (is_integer()) {
return std::get<int64_t>(val_->data);
}
if (is_double()) {
return static_cast<int64_t>(std::get<double>(val_->data));
}
return fallback;
}
double json_value::to_double(double fallback) const {
if (is_double()) {
return std::get<double>(val_->data);
}
if (is_integer()) {
return static_cast<double>(std::get<int64_t>(val_->data));
}
return fallback;
}
bool json_value::to_bool(bool fallback) const {
if (is_bool()) {
return std::get<bool>(val_->data);
}
return fallback;
}
std::string_view json_value::to_string() const {
return to_string(std::string_view{});
}
std::string_view json_value::to_string(std::string_view fallback) const {
if (is_string()) {
return std::get<std::string_view>(val_->data);
}
return fallback;
}
json_object json_value::to_object() const {
return to_object(json_object{});
}
json_object json_value::to_object(json_object fallback) const {
if (is_object()) {
return json_object{&std::get<detail::json::object>(val_->data), storage_};
}
return fallback;
}
json_array json_value::to_array() const {
return to_array(json_array{});
}
json_array json_value::to_array(json_array fallback) const {
if (is_array()) {
return json_array{&std::get<detail::json::array>(val_->data), storage_};
}
return fallback;
}
// -- comparison ---------------------------------------------------------------
bool json_value::equal_to(const json_value& other) const noexcept {
if (val_ == other.val_) {
return true;
}
if (val_ != nullptr && other.val_ != nullptr) {
return *val_ == *other.val_;
}
return false;
}
// -- parsing ------------------------------------------------------------------
expected<json_value> json_value::parse(std::string_view str) {
auto storage = make_counted<detail::json::storage>();
string_parser_state ps{str.begin(), str.end()};
auto root = detail::json::parse(ps, &storage->buf);
if (ps.code == pec::success)
return {json_value{root, std::move(storage)}};
return {make_error(ps)};
}
expected<json_value> json_value::parse_shallow(std::string_view str) {
auto storage = make_counted<detail::json::storage>();
string_parser_state ps{str.begin(), str.end()};
auto root = detail::json::parse_shallow(ps, &storage->buf);
if (ps.code == pec::success)
return {json_value{root, std::move(storage)}};
return {make_error(ps)};
}
expected<json_value> json_value::parse_in_situ(std::string& str) {
auto storage = make_counted<detail::json::storage>();
detail::json::mutable_string_parser_state ps{str.data(),
str.data() + str.size()};
auto root = detail::json::parse_in_situ(ps, &storage->buf);
if (ps.code == pec::success)
return {json_value{root, std::move(storage)}};
return {make_error(ps)};
}
expected<json_value> json_value::parse_file(const char* path) {
using iterator_t = std::istreambuf_iterator<char>;
std::ifstream input{path};
if (!input.is_open())
return make_error(sec::cannot_open_file);
auto storage = make_counted<detail::json::storage>();
detail::json::file_parser_state ps{iterator_t{input}, iterator_t{}};
auto root = detail::json::parse(ps, &storage->buf);
if (ps.code == pec::success)
return {json_value{root, std::move(storage)}};
return {make_error(ps)};
}
expected<json_value> json_value::parse_file(const std::string& path) {
return parse_file(path.c_str());
}
// -- free functions -----------------------------------------------------------
std::string to_string(const json_value& val) {
std::string result;
val.print_to(result);
return result;
}
} // namespace caf
...@@ -13,17 +13,9 @@ namespace { ...@@ -13,17 +13,9 @@ namespace {
static constexpr const char class_name[] = "caf::json_writer"; static constexpr const char class_name[] = "caf::json_writer";
constexpr bool can_morph(json_writer::type from, json_writer::type to) { constexpr std::string_view json_type_names[] = {"element", "object", "member",
return from == json_writer::type::element && to != json_writer::type::member; "array", "string", "number",
} "bool", "null"};
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)];
}
char last_non_ws_char(const std::vector<char>& buf) { char last_non_ws_char(const std::vector<char>& buf) {
auto not_ws = [](char c) { return !std::isspace(c); }; 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) { ...@@ -104,7 +96,7 @@ bool json_writer::begin_object(type_id_t id, std::string_view name) {
pop(); pop();
return true; return true;
}; };
if (inside_object() || skip_object_type_annotation_) if (skip_object_type_annotation_ || inside_object())
return begin_associative_array(0); return begin_associative_array(0);
else else
return begin_associative_array(0) // Put opening paren, ... return begin_associative_array(0) // Put opening paren, ...
...@@ -140,7 +132,7 @@ bool json_writer::begin_field(std::string_view name, bool is_present) { ...@@ -140,7 +132,7 @@ bool json_writer::begin_field(std::string_view name, bool is_present) {
return true; return true;
default: { default: {
std::string str = "expected object, found "; 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)); emplace_error(sec::runtime_error, class_name, __func__, std::move(str));
return false; return false;
} }
...@@ -222,7 +214,7 @@ bool json_writer::begin_key_value_pair() { ...@@ -222,7 +214,7 @@ bool json_writer::begin_key_value_pair() {
return true; return true;
default: { default: {
std::string str = "expected object, found "; 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)); emplace_error(sec::runtime_error, class_name, __func__, std::move(str));
return false; return false;
} }
...@@ -489,12 +481,12 @@ bool json_writer::pop_if(type t) { ...@@ -489,12 +481,12 @@ bool json_writer::pop_if(type t) {
return true; return true;
} else { } else {
std::string str = "pop_if failed: expected "; std::string str = "pop_if failed: expected ";
str += json_type_name(t); str += as_json_type_name(t);
if (stack_.empty()) { if (stack_.empty()) {
str += ", found an empty stack"; str += ", found an empty stack";
} else { } else {
str += ", found "; 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)); emplace_error(sec::runtime_error, std::move(str));
return false; return false;
...@@ -509,13 +501,13 @@ bool json_writer::pop_if_next(type t) { ...@@ -509,13 +501,13 @@ bool json_writer::pop_if_next(type t) {
return true; return true;
} else { } else {
std::string str = "pop_if_next failed: expected "; std::string str = "pop_if_next failed: expected ";
str += json_type_name(t); str += as_json_type_name(t);
if (stack_.size() < 2) { if (stack_.size() < 2) {
str += ", found a stack of size "; str += ", found a stack of size ";
detail::print(str, stack_.size()); detail::print(str, stack_.size());
} else { } else {
str += ", found "; 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)); emplace_error(sec::runtime_error, std::move(str));
return false; return false;
...@@ -536,9 +528,9 @@ bool json_writer::morph(type t, type& prev) { ...@@ -536,9 +528,9 @@ bool json_writer::morph(type t, type& prev) {
return true; return true;
} else { } else {
std::string str = "cannot convert "; std::string str = "cannot convert ";
str += json_type_name(stack_.back().t); str += as_json_type_name(stack_.back().t);
str += " to "; str += " to ";
str += json_type_name(t); str += as_json_type_name(t);
emplace_error(sec::runtime_error, std::move(str)); emplace_error(sec::runtime_error, std::move(str));
return false; return false;
} }
...@@ -555,7 +547,7 @@ void json_writer::unsafe_morph(type t) { ...@@ -555,7 +547,7 @@ void json_writer::unsafe_morph(type t) {
void json_writer::fail(type t) { void json_writer::fail(type t) {
std::string str = "failed to write a "; std::string str = "failed to write a ";
str += json_type_name(t); str += as_json_type_name(t);
str += ": invalid position (begin/end mismatch?)"; str += ": invalid position (begin/end mismatch?)";
emplace_error(sec::runtime_error, std::move(str)); emplace_error(sec::runtime_error, std::move(str));
} }
...@@ -589,4 +581,10 @@ void json_writer::sep() { ...@@ -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 } // namespace caf
#pragma once #pragma once
#include "caf/binary_deserializer.hpp"
#include "caf/binary_serializer.hpp"
#include "caf/byte_buffer.hpp"
#include "caf/cow_vector.hpp" #include "caf/cow_vector.hpp"
#include "caf/fwd.hpp" #include "caf/fwd.hpp"
#include "caf/result.hpp" #include "caf/result.hpp"
...@@ -13,6 +16,32 @@ ...@@ -13,6 +16,32 @@
#include <string> #include <string>
#include <utility> #include <utility>
// -- utility for testing serialization round-trips ----------------------------
template <class T>
T deep_copy(const T& val) {
using namespace std::literals;
caf::byte_buffer buf;
{
caf::binary_serializer sink{nullptr, buf};
if (!sink.apply(val)) {
auto msg = "serialization failed in deep_copy: "s;
msg += to_string(sink.get_error());
CAF_RAISE_ERROR(msg.c_str());
}
}
auto result = T{};
{
caf::binary_deserializer sink{nullptr, buf};
if (!sink.apply(result)) {
auto msg = "deserialization failed in deep_copy: "s;
msg += to_string(sink.get_error());
CAF_RAISE_ERROR(msg.c_str());
}
}
return result;
}
// -- forward declarations for all unit test suites ---------------------------- // -- forward declarations for all unit test suites ----------------------------
using float_actor = caf::typed_actor<caf::result<void>(float)>; using float_actor = caf::typed_actor<caf::result<void>(float)>;
......
...@@ -106,6 +106,12 @@ void stringify(std::string& str, size_t, detail::json::null_t) { ...@@ -106,6 +106,12 @@ void stringify(std::string& str, size_t, detail::json::null_t) {
str += "null"; str += "null";
} }
void stringify(std::string& str, size_t, detail::json::undefined_t) {
// The parser never emits undefined objects, but we still need to provide an
// overload for this type.
str += "null";
}
void stringify(std::string& str, size_t indent, const detail::json::array& xs) { void stringify(std::string& str, size_t indent, const detail::json::array& xs) {
if (xs.empty()) { if (xs.empty()) {
str += "[]"; str += "[]";
......
// 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_array
#include "caf/json_array.hpp"
#include "core-test.hpp"
#include "caf/json_array.hpp"
#include "caf/json_value.hpp"
using namespace caf;
using namespace std::literals;
namespace {
std::string printed(const json_array& arr) {
std::string result;
arr.print_to(result, 2);
return result;
}
} // namespace
TEST_CASE("default-constructed") {
auto arr = json_array{};
CHECK(arr.empty());
CHECK(arr.is_empty());
CHECK(arr.begin() == arr.end());
CHECK_EQ(arr.size(), 0u);
CHECK_EQ(to_string(arr), "[]");
CHECK_EQ(printed(arr), "[]");
CHECK_EQ(deep_copy(arr), arr);
}
TEST_CASE("from empty array") {
auto arr = json_value::parse("[]")->to_array();
CHECK(arr.empty());
CHECK(arr.is_empty());
CHECK(arr.begin() == arr.end());
CHECK_EQ(arr.size(), 0u);
CHECK_EQ(to_string(arr), "[]");
CHECK_EQ(printed(arr), "[]");
CHECK_EQ(deep_copy(arr), arr);
}
TEST_CASE("from non-empty array") {
auto arr = json_value::parse(R"_([1, "two", 3.0])_")->to_array();
CHECK(!arr.empty());
CHECK(!arr.is_empty());
CHECK(arr.begin() != arr.end());
REQUIRE_EQ(arr.size(), 3u);
CHECK_EQ((*arr.begin()).to_integer(), 1);
std::vector<json_value> vals;
for (const auto& val : arr) {
vals.emplace_back(val);
}
REQUIRE_EQ(vals.size(), 3u);
CHECK_EQ(vals[0].to_integer(), 1);
CHECK_EQ(vals[1].to_string(), "two");
CHECK_EQ(vals[2].to_double(), 3.0);
CHECK_EQ(to_string(arr), R"_([1, "two", 3])_");
CHECK_EQ(printed(arr), "[\n 1,\n \"two\",\n 3\n]");
CHECK_EQ(deep_copy(arr), arr);
}
// 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()
// 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_object
#include "caf/json_object.hpp"
#include "core-test.hpp"
#include "caf/json_array.hpp"
#include "caf/json_value.hpp"
using namespace caf;
using namespace std::literals;
namespace {
std::string printed(const json_object& obj) {
std::string result;
obj.print_to(result, 2);
return result;
}
} // namespace
TEST_CASE("default-constructed") {
auto obj = json_object{};
CHECK(obj.empty());
CHECK(obj.is_empty());
CHECK(obj.begin() == obj.end());
CHECK_EQ(obj.size(), 0u);
CHECK(obj.value("foo").is_undefined());
CHECK_EQ(to_string(obj), "{}");
CHECK_EQ(printed(obj), "{}");
CHECK_EQ(deep_copy(obj), obj);
}
TEST_CASE("from empty object") {
auto obj = json_value::parse("{}")->to_object();
CHECK(obj.empty());
CHECK(obj.is_empty());
CHECK(obj.begin() == obj.end());
CHECK_EQ(obj.size(), 0u);
CHECK(obj.value("foo").is_undefined());
CHECK_EQ(to_string(obj), "{}");
CHECK_EQ(printed(obj), "{}");
CHECK_EQ(deep_copy(obj), obj);
}
TEST_CASE("from non-empty object") {
auto obj = json_value::parse(R"_({"a": "one", "b": 2})_")->to_object();
CHECK(!obj.empty());
CHECK(!obj.is_empty());
CHECK(obj.begin() != obj.end());
REQUIRE_EQ(obj.size(), 2u);
CHECK_EQ(obj.begin().key(), "a");
CHECK_EQ(obj.begin().value().to_string(), "one");
CHECK_EQ(obj.value("a").to_string(), "one");
CHECK_EQ(obj.value("b").to_integer(), 2);
CHECK(obj.value("c").is_undefined());
std::vector<std::pair<std::string_view, json_value>> vals;
for (const auto& val : obj) {
vals.emplace_back(val);
}
REQUIRE_EQ(vals.size(), 2u);
CHECK_EQ(vals[0].first, "a");
CHECK_EQ(vals[0].second.to_string(), "one");
CHECK_EQ(vals[1].first, "b");
CHECK_EQ(vals[1].second.to_integer(), 2);
CHECK_EQ(to_string(obj), R"_({"a": "one", "b": 2})_");
CHECK_EQ(printed(obj), "{\n \"a\": \"one\",\n \"b\": 2\n}");
CHECK_EQ(deep_copy(obj), obj);
}
// 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_value
#include "caf/json_value.hpp"
#include "core-test.hpp"
#include "caf/binary_deserializer.hpp"
#include "caf/binary_serializer.hpp"
#include "caf/json_array.hpp"
#include "caf/json_object.hpp"
using namespace caf;
using namespace std::literals;
namespace {
std::string printed(const json_value& val) {
std::string result;
val.print_to(result, 2);
return result;
}
} // namespace
TEST_CASE("default-constructed") {
auto val = json_value{};
CHECK(val.is_null());
CHECK(!val.is_undefined());
CHECK(!val.is_integer());
CHECK(!val.is_integer());
CHECK(!val.is_double());
CHECK(!val.is_number());
CHECK(!val.is_bool());
CHECK(!val.is_string());
CHECK(!val.is_array());
CHECK(!val.is_object());
CHECK_EQ(val.to_integer(), 0);
CHECK_EQ(val.to_double(), 0.0);
CHECK_EQ(val.to_bool(), false);
CHECK_EQ(val.to_string(), ""sv);
CHECK_EQ(val.to_object().size(), 0u);
CHECK_EQ(to_string(val), "null");
CHECK_EQ(printed(val), "null");
CHECK_EQ(deep_copy(val), val);
}
TEST_CASE("from undefined") {
auto val = json_value::undefined();
CHECK(!val.is_null());
CHECK(val.is_undefined());
CHECK(!val.is_integer());
CHECK(!val.is_integer());
CHECK(!val.is_double());
CHECK(!val.is_number());
CHECK(!val.is_bool());
CHECK(!val.is_string());
CHECK(!val.is_array());
CHECK(!val.is_object());
CHECK_EQ(val.to_integer(), 0);
CHECK_EQ(val.to_double(), 0.0);
CHECK_EQ(val.to_bool(), false);
CHECK_EQ(val.to_string(), ""sv);
CHECK_EQ(val.to_object().size(), 0u);
CHECK_EQ(to_string(val), "null");
CHECK_EQ(printed(val), "null");
CHECK_EQ(deep_copy(val), val);
}
TEST_CASE("from integer") {
auto val = unbox(json_value::parse("42"));
CHECK(!val.is_null());
CHECK(!val.is_undefined());
CHECK(val.is_integer());
CHECK(!val.is_double());
CHECK(val.is_number());
CHECK(!val.is_bool());
CHECK(!val.is_string());
CHECK(!val.is_array());
CHECK(!val.is_object());
CHECK_EQ(val.to_integer(), 42);
CHECK_EQ(val.to_double(), 42.0);
CHECK_EQ(val.to_bool(), false);
CHECK_EQ(val.to_string(), ""sv);
CHECK_EQ(val.to_object().size(), 0u);
CHECK_EQ(to_string(val), "42");
CHECK_EQ(printed(val), "42");
CHECK_EQ(deep_copy(val), val);
}
TEST_CASE("from double") {
auto val = unbox(json_value::parse("42.0"));
CHECK(!val.is_null());
CHECK(!val.is_undefined());
CHECK(!val.is_integer());
CHECK(val.is_double());
CHECK(val.is_number());
CHECK(!val.is_bool());
CHECK(!val.is_string());
CHECK(!val.is_array());
CHECK(!val.is_object());
CHECK_EQ(val.to_integer(), 42);
CHECK_EQ(val.to_double(), 42.0);
CHECK_EQ(val.to_bool(), false);
CHECK_EQ(val.to_string(), ""sv);
CHECK_EQ(val.to_object().size(), 0u);
CHECK_EQ(to_string(val), "42");
CHECK_EQ(printed(val), "42");
CHECK_EQ(deep_copy(val), val);
}
TEST_CASE("from bool") {
auto val = unbox(json_value::parse("true"));
CHECK(!val.is_null());
CHECK(!val.is_undefined());
CHECK(!val.is_integer());
CHECK(!val.is_double());
CHECK(!val.is_number());
CHECK(val.is_bool());
CHECK(!val.is_string());
CHECK(!val.is_array());
CHECK(!val.is_object());
CHECK_EQ(val.to_integer(), 0);
CHECK_EQ(val.to_double(), 0.0);
CHECK_EQ(val.to_bool(), true);
CHECK_EQ(val.to_string(), ""sv);
CHECK_EQ(val.to_object().size(), 0u);
CHECK_EQ(to_string(val), "true");
CHECK_EQ(printed(val), "true");
CHECK_EQ(deep_copy(val), val);
}
TEST_CASE("from string") {
auto val = unbox(json_value::parse(R"_("Hello, world!")_"));
CHECK(!val.is_null());
CHECK(!val.is_undefined());
CHECK(!val.is_integer());
CHECK(!val.is_double());
CHECK(!val.is_number());
CHECK(!val.is_bool());
CHECK(val.is_string());
CHECK(!val.is_array());
CHECK(!val.is_object());
CHECK_EQ(val.to_integer(), 0);
CHECK_EQ(val.to_double(), 0.0);
CHECK_EQ(val.to_bool(), false);
CHECK_EQ(val.to_string(), "Hello, world!"sv);
CHECK_EQ(val.to_object().size(), 0u);
CHECK_EQ(to_string(val), R"_("Hello, world!")_");
CHECK_EQ(printed(val), R"_("Hello, world!")_");
CHECK_EQ(deep_copy(val), val);
}
TEST_CASE("from empty array") {
auto val = unbox(json_value::parse("[]"));
CHECK(!val.is_null());
CHECK(!val.is_undefined());
CHECK(!val.is_integer());
CHECK(!val.is_double());
CHECK(!val.is_number());
CHECK(!val.is_bool());
CHECK(!val.is_string());
CHECK(val.is_array());
CHECK(!val.is_object());
CHECK_EQ(val.to_integer(), 0);
CHECK_EQ(val.to_double(), 0.0);
CHECK_EQ(val.to_bool(), false);
CHECK_EQ(val.to_string(), ""sv);
CHECK_EQ(val.to_array().size(), 0u);
CHECK_EQ(val.to_object().size(), 0u);
CHECK_EQ(to_string(val), "[]");
CHECK_EQ(printed(val), "[]");
CHECK_EQ(deep_copy(val), val);
}
TEST_CASE("from array of size 1") {
auto val = unbox(json_value::parse("[1]"));
CHECK(!val.is_null());
CHECK(!val.is_undefined());
CHECK(!val.is_integer());
CHECK(!val.is_double());
CHECK(!val.is_number());
CHECK(!val.is_bool());
CHECK(!val.is_string());
CHECK(val.is_array());
CHECK(!val.is_object());
CHECK_EQ(val.to_integer(), 0);
CHECK_EQ(val.to_double(), 0.0);
CHECK_EQ(val.to_bool(), false);
CHECK_EQ(val.to_string(), ""sv);
CHECK_EQ(val.to_array().size(), 1u);
CHECK_EQ(val.to_object().size(), 0u);
CHECK_EQ(to_string(val), "[1]");
CHECK_EQ(printed(val), "[\n 1\n]");
CHECK_EQ(deep_copy(val), val);
}
TEST_CASE("from array of size 3") {
auto val = unbox(json_value::parse("[1, 2, 3]"));
CHECK(!val.is_null());
CHECK(!val.is_undefined());
CHECK(!val.is_integer());
CHECK(!val.is_double());
CHECK(!val.is_number());
CHECK(!val.is_bool());
CHECK(!val.is_string());
CHECK(val.is_array());
CHECK(!val.is_object());
CHECK_EQ(val.to_integer(), 0);
CHECK_EQ(val.to_double(), 0.0);
CHECK_EQ(val.to_bool(), false);
CHECK_EQ(val.to_string(), ""sv);
CHECK_EQ(val.to_array().size(), 3u);
CHECK_EQ(val.to_object().size(), 0u);
CHECK_EQ(to_string(val), "[1, 2, 3]");
CHECK_EQ(printed(val), "[\n 1,\n 2,\n 3\n]");
CHECK_EQ(deep_copy(val), val);
}
TEST_CASE("from empty object") {
auto val = unbox(json_value::parse("{}"));
CHECK(!val.is_null());
CHECK(!val.is_undefined());
CHECK(!val.is_integer());
CHECK(!val.is_double());
CHECK(!val.is_number());
CHECK(!val.is_bool());
CHECK(!val.is_string());
CHECK(!val.is_array());
CHECK(val.is_object());
CHECK_EQ(val.to_integer(), 0);
CHECK_EQ(val.to_double(), 0.0);
CHECK_EQ(val.to_bool(), false);
CHECK_EQ(val.to_string(), ""sv);
CHECK_EQ(val.to_object().size(), 0u);
CHECK_EQ(to_string(val), "{}");
CHECK_EQ(printed(val), "{}");
CHECK_EQ(deep_copy(val), val);
}
TEST_CASE("from non-empty object") {
auto val = unbox(json_value::parse(R"_({"foo": "bar"})_"));
CHECK(!val.is_null());
CHECK(!val.is_undefined());
CHECK(!val.is_integer());
CHECK(!val.is_double());
CHECK(!val.is_number());
CHECK(!val.is_bool());
CHECK(!val.is_string());
CHECK(!val.is_array());
CHECK(val.is_object());
CHECK_EQ(val.to_integer(), 0);
CHECK_EQ(val.to_double(), 0.0);
CHECK_EQ(val.to_bool(), false);
CHECK_EQ(val.to_string(), ""sv);
CHECK_EQ(val.to_object().size(), 1u);
CHECK_EQ(to_string(val), R"_({"foo": "bar"})_");
CHECK_EQ(printed(val), "{\n \"foo\": \"bar\"\n}");
CHECK_EQ(deep_copy(val), val);
}
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