Skip to content

Commit e2776ca

Browse files
committed
fable: Implement Optional schema for std::optional and boost::optional
BREAKING CHANGE: If you want to use boost::optional, you now need to include `fable/schema/boost_optional.hpp`.
1 parent 0c3e368 commit e2776ca

File tree

13 files changed

+262
-40
lines changed

13 files changed

+262
-40
lines changed

engine/src/stack.hpp

+7-5
Original file line numberDiff line numberDiff line change
@@ -30,17 +30,18 @@
3030
#include <utility> // for move
3131
#include <vector> // for vector<>
3232

33-
#include <boost/filesystem/path.hpp> // for path
34-
#include <boost/optional.hpp> // for optional<>
33+
#include <boost/filesystem/path.hpp> // for path
34+
#include <boost/optional.hpp> // for optional<>
35+
#include <fable/schema/boost_optional.hpp> // for Optional<>
36+
#include <fable/schema/custom.hpp> // for CustomDeserializer
37+
#include <fable/schema/factory.hpp> // for Factory
3538

3639
#include <cloe/component.hpp> // for ComponentFactory
3740
#include <cloe/controller.hpp> // for ControllerFactory
3841
#include <cloe/core.hpp> // for Conf, Confable, Json
3942
#include <cloe/simulator.hpp> // for SimulatorFactory
4043
#include <cloe/trigger.hpp> // for Source
4144
#include <cloe/utility/command.hpp> // for Command
42-
#include <fable/schema/custom.hpp> // for CustomDeserializer
43-
#include <fable/schema/factory.hpp> // for Factory
4445

4546
#include "plugin.hpp" // for Plugin
4647

@@ -49,7 +50,8 @@
4950
#endif
5051

5152
#ifndef CLOE_STACK_SUPPORTED_VERSIONS
52-
#define CLOE_STACK_SUPPORTED_VERSIONS {"4", "4.0", "4.1"}
53+
#define CLOE_STACK_SUPPORTED_VERSIONS \
54+
{ "4", "4.0", "4.1" }
5355
#endif
5456

5557
#ifndef CLOE_XDG_SUFFIX

fable/examples/contacts/src/main.cpp

+4-3
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,10 @@
6868
#include <CLI/CLI.hpp> // for CLI::App
6969
#include <boost/optional.hpp> // for boost::optional<>
7070

71-
#include <fable/confable.hpp> // for fable::{Confable, CONFABLE_SCHEMA}
72-
#include <fable/schema.hpp> // for fable::{Schema, String}
73-
#include <fable/utility.hpp> // for fable::{read_conf}
71+
#include <fable/confable.hpp> // for fable::{Confable, CONFABLE_SCHEMA}
72+
#include <fable/schema.hpp> // for fable::{Schema, String}
73+
#include <fable/schema/boost_optional.hpp> // for fable::{Optional, make_schema}
74+
#include <fable/utility.hpp> // for fable::{read_conf}
7475

7576
// All structs that are used directly with fable for serialization and
7677
// deserialization need to inherit from fable::Confable and override the

fable/include/fable/fable.hpp

+1
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,5 @@
3232
#include <fable/error.hpp>
3333
#include <fable/json.hpp>
3434
#include <fable/utility/memory.hpp>
35+
#include <fable/utility/optional.hpp>
3536
#include <fable/schema.hpp>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright 2023 Robert Bosch GmbH
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
* SPDX-License-Identifier: Apache-2.0
17+
*/
18+
/**
19+
* \file fable/schema/boost_optional.hpp
20+
* \see fable/schema/optional.cpp
21+
*/
22+
23+
#pragma once
24+
25+
#include <boost/optional/optional_fwd.hpp> // for optional<>
26+
27+
#include <fable/schema/optional.hpp> // for is_optional<>
28+
#include <fable/utility/boost_optional.hpp> // for adl_serializer<>
29+
30+
namespace fable {
31+
namespace schema {
32+
33+
template <typename X>
34+
struct is_optional<boost::optional<X>> : std::true_type {};
35+
36+
} // namespace schema
37+
} // namespace fable

