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
ab7535c7
Commit
ab7535c7
authored
Nov 22, 2020
by
Dominik Charousset
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Implement new get_as function for config_value
parent
dc04d789
Changes
12
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
1303 additions
and
161 deletions
+1303
-161
libcaf_core/caf/config_value.hpp
libcaf_core/caf/config_value.hpp
+324
-13
libcaf_core/caf/detail/bounds_checker.hpp
libcaf_core/caf/detail/bounds_checker.hpp
+7
-0
libcaf_core/caf/detail/parse.hpp
libcaf_core/caf/detail/parse.hpp
+32
-14
libcaf_core/caf/detail/print.hpp
libcaf_core/caf/detail/print.hpp
+8
-0
libcaf_core/caf/dictionary.hpp
libcaf_core/caf/dictionary.hpp
+0
-14
libcaf_core/caf/settings.hpp
libcaf_core/caf/settings.hpp
+3
-0
libcaf_core/src/config_value.cpp
libcaf_core/src/config_value.cpp
+313
-40
libcaf_core/src/detail/stringification_inspector.cpp
libcaf_core/src/detail/stringification_inspector.cpp
+2
-4
libcaf_core/src/settings.cpp
libcaf_core/src/settings.cpp
+2
-0
libcaf_core/test/config_value.cpp
libcaf_core/test/config_value.cpp
+602
-70
libcaf_core/test/core-test.hpp
libcaf_core/test/core-test.hpp
+6
-2
libcaf_core/test/detail/parser/read_config.cpp
libcaf_core/test/detail/parser/read_config.cpp
+4
-4
No files found.
libcaf_core/caf/config_value.hpp
View file @
ab7535c7
...
...
@@ -19,6 +19,8 @@
#pragma once
#include <chrono>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <iosfwd>
#include <iterator>
...
...
@@ -36,8 +38,10 @@
#include "caf/detail/parse.hpp"
#include "caf/detail/type_traits.hpp"
#include "caf/dictionary.hpp"
#include "caf/expected.hpp"
#include "caf/fwd.hpp"
#include "caf/inspector_access.hpp"
#include "caf/inspector_access_type.hpp"
#include "caf/optional.hpp"
#include "caf/raise_error.hpp"
#include "caf/string_algorithms.hpp"
...
...
@@ -45,6 +49,7 @@
#include "caf/sum_type.hpp"
#include "caf/sum_type_access.hpp"
#include "caf/sum_type_token.hpp"
#include "caf/timespan.hpp"
#include "caf/timestamp.hpp"
#include "caf/uri.hpp"
#include "caf/variant.hpp"
...
...
@@ -56,6 +61,11 @@ namespace caf {
/// contain lists of themselves.
class
CAF_CORE_EXPORT
config_value
{
public:
// -- friends ----------------------------------------------------------------
template
<
class
T
>
friend
expected
<
T
>
get_as
(
const
config_value
&
value
);
// -- member types -----------------------------------------------------------
using
integer
=
int64_t
;
...
...
@@ -64,16 +74,14 @@ public:
using
real
=
double
;
using
timespan
=
caf
::
timespan
;
using
string
=
std
::
string
;
using
list
=
std
::
vector
<
config_value
>
;
using
dictionary
=
caf
::
dictionary
<
config_value
>
;
using
types
=
detail
::
type_list
<
integer
,
boolean
,
real
,
timespan
,
uri
,
string
,
list
,
dictionary
>
;
using
types
=
detail
::
type_list
<
none_t
,
integer
,
boolean
,
real
,
timespan
,
uri
,
string
,
list
,
dictionary
>
;
using
variant_type
=
detail
::
tl_apply_t
<
types
,
variant
>
;
...
...
@@ -115,8 +123,8 @@ public:
// -- properties -------------------------------------------------------------
/// Converts the value to a list with one element
. Does nothing if the
value
/// already is a list.
/// Converts the value to a list with one element
(unless the config
value
///
holds `nullptr`). Does nothing if the value
already is a list.
void
convert_to_list
();
/// Returns the value as a list, converting it to one if needed.
...
...
@@ -152,6 +160,53 @@ public:
return
&
data_
;
}
/// Checks whether this config value is not null.
explicit
operator
bool
()
const
noexcept
{
return
data_
.
index
()
!=
0
;
}
/// Checks whether this config value is null.
bool
operator
!
()
const
noexcept
{
return
data_
.
index
()
==
0
;
}
// -- utility ----------------------------------------------------------------
/// @private
error_code
<
sec
>
default_construct
(
type_id_t
);
/// @private
expected
<
bool
>
to_boolean
()
const
;
/// @private
expected
<
integer
>
to_integer
()
const
;
/// @private
expected
<
real
>
to_real
()
const
;
/// @private
expected
<
timespan
>
to_timespan
()
const
;
/// @private
expected
<
list
>
to_list
()
const
;
/// @private
expected
<
dictionary
>
to_dictionary
()
const
;
/// @private
bool
can_convert_to_dictionary
()
const
;
/// @private
template
<
class
T
,
class
Token
>
expected
<
T
>
convert_to
(
Token
token
)
const
{
auto
tmp
=
T
{};
config_value_reader
reader
{
this
};
if
(
detail
::
load
(
reader
,
tmp
,
token
))
return
{
std
::
move
(
tmp
)};
else
return
{
reader
.
move_error
()};
}
private:
// -- properties -------------------------------------------------------------
...
...
@@ -159,6 +214,10 @@ private:
// -- auto conversion of related types ---------------------------------------
void
set
(
none_t
)
{
data_
=
none
;
}
void
set
(
bool
x
)
{
data_
=
x
;
}
...
...
@@ -217,6 +276,9 @@ private:
variant_type
data_
;
};
/// @relates config_value
CAF_CORE_EXPORT
std
::
string
to_string
(
const
config_value
&
x
);
// -- convenience constants ----------------------------------------------------
/// Type of the `top_level_cli_parsing` constant.
...
...
@@ -233,6 +295,232 @@ using nested_cli_parsing_t = std::true_type;
/// `parse_cli`.
constexpr
auto
nested_cli_parsing
=
nested_cli_parsing_t
{};
// -- conversion via get_as ----------------------------------------------------
template
<
class
T
>
expected
<
T
>
get_as
(
const
config_value
&
value
);
template
<
class
T
>
expected
<
T
>
get_as
(
const
config_value
&
,
inspector_access_type
::
none
)
{
static_assert
(
detail
::
always_false_v
<
T
>
,
"cannot convert to T: found no a suitable inspect overload"
);
}
template
<
class
T
>
expected
<
T
>
get_as
(
const
config_value
&
,
inspector_access_type
::
unsafe
)
{
static_assert
(
detail
::
always_false_v
<
T
>
,
"cannot convert types that are tagged as unsafe"
);
}
template
<
class
T
>
expected
<
T
>
get_as
(
const
config_value
&
x
,
inspector_access_type
::
specialization
token
)
{
return
x
.
convert_to
<
T
>
(
token
);
}
template
<
class
T
>
expected
<
T
>
get_as
(
const
config_value
&
x
,
inspector_access_type
::
inspect
token
)
{
return
x
.
convert_to
<
T
>
(
token
);
}
template
<
class
T
>
expected
<
T
>
get_as
(
const
config_value
&
x
,
inspector_access_type
::
builtin_inspect
token
)
{
return
x
.
convert_to
<
T
>
(
token
);
}
template
<
class
T
>
expected
<
T
>
get_as
(
const
config_value
&
x
,
inspector_access_type
::
builtin
)
{
if
constexpr
(
std
::
is_same
<
T
,
std
::
string
>::
value
)
{
return
to_string
(
x
);
}
else
if
constexpr
(
std
::
is_same
<
T
,
bool
>::
value
)
{
return
x
.
to_boolean
();
}
else
if
constexpr
(
std
::
is_integral
<
T
>::
value
)
{
if
(
auto
result
=
x
.
to_integer
())
{
if
(
detail
::
bounds_checker
<
T
>::
check
(
*
result
))
return
*
result
;
else
return
make_error
(
sec
::
conversion_failed
,
"narrowing error"
);
}
else
{
return
std
::
move
(
result
.
error
());
}
}
else
if
constexpr
(
std
::
is_floating_point
<
T
>::
value
)
{
if
(
auto
result
=
x
.
to_real
())
{
if
constexpr
(
sizeof
(
T
)
>=
sizeof
(
config_value
::
real
))
{
return
*
result
;
}
else
{
auto
narrowed
=
static_cast
<
T
>
(
*
result
);
if
(
!
std
::
isfinite
(
*
result
)
||
std
::
isfinite
(
narrowed
))
{
return
narrowed
;
}
else
{
return
make_error
(
sec
::
conversion_failed
,
"narrowing error"
);
}
}
}
else
{
return
std
::
move
(
result
.
error
());
}
}
else
{
static_assert
(
detail
::
always_false_v
<
T
>
,
"sorry, this conversion is not implemented yet"
);
}
}
template
<
class
T
>
expected
<
T
>
get_as
(
const
config_value
&
x
,
inspector_access_type
::
empty
)
{
// Technically, we could always simply return T{} here. However,
// *semantically* it only makes sense to converts dictionaries to objects. So
// at least we check for this condition here.
if
(
x
.
can_convert_to_dictionary
())
return
T
{};
else
return
make_error
(
sec
::
conversion_failed
,
"invalid element type: expected a dictionary"
);
}
template
<
class
T
,
size_t
...
Is
>
expected
<
T
>
get_as_tuple
(
const
config_value
::
list
&
x
,
std
::
index_sequence
<
Is
...
>
)
{
auto
boxed
=
std
::
make_tuple
(
get_as
<
std
::
tuple_element_t
<
Is
,
T
>>
(
x
[
Is
])...);
if
((
get
<
Is
>
(
boxed
)
&&
...))
return
T
{
std
::
move
(
*
get
<
Is
>
(
boxed
))...};
else
return
make_error
(
sec
::
conversion_failed
,
"invalid element types"
);
}
template
<
class
T
>
expected
<
T
>
get_as
(
const
config_value
&
x
,
inspector_access_type
::
tuple
)
{
static_assert
(
!
std
::
is_array
<
T
>::
value
,
"cannot return an array from a function"
);
if
(
auto
wrapped_values
=
x
.
to_list
())
{
static
constexpr
size_t
n
=
std
::
tuple_size
<
T
>::
value
;
if
(
wrapped_values
->
size
()
==
n
)
return
get_as_tuple
<
T
>
(
*
wrapped_values
,
std
::
make_index_sequence
<
n
>
{});
else
return
make_error
(
sec
::
conversion_failed
,
"wrong number of arguments"
);
}
else
{
return
{
std
::
move
(
wrapped_values
.
error
())};
}
}
template
<
class
T
>
expected
<
T
>
get_as
(
const
config_value
&
x
,
inspector_access_type
::
map
)
{
using
key_type
=
typename
T
::
key_type
;
using
mapped_type
=
typename
T
::
mapped_type
;
T
result
;
if
(
auto
dict
=
x
.
to_dictionary
())
{
for
(
auto
&&
[
string_key
,
wrapped_value
]
:
*
dict
)
{
config_value
wrapped_key
{
std
::
move
(
string_key
)};
if
(
auto
key
=
get_as
<
key_type
>
(
wrapped_key
))
{
if
(
auto
val
=
get_as
<
mapped_type
>
(
wrapped_value
))
{
if
(
!
result
.
emplace
(
std
::
move
(
*
key
),
std
::
move
(
*
val
)).
second
)
{
return
make_error
(
sec
::
conversion_failed
,
"ambiguous mapping of keys to key_type"
);
}
}
else
{
return
make_error
(
sec
::
conversion_failed
,
"failed to convert values to mapped_type"
);
}
}
else
{
return
make_error
(
sec
::
conversion_failed
,
"failed to convert keys to key_type"
);
}
}
return
{
std
::
move
(
result
)};
}
else
{
return
{
std
::
move
(
dict
.
error
())};
}
}
template
<
class
T
>
expected
<
T
>
get_as
(
const
config_value
&
x
,
inspector_access_type
::
list
)
{
if
(
auto
wrapped_values
=
x
.
to_list
())
{
using
value_type
=
typename
T
::
value_type
;
T
result
;
result
.
reserve
(
wrapped_values
->
size
());
for
(
const
auto
&
wrapped_value
:
*
wrapped_values
)
if
(
auto
maybe_value
=
get_as
<
value_type
>
(
wrapped_value
))
result
.
emplace_back
(
std
::
move
(
*
maybe_value
));
else
return
{
std
::
move
(
maybe_value
.
error
())};
return
{
std
::
move
(
result
)};
}
else
{
return
{
std
::
move
(
wrapped_values
.
error
())};
}
}
/// Converts a @ref config_value to builtin types or user-defined types that
/// opted into the type inspection API.
/// @relates config_value
template
<
class
T
>
expected
<
T
>
get_as
(
const
config_value
&
value
)
{
if
constexpr
(
std
::
is_same
<
T
,
timespan
>::
value
)
{
return
value
.
to_timespan
();
}
else
if
constexpr
(
std
::
is_same
<
T
,
config_value
::
list
>::
value
)
{
return
value
.
to_list
();
}
else
if
constexpr
(
std
::
is_same
<
T
,
config_value
::
dictionary
>::
value
)
{
return
value
.
to_dictionary
();
}
else
{
auto
token
=
inspect_access_type
<
config_value_reader
,
T
>
();
return
get_as
<
T
>
(
value
,
token
);
}
}
// -- conversion via get_or ----------------------------------------------------
/// Customization point for configuring automatic mappings from default value
/// types to deduced types. For example, `get_or(value, "foo"sv)` must return a
/// `string` rather than a `string_view`. However, user-defined overloads *must
/// not* specialize this class for any type from the namespaces `std` or `caf`.
template
<
class
T
>
struct
get_or_deduction_guide
{
using
value_type
=
T
;
template
<
class
V
>
static
decltype
(
auto
)
convert
(
V
&&
x
)
{
return
std
::
forward
<
V
>
(
x
);
}
};
template
<
>
struct
get_or_deduction_guide
<
string_view
>
{
using
value_type
=
std
::
string
;
static
value_type
convert
(
string_view
str
)
{
return
{
str
.
begin
(),
str
.
end
()};
}
};
template
<
class
T
>
struct
get_or_deduction_guide
<
span
<
T
>>
{
using
value_type
=
std
::
vector
<
T
>
;
static
value_type
convert
(
span
<
T
>
buf
)
{
return
{
buf
.
begin
(),
buf
.
end
()};
}
};
/// Configures @ref get_or to uses the @ref get_or_deduction_guide.
struct
get_or_auto_deduce
{};
/// Converts a @ref config_value to `To` or returns `fallback` if the conversion
/// fails.
/// @relates config_value
template
<
class
To
=
get_or_auto_deduce
,
class
Fallback
>
auto
get_or
(
const
config_value
&
x
,
Fallback
&&
fallback
)
{
if
constexpr
(
std
::
is_same
<
To
,
get_or_auto_deduce
>::
value
)
{
using
guide
=
get_or_deduction_guide
<
std
::
decay_t
<
Fallback
>>
;
using
value_type
=
typename
guide
::
value_type
;
if
(
auto
val
=
get_as
<
value_type
>
(
x
))
return
std
::
move
(
*
val
);
else
return
guide
::
convert
(
std
::
forward
<
Fallback
>
(
fallback
));
}
else
{
using
value_type
=
std
::
decay_t
<
Fallback
>
;
if
(
auto
val
=
get_as
<
value_type
>
(
x
))
return
std
::
move
(
*
val
);
else
return
std
::
forward
<
Fallback
>
(
fallback
);
}
}
// -- SumType-like access ------------------------------------------------------
template
<
class
T
>
...
...
@@ -900,6 +1188,26 @@ struct sum_type_access<config_value> {
}
};
/// @relates config_value
inline
bool
operator
==
(
const
config_value
&
x
,
std
::
nullptr_t
)
noexcept
{
return
x
.
get_data
().
index
()
==
0
;
}
/// @relates config_value
inline
bool
operator
==
(
std
::
nullptr_t
,
const
config_value
&
x
)
noexcept
{
return
x
.
get_data
().
index
()
==
0
;
}
/// @relates config_value
inline
bool
operator
!=
(
const
config_value
&
x
,
std
::
nullptr_t
)
noexcept
{
return
x
.
get_data
().
index
()
!=
0
;
}
/// @relates config_value
inline
bool
operator
!=
(
std
::
nullptr_t
,
const
config_value
&
x
)
noexcept
{
return
x
.
get_data
().
index
()
!=
0
;
}
/// @relates config_value
CAF_CORE_EXPORT
bool
operator
<
(
const
config_value
&
x
,
const
config_value
&
y
);
...
...
@@ -916,9 +1224,6 @@ inline bool operator!=(const config_value& x, const config_value& y) {
return
!
(
x
==
y
);
}
/// @relates config_value
CAF_CORE_EXPORT
std
::
string
to_string
(
const
config_value
&
x
);
/// @relates config_value
CAF_CORE_EXPORT
std
::
ostream
&
operator
<<
(
std
::
ostream
&
out
,
const
config_value
&
x
);
...
...
@@ -998,10 +1303,11 @@ struct variant_inspector_traits<config_value> {
using
value_type
=
config_value
;
static
constexpr
type_id_t
allowed_types
[]
=
{
type_id_v
<
none_t
>
,
type_id_v
<
config_value
::
integer
>
,
type_id_v
<
config_value
::
boolean
>
,
type_id_v
<
config_value
::
real
>
,
type_id_v
<
config_value
::
timespan
>
,
type_id_v
<
timespan
>
,
type_id_v
<
uri
>
,
type_id_v
<
config_value
::
string
>
,
type_id_v
<
config_value
::
list
>
,
...
...
@@ -1019,7 +1325,7 @@ struct variant_inspector_traits<config_value> {
template
<
class
U
>
static
void
assign
(
value_type
&
x
,
U
&&
value
)
{
x
.
get_data
()
=
std
::
move
(
value
);
x
=
std
::
move
(
value
);
}
template
<
class
F
>
...
...
@@ -1027,6 +1333,11 @@ struct variant_inspector_traits<config_value> {
switch
(
type
)
{
default:
return
false
;
case
type_id_v
<
none_t
>
:
{
auto
tmp
=
config_value
{};
continuation
(
tmp
);
return
true
;
}
case
type_id_v
<
config_value
:
:
integer
>:
{
auto
tmp
=
config_value
::
integer
{};
continuation
(
tmp
);
...
...
@@ -1042,8 +1353,8 @@ struct variant_inspector_traits<config_value> {
continuation
(
tmp
);
return
true
;
}
case
type_id_v
<
config_value
:
:
timespan
>:
{
auto
tmp
=
config_value
::
timespan
{};
case
type_id_v
<
timespan
>
:
{
auto
tmp
=
timespan
{};
continuation
(
tmp
);
return
true
;
}
...
...
libcaf_core/caf/detail/bounds_checker.hpp
View file @
ab7535c7
...
...
@@ -33,6 +33,13 @@ struct bounds_checker {
}
};
template
<
>
struct
bounds_checker
<
int64_t
,
false
>
{
static
constexpr
bool
check
(
int64_t
)
noexcept
{
return
true
;
}
};
template
<
class
To
>
struct
bounds_checker
<
To
,
true
>
{
static
constexpr
bool
check
(
int64_t
x
)
noexcept
{
...
...
libcaf_core/caf/detail/parse.hpp
View file @
ab7535c7
...
...
@@ -222,8 +222,15 @@ template <class First, class Second, size_t N>
void
parse_element
(
string_parser_state
&
ps
,
std
::
pair
<
First
,
Second
>&
kvp
,
const
char
(
&
char_blacklist
)[
N
]);
template
<
class
T
>
enable_if_tt
<
is_iterable
<
T
>>
parse
(
string_parser_state
&
ps
,
T
&
xs
)
{
struct
require_opening_char_t
{};
constexpr
auto
require_opening_char
=
require_opening_char_t
{};
struct
allow_omitting_opening_char_t
{};
constexpr
auto
allow_omitting_opening_char
=
allow_omitting_opening_char_t
{};
template
<
class
T
,
class
Policy
=
allow_omitting_opening_char_t
>
enable_if_tt
<
is_iterable
<
T
>>
parse
(
string_parser_state
&
ps
,
T
&
xs
,
Policy
=
{})
{
using
value_type
=
deconst_kvp_t
<
typename
T
::
value_type
>
;
static
constexpr
auto
is_map_type
=
is_pair
<
value_type
>::
value
;
static
constexpr
auto
opening_char
=
is_map_type
?
'{'
:
'['
;
...
...
@@ -252,19 +259,23 @@ enable_if_tt<is_iterable<T>> parse(string_parser_state& ps, T& xs) {
}
return
;
}
// An empty string simply results in an empty list/map.
if
(
ps
.
at_end
())
return
;
// List/map without [] or {}.
do
{
char
char_blacklist
[]
=
{
','
,
'\0'
};
value_type
tmp
;
parse_element
(
ps
,
tmp
,
char_blacklist
);
if
(
ps
.
code
>
pec
::
trailing_character
)
if
constexpr
(
std
::
is_same
<
Policy
,
require_opening_char_t
>::
value
)
{
ps
.
code
=
pec
::
unexpected_character
;
}
else
{
// An empty string simply results in an empty list/map.
if
(
ps
.
at_end
())
return
;
*
out
++
=
std
::
move
(
tmp
);
}
while
(
ps
.
consume
(
','
));
ps
.
code
=
ps
.
at_end
()
?
pec
::
success
:
pec
::
trailing_character
;
// List/map without [] or {}.
do
{
char
char_blacklist
[]
=
{
','
,
'\0'
};
value_type
tmp
;
parse_element
(
ps
,
tmp
,
char_blacklist
);
if
(
ps
.
code
>
pec
::
trailing_character
)
return
;
*
out
++
=
std
::
move
(
tmp
);
}
while
(
ps
.
consume
(
','
));
ps
.
code
=
ps
.
at_end
()
?
pec
::
success
:
pec
::
trailing_character
;
}
}
template
<
class
T
>
...
...
@@ -306,4 +317,11 @@ auto parse(string_view str, T& x) {
return
parse_result
(
ps
,
str
);
}
template
<
class
T
,
class
Policy
>
auto
parse
(
string_view
str
,
T
&
x
,
Policy
policy
)
{
string_parser_state
ps
{
str
.
begin
(),
str
.
end
()};
parse
(
ps
,
x
,
policy
);
return
parse_result
(
ps
,
str
);
}
}
// namespace caf::detail
libcaf_core/caf/detail/print.hpp
View file @
ab7535c7
...
...
@@ -18,6 +18,7 @@
#pragma once
#include "caf/none.hpp"
#include "caf/string_view.hpp"
#include <chrono>
...
...
@@ -60,6 +61,13 @@ void print_escaped(Buffer& buf, string_view str) {
buf
.
push_back
(
'"'
);
}
template
<
class
Buffer
>
void
print
(
Buffer
&
buf
,
none_t
)
{
using
namespace
caf
::
literals
;
auto
str
=
"null"
_sv
;
buf
.
insert
(
buf
.
end
(),
str
.
begin
(),
str
.
end
());
}
template
<
class
Buffer
>
void
print
(
Buffer
&
buf
,
bool
x
)
{
using
namespace
caf
::
literals
;
...
...
libcaf_core/caf/dictionary.hpp
View file @
ab7535c7
...
...
@@ -330,14 +330,6 @@ private:
map_type
xs_
;
};
// -- free functions -----------------------------------------------------------
// @relates dictionary
template
<
class
T
>
std
::
string
to_string
(
const
dictionary
<
T
>&
xs
)
{
return
deep_to_string
(
xs
.
container
());
}
// -- operators ----------------------------------------------------------------
// @relates dictionary
...
...
@@ -376,10 +368,4 @@ bool operator>=(const dictionary<T>& xs, const dictionary<T>& ys) {
return
xs
.
container
()
>=
ys
.
container
();
}
// @relates dictionary
template
<
class
T
>
std
::
ostream
&
operator
<<
(
std
::
ostream
&
out
,
const
dictionary
<
T
>&
xs
)
{
return
out
<<
to_string
(
xs
);
}
}
// namespace caf
libcaf_core/caf/settings.hpp
View file @
ab7535c7
...
...
@@ -33,6 +33,9 @@ namespace caf {
/// @relates config_value
using
settings
=
dictionary
<
config_value
>
;
/// @relates config_value
CAF_CORE_EXPORT
std
::
string
to_string
(
const
settings
&
xs
);
/// Tries to retrieve the value associated to `name` from `xs`.
/// @relates config_value
CAF_CORE_EXPORT
const
config_value
*
...
...
libcaf_core/src/config_value.cpp
View file @
ab7535c7
...
...
@@ -19,31 +19,48 @@
#include "caf/config_value.hpp"
#include <cctype>
#include <cmath>
#include <cstdlib>
#include <ostream>
#include "caf/deep_to_string.hpp"
#include "caf/detail/config_consumer.hpp"
#include "caf/detail/meta_object.hpp"
#include "caf/detail/overload.hpp"
#include "caf/detail/parse.hpp"
#include "caf/detail/parser/read_config.hpp"
#include "caf/detail/scope_guard.hpp"
#include "caf/detail/type_traits.hpp"
#include "caf/expected.hpp"
#include "caf/parser_state.hpp"
#include "caf/pec.hpp"
#include "caf/settings.hpp"
#include "caf/string_view.hpp"
namespace
caf
{
namespace
{
const
char
*
type_names
[]
{
"integer"
,
"boolean"
,
"real"
,
"timespan"
,
"uri"
,
"string"
,
"list"
,
"dictionary"
,
};
const
char
*
type_names
[]
=
{
"none"
,
"integer"
,
"boolean"
,
"real"
,
"timespan"
,
"uri"
,
"string"
,
"list"
,
"dictionary"
};
template
<
class
To
,
class
From
>
auto
no_conversion
()
{
return
[](
const
From
&
)
{
std
::
string
msg
=
"cannot convert "
;
msg
+=
type_names
[
detail
::
tl_index_of
<
config_value
::
types
,
From
>::
value
];
msg
+=
" to "
;
msg
+=
type_names
[
detail
::
tl_index_of
<
config_value
::
types
,
To
>::
value
];
auto
err
=
make_error
(
sec
::
conversion_failed
,
std
::
move
(
msg
));
return
expected
<
To
>
{
std
::
move
(
err
)};
};
}
template
<
class
To
,
class
...
From
>
auto
no_conversions
()
{
return
detail
::
make_overload
(
no_conversion
<
To
,
From
>
()...);
}
}
// namespace
...
...
@@ -94,12 +111,16 @@ expected<config_value> config_value::parse(string_view str) {
// -- properties ---------------------------------------------------------------
void
config_value
::
convert_to_list
()
{
if
(
holds_alternative
<
list
>
(
data_
))
return
;
using
std
::
swap
;
config_value
tmp
;
swap
(
*
this
,
tmp
);
data_
=
std
::
vector
<
config_value
>
{
std
::
move
(
tmp
)};
if
(
holds_alternative
<
list
>
(
data_
))
{
;
// nop
}
else
if
(
holds_alternative
<
none_t
>
(
data_
))
{
data_
=
config_value
::
list
{};
}
else
{
using
std
::
swap
;
config_value
tmp
;
swap
(
*
this
,
tmp
);
data_
=
config_value
::
list
{
std
::
move
(
tmp
)};
}
}
config_value
::
list
&
config_value
::
as_list
()
{
...
...
@@ -126,6 +147,238 @@ const char* config_value::type_name_at_index(size_t index) noexcept {
return
type_names
[
index
];
}
// -- utility ------------------------------------------------------------------
error_code
<
sec
>
config_value
::
default_construct
(
type_id_t
id
)
{
switch
(
id
)
{
case
type_id_v
<
bool
>
:
set
(
false
);
return
sec
::
none
;
case
type_id_v
<
double
>
:
case
type_id_v
<
float
>
:
case
type_id_v
<
long
double
>
:
set
(
0.0
);
return
sec
::
none
;
case
type_id_v
<
int16_t
>
:
case
type_id_v
<
int32_t
>
:
case
type_id_v
<
int64_t
>
:
case
type_id_v
<
int8_t
>
:
case
type_id_v
<
uint16_t
>
:
case
type_id_v
<
uint32_t
>
:
case
type_id_v
<
uint64_t
>
:
case
type_id_v
<
uint8_t
>
:
set
(
0
);
return
sec
::
none
;
case
type_id_v
<
std
:
:
string
>:
set
(
std
::
string
{});
return
sec
::
none
;
case
type_id_v
<
timespan
>
:
set
(
timespan
{});
return
sec
::
none
;
case
type_id_v
<
uri
>
:
set
(
uri
{});
return
sec
::
none
;
default:
if
(
auto
meta
=
detail
::
global_meta_object
(
id
))
{
auto
ptr
=
malloc
(
meta
->
padded_size
);
auto
free_guard
=
detail
::
make_scope_guard
([
ptr
]
{
free
(
ptr
);
});
meta
->
default_construct
(
ptr
);
auto
destroy_guard
=
detail
::
make_scope_guard
([
=
]
{
meta
->
destroy
(
ptr
);
});
config_value_writer
writer
{
this
};
if
(
meta
->
save
(
writer
,
ptr
))
{
return
sec
::
none
;
}
else
{
auto
&
err
=
writer
.
get_error
();
if
(
err
.
category
()
==
type_id_v
<
sec
>
)
return
static_cast
<
sec
>
(
err
.
code
());
else
return
sec
::
conversion_failed
;
}
}
else
{
return
sec
::
unknown_type
;
}
}
}
expected
<
bool
>
config_value
::
to_boolean
()
const
{
using
result_type
=
expected
<
bool
>
;
auto
f
=
detail
::
make_overload
(
no_conversions
<
bool
,
none_t
,
integer
,
real
,
timespan
,
uri
,
config_value
::
list
,
config_value
::
dictionary
>
(),
[](
boolean
x
)
{
return
result_type
{
x
};
},
[](
const
std
::
string
&
x
)
{
if
(
x
==
"true"
)
{
return
result_type
{
true
};
}
else
if
(
x
==
"false"
)
{
return
result_type
{
false
};
}
else
{
std
::
string
msg
=
"cannot convert "
;
detail
::
print_escaped
(
msg
,
x
);
msg
+=
" to a boolean"
;
return
result_type
{
make_error
(
sec
::
conversion_failed
,
std
::
move
(
msg
))};
}
});
return
visit
(
f
,
data_
);
}
expected
<
config_value
::
integer
>
config_value
::
to_integer
()
const
{
using
result_type
=
expected
<
integer
>
;
auto
f
=
detail
::
make_overload
(
no_conversions
<
integer
,
none_t
,
bool
,
timespan
,
uri
,
config_value
::
list
,
config_value
::
dictionary
>
(),
[](
integer
x
)
{
return
result_type
{
x
};
},
[](
real
x
)
{
using
limits
=
std
::
numeric_limits
<
config_value
::
integer
>
;
if
(
std
::
isfinite
(
x
)
// never convert NaN & friends
&&
std
::
fmod
(
x
,
1.0
)
==
0.0
// only convert whole numbers
&&
x
<=
static_cast
<
config_value
::
real
>
(
limits
::
max
())
&&
x
>=
static_cast
<
config_value
::
real
>
(
limits
::
min
()))
{
return
result_type
{
static_cast
<
config_value
::
integer
>
(
x
)};
}
else
{
auto
err
=
make_error
(
sec
::
conversion_failed
,
"cannot convert decimal or out-of-bounds real number to an integer"
);
return
result_type
{
std
::
move
(
err
)};
}
},
[](
const
std
::
string
&
x
)
{
auto
tmp_int
=
config_value
::
integer
{
0
};
if
(
detail
::
parse
(
x
,
tmp_int
)
==
none
)
return
result_type
{
tmp_int
};
auto
tmp_real
=
0.0
;
if
(
detail
::
parse
(
x
,
tmp_real
)
==
none
)
if
(
auto
ival
=
config_value
{
tmp_real
}.
to_integer
())
return
result_type
{
*
ival
};
std
::
string
msg
=
"cannot convert "
;
detail
::
print_escaped
(
msg
,
x
);
msg
+=
" to an integer"
;
return
result_type
{
make_error
(
sec
::
conversion_failed
,
std
::
move
(
msg
))};
});
return
visit
(
f
,
data_
);
}
expected
<
config_value
::
real
>
config_value
::
to_real
()
const
{
using
result_type
=
expected
<
real
>
;
auto
f
=
detail
::
make_overload
(
no_conversions
<
real
,
none_t
,
bool
,
timespan
,
uri
,
config_value
::
list
,
config_value
::
dictionary
>
(),
[](
integer
x
)
{
// This cast may lose precision on the value. We could try and check that,
// but refusing to convert on loss of precision could also be unexpected
// behavior. So we rather always convert, even if it costs precision.
return
result_type
{
static_cast
<
real
>
(
x
)};
},
[](
real
x
)
{
return
result_type
{
x
};
},
[](
const
std
::
string
&
x
)
{
auto
tmp
=
0.0
;
if
(
detail
::
parse
(
x
,
tmp
)
==
none
)
return
result_type
{
tmp
};
std
::
string
msg
=
"cannot convert "
;
detail
::
print_escaped
(
msg
,
x
);
msg
+=
" to a floating point number"
;
return
result_type
{
make_error
(
sec
::
conversion_failed
,
std
::
move
(
msg
))};
});
return
visit
(
f
,
data_
);
}
expected
<
timespan
>
config_value
::
to_timespan
()
const
{
using
result_type
=
expected
<
timespan
>
;
auto
f
=
detail
::
make_overload
(
no_conversions
<
timespan
,
none_t
,
bool
,
integer
,
real
,
uri
,
config_value
::
list
,
config_value
::
dictionary
>
(),
[](
timespan
x
)
{
// This cast may lose precision on the value. We could try and check that,
// but refusing to convert on loss of precision could also be unexpected
// behavior. So we rather always convert, even if it costs precision.
return
result_type
{
x
};
},
[](
const
std
::
string
&
x
)
{
auto
tmp
=
timespan
{};
if
(
detail
::
parse
(
x
,
tmp
)
==
none
)
return
result_type
{
tmp
};
std
::
string
msg
=
"cannot convert "
;
detail
::
print_escaped
(
msg
,
x
);
msg
+=
" to a timespan"
;
return
result_type
{
make_error
(
sec
::
conversion_failed
,
std
::
move
(
msg
))};
});
return
visit
(
f
,
data_
);
}
expected
<
config_value
::
list
>
config_value
::
to_list
()
const
{
using
result_type
=
expected
<
list
>
;
auto
f
=
detail
::
make_overload
(
no_conversions
<
list
,
none_t
,
bool
,
integer
,
real
,
timespan
,
uri
>
(),
[](
const
std
::
string
&
x
)
{
// Check whether we can parse the string as a list. If that fails, try
// whether we can parse it as a dictionary instead (and then convert that
// to a list).
config_value
::
list
tmp
;
if
(
detail
::
parse
(
x
,
tmp
,
detail
::
require_opening_char
)
==
none
)
return
result_type
{
std
::
move
(
tmp
)};
config_value
::
dictionary
dict
;
if
(
detail
::
parse
(
x
,
dict
,
detail
::
require_opening_char
)
==
none
)
{
tmp
.
clear
();
for
(
const
auto
&
[
key
,
val
]
:
dict
)
{
list
kvp
;
kvp
.
reserve
(
2
);
kvp
.
emplace_back
(
key
);
kvp
.
emplace_back
(
val
);
tmp
.
emplace_back
(
std
::
move
(
kvp
));
}
return
result_type
{
std
::
move
(
tmp
)};
}
std
::
string
msg
=
"cannot convert "
;
detail
::
print_escaped
(
msg
,
x
);
msg
+=
" to a list"
;
return
result_type
{
make_error
(
sec
::
conversion_failed
,
std
::
move
(
msg
))};
},
[](
const
list
&
x
)
{
return
result_type
{
x
};
},
[](
const
dictionary
&
x
)
{
list
tmp
;
for
(
const
auto
&
[
key
,
val
]
:
x
)
{
list
kvp
;
kvp
.
reserve
(
2
);
kvp
.
emplace_back
(
key
);
kvp
.
emplace_back
(
val
);
tmp
.
emplace_back
(
std
::
move
(
kvp
));
}
return
result_type
{
std
::
move
(
tmp
)};
});
return
visit
(
f
,
data_
);
}
expected
<
config_value
::
dictionary
>
config_value
::
to_dictionary
()
const
{
using
result_type
=
expected
<
dictionary
>
;
auto
f
=
detail
::
make_overload
(
no_conversions
<
dictionary
,
none_t
,
bool
,
integer
,
timespan
,
real
,
uri
,
list
>
(),
[](
const
std
::
string
&
x
)
{
dictionary
tmp
;
if
(
detail
::
parse
(
x
,
tmp
,
detail
::
require_opening_char
)
==
none
)
return
result_type
{
std
::
move
(
tmp
)};
std
::
string
msg
=
"cannot convert "
;
detail
::
print_escaped
(
msg
,
x
);
msg
+=
" to a dictionary"
;
return
result_type
{
make_error
(
sec
::
conversion_failed
,
std
::
move
(
msg
))};
},
[](
const
dictionary
&
x
)
{
return
result_type
{
x
};
});
return
visit
(
f
,
data_
);
}
bool
config_value
::
can_convert_to_dictionary
()
const
{
auto
f
=
detail
::
make_overload
(
//
[](
const
auto
&
)
{
return
false
;
},
[
this
](
const
std
::
string
&
)
{
// TODO: implement some dry-run mode and use it here to avoid creating an
// actual dictionary only to throw it away.
auto
maybe_dict
=
to_dictionary
();
return
static_cast
<
bool
>
(
maybe_dict
);
},
[](
const
dictionary
&
)
{
return
true
;
});
return
visit
(
f
,
data_
);
}
// -- related free functions ---------------------------------------------------
bool
operator
<
(
const
config_value
&
x
,
const
config_value
&
y
)
{
...
...
@@ -143,10 +396,17 @@ void to_string_impl(std::string& str, const config_value& x);
struct
to_string_visitor
{
std
::
string
&
str
;
void
operator
()(
const
std
::
string
&
x
)
{
detail
::
print_escaped
(
str
,
x
);
}
template
<
class
T
>
void
operator
()(
const
T
&
x
)
{
detail
::
stringification_inspector
f
{
str
};
f
.
value
(
x
);
detail
::
print
(
str
,
x
);
}
void
operator
()(
none_t
)
{
str
+=
"null"
;
}
void
operator
()(
const
uri
&
x
)
{
...
...
@@ -155,36 +415,38 @@ struct to_string_visitor {
}
void
operator
()(
const
config_value
::
list
&
xs
)
{
if
(
xs
.
empty
())
{
str
+=
"[]"
;
return
;
}
str
+=
'['
;
auto
i
=
xs
.
begin
();
to_string_impl
(
str
,
*
i
);
for
(
++
i
;
i
!=
xs
.
end
();
++
i
)
{
str
+=
", "
;
if
(
!
xs
.
empty
())
{
auto
i
=
xs
.
begin
();
to_string_impl
(
str
,
*
i
);
for
(
++
i
;
i
!=
xs
.
end
();
++
i
)
{
str
+=
", "
;
to_string_impl
(
str
,
*
i
);
}
}
str
+=
']'
;
}
void
append_key
(
const
std
::
string
&
key
)
{
if
(
std
::
all_of
(
key
.
begin
(),
key
.
end
(),
::
isalnum
))
str
.
append
(
key
.
begin
(),
key
.
end
());
else
(
*
this
)(
key
);
}
void
operator
()(
const
config_value
::
dictionary
&
xs
)
{
if
(
xs
.
empty
())
{
str
+=
"{}"
;
return
;
}
detail
::
stringification_inspector
f
{
str
};
str
+=
'{'
;
auto
i
=
xs
.
begin
();
f
.
value
(
i
->
first
);
str
+=
" = "
;
to_string_impl
(
str
,
i
->
second
);
for
(
++
i
;
i
!=
xs
.
end
();
++
i
)
{
str
+=
", "
;
f
.
value
(
i
->
first
);
if
(
!
xs
.
empty
())
{
auto
i
=
xs
.
begin
();
append_key
(
i
->
first
);
str
+=
" = "
;
to_string_impl
(
str
,
i
->
second
);
for
(
++
i
;
i
!=
xs
.
end
();
++
i
)
{
str
+=
", "
;
append_key
(
i
->
first
);
str
+=
" = "
;
to_string_impl
(
str
,
i
->
second
);
}
}
str
+=
'}'
;
}
...
...
@@ -198,8 +460,19 @@ void to_string_impl(std::string& str, const config_value& x) {
}
// namespace
std
::
string
to_string
(
const
config_value
&
x
)
{
if
(
auto
str
=
get_if
<
std
::
string
>
(
std
::
addressof
(
x
.
get_data
())))
{
return
*
str
;
}
else
{
std
::
string
result
;
to_string_impl
(
result
,
x
);
return
result
;
}
}
std
::
string
to_string
(
const
settings
&
xs
)
{
std
::
string
result
;
to_string_impl
(
result
,
x
);
to_string_visitor
f
{
result
};
f
(
xs
);
return
result
;
}
...
...
libcaf_core/src/detail/stringification_inspector.cpp
View file @
ab7535c7
...
...
@@ -130,15 +130,13 @@ bool stringification_inspector::value(float x) {
bool
stringification_inspector
::
value
(
double
x
)
{
sep
();
auto
str
=
std
::
to_string
(
x
);
result_
+=
str
;
detail
::
print
(
result_
,
x
);
return
true
;
}
bool
stringification_inspector
::
value
(
long
double
x
)
{
sep
();
auto
str
=
std
::
to_string
(
x
);
result_
+=
str
;
detail
::
print
(
result_
,
x
);
return
true
;
}
...
...
libcaf_core/src/settings.cpp
View file @
ab7535c7
...
...
@@ -22,6 +22,8 @@
namespace
caf
{
// note: to_string is implemented in config_value.cpp
const
config_value
*
get_if
(
const
settings
*
xs
,
string_view
name
)
{
// Access the key directly unless the user specified a dot-separated path.
auto
pos
=
name
.
find
(
'.'
);
...
...
libcaf_core/test/config_value.cpp
View file @
ab7535c7
...
...
@@ -23,6 +23,7 @@
#include "core-test.hpp"
#include "nasty.hpp"
#include <cmath>
#include <list>
#include <map>
#include <set>
...
...
@@ -41,6 +42,10 @@
#include "caf/string_view.hpp"
#include "caf/variant.hpp"
#include "caf/detail/bounds_checker.hpp"
#include "caf/detail/overload.hpp"
#include "caf/detail/parse.hpp"
using
std
::
string
;
using
namespace
caf
;
...
...
@@ -81,13 +86,606 @@ config_value cfg_lst(Ts&&... xs) {
return
config_value
{
std
::
move
(
lst
)};
}
struct
fixture
{
config_value
cv_null
;
config_value
cv_true
;
config_value
cv_false
;
config_value
cv_empty_uri
;
config_value
cv_empty_list
;
config_value
cv_empty_dict
;
config_value
cv_caf_uri
;
fixture
()
:
cv_true
(
true
),
cv_false
(
false
),
cv_empty_uri
(
uri
{}),
cv_empty_list
(
config_value
::
list
{}),
cv_empty_dict
(
config_value
::
dictionary
{})
{
cv_caf_uri
=
unbox
(
make_uri
(
"https://actor-framework.org"
));
}
};
}
// namespace
CAF_TEST_FIXTURE_SCOPE
(
config_value_tests
,
fixture
)
SCENARIO
(
"get_as can convert config values to boolean"
)
{
GIVEN
(
"a config value x with value true or false"
)
{
WHEN
(
"using get_as with bool"
)
{
THEN
(
"conversion succeeds"
)
{
CHECK_EQ
(
get_as
<
bool
>
(
cv_true
),
true
);
CHECK_EQ
(
get_as
<
bool
>
(
cv_false
),
false
);
}
}
}
GIVEN
(
"a config value x with value
\"
true
\"
or
\"
false
\"
"
)
{
WHEN
(
"using get_as with bool"
)
{
THEN
(
"conversion succeeds"
)
{
CHECK_EQ
(
get_as
<
bool
>
(
config_value
{
"true"
s
}),
true
);
CHECK_EQ
(
get_as
<
bool
>
(
config_value
{
"false"
s
}),
false
);
}
}
}
GIVEN
(
"non-boolean config_values"
)
{
WHEN
(
"using get_as with bool"
)
{
THEN
(
"conversion fails"
)
{
CHECK_EQ
(
get_as
<
bool
>
(
cv_null
),
sec
::
conversion_failed
);
CHECK_EQ
(
get_as
<
bool
>
(
cv_empty_uri
),
sec
::
conversion_failed
);
CHECK_EQ
(
get_as
<
bool
>
(
cv_empty_list
),
sec
::
conversion_failed
);
CHECK_EQ
(
get_as
<
bool
>
(
cv_empty_dict
),
sec
::
conversion_failed
);
CHECK_EQ
(
get_as
<
bool
>
(
config_value
{
0
}),
sec
::
conversion_failed
);
CHECK_EQ
(
get_as
<
bool
>
(
config_value
{
1
}),
sec
::
conversion_failed
);
CHECK_EQ
(
get_as
<
bool
>
(
config_value
{
0.
f
}),
sec
::
conversion_failed
);
CHECK_EQ
(
get_as
<
bool
>
(
config_value
{
1.
f
}),
sec
::
conversion_failed
);
CHECK_EQ
(
get_as
<
bool
>
(
config_value
{
""
s
}),
sec
::
conversion_failed
);
CHECK_EQ
(
get_as
<
bool
>
(
config_value
{
"1"
s
}),
sec
::
conversion_failed
);
}
}
}
}
SCENARIO
(
"get_as can convert config values to integers"
)
{
GIVEN
(
"a config value x with value 32,768"
)
{
auto
x
=
config_value
{
32'768
};
WHEN
(
"using get_as with integer types"
)
{
THEN
(
"conversion fails if bounds checks fail"
)
{
CHECK_EQ
(
get_as
<
uint64_t
>
(
x
),
32'768u
);
CHECK_EQ
(
get_as
<
int64_t
>
(
x
),
32'768
);
CHECK_EQ
(
get_as
<
uint32_t
>
(
x
),
32'768u
);
CHECK_EQ
(
get_as
<
int32_t
>
(
x
),
32'768
);
CHECK_EQ
(
get_as
<
uint16_t
>
(
x
),
32'768u
);
CHECK_EQ
(
get_as
<
int16_t
>
(
x
),
sec
::
conversion_failed
);
CHECK_EQ
(
get_as
<
uint8_t
>
(
x
),
sec
::
conversion_failed
);
CHECK_EQ
(
get_as
<
int8_t
>
(
x
),
sec
::
conversion_failed
);
}
}
}
GIVEN
(
"a config value x with value -5"
)
{
auto
x
=
config_value
{
-
5
};
WHEN
(
"using get_as with integer types"
)
{
THEN
(
"conversion fails for all unsigned types"
)
{
CAF_CHECK_EQUAL
(
get_as
<
uint64_t
>
(
x
),
sec
::
conversion_failed
);
CAF_CHECK_EQUAL
(
get_as
<
int64_t
>
(
x
),
-
5
);
CAF_CHECK_EQUAL
(
get_as
<
uint32_t
>
(
x
),
sec
::
conversion_failed
);
CAF_CHECK_EQUAL
(
get_as
<
int32_t
>
(
x
),
-
5
);
CAF_CHECK_EQUAL
(
get_as
<
uint16_t
>
(
x
),
sec
::
conversion_failed
);
CAF_CHECK_EQUAL
(
get_as
<
int16_t
>
(
x
),
-
5
);
CAF_CHECK_EQUAL
(
get_as
<
uint8_t
>
(
x
),
sec
::
conversion_failed
);
CAF_CHECK_EQUAL
(
get_as
<
int8_t
>
(
x
),
-
5
);
}
}
}
GIVEN
(
"a config value x with value
\"
50000
\"
"
)
{
auto
x
=
config_value
{
"50000"
s
};
WHEN
(
"using get_as with integer types"
)
{
THEN
(
"CAF parses the string and performs a bound check"
)
{
CAF_CHECK_EQUAL
(
get_as
<
uint64_t
>
(
x
),
50'000u
);
CAF_CHECK_EQUAL
(
get_as
<
int64_t
>
(
x
),
50'000
);
CAF_CHECK_EQUAL
(
get_as
<
uint32_t
>
(
x
),
50'000u
);
CAF_CHECK_EQUAL
(
get_as
<
int32_t
>
(
x
),
50'000
);
CAF_CHECK_EQUAL
(
get_as
<
uint16_t
>
(
x
),
50'000u
);
CAF_CHECK_EQUAL
(
get_as
<
int16_t
>
(
x
),
sec
::
conversion_failed
);
CAF_CHECK_EQUAL
(
get_as
<
uint8_t
>
(
x
),
sec
::
conversion_failed
);
CAF_CHECK_EQUAL
(
get_as
<
int8_t
>
(
x
),
sec
::
conversion_failed
);
}
}
}
GIVEN
(
"a config value x with value 50.0"
)
{
auto
x
=
config_value
{
50.0
};
WHEN
(
"using get_as with integer types"
)
{
THEN
(
"CAF parses the string and performs a bound check"
)
{
CHECK_EQ
(
get_as
<
uint64_t
>
(
x
),
50u
);
CHECK_EQ
(
get_as
<
int64_t
>
(
x
),
50
);
CHECK_EQ
(
get_as
<
uint32_t
>
(
x
),
50u
);
CHECK_EQ
(
get_as
<
int32_t
>
(
x
),
50
);
CHECK_EQ
(
get_as
<
uint16_t
>
(
x
),
50u
);
CHECK_EQ
(
get_as
<
int16_t
>
(
x
),
50
);
CHECK_EQ
(
get_as
<
uint8_t
>
(
x
),
50u
);
CHECK_EQ
(
get_as
<
int8_t
>
(
x
),
50
);
}
}
}
GIVEN
(
"a config value x with value 50.05"
)
{
auto
x
=
config_value
{
50.05
};
WHEN
(
"using get_as with integer types"
)
{
THEN
(
"CAF fails to convert the real to an integer"
)
{
CHECK_EQ
(
get_as
<
uint64_t
>
(
x
),
sec
::
conversion_failed
);
CHECK_EQ
(
get_as
<
int64_t
>
(
x
),
sec
::
conversion_failed
);
CHECK_EQ
(
get_as
<
uint32_t
>
(
x
),
sec
::
conversion_failed
);
CHECK_EQ
(
get_as
<
int32_t
>
(
x
),
sec
::
conversion_failed
);
CHECK_EQ
(
get_as
<
uint16_t
>
(
x
),
sec
::
conversion_failed
);
CHECK_EQ
(
get_as
<
int16_t
>
(
x
),
sec
::
conversion_failed
);
CHECK_EQ
(
get_as
<
uint8_t
>
(
x
),
sec
::
conversion_failed
);
CHECK_EQ
(
get_as
<
int8_t
>
(
x
),
sec
::
conversion_failed
);
}
}
}
GIVEN
(
"a config value x with value
\"
50.000
\"
"
)
{
auto
x
=
config_value
{
"50.000"
s
};
WHEN
(
"using get_as with integer types"
)
{
THEN
(
"CAF parses the string and performs a bound check"
)
{
CHECK_EQ
(
get_as
<
uint64_t
>
(
x
),
50u
);
CHECK_EQ
(
get_as
<
int64_t
>
(
x
),
50
);
CHECK_EQ
(
get_as
<
uint32_t
>
(
x
),
50u
);
CHECK_EQ
(
get_as
<
int32_t
>
(
x
),
50
);
CHECK_EQ
(
get_as
<
uint16_t
>
(
x
),
50u
);
CHECK_EQ
(
get_as
<
int16_t
>
(
x
),
50
);
CHECK_EQ
(
get_as
<
uint8_t
>
(
x
),
50u
);
CHECK_EQ
(
get_as
<
int8_t
>
(
x
),
50
);
}
}
}
GIVEN
(
"a config value x with value
\"
50.05
\"
"
)
{
auto
x
=
config_value
{
"50.05"
s
};
WHEN
(
"using get_as with integer types"
)
{
THEN
(
"CAF fails to convert the real to an integer"
)
{
CHECK_EQ
(
get_as
<
uint64_t
>
(
x
),
sec
::
conversion_failed
);
CHECK_EQ
(
get_as
<
int64_t
>
(
x
),
sec
::
conversion_failed
);
CHECK_EQ
(
get_as
<
uint32_t
>
(
x
),
sec
::
conversion_failed
);
CHECK_EQ
(
get_as
<
int32_t
>
(
x
),
sec
::
conversion_failed
);
CHECK_EQ
(
get_as
<
uint16_t
>
(
x
),
sec
::
conversion_failed
);
CHECK_EQ
(
get_as
<
int16_t
>
(
x
),
sec
::
conversion_failed
);
CHECK_EQ
(
get_as
<
uint8_t
>
(
x
),
sec
::
conversion_failed
);
CHECK_EQ
(
get_as
<
int8_t
>
(
x
),
sec
::
conversion_failed
);
}
}
}
GIVEN
(
"config_values of null, URI, boolean, list or dictionary"
)
{
WHEN
(
"using get_as with integer types"
)
{
THEN
(
"conversion fails"
)
{
CHECK_EQ
(
get_as
<
int64_t
>
(
cv_null
),
sec
::
conversion_failed
);
CHECK_EQ
(
get_as
<
int64_t
>
(
cv_true
),
sec
::
conversion_failed
);
CHECK_EQ
(
get_as
<
int64_t
>
(
cv_false
),
sec
::
conversion_failed
);
CHECK_EQ
(
get_as
<
int64_t
>
(
cv_empty_uri
),
sec
::
conversion_failed
);
CHECK_EQ
(
get_as
<
int64_t
>
(
cv_empty_list
),
sec
::
conversion_failed
);
CHECK_EQ
(
get_as
<
int64_t
>
(
cv_empty_dict
),
sec
::
conversion_failed
);
}
}
}
}
SCENARIO
(
"get_as can convert config values to floating point numbers"
)
{
GIVEN
(
"a config value x with value 1.79769e+308"
)
{
auto
x
=
config_value
{
1.79769e+308
};
WHEN
(
"using get_as with floating point types"
)
{
THEN
(
"conversion fails if bounds checks fail"
)
{
CHECK_EQ
(
get_as
<
long
double
>
(
x
),
1.79769e+308
);
CHECK_EQ
(
get_as
<
double
>
(
x
),
1.79769e+308
);
CHECK_EQ
(
get_as
<
float
>
(
x
),
sec
::
conversion_failed
);
}
}
}
GIVEN
(
"a config value x with value
\"
3e7
\"
"
)
{
auto
x
=
config_value
{
"3e7"
s
};
WHEN
(
"using get_as with floating point types"
)
{
THEN
(
"CAF parses the string and converts the value"
)
{
CHECK_EQ
(
get_as
<
long
double
>
(
x
),
3e7
);
CHECK_EQ
(
get_as
<
double
>
(
x
),
3e7
);
CHECK_EQ
(
get_as
<
float
>
(
x
),
3e7
f
);
}
}
}
GIVEN
(
"a config value x with value 123"
)
{
auto
x
=
config_value
{
123
};
WHEN
(
"using get_as with floating point types"
)
{
THEN
(
"CAF converts the value"
)
{
CHECK_EQ
(
get_as
<
long
double
>
(
x
),
123.0
);
CHECK_EQ
(
get_as
<
double
>
(
x
),
123.0
);
CHECK_EQ
(
get_as
<
float
>
(
x
),
123.
f
);
}
}
}
GIVEN
(
"config_values of null, URI, boolean, list or dictionary"
)
{
WHEN
(
"using get_as with floating point types"
)
{
THEN
(
"conversion fails"
)
{
CHECK_EQ
(
get_as
<
int64_t
>
(
cv_null
),
sec
::
conversion_failed
);
CHECK_EQ
(
get_as
<
int64_t
>
(
cv_true
),
sec
::
conversion_failed
);
CHECK_EQ
(
get_as
<
int64_t
>
(
cv_false
),
sec
::
conversion_failed
);
CHECK_EQ
(
get_as
<
int64_t
>
(
cv_empty_uri
),
sec
::
conversion_failed
);
CHECK_EQ
(
get_as
<
int64_t
>
(
cv_empty_list
),
sec
::
conversion_failed
);
CHECK_EQ
(
get_as
<
int64_t
>
(
cv_empty_dict
),
sec
::
conversion_failed
);
}
}
}
}
SCENARIO
(
"get_as can convert config values to timespans"
)
{
using
namespace
std
::
chrono_literals
;
GIVEN
(
"a config value with value 42s"
)
{
auto
x
=
config_value
{
timespan
{
42s
}};
WHEN
(
"using get_as with timespan"
)
{
THEN
(
"conversion succeeds"
)
{
CHECK_EQ
(
get_as
<
timespan
>
(
x
),
timespan
{
42s
});
CHECK_EQ
(
get_as
<
std
::
string
>
(
x
),
"42s"
);
}
}
WHEN
(
"using get_as with type other than timespan or string"
)
{
THEN
(
"conversion fails"
)
{
CHECK_EQ
(
get_as
<
int64_t
>
(
x
),
sec
::
conversion_failed
);
CHECK_EQ
(
get_as
<
double
>
(
x
),
sec
::
conversion_failed
);
// CHECK_EQ(get_as<uri>(x), sec::conversion_failed);
CHECK_EQ
(
get_as
<
config_value
::
list
>
(
x
),
sec
::
conversion_failed
);
CHECK_EQ
(
get_as
<
config_value
::
dictionary
>
(
x
),
sec
::
conversion_failed
);
}
}
}
}
SCENARIO
(
"get_as can convert config values to strings"
)
{
using
string
=
std
::
string
;
GIVEN
(
"any config value"
)
{
WHEN
(
"using get_as with string"
)
{
THEN
(
"CAF renders the value as string"
)
{
CHECK_EQ
(
get_as
<
string
>
(
cv_null
),
"null"
);
CHECK_EQ
(
get_as
<
string
>
(
cv_true
),
"true"
);
CHECK_EQ
(
get_as
<
string
>
(
cv_false
),
"false"
);
CHECK_EQ
(
get_as
<
string
>
(
cv_empty_list
),
"[]"
);
CHECK_EQ
(
get_as
<
string
>
(
cv_empty_dict
),
"{}"
);
CHECK_EQ
(
get_as
<
string
>
(
config_value
{
42
}),
"42"
);
CHECK_EQ
(
get_as
<
string
>
(
config_value
{
4.2
}),
"4.2"
);
CHECK_EQ
(
get_as
<
string
>
(
config_value
{
timespan
{
4
}}),
"4ns"
);
CHECK_EQ
(
get_as
<
string
>
(
cv_caf_uri
),
"https://actor-framework.org"
);
}
}
}
}
SCENARIO
(
"get_as can convert config values to lists"
)
{
using
list
=
config_value
::
list
;
GIVEN
(
"a config value with value [1, 2, 3]"
)
{
auto
x
=
make_config_value_list
(
1
,
2
,
3
);
WHEN
(
"using get_as with config_value::list"
)
{
THEN
(
"conversion succeeds"
)
{
auto
maybe_res
=
get_as
<
list
>
(
x
);
if
(
CHECK
(
maybe_res
)
&&
CHECK_EQ
(
maybe_res
->
size
(),
3u
))
{
auto
&
res
=
*
maybe_res
;
CHECK_EQ
(
get_as
<
int
>
(
res
[
0
]),
1
);
CHECK_EQ
(
get_as
<
int
>
(
res
[
1
]),
2
);
CHECK_EQ
(
get_as
<
int
>
(
res
[
2
]),
3
);
}
}
}
WHEN
(
"using get_as with vector<int>"
)
{
THEN
(
"conversion succeeds"
)
{
auto
maybe_res
=
get_as
<
std
::
vector
<
int
>>
(
x
);
if
(
CHECK
(
maybe_res
)
&&
CHECK_EQ
(
maybe_res
->
size
(),
3u
))
{
auto
&
res
=
*
maybe_res
;
CHECK_EQ
(
res
[
0
],
1
);
CHECK_EQ
(
res
[
1
],
2
);
CHECK_EQ
(
res
[
2
],
3
);
}
}
}
}
GIVEN
(
"a config value with value
\"
[1, 2, 3]
\"
"
)
{
auto
x
=
config_value
(
"[1, 2, 3]"
s
);
WHEN
(
"using get_as with list"
)
{
THEN
(
"conversion succeeds"
)
{
auto
maybe_res
=
get_as
<
list
>
(
x
);
if
(
CHECK
(
maybe_res
)
&&
CHECK_EQ
(
maybe_res
->
size
(),
3u
))
{
auto
&
res
=
*
maybe_res
;
CHECK_EQ
(
get_as
<
int
>
(
res
[
0
]),
1
);
CHECK_EQ
(
get_as
<
int
>
(
res
[
1
]),
2
);
CHECK_EQ
(
get_as
<
int
>
(
res
[
2
]),
3
);
}
}
}
WHEN
(
"using get_as with vector<int>"
)
{
THEN
(
"conversion succeeds"
)
{
auto
maybe_res
=
get_as
<
std
::
vector
<
int
>>
(
x
);
if
(
CHECK
(
maybe_res
)
&&
CHECK_EQ
(
maybe_res
->
size
(),
3u
))
{
auto
&
res
=
*
maybe_res
;
CHECK_EQ
(
res
[
0
],
1
);
CHECK_EQ
(
res
[
1
],
2
);
CHECK_EQ
(
res
[
2
],
3
);
}
}
}
}
}
SCENARIO
(
"get_as can convert config values to dictionaries"
)
{
using
dictionary
=
config_value
::
dictionary
;
auto
dict
=
config_value
::
dictionary
{
{
"a"
,
config_value
{
1
}},
{
"b"
,
config_value
{
2
}},
{
"c"
,
config_value
{
3
}},
};
std
::
vector
<
config_value
>
given_values
;
given_values
.
emplace_back
(
std
::
move
(
dict
));
given_values
.
emplace_back
(
"{a = 1, b = 2, c = 3}"
s
);
for
(
auto
&
x
:
given_values
)
{
GIVEN
(
"the config value "
<<
x
)
{
WHEN
(
"using get_as with config_value::dictionary"
)
{
THEN
(
"conversion succeeds"
)
{
auto
maybe_res
=
get_as
<
dictionary
>
(
x
);
if
(
CHECK
(
maybe_res
)
&&
CHECK_EQ
(
maybe_res
->
size
(),
3u
))
{
auto
&
res
=
*
maybe_res
;
CHECK_EQ
(
get_as
<
int
>
(
res
[
"a"
]),
1
);
CHECK_EQ
(
get_as
<
int
>
(
res
[
"b"
]),
2
);
CHECK_EQ
(
get_as
<
int
>
(
res
[
"c"
]),
3
);
}
}
}
WHEN
(
"using get_as with config_value::list"
)
{
THEN
(
"CAF converts the dictionary to a list of lists"
)
{
auto
maybe_res
=
get_as
<
list
>
(
x
);
if
(
CHECK
(
maybe_res
)
&&
CHECK_EQ
(
maybe_res
->
size
(),
3u
))
{
auto
&
res
=
*
maybe_res
;
if
(
auto
kvp
=
unbox
(
get_as
<
list
>
(
res
[
0
]));
CHECK_EQ
(
kvp
.
size
(),
2u
))
{
CHECK_EQ
(
get_as
<
std
::
string
>
(
kvp
[
0
]),
"a"
);
CHECK_EQ
(
get_as
<
int
>
(
kvp
[
1
]),
1
);
}
if
(
auto
kvp
=
unbox
(
get_as
<
list
>
(
res
[
1
]));
CHECK_EQ
(
kvp
.
size
(),
2u
))
{
CHECK_EQ
(
get_as
<
std
::
string
>
(
kvp
[
0
]),
"b"
);
CHECK_EQ
(
get_as
<
int
>
(
kvp
[
1
]),
2
);
}
if
(
auto
kvp
=
unbox
(
get_as
<
list
>
(
res
[
2
]));
CHECK_EQ
(
kvp
.
size
(),
2u
))
{
CHECK_EQ
(
get_as
<
std
::
string
>
(
kvp
[
0
]),
"c"
);
CHECK_EQ
(
get_as
<
int
>
(
kvp
[
1
]),
3
);
}
}
}
}
WHEN
(
"using get_as with vector<tuple<string, int>>"
)
{
THEN
(
"CAF converts the dictionary to a list of tuples"
)
{
using
kvp_t
=
std
::
tuple
<
std
::
string
,
int
>
;
auto
maybe_res
=
get_as
<
std
::
vector
<
kvp_t
>>
(
x
);
MESSAGE
(
"maybe_res: "
<<
maybe_res
);
if
(
CHECK
(
maybe_res
)
&&
CHECK_EQ
(
maybe_res
->
size
(),
3u
))
{
auto
&
res
=
*
maybe_res
;
CHECK_EQ
(
res
[
0
],
kvp_t
(
"a"
,
1
));
CHECK_EQ
(
res
[
1
],
kvp_t
(
"b"
,
2
));
CHECK_EQ
(
res
[
2
],
kvp_t
(
"c"
,
3
));
}
}
}
}
}
}
SCENARIO
(
"get_as can convert config values to maps"
)
{
auto
dict
=
config_value
::
dictionary
{
{
"1"
,
config_value
{
1
}},
{
"2"
,
config_value
{
4
}},
{
"3"
,
config_value
{
9
}},
};
std
::
vector
<
config_value
>
given_values
;
given_values
.
emplace_back
(
std
::
move
(
dict
));
given_values
.
emplace_back
(
"{1 = 1, 2 = 4, 3 = 9}"
s
);
for
(
auto
&
x
:
given_values
)
{
GIVEN
(
"the config value "
<<
x
)
{
WHEN
(
"using get_as with map<string, int>"
)
{
THEN
(
"conversion succeeds"
)
{
auto
maybe_res
=
get_as
<
std
::
map
<
std
::
string
,
int
>>
(
x
);
if
(
CHECK
(
maybe_res
)
&&
CHECK_EQ
(
maybe_res
->
size
(),
3u
))
{
auto
&
res
=
*
maybe_res
;
CHECK_EQ
(
res
[
"1"
],
1
);
CHECK_EQ
(
res
[
"2"
],
4
);
CHECK_EQ
(
res
[
"3"
],
9
);
}
}
}
WHEN
(
"using get_as with unordered_map<string, int>"
)
{
THEN
(
"conversion succeeds"
)
{
auto
maybe_res
=
get_as
<
std
::
unordered_map
<
std
::
string
,
int
>>
(
x
);
if
(
CHECK
(
maybe_res
)
&&
CHECK_EQ
(
maybe_res
->
size
(),
3u
))
{
auto
&
res
=
*
maybe_res
;
CHECK_EQ
(
res
[
"1"
],
1
);
CHECK_EQ
(
res
[
"2"
],
4
);
CHECK_EQ
(
res
[
"3"
],
9
);
}
}
}
WHEN
(
"using get_as with map<int, int>"
)
{
THEN
(
"conversion succeeds"
)
{
auto
maybe_res
=
get_as
<
std
::
map
<
int
,
int
>>
(
x
);
if
(
CHECK
(
maybe_res
)
&&
CHECK_EQ
(
maybe_res
->
size
(),
3u
))
{
auto
&
res
=
*
maybe_res
;
CHECK_EQ
(
res
[
1
],
1
);
CHECK_EQ
(
res
[
2
],
4
);
CHECK_EQ
(
res
[
3
],
9
);
}
}
}
WHEN
(
"using get_as with unordered_map<int, int>"
)
{
THEN
(
"conversion succeeds"
)
{
auto
maybe_res
=
get_as
<
std
::
unordered_map
<
int
,
int
>>
(
x
);
if
(
CHECK
(
maybe_res
)
&&
CHECK_EQ
(
maybe_res
->
size
(),
3u
))
{
auto
&
res
=
*
maybe_res
;
CHECK_EQ
(
res
[
1
],
1
);
CHECK_EQ
(
res
[
2
],
4
);
CHECK_EQ
(
res
[
3
],
9
);
}
}
}
}
}
}
SCENARIO
(
"get_as can convert config values to custom types"
)
{
std
::
vector
<
std
::
pair
<
weekday
,
std
::
string
>>
weekday_values
{
{
weekday
::
monday
,
"monday"
s
},
{
weekday
::
tuesday
,
"tuesday"
s
},
{
weekday
::
wednesday
,
"wednesday"
s
},
{
weekday
::
thursday
,
"thursday"
s
},
{
weekday
::
friday
,
"friday"
s
},
{
weekday
::
saturday
,
"saturday"
s
},
{
weekday
::
sunday
,
"sunday"
s
}};
for
(
const
auto
&
[
enum_val
,
str_val
]
:
weekday_values
)
{
config_value
x
{
str_val
};
GIVEN
(
"the config value "
<<
x
)
{
WHEN
(
"using get_as with weekday"
)
{
THEN
(
"CAF picks up the custom inspect_value overload for conversion"
)
{
auto
maybe_res
=
get_as
<
weekday
>
(
x
);
if
(
CHECK
(
maybe_res
))
CHECK_EQ
(
*
maybe_res
,
enum_val
);
}
}
}
}
config_value
::
dictionary
my_request_dict
;
my_request_dict
[
"a"
]
=
config_value
{
10
};
my_request_dict
[
"b"
]
=
config_value
{
20
};
auto
my_request_val
=
config_value
{
my_request_dict
};
GIVEN
(
"the config value "
<<
my_request_val
)
{
WHEN
(
"using get_as with my_request"
)
{
THEN
(
"CAF picks up the custom inspect overload for conversion"
)
{
auto
maybe_res
=
get_as
<
my_request
>
(
my_request_val
);
if
(
CHECK
(
maybe_res
))
CHECK_EQ
(
*
maybe_res
,
my_request
(
10
,
20
));
}
}
}
std
::
vector
<
config_value
>
obj_vals
{
config_value
{
my_request_val
},
config_value
{
config_value
::
dictionary
{}},
config_value
{
"{}"
s
}};
for
(
auto
&
x
:
obj_vals
)
{
GIVEN
(
"the config value "
<<
x
)
{
WHEN
(
"using get_as with dummy_tag_type"
)
{
THEN
(
"CAF only checks whether the config value is dictionary-ish"
)
{
CHECK
(
get_as
<
dummy_tag_type
>
(
my_request_val
));
}
}
}
}
std
::
vector
<
config_value
>
non_obj_vals
{
config_value
{},
config_value
{
42
},
config_value
{
"[1,2,3]"
s
}};
for
(
auto
&
x
:
non_obj_vals
)
{
GIVEN
(
"the config value "
<<
x
)
{
WHEN
(
"using get_as with dummy_tag_type"
)
{
THEN
(
"conversion fails"
)
{
CHECK_EQ
(
get_as
<
dummy_tag_type
>
(
x
),
sec
::
conversion_failed
);
}
}
}
}
}
SCENARIO
(
"get_or converts or returns a fallback value"
)
{
using
namespace
caf
::
literals
;
GIVEN
(
"the config value 42"
)
{
config_value
x
{
42
};
WHEN
(
"using get_or with type int"
)
{
THEN
(
"CAF ignores the default value"
)
{
CHECK_EQ
(
get_or
(
x
,
10
),
42
);
}
}
WHEN
(
"using get_or with type string"
)
{
THEN
(
"CAF ignores the default value"
)
{
CHECK_EQ
(
get_or
(
x
,
"foo"
s
),
"42"
s
);
}
}
WHEN
(
"using get_or with type bool"
)
{
THEN
(
"CAF returns the default value"
)
{
CHECK_EQ
(
get_or
(
x
,
false
),
false
);
}
}
WHEN
(
"using get_or with type span<int>"
)
{
int
fallback_arr
[]
=
{
10
,
20
,
30
};
auto
fallback
=
make_span
(
fallback_arr
);
THEN
(
"CAF returns the default value after converting it to vector<int>"
)
{
auto
result
=
get_or
(
x
,
fallback
);
static_assert
(
std
::
is_same
<
decltype
(
result
),
std
::
vector
<
int
>>::
value
);
CHECK_EQ
(
result
,
std
::
vector
<
int
>
({
10
,
20
,
30
}));
}
}
}
}
SCENARIO
(
"config values can default-construct all registered types"
)
{
auto
from
=
[](
type_id_t
id
)
{
config_value
result
;
if
(
auto
err
=
result
.
default_construct
(
id
))
CAF_FAIL
(
"default construction failed: "
<<
err
);
return
result
;
};
auto
keys
=
[](
const
auto
&
dict
)
{
std
::
vector
<
std
::
string
>
result
;
for
(
const
auto
&
kvp
:
dict
)
result
.
emplace_back
(
kvp
.
first
);
return
result
;
};
GIVEN
(
"a config value"
)
{
WHEN
(
"calling default_construct for any integral type"
)
{
THEN
(
"the config value becomes config_value::integer{0}"
)
{
CHECK_EQ
(
from
(
type_id_v
<
int8_t
>
),
config_value
{
0
});
CHECK_EQ
(
from
(
type_id_v
<
int16_t
>
),
config_value
{
0
});
CHECK_EQ
(
from
(
type_id_v
<
int32_t
>
),
config_value
{
0
});
CHECK_EQ
(
from
(
type_id_v
<
int64_t
>
),
config_value
{
0
});
CHECK_EQ
(
from
(
type_id_v
<
uint8_t
>
),
config_value
{
0
});
CHECK_EQ
(
from
(
type_id_v
<
uint16_t
>
),
config_value
{
0
});
CHECK_EQ
(
from
(
type_id_v
<
uint32_t
>
),
config_value
{
0
});
CHECK_EQ
(
from
(
type_id_v
<
uint64_t
>
),
config_value
{
0
});
}
}
WHEN
(
"calling default_construct for any floating point type"
)
{
THEN
(
"the config value becomes config_value::real{0}"
)
{
CHECK_EQ
(
from
(
type_id_v
<
float
>
),
config_value
{
0.0
});
CHECK_EQ
(
from
(
type_id_v
<
double
>
),
config_value
{
0.0
});
CHECK_EQ
(
from
(
type_id_v
<
long
double
>
),
config_value
{
0.0
});
}
}
WHEN
(
"calling default_construct for std::string"
)
{
THEN
(
"the config value becomes
\"\"
"
)
{
CHECK_EQ
(
from
(
type_id_v
<
std
::
string
>
),
config_value
{
std
::
string
{}});
}
}
WHEN
(
"calling default_construct for caf::timespan"
)
{
THEN
(
"the config value becomes 0s"
)
{
CHECK_EQ
(
from
(
type_id_v
<
timespan
>
),
config_value
{
timespan
{
0
}});
}
}
WHEN
(
"calling default_construct for caf::uri"
)
{
THEN
(
"the config value becomes an empty URI"
)
{
CHECK_EQ
(
from
(
type_id_v
<
uri
>
),
config_value
{
uri
{}});
}
}
WHEN
(
"calling default_construct for any list-like type"
)
{
THEN
(
"the config value becomes a config_value::list"
)
{
CHECK_EQ
(
from
(
type_id_v
<
std
::
vector
<
actor
>>
).
get_data
().
index
(),
7u
);
CHECK_EQ
(
from
(
type_id_v
<
std
::
vector
<
bool
>>
).
get_data
().
index
(),
7u
);
}
}
WHEN
(
"calling default_construct for any custom non-list type"
)
{
THEN
(
"the config value becomes a dictionary"
)
{
auto
val
=
from
(
type_id_v
<
my_request
>
);
CHECK_EQ
(
val
.
get_data
().
index
(),
8u
);
auto
&
dict
=
val
.
as_dictionary
();
CHECK_EQ
(
keys
(
dict
),
std
::
vector
<
std
::
string
>
({
"a"
,
"b"
}));
CHECK_EQ
(
dict
[
"a"
].
get_data
().
index
(),
1u
);
CHECK_EQ
(
get_as
<
int32_t
>
(
dict
[
"a"
]),
0
);
CHECK_EQ
(
dict
[
"b"
].
get_data
().
index
(),
1u
);
CHECK_EQ
(
get_as
<
int32_t
>
(
dict
[
"b"
]),
0
);
}
}
}
}
CAF_TEST
(
default_constructed
)
{
config_value
x
;
CAF_CHECK_EQUAL
(
holds_alternative
<
int64_t
>
(
x
),
true
);
CAF_CHECK_EQUAL
(
get
<
int64_t
>
(
x
),
0
);
CAF_CHECK_EQUAL
(
x
.
type_name
(),
"integer"
s
);
CAF_CHECK_EQUAL
(
holds_alternative
<
none_t
>
(
x
),
true
);
CAF_CHECK_EQUAL
(
x
.
type_name
(),
"none"
s
);
}
CAF_TEST
(
positive
integer
)
{
...
...
@@ -441,70 +1039,4 @@ CAF_TEST(conversion to std::unordered_multimap) {
CAF_CHECK_EQUAL
(
*
ys
,
map_type
({{
"a"
,
1
},
{
"b"
,
2
},
{
"c"
,
3
},
{
"d"
,
4
}}));
}
namespace
{
struct
point_3d
{
int32_t
x
;
int32_t
y
;
int32_t
z
;
};
[[
maybe_unused
]]
bool
operator
==
(
const
point_3d
&
x
,
const
point_3d
&
y
)
{
return
std
::
tie
(
x
.
x
,
x
.
y
,
x
.
z
)
==
std
::
tie
(
y
.
x
,
y
.
y
,
y
.
z
);
}
template
<
class
Inspector
>
bool
inspect
(
Inspector
&
f
,
point_3d
&
x
)
{
return
f
.
object
(
x
).
fields
(
f
.
field
(
"x"
,
x
.
x
),
f
.
field
(
"y"
,
x
.
y
),
f
.
field
(
"z"
,
x
.
z
));
}
struct
line
{
point_3d
p1
;
point_3d
p2
;
};
[[
maybe_unused
]]
bool
operator
==
(
const
line
&
x
,
const
line
&
y
)
{
return
std
::
tie
(
x
.
p1
,
x
.
p2
)
==
std
::
tie
(
y
.
p1
,
y
.
p2
);
}
template
<
class
Inspector
>
bool
inspect
(
Inspector
&
f
,
line
&
x
)
{
return
f
.
object
(
x
).
fields
(
f
.
field
(
"p1"
,
x
.
p1
),
f
.
field
(
"p2"
,
x
.
p2
));
}
}
// namespace
CAF_TEST
(
config
values
pick
up
user
defined
inspect
overloads
)
{
CAF_MESSAGE
(
"users can fill dictionaries with field contents"
);
{
config_value
x
;
auto
&
dict
=
x
.
as_dictionary
();
put
(
dict
,
"p1.x"
,
1
);
put
(
dict
,
"p1.y"
,
2
);
put
(
dict
,
"p1.z"
,
3
);
put
(
dict
,
"p2.x"
,
10
);
put
(
dict
,
"p2.y"
,
20
);
put
(
dict
,
"p2.z"
,
30
);
auto
l
=
get_if
<
line
>
(
&
x
);
if
(
CAF_CHECK_NOT_EQUAL
(
l
,
none
))
CAF_CHECK_EQUAL
(
*
l
,
(
line
{{
1
,
2
,
3
},
{
10
,
20
,
30
}}));
}
CAF_MESSAGE
(
"users can pass objects as dictionaries on the command line"
);
{
auto
val
=
config_value
::
parse
(
"{p1{x=1,y=2,z=3},p2{x=10,y=20,z=30}}"
);
CAF_CHECK
(
val
);
if
(
val
)
{
auto
l
=
get_if
<
line
>
(
std
::
addressof
(
*
val
));
if
(
CAF_CHECK_NOT_EQUAL
(
l
,
none
))
CAF_CHECK_EQUAL
(
*
l
,
(
line
{{
1
,
2
,
3
},
{
10
,
20
,
30
}}));
}
}
CAF_MESSAGE
(
"value readers appear as inspectors with human-readable format"
);
{
config_value
x
{
std
::
string
{
"saturday"
}};
auto
val
=
get_if
<
weekday
>
(
&
x
);
if
(
CAF_CHECK
(
val
))
CAF_CHECK_EQUAL
(
*
val
,
weekday
::
saturday
);
}
}
CAF_TEST_FIXTURE_SCOPE_END
()
libcaf_core/test/core-test.hpp
View file @
ab7535c7
...
...
@@ -108,8 +108,12 @@ struct i64_wrapper {
};
struct
my_request
{
int32_t
a
;
int32_t
b
;
int32_t
a
=
0
;
int32_t
b
=
0
;
my_request
()
=
default
;
my_request
(
int
a
,
int
b
)
:
a
(
a
),
b
(
b
)
{
// nop
}
};
[[
maybe_unused
]]
inline
bool
operator
==
(
const
my_request
&
x
,
...
...
libcaf_core/test/detail/parser/read_config.cpp
View file @
ab7535c7
...
...
@@ -145,7 +145,7 @@ const auto conf0_log = make_log(
"key: foo=bar"
,
"{"
,
"key: foo"
,
"value (string):
\"
bar
\"
"
,
"value (string):
bar
"
,
"}"
,
"key: 1group"
,
"{"
,
...
...
@@ -162,7 +162,7 @@ const auto conf0_log = make_log(
"key: padding"
,
"value (integer): 10"
,
"key: file-name"
,
"value (string):
\"
foobar.ini
\"
"
,
"value (string):
foobar.ini
"
,
"}"
,
"key: scheduler"
,
"{"
,
...
...
@@ -184,7 +184,7 @@ const auto conf0_log = make_log(
"value (integer): 23"
,
"value (integer): 2"
,
"value (integer): 4"
,
"value (string):
\"
abc
\"
"
,
"value (string):
abc
"
,
"]"
,
"key: some-map"
,
"{"
,
...
...
@@ -193,7 +193,7 @@ const auto conf0_log = make_log(
"key: entry2"
,
"value (integer): 23"
,
"key: entry3"
,
"value (string):
\"
abc
\"
"
,
"value (string):
abc
"
,
"}"
,
"key: middleman"
,
"{"
,
...
...
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