Unverified Commit 6dc7bf42 authored by Dominik Charousset's avatar Dominik Charousset Committed by GitHub

Merge pull request #1233

Add json_reader and json_writer
parents 1ed9a0b4 11dafbf2
......@@ -5,6 +5,11 @@ is based on [Keep a Changelog](https://keepachangelog.com).
## [Unreleased]
### Added
- CAF includes two new inspector types for consuming and generating
JSON-formatted text: `json_writer` and `json_reader`.
### Changed
- Setter functions for fields may now return either `bool`, `caf::error` or
......
......@@ -98,21 +98,23 @@ caf_add_component(
src/detail/abstract_worker.cpp
src/detail/abstract_worker_hub.cpp
src/detail/append_percent_encoded.cpp
src/detail/base64.cpp
src/detail/behavior_impl.cpp
src/detail/behavior_stack.cpp
src/detail/blocking_behavior.cpp
src/detail/config_consumer.cpp
src/detail/encode_base64.cpp
src/detail/get_mac_addresses.cpp
src/detail/get_process_id.cpp
src/detail/get_root_uuid.cpp
src/detail/glob_match.cpp
src/detail/group_tunnel.cpp
src/detail/invoke_result_visitor.cpp
src/detail/json.cpp
src/detail/local_group_module.cpp
src/detail/message_builder_element.cpp
src/detail/message_data.cpp
src/detail/meta_object.cpp
src/detail/monotonic_buffer_resource.cpp
src/detail/parse.cpp
src/detail/parser/chars.cpp
src/detail/pretty_type_name.cpp
......@@ -154,6 +156,8 @@ caf_add_component(
src/ipv6_address.cpp
src/ipv6_endpoint.cpp
src/ipv6_subnet.cpp
src/json_reader.cpp
src/json_writer.cpp
src/load_inspector.cpp
src/local_actor.cpp
src/logger.cpp
......@@ -241,14 +245,16 @@ caf_add_component(
decorator.sequencer
deep_to_string
detached_actors
detail.base64
detail.bounds_checker
detail.config_consumer
detail.encode_base64
detail.group_tunnel
detail.ieee_754
detail.json
detail.limited_vector
detail.local_group_module
detail.meta_object
detail.monotonic_buffer_resource
detail.parse
detail.parser.read_bool
detail.parser.read_config
......@@ -290,6 +296,8 @@ caf_add_component(
ipv6_address
ipv6_endpoint
ipv6_subnet
json_reader
json_writer
load_inspector
logger
mailbox_element
......@@ -301,6 +309,7 @@ caf_add_component(
mixin.requester
mixin.sender
mock_streaming_classes
mtl
native_streaming_classes
node_id
optional
......
......@@ -65,6 +65,7 @@
#include "caf/message_handler.hpp"
#include "caf/message_id.hpp"
#include "caf/message_priority.hpp"
#include "caf/mtl.hpp"
#include "caf/node_id.hpp"
#include "caf/others.hpp"
#include "caf/proxy_registry.hpp"
......
......@@ -51,6 +51,26 @@ public:
/// Reads run-time-type information for the next object if available.
virtual bool fetch_next_object_type(type_id_t& type) = 0;
/// Reads run-time-type information for the next object if available. The
/// default implementation calls `fetch_next_object_type` and queries the CAF
/// type name. However, implementations of the interface may retrieve the type
/// name differently and the type name may not correspond to any type known to
/// CAF. For example. the @ref json_reader returns the content of the `@type`
/// field of the current object if available.
/// @warning the characters in `type_name` may point to an internal buffer
/// that becomes invalid as soon as calling *any* other member
/// function on the deserializer. Convert the `type_name` to a string
/// before storing it.
virtual bool fetch_next_object_name(string_view& type_name);
/// Convenience function for querying `fetch_next_object_name` comparing the
/// result to `type_name` in one shot.
bool next_object_name_matches(string_view type_name);
/// Like `next_object_name_matches`, but sets an error on the deserializer
/// on a mismatch.
bool assert_next_object_name(string_view type_name);
/// Begins processing of an object, may perform a type check depending on the
/// data format.
/// @param type 16-bit ID for known types, @ref invalid_type_id otherwise.
......
......@@ -18,24 +18,20 @@ enum class hex_format {
lowercase,
};
template <hex_format format = hex_format::uppercase>
void append_hex(std::string& result, const void* vptr, size_t n) {
if (n == 0) {
result += "00";
template <hex_format format = hex_format::uppercase, class Buf = std::string>
void append_hex(Buf& result, const void* vptr, size_t n) {
if (n == 0)
return;
}
auto xs = reinterpret_cast<const uint8_t*>(vptr);
const char* tbl;
if constexpr (format == hex_format::uppercase)
tbl = "0123456789ABCDEF";
else
tbl = "0123456789abcdef";
char buf[3] = {0, 0, 0};
for (size_t i = 0; i < n; ++i) {
auto c = xs[i];
buf[0] = tbl[c >> 4];
buf[1] = tbl[c & 0x0F];
result += buf;
result.push_back(tbl[c >> 4]);
result.push_back(tbl[c & 0x0F]);
}
}
......
// 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/byte_buffer.hpp"
#include "caf/byte_span.hpp"
#include "caf/detail/core_export.hpp"
#include "caf/optional.hpp"
#include "caf/span.hpp"
#include "caf/string_view.hpp"
#include <string>
namespace caf::detail {
class CAF_CORE_EXPORT base64 {
public:
static void encode(string_view str, std::string& out);
static void encode(string_view str, byte_buffer& out);
static void encode(const_byte_span bytes, std::string& out);
static void encode(const_byte_span bytes, byte_buffer& out);
static std::string encode(string_view str) {
std::string result;
encode(str, result);
return result;
}
static std::string encode(const_byte_span bytes) {
std::string result;
encode(bytes, result);
return result;
}
static bool decode(string_view in, std::string& out);
static bool decode(string_view in, byte_buffer& out);
static bool decode(const_byte_span bytes, std::string& out);
static bool decode(const_byte_span bytes, byte_buffer& out);
static optional<std::string> decode(string_view in) {
std::string result;
if (decode(in, result))
return {std::move(result)};
else
return {};
}
static optional<std::string> decode(const_byte_span in) {
std::string result;
if (decode(in, result))
return {std::move(result)};
else
return {};
}
};
} // namespace caf::detail
......@@ -4,17 +4,22 @@
#pragma once
#include "caf/byte.hpp"
#include "caf/detail/core_export.hpp"
#include "caf/span.hpp"
#include "caf/byte_span.hpp"
#include "caf/detail/base64.hpp"
#include "caf/string_view.hpp"
#include <string>
namespace caf::detail {
CAF_CORE_EXPORT std::string encode_base64(string_view str);
[[deprecated("use base64::encode instead")]] inline std::string
encode_base64(string_view str) {
return base64::encode(str);
}
CAF_CORE_EXPORT std::string encode_base64(span<const byte> bytes);
[[deprecated("use base64::encode instead")]] inline std::string
encode_base64(const_byte_span bytes) {
return base64::encode(bytes);
}
} // namespace caf::detail
// 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 <cstdint>
#include <variant>
#include <vector>
#include "caf/detail/monotonic_buffer_resource.hpp"
#include "caf/parser_state.hpp"
#include "caf/string_view.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
// "wink out" the entire JSON object by simply reclaiming the memory without
// having to call a single destructor. The API is not optimized for convenience
// or safety, since the only place we use this API is the json_reader.
namespace caf::detail::json {
struct null_t {};
class value {
public:
using array_allocator = monotonic_buffer_resource::allocator<value>;
using array = std::vector<value, array_allocator>;
struct member {
string_view key;
value* val = nullptr;
};
using member_allocator = monotonic_buffer_resource::allocator<member>;
using object = std::vector<member, member_allocator>;
using data_type
= std::variant<null_t, int64_t, double, bool, string_view, array, object>;
static constexpr size_t null_index = 0;
static constexpr size_t integer_index = 1;
static constexpr size_t double_index = 2;
static constexpr size_t bool_index = 3;
static constexpr size_t string_index = 4;
static constexpr size_t array_index = 5;
static constexpr size_t object_index = 6;
data_type data;
};
using array = value::array;
using member = value::member;
using object = value::object;
value* make_value(monotonic_buffer_resource* storage);
array* make_array(monotonic_buffer_resource* storage);
object* make_object(monotonic_buffer_resource* storage);
value* parse(string_parser_state& ps, monotonic_buffer_resource* storage);
} // namespace caf::detail::json
// 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 <cstddef>
#include <map>
#include "caf/config.hpp"
#include "caf/detail/core_export.hpp"
#ifdef CAF_CLANG
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wc99-extensions"
#elif defined(CAF_GCC)
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wpedantic"
#elif defined(CAF_MSVC)
# pragma warning(push)
# pragma warning(disable : 4200)
#endif
namespace caf::detail {
/// Replacement for `std::pmr::monotonic_buffer_resource`, which sadly is not
/// available on all platforms CAF currenlty supports. This resource does not
/// support upstream resources and instead always uses `malloc` and `free`.
class CAF_CORE_EXPORT monotonic_buffer_resource {
public:
// -- member types -----------------------------------------------------------
/// A single block of memory.
struct block {
block* next;
std::byte bytes[];
};
/// A bucket for storing multiple blocks.
struct bucket {
block* head = nullptr;
std::byte* curr_pos = nullptr;
std::byte* curr_end = nullptr;
block* spare = nullptr;
size_t block_size = 0;
};
template <class T>
class allocator {
public:
using value_type = T;
template <class U>
struct rebind {
using other = allocator<U>;
};
explicit allocator(monotonic_buffer_resource* mbr) : mbr_(mbr) {
// nop
}
allocator() : mbr_(nullptr) {
// nop
}
allocator(const allocator&) = default;
allocator& operator=(const allocator&) = default;
template <class U>
allocator(const allocator<U>& other) : mbr_(other.resource()) {
// nop
}
template <class U>
allocator& operator=(const allocator<U>& other) {
mbr_ = other.resource();
return *this;
}
T* allocate(size_t n) {
return static_cast<T*>(mbr_->allocate(sizeof(T) * n, alignof(T)));
}
constexpr void deallocate(void*, size_t) noexcept {
// nop
}
constexpr auto resource() const noexcept {
return mbr_;
}
private:
monotonic_buffer_resource* mbr_;
};
// -- constructors, destructors, and assignment operators --------------------
monotonic_buffer_resource();
~monotonic_buffer_resource();
// -- allocator interface ----------------------------------------------------
/// Release all allocated memory to the OS even if no destructors were called
/// for the allocated objects.
void release();
/// Reclaims all allocated memory (re-using it) even if no destructors were
/// called for the allocated objects.
void reclaim();
/// Allocates memory.
[[nodiscard]] void* allocate(size_t bytes,
size_t alignment = alignof(max_align_t));
/// Fancy no-op.
constexpr void deallocate(void*, size_t, size_t = alignof(max_align_t)) {
// nop
}
/// Counts how many blocks currently exist in the bucket for @p alloc_size.
size_t blocks(size_t alloc_size);
/// Counts how many blocks currently exist in total.
size_t blocks();
private:
// Counts how many blocks exist in `where`.
size_t blocks(bucket& where);
// Gets the next free memory chunk.
[[nodiscard]] void* do_alloc(bucket& from, size_t bytes, size_t alignment);
// Adds another block to the bucket.
void grow(bucket& what);
// Select a bucket based on the allocation size.
bucket& bucket_by_size(size_t alloc_size);
// Sets all pointers back to nullptr.
void reset(bucket& bkt);
// Releases all memory in the bucket, leaving it in an invalid state.
void release(bucket& bkt);
// Shifts all blocks to `spare` list.
void reclaim(bucket& bkt);
// Objects of size <= 64 bytes.
bucket small_;
// Objects of size <= 512 bytes.
bucket medium_;
// Objects of various sizes > 512 bytes.
std::map<size_t, bucket> var_;
};
template <class T, class U>
bool operator==(monotonic_buffer_resource::allocator<T> x,
monotonic_buffer_resource::allocator<U> y) {
return x.resource() == y.resource();
}
template <class T, class U>
bool operator!=(monotonic_buffer_resource::allocator<T> x,
monotonic_buffer_resource::allocator<U> y) {
return x.resource() != y.resource();
}
} // namespace caf::detail
#ifdef CAF_CLANG
# pragma clang diagnostic pop
#elif defined(CAF_GCC)
# pragma GCC diagnostic pop
#elif defined(CAF_MSVC)
# pragma warning(pop)
#endif
// 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 <type_traits>
#include "caf/actor.hpp"
#include "caf/detail/type_list.hpp"
#include "caf/fwd.hpp"
namespace caf::detail {
template <class F>
struct mtl_util;
template <class... Rs, class... Ts>
struct mtl_util<result<Rs...>(Ts...)> {
template <class Self, class Adapter, class Inspector>
static bool
send(Self& self, const actor& dst, Adapter& adapter, Inspector& f, Ts... xs) {
f.revert();
if (adapter.read(f, xs...)) {
self->send(dst, std::move(xs)...);
return true;
} else {
return false;
}
}
template <class Self, class Adapter, class Inspector>
static bool
send(Self& self, const actor& dst, Adapter& adapter, Inspector& f) {
return send(self, dst, adapter, f, Ts{}...);
}
template <class Self, class Timeout, class Adapter, class Inspector,
class OnResult, class OnError>
static bool request(Self& self, const actor& dst, Timeout timeout,
Adapter& adapter, Inspector& f, OnResult& on_result,
OnError& on_error, Ts... xs) {
f.revert();
if (adapter.read(f, xs...)) {
if constexpr (std::is_same<type_list<Rs...>, type_list<void>>::value)
self->request(dst, timeout, std::move(xs)...)
.then([f{std::move(on_result)}]() mutable { f(); },
std::move(on_error));
else
self->request(dst, timeout, std::move(xs)...)
.then([f{std::move(on_result)}](Rs&... res) mutable { f(res...); },
std::move(on_error));
return true;
} else {
return false;
}
}
template <class Self, class Timeout, class Adapter, class Inspector,
class OnResult, class OnError>
static bool request(Self& self, const actor& dst, Timeout timeout,
Adapter& adapter, Inspector& f, OnResult& on_result,
OnError& on_error) {
return request(self, dst, timeout, adapter, f, on_result, on_error,
Ts{}...);
}
};
} // namespace caf::detail
......@@ -77,6 +77,33 @@ parse(string_parser_state& ps, T& x) {
return parse(ps, reinterpret_cast<squashed_type&>(x));
}
// When parsing regular integers, a "071" is 57 because the parser reads it as
// an octal number. This wrapper forces the parser to ignore leading zeros
// instead and always read numbers as decimals.
template <class Integer>
struct zero_padded_integer {
Integer val = 0;
};
template <class Integer>
void parse(string_parser_state& ps, zero_padded_integer<Integer>& x) {
x.val = 0;
ps.skip_whitespaces();
if (ps.at_end()) {
// Let the actual parser implementation set an appropriate error code.
parse(ps, x.val);
} else {
// Skip all leading zeros and then dispatch to the matching parser.
auto c = ps.current();
auto j = ps.i + 1;
while (c == '0' && j != ps.e && isdigit(*j)) {
c = ps.next();
++j;
}
parse(ps, x.val);
}
}
// -- floating point types -----------------------------------------------------
CAF_CORE_EXPORT void parse(string_parser_state& ps, float& x);
......@@ -111,7 +138,7 @@ CAF_CORE_EXPORT void parse(string_parser_state&, dictionary<config_value>&);
template <class... Ts>
bool parse_sequence(string_parser_state& ps, Ts&&... xs) {
auto parse_one = [&](auto& x) {
auto parse_one = [&ps](auto& x) {
parse(ps, x);
return ps.code <= pec::trailing_character;
};
......@@ -162,13 +189,14 @@ void parse(string_parser_state& ps,
namespace sc = std::chrono;
using value_type = sc::time_point<sc::system_clock, Duration>;
// Parse ISO 8601 as generated by detail::print.
int32_t year = 0;
int32_t month = 0;
int32_t day = 0;
int32_t hour = 0;
int32_t minute = 0;
int32_t second = 0;
int32_t milliseconds = 0;
using int_wrapper = zero_padded_integer<int32_t>;
auto year = int_wrapper{};
auto month = int_wrapper{};
auto day = int_wrapper{};
auto hour = int_wrapper{};
auto minute = int_wrapper{};
auto second = int_wrapper{};
auto milliseconds = int_wrapper{};
if (!parse_sequence(ps,
// YYYY-MM-DD
year, literal{{"-"}}, month, literal{{"-"}}, day,
......@@ -183,19 +211,19 @@ void parse(string_parser_state& ps,
return;
// Fill tm record.
tm record;
record.tm_sec = second;
record.tm_min = minute;
record.tm_hour = hour;
record.tm_mday = day;
record.tm_mon = month - 1; // months since January
record.tm_year = year - 1900; // years since 1900
record.tm_wday = -1; // n/a
record.tm_yday = -1; // n/a
record.tm_isdst = -1; // n/a
record.tm_sec = second.val;
record.tm_min = minute.val;
record.tm_hour = hour.val;
record.tm_mday = day.val;
record.tm_mon = month.val - 1; // months since January
record.tm_year = year.val - 1900; // years since 1900
record.tm_wday = -1; // n/a
record.tm_yday = -1; // n/a
record.tm_isdst = -1; // n/a
// Convert to chrono time and add the milliseconds.
auto tstamp = sc::system_clock::from_time_t(mktime(&record));
auto since_epoch = sc::duration_cast<Duration>(tstamp.time_since_epoch());
since_epoch += sc::milliseconds{milliseconds};
since_epoch += sc::milliseconds{milliseconds.val};
x = value_type{since_epoch};
}
......
......@@ -26,17 +26,33 @@ void print_escaped(Buffer& buf, string_view str) {
default:
buf.push_back(c);
break;
case '\\':
buf.push_back('\\');
buf.push_back('\\');
break;
case '\b':
buf.push_back('\\');
buf.push_back('b');
break;
case '\f':
buf.push_back('\\');
buf.push_back('f');
break;
case '\n':
buf.push_back('\\');
buf.push_back('n');
break;
case '\r':
buf.push_back('\\');
buf.push_back('r');
break;
case '\t':
buf.push_back('\\');
buf.push_back('t');
break;
case '\\':
buf.push_back('\\');
case '\v':
buf.push_back('\\');
buf.push_back('v');
break;
case '"':
buf.push_back('\\');
......@@ -47,6 +63,53 @@ void print_escaped(Buffer& buf, string_view str) {
buf.push_back('"');
}
template <class Buffer>
void print_unescaped(Buffer& buf, string_view str) {
buf.reserve(buf.size() + str.size());
auto i = str.begin();
auto e = str.end();
while (i != e) {
switch (*i) {
default:
buf.push_back(*i);
++i;
break;
case '\\':
if (++i != e) {
switch (*i) {
case '"':
buf.push_back('"');
break;
case '\\':
buf.push_back('\\');
break;
case 'b':
buf.push_back('\b');
break;
case 'f':
buf.push_back('\f');
break;
case 'n':
buf.push_back('\n');
break;
case 'r':
buf.push_back('\r');
break;
case 't':
buf.push_back('\t');
break;
case 'v':
buf.push_back('\v');
break;
default:
buf.push_back('?');
}
++i;
}
}
}
}
template <class Buffer>
void print(Buffer& buf, none_t) {
using namespace caf::literals;
......
......@@ -215,6 +215,27 @@ public:
return true;
}
// -- convenience API --------------------------------------------------------
template <class T>
static std::string render(const T& x) {
if constexpr (std::is_same<std::nullptr_t, T>::value) {
return "null";
} else if constexpr (std::is_constructible<string_view, T>::value) {
if constexpr (std::is_pointer<T>::value) {
if (x == nullptr)
return "null";
}
auto str = string_view{x};
return std::string{str.begin(), str.end()};
} else {
std::string result;
stringification_inspector f{result};
save(f, detail::as_mutable_ref(x));
return result;
}
}
private:
template <class T>
void append(T&& str) {
......
......@@ -145,6 +145,13 @@ public:
int compare(uint8_t code, type_id_t category) const noexcept;
// -- modifiers --------------------------------------------------------------
/// Reverts this error to "not an error" as if calling `*this = error{}`.
void reset() noexcept {
data_.reset();
}
// -- static convenience functions -------------------------------------------
/// @cond PRIVATE
......
......@@ -764,10 +764,7 @@ struct inspector_access<
detail::print(str, x);
return str;
};
auto set = [&x](std::string str) {
auto err = detail::parse(str, x);
return !err;
};
auto set = [&x](std::string str) { return detail::parse(str, x); };
return f.apply(get, set);
} else {
using rep_type = typename Duration::rep;
......
// 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/deserializer.hpp"
#include "caf/detail/core_export.hpp"
#include "caf/detail/json.hpp"
#include "caf/string_view.hpp"
#include <variant>
namespace caf {
/// Deserializes an inspectable object from a JSON-formatted string.
class CAF_CORE_EXPORT json_reader : public deserializer {
public:
// -- member types -----------------------------------------------------------
using super = deserializer;
struct sequence {
detail::json::array::const_iterator pos;
detail::json::array::const_iterator end;
bool at_end() const noexcept {
return pos == end;
}
auto& current() {
return *pos;
}
void advance() {
++pos;
}
};
struct members {
detail::json::object::const_iterator pos;
detail::json::object::const_iterator end;
bool at_end() const noexcept {
return pos == end;
}
auto& current() {
return *pos;
}
void advance() {
++pos;
}
};
using json_key = string_view;
using value_type
= std::variant<const detail::json::value*, const detail::json::object*,
detail::json::null_t, json_key, sequence, members>;
using stack_allocator
= detail::monotonic_buffer_resource::allocator<value_type>;
using stack_type = std::vector<value_type, stack_allocator>;
/// Denotes the type at the current position.
enum class position {
value,
object,
null,
key,
sequence,
members,
past_the_end,
invalid,
};
// -- constants --------------------------------------------------------------
/// The value value for `field_type_suffix()`.
static constexpr string_view field_type_suffix_default = "-type";
// -- constructors, destructors, and assignment operators --------------------
json_reader();
explicit json_reader(actor_system& sys);
explicit json_reader(execution_unit* ctx);
json_reader(const json_reader&) = delete;
json_reader& operator=(const json_reader&) = delete;
~json_reader() override;
// -- properties -------------------------------------------------------------
/// 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]] string_view field_type_suffix() const noexcept {
return field_type_suffix_;
}
/// Configures whether the writer omits empty fields.
void field_type_suffix(string_view suffix) noexcept {
field_type_suffix_ = suffix;
}
// -- modifiers --------------------------------------------------------------
/// Parses @p json_text into an internal representation. After loading the
/// JSON input, the reader is ready for attempting to deserialize inspectable
/// objects.
/// @warning The internal data structure keeps pointers into @p json_text.
/// Hence, the buffer pointed to by the string view must remain valid
/// until either destroying this reader or calling `reset`.
/// @note Implicitly calls `reset`.
bool load(string_view json_text);
/// Reverts the state of the reader back to where it was after calling `load`.
/// @post The reader is ready for attempting to deserialize another
/// inspectable object.
void revert();
/// Removes any loaded JSON data and reclaims memory resources.
void reset();
// -- overrides --------------------------------------------------------------
bool fetch_next_object_type(type_id_t& type) override;
bool fetch_next_object_name(string_view& type_name) override;
bool begin_object(type_id_t type, string_view name) override;
bool end_object() override;
bool begin_field(string_view) override;
bool begin_field(string_view name, bool& is_present) override;
bool begin_field(string_view name, span<const type_id_t> types,
size_t& index) override;
bool begin_field(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(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& x) override;
bool value(std::u16string& x) override;
bool value(std::u32string& x) override;
bool value(span<byte> x) override;
private:
[[nodiscard]] position pos() const noexcept;
template <bool PopOrAdvanceOnSuccess, class F>
bool consume(const char* fun_name, F f);
template <class T>
bool integer(T& x);
template <position P>
auto& top() noexcept {
return std::get<static_cast<size_t>(P)>(st_->back());
}
template <position P>
const auto& top() const noexcept {
return std::get<static_cast<size_t>(P)>(st_->back());
}
void pop() {
st_->pop_back();
}
template <class T>
void push(T&& x) {
st_->emplace_back(std::forward<T>(x));
}
detail::monotonic_buffer_resource buf_;
stack_type* st_ = nullptr;
detail::json::value* root_ = nullptr;
string_view field_type_suffix_ = 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 <vector>
#include "caf/detail/core_export.hpp"
#include "caf/serializer.hpp"
namespace caf {
/// Serializes an inspectable object to a JSON-formatted string.
class CAF_CORE_EXPORT json_writer : public serializer {
public:
// -- member types -----------------------------------------------------------
using super = serializer;
/// Reflects the structure of JSON objects according to ECMA-404. This enum
/// skips types such as `members` or `value` since they are not needed to
/// generate JSON.
enum class type : uint8_t {
element, /// Can morph into any other type except `member`.
object, /// Contains any number of members.
member, /// A single key-value pair.
key, /// The key of a field.
array, /// Contains any number of elements.
string, /// A character sequence (terminal type).
number, /// An integer or floating point (terminal type).
boolean, /// Either "true" or "false" (terminal type).
null, /// The literal "null" (terminal type).
};
// -- constants --------------------------------------------------------------
/// The default value for `skip_empty_fields()`.
static constexpr bool skip_empty_fields_default = true;
/// The value value for `field_type_suffix()`.
static constexpr string_view field_type_suffix_default = "-type";
// -- constructors, destructors, and assignment operators --------------------
json_writer();
explicit json_writer(actor_system& sys);
explicit json_writer(execution_unit* ctx);
~json_writer() override;
// -- properties -------------------------------------------------------------
/// Returns a string view into the internal buffer.
/// @warning This view becomes invalid when calling any non-const member
/// function on the writer object.
[[nodiscard]] string_view str() const noexcept {
return {buf_.data(), buf_.size()};
}
/// Returns the current indentation factor.
[[nodiscard]] size_t indentation() const noexcept {
return indentation_factor_;
}
/// Sets the indentation level.
/// @param factor The number of spaces to add to each level of indentation. A
/// value of 0 (the default) disables indentation, printing the
/// entire JSON output into a single line.
void indentation(size_t factor) noexcept {
indentation_factor_ = factor;
}
/// Returns whether the writer generates compact JSON output without any
/// spaces or newlines to separate values.
[[nodiscard]] bool compact() const noexcept {
return indentation_factor_ == 0;
}
/// 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 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]] string_view field_type_suffix() const noexcept {
return field_type_suffix_;
}
/// Configures whether the writer omits empty fields.
void field_type_suffix(string_view suffix) noexcept {
field_type_suffix_ = suffix;
}
// -- modifiers --------------------------------------------------------------
/// Removes all characters from the buffer and restores the writer to its
/// initial state.
/// @warning Invalidates all string views into the buffer.
void reset();
// -- overrides --------------------------------------------------------------
bool begin_object(type_id_t type, string_view name) override;
bool end_object() override;
bool begin_field(string_view) override;
bool begin_field(string_view name, bool is_present) override;
bool begin_field(string_view name, span<const type_id_t> types,
size_t index) override;
bool begin_field(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(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(string_view x) override;
bool value(const std::u16string& x) override;
bool value(const std::u32string& x) override;
bool value(span<const byte> x) override;
private:
// -- implementation details -------------------------------------------------
template <class T>
bool number(T);
// -- state management -------------------------------------------------------
void init();
// Returns the current top of the stack or `null_literal` if empty.
type top();
// Enters a new level of nesting.
void push(type = type::element);
// 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);
// Backs up one level of nesting but checks that the top is `t` afterwards.
bool pop_if_next(type t);
// Tries to morph the current top of the stack to t.
bool morph(type t);
// Tries to morph the current top of the stack to t. Stores the previous value
// to `prev`.
bool morph(type t, type& prev);
// Morphs the current top of the stack to t without performing *any* checks.
void unsafe_morph(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;
// -- printing ---------------------------------------------------------------
// Adds a newline unless `compact() == true`.
void nl();
// Adds `c` to the output buffer.
void add(char c) {
buf_.push_back(c);
}
// Adds `str` to the output buffer.
void add(string_view str) {
buf_.insert(buf_.end(), str.begin(), str.end());
}
// Adds a separator to the output buffer unless the current entry is empty.
// The separator is just a comma when in compact mode and otherwise a comma
// followed by a newline.
void sep();
// -- member variables -------------------------------------------------------
// The current level of indentation.
size_t indentation_level_ = 0;
// The number of whitespaces to add per indentation level.
size_t indentation_factor_ = 0;
// Buffer for producing the JSON output.
std::vector<char> buf_;
struct entry {
type t;
bool filled;
friend bool operator==(entry x, type y) noexcept {
return x.t == y;
};
};
// 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_ = skip_empty_fields_default;
string_view field_type_suffix_ = field_type_suffix_default;
};
} // namespace caf
......@@ -42,7 +42,7 @@ public:
template <class... Ts>
void emplace_error(Ts&&... xs) {
err_ = make_error(xs...);
err_ = make_error(std::forward<Ts>(xs)...);
}
const error& get_error() const noexcept {
......
......@@ -29,6 +29,10 @@ public:
type_name_or_anonymous<T>(), dptr()};
}
constexpr auto virtual_object(string_view type_name) noexcept {
return super::object_t<Subtype>{invalid_type_id, type_name, dptr()};
}
template <class T>
bool list(T& xs) {
xs.clear();
......
// 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 <type_traits>
#include "caf/actor.hpp"
#include "caf/actor_cast.hpp"
#include "caf/detail/mtl_util.hpp"
#include "caf/typed_actor.hpp"
namespace caf {
/// Enables event-based actors to generate messages from a user-defined data
/// exchange format such as JSON and to send the generated messages to another
/// (typed) actor.
template <class Self, class Adapter, class Reader>
class event_based_mtl {
public:
// -- sanity checks ----------------------------------------------------------
static_assert(std::is_nothrow_copy_assignable<Adapter>::value);
static_assert(std::is_nothrow_move_assignable<Adapter>::value);
// -- constructors, destructors, and assignment operators --------------------
event_based_mtl() = delete;
event_based_mtl(Self* self, Adapter adapter, Reader* reader) noexcept
: self_(self), adapter_(std::move(adapter)), reader_(reader) {
// nop
}
event_based_mtl(const event_based_mtl&) noexcept = default;
event_based_mtl& operator=(const event_based_mtl&) noexcept = default;
// -- properties -------------------------------------------------------------
auto self() {
return self_;
}
auto& adapter() {
return adapter_;
}
auto& reader() {
return *reader_;
}
// -- messaging --------------------------------------------------------------
/// Tries to get a message from the reader that matches any of the accepted
/// inputs of `dst` and sends the converted messages on success.
/// @param dst The destination for the next message.
/// @returns `true` if the adapter was able to generate and send a message,
/// `false` otherwise.
template <class... Fs>
bool try_send(const typed_actor<Fs...>& dst) {
auto dst_hdl = actor_cast<actor>(dst);
return (detail::mtl_util<Fs>::send(self_, dst_hdl, adapter_, *reader_)
|| ...);
}
/// Tries to get a message from the reader that matches any of the accepted
/// inputs of `dst` and sends a request message to `dst` on success.
/// @param dst The destination for the next message.
/// @param timeout The relative timeout for the request message.
/// @param on_result The one-shot handler for the response message. This
/// function object must accept *all* possible response types
/// from `dst`.
/// @param on_error The one-shot handler for timeout and other errors.
/// @returns `true` if the adapter was able to generate and send a message,
/// `false` otherwise.
template <class... Fs, class Timeout, class OnResult, class OnError>
bool try_request(const typed_actor<Fs...>& dst, Timeout timeout,
OnResult on_result, OnError on_error) {
using on_error_result = decltype(on_error(std::declval<error&>()));
static_assert(std::is_same<void, on_error_result>::value);
auto dst_hdl = actor_cast<actor>(dst);
return (detail::mtl_util<Fs>::request(self_, dst_hdl, timeout, adapter_,
*reader_, on_result, on_error)
|| ...);
}
private:
Self* self_;
Adapter adapter_;
Reader* reader_;
};
/// Creates an MTL (message translation layer) to enable an actor to exchange
/// messages with non-CAF endpoints over a user-defined data exchange format
/// such as JSON.
/// @param self Points to an event-based or blocking actor.
/// @param adapter Translates between internal and external message types.
/// @param reader Points to an object that either implements the interface
/// @ref deserializer directly or that provides a compatible API.
template <class Self, class Adapter, class Reader>
auto make_mtl(Self* self, Adapter adapter, Reader* reader) {
if constexpr (std::is_base_of<non_blocking_actor_base, Self>::value) {
return event_based_mtl{self, adapter, reader};
} else {
static_assert(detail::always_false_v<Self>,
"sorry, support for blocking actors not implemented yet");
}
}
} // namespace caf
......@@ -42,7 +42,7 @@ public:
template <class... Ts>
void emplace_error(Ts&&... xs) {
err_ = make_error(xs...);
err_ = make_error(std::forward<Ts>(xs)...);
}
const error& get_error() const noexcept {
......
......@@ -24,6 +24,10 @@ public:
type_name_or_anonymous<T>(), dptr()};
}
constexpr auto virtual_object(string_view type_name) noexcept {
return super::object_t<Subtype>{invalid_type_id, type_name, dptr()};
}
template <class T>
bool list(const T& xs) {
using value_type = typename T::value_type;
......
......@@ -111,6 +111,8 @@ CAF_CORE_EXPORT type_id_t query_type_id(string_view name);
} // namespace caf
// -- CAF_BEGIN_TYPE_ID_BLOCK --------------------------------------------------
/// Starts a code block for registering custom types to CAF. Stores the first ID
/// for the project as `caf::id_block::${project_name}_first_type_id`. Usually,
/// users should use `caf::first_custom_type_id` as `first_id`. However, this
......@@ -127,6 +129,8 @@ CAF_CORE_EXPORT type_id_t query_type_id(string_view name);
constexpr type_id_t project_name##_first_type_id = first_id; \
}
// -- CAF_ADD_TYPE_ID ----------------------------------------------------------
#ifdef CAF_MSVC
# define CAF_DETAIL_NEXT_TYPE_ID(project_name, fully_qualified_name) \
template <> \
......@@ -195,6 +199,78 @@ CAF_CORE_EXPORT type_id_t query_type_id(string_view name);
CAF_PP_OVERLOAD(CAF_ADD_TYPE_ID_, __VA_ARGS__)(__VA_ARGS__)
#endif
// -- CAF_ADD_TYPE_ID_FROM_EXPR ------------------------------------------------
#ifdef CAF_MSVC
# define CAF_DETAIL_NEXT_TYPE_ID_FROM_EXPR(project_name, type_expr) \
template <> \
struct type_id<CAF_PP_EXPAND type_expr> { \
static constexpr type_id_t value \
= id_block::project_name##_first_type_id \
+ (CAF_PP_CAT(CAF_PP_COUNTER, ()) \
- id_block::project_name##_type_id_counter_init - 1); \
};
#else
# define CAF_DETAIL_NEXT_TYPE_ID_FROM_EXPR(project_name, type_expr) \
template <> \
struct type_id<CAF_PP_EXPAND type_expr> { \
static constexpr type_id_t value \
= id_block::project_name##_first_type_id \
+ (__COUNTER__ - id_block::project_name##_type_id_counter_init - 1); \
};
#endif
#define CAF_ADD_TYPE_ID_FROM_EXPR_2(project_name, type_expr) \
namespace caf { \
CAF_DETAIL_NEXT_TYPE_ID_FROM_EXPR(project_name, type_expr) \
template <> \
struct type_by_id<type_id<CAF_PP_EXPAND type_expr>::value> { \
using type = CAF_PP_EXPAND type_expr; \
}; \
template <> \
struct type_name<CAF_PP_EXPAND type_expr> { \
static constexpr string_view value = CAF_PP_STR(CAF_PP_EXPAND type_expr); \
}; \
template <> \
struct type_name_by_id<type_id<CAF_PP_EXPAND type_expr>::value> \
: type_name<CAF_PP_EXPAND type_expr> {}; \
}
#define CAF_ADD_TYPE_ID_FROM_EXPR_3(project_name, type_expr, user_type_name) \
namespace caf { \
CAF_DETAIL_NEXT_TYPE_ID_FROM_EXPR(project_name, type_expr) \
template <> \
struct type_by_id<type_id<CAF_PP_EXPAND type_expr>::value> { \
using type = CAF_PP_EXPAND type_expr; \
}; \
template <> \
struct type_name<CAF_PP_EXPAND type_expr> { \
static constexpr string_view value = user_type_name; \
}; \
template <> \
struct type_name_by_id<type_id<CAF_PP_EXPAND type_expr>::value> \
: type_name<CAF_PP_EXPAND type_expr> {}; \
}
/// @def CAF_ADD_TYPE_ID_FROM_EXPR(project_name, type_expr, user_type_name)
/// Assigns the next free type ID to the type resulting from `type_expr`.
/// @param project_name User-defined name for type ID block.
/// @param type_expr A compile-time expression resulting in a type, e.g., a
/// `decltype` statement.
/// @param user_type_name Optional parameter. If present, defines the content of
/// `caf::type_name`. Defaults to `type_expr`.
#ifdef CAF_MSVC
# define CAF_ADD_TYPE_ID_FROM_EXPR(...) \
CAF_PP_CAT(CAF_PP_OVERLOAD(CAF_ADD_TYPE_ID_FROM_EXPR_, \
__VA_ARGS__)(__VA_ARGS__), \
CAF_PP_EMPTY())
#else
# define CAF_ADD_TYPE_ID_FROM_EXPR(...) \
CAF_PP_OVERLOAD(CAF_ADD_TYPE_ID_FROM_EXPR_, __VA_ARGS__)(__VA_ARGS__)
#endif
// -- CAF_ADD_ATOM -------------------------------------------------------------
/// Creates a new tag type (atom) in the global namespace and assigns the next
/// free type ID to it.
#define CAF_ADD_ATOM_2(project_name, atom_name) \
......@@ -260,6 +336,8 @@ CAF_CORE_EXPORT type_id_t query_type_id(string_view name);
CAF_PP_OVERLOAD(CAF_ADD_ATOM_, __VA_ARGS__)(__VA_ARGS__)
#endif
// -- CAF_END_TYPE_ID_BLOCK ----------------------------------------------------
/// Finalizes a code block for registering custom types to CAF. Defines a struct
/// `caf::type_id::${project_name}` with two static members `begin` and `end`.
/// The former stores the first assigned type ID. The latter stores the last
......@@ -275,32 +353,24 @@ CAF_CORE_EXPORT type_id_t query_type_id(string_view name);
}; \
}
namespace caf::detail {
// We can't pass a builtin type such as `bool` to CAF_ADD_TYPE_ID because it
// expands to `::bool`, which for some reason is invalid in C++. This alias only
// exists to work around this limitation.
template <class T>
using id_t = T;
} // namespace caf::detail
// -- type ID block for the core module ----------------------------------------
CAF_BEGIN_TYPE_ID_BLOCK(core_module, 0)
// -- C types
CAF_ADD_TYPE_ID(core_module, (caf::detail::id_t<bool>), "bool")
CAF_ADD_TYPE_ID(core_module, (caf::detail::id_t<double>), "double")
CAF_ADD_TYPE_ID(core_module, (caf::detail::id_t<float>), "float")
CAF_ADD_TYPE_ID(core_module, (caf::detail::id_t<int16_t>), "int16_t")
CAF_ADD_TYPE_ID(core_module, (caf::detail::id_t<int32_t>), "int32_t")
CAF_ADD_TYPE_ID(core_module, (caf::detail::id_t<int64_t>), "int64_t")
CAF_ADD_TYPE_ID(core_module, (caf::detail::id_t<int8_t>), "int8_t")
CAF_ADD_TYPE_ID(core_module, (caf::detail::id_t<long double>), "ldouble")
CAF_ADD_TYPE_ID(core_module, (caf::detail::id_t<uint16_t>), "uint16_t")
CAF_ADD_TYPE_ID(core_module, (caf::detail::id_t<uint32_t>), "uint32_t")
CAF_ADD_TYPE_ID(core_module, (caf::detail::id_t<uint64_t>), "uint64_t")
CAF_ADD_TYPE_ID(core_module, (caf::detail::id_t<uint8_t>), "uint8_t")
CAF_ADD_TYPE_ID_FROM_EXPR(core_module, (bool) )
CAF_ADD_TYPE_ID_FROM_EXPR(core_module, (double) )
CAF_ADD_TYPE_ID_FROM_EXPR(core_module, (float) )
CAF_ADD_TYPE_ID_FROM_EXPR(core_module, (int16_t))
CAF_ADD_TYPE_ID_FROM_EXPR(core_module, (int32_t))
CAF_ADD_TYPE_ID_FROM_EXPR(core_module, (int64_t))
CAF_ADD_TYPE_ID_FROM_EXPR(core_module, (int8_t))
CAF_ADD_TYPE_ID_FROM_EXPR(core_module, (long double), "ldouble")
CAF_ADD_TYPE_ID_FROM_EXPR(core_module, (uint16_t))
CAF_ADD_TYPE_ID_FROM_EXPR(core_module, (uint32_t))
CAF_ADD_TYPE_ID_FROM_EXPR(core_module, (uint64_t))
CAF_ADD_TYPE_ID_FROM_EXPR(core_module, (uint8_t))
// -- STL types
......
......@@ -21,6 +21,44 @@ deserializer::~deserializer() {
// nop
}
bool deserializer::fetch_next_object_name(string_view& type_name) {
auto t = type_id_t{};
if (fetch_next_object_type(t)) {
type_name = query_type_name(t);
return true;
} else {
return false;
}
}
bool deserializer::next_object_name_matches(string_view type_name) {
string_view found;
if (fetch_next_object_name(found)) {
return type_name == found;
} else {
return false;
}
}
bool deserializer::assert_next_object_name(string_view type_name) {
string_view found;
if (fetch_next_object_name(found)) {
if (type_name == found) {
return true;
} else {
std::string str = "required type ";
str.insert(str.end(), type_name.begin(), type_name.end());
str += ", got ";
str.insert(str.end(), found.begin(), found.end());
emplace_error(sec::type_clash, __func__, std::move(str));
return false;
}
} else {
emplace_error(sec::runtime_error, __func__, "no type name available");
return false;
}
}
bool deserializer::begin_key_value_pair() {
return begin_tuple(2);
}
......
// 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/detail/base64.hpp"
#include <cstdint>
namespace caf::detail {
namespace {
// clang-format off
constexpr uint8_t decoding_tbl[] = {
/* ..0 ..1 ..2 ..3 ..4 ..5 ..6 ..7 ..8 ..9 ..A ..B ..C ..D ..E ..F */
/* 0.. */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/* 1.. */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/* 2.. */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0, 63,
/* 3.. */ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, 0, 0, 0,
/* 4.. */ 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
/* 5.. */ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 0,
/* 6.. */ 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
/* 7.. */ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0, 0, 0, 0, 0};
// clang-format on
constexpr const char encoding_tbl[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+/";
template <class Storage>
void encode_impl(string_view in, Storage& out) {
using value_type = typename Storage::value_type;
// Consumes three characters from the input at once.
auto consume = [&out](const char* str) {
int buf[] = {
(str[0] & 0xfc) >> 2,
((str[0] & 0x03) << 4) + ((str[1] & 0xf0) >> 4),
((str[1] & 0x0f) << 2) + ((str[2] & 0xc0) >> 6),
str[2] & 0x3f,
};
for (auto x : buf)
out.push_back(static_cast<value_type>(encoding_tbl[x]));
};
// Iterate the input in chunks of three bytes.
auto i = in.begin();
for (; std::distance(i, in.end()) >= 3; i += 3)
consume(i);
// Deal with any leftover: pad the input with zeros and then fixup the output.
if (i != in.end()) {
char buf[] = {0, 0, 0};
std::copy(i, in.end(), buf);
consume(buf);
for (auto j = out.end() - (3 - (in.size() % 3)); j != out.end(); ++j)
*j = static_cast<value_type>('=');
}
}
template <class Storage>
bool decode_impl(string_view in, Storage& out) {
using value_type = typename Storage::value_type;
// Short-circuit empty inputs.
if (in.empty())
return true;
// Refuse invalid inputs: Base64 always produces character groups of size 4.
if (in.size() % 4 != 0)
return false;
// Consume four characters from the input at once.
auto val = [](char c) { return decoding_tbl[c & 0x7F]; };
for (auto i = in.begin(); i != in.end(); i += 4) {
// clang-format off
auto bits = (val(i[0]) << 18)
| (val(i[1]) << 12)
| (val(i[2]) << 6)
| (val(i[3]));
// clang-format on
out.push_back(static_cast<value_type>((bits & 0xFF0000) >> 16));
out.push_back(static_cast<value_type>((bits & 0x00FF00) >> 8));
out.push_back(static_cast<value_type>((bits & 0x0000FF)));
}
// Fix up the output buffer if the input contained padding.
auto s = in.size();
if (in[s - 2] == '=') {
out.pop_back();
out.pop_back();
} else if (in[s - 1] == '=') {
out.pop_back();
}
return true;
}
string_view as_string_view(const_byte_span bytes) {
return {reinterpret_cast<const char*>(bytes.data()), bytes.size()};
}
} // namespace
void base64::encode(string_view str, std::string& out) {
encode_impl(str, out);
}
void base64::encode(string_view str, byte_buffer& out) {
encode_impl(str, out);
}
void base64::encode(const_byte_span bytes, std::string& out) {
encode_impl(as_string_view(bytes), out);
}
void base64::encode(const_byte_span bytes, byte_buffer& out) {
encode_impl(as_string_view(bytes), out);
}
bool base64::decode(string_view in, std::string& out) {
return decode_impl(in, out);
}
bool base64::decode(string_view in, byte_buffer& out) {
return decode_impl(in, out);
}
bool base64::decode(const_byte_span bytes, std::string& out) {
return decode_impl(as_string_view(bytes), out);
}
bool base64::decode(const_byte_span bytes, byte_buffer& out) {
return decode_impl(as_string_view(bytes), out);
}
} // namespace caf::detail
// 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/detail/encode_base64.hpp"
namespace caf::detail {
namespace {
constexpr const char base64_tbl[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+/";
} // namespace
std::string encode_base64(string_view str) {
auto bytes = make_span(reinterpret_cast<const byte*>(str.data()), str.size());
return encode_base64(bytes);
}
std::string encode_base64(span<const byte> bytes) {
std::string result;
// Consumes three characters from input at once.
auto consume = [&result](const byte* i) {
auto at = [i](size_t index) { return to_integer<int>(i[index]); };
int buf[] = {
(at(0) & 0xfc) >> 2,
((at(0) & 0x03) << 4) + ((at(1) & 0xf0) >> 4),
((at(1) & 0x0f) << 2) + ((at(2) & 0xc0) >> 6),
at(2) & 0x3f,
};
for (auto x : buf)
result += base64_tbl[x];
};
// Iterate the input in chunks of three bytes.
auto i = bytes.begin();
for (; std::distance(i, bytes.end()) >= 3; i += 3)
consume(i);
if (i != bytes.end()) {
// Pad input with zeros.
byte buf[] = {byte{0}, byte{0}, byte{0}};
std::copy(i, bytes.end(), buf);
consume(buf);
// Override padded bytes (garbage) with '='.
for (auto j = result.end() - (3 - (bytes.size() % 3)); j != result.end();
++j)
*j = '=';
}
return result;
}
} // namespace caf::detail
// 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/detail/json.hpp"
#include <iterator>
#include <memory>
#include "caf/config.hpp"
#include "caf/detail/parser/chars.hpp"
#include "caf/detail/parser/is_char.hpp"
#include "caf/detail/parser/read_bool.hpp"
#include "caf/detail/parser/read_number.hpp"
#include "caf/detail/scope_guard.hpp"
#include "caf/pec.hpp"
CAF_PUSH_UNUSED_LABEL_WARNING
#include "caf/detail/parser/fsm.hpp"
namespace caf::detail::parser {
struct obj_consumer;
struct arr_consumer;
struct val_consumer {
monotonic_buffer_resource* storage;
json::value* ptr;
template <class T>
void value(T x) {
ptr->data = x;
}
arr_consumer begin_array();
obj_consumer begin_object();
};
struct key_consumer {
string_view* ptr;
void value(string_view str) {
*ptr = str;
}
};
struct member_consumer {
monotonic_buffer_resource* storage;
json::member* ptr;
key_consumer begin_key() {
return {std::addressof(ptr->key)};
}
val_consumer begin_val() {
ptr->val = json::make_value(storage);
return {storage, ptr->val};
}
};
struct obj_consumer {
json::object* ptr;
member_consumer begin_member() {
ptr->emplace_back();
return {ptr->get_allocator().resource(), std::addressof(ptr->back())};
}
};
struct arr_consumer {
json::array* ptr;
val_consumer begin_value() {
ptr->emplace_back();
return {ptr->get_allocator().resource(), std::addressof(ptr->back())};
}
};
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});
auto& obj = std::get<json::object>(ptr->data);
obj.reserve(16);
return {&obj};
}
void read_value(string_parser_state& ps, val_consumer consumer);
template <class Consumer>
void read_json_null_or_nan(string_parser_state& ps, Consumer consumer) {
enum { nil, is_null, is_nan };
auto res_type = nil;
auto g = make_scope_guard([&] {
if (ps.code <= pec::trailing_character) {
CAF_ASSERT(res_type != nil);
if (res_type == is_null)
consumer.value(json::null_t{});
else
consumer.value(std::numeric_limits<double>::quiet_NaN());
}
});
// clang-format off
start();
state(init) {
transition(init, " \t\n")
transition(has_n, 'n')
}
state(has_n) {
transition(has_nu, 'u')
transition(has_na, 'a')
}
state(has_nu) {
transition(has_nul, 'l')
}
state(has_nul) {
transition(done, 'l', res_type = is_null)
}
state(has_na) {
transition(done, 'n', res_type = is_nan)
}
term_state(done) {
transition(init, " \t\n")
}
fin();
// clang-format on
}
template <class Consumer>
void read_json_string(string_parser_state& ps, Consumer consumer) {
auto first = string_view::iterator{};
// clang-format off
start();
state(init) {
transition(init, " \t\n")
transition(read_chars, '"', first = ps.i + 1)
}
state(read_chars) {
transition(escape, '\\')
transition(done, '"', consumer.value(string_view{first, ps.i}))
transition(read_chars, any_char)
}
state(escape) {
// TODO: Add support for JSON's \uXXXX escaping.
transition(read_chars, "\"\\/bfnrt")
}
term_state(done) {
transition(done, " \t\n")
}
fin();
// clang-format on
}
void read_member(string_parser_state& ps, member_consumer consumer) {
// clang-format off
start();
state(init) {
transition(init, " \t\n")
fsm_epsilon(read_json_string(ps, consumer.begin_key()), after_key, '"')
}
state(after_key) {
transition(after_key, " \t\n")
fsm_transition(read_value(ps, consumer.begin_val()), done, ':')
}
term_state(done) {
transition(done, " \t\n")
}
fin();
// clang-format on
}
void read_json_object(string_parser_state& ps, obj_consumer consumer) {
// clang-format off
start();
state(init) {
transition(init, " \t\n")
transition(has_open_brace, '{')
}
state(has_open_brace) {
transition(has_open_brace, " \t\n")
fsm_epsilon(read_member(ps, consumer.begin_member()), after_member, '"')
transition(done, '}')
}
state(after_member) {
transition(after_member, " \t\n")
transition(after_comma, ',')
transition(done, '}')
}
state(after_comma) {
transition(after_comma, " \t\n")
fsm_epsilon(read_member(ps, consumer.begin_member()), after_member, '"')
}
term_state(done) {
transition(done, " \t\n")
}
fin();
// clang-format on
}
void read_json_array(string_parser_state& ps, arr_consumer consumer) {
// clang-format off
start();
state(init) {
transition(init, " \t\n")
transition(has_open_brace, '[')
}
state(has_open_brace) {
transition(has_open_brace, " \t\n")
transition(done, ']')
fsm_epsilon(read_value(ps, consumer.begin_value()), after_value)
}
state(after_value) {
transition(after_value, " \t\n")
transition(after_comma, ',')
transition(done, ']')
}
state(after_comma) {
transition(after_comma, " \t\n")
fsm_epsilon(read_value(ps, consumer.begin_value()), after_value)
}
term_state(done) {
transition(done, " \t\n")
}
fin();
// clang-format on
}
void read_value(string_parser_state& ps, val_consumer consumer) {
// clang-format off
start();
state(init) {
transition(init, " \t\n")
fsm_epsilon(read_json_string(ps, 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, consumer.begin_object()), done, '{')
fsm_epsilon(read_json_array(ps, consumer.begin_array()), done, '[')
}
term_state(done) {
transition(done, " \t\n")
}
fin();
// clang-format on
}
} // namespace caf::detail::parser
#include "caf/detail/parser/fsm_undef.hpp"
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});
}
void init(value* ptr, monotonic_buffer_resource*) {
new (ptr) value();
}
template <class T>
T* make_impl(monotonic_buffer_resource* storage) {
monotonic_buffer_resource::allocator<T> alloc{storage};
auto result = alloc.allocate(1);
init(result, storage);
return result;
}
} // namespace
value* make_value(monotonic_buffer_resource* storage) {
return make_impl<value>(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;
}
value* parse(string_parser_state& ps, monotonic_buffer_resource* storage) {
monotonic_buffer_resource::allocator<value> alloc{storage};
auto result = new (alloc.allocate(1)) value();
parser::read_value(ps, {storage, result});
return result;
}
} // namespace caf::detail::json
// 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/detail/monotonic_buffer_resource.hpp"
#include <limits>
#include <memory>
#include "caf/raise_error.hpp"
namespace caf::detail {
monotonic_buffer_resource::monotonic_buffer_resource() {
// 8kb blocks for the small and medium sized buckets.
small_.block_size = 8 * 1024;
medium_.block_size = 8 * 1024;
}
monotonic_buffer_resource::~monotonic_buffer_resource() {
release(small_);
release(medium_);
for (auto& kvp : var_)
release(kvp.second);
}
void monotonic_buffer_resource::release() {
release(small_);
reset(small_);
release(medium_);
reset(medium_);
for (auto& kvp : var_)
release(kvp.second);
var_.clear();
}
void monotonic_buffer_resource::reclaim() {
// Only reclaim the small and medium buffers. They have a high chance of
// actually reducing future heap allocations. Because of the relatively small
// bucket sizes, the variable buckets have a higher change of not producing
// 'hits' in future runs. We can get smarter about managing larger
// allocations, but ultimately this custom memory resource implementation is a
// placeholder until we can use the new `std::pmr` utilities. So as long as
// performance is "good enough", we keep our implementation simple and err on
// the side of caution for now.
reclaim(small_);
reclaim(medium_);
for (auto& kvp : var_)
release(kvp.second);
var_.clear();
}
void* monotonic_buffer_resource::allocate(size_t bytes, size_t alignment) {
return do_alloc(bucket_by_size(bytes), bytes, alignment);
}
size_t monotonic_buffer_resource::blocks(size_t alloc_size) {
return blocks(bucket_by_size(alloc_size));
}
size_t monotonic_buffer_resource::blocks() {
auto result = blocks(small_) + blocks(medium_);
for (auto& kvp : var_)
result += blocks(kvp.second);
return result;
}
size_t monotonic_buffer_resource::blocks(bucket& where) {
size_t result = 0;
for (auto ptr = where.head; ptr != nullptr; ptr = ptr->next)
++result;
for (auto ptr = where.spare; ptr != nullptr; ptr = ptr->next)
++result;
return result;
}
void* monotonic_buffer_resource::do_alloc(bucket& from, size_t bytes,
size_t alignment) {
for (;;) {
if (from.curr_pos != nullptr) {
auto result = static_cast<void*>(from.curr_pos);
auto space = static_cast<size_t>(from.curr_end - from.curr_pos);
if (std::align(alignment, bytes, result, space)) {
from.curr_pos = static_cast<std::byte*>(result) + bytes;
return result;
}
}
// Try again after allocating more storage.
grow(from);
}
}
void monotonic_buffer_resource::grow(bucket& what) {
auto init = [&what](block* blk) {
blk->next = what.head;
what.head = blk;
what.curr_pos = blk->bytes;
what.curr_end = reinterpret_cast<std::byte*>(blk) + what.block_size;
};
if (what.spare != nullptr) {
auto blk = what.spare;
what.spare = blk->next;
init(blk);
} else if (auto ptr = malloc(what.block_size)) {
init(static_cast<block*>(ptr));
} else {
CAF_RAISE_ERROR(std::bad_alloc, "monotonic_buffer_resource");
}
}
monotonic_buffer_resource::bucket&
monotonic_buffer_resource::bucket_by_size(size_t alloc_size) {
constexpr auto max_alloc_size = std::numeric_limits<size_t>::max()
- sizeof(block) - alignof(max_align_t);
auto var_bucket = [this](size_t key, size_t block_size) -> bucket& {
if (auto i = var_.find(key); i != var_.end()) {
return i->second;
} else {
bucket tmp;
tmp.block_size = block_size;
return var_.emplace(key, tmp).first->second;
}
};
if (alloc_size <= 64) {
return small_;
} else if (alloc_size <= 512) {
return medium_;
} else if (alloc_size <= 1'048'576) { // 1MB
// Align on 1kb and reserve memory for up to four elements.
auto bucket_key = ((alloc_size / 1024) + 1) * 1024;
return var_bucket(bucket_key, bucket_key * 4);
} else if (alloc_size <= max_alloc_size) {
// Fall back to individual allocations.
return var_bucket(alloc_size,
alloc_size + sizeof(block) + alignof(max_align_t));
} else {
CAF_RAISE_ERROR(std::bad_alloc, "monotonic_buffer_resource");
}
}
void monotonic_buffer_resource::reset(bucket& bkt) {
bkt.head = nullptr;
bkt.curr_pos = nullptr;
bkt.curr_end = nullptr;
bkt.spare = nullptr;
}
void monotonic_buffer_resource::release(bucket& bkt) {
for (auto ptr = bkt.head; ptr != nullptr;) {
auto blk = ptr;
ptr = ptr->next;
free(blk);
}
for (auto ptr = bkt.spare; ptr != nullptr;) {
auto blk = ptr;
ptr = ptr->next;
free(blk);
}
}
void monotonic_buffer_resource::reclaim(bucket& bkt) {
for (auto ptr = bkt.head; ptr != nullptr;) {
auto blk = ptr;
ptr = ptr->next;
blk->next = bkt.spare;
bkt.spare = blk;
}
bkt.head = nullptr;
bkt.curr_pos = nullptr;
bkt.curr_end = nullptr;
}
} // namespace caf::detail
......@@ -9,30 +9,6 @@
#include <algorithm>
#include <ctime>
namespace {
void escape(std::string& result, char c) {
switch (c) {
default:
result += c;
break;
case '\n':
result += R"(\n)";
break;
case '\t':
result += R"(\t)";
break;
case '\\':
result += R"(\\)";
break;
case '"':
result += R"(\")";
break;
}
}
} // namespace
namespace caf::detail {
bool stringification_inspector::begin_object(type_id_t, string_view name) {
......@@ -180,10 +156,7 @@ bool stringification_inspector::value(string_view str) {
};
if (always_quote_strings
|| std::any_of(str.begin(), str.end(), needs_escaping)) {
result_ += '"';
for (char c : str)
escape(result_, c);
result_ += '"';
detail::print_escaped(result_, str);
} else {
result_.insert(result_.end(), str.begin(), str.end());
}
......
This diff is collapsed.
This diff is collapsed.
......@@ -297,6 +297,119 @@ bool inspect(Inspector& f, dummy_enum& x) {
return f.apply(get, set);
}
struct point {
int32_t x;
int32_t y;
};
[[maybe_unused]] constexpr bool operator==(point a, point b) noexcept {
return a.x == b.x && a.y == b.y;
}
[[maybe_unused]] constexpr bool operator!=(point a, point b) noexcept {
return !(a == b);
}
template <class Inspector>
bool inspect(Inspector& f, point& x) {
return f.object(x).fields(f.field("x", x.x), f.field("y", x.y));
}
struct rectangle {
point top_left;
point bottom_right;
};
template <class Inspector>
bool inspect(Inspector& f, rectangle& x) {
return f.object(x).fields(f.field("top-left", x.top_left),
f.field("bottom-right", x.bottom_right));
}
[[maybe_unused]] constexpr bool operator==(const rectangle& x,
const rectangle& y) noexcept {
return x.top_left == y.top_left && x.bottom_right == y.bottom_right;
}
[[maybe_unused]] constexpr bool operator!=(const rectangle& x,
const rectangle& y) noexcept {
return !(x == y);
}
struct circle {
point center;
int32_t radius;
};
template <class Inspector>
bool inspect(Inspector& f, circle& x) {
return f.object(x).fields(f.field("center", x.center),
f.field("radius", x.radius));
}
[[maybe_unused]] constexpr bool operator==(const circle& x,
const circle& y) noexcept {
return x.center == y.center && x.radius == y.radius;
}
[[maybe_unused]] constexpr bool operator!=(const circle& x,
const circle& y) noexcept {
return !(x == y);
}
struct widget {
std::string color;
caf::variant<rectangle, circle> shape;
};
template <class Inspector>
bool inspect(Inspector& f, widget& x) {
return f.object(x).fields(f.field("color", x.color),
f.field("shape", x.shape));
}
[[maybe_unused]] inline bool operator==(const widget& x,
const widget& y) noexcept {
return x.color == y.color && x.shape == y.shape;
}
[[maybe_unused]] inline bool operator!=(const widget& x,
const widget& y) noexcept {
return !(x == y);
}
struct dummy_user {
std::string name;
caf::optional<std::string> nickname;
};
template <class Inspector>
bool inspect(Inspector& f, dummy_user& x) {
return f.object(x).fields(f.field("name", x.name),
f.field("nickname", x.nickname));
}
struct phone_book {
std::string city;
std::map<std::string, int64_t> entries;
};
[[maybe_unused]] constexpr bool operator==(const phone_book& x,
const phone_book& y) noexcept {
return std::tie(x.city, x.entries) == std::tie(y.city, y.entries);
}
[[maybe_unused]] constexpr bool operator!=(const phone_book& x,
const phone_book& y) noexcept {
return !(x == y);
}
template <class Inspector>
bool inspect(Inspector& f, phone_book& x) {
return f.object(x).fields(f.field("city", x.city),
f.field("entries", x.entries));
}
// -- type IDs for for all unit test suites ------------------------------------
#define ADD_TYPE_ID(type) CAF_ADD_TYPE_ID(core_test, type)
......@@ -307,10 +420,12 @@ CAF_BEGIN_TYPE_ID_BLOCK(core_test, caf::first_custom_type_id)
ADD_TYPE_ID((caf::stream<int32_t>) )
ADD_TYPE_ID((caf::stream<std::pair<level, std::string>>) )
ADD_TYPE_ID((caf::stream<std::string>) )
ADD_TYPE_ID((circle))
ADD_TYPE_ID((dummy_enum))
ADD_TYPE_ID((dummy_enum_class))
ADD_TYPE_ID((dummy_struct))
ADD_TYPE_ID((dummy_tag_type))
ADD_TYPE_ID((dummy_user))
ADD_TYPE_ID((fail_on_copy))
ADD_TYPE_ID((float_actor))
ADD_TYPE_ID((foo_actor))
......@@ -319,7 +434,10 @@ CAF_BEGIN_TYPE_ID_BLOCK(core_test, caf::first_custom_type_id)
ADD_TYPE_ID((int_actor))
ADD_TYPE_ID((level))
ADD_TYPE_ID((my_request))
ADD_TYPE_ID((phone_book))
ADD_TYPE_ID((point))
ADD_TYPE_ID((raw_struct))
ADD_TYPE_ID((rectangle))
ADD_TYPE_ID((s1))
ADD_TYPE_ID((s2))
ADD_TYPE_ID((s3))
......@@ -335,6 +453,7 @@ CAF_BEGIN_TYPE_ID_BLOCK(core_test, caf::first_custom_type_id)
ADD_TYPE_ID((test_array))
ADD_TYPE_ID((test_empty_non_pod))
ADD_TYPE_ID((test_enum))
ADD_TYPE_ID((widget))
ADD_ATOM(abc_atom)
ADD_ATOM(get_state_atom)
......
// 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 detail.base64
#include "caf/detail/base64.hpp"
#include "core-test.hpp"
using namespace caf;
using namespace caf::literals;
using namespace std::literals::string_literals;
using caf::detail::base64;
CAF_TEST(encoding) {
CHECK_EQ(base64::encode("A"_sv), "QQ=="_sv);
CHECK_EQ(base64::encode("AB"_sv), "QUI="_sv);
CHECK_EQ(base64::encode("ABC"_sv), "QUJD"_sv);
CHECK_EQ(base64::encode("https://actor-framework.org"_sv),
"aHR0cHM6Ly9hY3Rvci1mcmFtZXdvcmsub3Jn"_sv);
}
CAF_TEST(decoding) {
CHECK_EQ(base64::decode("QQ=="_sv), "A"s);
CHECK_EQ(base64::decode("QUI="_sv), "AB"s);
CHECK_EQ(base64::decode("QUJD"_sv), "ABC"s);
CHECK_EQ(base64::decode("aHR0cHM6Ly9hY3Rvci1mcmFtZXdvcmsub3Jn"_sv),
"https://actor-framework.org"s);
}
// 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 detail.encode_base64
#include "caf/detail/encode_base64.hpp"
#include "caf/test/dsl.hpp"
#include <array>
using namespace caf;
namespace {
template <class... Ts>
auto encode(Ts... xs) {
std::array<byte, sizeof...(Ts)> bytes{{static_cast<byte>(xs)...}};
return detail::encode_base64(bytes);
}
} // namespace
CAF_TEST(base64 encoding converts byte sequences to strings) {
CAF_CHECK_EQUAL(encode(0xb3, 0x7a, 0x4f, 0x2c, 0xc0, 0x62, 0x4f, 0x16, 0x90,
0xf6, 0x46, 0x06, 0xcf, 0x38, 0x59, 0x45, 0xb2, 0xbe,
0xc4, 0xea),
"s3pPLMBiTxaQ9kYGzzhZRbK+xOo=");
}
// 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 detail.json
#include "caf/detail/json.hpp"
#include "core-test.hpp"
using namespace caf;
using namespace caf::literals;
namespace {
// Worth mentioning: the output we check against is the trivial format produced
// by stringify(), which is not valid JSON due to trailing commas.
constexpr std::pair<string_view, string_view> baselines[] = {
{
R"({})",
R"({})",
},
{
R"( { } )",
R"({})",
},
{
R"(42)",
R"(42)",
},
{
R"(true)",
R"(true)",
},
{
R"(false)",
R"(false)",
},
{
R"(null)",
R"(null)",
},
{
R"({"foo":"bar"})",
R"({
"foo": "bar",
})",
},
{
R"(["foo","bar"])",
R"([
"foo",
"bar",
])",
},
{
R"({
"ints":[1,2,3],"awesome?":true,"ptr":null,"empty-list":[],"nested":{
"hello": "world",
"greeting": "hello world!"
},
"empty-object": {}
})",
R"({
"ints": [
1,
2,
3,
],
"awesome?": true,
"ptr": null,
"empty-list": [],
"nested": {
"hello": "world",
"greeting": "hello world!",
},
"empty-object": {},
})",
},
};
void stringify(std::string& str, size_t indent, const detail::json::value& val);
void stringify(std::string& str, size_t, int64_t val) {
str += std::to_string(val);
}
void stringify(std::string& str, size_t, double val) {
str += std::to_string(val);
}
void stringify(std::string& str, size_t, bool val) {
if (val)
str += "true";
else
str += "false";
}
void stringify(std::string& str, size_t, string_view val) {
str.push_back('"');
str.insert(str.end(), val.begin(), val.end());
str.push_back('"');
}
void stringify(std::string& str, size_t, detail::json::null_t) {
str += "null";
}
void stringify(std::string& str, size_t indent, const detail::json::array& xs) {
if (xs.empty()) {
str += "[]";
} else {
str += "[\n";
for (const auto& x : xs) {
str.insert(str.end(), indent + 2, ' ');
stringify(str, indent + 2, x);
str += ",\n";
}
str.insert(str.end(), indent, ' ');
str += "]";
}
}
void stringify(std::string& str, size_t indent,
const detail::json::object& obj) {
if (obj.empty()) {
str += "{}";
} else {
str += "{\n";
for (const auto& [key, val] : obj) {
str.insert(str.end(), indent + 2, ' ');
stringify(str, indent + 2, key);
str += ": ";
stringify(str, indent + 2, *val);
str += ",\n";
}
str.insert(str.end(), indent, ' ');
str += "}";
}
}
void stringify(std::string& str, size_t indent,
const detail::json::value& val) {
auto f = [&str, indent](auto&& x) { stringify(str, indent, x); };
std::visit(f, val.data);
}
std::string stringify(const detail::json::value& val) {
std::string result;
stringify(result, 0, val);
return result;
}
} // namespace
CAF_TEST(json baselines) {
size_t baseline_index = 0;
detail::monotonic_buffer_resource resource;
for (auto [input, output] : baselines) {
MESSAGE("test baseline at index " << baseline_index++);
string_parser_state ps{input.begin(), input.end()};
auto val = detail::json::parse(ps, &resource);
CHECK_EQ(ps.code, pec::success);
CHECK_EQ(stringify(*val), output);
resource.reclaim();
}
}
// 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 detail.monotonic_buffer_resource
#include "caf/detail/monotonic_buffer_resource.hpp"
#include "core-test.hpp"
#include <list>
#include <map>
#include <vector>
using namespace caf;
SCENARIO("monotonic buffers group allocations") {
GIVEN("a monotonic buffer resource") {
detail::monotonic_buffer_resource mbr;
WHEN("calling allocate multiple times for the same size") {
THEN("the resource returns consecutive pointers") {
CHECK_EQ(mbr.blocks(8), 0u);
auto p1 = mbr.allocate(8);
auto p2 = mbr.allocate(8);
auto p3 = mbr.allocate(8);
CHECK_EQ(mbr.blocks(8), 1u);
CHECK(p1 < p2);
CHECK(p2 < p3);
}
}
}
GIVEN("a monotonic buffer resource") {
detail::monotonic_buffer_resource mbr;
WHEN("calling allocate with various sizes") {
THEN("the resource puts allocations into buckets") {
void* unused = nullptr; // For silencing nodiscard warnings.
CHECK_EQ(mbr.blocks(), 0u);
MESSAGE("perform small allocations");
unused = mbr.allocate(64);
CHECK_EQ(mbr.blocks(), 1u);
unused = mbr.allocate(64);
CHECK_EQ(mbr.blocks(), 1u);
MESSAGE("perform medium allocations");
unused = mbr.allocate(65);
CHECK_EQ(mbr.blocks(), 2u);
unused = mbr.allocate(512);
CHECK_EQ(mbr.blocks(), 2u);
MESSAGE("perform large allocations <= 1 MB (pools allocations)");
unused = mbr.allocate(513);
CHECK_EQ(mbr.blocks(), 3u);
unused = mbr.allocate(1023);
CHECK_EQ(mbr.blocks(), 3u);
MESSAGE("perform large allocations > 1 MB (allocates individually)");
unused = mbr.allocate(1'048'577);
CHECK_EQ(mbr.blocks(), 4u);
unused = mbr.allocate(1'048'577);
CHECK_EQ(mbr.blocks(), 5u);
}
}
}
}
SCENARIO("monotonic buffers re-use small memory blocks after calling reclaim") {
std::vector<void*> locations;
GIVEN("a monotonic buffer resource with some allocations performed on it") {
detail::monotonic_buffer_resource mbr;
locations.push_back(mbr.allocate(64));
locations.push_back(mbr.allocate(64));
locations.push_back(mbr.allocate(65));
locations.push_back(mbr.allocate(512));
WHEN("calling reclaim on the resource") {
mbr.reclaim();
THEN("performing the same allocations returns the same addresses again") {
if (CHECK_EQ(mbr.blocks(), 2u)) {
CHECK_EQ(locations[0], mbr.allocate(64));
CHECK_EQ(locations[1], mbr.allocate(64));
CHECK_EQ(locations[2], mbr.allocate(65));
CHECK_EQ(locations[3], mbr.allocate(512));
CHECK_EQ(mbr.blocks(), 2u);
}
}
}
}
}
SCENARIO("monotonic buffers provide storage for STL containers") {
GIVEN("a monotonic buffer resource and a std::vector") {
using int_allocator = detail::monotonic_buffer_resource::allocator<int32_t>;
detail::monotonic_buffer_resource mbr;
WHEN("pushing to the vector") {
THEN("the memory resource fills up") {
CHECK_EQ(mbr.blocks(), 0u);
std::vector<int32_t, int_allocator> xs{int_allocator{&mbr}};
xs.push_back(42);
CHECK_EQ(xs.size(), 1u);
CHECK_EQ(mbr.blocks(), 1u);
xs.insert(xs.end(), 17, 0);
}
}
}
GIVEN("a monotonic buffer resource and a std::list") {
using int_allocator = detail::monotonic_buffer_resource::allocator<int32_t>;
detail::monotonic_buffer_resource mbr;
WHEN("pushing to the list") {
THEN("the memory resource fills up") {
CHECK_EQ(mbr.blocks(), 0u);
std::list<int32_t, int_allocator> xs{int_allocator{&mbr}};
xs.push_back(42);
CHECK_EQ(xs.size(), 1u);
CHECK_EQ(mbr.blocks(), 1u);
}
}
}
}
// 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_reader
#include "caf/json_reader.hpp"
#include "core-test.hpp"
#include "caf/dictionary.hpp"
using namespace caf;
namespace {
struct fixture {
template <class T>
void add_test_case(string_view input, T val) {
auto f = [this, input, obj{std::move(val)}]() -> bool {
auto tmp = T{};
auto res = CHECK(reader.load(input)) // parse JSON
&& CHECK(reader.apply(tmp)); // deserialize object
if (res) {
if constexpr (std::is_same<T, message>::value)
res = CHECK_EQ(to_string(tmp), to_string(obj));
else
res = CHECK_EQ(tmp, obj);
}
if (!res)
MESSAGE("rejected input: " << input);
return res;
};
test_cases.emplace_back(std::move(f));
}
template <class T, class... Ts>
std::vector<T> ls(Ts... xs) {
std::vector<T> result;
(result.emplace_back(std::move(xs)), ...);
return result;
}
template <class T, class... Ts>
std::set<T> set(Ts... xs) {
std::set<T> result;
(result.emplace(std::move(xs)), ...);
return result;
}
template <class T>
using dict = dictionary<T>;
fixture();
json_reader reader;
std::vector<std::function<bool()>> test_cases;
};
fixture::fixture() {
using i32_list = std::vector<int32_t>;
using str_list = std::vector<std::string>;
using str_set = std::set<std::string>;
add_test_case(R"_(true)_", true);
add_test_case(R"_(false)_", false);
add_test_case(R"_([true, false])_", ls<bool>(true, false));
add_test_case(R"_(42)_", int32_t{42});
add_test_case(R"_([1, 2, 3])_", ls<int32_t>(1, 2, 3));
add_test_case(R"_([[1, 2], [3], []])_",
ls<i32_list>(ls<int32_t>(1, 2), ls<int32_t>(3), ls<int32_t>()));
add_test_case(R"_(2.0)_", 2.0);
add_test_case(R"_([2.0, 4.0, 8.0])_", ls<double>(2.0, 4.0, 8.0));
add_test_case(R"_("hello \"world\"!")_", std::string{R"_(hello "world"!)_"});
add_test_case(R"_(["hello", "world"])_", ls<std::string>("hello", "world"));
add_test_case(R"_(["hello", "world"])_", set<std::string>("hello", "world"));
add_test_case(R"_({"a": 1, "b": 2})_", my_request(1, 2));
add_test_case(R"_({"a": 1, "b": 2})_", dict<int>({{"a", 1}, {"b", 2}}));
add_test_case(R"_({"xs": ["x1", "x2"], "ys": ["y1", "y2"]})_",
dict<str_list>({{"xs", ls<std::string>("x1", "x2")},
{"ys", ls<std::string>("y1", "y2")}}));
add_test_case(R"_({"xs": ["x1", "x2"], "ys": ["y1", "y2"]})_",
dict<str_set>({{"xs", set<std::string>("x1", "x2")},
{"ys", set<std::string>("y1", "y2")}}));
add_test_case(R"_([{"@type": "my_request", "a": 1, "b": 2}])_",
make_message(my_request(1, 2)));
add_test_case(
R"_({"top-left":{"x":100,"y":200},"bottom-right":{"x":10,"y":20}})_",
rectangle{{100, 200}, {10, 20}});
add_test_case(R"({"@type": "phone_book",)"
R"( "city": "Model City",)"
R"( "entries": )"
R"({"Bob": 5556837,)"
R"( "Jon": 5559347}})",
phone_book{"Model City", {{"Bob", 5556837}, {"Jon", 5559347}}});
add_test_case(R"({"@type": "widget", )"
R"("color": "red", )"
R"("@shape-type": "circle", )"
R"("shape": )"
R"({"center": {"x": 15, "y": 15}, )"
R"("radius": 5}})",
widget{"red", circle{{15, 15}, 5}});
add_test_case(R"({"@type": "widget", )"
R"("color": "blue", )"
R"("@shape-type": "rectangle", )"
R"("shape": )"
R"({"top-left": {"x": 10, "y": 10}, )"
R"("bottom-right": {"x": 20, "y": 20}}})",
widget{"blue", rectangle{{10, 10}, {20, 20}}});
}
} // namespace
CAF_TEST_FIXTURE_SCOPE(json_reader_tests, fixture)
CAF_TEST(json baselines) {
size_t baseline_index = 0;
detail::monotonic_buffer_resource resource;
for (auto& f : test_cases) {
MESSAGE("test case at index " << baseline_index++);
if (!f())
if (auto reason = reader.get_error())
MESSAGE("JSON reader stopped due to: " << reason);
}
}
CAF_TEST_FIXTURE_SCOPE_END()
// 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_writer
#include "caf/json_writer.hpp"
#include "core-test.hpp"
using namespace caf;
using namespace std::literals::string_literals;
namespace {
struct fixture {
template <class T>
expected<std::string>
to_json_string(T&& x, size_t indentation,
bool skip_empty_fields
= json_writer::skip_empty_fields_default) {
json_writer writer;
writer.indentation(indentation);
writer.skip_empty_fields(skip_empty_fields);
if (writer.apply(std::forward<T>(x))) {
auto buf = writer.str();
return {std::string{buf.begin(), buf.end()}};
} else {
MESSAGE("partial JSON output: " << writer.str());
return {writer.get_error()};
}
}
};
} // namespace
CAF_TEST_FIXTURE_SCOPE(json_writer_tests, fixture)
SCENARIO("the JSON writer converts builtin types to strings") {
GIVEN("an integer") {
auto x = 42;
WHEN("converting it to JSON with any indentation factor") {
THEN("the JSON output is the number") {
CHECK_EQ(to_json_string(x, 0), "42"s);
CHECK_EQ(to_json_string(x, 2), "42"s);
}
}
}
GIVEN("a string") {
std::string x = R"_(hello "world"!)_";
WHEN("converting it to JSON with any indentation factor") {
THEN("the JSON output is the escaped string") {
std::string out = R"_("hello \"world\"!")_";
CHECK_EQ(to_json_string(x, 0), out);
CHECK_EQ(to_json_string(x, 2), out);
}
}
}
GIVEN("a list") {
auto x = std::vector<int>{1, 2, 3};
WHEN("converting it to JSON with indentation factor 0") {
THEN("the JSON output is a single line") {
std::string out = "[1, 2, 3]";
CHECK_EQ(to_json_string(x, 0), out);
}
}
WHEN("converting it to JSON with indentation factor 2") {
THEN("the JSON output uses multiple lines") {
std::string out = R"_([
1,
2,
3
])_";
CHECK_EQ(to_json_string(x, 2), out);
}
}
}
GIVEN("a dictionary") {
std::map<std::string, std::string> x;
x.emplace("a", "A");
x.emplace("b", "B");
x.emplace("c", "C");
WHEN("converting it to JSON with indentation factor 0") {
THEN("the JSON output is a single line") {
CHECK_EQ(to_json_string(x, 0), R"_({"a": "A", "b": "B", "c": "C"})_"s);
}
}
WHEN("converting it to JSON with indentation factor 2") {
THEN("the JSON output uses multiple lines") {
std::string out = R"_({
"a": "A",
"b": "B",
"c": "C"
})_";
CHECK_EQ(to_json_string(x, 2), out);
}
}
}
GIVEN("a message") {
auto x = make_message(put_atom_v, "foo", 42);
WHEN("converting it to JSON with indentation factor 0") {
THEN("the JSON output is a single line") {
std::string out = R"_([{"@type": "caf::put_atom"}, "foo", 42])_";
CHECK_EQ(to_json_string(x, 0), out);
}
}
WHEN("converting it to JSON with indentation factor 2") {
THEN("the JSON output uses multiple lines") {
std::string out = R"_([
{
"@type": "caf::put_atom"
},
"foo",
42
])_";
CHECK_EQ(to_json_string(x, 2), out);
}
}
}
}
SCENARIO("the JSON writer converts simple structs to strings") {
GIVEN("a dummy_struct object") {
dummy_struct x{10, "foo"};
WHEN("converting it to JSON with indentation factor 0") {
THEN("the JSON output is a single line") {
std::string out = R"_({"@type": "dummy_struct", "a": 10, "b": "foo"})_";
CHECK_EQ(to_json_string(x, 0), out);
}
}
WHEN("converting it to JSON with indentation factor 2") {
THEN("the JSON output uses multiple lines") {
std::string out = R"_({
"@type": "dummy_struct",
"a": 10,
"b": "foo"
})_";
CHECK_EQ(to_json_string(x, 2), out);
}
}
}
}
SCENARIO("the JSON writer converts nested structs to strings") {
GIVEN("a rectangle object") {
auto x = rectangle{{100, 200}, {10, 20}};
WHEN("converting it to JSON with indentation factor 0") {
THEN("the JSON output is a single line") {
std::string out = R"({"@type": "rectangle", )"
R"("top-left": {"x": 100, "y": 200}, )"
R"("bottom-right": {"x": 10, "y": 20}})";
CHECK_EQ(to_json_string(x, 0), out);
}
}
WHEN("converting it to JSON with indentation factor 2") {
THEN("the JSON output uses multiple lines") {
std::string out = R"_({
"@type": "rectangle",
"top-left": {
"x": 100,
"y": 200
},
"bottom-right": {
"x": 10,
"y": 20
}
})_";
CHECK_EQ(to_json_string(x, 2), out);
}
}
}
}
SCENARIO("the JSON writer converts structs with member dictionaries") {
GIVEN("a phone_book object") {
phone_book x;
x.city = "Model City";
x.entries["Bob"] = 555'6837;
x.entries["Jon"] = 555'9347;
WHEN("converting it to JSON with indentation factor 0") {
THEN("the JSON output is a single line") {
std::string out = R"({"@type": "phone_book",)"
R"( "city": "Model City",)"
R"( "entries": )"
R"({"Bob": 5556837,)"
R"( "Jon": 5559347}})";
CHECK_EQ(to_json_string(x, 0), out);
}
}
WHEN("converting it to JSON with indentation factor 2") {
THEN("the JSON output uses multiple lines") {
std::string out = R"({
"@type": "phone_book",
"city": "Model City",
"entries": {
"Bob": 5556837,
"Jon": 5559347
}
})";
CHECK_EQ(to_json_string(x, 2), out);
}
}
}
}
SCENARIO("the JSON writer omits or nulls missing values") {
GIVEN("a dummy_user object without nickname") {
dummy_user user;
user.name = "Bjarne";
WHEN("converting it to JSON with skip_empty_fields = true (default)") {
THEN("the JSON output omits the field 'nickname'") {
std::string out = R"({"@type": "dummy_user", "name": "Bjarne"})";
CHECK_EQ(to_json_string(user, 0), out);
}
}
WHEN("converting it to JSON with skip_empty_fields = false") {
THEN("the JSON output includes the field 'nickname' with a null value") {
std::string out
= R"({"@type": "dummy_user", "name": "Bjarne", "nickname": null})";
CHECK_EQ(to_json_string(user, 0, false), out);
}
}
}
}
SCENARIO("the JSON writer annotates variant fields") {
GIVEN("a widget object with rectangle shape") {
widget x;
x.color = "red";
x.shape = rectangle{{10, 10}, {20, 20}};
WHEN("converting it to JSON with indentation factor 0") {
THEN("the JSON is a single line containing '@shape-type = rectangle'") {
std::string out = R"({"@type": "widget", )"
R"("color": "red", )"
R"("@shape-type": "rectangle", )"
R"("shape": )"
R"({"top-left": {"x": 10, "y": 10}, )"
R"("bottom-right": {"x": 20, "y": 20}}})";
CHECK_EQ(to_json_string(x, 0), out);
}
}
WHEN("converting it to JSON with indentation factor 2") {
THEN("the JSON is multiple lines containing '@shape-type = rectangle'") {
std::string out = R"({
"@type": "widget",
"color": "red",
"@shape-type": "rectangle",
"shape": {
"top-left": {
"x": 10,
"y": 10
},
"bottom-right": {
"x": 20,
"y": 20
}
}
})";
CHECK_EQ(to_json_string(x, 2), out);
}
}
}
GIVEN("a widget object with circle shape") {
widget x;
x.color = "red";
x.shape = circle{{15, 15}, 5};
WHEN("converting it to JSON with indentation factor 0") {
THEN("the JSON is a single line containing '@shape-type = circle'") {
std::string out = R"({"@type": "widget", )"
R"("color": "red", )"
R"("@shape-type": "circle", )"
R"("shape": )"
R"({"center": {"x": 15, "y": 15}, )"
R"("radius": 5}})";
CHECK_EQ(to_json_string(x, 0), out);
}
}
WHEN("converting it to JSON with indentation factor 2") {
THEN("the JSON is multiple lines containing '@shape-type = circle'") {
std::string out = R"({
"@type": "widget",
"color": "red",
"@shape-type": "circle",
"shape": {
"center": {
"x": 15,
"y": 15
},
"radius": 5
}
})";
CHECK_EQ(to_json_string(x, 2), out);
}
}
}
}
CAF_TEST_FIXTURE_SCOPE_END()
// 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 mtl
#include "caf/mtl.hpp"
#include "core-test.hpp"
#include "caf/json_reader.hpp"
#include "caf/json_writer.hpp"
#include "caf/typed_actor.hpp"
#include "caf/typed_event_based_actor.hpp"
using namespace caf;
namespace {
using testee_actor = typed_actor<result<void>(put_atom, std::string, int32_t),
result<int32_t>(get_atom, std::string)>;
struct testee_state {
static inline const char* name = "testee";
std::map<std::string, std::int32_t> kv_store;
testee_actor::behavior_type make_behavior() {
return {
[this](put_atom, const std::string& key, int32_t val) {
kv_store[key] = val;
},
[this](get_atom, const std::string& key) -> result<int32_t> {
if (auto i = kv_store.find(key); i != kv_store.end())
return {i->second};
else
return {make_error(sec::runtime_error, "key not found")};
},
};
}
};
using testee_impl = testee_actor::stateful_impl<testee_state>;
template <class T>
struct kvp_field_name;
template <>
struct kvp_field_name<std::string> {
static constexpr string_view value = "key";
};
template <>
struct kvp_field_name<int32_t> {
static constexpr string_view value = "value";
};
template <class T>
constexpr string_view kvp_field_name_v = kvp_field_name<T>::value;
// Adapter for converting atom-prefixed message to pseudo-objects.
struct adapter {
template <class Inspector, class Atom, class... Ts>
bool read(Inspector& f, Atom, Ts&... xs) {
auto type_annotation = type_name_v<Atom>;
if (f.assert_next_object_name(type_annotation)
&& f.virtual_object(type_annotation)
.fields(f.field(kvp_field_name_v<Ts>, xs)...)) {
last_read = make_type_id_list<Atom, Ts...>();
return true;
} else {
return false;
}
}
template <class Inspector>
bool write(Inspector& f, int32_t result) {
return f.apply(result);
}
template <class Inspector>
bool write(Inspector& f) {
return f.apply(unit);
}
// Stores the type IDs for the last successful read.
type_id_list last_read = make_type_id_list();
};
struct driver_state {
static inline const char* name = "driver";
event_based_actor* self;
testee_actor kvs;
json_reader reader;
json_writer writer;
driver_state(event_based_actor* self, testee_actor kvs)
: self(self), kvs(std::move(kvs)) {
// nop
}
behavior make_behavior() {
return {
[this](const std::string& mode,
const std::string& json_text) -> result<message> {
reader.load(json_text);
auto mtl = make_mtl(self, adapter{}, &reader);
CHECK(mtl.self() == self);
CHECK(std::addressof(mtl.reader()) == &reader);
if (mode == "try_send") {
CHECK(mtl.try_send(kvs));
MESSAGE("adapter generated: " << mtl.adapter().last_read);
return make_message();
} else {
CAF_ASSERT(mode == "try_request");
auto rp = self->make_response_promise();
auto on_result = [this, rp](auto&... xs) mutable {
// Must receive either an int32_t or an empty message.
if constexpr (sizeof...(xs) == 1) {
CHECK_EQ(make_type_id_list<int32_t>(),
make_type_id_list<std::decay_t<decltype(xs)>...>());
} else {
static_assert(sizeof...(xs) == 0);
}
// Convert input to JSON and fulfill the promise using the string.
writer.reset();
adapter{}.write(writer, xs...);
rp.deliver(to_string(writer.str()));
};
auto on_error = [rp](error& err) mutable {
rp.deliver(std::move(err));
};
CHECK(mtl.try_request(kvs, infinite, on_result, on_error));
MESSAGE("adapter generated: " << mtl.adapter().last_read);
return rp;
}
},
[](int32_t) {
// nop
},
};
}
};
using driver_impl = stateful_actor<driver_state>;
struct fixture : test_coordinator_fixture<> {
testee_actor testee;
actor driver;
fixture() {
testee = sys.spawn<testee_impl, lazy_init>();
driver = sys.spawn<driver_impl, lazy_init>(testee);
}
};
} // namespace
BEGIN_FIXTURE_SCOPE(fixture)
SCENARIO("an MTL allows sending asynchronous messages") {
GIVEN("a driver using an MTL to communicate to the testee") {
WHEN("sending a JSON put message to the driver") {
std::string put = R"({"@type": "caf::put_atom", "key": "a", "value": 1})";
THEN("try_send generates a CAF put message to the testee") {
inject((std::string, std::string),
from(self).to(driver).with("try_send", put));
expect((put_atom, std::string, int32_t),
from(driver).to(testee).with(_, "a", 1));
CHECK(!sched.has_job());
}
}
WHEN("send a JSON get message to the driver afterwards") {
std::string get = R"({"@type": "caf::get_atom", "key": "a"})";
THEN("try_send generates a CAF get message to the testee") {
inject((std::string, std::string),
from(self).to(driver).with("try_send", get));
expect((get_atom, std::string), from(driver).to(testee).with(_, "a"));
expect((int32_t), from(testee).to(driver).with(_, 1));
CHECK(!sched.has_job());
}
}
}
}
SCENARIO("an MTL allows sending requests") {
GIVEN("a driver using an MTL to communicate to the testee") {
WHEN("sending a JSON put message to the driver") {
std::string put = R"({"@type": "caf::put_atom", "key": "a", "value": 1})";
THEN("try_request generates a CAF put message to the testee") {
inject((std::string, std::string),
from(self).to(driver).with("try_request", put));
expect((put_atom, std::string, int32_t),
from(driver).to(testee).with(_, "a", 1));
expect((void), from(testee).to(driver));
expect((std::string),
from(driver).to(self).with(R"({"@type": "caf::unit_t"})"));
CHECK(!sched.has_job());
}
}
WHEN("send a JSON get message to the driver afterwards") {
std::string get = R"({"@type": "caf::get_atom", "key": "a"})";
THEN("try_request generates a CAF get message to the testee") {
inject((std::string, std::string),
from(self).to(driver).with("try_request", get));
expect((get_atom, std::string), from(driver).to(testee).with(_, "a"));
expect((int32_t), from(testee).to(driver).with(_, 1));
expect((std::string), from(driver).to(self).with("1"));
CHECK(!sched.has_job());
}
}
}
}
END_FIXTURE_SCOPE()
......@@ -42,6 +42,8 @@
#include "caf/detail/stringification_inspector.hpp"
#include "caf/detail/type_traits.hpp"
#include "caf/event_based_actor.hpp"
#include "caf/json_reader.hpp"
#include "caf/json_writer.hpp"
#include "caf/message.hpp"
#include "caf/message_handler.hpp"
#include "caf/proxy_registry.hpp"
......@@ -143,6 +145,10 @@ struct fixture : test_coordinator_fixture<> {
message msg;
message recursive;
json_writer jwriter;
json_reader jreader;
template <class... Ts>
byte_buffer serialize(const Ts&... xs) {
byte_buffer buf;
......@@ -161,12 +167,39 @@ struct fixture : test_coordinator_fixture<> {
CAF_FAIL("deserialization failed: " << source.get_error());
}
template <class... Ts>
std::string serialize_json(const Ts&... xs) {
jwriter.reset();
if (!(jwriter.apply(xs) && ...))
CAF_FAIL("JSON serialization failed: "
<< jwriter.get_error()
<< ", data: " << deep_to_string(std::forward_as_tuple(xs...)));
auto str = jwriter.str();
return std::string{str.begin(), str.end()};
}
template <class... Ts>
void deserialize_json(const std::string& str, Ts&... xs) {
if (!jreader.load(str))
CAF_FAIL("JSON loading failed: " << jreader.get_error()
<< "\n input: " << str);
if (!(jreader.apply(xs) && ...))
CAF_FAIL("JSON deserialization failed: " << jreader.get_error()
<< "\n input: " << str);
}
// serializes `x` and then deserializes and returns the serialized value
template <class T>
T roundtrip(T x) {
T result;
deserialize(serialize(x), result);
return result;
T roundtrip(T x, bool enable_json = true) {
auto r1 = T{};
deserialize(serialize(x), r1);
if (enable_json) {
auto r2 = T{};
deserialize_json(serialize_json(x), r2);
if (!CAF_CHECK_EQUAL(r1, r2))
CAF_MESSAGE("generated JSON: " << serialize_json(x));
}
return r1;
}
// converts `x` to a message, serialize it, then deserializes it, and
......@@ -184,7 +217,7 @@ struct fixture : test_coordinator_fixture<> {
return result.get_as<T>(0);
}
fixture() {
fixture() : jwriter(sys), jreader(sys) {
rs.str.assign(std::string(str.rbegin(), str.rend()));
msg = make_message(i32, i64, ts, te, str, rs);
config_value::dictionary dict;
......@@ -217,12 +250,20 @@ struct is_message {
} // namespace
#define CHECK_RT(val) CAF_CHECK_EQUAL(val, roundtrip(val))
#define CHECK_RT(val) \
do { \
CAF_MESSAGE(#val); \
CAF_CHECK_EQUAL(val, roundtrip(val)); \
} while (false)
#define CHECK_PRED_RT(pred, value) CAF_CHECK(pred(roundtrip(value)))
#define CHECK_PRED_RT(pred, value) \
do { \
CAF_MESSAGE(#pred "(" #value ")"); \
CAF_CHECK(pred(roundtrip(value, false))); \
} while (false)
#define CHECK_SIGN_RT(value) \
CAF_CHECK_EQUAL(std::signbit(roundtrip(value)), std::signbit(value))
CAF_CHECK_EQUAL(std::signbit(roundtrip(value, false)), std::signbit(value))
#define CHECK_MSG_RT(val) CAF_CHECK_EQUAL(val, msg_roundtrip(val))
......@@ -328,7 +369,7 @@ CAF_TEST(messages) {
deserialize(buf2, y);
CAF_CHECK_EQUAL(to_string(msg), to_string(y));
CAF_CHECK(is_message(y).equal(i32, i64, ts, te, str, rs));
CAF_CHECK_EQUAL(to_string(recursive), to_string(roundtrip(recursive)));
CAF_CHECK_EQUAL(to_string(recursive), to_string(roundtrip(recursive, false)));
}
CAF_TEST(multiple_messages) {
......@@ -370,11 +411,11 @@ CAF_TEST(variant_with_tree_types) {
CAF_MESSAGE("deserializing into a non-empty vector overrides any content");
using test_variant = variant<int, double, std::string>;
test_variant x{42};
CAF_CHECK_EQUAL(x, roundtrip(x));
CAF_CHECK_EQUAL(x, roundtrip(x, false));
x = 12.34;
CAF_CHECK_EQUAL(x, roundtrip(x));
CAF_CHECK_EQUAL(x, roundtrip(x, false));
x = std::string{"foobar"};
CAF_CHECK_EQUAL(x, roundtrip(x));
CAF_CHECK_EQUAL(x, roundtrip(x, false));
}
// -- our vector<bool> serialization packs into an uint64_t. Hence, the
......@@ -456,10 +497,10 @@ CAF_TEST(serializers handle actor handles) {
[](int i) { return i; },
};
});
CAF_CHECK_EQUAL(dummy, roundtrip(dummy));
CAF_CHECK_EQUAL(dummy, roundtrip(dummy, false));
CAF_CHECK_EQUAL(dummy, msg_roundtrip(dummy));
std::vector<strong_actor_ptr> wrapped{actor_cast<strong_actor_ptr>(dummy)};
CAF_CHECK_EQUAL(wrapped, roundtrip(wrapped));
CAF_CHECK_EQUAL(wrapped, roundtrip(wrapped, false));
CAF_CHECK_EQUAL(wrapped, msg_roundtrip(wrapped));
anon_send_exit(dummy, exit_reason::user_shutdown);
}
......
......@@ -40,7 +40,7 @@ CAF_TEST_FIXTURE_SCOPE(receive_buffer_tests, fixture)
CAF_TEST(constructors) {
CAF_CHECK_EQUAL(a.size(), 0ul);
CAF_CHECK_EQUAL(a.capacity(), 0ul);
CAF_CHECK_EQUAL(a.data(), nullptr);
CAF_CHECK(a.data() == nullptr);
CAF_CHECK(a.empty());
CAF_CHECK_EQUAL(b.size(), 1024ul);
CAF_CHECK_EQUAL(b.capacity(), 1024ul);
......
......@@ -573,9 +573,10 @@ using caf_test_case_auto_fixture = caf::test::dummy_fixture;
#define CAF_REQUIRE(...) \
do { \
auto CAF_UNIQUE(__result) = ::caf::test::detail::check( \
::caf::test::engine::current_test(), __FILE__, __LINE__, #__VA_ARGS__, \
false, static_cast<bool>(__VA_ARGS__)); \
auto CAF_UNIQUE(__result) \
= ::caf::test::detail::check(::caf::test::engine::current_test(), \
__FILE__, __LINE__, #__VA_ARGS__, false, \
static_cast<bool>(__VA_ARGS__)); \
if (!CAF_UNIQUE(__result)) \
::caf::test::detail::requirement_failed(#__VA_ARGS__); \
::caf::test::engine::last_check_file(__FILE__); \
......@@ -630,78 +631,82 @@ using caf_test_case_auto_fixture = caf::test::dummy_fixture;
#define CAF_CHECK_EQUAL(x_expr, y_expr) \
[](const auto& x_val, const auto& y_val) { \
return ::caf::test::detail::check_bin(x_val == y_val, __FILE__, __LINE__, \
#x_expr " == " #y_expr, \
caf::deep_to_string(x_val), \
caf::deep_to_string(y_val)); \
return ::caf::test::detail::check_bin( \
x_val == y_val, __FILE__, __LINE__, #x_expr " == " #y_expr, \
caf::detail::stringification_inspector::render(x_val), \
caf::detail::stringification_inspector::render(y_val)); \
}(x_expr, y_expr)
#define CAF_CHECK_NOT_EQUAL(x_expr, y_expr) \
[](const auto& x_val, const auto& y_val) { \
return ::caf::test::detail::check_bin(x_val != y_val, __FILE__, __LINE__, \
#x_expr " != " #y_expr, \
caf::deep_to_string(x_val), \
caf::deep_to_string(y_val)); \
return ::caf::test::detail::check_bin( \
x_val != y_val, __FILE__, __LINE__, #x_expr " != " #y_expr, \
caf::detail::stringification_inspector::render(x_val), \
caf::detail::stringification_inspector::render(y_val)); \
}(x_expr, y_expr)
#define CAF_CHECK_LESS(x_expr, y_expr) \
[](const auto& x_val, const auto& y_val) { \
return ::caf::test::detail::check_bin(x_val < y_val, __FILE__, __LINE__, \
#x_expr " < " #y_expr, \
caf::deep_to_string(x_val), \
caf::deep_to_string(y_val)); \
return ::caf::test::detail::check_bin( \
x_val < y_val, __FILE__, __LINE__, #x_expr " < " #y_expr, \
caf::detail::stringification_inspector::render(x_val), \
caf::detail::stringification_inspector::render(y_val)); \
}(x_expr, y_expr)
#define CAF_CHECK_NOT_LESS(x_expr, y_expr) \
[](const auto& x_val, const auto& y_val) { \
return ::caf::test::detail::check_bin( \
!(x_val < y_val), __FILE__, __LINE__, "not " #x_expr " < " #y_expr, \
caf::deep_to_string(x_val), caf::deep_to_string(y_val)); \
caf::detail::stringification_inspector::render(x_val), \
caf::detail::stringification_inspector::render(y_val)); \
}(x_expr, y_expr)
#define CAF_CHECK_LESS_OR_EQUAL(x_expr, y_expr) \
[](const auto& x_val, const auto& y_val) { \
return ::caf::test::detail::check_bin(x_val <= y_val, __FILE__, __LINE__, \
#x_expr " <= " #y_expr, \
caf::deep_to_string(x_val), \
caf::deep_to_string(y_val)); \
return ::caf::test::detail::check_bin( \
x_val <= y_val, __FILE__, __LINE__, #x_expr " <= " #y_expr, \
caf::detail::stringification_inspector::render(x_val), \
caf::detail::stringification_inspector::render(y_val)); \
}(x_expr, y_expr)
#define CAF_CHECK_NOT_LESS_OR_EQUAL(x_expr, y_expr) \
[](const auto& x_val, const auto& y_val) { \
return ::caf::test::detail::check_bin( \
!(x_val <= y_val), __FILE__, __LINE__, "not " #x_expr " <= " #y_expr, \
caf::deep_to_string(x_val), caf::deep_to_string(y_val)); \
caf::detail::stringification_inspector::render(x_val), \
caf::detail::stringification_inspector::render(y_val)); \
}(x_expr, y_expr)
#define CAF_CHECK_GREATER(x_expr, y_expr) \
[](const auto& x_val, const auto& y_val) { \
return ::caf::test::detail::check_bin(x_val > y_val, __FILE__, __LINE__, \
#x_expr " > " #y_expr, \
caf::deep_to_string(x_val), \
caf::deep_to_string(y_val)); \
return ::caf::test::detail::check_bin( \
x_val > y_val, __FILE__, __LINE__, #x_expr " > " #y_expr, \
caf::detail::stringification_inspector::render(x_val), \
caf::detail::stringification_inspector::render(y_val)); \
}(x_expr, y_expr)
#define CAF_CHECK_NOT_GREATER(x_expr, y_expr) \
[](const auto& x_val, const auto& y_val) { \
return ::caf::test::detail::check_bin( \
!(x_val > y_val), __FILE__, __LINE__, "not " #x_expr " > " #y_expr, \
caf::deep_to_string(x_val), caf::deep_to_string(y_val)); \
caf::detail::stringification_inspector::render(x_val), \
caf::detail::stringification_inspector::render(y_val)); \
}(x_expr, y_expr)
#define CAF_CHECK_GREATER_OR_EQUAL(x_expr, y_expr) \
[](const auto& x_val, const auto& y_val) { \
return ::caf::test::detail::check_bin(x_val >= y_val, __FILE__, __LINE__, \
#x_expr " >= " #y_expr, \
caf::deep_to_string(x_val), \
caf::deep_to_string(y_val)); \
return ::caf::test::detail::check_bin( \
x_val >= y_val, __FILE__, __LINE__, #x_expr " >= " #y_expr, \
caf::detail::stringification_inspector::render(x_val), \
caf::detail::stringification_inspector::render(y_val)); \
}(x_expr, y_expr)
#define CAF_CHECK_NOT_GREATER_OR_EQUAL(x_expr, y_expr) \
[](const auto& x_val, const auto& y_val) { \
return ::caf::test::detail::check_bin( \
!(x_val >= y_val), __FILE__, __LINE__, "not " #x_expr " >= " #y_expr, \
caf::deep_to_string(x_val), caf::deep_to_string(y_val)); \
caf::detail::stringification_inspector::render(x_val), \
caf::detail::stringification_inspector::render(y_val)); \
}(x_expr, y_expr)
// -- CAF_CHECK* predicate family ----------------------------------------------
......
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