fable/include/fable/schema/optional.hpp

+42-13
Original file line numberDiff line numberDiff line change
@@ -24,21 +24,49 @@
2424

2525
#pragma once
2626

27-
#include <string> // for string
28-
#include <utility> // for move
27+
#include <optional> // for optional<>
28+
#include <string> // for string
29+
#include <type_traits> // for is_same_v<>, enable_if<>
30+
#include <utility> // for move
2931

30-
#include <boost/optional.hpp> // for optional<>
31-
32-
#include <fable/schema/interface.hpp> // for Base<>, Box
33-
#include <fable/utility/boost_optional.hpp> // for to_json, from_json
32+
#include <fable/schema/interface.hpp> // for Base<>, Box
33+
#include <fable/utility/optional.hpp> // for adl_serializer<>
3434

3535
namespace fable {
3636
namespace schema {
3737

38+
/**
39+
* Helper type trait class to use with std::enable_if and friends.
40+
*
41+
* The value `is_optional<T>::value` is true if T is one of:
42+
* - std::optional
43+
* - boost::optional, if boost_optional.hpp is included
44+
*
45+
* \see fable/schema/boost_optional.hpp
46+
*/
47+
template <typename T>
48+
struct is_optional : std::false_type {};
49+
50+
template <typename X>
51+
struct is_optional<std::optional<X>> : std::true_type {};
52+
53+
/**
54+
* Optional de-/serializes a value that can be null.
55+
*
56+
* In a JSON object, a field that has the value null is not the
57+
* same thing as a field that is missing!
58+
*
59+
* Optional is a template class that supports both:
60+
* - std::optional
61+
* - boost::optional, if boost_optional.hpp is included
62+
*/
3863
template <typename T, typename P>
3964
class Optional : public Base<Optional<T, P>> {
65+
static_assert(is_optional<T>::value);
66+
4067
public: // Types and Constructors
41-
using Type = boost::optional<T>;
68+
using Type = T;
69+
using ValueType = typename Type::value_type;
4270
using PrototypeSchema = std::remove_cv_t<std::remove_reference_t<P>>;
4371

4472
Optional(Type* ptr, std::string desc);
@@ -49,8 +77,8 @@ class Optional : public Base<Optional<T, P>> {
4977

5078
#if 0
5179
// This is defined in: fable/schema/xmagic.hpp
52-
Optional(Type* ptr, std::string desc)
53-
: Optional(ptr, make_prototype<T>(), std::move(desc)) {}
80+
Optional(T* ptr, std::string desc)
81+
: Optional<T, P>(ptr, make_prototype<typename T::value_type>(), std::move(desc)) {}
5482
#endif
5583

5684
public: // Overrides
@@ -92,15 +120,15 @@ class Optional : public Base<Optional<T, P>> {
92120

93121
Json serialize(const Type& x) const {
94122
if (x) {
95-
return prototype_.serialize(x.get());
123+
return prototype_.serialize(x.value());
96124
} else {
97125
return nullptr;
98126
}
99127
}
100128

101129
Type deserialize(const Conf& c) const {
102130
if (c->type() == JsonType::null) {
103-
return boost::none;
131+
return Type{};
104132
}
105133
return prototype_.deserialize(c);
106134
}
@@ -112,8 +140,9 @@ class Optional : public Base<Optional<T, P>> {
112140
Type* ptr_{nullptr};
113141
};
114142

115-
template <typename T, typename P, typename S>
116-
Optional<T, P> make_schema(boost::optional<T>* ptr, P&& prototype, S&& desc) {
143+
// Define make_schema only for std::optional and boost::optional.
144+
template <typename T, typename P, typename S, std::enable_if_t<is_optional<T>::value, bool> = true>
145+
inline Optional<T, P> make_schema(T* ptr, P&& prototype, S&& desc) {
117146
return Optional<T, P>(ptr, std::forward<P>(prototype), std::forward<S>(desc));
118147
}
119148

fable/include/fable/schema/passthru.hpp

+2-5
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,8 @@
2626
#include <string> // for string
2727
#include <utility> // for move
2828

29-
#include <boost/optional.hpp> // for optional<>
30-
31-
#include <fable/schema/ignore.hpp> // for Ignore
32-
#include <fable/schema/interface.hpp> // for Base<>, Box
33-
#include <fable/utility/boost_optional.hpp> // for to_json, from_json
29+
#include <fable/schema/ignore.hpp> // for Ignore
30+
#include <fable/schema/interface.hpp> // for Base<>, Box
3431

3532
namespace fable {
3633
namespace schema {

fable/include/fable/schema/xmagic.hpp

+5-5
Original file line numberDiff line numberDiff line change
@@ -82,12 +82,12 @@ Map<T, decltype(make_prototype<T>())> make_schema(std::map<std::string, T>* ptr,
8282
}
8383

8484
template <typename T, typename P>
85-
Optional<T, P>::Optional(boost::optional<T>* ptr, std::string desc)
86-
: Optional<T, P>(ptr, make_prototype<T>(), std::move(desc)) {}
85+
Optional<T, P>::Optional(T* ptr, std::string desc)
86+
: Optional<T, P>(ptr, make_prototype<typename T::value_type>(), std::move(desc)) {}
8787

88-
template <typename T, typename S>
89-
Optional<T, decltype(make_prototype<T>())> make_schema(boost::optional<T>* ptr, S&& desc) {
90-
return Optional<T, decltype(make_prototype<T>())>(ptr, std::forward<S>(desc));
88+
template <typename T, typename S, std::enable_if_t<is_optional<T>::value, bool> = true>
89+
Optional<T, decltype(make_prototype<typename T::value_type>())> make_schema(T* ptr, S&& desc) {
90+
return Optional<T, decltype(make_prototype<typename T::value_type>())>(ptr, std::forward<S>(desc));
9191
}
9292

9393
template <typename T, typename S, std::enable_if_t<std::is_base_of_v<Confable, T>, int>>
+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Copyright 2023 Robert Bosch GmbH
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
* SPDX-License-Identifier: Apache-2.0
17+
*/
18+
/**
19+
* \file fable/utility/memory.hpp
20+
*
21+
* This file contains specializations of `nlohmann::adl_serializer` for std
22+
* types.
23+
*
24+
* In order to provide serialization for third-party types, we need to either
25+
* use their namespace or provide a specialization in that of nlohmann. It is
26+
* illegal to define anything in the std namespace, so we are left no choice in
27+
* this regard.
28+
*
29+
* See: https://github.com/nlohmann/json
30+
*/
31+
32+
#pragma once
33+
34+
#include <optional> // for optional<>
35+
36+
#include <nlohmann/json.hpp>
37+
38+
namespace nlohmann {
39+
40+
template <typename T>
41+
struct adl_serializer<std::optional<T>> {
42+
static void to_json(json& j, const std::optional<T>& opt) {
43+
if (opt) {
44+
j = *opt;
45+
} else {
46+
j = nullptr;
47+
}
48+
}
49+
50+
static void from_json(const json& j, std::optional<T>& opt) {
51+
if (j.type() == json::value_t::null) {
52+
opt.reset();
53+
} else {
54+
*opt = j.get<T>();
55+
}
56+
}
57+
};
58+
59+
} // namespace nlohmann

fable/src/fable/schema/enum_test.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ FABLE_ENUM_SERIALIZATION(logger::LogLevel, ({
4545
using LogLevel = logger::LogLevel;
4646

4747
struct LoggerStruct : public fable::Confable {
48-
boost::optional<LogLevel> level;
48+
std::optional<LogLevel> level;
4949

5050
CONFABLE_SCHEMA(LoggerStruct) {
5151
using namespace fable::schema; // NOLINT(build/namespaces)

0 commit comments

Comments
 (0)