smart_config/_docs/sources.rs
1//! # Parsing configurations from multiple sources
2//!
3//! # Source kinds
4//!
5//! [Configuration types](trait@crate::DescribeConfig) can be parsed from multiple [sources](crate::ConfigSource).
6//! Sources supported out of the box are:
7//!
8//! - [YAML](crate::Yaml) and [JSON](crate::Json) files
9//! - [environment variables](crate::Environment).
10//!
11//! YAML and JSON sources are *structured*, i.e., support the full JSON object model. In other words,
12//! config parameters can be serialized as objects or arrays.
13//!
14//! On the other hand, environment variables only support serialization of params as strings. There are a couple of workarounds
15//! to bridge the gap:
16//!
17//! - [`Environment::coerce_json()`](crate::Environment::coerce_json()) allows to signal that the variable value is JSON,
18//! via the `__JSON` suffix appended to the variable name.
19//! - Several deserializers like [`Delimited`](crate::de::Delimited) and [`DelimitedEntries`](crate::de::DelimitedEntries)
20//! allow to deserialize structured data from a string *in addition* to the structured object / array deserialization.
21//!
22//! # Combining sources
23//!
24//! Config sources can be combined in a [`ConfigSources`](crate::ConfigSources) object.
25//! *How* sources are combined, depends on the app; the library doesn't force any particular choice.
26//!
27//! As an example, assume that we want to source configuration
28//! from files supplied by the user via command-line args (like `--cfg-file base.yml:overrides.yml`),
29//! and with higher-priority env variable overrides, where env variables are prefixed by `APP_`.
30//! In this case, `ConfigSources` can be constructed as follows (using [`clap`] as the command-line parser):
31//!
32//! [`clap`]: https://docs.rs/clap/
33//!
34//! ```no_run
35//! # use std::{fs, io, path::PathBuf};
36//! # use anyhow::Context;
37//! # use clap::Parser as _;
38//! use smart_config::{
39//! ConfigRepository, ConfigSchema, ConfigSources, Environment, Json, Yaml,
40//! };
41//!
42//! // Separator between paths
43//! const PATH_SEP: char = if cfg!(windows) { ';' } else { ':' };
44//!
45//! #[derive(Debug, clap::Parser)]
46//! struct Cli {
47//! /// Configuration files.
48//! #[arg(long, value_name = "FILE", value_delimiter = PATH_SEP)]
49//! cfg_file: Vec<PathBuf>,
50//! // Other command-line args...
51//! }
52//!
53//! let cli: Cli = Cli::parse();
54//! let mut sources = ConfigSources::default();
55//!
56//! // Add file configuration sources
57//! for file in &cli.cfg_file {
58//! // Auto-detect file type by its extension
59//! let ext = file.extension().context("no file extension")?;
60//! let ext = ext.to_str().context("unsupported file extension")?;
61//! match ext {
62//! "json" => {
63//! let file_reader = io::BufReader::new(fs::File::open(file)?);
64//! let json = serde_json::from_reader(file_reader)?;
65//! sources.push(Json::new(&file.to_string_lossy(), json));
66//! }
67//! "yml" | "yaml" => {
68//! let file_reader = io::BufReader::new(fs::File::open(file)?);
69//! let yaml = serde_yaml::from_reader(file_reader)?;
70//! sources.push(Yaml::new(&file.to_string_lossy(), yaml)?);
71//! }
72//! _ => anyhow::bail!("unsupported extension: {ext}"),
73//! }
74//! }
75//!
76//! // Add environment variables
77//! let mut env = Environment::prefixed("APP_");
78//! // Coerce JSON-suffixed env variables
79//! env.coerce_json()?;
80//! sources.push(env);
81//!
82//! // Parse configurations from the sources.
83//! let schema: ConfigSchema = // ...
84//! # ConfigSchema::default();
85//! let repo = ConfigRepository::new(&schema).with_all(sources);
86//! // `repo` can be used to parse configurations, output canonical param values etc.
87//! # anyhow::Ok(())
88//! ```
89//!
90//! # See also
91//!
92//! - [Examples of YAML and env sources](super::derive_examples#advanced-features)