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