Commit d0c2877e authored by Dominik Charousset's avatar Dominik Charousset

Add new API for interacting with JSON values

parent 4f44a6c8
......@@ -160,7 +160,9 @@ caf_add_component(
src/ipv6_address.cpp
src/ipv6_endpoint.cpp
src/ipv6_subnet.cpp
src/json_object.cpp
src/json_reader.cpp
src/json_value.cpp
src/json_writer.cpp
src/load_inspector.cpp
src/local_actor.cpp
......@@ -312,7 +314,10 @@ caf_add_component(
ipv6_address
ipv6_endpoint
ipv6_subnet
json_array
json_object
json_reader
json_value
json_writer
load_inspector
logger
......
......@@ -4,13 +4,19 @@
#pragma once
#include "caf/detail/monotonic_buffer_resource.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 <cstring>
#include <iterator>
#include <new>
#include <string_view>
#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
// buffer resource. This minimizes memory allocations and also enables us to
......@@ -22,23 +28,281 @@ namespace caf::detail::json {
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);
}
class CAF_CORE_EXPORT value {
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 {
std::string_view key;
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,
std::string_view, array, object>;
std::string_view, array, object, undefined_t>;
static constexpr size_t null_index = 0;
......@@ -54,21 +318,343 @@ public:
static constexpr size_t object_index = 6;
static constexpr size_t undefined_index = 7;
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;
}
};
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 member = value::member;
using object = value::object;
// -- utility classes ----------------------------------------------------------
// Wraps a buffer resource with a reference count.
struct CAF_CORE_EXPORT storage : public ref_counted {
/// Provides the memory for all of our parsed JSON entities.
detail::monotonic_buffer_resource buf;
};
using storage_ptr = intrusive_ptr<storage>;
// -- factory functions --------------------------------------------------------
value* make_value(monotonic_buffer_resource* storage);
inline value* make_value(const storage_ptr& ptr) {
return make_value(&ptr->buf);
}
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);
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 {
using alloc_t = detail::monotonic_buffer_resource::allocator<char>;
auto buf = alloc_t{res}.allocate(tmp.size());
strncpy(buf, tmp.data(), tmp.size());
val.data = std::string_view{buf, tmp.size()};
}
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 {
using alloc_t = detail::monotonic_buffer_resource::allocator<char>;
auto buf = alloc_t{res}.allocate(key.size());
strncpy(buf, key.data(), key.size());
kvp.key = std::string_view{buf, key.size()};
}
// 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 ------------------------------------------------------------------
// Parses the input 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);
} // namespace caf::detail::json
......@@ -55,20 +55,20 @@ public:
using other = allocator<U>;
};
explicit allocator(monotonic_buffer_resource* mbr) : mbr_(mbr) {
constexpr explicit allocator(monotonic_buffer_resource* mbr) : mbr_(mbr) {
// nop
}
allocator() : mbr_(nullptr) {
constexpr allocator() : mbr_(nullptr) {
// nop
}
allocator(const allocator&) = default;
constexpr allocator(const allocator&) = default;
allocator& operator=(const allocator&) = default;
constexpr allocator& operator=(const allocator&) = default;
template <class U>
allocator(const allocator<U>& other) : mbr_(other.resource()) {
constexpr allocator(const allocator<U>& other) : mbr_(other.resource()) {
// nop
}
......
......@@ -108,6 +108,11 @@ class ipv4_subnet;
class ipv6_address;
class ipv6_endpoint;
class ipv6_subnet;
class json_array;
class json_object;
class json_reader;
class json_value;
class json_writer;
class local_actor;
class mailbox_element;
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_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()};
}
// -- 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_;
};
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);
}
} // 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()};
}
// -- 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);
}
} // 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/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);
// -- 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_;
};
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);
}
} // namespace caf
......@@ -16,6 +16,8 @@ namespace caf {
/// Stores all information necessary for implementing an FSM-based parser.
template <class Iterator, class Sentinel>
struct parser_state {
using iterator_type = Iterator;
/// Current position of the parser.
Iterator i;
......@@ -139,4 +141,7 @@ auto make_error(const parser_state<Iterator, Sentinel>& ps, Ts&&... xs)
/// Specialization for parsers operating on string views.
using string_parser_state = parser_state<std::string_view::iterator>;
/// Specialization for parsers operating on mutable character sequences.
using mutable_string_parser_state = parser_state<char*>;
} // 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/actor_control_block.hpp"
#include "caf/type_id.hpp"
namespace caf {
/// @relates local_actor
/// Default handler function that leaves messages in the mailbox.
/// Can also be used inside custom message handlers to signalize
/// skipping to the runtime.
class CAF_CORE_EXPORT stream {
public:
type_id_t type() const noexcept {
return type_;
}
private:
type_id_t type_;
strong_actor_ptr source_;
uint64_t id_;
};
} // namespace caf
......@@ -383,7 +383,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_u16string))
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::error))
CAF_ADD_TYPE_ID(core_module, (caf::exit_msg))
......@@ -397,6 +396,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_endpoint))
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_id))
CAF_ADD_TYPE_ID(core_module, (caf::node_down_msg))
......@@ -404,6 +406,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::pec))
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::strong_actor_ptr))
CAF_ADD_TYPE_ID(core_module, (caf::timespan))
......
// 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/actor_control_block.hpp"
namespace caf {
/// @relates local_actor
/// Default handler function that leaves messages in the mailbox.
/// Can also be used inside custom message handlers to signalize
/// skipping to the runtime.
template <class T>
class typed_stream {
public:
private:
strong_actor_ptr source_;
uint64_t id_;
};
} // namespace caf
......@@ -4,6 +4,7 @@
#include "caf/detail/json.hpp"
#include <cstring>
#include <iterator>
#include <memory>
......@@ -14,6 +15,7 @@
#include "caf/detail/parser/read_number.hpp"
#include "caf/detail/scope_guard.hpp"
#include "caf/pec.hpp"
#include "caf/span.hpp"
CAF_PUSH_UNUSED_LABEL_WARNING
......@@ -23,6 +25,116 @@ namespace {
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;
}
struct regular_unescaper {
std::string_view unescape(caf::detail::monotonic_buffer_resource* storage,
std::string_view str, bool is_escaped) const {
caf::detail::monotonic_buffer_resource::allocator<char> alloc{storage};
auto* str_buf = alloc.allocate(str.size());
if (!is_escaped) {
strncpy(str_buf, str.data(), str.size());
return std::string_view{str_buf, str.size()};
}
auto unescaped_size = do_unescape(str.data(), str.data() + str.size(),
str_buf);
return std::string_view{str_buf, unescaped_size};
}
template <class Consumer>
void assign(Consumer& f, const char* str, size_t len, bool is_escaped) const {
auto val = std::string_view{str, len};
f.value(unescape(f.storage, val, is_escaped));
}
};
struct shallow_unescaper {
std::string_view unescape(caf::detail::monotonic_buffer_resource* storage,
std::string_view str, bool is_escaped) const {
if (!is_escaped) {
return str;
}
caf::detail::monotonic_buffer_resource::allocator<char> alloc{storage};
auto* str_buf = alloc.allocate(str.size());
auto unescaped_size = do_unescape(str.data(), str.data() + str.size(),
str_buf);
return std::string_view{str_buf, unescaped_size};
}
template <class Consumer>
void assign(Consumer& f, const char* str, size_t len, bool is_escaped) const {
auto val = std::string_view{str, len};
f.value(unescape(f.storage, val, is_escaped));
}
};
struct in_situ_unescaper {
std::string_view unescape(caf::span<char> str, bool is_escaped) const {
if (!is_escaped) {
return std::string_view{str.data(), str.size()};
}
auto unescaped_size = do_unescape(str.data(), str.data() + str.size(),
str.data());
return std::string_view{str.data(), unescaped_size};
}
template <class Consumer>
void assign(Consumer& f, char* str, size_t len, bool is_escaped) const {
auto val = caf::span<char>{str, len};
f.value(unescape(val, is_escaped));
}
};
} // namespace
namespace caf::detail::parser {
......@@ -46,6 +158,7 @@ struct val_consumer {
};
struct key_consumer {
monotonic_buffer_resource* storage;
std::string_view* ptr;
void value(std::string_view str) {
......@@ -58,7 +171,7 @@ struct member_consumer {
json::member* ptr;
key_consumer begin_key() {
return {std::addressof(ptr->key)};
return {storage, std::addressof(ptr->key)};
}
val_consumer begin_val() {
......@@ -71,8 +184,8 @@ struct obj_consumer {
json::object* ptr;
member_consumer begin_member() {
ptr->emplace_back();
return {ptr->get_allocator().resource(), std::addressof(ptr->back())};
auto& new_member = ptr->emplace_back();
return {ptr->get_allocator().resource(), &new_member};
}
};
......@@ -80,30 +193,28 @@ struct arr_consumer {
json::array* ptr;
val_consumer begin_value() {
ptr->emplace_back();
return {ptr->get_allocator().resource(), std::addressof(ptr->back())};
auto& new_element = ptr->emplace_back();
return {ptr->get_allocator().resource(), &new_element};
}
};
arr_consumer val_consumer::begin_array() {
ptr->data = json::array(json::value::array_allocator{storage});
auto& arr = std::get<json::array>(ptr->data);
arr.reserve(16);
return {&arr};
}
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);
obj.reserve(16);
return {&obj};
}
void read_value(string_parser_state& ps, size_t nesting_level,
val_consumer consumer);
template <class Consumer>
void read_json_null_or_nan(string_parser_state& ps, Consumer consumer) {
template <class ParserState, class Consumer>
void read_json_null_or_nan(ParserState& ps, Consumer consumer) {
enum { nil, is_null, is_nan };
auto res_type = nil;
auto g = make_scope_guard([&] {
......@@ -141,9 +252,9 @@ void read_json_null_or_nan(string_parser_state& ps, Consumer consumer) {
// clang-format on
}
template <class Consumer>
void read_json_string(string_parser_state& ps, Consumer consumer) {
std::string_view::iterator first;
template <class ParserState, class Unescaper, class Consumer>
void read_json_string(ParserState& ps, Unescaper escaper, Consumer consumer) {
typename ParserState::iterator_type first;
// clang-format off
start();
state(init) {
......@@ -153,13 +264,20 @@ void read_json_string(string_parser_state& ps, Consumer consumer) {
state(read_chars) {
transition(escape, '\\')
transition(done, '"',
consumer.value(std::string_view{
std::addressof(*first), static_cast<size_t>(ps.i - first)}))
escaper.assign(consumer, std::addressof(*first),
static_cast<size_t>(ps.i - first), false))
transition(read_chars, any_char)
}
state(read_chars_after_escape) {
transition(escape, '\\')
transition(done, '"',
escaper.assign(consumer, std::addressof(*first),
static_cast<size_t>(ps.i - first), true))
transition(read_chars_after_escape, any_char)
}
state(escape) {
// TODO: Add support for JSON's \uXXXX escaping.
transition(read_chars, "\"\\/bfnrt")
transition(read_chars_after_escape, "\"\\/bfnrt")
}
term_state(done) {
transition(done, " \t\n")
......@@ -168,17 +286,20 @@ void read_json_string(string_parser_state& ps, Consumer consumer) {
// clang-format on
}
void read_member(string_parser_state& ps, size_t nesting_level,
template <class ParserState, class Unescaper>
void read_member(ParserState& ps, Unescaper unescaper, size_t nesting_level,
member_consumer consumer) {
// clang-format off
start();
state(init) {
transition(init, " \t\n")
fsm_epsilon(read_json_string(ps, consumer.begin_key()), after_key, '"')
fsm_epsilon(read_json_string(ps, unescaper, consumer.begin_key()),
after_key, '"')
}
state(after_key) {
transition(after_key, " \t\n")
fsm_transition(read_value(ps, nesting_level, consumer.begin_val()),
fsm_transition(read_value(ps, unescaper, nesting_level,
consumer.begin_val()),
done, ':')
}
term_state(done) {
......@@ -188,8 +309,9 @@ void read_member(string_parser_state& ps, size_t nesting_level,
// clang-format on
}
void read_json_object(string_parser_state& ps, size_t nesting_level,
obj_consumer consumer) {
template <class ParserState, class Unescaper>
void read_json_object(ParserState& ps, Unescaper unescaper,
size_t nesting_level, obj_consumer consumer) {
if (nesting_level >= max_nesting_level) {
ps.code = pec::nested_too_deeply;
return;
......@@ -202,7 +324,8 @@ void read_json_object(string_parser_state& ps, size_t nesting_level,
}
state(has_open_brace) {
transition(has_open_brace, " \t\n")
fsm_epsilon(read_member(ps, nesting_level + 1, consumer.begin_member()),
fsm_epsilon(read_member(ps, unescaper, nesting_level + 1,
consumer.begin_member()),
after_member, '"')
transition(done, '}')
}
......@@ -213,7 +336,8 @@ void read_json_object(string_parser_state& ps, size_t nesting_level,
}
state(after_comma) {
transition(after_comma, " \t\n")
fsm_epsilon(read_member(ps, nesting_level + 1, consumer.begin_member()),
fsm_epsilon(read_member(ps, unescaper, nesting_level + 1,
consumer.begin_member()),
after_member, '"')
}
term_state(done) {
......@@ -223,7 +347,8 @@ void read_json_object(string_parser_state& ps, size_t nesting_level,
// clang-format on
}
void read_json_array(string_parser_state& ps, size_t nesting_level,
template <class ParserState, class Unescaper>
void read_json_array(ParserState& ps, Unescaper unescaper, size_t nesting_level,
arr_consumer consumer) {
if (nesting_level >= max_nesting_level) {
ps.code = pec::nested_too_deeply;
......@@ -238,7 +363,8 @@ void read_json_array(string_parser_state& ps, size_t nesting_level,
state(has_open_brace) {
transition(has_open_brace, " \t\n")
transition(done, ']')
fsm_epsilon(read_value(ps, nesting_level + 1, consumer.begin_value()),
fsm_epsilon(read_value(ps, unescaper, nesting_level + 1,
consumer.begin_value()),
after_value)
}
state(after_value) {
......@@ -248,7 +374,8 @@ void read_json_array(string_parser_state& ps, size_t nesting_level,
}
state(after_comma) {
transition(after_comma, " \t\n")
fsm_epsilon(read_value(ps, nesting_level + 1, consumer.begin_value()),
fsm_epsilon(read_value(ps, unescaper, nesting_level + 1,
consumer.begin_value()),
after_value)
}
term_state(done) {
......@@ -258,19 +385,22 @@ void read_json_array(string_parser_state& ps, size_t nesting_level,
// clang-format on
}
void read_value(string_parser_state& ps, size_t nesting_level,
template <class ParserState, class Unescaper>
void read_value(ParserState& ps, Unescaper unescaper, size_t nesting_level,
val_consumer consumer) {
// clang-format off
start();
state(init) {
transition(init, " \t\n")
fsm_epsilon(read_json_string(ps, consumer), done, '"')
fsm_epsilon(read_json_string(ps, unescaper, consumer), done, '"')
fsm_epsilon(read_bool(ps, consumer), done, "ft")
fsm_epsilon(read_json_null_or_nan(ps, consumer), done, "n")
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, unescaper, nesting_level,
consumer.begin_object()),
done, '{')
fsm_epsilon(read_json_array(ps, nesting_level, consumer.begin_array()),
fsm_epsilon(read_json_array(ps, unescaper, nesting_level,
consumer.begin_array()),
done, '[')
}
term_state(done) {
......@@ -288,9 +418,10 @@ namespace caf::detail::json {
namespace {
template <class T, class Allocator>
void init(std::vector<T, Allocator>* ptr, monotonic_buffer_resource* storage) {
new (ptr) std::vector<T, Allocator>(Allocator{storage});
template <class T>
void init(linked_list<T>* ptr, monotonic_buffer_resource* 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*) {
......@@ -305,6 +436,14 @@ T* make_impl(monotonic_buffer_resource* storage) {
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
value* make_value(monotonic_buffer_resource* storage) {
......@@ -313,20 +452,53 @@ value* make_value(monotonic_buffer_resource* storage) {
array* make_array(monotonic_buffer_resource* storage) {
auto result = make_impl<array>(storage);
result->reserve(16);
return result;
}
object* make_object(monotonic_buffer_resource* storage) {
auto result = make_impl<object>(storage);
result->reserve(16);
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) {
regular_unescaper unescaper;
monotonic_buffer_resource::allocator<value> alloc{storage};
auto result = new (alloc.allocate(1)) value();
parser::read_value(ps, unescaper, 0, {storage, result});
return result;
}
value* parse_shallow(string_parser_state& ps,
monotonic_buffer_resource* storage) {
shallow_unescaper unescaper;
monotonic_buffer_resource::allocator<value> alloc{storage};
auto result = new (alloc.allocate(1)) value();
parser::read_value(ps, unescaper, 0, {storage, result});
return result;
}
value* parse_in_situ(mutable_string_parser_state& ps,
monotonic_buffer_resource* storage) {
in_situ_unescaper unescaper;
monotonic_buffer_resource::allocator<value> alloc{storage};
auto result = new (alloc.allocate(1)) value();
parser::read_value(ps, 0, {storage, result});
parser::read_value(ps, unescaper, 0, {storage, result});
return result;
}
......
......@@ -21,6 +21,9 @@
#include "caf/ipv6_address.hpp"
#include "caf/ipv6_endpoint.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_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_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();
}
} // namespace caf
......@@ -145,7 +145,7 @@ json_reader::~json_reader() {
bool json_reader::load(std::string_view json_text) {
reset();
string_parser_state ps{json_text.begin(), json_text.end()};
root_ = detail::json::parse(ps, &buf_);
root_ = detail::json::parse_shallow(ps, &buf_);
if (ps.code != pec::success) {
set_error(make_error(ps));
st_ = nullptr;
......
// 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"
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 {make_error(ps)};
} else {
return {json_value{root, std::move(storage)}};
}
}
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 {make_error(ps)};
} else {
return {json_value{root, std::move(storage)}};
}
}
expected<json_value> json_value::parse_in_situ(std::string& str) {
auto storage = make_counted<detail::json::storage>();
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 {make_error(ps)};
} else {
return {json_value{root, std::move(storage)}};
}
}
} // namespace caf
#include "caf/binary_deserializer.hpp"
#include "caf/binary_serializer.hpp"
#include "caf/byte_buffer.hpp"
#include "caf/cow_vector.hpp"
#include "caf/fwd.hpp"
#include "caf/result.hpp"
......@@ -10,6 +13,32 @@
#include <string>
#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 ----------------------------
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) {
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) {
if (xs.empty()) {
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;
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(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(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(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_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;
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(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(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(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;
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(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(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(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(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);
MESSAGE(deep_to_string(val));
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(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(deep_copy(val), val);
}
TEST_CASE("from non-empty array") {
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(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(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(deep_copy(val), val);
}
......@@ -13,6 +13,16 @@
} \
void CAF_UNIQUE(test)::run_test_impl()
#define TEST_CASE(description) \
namespace { \
struct CAF_UNIQUE(test) : caf_test_case_auto_fixture { \
void run_test_impl(); \
}; \
::caf::test::detail::adder<::caf::test::test_impl<CAF_UNIQUE(test)>> \
CAF_UNIQUE(a){CAF_XSTR(CAF_SUITE), description, false}; \
} \
void CAF_UNIQUE(test)::run_test_impl()
#define GIVEN(description) \
CAF_MESSAGE("GIVEN " description); \
if (true)
......
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