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
f4ecc9b4
Commit
f4ecc9b4
authored
Jul 26, 2020
by
Dominik Charousset
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add scaffold for the new inspector API
parent
5930a041
Changes
8
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
1600 additions
and
105 deletions
+1600
-105
libcaf_core/CMakeLists.txt
libcaf_core/CMakeLists.txt
+2
-0
libcaf_core/caf/inspector_access.hpp
libcaf_core/caf/inspector_access.hpp
+71
-0
libcaf_core/caf/load_inspector.hpp
libcaf_core/caf/load_inspector.hpp
+363
-0
libcaf_core/caf/save_inspector.hpp
libcaf_core/caf/save_inspector.hpp
+157
-0
libcaf_core/test/load_inspector.cpp
libcaf_core/test/load_inspector.cpp
+343
-0
libcaf_core/test/save_inspector.cpp
libcaf_core/test/save_inspector.cpp
+352
-0
manual/ConfiguringActorApplications.rst
manual/ConfiguringActorApplications.rst
+2
-2
manual/TypeInspection.rst
manual/TypeInspection.rst
+310
-103
No files found.
libcaf_core/CMakeLists.txt
View file @
f4ecc9b4
...
@@ -280,6 +280,7 @@ caf_add_test_suites(caf-core-test
...
@@ -280,6 +280,7 @@ caf_add_test_suites(caf-core-test
ipv6_address
ipv6_address
ipv6_endpoint
ipv6_endpoint
ipv6_subnet
ipv6_subnet
load_inspector
local_group
local_group
logger
logger
mailbox_element
mailbox_element
...
@@ -302,6 +303,7 @@ caf_add_test_suites(caf-core-test
...
@@ -302,6 +303,7 @@ caf_add_test_suites(caf-core-test
policy.select_any
policy.select_any
request_timeout
request_timeout
result
result
save_inspector
selective_streaming
selective_streaming
serial_reply
serial_reply
serialization
serialization
...
...
libcaf_core/caf/inspector_access.hpp
0 → 100644
View file @
f4ecc9b4
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2020 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#pragma once
#include "caf/detail/inspect.hpp"
#include "caf/fwd.hpp"
namespace
caf
{
/// Customization point for adding support for a custom type. The default
/// implementation requires an `inspect` overload for `T` that is available via
/// ADL.
template
<
class
T
>
struct
inspector_access
{
template
<
class
Inspector
>
static
bool
apply
(
Inspector
&
f
,
T
&
x
)
{
using
detail
::
inspect
;
using
result_type
=
decltype
(
inspect
(
f
,
x
));
if
constexpr
(
std
::
is_same
<
result_type
,
bool
>::
value
)
return
inspect
(
f
,
x
);
else
return
apply_deprecated
(
f
,
x
);
}
template
<
class
Inspector
>
[[
deprecated
(
"inspect() overloads should return bool"
)]]
static
bool
apply_deprecated
(
Inspector
&
f
,
T
&
x
)
{
if
(
auto
err
=
inspect
(
f
,
x
))
{
f
.
set_error
(
std
::
move
(
err
));
return
false
;
}
return
true
;
}
};
/// Customization point to influence how inspectors treat fields of type `T`.
template
<
class
T
>
struct
inspector_access_traits
{
static
constexpr
bool
is_optional
=
false
;
static
constexpr
bool
is_sum_type
=
false
;
};
template
<
class
T
>
struct
inspector_access_traits
<
optional
<
T
>>
{
static
constexpr
bool
is_optional
=
true
;
static
constexpr
bool
is_sum_type
=
false
;
};
template
<
class
...
Ts
>
struct
inspector_access_traits
<
variant
<
Ts
...
>>
{
static
constexpr
bool
is_optional
=
false
;
static
constexpr
bool
is_sum_type
=
true
;
};
}
// namespace caf
libcaf_core/caf/load_inspector.hpp
0 → 100644
View file @
f4ecc9b4
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2020 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#pragma once
#include <type_traits>
#include <utility>
#include "caf/error.hpp"
#include "caf/inspector_access.hpp"
#include "caf/sec.hpp"
#include "caf/string_view.hpp"
namespace
caf
{
/// Base type for inspectors that load objects from some input source. Deriving
/// from this class enables the inspector DSL.
/// @note The derived type still needs to provide an `object()` member function
/// for the DSL.
class
load_inspector
{
public:
// -- contants ---------------------------------------------------------------
/// Convenience constant to indicate success of a processing step.
static
constexpr
bool
ok
=
true
;
/// Convenience constant to indicate that a processing step failed and no
/// further processing steps should take place.
static
constexpr
bool
stop
=
false
;
/// Enables dispatching on the inspector type.
static
constexpr
bool
is_loading
=
true
;
/// A load inspector never reads the state of an object.
static
constexpr
bool
reads_state
=
false
;
/// A load inspector overrides the state of an object.
static
constexpr
bool
writes_state
=
true
;
// -- error management -------------------------------------------------------
template
<
class
Inspector
>
static
void
set_invariant_check_error
(
Inspector
&
f
,
string_view
field_name
,
string_view
object_name
)
{
std
::
string
msg
=
"invalid argument to field "
;
msg
.
insert
(
msg
.
end
(),
field_name
.
begin
(),
field_name
.
end
());
msg
+=
" for object of type "
;
msg
.
insert
(
msg
.
end
(),
object_name
.
begin
(),
object_name
.
end
());
msg
+=
": invariant check failed"
;
f
.
set_error
(
make_error
(
caf
::
sec
::
invalid_argument
,
std
::
move
(
msg
)));
}
template
<
class
Inspector
>
static
void
set_field_store_error
(
Inspector
&
f
,
string_view
field_name
,
string_view
object_name
)
{
std
::
string
msg
=
"invalid argument to field "
;
msg
.
insert
(
msg
.
end
(),
field_name
.
begin
(),
field_name
.
end
());
msg
+=
" for object of type "
;
msg
.
insert
(
msg
.
end
(),
object_name
.
begin
(),
object_name
.
end
());
msg
+=
": setter returned false"
;
f
.
set_error
(
make_error
(
caf
::
sec
::
invalid_argument
,
std
::
move
(
msg
)));
}
// -- DSL types for regular fields -------------------------------------------
template
<
class
T
,
class
Predicate
>
struct
field_with_invariant_and_fallback_t
{
string_view
field_name
;
T
*
val
;
T
fallback
;
Predicate
predicate
;
template
<
class
Inspector
>
bool
operator
()(
string_view
object_name
,
Inspector
&
f
)
{
bool
is_present
=
false
;
if
(
!
f
.
begin_field
(
field_name
,
is_present
))
return
stop
;
if
(
is_present
)
{
if
(
inspector_access
<
T
>::
apply
(
f
,
*
val
)
&&
f
.
end_field
())
{
if
(
predicate
(
*
val
))
return
ok
;
set_invariant_check_error
(
f
,
field_name
,
object_name
);
}
return
stop
;
}
*
val
=
std
::
move
(
fallback
);
return
f
.
end_field
();
}
};
template
<
class
T
>
struct
field_with_fallback_t
{
string_view
field_name
;
T
*
val
;
T
fallback
;
template
<
class
Inspector
>
bool
operator
()(
string_view
,
Inspector
&
f
)
{
bool
is_present
=
false
;
if
(
!
f
.
begin_field
(
field_name
,
is_present
))
return
stop
;
if
(
is_present
)
return
inspector_access
<
T
>::
apply
(
f
,
*
val
)
&&
f
.
end_field
();
*
val
=
std
::
move
(
fallback
);
return
f
.
end_field
();
}
template
<
class
Predicate
>
auto
invariant
(
Predicate
predicate
)
&&
{
return
field_with_invariant_and_fallback_t
<
T
,
Predicate
>
{
field_name
,
val
,
std
::
move
(
fallback
),
std
::
move
(
predicate
),
};
}
};
template
<
class
T
,
class
Predicate
>
struct
field_with_invariant_t
{
string_view
field_name
;
T
*
val
;
Predicate
predicate
;
template
<
class
Inspector
>
bool
operator
()(
string_view
object_name
,
Inspector
&
f
)
{
if
(
f
.
begin_field
(
field_name
)
//
&&
inspector_access
<
T
>::
apply
(
f
,
*
val
)
//
&&
f
.
end_field
())
{
if
(
predicate
(
*
val
))
return
ok
;
set_invariant_check_error
(
f
,
field_name
,
object_name
);
}
return
stop
;
}
auto
fallback
(
T
value
)
&&
{
return
field_with_invariant_and_fallback_t
<
T
,
Predicate
>
{
field_name
,
val
,
std
::
move
(
value
),
std
::
move
(
predicate
),
};
}
};
template
<
class
T
>
struct
field_t
{
string_view
field_name
;
T
*
val
;
template
<
class
Inspector
>
bool
operator
()(
string_view
,
Inspector
&
f
)
{
if
constexpr
(
inspector_access_traits
<
T
>::
is_optional
)
{
bool
is_present
=
false
;
if
(
!
f
.
begin_field
(
field_name
,
is_present
))
return
stop
;
using
value_type
=
std
::
decay_t
<
decltype
(
**
val
)
>
;
if
(
is_present
)
{
auto
tmp
=
value_type
{};
if
(
!
inspector_access
<
value_type
>::
apply
(
f
,
tmp
))
return
stop
;
*
val
=
std
::
move
(
tmp
);
return
f
.
end_field
();
}
else
{
*
val
=
T
{};
return
f
.
end_field
();
}
}
else
{
return
f
.
begin_field
(
field_name
)
//
&&
inspector_access
<
T
>::
apply
(
f
,
*
val
)
//
&&
f
.
end_field
();
}
}
auto
fallback
(
T
value
)
&&
{
return
field_with_fallback_t
<
T
>
{
field_name
,
val
,
std
::
move
(
value
)};
}
template
<
class
Predicate
>
auto
invariant
(
Predicate
predicate
)
&&
{
return
field_with_invariant_t
<
T
,
Predicate
>
{
field_name
,
val
,
std
::
move
(
predicate
),
};
}
};
// -- DSL types for virtual fields (getter and setter access) ----------------
template
<
class
T
,
class
Set
,
class
Predicate
>
struct
virt_field_with_invariant_and_fallback_t
{
string_view
field_name
;
Set
set
;
T
fallback
;
Predicate
predicate
;
template
<
class
Inspector
>
bool
operator
()(
string_view
object_name
,
Inspector
&
f
)
{
bool
is_present
=
false
;
if
(
!
f
.
begin_field
(
field_name
,
is_present
))
return
stop
;
if
(
is_present
)
{
auto
tmp
=
T
{};
if
(
!
inspector_access
<
T
>::
apply
(
f
,
tmp
))
return
stop
;
if
(
!
predicate
(
tmp
))
{
set_invariant_check_error
(
f
,
field_name
,
object_name
);
return
stop
;
}
if
(
!
set
(
std
::
move
(
tmp
)))
{
set_field_store_error
(
f
,
field_name
,
object_name
);
return
stop
;
}
return
f
.
end_field
();
}
if
(
!
set
(
std
::
move
(
fallback
)))
{
set_field_store_error
(
f
,
field_name
,
object_name
);
return
stop
;
}
return
f
.
end_field
();
}
};
template
<
class
T
,
class
Set
>
struct
virt_field_with_fallback_t
{
string_view
field_name
;
Set
set
;
T
fallback
;
template
<
class
Inspector
>
bool
operator
()(
string_view
object_name
,
Inspector
&
f
)
{
bool
is_present
=
false
;
if
(
!
f
.
begin_field
(
field_name
,
is_present
))
return
stop
;
if
(
is_present
)
{
auto
tmp
=
T
{};
if
(
!
inspector_access
<
T
>::
apply
(
f
,
tmp
))
return
stop
;
if
(
!
set
(
std
::
move
(
tmp
)))
{
set_field_store_error
(
f
,
field_name
,
object_name
);
return
stop
;
}
return
f
.
end_field
();
}
if
(
!
set
(
std
::
move
(
fallback
)))
{
set_field_store_error
(
f
,
field_name
,
object_name
);
return
stop
;
}
return
f
.
end_field
();
}
};
template
<
class
T
,
class
Set
,
class
Predicate
>
struct
virt_field_with_invariant_t
{
string_view
field_name
;
Set
set
;
Predicate
predicate
;
template
<
class
Inspector
>
bool
operator
()(
string_view
object_name
,
Inspector
&
f
)
{
if
(
!
f
.
begin_field
(
field_name
))
return
stop
;
auto
tmp
=
T
{};
if
(
!
inspector_access
<
T
>::
apply
(
f
,
tmp
))
return
stop
;
if
(
!
predicate
(
tmp
))
{
set_invariant_check_error
(
f
,
field_name
,
object_name
);
return
stop
;
}
if
(
!
set
(
std
::
move
(
tmp
)))
{
set_field_store_error
(
f
,
field_name
,
object_name
);
return
stop
;
}
return
f
.
end_field
();
}
auto
fallback
(
T
value
)
&&
{
return
virt_field_with_invariant_and_fallback_t
<
T
,
Set
,
Predicate
>
{
field_name
,
std
::
move
(
set
),
std
::
move
(
value
),
std
::
move
(
predicate
),
};
}
};
template
<
class
T
,
class
Set
>
struct
virt_field_t
{
string_view
field_name
;
Set
set
;
template
<
class
Inspector
>
bool
operator
()(
string_view
,
Inspector
&
f
)
{
if
(
!
f
.
begin_field
(
field_name
))
return
stop
;
auto
tmp
=
T
{};
if
(
!
inspector_access
<
T
>::
apply
(
f
,
tmp
))
return
stop
;
if
(
!
set
(
std
::
move
(
tmp
)))
{
}
return
f
.
end_field
();
}
auto
fallback
(
T
value
)
&&
{
return
virt_field_with_fallback_t
<
T
,
Set
>
{
field_name
,
std
::
move
(
set
),
std
::
move
(
value
),
};
}
};
template
<
class
Inspector
>
struct
object_t
{
string_view
object_name
;
Inspector
*
f
;
template
<
class
...
Fields
>
bool
fields
(
Fields
&&
...
fs
)
{
return
f
->
begin_object
(
object_name
)
&&
(
fs
(
object_name
,
*
f
)
&&
...)
&&
f
->
end_object
();
}
auto
pretty_name
(
string_view
name
)
&&
{
return
object_t
{
name
,
f
};
}
};
// -- factory functions ------------------------------------------------------
template
<
class
T
>
static
auto
field
(
string_view
name
,
T
&
x
)
{
return
field_t
<
T
>
{
name
,
&
x
};
}
template
<
class
Get
,
class
Set
>
static
auto
field
(
string_view
name
,
Get
get
,
Set
set
)
{
using
field_type
=
std
::
decay_t
<
decltype
(
get
())
>
;
using
set_result
=
decltype
(
set
(
std
::
declval
<
field_type
>
()));
static_assert
(
std
::
is_same
<
set_result
,
bool
>::
value
,
"setters of fields must return bool"
);
return
virt_field_t
<
field_type
,
Set
>
{
name
,
set
};
}
};
}
// namespace caf
libcaf_core/caf/save_inspector.hpp
0 → 100644
View file @
f4ecc9b4
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2020 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#pragma once
#include "caf/error.hpp"
#include "caf/inspector_access.hpp"
#include "caf/sec.hpp"
#include "caf/string_view.hpp"
namespace
caf
{
/// Base type for inspectors that save objects to some output sink. Deriving
/// from this class enables the inspector DSL.
/// @note The derived type still needs to provide an `object()` member function
/// for the DSL.
class
save_inspector
{
public:
// -- contants ---------------------------------------------------------------
/// Convenience constant to indicate success of a processing step.
static
constexpr
bool
ok
=
true
;
/// Convenience constant to indicate that a processing step failed and no
/// further processing steps should take place.
static
constexpr
bool
stop
=
false
;
/// Enables dispatching on the inspector type.
static
constexpr
bool
is_loading
=
false
;
/// A save inspector only reads the state of an object.
static
constexpr
bool
reads_state
=
true
;
/// A load inspector never modifies the state of an object.
static
constexpr
bool
writes_state
=
false
;
// -- DSL types for regular fields -------------------------------------------
template
<
class
T
>
struct
field_t
{
string_view
field_name
;
T
*
val
;
template
<
class
Inspector
>
bool
operator
()(
string_view
,
Inspector
&
f
)
{
if
constexpr
(
inspector_access_traits
<
T
>::
is_optional
)
{
auto
&
ref
=
*
val
;
using
value_type
=
std
::
decay_t
<
decltype
(
*
ref
)
>
;
if
(
ref
)
{
return
f
.
begin_field
(
field_name
,
true
)
//
&&
inspector_access
<
value_type
>::
apply
(
f
,
*
ref
)
//
&&
f
.
end_field
();
}
else
{
return
f
.
begin_field
(
field_name
,
false
)
&&
f
.
end_field
();
}
}
else
{
return
f
.
begin_field
(
field_name
)
//
&&
inspector_access
<
T
>::
apply
(
f
,
*
val
)
//
&&
f
.
end_field
();
}
}
template
<
class
Unused
>
field_t
&
fallback
(
Unused
&&
)
{
return
*
this
;
}
template
<
class
Predicate
>
field_t
invariant
(
Predicate
&&
)
{
return
*
this
;
}
};
// -- DSL types for virtual fields (getter and setter access) ----------------
template
<
class
T
,
class
Get
>
struct
virt_field_t
{
string_view
field_name
;
Get
get
;
template
<
class
Inspector
>
bool
operator
()(
string_view
,
Inspector
&
f
)
{
if
(
!
f
.
begin_field
(
field_name
))
return
stop
;
auto
&&
value
=
get
();
using
value_type
=
std
::
remove_reference_t
<
decltype
(
value
)
>
;
if
constexpr
(
std
::
is_const
<
value_type
>::
value
)
{
// Force a mutable reference, because the inspect API requires it. This
// const_cast is always safe, because we never actually modify the
// object.
using
mutable_ref
=
std
::
remove_const_t
<
value_type
>&
;
if
(
!
inspector_access
<
T
>::
apply
(
f
,
const_cast
<
mutable_ref
>
(
value
)))
return
stop
;
}
else
{
if
(
!
inspector_access
<
T
>::
apply
(
f
,
value
))
return
stop
;
}
return
f
.
end_field
();
}
template
<
class
Unused
>
virt_field_t
&
fallback
(
Unused
&&
)
{
return
*
this
;
}
template
<
class
Predicate
>
virt_field_t
invariant
(
Predicate
&&
)
{
return
*
this
;
}
};
template
<
class
Inspector
>
struct
object_t
{
string_view
object_name
;
Inspector
*
f
;
template
<
class
...
Fields
>
bool
fields
(
Fields
&&
...
fs
)
{
return
f
->
begin_object
(
object_name
)
&&
(
fs
(
object_name
,
*
f
)
&&
...)
&&
f
->
end_object
();
}
auto
pretty_name
(
string_view
name
)
&&
{
return
object_t
{
name
,
f
};
}
};
// -- factory functions ------------------------------------------------------
template
<
class
T
>
static
auto
field
(
string_view
name
,
T
&
x
)
{
return
field_t
<
T
>
{
name
,
&
x
};
}
template
<
class
Get
,
class
Set
>
static
auto
field
(
string_view
name
,
Get
get
,
Set
&&
)
{
using
field_type
=
std
::
decay_t
<
decltype
(
get
())
>
;
return
virt_field_t
<
field_type
,
Get
>
{
name
,
get
};
}
};
}
// namespace caf
libcaf_core/test/load_inspector.cpp
0 → 100644
View file @
f4ecc9b4
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2020 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#define CAF_SUITE load_inspector
#include "caf/load_inspector.hpp"
#include "caf/test/dsl.hpp"
#include <cstdint>
#include <string>
#include <vector>
namespace
caf
{
template
<
>
struct
inspector_access
<
std
::
string
>
{
template
<
class
Inspector
>
static
bool
apply
(
Inspector
&
f
,
std
::
string
&
x
)
{
return
f
.
value
(
x
);
}
};
template
<
>
struct
inspector_access
<
int32_t
>
{
template
<
class
Inspector
>
static
bool
apply
(
Inspector
&
f
,
int32_t
&
x
)
{
return
f
.
value
(
x
);
}
};
template
<
>
struct
inspector_access
<
double
>
{
template
<
class
Inspector
>
static
bool
apply
(
Inspector
&
f
,
double
&
x
)
{
return
f
.
value
(
x
);
}
};
}
// namespace caf
using
namespace
caf
;
namespace
{
using
string_list
=
std
::
vector
<
std
::
string
>
;
struct
point_3d
{
static
inline
string_view
tname
=
"point_3d"
;
int32_t
x
;
int32_t
y
;
int32_t
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
{
static
inline
string_view
tname
=
"line"
;
point_3d
p1
;
point_3d
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
));
}
struct
duration
{
static
inline
string_view
tname
=
"duration"
;
std
::
string
unit
;
double
count
;
};
bool
valid_time_unit
(
const
std
::
string
&
unit
)
{
return
unit
==
"seconds"
||
unit
==
"minutes"
;
}
template
<
class
Inspector
>
bool
inspect
(
Inspector
&
f
,
duration
&
x
)
{
return
f
.
object
(
x
).
fields
(
f
.
field
(
"unit"
,
x
.
unit
).
fallback
(
"seconds"
).
invariant
(
valid_time_unit
),
f
.
field
(
"count"
,
x
.
count
));
}
struct
person
{
static
inline
string_view
tname
=
"person"
;
std
::
string
name
;
optional
<
std
::
string
>
phone
;
};
template
<
class
Inspector
>
bool
inspect
(
Inspector
&
f
,
person
&
x
)
{
return
f
.
object
(
x
).
fields
(
f
.
field
(
"name"
,
x
.
name
),
f
.
field
(
"phone"
,
x
.
phone
));
}
class
foobar
{
public:
static
inline
string_view
tname
=
"foobar"
;
const
std
::
string
&
foo
()
{
return
foo_
;
}
void
foo
(
std
::
string
value
)
{
foo_
=
std
::
move
(
value
);
}
const
std
::
string
&
bar
()
{
return
bar_
;
}
void
bar
(
std
::
string
value
)
{
bar_
=
std
::
move
(
value
);
}
private:
std
::
string
foo_
;
std
::
string
bar_
;
};
template
<
class
Inspector
>
bool
inspect
(
Inspector
&
f
,
foobar
&
x
)
{
auto
get_foo
=
[
&
x
]()
->
decltype
(
auto
)
{
return
x
.
foo
();
};
auto
set_foo
=
[
&
x
](
std
::
string
value
)
{
x
.
foo
(
std
::
move
(
value
));
return
true
;
};
auto
get_bar
=
[
&
x
]()
->
decltype
(
auto
)
{
return
x
.
bar
();
};
auto
set_bar
=
[
&
x
](
std
::
string
value
)
{
x
.
bar
(
std
::
move
(
value
));
return
true
;
};
return
f
.
object
(
x
).
fields
(
f
.
field
(
"foo"
,
get_foo
,
set_foo
),
f
.
field
(
"bar"
,
get_bar
,
set_bar
));
}
struct
testee
:
load_inspector
{
std
::
string
log
;
error
err
;
void
set_error
(
error
x
)
{
err
=
std
::
move
(
x
);
}
size_t
indent
=
0
;
void
new_line
()
{
log
+=
'\n'
;
log
.
insert
(
log
.
end
(),
indent
,
' '
);
}
template
<
class
T
>
auto
object
(
T
&
)
{
return
object_t
<
testee
>
{
T
::
tname
,
this
};
}
bool
begin_object
(
string_view
object_name
)
{
new_line
();
indent
+=
2
;
log
+=
"begin object "
;
log
.
insert
(
log
.
end
(),
object_name
.
begin
(),
object_name
.
end
());
return
ok
;
}
bool
end_object
()
{
indent
-=
2
;
new_line
();
log
+=
"end object"
;
return
ok
;
}
bool
begin_field
(
string_view
name
)
{
new_line
();
indent
+=
2
;
log
+=
"begin field "
;
log
.
insert
(
log
.
end
(),
name
.
begin
(),
name
.
end
());
return
ok
;
}
bool
begin_field
(
string_view
name
,
bool
&
is_present
)
{
new_line
();
indent
+=
2
;
log
+=
"begin optional field "
;
log
.
insert
(
log
.
end
(),
name
.
begin
(),
name
.
end
());
is_present
=
false
;
return
ok
;
}
bool
end_field
()
{
indent
-=
2
;
new_line
();
log
+=
"end field"
;
return
ok
;
}
template
<
class
T
>
bool
value
(
T
&
x
)
{
new_line
();
log
+=
type_name_v
<
T
>
;
log
+=
" value"
;
x
=
T
{};
return
ok
;
}
};
struct
fixture
{
testee
f
;
};
}
// namespace
CAF_TEST_FIXTURE_SCOPE
(
load_inspector_tests
,
fixture
)
CAF_TEST
(
load
inspectors
can
visit
simple
POD
types
)
{
point_3d
p
{
1
,
1
,
1
};
CAF_CHECK_EQUAL
(
inspect
(
f
,
p
),
true
);
CAF_CHECK_EQUAL
(
p
.
x
,
0
);
CAF_CHECK_EQUAL
(
p
.
y
,
0
);
CAF_CHECK_EQUAL
(
p
.
z
,
0
);
CAF_CHECK_EQUAL
(
f
.
log
,
R"_(
begin object point_3d
begin field x
int32_t value
end field
begin field y
int32_t value
end field
begin field z
int32_t value
end field
end object)_"
);
}
CAF_TEST
(
load
inspectors
recurse
into
members
)
{
line
l
{
point_3d
{
1
,
1
,
1
},
point_3d
{
1
,
1
,
1
}};
CAF_CHECK_EQUAL
(
inspect
(
f
,
l
),
true
);
CAF_CHECK_EQUAL
(
l
.
p1
.
x
,
0
);
CAF_CHECK_EQUAL
(
l
.
p1
.
y
,
0
);
CAF_CHECK_EQUAL
(
l
.
p1
.
z
,
0
);
CAF_CHECK_EQUAL
(
l
.
p2
.
x
,
0
);
CAF_CHECK_EQUAL
(
l
.
p2
.
y
,
0
);
CAF_CHECK_EQUAL
(
l
.
p2
.
z
,
0
);
CAF_CHECK_EQUAL
(
f
.
log
,
R"_(
begin object line
begin field p1
begin object point_3d
begin field x
int32_t value
end field
begin field y
int32_t value
end field
begin field z
int32_t value
end field
end object
end field
begin field p2
begin object point_3d
begin field x
int32_t value
end field
begin field y
int32_t value
end field
begin field z
int32_t value
end field
end object
end field
end object)_"
);
}
CAF_TEST
(
load
inspectors
support
fields
with
fallbacks
and
invariants
)
{
duration
d
{
"minutes"
,
42
};
CAF_CHECK_EQUAL
(
inspect
(
f
,
d
),
true
);
CAF_CHECK_EQUAL
(
d
.
unit
,
"seconds"
);
CAF_CHECK_EQUAL
(
d
.
count
,
0.0
);
CAF_CHECK_EQUAL
(
f
.
log
,
R"_(
begin object duration
begin optional field unit
end field
begin field count
double value
end field
end object)_"
);
}
CAF_TEST
(
load
inspectors
support
fields
with
optional
values
)
{
person
p
{
"Bruce Almighty"
,
std
::
string
{
"776-2323"
}};
CAF_CHECK_EQUAL
(
inspect
(
f
,
p
),
true
);
CAF_CHECK_EQUAL
(
p
.
name
,
""
);
CAF_CHECK_EQUAL
(
p
.
phone
,
none
);
CAF_CHECK_EQUAL
(
f
.
log
,
R"_(
begin object person
begin field name
std::string value
end field
begin optional field phone
end field
end object)_"
);
}
CAF_TEST
(
load
inspectors
support
fields
with
getters
and
setters
)
{
foobar
fb
;
fb
.
foo
(
"hello"
);
fb
.
bar
(
"world"
);
CAF_CHECK_EQUAL
(
inspect
(
f
,
fb
),
true
);
CAF_CHECK_EQUAL
(
fb
.
foo
(),
""
);
CAF_CHECK_EQUAL
(
fb
.
bar
(),
""
);
CAF_CHECK_EQUAL
(
f
.
log
,
R"_(
begin object foobar
begin field foo
std::string value
end field
begin field bar
std::string value
end field
end object)_"
);
}
CAF_TEST_FIXTURE_SCOPE_END
()
libcaf_core/test/save_inspector.cpp
0 → 100644
View file @
f4ecc9b4
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2020 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#define CAF_SUITE save_inspector
#include "caf/save_inspector.hpp"
#include "caf/test/dsl.hpp"
#include <cstdint>
#include <string>
#include <vector>
namespace
caf
{
template
<
>
struct
inspector_access
<
std
::
string
>
{
template
<
class
Inspector
>
static
bool
apply
(
Inspector
&
f
,
std
::
string
&
x
)
{
return
f
.
value
(
x
);
}
};
template
<
>
struct
inspector_access
<
int32_t
>
{
template
<
class
Inspector
>
static
bool
apply
(
Inspector
&
f
,
int32_t
&
x
)
{
return
f
.
value
(
x
);
}
};
template
<
>
struct
inspector_access
<
double
>
{
template
<
class
Inspector
>
static
bool
apply
(
Inspector
&
f
,
double
&
x
)
{
return
f
.
value
(
x
);
}
};
}
// namespace caf
using
namespace
caf
;
namespace
{
using
string_list
=
std
::
vector
<
std
::
string
>
;
struct
point_3d
{
static
inline
string_view
tname
=
"point_3d"
;
int32_t
x
;
int32_t
y
;
int32_t
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
{
static
inline
string_view
tname
=
"line"
;
point_3d
p1
;
point_3d
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
));
}
struct
duration
{
static
inline
string_view
tname
=
"duration"
;
std
::
string
unit
;
double
count
;
};
bool
valid_time_unit
(
const
std
::
string
&
unit
)
{
return
unit
==
"seconds"
||
unit
==
"minutes"
;
}
template
<
class
Inspector
>
bool
inspect
(
Inspector
&
f
,
duration
&
x
)
{
return
f
.
object
(
x
).
fields
(
f
.
field
(
"unit"
,
x
.
unit
).
fallback
(
"seconds"
).
invariant
(
valid_time_unit
),
f
.
field
(
"count"
,
x
.
count
));
}
struct
person
{
static
inline
string_view
tname
=
"person"
;
std
::
string
name
;
optional
<
std
::
string
>
phone
;
};
template
<
class
Inspector
>
bool
inspect
(
Inspector
&
f
,
person
&
x
)
{
return
f
.
object
(
x
).
fields
(
f
.
field
(
"name"
,
x
.
name
),
f
.
field
(
"phone"
,
x
.
phone
));
}
class
foobar
{
public:
static
inline
string_view
tname
=
"foobar"
;
const
std
::
string
&
foo
()
{
return
foo_
;
}
void
foo
(
std
::
string
value
)
{
foo_
=
std
::
move
(
value
);
}
const
std
::
string
&
bar
()
{
return
bar_
;
}
void
bar
(
std
::
string
value
)
{
bar_
=
std
::
move
(
value
);
}
private:
std
::
string
foo_
;
std
::
string
bar_
;
};
template
<
class
Inspector
>
bool
inspect
(
Inspector
&
f
,
foobar
&
x
)
{
auto
get_foo
=
[
&
x
]()
->
decltype
(
auto
)
{
return
x
.
foo
();
};
auto
set_foo
=
[
&
x
](
std
::
string
value
)
{
x
.
foo
(
std
::
move
(
value
));
return
true
;
};
auto
get_bar
=
[
&
x
]()
->
decltype
(
auto
)
{
return
x
.
bar
();
};
auto
set_bar
=
[
&
x
](
std
::
string
value
)
{
x
.
bar
(
std
::
move
(
value
));
return
true
;
};
return
f
.
object
(
x
).
fields
(
f
.
field
(
"foo"
,
get_foo
,
set_foo
),
f
.
field
(
"bar"
,
get_bar
,
set_bar
));
}
struct
testee
:
save_inspector
{
std
::
string
log
;
error
err
;
void
set_error
(
error
x
)
{
err
=
std
::
move
(
x
);
}
size_t
indent
=
0
;
void
new_line
()
{
log
+=
'\n'
;
log
.
insert
(
log
.
end
(),
indent
,
' '
);
}
template
<
class
T
>
auto
object
(
T
&
)
{
return
object_t
<
testee
>
{
T
::
tname
,
this
};
}
bool
begin_object
(
string_view
object_name
)
{
new_line
();
indent
+=
2
;
log
+=
"begin object "
;
log
.
insert
(
log
.
end
(),
object_name
.
begin
(),
object_name
.
end
());
return
ok
;
}
bool
end_object
()
{
indent
-=
2
;
new_line
();
log
+=
"end object"
;
return
ok
;
}
bool
begin_field
(
string_view
name
)
{
new_line
();
indent
+=
2
;
log
+=
"begin field "
;
log
.
insert
(
log
.
end
(),
name
.
begin
(),
name
.
end
());
return
ok
;
}
bool
begin_field
(
string_view
name
,
bool
)
{
new_line
();
indent
+=
2
;
log
+=
"begin optional field "
;
log
.
insert
(
log
.
end
(),
name
.
begin
(),
name
.
end
());
return
ok
;
}
bool
end_field
()
{
indent
-=
2
;
new_line
();
log
+=
"end field"
;
return
ok
;
}
template
<
class
T
>
bool
value
(
const
T
&
)
{
new_line
();
log
+=
type_name_v
<
T
>
;
log
+=
" value"
;
return
ok
;
}
};
struct
fixture
{
testee
f
;
};
}
// namespace
CAF_TEST_FIXTURE_SCOPE
(
load_inspector_tests
,
fixture
)
CAF_TEST
(
save
inspectors
can
visit
simple
POD
types
)
{
point_3d
p
{
1
,
1
,
1
};
CAF_CHECK_EQUAL
(
inspect
(
f
,
p
),
true
);
CAF_CHECK_EQUAL
(
p
.
x
,
1
);
CAF_CHECK_EQUAL
(
p
.
y
,
1
);
CAF_CHECK_EQUAL
(
p
.
z
,
1
);
CAF_CHECK_EQUAL
(
f
.
log
,
R"_(
begin object point_3d
begin field x
int32_t value
end field
begin field y
int32_t value
end field
begin field z
int32_t value
end field
end object)_"
);
}
CAF_TEST
(
save
inspectors
recurse
into
members
)
{
line
l
{
point_3d
{
1
,
1
,
1
},
point_3d
{
1
,
1
,
1
}};
CAF_CHECK_EQUAL
(
inspect
(
f
,
l
),
true
);
CAF_CHECK_EQUAL
(
l
.
p1
.
x
,
1
);
CAF_CHECK_EQUAL
(
l
.
p1
.
y
,
1
);
CAF_CHECK_EQUAL
(
l
.
p1
.
z
,
1
);
CAF_CHECK_EQUAL
(
l
.
p2
.
x
,
1
);
CAF_CHECK_EQUAL
(
l
.
p2
.
y
,
1
);
CAF_CHECK_EQUAL
(
l
.
p2
.
z
,
1
);
CAF_CHECK_EQUAL
(
f
.
log
,
R"_(
begin object line
begin field p1
begin object point_3d
begin field x
int32_t value
end field
begin field y
int32_t value
end field
begin field z
int32_t value
end field
end object
end field
begin field p2
begin object point_3d
begin field x
int32_t value
end field
begin field y
int32_t value
end field
begin field z
int32_t value
end field
end object
end field
end object)_"
);
}
CAF_TEST
(
save
inspectors
support
fields
with
fallbacks
and
invariants
)
{
duration
d
{
"minutes"
,
42.0
};
CAF_CHECK_EQUAL
(
inspect
(
f
,
d
),
true
);
CAF_CHECK_EQUAL
(
d
.
unit
,
"minutes"
);
CAF_CHECK_EQUAL
(
d
.
count
,
42.0
);
CAF_CHECK_EQUAL
(
f
.
log
,
R"_(
begin object duration
begin field unit
std::string value
end field
begin field count
double value
end field
end object)_"
);
}
CAF_TEST
(
save
inspectors
support
fields
with
optional
values
)
{
person
p1
{
"Eduard Example"
,
none
};
CAF_CHECK_EQUAL
(
inspect
(
f
,
p1
),
true
);
CAF_CHECK_EQUAL
(
f
.
log
,
R"_(
begin object person
begin field name
std::string value
end field
begin optional field phone
end field
end object)_"
);
f
.
log
.
clear
();
person
p2
{
"Bruce Almighty"
,
std
::
string
{
"776-2323"
}};
CAF_CHECK_EQUAL
(
inspect
(
f
,
p2
),
true
);
CAF_CHECK_EQUAL
(
f
.
log
,
R"_(
begin object person
begin field name
std::string value
end field
begin optional field phone
std::string value
end field
end object)_"
);
}
CAF_TEST
(
save
inspectors
support
fields
with
getters
and
setters
)
{
foobar
fb
;
fb
.
foo
(
"hello"
);
fb
.
bar
(
"world"
);
CAF_CHECK_EQUAL
(
inspect
(
f
,
fb
),
true
);
CAF_CHECK_EQUAL
(
fb
.
foo
(),
"hello"
);
CAF_CHECK_EQUAL
(
fb
.
bar
(),
"world"
);
CAF_CHECK_EQUAL
(
f
.
log
,
R"_(
begin object foobar
begin field foo
std::string value
end field
begin field bar
std::string value
end field
end object)_"
);
}
CAF_TEST_FIXTURE_SCOPE_END
()
manual/ConfiguringActorApplications.rst
View file @
f4ecc9b4
...
@@ -229,7 +229,7 @@ are usually detected at runtime and thus have no hard-coded default.
...
@@ -229,7 +229,7 @@ are usually detected at runtime and thus have no hard-coded default.
.. literalinclude:: /examples/caf-application.conf
.. literalinclude:: /examples/caf-application.conf
:language: none
:language: none
.. _
add-custom-message-type
:
.. _
custom-message-types
:
Adding Custom Message Types
Adding Custom Message Types
---------------------------
---------------------------
...
@@ -294,7 +294,7 @@ Adding the calculator actor type to our config is achieved by calling
...
@@ -294,7 +294,7 @@ Adding the calculator actor type to our config is achieved by calling
``add_actor_type``. After calling this in our config, we can spawn the
``add_actor_type``. After calling this in our config, we can spawn the
``calculator`` anywhere in the distributed actor system (assuming all nodes use
``calculator`` anywhere in the distributed actor system (assuming all nodes use
the same config). Note that the handle type still requires a type ID (see
the same config). Note that the handle type still requires a type ID (see
add-custom-message-type
_).
custom-message-types
_).
Our final example illustrates how to spawn a ``calculator`` locally by
Our final example illustrates how to spawn a ``calculator`` locally by
using its type name. Because the dynamic type name lookup can fail and the
using its type name. Because the dynamic type name lookup can fail and the
...
...
manual/TypeInspection.rst
View file @
f4ecc9b4
.. _type-inspection:
.. _type-inspection:
Type Inspection (Serialization and String Conversion)
Type Inspection
=====================================================
===============
CAF is designed with distributed systems in mind. Hence, all message types must
We designed CAF with distributed systems in mind. Hence, all message types must
be serializable and need a platform-neutral, unique name that is configured at
be serializable. Using a message type that is not serializable causes a compiler
startup (see :ref:`add-custom-message-type`). Using a message type that is not
error unless explicitly listed as unsafe message type by the user (see
serializable causes a compiler error (see :ref:`unsafe-message-type`). CAF
:ref:`unsafe-message-type`).
serializes individual elements of a message by using the inspection API. This
API allows users to provide code for serialization as well as string conversion
The inspection API allows CAF to deconstruct C++ objects into fields and values.
with a single free function. The signature for a class ``my_class`` is always as
Users can either provide free functions named ``inspect`` that CAF picks up via
follows:
`ADL <https://en.wikipedia.org/wiki/Argument-dependent_name_lookup>`_ or
specialize ``caf::inspector_access``.
In both cases, users call members and member functions on an ``Inspector`` that
provides a domain-specific language (DSL) for describing the structure of a C++
object.
POD Types
---------
Plain old data (POD) types always declare all member variables *public*. An
``inspect`` overload for PODs simply passes all member variables as fields to
the *inspector*. For example, consider the following POD type ``point_3d``:
.. code-block:: C++
.. code-block:: C++
template <class Inspector>
struct point_3d {
typename Inspector::result_type inspect(Inspector& f, my_class& x) {
int x;
return f(...);
int y;
}
int z;
};
The function ``inspect`` passes meta information and data fields to the
To allow CAF to properly serialize and deserialize the POD type, the simplest
variadic call operator of the inspector. The following example illustrates an
way is to write a free ``inspect`` function:
implementation for ``inspect`` for a simple POD struct.
.. literalinclude:: /examples/custom_type/custom_types_1.cpp
.. code-block:: C++
:language: C++
:lines: 23-33
template <class Inspector>
bool inspect(Inspector& f, point_3d& x) {
return f.object().fields(f.field("x", x.x),
f.field("y", x.y),
f.field("z", x.z));
}
The inspector recursively inspects all data fields and has builtin support for
After providing this function as well as listing ``point_3d`` in a type ID
(1) ``std::tuple``, (2) ``std::pair``, (3) C arrays, (4) any
block, CAF can save and load our custom POD type. Of course, this is a recursive
container type with ``x.size()``, ``x.empty()``,
process that allows us to use ``point_3d`` as member variables of other types.
``x.begin()`` and ``x.end()``.
We consciously made the inspect API as generic as possible to allow for
Working with 3rd party libraries usually rules out adding free functions for
extensibility. This allows users to use CAF's types in other contexts, to
existing classes, because the namespace belongs to another project. Hence, CAF
implement parsers, etc.
also allows specializing ``inspector_access`` instead. This requires slightly
more boilerplate code, but follows the same pattern:
.. code-block:: C++
Inspector Concept
namespace caf {
-----------------
template <>
struct inspector_access<point_3d> {
template <class Inspector>
static bool apply(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));
}
};
}
The following concept class shows the requirements for inspectors. The
After listing ``point_3d`` in a type ID block and either providing a free
placeholder ``T`` represents any user-defined type. For example,
``inspect`` overload or specializing ``inspector_access``, CAF is able to:
``error`` when performing I/O operations or some integer type when
implementing a hash function.
- Serialize and deserialize ``point_3d`` objects to/from Byte sequences.
- Render a ``point_3d`` as a human-readable string via ``caf::deep_to_string``.
- Read ``point_3d`` objects from a configuration file.
Types with Getter and Setter Access
-----------------------------------
Types that declare their fields *private* and only grant access via getter and
setter cannot pass references to the member variables to the inspector. Instead,
they can pass a pair of function objects to the inspector to read and write the
field.
Consider the following non-POD type ``foobar``:
.. code-block:: C++
.. code-block:: C++
Inspector {
class foobar {
using result_type = T;
public:
const std::string& foo() {
return foo_;
}
void foo(std::string value) {
foo_ = std::move(value);
}
const std::string& bar() {
return bar_;
}
void bar(std::string value) {
bar_ = std::move(value);
}
private:
std::string foo_;
std::string bar_;
};
Since ``foo_`` and ``bar_`` are not accessible from outside the class, the
inspector has to use the getter and setter functions. However, C++ has no
formalized API for getters and setters. Moreover, not all setters are so trivial
as in the example above. Setters may enforce invariants, for example, and thus
may fail.
In order to work with any flair of getter and setter functions, CAF requires
users to wrap these member functions calls into two function objects. The first
one wraps the getter, takes no arguments, and returns the underlying value
(either by reference or by value). The second one wraps the setter, takes
exactly one argument (the new value), and returns a ``bool`` that indicates
whether the operation succeeded (by returning ``true``) or failed (``by
returning false``).
The example below shows a possible ``inspect`` implementation for the ``fobar``
class shown before:
static constexpr bool reads_state = ...;
.. code-block:: C++
template <class Inspector>
bool inspect(Inspector& f, foobar& x) {
auto get_foo = [&x]() -> decltype(auto) { return x.foo(); };
auto set_foo = [&x](std::string value) {
x.foo(std::move(value));
return true;
};
auto get_bar = [&x]() -> decltype(auto) { return x.bar(); };
auto set_bar = [&x](std::string value) {
x.bar(std::move(value));
return true;
};
return f.object(x).fields(f.field("foo", get_foo, set_foo),
f.field("bar", get_bar, set_bar));
}
.. note::
For classes that lie in the responsibility of the same developers that
implement the ``inspect`` function, implementing ``inspect`` as friend
function inside the class usually can avoid going through the getter and
setter functions.
Inspector DSL
-------------
As shown in previous examples, type inspection with an inspector ``f`` always
starts with ``f.object(...)`` as entry point. After that, users may set a pretty
type name with ``pretty_name`` that inspectors may use when generating
human-readable output. Afterwards, the inspectors expect users to call
``fields`` with all member variables of the object.
The following pseudo code illustrates how the inspector DSL is structured in
terms of types involved and member function interfaces.
static constexpr bool writes_state = ...;
template <class... Ts>
.. code-block:: C++
result_type operator()(Ts&&...);
}
class Inspector {
public:
Object object(T obj);
A saving ``Inspector`` is required to handle constant lvalue and rvalue
Field field(string_view name, T& ref);
references. A loading ``Inspector`` must only accept mutable lvalue
references to data fields, but still allow for constant lvalue references and
rvalue references to annotations.
Annotations
Field field(string_view name, function<T()> get, function<void(T)> set);
-----------
};
Annotations allow users to fine-tune the behavior of inspectors by providing
class FieldWithFallbackAndInvariant;
addition meta information about a type. All annotations live in the namespace
``caf::meta`` and derive from ``caf::meta::annotation``. An
inspector can query whether a type ``T`` is an annotation with
``caf::meta::is_annotation<T>::value``. Annotations are passed to the
call operator of the inspector along with data fields. The following list shows
all annotations supported by CAF:
* ``type_name(n)``: Display type name as ``n`` in human-friendly output (position before data fields).
class FieldWithFallback {
* ``hex_formatted()``: Format the following data field in hex format.
public:
* ``omittable()``: Omit the following data field in human-friendly output.
FieldWithFallbackAndInvariant invariant(function<bool>(T));
* ``omittable_if_empty()``: Omit the following data field if it is empty in human-friendly output.
};
* ``omittable_if_none()``: Omit the following data field if it equals ``none`` in human-friendly output.
* ``save_callback(f)``: Call ``f`` when serializing (position after data fields).
* ``load_callback(f)``: Call ``f`` after deserializing all data fields (position after data fields).
Backwards and Third-party Compatibility
class FieldWithInvariant;
---------------------------------------
CAF evaluates common free function other than ``inspect`` in order to
class Field {
simplify users to integrate CAF into existing code bases.
public:
FieldWithFallback fallback(T);
Serializers and deserializers call user-defined ``serialize``
FieldWithInvariant invariant(function<bool>(T));
functions. Both types support ``operator&`` as well as
};
``operator()`` for individual data fields. A ``serialize``
function has priority over ``inspect``.
When converting a user-defined type to a string, CAF calls user-defined
class FieldsInspector {
``to_string`` functions and prefers those over ``inspect``.
public:
bool fields(...);
};
class Object : public FieldsInspector {
public:
FieldsInspector pretty_name(string_view);
};
The ``Inspector`` has the ``object`` member variable to set things in motion,
but it also serves as a factory for ``Field`` definitions. Fields always have a
name and users can either bind a reference to the member variable or provide
getter and setter functions. Optionally, users can equip fields with a fallback
value or an invariant predicate. Providing a fallback value automatically makes
fields optional. For example, consider the following class ``duration`` and its
implementation for ``inspect``:
.. code-block:: C++
struct duration {
string unit;
double count;
};
bool valid_time_unit(const string& unit) {
return unit == "seconds" || unit == "minutes";
}
template <class Inspector>
bool inspect(Inspector& f, duration& x) {
return f.object(x).fields(
f.field("unit", x.unit).fallback("seconds").invariant(valid_time_unit),
f.field("count", x.count));
}
Real code probably would not use a ``string`` to store the time unit. However,
with the fallback, we have enabled CAF to use ``"seconds"`` whenever the input
contains no value for the ``unit`` field. Further, the invariant makes sure that
we verify our input before accepting it.
Whether optional fields are supported depends on the format / inspector. For
example, the binary serialization protocol in CAF has no notion of optional
fields. Hence, the default binary serializers simply read and write fields in
the order they apear in. However, the inspectors for the configuration framework
do allow optional fields. With our ``inspect`` overload for ``duration``, we
could configure a parameter named ``example-app.request-timeout`` as follows:
.. code-block:: none
# example 1: ok, falls back to "seconds"
example-app {
request-timeout {
count = 1.3
}
}
# example 2: ok, explicit definition of the time unit
example-app {
request-timeout {
count = 1.3
unit = "minutes"
}
}
# example 3: error, "parsecs" is not a time unit (invariant does not hold)
example-app {
request-timeout {
count = 12
unit = "parsecs"
}
}
Inspector Traits
----------------
When writing custom ``inspect`` functions, providing a single overload for all
inspectors may result in undesired tradeoffs or convoluted code. For example,
inspection code may be much cleaner when split into a ``save`` and a ``load``
function. The kind of output format may also play a role. When reading and
writing human-readable data, we might want to improve the user experience by
using the constant names in enumeration types rather than using the underlying
integer values.
To enable static dispatching based on the inspector kind, all inspectors provide
the following ``static constexpr bool`` constants:
- ``is_loading``: If ``true``, tags the inspector as a deserializer that
overrides the state of an object. Otherwise, the inspector is *saving*, i.e.,
it visits the state of an object without modifying it.
- ``has_human_readable_format``: If ``true``, tags the inspector as reading and
writing a data format that may be consumed or generated by humans. Otherwise,
the ``inspect`` overload can assume a binary data format and optimize for
space rather than user experience. For example, an ``inspect`` overload for
enumeration types may use the constant names for a human-readable format but
otherwise use the underlying integer values.
Using theses constants, users can easily split ``inspect`` overloads into *load*
and *safe*:
.. code-block:: C++
template <class Inspector>
bool inspect(Inspector& f, my_class& x) {
if constexpr (Inspector:is_loading)
return load(f, x);
else
return save(f, x);
}
.. _unsafe-message-type:
.. _unsafe-message-type:
Whitelisting
Unsafe Message Types
Unsafe Message Types
--------------------
-------------
--------------------
Message types that
are not serializable cause compile time errors when used in
Message types that
do not provide serialization code cause compile time errors
actor communication. When using CAF for concurrency only, this errors can be
when used in actor communication. When using CAF for concurrency only, this
suppressed by whitelisting types with
errors can be suppressed by explicitly allowing types via
``CAF_ALLOW_UNSAFE_MESSAGE_TYPE``. The macro is defined as follows.
``CAF_ALLOW_UNSAFE_MESSAGE_TYPE``. The macro is defined as follows.
Splitting Save and Load Operations
.. code-block:: C++
----------------------------------
#define CAF_ALLOW_UNSAFE_MESSAGE_TYPE(type_name) \
If loading and storing cannot be implemented in a single function, users can
namespace caf { \
query whether the inspector is loading or storing. For example, consider the
template <> \
following class ``foo`` with getter and setter functions and no public
struct allowed_unsafe_message_type<type_name> : std::true_type {}; \
access to its members.
}
.. literalinclude:: /examples/custom_type/custom_types_3.cpp
Keep in mind that *unsafe* means that your program runs into undefined behavior
:language: C++
(or segfaults) when you break your promise and try to serialize messages that
:start-after: --(rst-foo-begin)--
contain unsafe message types.
:end-before: --(rst-foo-end)--
.. note::
Since there is no access to the data fields ``a_`` and ``b_``
(and assuming no changes to ``foo`` are possible), we need to split our
Even *unsafe* messages types still require a :ref:`type ID
implementation of ``inspect`` as shown below.
<custom-message-types>`.
.. literalinclude:: /examples/custom_type/custom_types_3.cpp
.. _custom-inspectors:
:language: C++
:start-after: --(rst-inspect-begin)--
Custom Inspectors (Serializers and Deserializers)
:end-before: --(rst-inspect-end)--
-------------------------------------------------
The purpose of the scope guard in the example above is to write the content of
Writing custom serializers and deserializers enables users to add support for
the temporaries back to ``foo`` at scope exit automatically. Storing
alternative wire formats such as `Google Protocol Buffers
the result of ``f(...)`` in a temporary first and then writing the
<https://developers.google.com/protocol-buffers>`_ or `MessagePack
changes to ``foo`` is not possible, because ``f(...)`` can
<https://msgpack.org/index.html>`_ as well as supporting non-binary formats such
return ``void``.
as `XML <https://www.w3.org/XML>`_ or `JSON
<https://www.json.org/json-en.html>`_.
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