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