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
Show 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
ipv6_address
ipv6_endpoint
ipv6_subnet
load_inspector
local_group
logger
mailbox_element
...
...
@@ -302,6 +303,7 @@ caf_add_test_suites(caf-core-test
policy.select_any
request_timeout
result
save_inspector
selective_streaming
serial_reply
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.
.. literalinclude:: /examples/caf-application.conf
:language: none
.. _
add-custom-message-type
:
.. _
custom-message-types
:
Adding Custom Message Types
---------------------------
...
...
@@ -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
``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
add-custom-message-type
_).
custom-message-types
_).
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
...
...
manual/TypeInspection.rst
View file @
f4ecc9b4
.. _type-inspection:
Type Inspection (Serialization and String Conversion)
=====================================================
CAF is designed with distributed systems in mind. Hence, all message types must
be serializable and need a platform-neutral, unique name that is configured at
startup (see :ref:`add-custom-message-type`). Using a message type that is not
serializable causes a compiler error (see :ref:`unsafe-message-type`). CAF
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
with a single free function. The signature for a class ``my_class`` is always as
follows:
Type Inspection
===============
We designed CAF with distributed systems in mind. Hence, all message types must
be serializable. Using a message type that is not serializable causes a compiler
error unless explicitly listed as unsafe message type by the user (see
:ref:`unsafe-message-type`).
The inspection API allows CAF to deconstruct C++ objects into fields and values.
Users can either provide free functions named ``inspect`` that CAF picks up via
`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++
struct point_3d {
int x;
int y;
int z;
};
To allow CAF to properly serialize and deserialize the POD type, the simplest
way is to write a free ``inspect`` function:
.. code-block:: C++
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));
}
After providing this function as well as listing ``point_3d`` in a type ID
block, CAF can save and load our custom POD type. Of course, this is a recursive
process that allows us to use ``point_3d`` as member variables of other types.
Working with 3rd party libraries usually rules out adding free functions for
existing classes, because the namespace belongs to another project. Hence, CAF
also allows specializing ``inspector_access`` instead. This requires slightly
more boilerplate code, but follows the same pattern:
.. code-block:: C++
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));
}
};
}
After listing ``point_3d`` in a type ID block and either providing a free
``inspect`` overload or specializing ``inspector_access``, CAF is able to:
- 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++
class foobar {
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:
.. code-block:: C++
template <class Inspector>
typename Inspector::result_type inspect(Inspector& f, my_class& x) {
return f(...);
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));
}
The function ``inspect`` passes meta information and data fields to the
variadic call operator of the inspector. The following example illustrates an
implementation for ``inspect`` for a simple POD struct.
.. note::
.. literalinclude:: /examples/custom_type/custom_types_1.cpp
:language: C++
:lines: 23-33
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.
The inspector recursively inspects all data fields and has builtin support for
(1) ``std::tuple``, (2) ``std::pair``, (3) C arrays, (4) any
container type with ``x.size()``, ``x.empty()``,
``x.begin()`` and ``x.end()``.
Inspector DSL
-------------
We consciously made the inspect API as generic as possible to allow for
extensibility. This allows users to use CAF's types in other contexts, to
implement parsers, etc.
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.
Inspector Concept
-----------------
The following pseudo code illustrates how the inspector DSL is structured in
terms of types involved and member function interfaces.
The following concept class shows the requirements for inspectors. The
placeholder ``T`` represents any user-defined type. For example,
``error`` when performing I/O operations or some integer type when
implementing a hash function.
.. code-block:: C++
Inspector {
using result_type = T;
class Inspector {
public:
Object object(T obj);
Field field(string_view name, T& ref);
Field field(string_view name, function<T()> get, function<void(T)> set);
};
class FieldWithFallbackAndInvariant;
class FieldWithFallback {
public:
FieldWithFallbackAndInvariant invariant(function<bool>(T));
};
class FieldWithInvariant;
class Field {
public:
FieldWithFallback fallback(T);
static constexpr bool reads_state = ...;
FieldWithInvariant invariant(function<bool>(T));
};
static constexpr bool writes_state = ...;
class FieldsInspector {
public:
bool fields(...);
};
template <class... Ts>
result_type operator()(Ts&&...);
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";
}
A saving ``Inspector`` is required to handle constant lvalue and rvalue
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.
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));
}
Annotations
-----------
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.
Annotations allow users to fine-tune the behavior of inspectors by providing
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:
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:
* ``type_name(n)``: Display type name as ``n`` in human-friendly output (position before data fields).
* ``hex_formatted()``: Format the following data field in hex format.
* ``omittable()``: Omit the following data field in human-friendly output.
* ``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).
.. code-block:: none
Backwards and Third-party Compatibility
---------------------------------------
# example 1: ok, falls back to "seconds"
example-app {
request-timeout {
count = 1.3
}
}
CAF evaluates common free function other than ``inspect`` in order to
simplify users to integrate CAF into existing code bases.
# 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"
}
}
Serializers and deserializers call user-defined ``serialize``
functions. Both types support ``operator&`` as well as
``operator()`` for individual data fields. A ``serialize``
function has priority over ``inspect``.
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*:
When converting a user-defined type to a string, CAF calls user-defined
``to_string`` functions and prefers those over ``inspect``.
.. 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:
Whitelisting
Unsafe Message Types
--------------------
-------------
Unsafe Message Types
--------------------
Message types that
are not serializable cause compile time errors when used in
actor communication. When using CAF for concurrency only, this errors can be
suppressed by whitelisting types with
Message types that
do not provide serialization code cause compile time errors
when used in actor communication. When using CAF for concurrency only, this
errors can be suppressed by explicitly allowing types via
``CAF_ALLOW_UNSAFE_MESSAGE_TYPE``. The macro is defined as follows.
Splitting Save and Load Operations
----------------------------------
If loading and storing cannot be implemented in a single function, users can
query whether the inspector is loading or storing. For example, consider the
following class ``foo`` with getter and setter functions and no public
access to its members.
.. literalinclude:: /examples/custom_type/custom_types_3.cpp
:language: C++
:start-after: --(rst-foo-begin)--
:end-before: --(rst-foo-end)--
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
implementation of ``inspect`` as shown below.
.. literalinclude:: /examples/custom_type/custom_types_3.cpp
:language: C++
:start-after: --(rst-inspect-begin)--
:end-before: --(rst-inspect-end)--
The purpose of the scope guard in the example above is to write the content of
the temporaries back to ``foo`` at scope exit automatically. Storing
the result of ``f(...)`` in a temporary first and then writing the
changes to ``foo`` is not possible, because ``f(...)`` can
return ``void``.
.. code-block:: C++
#define CAF_ALLOW_UNSAFE_MESSAGE_TYPE(type_name) \
namespace caf { \
template <> \
struct allowed_unsafe_message_type<type_name> : std::true_type {}; \
}
Keep in mind that *unsafe* means that your program runs into undefined behavior
(or segfaults) when you break your promise and try to serialize messages that
contain unsafe message types.
.. note::
Even *unsafe* messages types still require a :ref:`type ID
<custom-message-types>`.
.. _custom-inspectors:
Custom Inspectors (Serializers and Deserializers)
-------------------------------------------------
Writing custom serializers and deserializers enables users to add support for
alternative wire formats such as `Google Protocol Buffers
<https://developers.google.com/protocol-buffers>`_ or `MessagePack
<https://msgpack.org/index.html>`_ as well as supporting non-binary formats such
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