Flow 1.0.2
Flow project: Full implementation reference.
cfg_fwd.hpp
Go to the documentation of this file.
1/* Flow
2 * Copyright 2023 Akamai Technologies, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the
5 * "License"); you may not use this file except in
6 * compliance with the License. You may obtain a copy
7 * of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in
12 * writing, software distributed under the License is
13 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
14 * CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing
16 * permissions and limitations under the License. */
17
18/// @file
19#pragma once
20
22#include <boost/program_options.hpp>
23#include <boost/filesystem.hpp>
24#include <boost/any.hpp>
25#include <vector>
26#include <string>
27#include <ostream>
28
29/**
30 * Flow module that facilitates configuring modules, such as applications and APIs, via statically and/or dynamically
31 * parsed sets of name/value pairs from config sources like files and command lines. (It is also possible to use a
32 * subset of the provided features to simplify option-related tasks even without parsing them from a file/etc.)
33 *
34 * ### Main concepts: your `Value_set`s and flow::cfg::Option_set ###
35 * "Configuration" has lots of possible meanings, and flow::cfg certainly doesn't cover any huge range of what
36 * could be called configuration. Instead it is focused on a fairly common basic object type: a straightforward
37 * `struct` of essentially scalar values. (Composition/nested `struct`s are also supported, but don't worry about it
38 * yet.) Such `struct`s are often passed around larger programs and used to configure various modules.
39 * It is typically a given that each type V for a data member in such a `struct`:
40 * - is copyable in a resonable way;
41 * - is comparable via `==` in a reasonable way.
42 *
43 * It is also reasonable that each member has some default value, set in the `struct` no-args ctor, so that
44 * the full set of values in a default-cted instance of the `struct` represents a reasonable overall config for the
45 * corresponding module.
46 *
47 * It is also often desirable to print dumps of the current contents of such a `struct`. In that case we add
48 * the requirement on each type V:
49 * - it has a standard `ostream` output `<<` operator.
50 *
51 * Often (though not always) one needs to *parse* into the member of this `struct`, from a stream/file
52 * or perhaps command line (one or more *config sources*). If this is indeed required then add the requirement on
53 * each V:
54 * - it has a standard `istream` input `>>` operator.
55 *
56 * Sometimes such a `struct` is essentially *static*, meaning once the values are filled out (manually, or from
57 * a config source), they shouldn't change. More rarely they are *dynamic*, meaning
58 * values can change from time to time, particularly on outside events like a SIGHUP triggering reading
59 * values from a file. In the latter case it is typical to wrap them in `shared_ptr`s; then a piece of code can
60 * save a copy of such a handle to an immutable instance of the `struct`, wherein it can rely on a consistent
61 * set of config, even while dynamic config updates will cause more such instances to be generated over time.
62 * Hence add this requirement on the `struct` itself:
63 * - it should derive from util::Shared_ptr_alias_holder, so as to gain `Ptr` and `Const_ptr` aliases
64 * (to `shared_ptr<>`).
65 *
66 * Call the `struct` type satisfying the above requirements `Value_set`. All facilities dealing with such value sets
67 * are templates parameterized on `Value_set`. Generally speaking it is straightforward to satisfy these
68 * requirements; for example they hold for ~all built-in scalar types, plus `chrono::duration`, `std::string`,
69 * and many more oft-used types; and deriving from `Shared_ptr_alias_holder` is just a line or two.
70 *
71 * Maintaining a `Value_set` like that is straighforward, but the things one wants to do with with an *entire*
72 * `Value_set` -- as opposed to individual members in it -- tend to be laborious, anti-consistent, and error-prone
73 * to do. To wit:
74 * - stream output (tediously list members along with laborious and inconsistent `ostream<<` logic);
75 * - parsing (it's just hard -- and even with boost.program_options there's a ton of boiler-plate to write and
76 * tons of degrees of freedom);
77 * - documenting the available settings/meanings/semantics to the user (help message);
78 * - comparison `==` (tediously list `==` checks for each member);
79 * - validation/checking for valid values (tedious `if` statements and even more tedious stream output.
80 *
81 * Just about the only thing that *is* easy is simply accessing the values in a `Value_set`. Indeed, this is the one
82 * "feature" built-into `Value_set` that we want to leave available at all costs; it is concise, compile-time-checked,
83 * and fast (all of which is less true or untrue of, say, a `map<string, boost::any>` and similar solutions).
84 *
85 * The main value of flow::cfg, as of this writing, is the Option_set class template. `Option_set<Value_set>`
86 * adds concise ways of doing all of the above in a streamlined way. Even if you need not actually parse your
87 * `Value_set` from a file, it still lets you concisely output, compare, validate the `Value_set`s themselves.
88 *
89 * Therefore see Option_set doc header. Do realize that it is expected to be common-place to have multiple
90 * `Value_set` types -- and therefore separate `Option_set<>` instances for each. A server program, for example,
91 * might start with just a `Static_value_set` for initial, immutable config; and later add a `Dynamic_value_set`
92 * for settings that can change since startup. Flow itself has flow::net_flow, wherein (e.g.)
93 * net_flow::Peer_socket has a set of socket options, some static and some dynamically changeable once a connection
94 * is open; hence it might have a `struct` for dynamic socket options and another for static.
95 *
96 * ### Config_manager ###
97 * An Option_set is designed to be used in flexible combination with other `Option_set`s. The variations on how
98 * one might use them are equally as unpredictable and varied as how one might use `Value_set`-type `struct`s.
99 *
100 * Config_manager is an optional feature built on a couple of `Option_set`s, one for dynamic and one for static
101 * config. It exists to provide a simple but often-sufficient config manager for use by a server process. Create
102 * one of these, fill out your process's overall static and dynamic config `struct`s, and you should be able to
103 * have a working config system with minimal boiler-plate, based on a static config file and command line; and
104 * a separate dynamic config file (with the optional ability to set initial dynamic setting values from the
105 * static config sources).
106 *
107 * If Config_manager is insufficient, one can build their own system on top of Option_set.
108 *
109 * @see Option_set class template, the main feature of flow::cfg.
110 */
111namespace flow::cfg
112{
113// Types.
114
115// Find doc headers near the bodies of these compound types.
116
117template<typename Root, typename Target,
118 typename Target_ptr
119 = typename std::pointer_traits<typename Root::Ptr>::template rebind<Target const>>
121
122class Option_set_base;
123
124template<typename Value_set>
125class Option_set;
126
127
128/* (The @namespace and @brief thingies shouldn't be needed, but some Doxygen bug necessitated them.
129 * See flow::util::bind_ns for explanation... same thing here.) */
130
131/**
132 * @namespace flow::cfg::fs
133 * @brief Short-hand for `namespace boost::filesystem`.
134 */
135namespace fs = boost::filesystem;
136
137/**
138 * @namespace flow::cfg::opts
139 * @brief Short-hand for `namespace boost::program_options`.
140 */
141namespace opts = boost::program_options;
142
143// Free functions.
144
145/**
146 * Serializes (briefly) an Option_set to a standard output stream.
147 *
148 * @relatesalso Option_set
149 *
150 * @tparam Value_set
151 * See Option_set doc header.
152 * @param os
153 * Stream to which to serialize.
154 * @param val
155 * Value to serialize.
156 * @return `os`.
157 */
158template<typename Value_set>
159std::ostream& operator<<(std::ostream& os, const Option_set<Value_set>& val);
160
161/**
162 * Utility, used by FLOW_CFG_OPTION_SET_DECLARE_OPTION() internally but made available as a public API
163 * in case it is useful, that converts a string containing a conventionally formatted data member name into the
164 * corresponding auto-determined config option name.
165 *
166 * @note An example for convenience, accurate as of the time of this writing:
167 * `m_cool_object.m_cool_sub_object->m_badass_sub_guy.m_cool_option_name` transforms to
168 * `cool-object.cool-sub-object.badass-sub-guy.cool-option-name`.
169 *
170 * The format for the contents of `member_id` shall be as follows: It shall consist of one or more
171 * identifiers following the Flow coding guide, each starting with `m_`, concatenated with C++ object separator
172 * sequences, each sequence chosen to be either `.` (object dereference) or `->` (pointer dereference). `m_` for
173 * each identifier is optional for this function -- though the coding guide requires it as of this writing anyway.
174 *
175 * Note that boost.program_options allows config files (when used as config sources, e.g,
176 * Option_set::parse_config_file()) to declare a `[config-section]` which results in each `option-name` listed
177 * in that section to be treated as-if named `config-section.option-name`. This has synergy with nested objects
178 * within a config value set being separated by dots also (or `->` if desired for orthogonal reasons, such as
179 * if a smart pointer is used).
180 *
181 * @param member_id
182 * Identifier, perhaps obtained via the preprocessor `#feature` from an argument to a functional macro.
183 * @return See above.
184 */
186
187/**
188 * Similar to value_set_member_id_to_opt_name() but used by FLOW_CFG_OPTION_SET_DECLARE_OPTION_KEYED() internally
189 * (also made available as a public API in case it is useful), that does the job of
190 * value_set_member_id_to_opt_name() in addition to substituting the last `[...]` fragment with
191 * a dot separator, followed by the `ostream` encoding of `key`.
192 *
193 * @note An example for convenience, accurate as of the time of this writing:
194 * `m_cool_object.m_cool_sub_object->m_badass_sub_guy[cool_key].m_cool_option_name`, with `cool_key == 3`,
195 * transforms to `cool-object.cool-sub-object.badass-sub-guy.3.cool-option-name`.
196 *
197 * Behavior is undefined if the `[...]` part doesn't exist or is preceded or succeeded by nothing.
198 * In reality for things to work as expected that part should also be followed by a C++ object separator
199 * as in value_set_member_id_to_opt_name() doc header; so, e.g., `m_blah[idx]->m_blah` or `m_blah[idx].m_blah`.
200 *
201 * @tparam Key
202 * An `ostream<<`able type. Common: `size_t` and `std::string`.
203 * @param member_id
204 * Identifier, perhaps obtained via the preprocessor `#` feature from an argument to a functional macro.
205 * @param key
206 * The value to splice in when replacing the key fragment inside `[]` (after the inserted `.`).
207 * @return See above.
208 */
209template<typename Key>
210std::string value_set_member_id_to_opt_name_keyed(util::String_view member_id, const Key& key);
211
212/**
213 * Serializes a value of type `Value` to the given `ostream` suitably for output in Option_set-related output to user
214 * such as in help messages. The generic version simply forwards to `ostream<<` operator; but specializations/overloads
215 * can massage/customize the output more suitably for usability. The user may provide their own specializations or
216 * overload on top of any already provided.
217 *
218 * @see E.g.: the `Value = chrono::duration` overload.
219 * @tparam Value
220 * Type of `val`. For this generic implementation `ostream << Value` operator must exist.
221 * @param os
222 * Stream to which to serialize.
223 * @param val
224 * Value to serialize.
225 */
226template<typename Value>
227void value_to_ostream(std::ostream& os, const Value& val);
228
229/**
230 * Overload that serializes a value of `chrono`-based `duration` including `Fine_duration` --
231 * which is recommended to use for Option_set-configured time durations -- to the given `ostream` suitably for output
232 * in Option_set-related output to user such as in help messages. As of this writing it improves upon the default
233 * `ostream<<` behavior by converting to coarser units without losing precision (e.g., not `"9000000000 ns"` but
234 * `"9 s"`).
235 *
236 * @tparam Rep
237 * See `chrono::duration`.
238 * @tparam Period
239 * See `chrono::duration`.
240 * @param os
241 * Stream to which to serialize.
242 * @param val
243 * Value to serialize.
244 */
245template<typename Rep, typename Period>
246void value_to_ostream(std::ostream& os, const boost::chrono::duration<Rep, Period>& val);
247
248/**
249 * Overload that serializes a list value (with `Element` type itself similarly serializable)
250 * to the given `ostream` suitably for output in Option_set-related output to user such as in help messages.
251 *
252 * @tparam Element
253 * Any type for which `value_to_ostream(Element_type)` is available; but as of this writing
254 * it cannot itself be `std::vector<>` (no lists of lists).
255 * @param os
256 * Stream to which to serialize.
257 * @param val
258 * Value to serialize.
259 */
260template<typename Element>
261void value_to_ostream(std::ostream& os, const std::vector<Element>& val);
262
263} // namespace flow::cfg
264
265/// We may add some ADL-based overloads into this namespace outside `flow`.
267{
268// Free functions.
269
270/**
271 * ADL-based overload of boost.program_options `validate()` to allow for empty `boost::filesystem::path` values
272 * in flow::cfg config parsing as well as any other boost.program_options parsing in the application.
273 * Likely there's no need to call this directly: it is invoked by boost.program_options when parsing `path`s.
274 *
275 * More precisely: This has ~two effects:
276 * - flow::cfg::Option_set (and flow::cfg::Config_manager and anything else built on `Option_set`) will
277 * successfully parse an empty (or all-spaces) value for a setting of type `boost::filesystem::path`, resulting
278 * in a `path` equal to a default-constructed `path()`.
279 * - You may still disallow it via a validator expression in FLOW_CFG_OPTION_SET_DECLARE_OPTION() as usual.
280 * (Sometimes an empty "path" value is useful as a special or sentinel value.) Such flow::cfg-user-specified
281 * individual-option-validator checking, internally, occurs after (and only if) the lower-level parsing already
282 * succeeded.
283 * - Any other use (even outside of flow::cfg) of boost.program_options to parse a `path` will now also allow
284 * an empty value (on command line, in file, etc.) in the same application.
285 *
286 * ### Rationale ###
287 * Allowing empty `path`s in flow::cfg is required for usability. It was a common use case to allow for a blank special
288 * value for path settings. However trying to in fact provide an empty value in a flow::cfg file (e.g., simply
289 * `log-file=`) resulted in an "Invalid argument" error message and refusal of
290 * flow::cfg::Option_set::parse_config_file() to successfully parse.
291 *
292 * (The following discussion is about implementation details and would normally be omitted from this public-facing API
293 * doc header. However, in this slightly unusual (for Flow) situation the solution happens to subtly affect
294 * code outside of flow::cfg. Therefore it is appropriate to discuss these internals here.)
295 *
296 * The reason for this problem: flow::cfg uses boost.program_options for config source (especially file) parsing.
297 * By default, when parsing a string into the proper type `T` (here `T` being `path`),
298 * boost.program_options uses `istream >> T` overload. Turns out that reading a blank from a stream into `path` causes
299 * the `istream` bad-bit to set, meaning `lexical_cast` throws `bad_lexical_cast`; boost.program_options then manifests
300 * this as an arg parse error. (If `T` were `std::string`, by contrast, no such problem would occur:
301 * `istream >> string` will happily accept a blank string.)
302 *
303 * boost.program_options clearly suggests the proper way to custom-parse types at
304 * https://www.boost.org/doc/libs/1_78_0/doc/html/program_options/howto.html#id-1.3.32.6.7 --
305 * namely, define a `validate()` overload in the same namespace as type `T`, with a `T`-typed arg.
306 * ADL (argument-dependent lookup) will then use that overload -- instead of the default one, which simply invokes
307 * `lexical_cast` (i.e., `istream>>`) -- and reports an error if that throws. This is the same technique used
308 * to conveniently select `hash_value()` for `unordered_*`; `swap()` for STL containers; etc.
309 *
310 * This solves the problem. However, somewhat unusually, this imposes the same more-permissive semantics on
311 * *all* other uses of boost.program_options in any application linking Flow (which includes this overload).
312 * This is arguably not-great; ideally we'd affect flow::cfg and nothing else. That said (1) there's no apparent
313 * alternative in boost.program_options; and more to the point (2) this feels like either an improvement or
314 * neutral. If an empty path must be disallowed, this can be done via `notifier()` (or just a manual check).
315 * So, all in all, this seemed fine.
316 *
317 * @param target
318 * `target` shall be loaded with the `path` on success. Otherwise we shall throw, per required
319 * boost.program_options semantics.
320 * @param user_values
321 * The (trimmed) raw strings from the user for this occurrence of the setting.
322 * In our case there must be only one, since a `path` has only one value.
323 */
324void validate(boost::any& target, const std::vector<std::string>& user_values, path*, int);
325
326} // namespace boost::filesystem
Class which facilitates managing access to a dynamic configuration.
Un-templated base for Option_set.
Definition: option_set.hpp:34
The core config-parsing facility, which builds parsing/comparison/output capabilities on top of a giv...
Definition: option_set.hpp:422
We may add some ADL-based overloads into this namespace outside flow.
Definition: cfg_fwd.hpp:267
void validate(boost::any &target, const std::vector< std::string > &user_values, path *, int)
ADL-based overload of boost.program_options validate() to allow for empty boost::filesystem::path val...
Definition: option_set.cpp:106
Flow module that facilitates configuring modules, such as applications and APIs, via statically and/o...
Definition: cfg_fwd.hpp:112
void value_to_ostream(std::ostream &os, const Value &val)
Serializes a value of type Value to the given ostream suitably for output in Option_set-related outpu...
std::string value_set_member_id_to_opt_name(util::String_view member_id)
Utility, used by FLOW_CFG_OPTION_SET_DECLARE_OPTION() internally but made available as a public API i...
Definition: option_set.cpp:38
std::string value_set_member_id_to_opt_name_keyed(util::String_view member_id, const Key &key)
Similar to value_set_member_id_to_opt_name() but used by FLOW_CFG_OPTION_SET_DECLARE_OPTION_KEYED() i...
std::ostream & operator<<(std::ostream &os, const Option_set< Value_set > &val)
Serializes (briefly) an Option_set to a standard output stream.
Basic_string_view< char > String_view
Commonly used char-based Basic_string_view. See its doc header.