Flow 1.0.2
Flow project: Full implementation reference.
static_cfg_manager.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
23
24namespace flow::cfg
25{
26
27// Types.
28
29/**
30 * A `Config_manager`-related adapter-style class that manages a simple config setup involving a single
31 * (though arbitrarily complex) `Option_set<>`-ready raw value `struct` config store type `Value_set`, meant to
32 * be used only in static fashion. That is to say, the parsed config values are not meant to be accessed
33 * *while* the config is being read from file.
34 *
35 * If you desire dynamic config (which can be read from file(s) at any time), and/or you need to manage more than
36 * one config `struct` (e.g., you're controlling 2+ entirely separate modules), then please use
37 * Config_manager which supports all that. (You can also develop your own handling of `Option_set<Value_set>`
38 * instead. See Config_manager doc header.)
39 *
40 * The life cycle and usage are simple. Define your `Value_set` (see Option_set doc header for formal requirements,
41 * but basically you'll need a `struct`, an option-defining function using FLOW_CFG_OPTION_SET_DECLARE_OPTION(),
42 * and possibly an inter-option validator function). Construct the `Static_config_manager<Value_set>`.
43 * Call apply() to read a file. (You can do this more than once, potentially for different files. As of this writing
44 * only files are supported, but adding command line parsing would be an incremental change.) Then,
45 * call values() to get the reference to the immutable parsed `Value_set`. This reference can be passed around the
46 * application; values() will always return the same reference. Technically you could call apply() even after
47 * using values(); but it is not thread-safe to do so while accessing config values which would change concurrently
48 * with no protection.
49 *
50 * Also you may create a `const` (immutable) Static_config_manager via its constructor and then just use it to output
51 * a help message (log_help() or help_to_ostream()). This could be used with your program's `--help` option or similar,
52 * and that's it (no parsing takes place).
53 *
54 * ~~~
55 * // Example of a Static_config_manager created just to log the help, and that's it.
56 * flow::cfg::Static_config_manager<Static_value_set>
57 * (get_logger(), "cfg", &static_cfg_declare_opts)
58 * .log_help();
59 * ~~~
60 *
61 * @note Config_manager provides the optional `commit == false` mode in apply_static(); this enables the
62 * "multi-source parsing and source skipping" described in its doc header. Static_config_manager, to keep
63 * its mission simple, cuts off access to this feature, meaning implicitly it always takes
64 * `commit` to equal `true`. That is, it is expected you'll be loading static config from exactly
65 * one file/one apply() call per attempt. Your Final_validator_func::Type `final_validator_func()`
66 * *should* return either Final_validator_outcome::S_ACCEPT or Final_validator_outcome::S_FAIL. It *can* return
67 * Final_validator_outcome::S_SKIP, but that would mean apply() will return `true` (success)
68 * but simply no-op (not update the canonical config as returned by values()).
69 *
70 * ### Rationale ###
71 * Config_manager is in the author's opinion not difficult to use, but it *does* use template parameter packs
72 * (`typename... Value_set`), and its API can be somewhat difficult to grok when all you have is the aforementioned
73 * simple use case.
74 *
75 * @tparam Value_set
76 * The settings `struct` -- see Option_set doc header for requirements.
77 */
78template<typename Value_set>
80 private Config_manager<Value_set, Null_value_set>
81{
82public:
83 // Types.
84
85 /**
86 * Tag type: indicates an apply() method must *allow* invalid defaults and only complain if the config source
87 * does not explicitly supply a valid value. Otherwise the defaults themselves are also stringently checked
88 * regardless of whether they are overridden. This setting applies only to individual-option-validators.
89 * Final_validator_func validation is orthogonal to this.
90 */
92 {
93 S_ALLOW_INVALID_DEFAULTS ///< Sole value for tag type #allow_invalid_defaults_tag_t.
94 };
95
96 /**
97 * The class we `private`ly subclass (in HAS-A fashion, not IS-A fashion). It is `public` basically so that we can
98 * refer to it in various forwarding `using` method directives below.
99 */
101
102 // Constructors/destructor.
103
104 /**
105 * Constructs a Static_config_manager ready to read static config via apply() and access it via values().
106 *
107 * ### Logging assumption ###
108 * `*logger_ptr` is a standard logging arg. Note, though, that the class will assume that log verbosity may not have
109 * yet been configured -- since this Static_config_manager may be the thing configuring it. Informal recommendations:
110 * - You should let through INFO and WARNING messages in `*logger_ptr`.
111 * - If you plan to use `*this` only for log_help() (such as in your `--help` implementation), you should
112 * *not* let through TRACE-or-more-verbose.
113 * - Once (and if) you engage any actual parsing (apply()), TRACE may be helpful
114 * in debugging as usual.
115 *
116 * @param logger_ptr
117 * Logger to use for subsequently logging.
118 * @param nickname
119 * Brief string used for logging subsequently.
120 * @param declare_opts_func_moved
121 * The declare-options callback as required by
122 * `Option_set<Value_set>` constructor; see its doc header for instructions.
123 */
124 explicit Static_config_manager(log::Logger* logger_ptr, util::String_view nickname,
125 typename Option_set<Value_set>::Declare_options_func&& declare_opts_func_moved);
126
127 // Methods.
128
129 /**
130 * Invoke this after construction to load the permanent set of static config from config sources including
131 * a static config file. See also apply() overload with #allow_invalid_defaults_tag_t tag.
132 *
133 * After this runs and succeeds, you may use values() to access the loaded values (but see notes on
134 * `final_validator_func` arg and return value, regarding Final_validator_outcome::S_SKIP w/r/t
135 * `final_validator_func` arg).
136 *
137 * On failure returns `false`; else returns `true`. In the former case the overall state is equal to that
138 * at entry to the method. Tip: On failure you may want to exit program with error; or you
139 * can continue knowing that values() will return default values according to `Value_set()` no-arg ctor.
140 * WARNING(s) logged given failure.
141 *
142 * Before apply(), or after it fails, the contents of what values() returns will be the defaults from your `Value_set`
143 * structure in its constructed state. This may or may not have utility depending on your application.
144 *
145 * apply() will *not* be tolerant of unknown option names appearing in the config source.
146 *
147 * @note `final_validator_func()` can be made quite brief by using convenience macro
148 * FLOW_CFG_OPT_CHECK_ASSERT(). This will take care of most logging in most cases.
149 *
150 * @todo Add support for command line as a config source in addition to file(s), for static config in
151 * cfg::Config_manager.
152 *
153 * @param cfg_path
154 * File to read.
155 * @param final_validator_func
156 * If parsing and individual-option-validation succeed, the method shall return success if
157 * `final_validator_func(V)` returns Final_validator_outcome::S_ACCEPT or Final_validator_outcome::S_SKIP,
158 * where V is the parsed `Value_set`; and in the former case values() post-this-method will return V.
159 * Informally: Please place individual-option validation
160 * into FLOW_CFG_OPTION_SET_DECLARE_OPTION() invocations; only use `final_validator_func()` for
161 * internal consistency checks (if any).
162 * Informally: It is unlikely, with Static_config_manager, that it should return SKIP; that feature
163 * is only useful with the multi-file-update feature which is not accessible through
164 * Static_config_manager. See the note about this in our class doc header.
165 * @return `true` if and only if successfully parsed config source and validated all settings including
166 * `final_validator_func() != S_FAIL`; and defaults were also all individually
167 * valid. If `true`, and `final_validator_func() == S_ACCEPT`, then values() shall return the
168 * parsed `Value_set`. If `true`, but `final_validator_func() == S_SKIP`, then values() shall return
169 * the default-cted `Value_set`
170 */
171 bool apply(const fs::path& cfg_path,
172 const typename Final_validator_func<Value_set>::Type& final_validator_func);
173
174 /**
175 * Identical to similar apply() overload without #allow_invalid_defaults_tag_t tag; but skips the stringent
176 * check on individual defaults' validity.
177 *
178 * @see #allow_invalid_defaults_tag_t doc header and/or return-value doc just below.
179 *
180 * @param cfg_path
181 * See other apply().
182 * @param final_validator_func
183 * See other apply().
184 * @return See other apply(). However the latter will return `false` if a default is invalid, even if
185 * file `cfg_path` explicitly sets it to a valid value. This tagged overload will not.
186 */
188 const fs::path& cfg_path,
189 const typename Final_validator_func<Value_set>::Type& final_validator_func);
190
191 /**
192 * Equivalent to the other apply() but with no inter-option validation (meaning the per-option validation
193 * passed to constructor is sufficient). See also apply() overload with #allow_invalid_defaults_tag_t tag.
194 *
195 * @param cfg_path
196 * File to read.
197 * @return `true` if and only if successfully parsed config source(s) and validated all settings;
198 * and defaults were also all individually valid.
199 */
200 bool apply(const fs::path& cfg_path);
201
202 /**
203 * Identical to similar apply() overload without #allow_invalid_defaults_tag_t tag; but skips the stringent
204 * check on individual defaults' validity.
205 *
206 * @see #allow_invalid_defaults_tag_t doc header and/or return-value doc just below.
207 *
208 * @param cfg_path
209 * See other apply().
210 * @return See other apply(). However the latter will return `false` if a default is invalid, even if
211 * file `cfg_path` explicitly sets it to a valid value. This tagged overload will not.
212 */
214 const fs::path& cfg_path);
215
216 /**
217 * Returns (always the same) reference to the managed `Value_set` structure. Before successful apply() these
218 * values will be at their defaults. Tip: It should be sufficient to pass around only the `const`
219 * ref obtained here all around the app -- no `Value_set` copying should be needed.
220 *
221 * @return See above. To reiterate: always the same reference is returned.
222 */
223 const Value_set& values() const;
224
225 /**
226 * Prints a human-targeted long-form summary of our contents, doubling as a usage message and a dump of current
227 * values where applicable. This is not thread-safe against several non-`const`- methods.
228 *
229 * @param os
230 * Stream to which to serialize.
231 */
233
234 /**
235 * Logs what state_to_ostream() would print. This is not thread-safe against several non-`const`- methods.
236 *
237 * @param sev
238 * Severity to use for the log message(s).
239 */
240 using Impl::log_state;
241
242 /**
243 * Prints a human-targeted long-form usage message that includes all options with their descriptions and defaults.
244 * This is thread-safe against all concurrent methods on `*this` and can be invoked anytime after ctor.
245 *
246 * @param os
247 * Stream to which to serialize.
248 */
250
251 /**
252 * Logs what help_to_ostream() would print.
253 *
254 * @param sev
255 * Severity to use for the log message(s).
256 */
257 using Impl::log_help;
258
259private:
260 /// @cond
261 // -^- Doxygen, please ignore the following. It gets confused by the following non-member `friend`.
262
263 // Friend for access to `private` base.
264 template<typename Value_set2>
265 friend std::ostream& operator<<(std::ostream& os, const Static_config_manager<Value_set2>& val);
266
267 // -v- Doxygen, please stop ignoring.
268 /// @endcond
269}; // class Static_config_manager
270
271// Free functions.
272
273/**
274 * Serializes (briefly) a Static_config_manager to a standard output stream.
275 *
276 * @relatesalso Static_config_manager
277 *
278 * @tparam Value_set
279 * See Static_config_manager doc header.
280 * @param os
281 * Stream to which to serialize.
282 * @param val
283 * Value to serialize.
284 * @return `os`.
285 */
286template<typename Value_set>
287std::ostream& operator<<(std::ostream& os, const Static_config_manager<Value_set>& val);
288
289// Template implementations.
290
291template<typename Value_set>
293 (log::Logger* logger_ptr, util::String_view nickname,
294 typename Option_set<Value_set>::Declare_options_func&& declare_opts_func_moved) :
295 Impl(logger_ptr, nickname, std::move(declare_opts_func_moved), null_declare_opts_func())
296{
297 // Nope.
298}
299
300template<typename Value_set>
301bool Static_config_manager<Value_set>::apply(const fs::path& cfg_path,
302 const typename Final_validator_func<Value_set>::Type& final_validator_func)
303{
304 return this->template apply_static<Value_set>(cfg_path, final_validator_func);
305 /* Yes, that ->template thing is highly weird-looking. Without it gcc was giving me
306 * "expected primary-expression before '<'". I got the answer -- which reminds me of `typename` -- here:
307 * https://stackoverflow.com/questions/610245/where-and-why-do-i-have-to-put-the-template-and-typename-keywords
308 * https://stackoverflow.com/questions/37995745/expected-primary-expression-before-token
309 *
310 * It also doesn't like it, if I remove `this->`. */
311}
312
313template<typename Value_set>
315 const fs::path& cfg_path,
316 const typename Final_validator_func<Value_set>::Type& final_validator_func)
317{
318 return this->template apply_static<Value_set>(Impl::S_ALLOW_INVALID_DEFAULTS, cfg_path, final_validator_func);
319}
320
321template<typename Value_set>
322bool Static_config_manager<Value_set>::apply(const fs::path& cfg_path)
323{
324 return apply(cfg_path, Final_validator_func<Value_set>::null()); // 2nd arg is a no-op/always-passes validator.
325}
326
327template<typename Value_set>
329{
330 return apply(S_ALLOW_INVALID_DEFAULTS, cfg_path, Final_validator_func<Value_set>::null());
331}
332
333template<typename Value_set>
335{
336 return this->template static_values<Value_set>(0); // The managed value set is in slot 0.
337}
338
339template<typename Value_set>
340std::ostream& operator<<(std::ostream& os, const Static_config_manager<Value_set>& val)
341{
342 return os << static_cast<const typename Static_config_manager<Value_set>::Impl&>(val);
343}
344
345} // namespace flow::cfg
Manages a config setup, intended for a single daemon process, by maintaining 1 or more set(s) of stat...
void log_help(log::Sev sev=log::Sev::S_INFO) const
Logs what help_to_ostream() would print.
void state_to_ostream(std::ostream &os) const
Prints a human-targeted long-form summary of our contents, doubling as a usage message and a dump of ...
void log_state(log::Sev sev=log::Sev::S_INFO) const
Logs what state_to_ostream() would print.
void help_to_ostream(std::ostream &os) const
Prints a human-targeted long-form usage message that includes all options with their descriptions and...
A Config_manager-related adapter-style class that manages a simple config setup involving a single (t...
allow_invalid_defaults_tag_t
Tag type: indicates an apply() method must allow invalid defaults and only complain if the config sou...
@ S_ALLOW_INVALID_DEFAULTS
Sole value for tag type allow_invalid_defaults_tag_t.
Static_config_manager(log::Logger *logger_ptr, util::String_view nickname, typename Option_set< Value_set >::Declare_options_func &&declare_opts_func_moved)
Constructs a Static_config_manager ready to read static config via apply() and access it via values()...
std::ostream & operator<<(std::ostream &os, const Static_config_manager< Value_set > &val)
Serializes (briefly) a Static_config_manager to a standard output stream.
bool apply(const fs::path &cfg_path, const typename Final_validator_func< Value_set >::Type &final_validator_func)
Invoke this after construction to load the permanent set of static config from config sources includi...
const Value_set & values() const
Returns (always the same) reference to the managed Value_set structure.
Interface that the user should implement, passing the implementing Logger into logging classes (Flow'...
Definition: log.hpp:1291
Flow module that facilitates configuring modules, such as applications and APIs, via statically and/o...
Definition: cfg_fwd.hpp:112
Option_set< Null_value_set >::Declare_options_func null_declare_opts_func()
Returns a value usable as declare_opts_func_moved Config_manager ctor arg for a Null_value_set value ...
Definition: cfg_manager.cpp:26
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.
Utility/traits type to concisely work with final-validation functions when calling methods like Confi...
Definition: cfg_manager.hpp:41