config_manager/__cookbook.rs
1// SPDX-License-Identifier: MIT
2// Copyright (c) 2022 JSRPC “Kryptonite”
3
4//! # Documentation: Cookbook
5//! 1. [Examples](#examples)
6//! 2. [Intro](#intro)
7//! 3. [Options](#options)
8//! 4. [Structure level attributes](#structure-attributes)
9//! 1. [env_prefix](#env_prefix)
10//! 2. [file](#file)
11//! 3. [clap](#clap)
12//! 4. [table](#table)
13//! 5. [default source order](#default_order)
14//! 5. [Field level attributes](#field-attributes)
15//! 1. [source](#source)
16//! - [default](#default)
17//! - [env](#env)
18//! - [config](#config)
19//! - [clap](#clap-1)
20//! - [deserialize_with](#deserialize_with)
21//! 2. [flatten](#flatten)
22//! - [attributes](#flatten-attributes)
23//! 3. [subcommand](#subcommand)
24//! 6. [`get_command` method](#get_command)
25//! # [Appendix](#appendix)
26//! 1. [Allowed clap attributes](#clap-attributes)
27//! 1. [App attributes](#clap-command)
28//! 2. [Field attributes](#clap-arg)
29//! ## Examples
30//! There are [tests](https://github.com/3xMike/config-manager/tree/main/tests)
31//! and [examples](https://github.com/3xMike/config-manager/tree/main/examples) in
32//! the crate repository to get you started
33//!
34//! ## Intro
35//! To label a structure as a config, it is required to annotate it with `#[config]`:
36//! ```
37//! use config_manager::config;
38//!
39//! #[config]
40//! struct Application {}
41//! ```
42//! **OBTAINING RESULTS**\
43//! [ConfigInit](../trait.ConfigInit.html) trait will be derived
44//! for the struct and one can invoke the initialization and obtain the result
45//! with [`<Application as ConfigInit>::parse()`](../trait.ConfigInit.html#method.parse)
46//! or [`<Application as ConfigInit>::parse_options(options)`](../trait.ConfigInit.html#tymethod.parse_options) method.
47//!
48//! **All the sources of the value of a field must be specified explicitly.** Fields that does not
49//! have at least one source specified are not allowed.
50//! ```
51//! use config_manager::config;
52//!
53//! #[config]
54//! struct Application {
55//! #[source(clap(long = "cli_my_field"), env = "ENV_MY_FIELD")]
56//! my_field: i32,
57//! }
58//! ```
59//!
60//! In this example, it will be checked that `cli_my_field` is specified via the command line
61//! interface (i.e. <nobr>`./your_binary --cli_my_field=42`;</nobr>
62//! see [the clap documentation](https://docs.rs/clap/3.1.18/clap/) for more details).
63//! If `cli_my_field` is indeed specified, it will be parsed with `serde`
64//! and, if the parsing is successful, the value for `my_field` will be assigned from the result.
65//! In case of a parsing error, the error will be returned instead.
66//!
67//! If `cli_my_field` is not specified, it will be checked that the `ENV_MY_FIELD`
68//! environment variable is present. If the `ENV_MY_FIELD` environment variable is present, its
69//! value will be parsed with `serde` and, if the parsing is successful, the value for `my_field`
70//! will be assigned from the result. In case of a parsing error, the error will be returned instead.
71//!
72//! If the `ENV_MY_FIELD` environment variable is not found, an error will be returned, because
73//! this is the last source we can take the value from, and there was a failure.
74//!
75//! ### Note
76//! The order of the sources is important! The following example does **NOT** do the same thing as
77//! the previous:
78//! ```
79//! use config_manager::config;
80//!
81//! #[config]
82//! struct Application {
83//! #[source(env = "ENV_MY_FIELD", clap(long = "cli_my_field"))]
84//! my_field: i32,
85//! }
86//! ```
87//!
88//! In this example, the `env` source will be checked first.
89//!
90//! **NOTES**
91//! - The possible sources are: `clap`, `env`, `config`, `default` (see below)
92//! - Default value will be assigned the last (after the others were not found).
93//! - If the value is not found in any of the sources, an error will be returned
94//! - Field type must implement `serde::de::Deserialize`
95//! - All attributes except `default` must match either `attribute = literal`, or
96//! `attribute(init_from = ...valid Rust code...)`, or `attribute`. In the last case, the "key"
97//! value (the CLI argument name, the environment variable name, or the config file key name —
98//! depending on the source) will match the field name. For example, annotating `my_field` with
99//! `#[clap]` means that the value could be assigned to `my_field` by specifying
100//! `--my_field=...` via the CLI
101//! - Attribute `default` must match `default = ...valid Rust code...` or `default`
102//! - If the `deserialize_with` attribute is not set, values from command line,
103//! environment will be deserialized according to [hjson syntax](https://hjson.github.io/)
104//!
105//! ## Options
106//! Parsing process may be run with a set of options by using the [ConfigInit::parse_options(options)](../trait.ConfigInit.html#tymethod.parse_options).
107//! The key point here is the fact that the options take precedence over the corresponding structure attributes, that can be useful in testing and other cases.\
108//! More information can be found in the [ConfigOption](../enum.ConfigOption.html) documentation.
109//!
110//! ## Structure attributes
111//! ### `env_prefix`
112//! Prefix of the environment variables. If not specified, the prefix will not be added.
113//! Thus, the `iter` field in the example below will be searched in the environment by the `demo_iter` key.
114//! ```
115//! # use config_manager::config;
116//! #
117//! #[config(
118//! env_prefix = "demo"
119//! )]
120//! struct AppConfig {
121//! #[source(env)]
122//! iter: i32,
123//! }
124//! ```
125//! **Notes**
126//! - The delimiter ('_') is placed automatically
127//! - `env_prefix = ""` will not add any prefix
128//! - `env`, `env_prefix` and similar attributes are case-insensitive. If both the `demo_iter` and
129//! `DEMO_ITER` environment variables are present, which of these two will be parsed *is not defined*
130//! - One can use `env_prefix` (without a value) to set the binary file name as a prefix
131//!
132//! **Example**
133//! ```
134//! # use config_manager::config;
135//! #
136//! #[config(env_prefix)]
137//! struct Config {
138//! #[source(env)]
139//! capacity: i32,
140//! }
141//! ```
142//! In the example above, the `capacity` field will be searched in the environment
143//! by the "*bin*_capacity" key, where `bin` is the name of the executable file.
144//!
145//! ### `file`
146//! Description of the configuration file. Has the following nested attributes:
147//! - `format`: `toml`/`json`/`yaml`/`ron`/`json5`
148//! - `env`: environment key containing path to the configuration file (case-insensitive)
149//! - `clap`: clap attributes of the argument, responsible for the path to the configuration file
150//!
151//! **Note:** in this case, clap attribute must have the nested `long` attribute (`clap(long = "...")`)
152//! - `default`: default configuration file path
153//! - `optional`: If the attribute is set, macro won't return Error if file is not found at set (by `clap`/`env`/`default`) path. Does not take values.\
154//!
155//! **Note:** It is allowed to specify multiple files: all of them will be merged.
156//! If there is a collision (the values of a particular key have been specified in two or more files),
157//! the value will be assigned from the file that has been described later (in the attribute list).
158//!
159//! **Example**
160//! ```
161//! # use config_manager::config;
162//! #
163//! #[config(
164//! env_prefix = "",
165//! file(format = "toml", env = "demo_config")
166//! )]
167//! struct AppConfig {
168//! #[source(clap(long), env, default = 5)]
169//! iter: i32,
170//! }
171//! ```
172//! In this case, the initialization order for the `iter` field is:
173//! - command line argument `--iter`
174//! - environment variable `iter`
175//! - variable `iter` from configuration file with path set by the `demo_config` environment variable
176//! - default value (`5`)
177//!
178//! ### `clap`
179//! Clap app attributes, like `name`, `version`, etc.
180//! Full list of supported clap attributes can be checked in the [Appendix](#clap-command)
181//!
182//! **Note**: Following attributes can be used without value (for example: `clap(name)`):
183//! - `name`: will be taken as package from Cargo.toml,
184//! - `version`: will be taken as crate version from Cargo.toml,
185//! - `author`: will be taken as crate authors from Cargo.toml,
186//! - `about`: will be taken as crate discription from Cargo.toml,
187//! - `long_about`: will be taken from doc comments of the struct (aka `///` or `/** */`).
188//!
189//! ### `table`
190//! Table of the configuration files to find fields of the structure.
191//!
192//! **Example**
193//! ```
194//! # use config_manager::config;
195//! #
196//! #[config(file(format = "toml", default = "./config.toml"), table = "input.data")]
197//! struct Config {
198//! #[source(config)]
199//! frames: i32,
200//! }
201//! ```
202//! Field `frames` will be searched in the "input.data" table of the configuration file "config.toml".
203//!
204//! ### `default_order`
205//! The default order of any field that wasn't annotated with any of `source`,`flatten` or `subcommand`.\
206//! `clap`, `env`, `config` and `default` are all possible parameters.
207//! Each attribute will be applied to each unannotated field in a "short" form
208//! (i.e., form without value; for example, `#[source(default)]` means that
209//! `Default::default()` will be used as a default value. See the [source](#source) section for more information).
210//! **Example**
211//! ```
212//! # use config_manager::config;
213//! #
214//! #[config(default_order(env, clap, default))]
215//! struct Config {
216//! rotation: f32,
217//! }
218//! ```
219//! It will be checked that the `ROTATION` environment variable is set; if not, the `--rotation` command line argument will be checked,
220//! and, lastly, the `Default::default()` will be assigned.
221//! **Note:** If this attribute isn't set, the default order is:
222//! 1. command line
223//! 2. environment variables
224//! 3. configuration files
225//!
226//! ## Field attributes
227//! Only fields can be annotated with the following attributes and only one of them can be assigned to a field.
228//!
229//! **Note:** if a field is not annotated with any of the following attributes,
230//! it will be parsed using the default source order (see the section above).
231//!
232//! ### Source
233//! If a field is annotated with the `source` attribute, at least one of the following nested attributes must be present.
234//!
235//! #### `default`
236//! Valid Rust code that will be assigned to the field if other sources are not found. \
237//! If the attribute is set without a value (`#[source(default)]`),
238//! the default value is [Default::default()].
239//!
240//! **Example**
241//! ```
242//! # use config_manager::config;
243//! #
244//! #[config]
245//! struct AppConfig {
246//! #[source(default = Vec::new())]
247//! buf: Vec<String>,
248//! #[source(default)]
249//! opt: Option<String>
250//! // Option::<String>::default() will be assigned (None)
251//! }
252//! ```
253//! **Note:** For [String] fields [Into::into()] will be invoked under the hood. So it is possible to use `&str` to initialize [String] field:
254//! ```
255//! # use config_manager::config;
256//! #
257//! #[config]
258//! struct AppConfig {
259//! #[source(default = "default value")]
260//! string: String,
261//! }
262//! ```
263//!
264//! #### `env`
265//! The name of the environment variable from which the value is to be set.
266//! `env_prefix` (see above) is ignored if present with a value (`#[source(env = "...")]`). The case is ignored. \
267//! If the attribute is set without value, the name of the environment variable to be set is `env_prefix + field_name`.
268//!
269//! #### `config`
270//! Name of the configuration file field to set the value from. It can contain dots: in this case
271//! the name will be parsed as the path of the field.\
272//! If the attribute is set without a value (`#[source(config)]`), the field name is the name of the configuration file field to be set.
273//! **Example**
274//! ```
275//! # use config_manager::config;
276//! #
277//! #[config(file(format = "toml", default = "./config.toml"), table = "input.data")]
278//! struct Config {
279//! #[source(config = "images.frame_rate")]
280//! rate: i32,
281//! }
282//! ```
283//! Field `rate` will be searched in the "input.data.images" table of the "config.toml"
284//! configuration file by the `frame_rate` key.
285//!
286//! #### `clap`
287//! Clap-crate field attributes, like `long`, `short`. Full list of supported clap attributes can be checked in the [Appendix](#clap-arg).
288//!
289//! **Note:** There is a new attribute: `flag`. If used on `field: bool`, this field can be set via CLI like a flag: `--field`. \
290//! **Note:** Following attributes can be used without value (for example: `clap(short)`):
291//! - `long`: the field name,
292//! - `short`: the first letter of the field name,
293//! - `help`: will be taken from doc comments of the field (aka `///` or `/** */`),
294//! - `long_help`: will be taken from doc comments of the field (aka `///` or `/** */`),
295//!
296//! **Note:** `#[source(clap)]` is equivalent to `#[source(clap(long))]` \
297//! **Note:** boolean fields can be marked as `#[source(clap(flag))]` that allow to set it as `true` with no value provided. \
298//! **Example:** the following field can be set to `true` using the CLI: `./my_app -f` or `./my_asp --flag true`.
299//! ```
300//! # use config_manager::config;
301//!
302//! #[config]
303//! struct Cfg {
304//! #[source(clap(long, short, flag))]
305//! flag: bool
306//! }
307//! ```
308//!
309//! #### `deserialize_with`
310//! Custom deserialization of the field. The deserialization function should have the following signature:
311//! ```ignore
312//! fn fn_name<S: AsRef<str>>(s: S) -> Result<FieldType, impl std::fmt::Display>
313//! ```
314//! **Note:** actually, `&String` will be passed to the function,
315//! so function can take any argument that is derivable from `&String`.
316//! It may be `&str`, `&String`, `T: AsRef<str>`, `T: AsRef<String>`, and so on.
317//! It is recommended to choose error type explicitly.
318//!
319//! **Example**
320//! ```
321//! # use config_manager::config;
322//! use std::time::Duration;
323//!
324//! #[config]
325//! struct MethodConfig {
326//! #[source(clap(long), deserialize_with = "deser_duration")]
327//! a: Duration,
328//! }
329//!
330//! fn deser_duration(dur: &str) -> Result<Duration, String> {
331//! match dur.parse::<u64>() {
332//! Ok(dur) => Ok(Duration::from_millis(dur)),
333//! Err(err) => Err(err.to_string()),
334//! }
335//! }
336//! ```
337//!
338//! ### Flatten
339//! If a field is annotated with the `flatten` attribute, it will be parsed as a nested structure and its fields will be initiated
340//! like fields of the primary config. In this case, the field's type must implement `config_manager::Flatten`
341//! (it is highly discouraged to implement this trait manually, use derive macro: `#[derive(Flatten)]`) and `serde::Deserialize`
342//!
343//! **Example**
344//! ```
345//! use config_manager::{config, Flatten};
346//! # use serde::Deserialize;
347//! #
348//! #[config]
349//! struct PrimalConfig {
350//! #[flatten]
351//! child: NestedConfig,
352//! }
353//!
354//! #[derive(Deserialize, Flatten)]
355//! struct NestedConfig {
356//! #[source(env = "recharge")]
357//! recharge_time: f32,
358//! #[source(default = 0.0)]
359//! capacity: f32,
360//! }
361//! ```
362//! **Notes:**
363//! - Nested configs can also contain `flatten` fields
364//! - `env_prefix` will be inherited from the initial struct
365//!
366//! #### Flatten attributes
367//! Flatten struct may have the following helper attributes: `table`, `flatten`, `source` (they work the same way as the described above ones).
368//! ### Subcommand
369//! If a field is annotated with the `subcommand` attribute, it will be taken as a `clap` subcommand
370//! (see [clap documentation](https://docs.rs/clap/latest/clap/_derive/_tutorial/index.html#subcommands) for more info).
371//! The field's type must implement `clap::Subcommand` and `serde::Deserialize`.
372//!
373//! **Example**
374//! ```
375//! # use config_manager::config;
376//! # use serde::Deserialize;
377//! # use clap;
378//! #
379//! #[config]
380//! struct Cargo {
381//! #[subcommand]
382//! sub: CargoCommands,
383//! }
384//!
385//! #[derive(Deserialize, clap::Subcommand)]
386//! enum CargoCommands {
387//! #[clap(about = "Compile the current package")]
388//! Build {
389//! // ...
390//! },
391//! #[clap(about = "Analyze the current package and report errors, but don't build object files")]
392//! Check {
393//! // ...
394//! },
395//! #[clap(about = "Build this package's and its dependencies' documentation")]
396//! Doc,
397//! #[clap(about = "Create a new cargo package")]
398//! New,
399//! // ...
400//! }
401//! ```
402//! **Notes:**
403//! - Value for the `subcommand` enumeration will be searched only in command line, so the `source` and the `flatten` attributes are forbidden
404//! (flatten `subcommand` attribute is allowed due to clap documentation).
405//! - Multiple `subcommand` fields are forbidden.
406//! - `subcommand` field in nested(`flatten`) structures are forbidden.
407//! - `subcommand` field can be optional (`Option<T>`, `T: clap::Subcommand + serde::Deserialize`),
408//! so if no subcommand is found in the command line, the `None` will be assigned.
409//!
410//! ## get_command
411//! [ConfigInit](../trait.ConfigInit.html) trait has the [get_command](../trait.ConfigInit.html#tymethod.get_command)
412//! method that builds [Command](https://docs.rs/clap/latest/clap/struct.Command.html) that can initialize the structure. \
413//! By using this method along with the [ClapSource::Matches](config_manager/enum.ClapSource.html#variant.Matches) option,
414//! one can initialize the structure as a subcommand, so settings of the application and the configuration can be divided, like:
415//! ```console
416//! binary [APPLICATION'S SETTINGS] configuration [CONFIGURATION'S SETTINGS]
417//! ```
418//!
419//! **Example**
420//! ```
421//! # use std::collections::HashSet;
422//! use config_manager::*;
423//! use clap::*;
424//!
425//! #[config(clap(name = "configuration", version, author))]
426//! struct Config {
427//! #[source(clap(long, short))]
428//! a: i32,
429//! }
430//!
431//! fn init_from_app() -> Option<Config> {
432//! let app = Command::new("binary")
433//! .arg(Arg::new("some_field").long("field"))
434//! .subcommand(Config::get_command())
435//! .get_matches();
436//!
437//! if let Some(subcommand) = app.subcommand_matches(Config::get_command().get_name()) {
438//! let opts = ConfigOption::ExplicitSource(Source::Clap(ClapSource::Matches(subcommand.clone())));
439//! Some(Config::parse_options(HashSet::from([opts])).unwrap())
440//! } else {
441//! None
442//! }
443//! }
444//! ```
445//!
446//! # Appendix
447//! ## Clap attributes
448//! All the attributes should be set in the form:
449//! ```ignore
450//! #[clap(attribute = value)]
451//! ```
452//! Where `value` is the code that should be passed to the corresponding clap method (or no value, if the attribute is a flag). For example:
453//! ```ignore
454//! #[clap(value_hint = ValueHint::Username, short_aliases = ['c', 'e'], long = "my_field", exclusive)]
455//! field: usize,
456//! ```
457//! Full usage can be checked [in the crate tests](https://github.com/3xMike/config-manager/blob/main/tests/parse_method/get_command.rs)
458//! ### Clap command
459//! Documentation on original methods: [clap::Command]
460//!
461//! Next attributes are allowed: \
462//! name, version, author, about, long_about, color, styles, term_width, max_term_width, disable_version_flag, next_line_help, disable_help_flag, disable_colored_help, help_expected, hide_possible_values, bin_name, display_name, after_help, after_long_help, before_help, before_long_help, long_version, override_usage, override_help, help_template, next_help_heading, next_display_order, allow_missing_positional, arg_required_else_help,
463//! ### Clap arg
464//! Documentation on original methods: [clap::Arg]
465//!
466//! Next attributes are allowed: \
467//! help, long_help, short, long, flag, help_heading, alias, short_alias, aliases, short_aliases, visible_alias, visible_short_alias, visible_aliases, visible_short_aliases, index, last, requires, exclusive, value_name, value_hint, ignore_case, allow_hyphen_values, allow_negative_numbers, require_equals, display_order, next_line_help, hide, hide_possible_values, hide_default_value, hide_short_help, hide_long_help, conflicts_with, conflicts_with_all, overrides_with, overrides_with_all,