figment/lib.rs
1#![cfg_attr(nightly, feature(doc_cfg))]
2#![deny(missing_docs)]
3
4//! Semi-hierarchical configuration so con-free, it's unreal.
5//!
6//! ```rust
7//! use serde::Deserialize;
8//! use figment::{Figment, providers::{Format, Toml, Json, Env}};
9//!
10//! #[derive(Deserialize)]
11//! struct Package {
12//! name: String,
13//! description: Option<String>,
14//! authors: Vec<String>,
15//! publish: Option<bool>,
16//! // ... and so on ...
17//! }
18//!
19//! #[derive(Deserialize)]
20//! struct Config {
21//! package: Package,
22//! rustc: Option<String>,
23//! rustdoc: Option<String>,
24//! // ... and so on ...
25//! }
26//!
27//! # figment::Jail::expect_with(|jail| {
28//! # jail.create_file("Cargo.toml", r#"
29//! # [package]
30//! # name = "test"
31//! # authors = ["bob"]
32//! # publish = false
33//! # "#)?;
34//! let config: Config = Figment::new()
35//! .merge(Toml::file("Cargo.toml"))
36//! .merge(Env::prefixed("CARGO_"))
37//! .merge(Env::raw().only(&["RUSTC", "RUSTDOC"]))
38//! .join(Json::file("Cargo.json"))
39//! .extract()?;
40//! # Ok(())
41//! # });
42//! ```
43//!
44//! # Table of Contents
45//!
46//! * [Overview](#overview) - A brief overview of the entire crate.
47//! * [Metadata](#metadata) - Figment's value metadata tracking.
48//! * [Extracting and Profiles](#extracting-and-profiles) - Semi-hierarchical
49//! "profiles", profile selection, nesting, and extraction.
50//! * [Crate Feature Flags](#crate-feature-flags) - Feature flags and what
51//! they enable.
52//! * [Available Providers](#available-providers) - Table of providers
53//! provided by this and other crates.
54//! * [For `Provider` Authors](#for-provider-authors) - Tips for writing
55//! [`Provider`]s.
56//! * [For Library Authors](#for-library-authors) - Brief guide for authors
57//! wishing to use Figment in their libraries or frameworks.
58//! * [For Application Authors](#for-application-authors) - Brief guide for
59//! authors of applications that use libraries that use Figment.
60//! * [For CLI Application Authors](#for-cli-application-authors) - Brief
61//! guide for authors of applications with a CLI and other configuration
62//! sources.
63//! * [Tips](#tips) - Things to remember when working with Figment.
64//! * [Type Index](#modules) - The real rustdocs.
65//!
66//! # Overview
67//!
68//! Figment is a library for declaring and combining configuration sources and
69//! extracting typed values from the combined sources. It distinguishes itself
70//! from other libraries with similar motives by seamlessly and comprehensively
71//! tracking configuration value provenance, even in the face of myriad sources.
72//! This means that error values and messages are precise and know exactly where
73//! and how misconfiguration arose.
74//!
75//! There are two prevailing concepts:
76//!
77//! * **Providers:** Types implementing the [`Provider`] trait, which
78//! implement a configuration source.
79//! * **Figments:** The [`Figment`] type, which combines providers via
80//! [`merge`](Figment::merge()) or [`join`](Figment::join) and allows
81//! typed [`extraction`](Figment::extract()). Figments are also providers
82//! themselves.
83//!
84//! Defining a configuration consists of constructing a `Figment` and merging or
85//! joining any number of [`Provider`]s. Values for duplicate keys from a
86//! _merged_ provider replace those from previous providers, while no
87//! replacement occurs for _joined_ providers. Sources are read eagerly,
88//! immediately upon merging and joining.
89//!
90//! The simplest useful figment has one provider. The figment below will use all
91//! environment variables prefixed with `MY_APP_` as configuration values, after
92//! removing the prefix:
93//!
94//! ```
95//! use figment::{Figment, providers::Env};
96//!
97//! let figment = Figment::from(Env::prefixed("MY_APP_"));
98//! ```
99//!
100//! Most figments will use more than one provider, merging and joining as
101//! necessary. The figment below reads `App.toml`, environment variables
102//! prefixed with `APP_` and fills any holes (but does not replace existing
103//! values) with values from `App.json`:
104//!
105//! ```
106//! use figment::{Figment, providers::{Format, Toml, Json, Env}};
107//!
108//! let figment = Figment::new()
109//! .merge(Toml::file("App.toml"))
110//! .merge(Env::prefixed("APP_"))
111//! .join(Json::file("App.json"));
112//! ```
113//!
114//! Values can be [`extracted`](Figment::extract()) into any value that
115//! implements [`Deserialize`](serde::Deserialize). The [`Jail`] type allows for
116//! semi-sandboxed configuration testing. The example below showcases
117//! extraction and testing:
118//!
119//! ```rust
120//! use serde::Deserialize;
121//! use figment::{Figment, providers::{Format, Toml, Json, Env}};
122//!
123//! #[derive(Debug, PartialEq, Deserialize)]
124//! struct AppConfig {
125//! name: String,
126//! count: usize,
127//! authors: Vec<String>,
128//! }
129//!
130//! figment::Jail::expect_with(|jail| {
131//! jail.create_file("App.toml", r#"
132//! name = "Just a TOML App!"
133//! count = 100
134//! "#)?;
135//!
136//! jail.create_file("App.json", r#"
137//! {
138//! "name": "Just a JSON App",
139//! "authors": ["figment", "developers"]
140//! }
141//! "#)?;
142//!
143//! jail.set_env("APP_COUNT", 250);
144//!
145//! // Sources are read _eagerly_: sources are read as soon as they are
146//! // merged/joined into a figment.
147//! let figment = Figment::new()
148//! .merge(Toml::file("App.toml"))
149//! .merge(Env::prefixed("APP_"))
150//! .join(Json::file("App.json"));
151//!
152//! let config: AppConfig = figment.extract()?;
153//! assert_eq!(config, AppConfig {
154//! name: "Just a TOML App!".into(),
155//! count: 250,
156//! authors: vec!["figment".into(), "developers".into()],
157//! });
158//!
159//! Ok(())
160//! });
161//! ```
162//!
163//! # Metadata
164//!
165//! Figment takes _great_ care to propagate as much information as possible
166//! about configuration sources. All values extracted from a figment are
167//! [tagged](crate::value::Tag) with the originating [`Metadata`] and
168//! [`Profile`]. The tag is preserved across merges, joins, and errors, which
169//! also include the [`path`](Error::path) of the offending key. Precise
170//! tracking allows for rich error messages as well as ["magic"] values like
171//! [`RelativePathBuf`], which automatically creates a path relative to the
172//! configuration file in which it was declared.
173//!
174//! A [`Metadata`] consists of:
175//!
176//! * The name of the configuration source.
177//! * An ["interpolater"](Metadata::interpolate()) that takes a path to a key
178//! and converts it into a provider-native key.
179//! * A [`Source`] specifying where the value was sourced from.
180//! * A code source [`Location`] where the value's provider was added to a
181//! [`Figment`].
182//!
183//! Along with the information in an [`Error`], this means figment can produce
184//! rich error values and messages:
185//!
186//! ```text
187//! error: invalid type: found string "hi", expected u16
188//! --> key `debug.port` in TOML file App.toml
189//! ```
190//!
191//! [`RelativePathBuf`]: value::magic::RelativePathBuf
192//! ["magic"]: value::magic
193//! [`Location`]: std::panic::Location
194//!
195//! # Extracting and Profiles
196//!
197//! Providers _always_ [produce](Provider::data()) [`Dict`](value::Dict)s nested
198//! in [`Profile`]s. A profile is [`selected`](Figment::select()) when
199//! extracting, and the dictionary corresponding to that profile is deserialized
200//! into the requested type. If no profile is selected, the
201//! [`Default`](Profile::Default) profile is used.
202//!
203//! There are two built-in profiles: the aforementioned default profile and the
204//! [`Global`](Profile::Global) profile. As the name implies, the default
205//! profile contains default values for all profiles. The global profile _also_
206//! contains values that correspond to all profiles, but those values supersede
207//! values of any other profile _except_ the global profile, even when another
208//! source is merged.
209//!
210//! Some providers can be configured as `nested`, which allows top-level keys in
211//! dictionaries produced by the source to be treated as profiles. The following
212//! example showcases profiles and nesting:
213//!
214//! ```rust
215//! use serde::Deserialize;
216//! use figment::{Figment, providers::{Format, Toml, Json, Env}};
217//!
218//! #[derive(Debug, PartialEq, Deserialize)]
219//! struct Config {
220//! name: String,
221//! }
222//!
223//! impl Config {
224//! // Note the `nested` option on both `file` providers. This makes each
225//! // top-level dictionary act as a profile.
226//! fn figment() -> Figment {
227//! Figment::new()
228//! .merge(Toml::file("Base.toml").nested())
229//! .merge(Toml::file("App.toml").nested())
230//! }
231//! }
232//!
233//! figment::Jail::expect_with(|jail| {
234//! jail.create_file("Base.toml", r#"
235//! [default]
236//! name = "Base-Default"
237//!
238//! [debug]
239//! name = "Base-Debug"
240//! "#)?;
241//!
242//! // The default profile is used...by default.
243//! let config: Config = Config::figment().extract()?;
244//! assert_eq!(config, Config { name: "Base-Default".into(), });
245//!
246//! // A different profile can be selected with `select`.
247//! let config: Config = Config::figment().select("debug").extract()?;
248//! assert_eq!(config, Config { name: "Base-Debug".into(), });
249//!
250//! // Selecting non-existent profiles is okay as long as we have defaults.
251//! let config: Config = Config::figment().select("undefined").extract()?;
252//! assert_eq!(config, Config { name: "Base-Default".into(), });
253//!
254//! // Replace the previous `Base.toml`. This one has a `global` profile.
255//! jail.create_file("Base.toml", r#"
256//! [default]
257//! name = "Base-Default"
258//!
259//! [debug]
260//! name = "Base-Debug"
261//!
262//! [global]
263//! name = "Base-Global"
264//! "#)?;
265//!
266//! // Global values override all profile values.
267//! let config_def: Config = Config::figment().extract()?;
268//! let config_deb: Config = Config::figment().select("debug").extract()?;
269//! assert_eq!(config_def, Config { name: "Base-Global".into(), });
270//! assert_eq!(config_deb, Config { name: "Base-Global".into(), });
271//!
272//! // Merges from succeeding providers take precedence, even for globals.
273//! jail.create_file("App.toml", r#"
274//! [debug]
275//! name = "App-Debug"
276//!
277//! [global]
278//! name = "App-Global"
279//! "#)?;
280//!
281//! let config_def: Config = Config::figment().extract()?;
282//! let config_deb: Config = Config::figment().select("debug").extract()?;
283//! assert_eq!(config_def, Config { name: "App-Global".into(), });
284//! assert_eq!(config_deb, Config { name: "App-Global".into(), });
285//!
286//! Ok(())
287//! });
288//! ```
289//!
290//! # Crate Feature Flags
291//!
292//! To help with compilation times, types, modules, and providers are gated by
293//! features. They are:
294//!
295//! | feature | gated namespace | description |
296//! |---------|-----------------------------|-------------------------------------------|
297//! | `test` | [`Jail`] | Semi-sandboxed environment for testing. |
298//! | `env` | [`providers::Env`] | Environment variable [`Provider`]. |
299//! | `toml` | [`providers::Toml`] | TOML file/string [`Provider`]. |
300//! | `json` | [`providers::Json`] | JSON file/string [`Provider`]. |
301//! | `yaml` | [`providers::Yaml`] | YAML file/string [`Provider`]. |
302//! | `yaml` | [`providers::YamlExtended`] | [YAML Extended] file/string [`Provider`]. |
303//!
304//! [YAML Extended]: providers::YamlExtended::from_str()
305//!
306//! # Available Providers
307//!
308//! In addition to the four gated providers above, figment provides the
309//! following providers out-of-the-box:
310//!
311//! | provider | description |
312//! |---------------------------------------|----------------------------------------|
313//! | [`providers::Serialized`] | Source from any [`Serialize`] type. |
314//! | [`(impl AsRef<str>, impl Serialize)`] | Global source from a `("key", value)`. |
315//! | [`&T` _where_ `T: Provider`] | Source from `T` as a reference. |
316//!
317//! <small>
318//!
319//! Note: `key` in `(key, value)` is a _key path_, e.g. `"a"` or `"a.b.c"`,
320//! where the latter indicates a nested value `c` in `b` in `a`.
321//!
322//! See [`Figment#extraction`] and [Data
323//! (keyed)](providers::Serialized#provider-details) for key path details.
324//!
325//! </small>
326//!
327//! [`Serialize`]: serde::Serialize
328//! [`(impl AsRef<str>, impl Serialize)`]: Provider#impl-Provider-for-(K%2C%20V)
329//! [`&T` _where_ `T: Provider`]: Provider#impl-Provider-for-%26%27_%20T
330//!
331//! ### Third-Party Providers
332//!
333//! The following external libraries implement Figment providers:
334//!
335//! - [`figment_file_provider_adapter`](https://crates.io/crates/figment_file_provider_adapter)
336//!
337//! Wraps existing providers. For any key ending in `_FILE` (configurable),
338//! emits a key without the `_FILE` suffix with a value corresponding to the
339//! contents of the file whose path is the original key's value.
340//!
341//! # For Provider Authors
342//!
343//! The [`Provider`] trait documentation details extensively how to implement a
344//! provider for Figment. For data format based providers, the [`Format`] trait
345//! allows for even simpler implementations.
346//!
347//! [`Format`]: providers::Format
348//!
349//! # For Library Authors
350//!
351//! For libraries and frameworks that wish to expose customizable configuration,
352//! we encourage the following structure:
353//!
354//! ```rust
355//! use serde::{Serialize, Deserialize};
356//!
357//! use figment::{Figment, Provider, Error, Metadata, Profile};
358//!
359//! // The library's required configuration.
360//! #[derive(Debug, Deserialize, Serialize)]
361//! struct Config { /* the library's required/expected values */ }
362//!
363//! // The default configuration.
364//! impl Default for Config {
365//! fn default() -> Self {
366//! Config { /* default values */ }
367//! }
368//! }
369//!
370//! impl Config {
371//! // Allow the configuration to be extracted from any `Provider`.
372//! fn from<T: Provider>(provider: T) -> Result<Config, Error> {
373//! Figment::from(provider).extract()
374//! }
375//!
376//! // Provide a default provider, a `Figment`.
377//! fn figment() -> Figment {
378//! use figment::providers::Env;
379//!
380//! // In reality, whatever the library desires.
381//! Figment::from(Config::default()).merge(Env::prefixed("APP_"))
382//! }
383//! }
384//!
385//! use figment::value::{Map, Dict};
386//!
387//! // Make `Config` a provider itself for composability.
388//! impl Provider for Config {
389//! fn metadata(&self) -> Metadata {
390//! Metadata::named("Library Config")
391//! }
392//!
393//! fn data(&self) -> Result<Map<Profile, Dict>, Error> {
394//! figment::providers::Serialized::defaults(Config::default()).data()
395//! }
396//!
397//! fn profile(&self) -> Option<Profile> {
398//! // Optionally, a profile that's selected by default.
399//! # None
400//! }
401//! }
402//! ```
403//!
404//! This structure has the following properties:
405//!
406//! * The library provides a `Config` structure that clearly indicates which
407//! values the library requires.
408//! * Users can completely customize configuration via their own [`Provider`].
409//! * The library's `Config` is itself a [`Provider`] for composability.
410//! * The library provides a `Figment` which it will use as the default
411//! configuration provider.
412//!
413//! `Config::from(Config::figment())` can be used as the library default while
414//! allowing complete customization of the configuration sources. Developers
415//! building on the library can base their figments on `Config::default()`,
416//! `Config::figment()`, both or neither.
417//!
418//! For frameworks, a top-level structure should expose the `Figment` that was
419//! used to extract the `Config`, allowing other libraries making use of the
420//! framework to also extract values from the same `Figment`:
421//!
422//! ```rust,no_run
423//! use figment::{Figment, Provider, Error};
424//! # struct Config;
425//! # impl Config {
426//! # fn figment() -> Figment { panic!() }
427//! # fn from<T: Provider>(_: T) -> Result<Config, Error> { panic!() }
428//! # }
429//!
430//! struct App {
431//! /// The configuration.
432//! pub config: Config,
433//! /// The figment used to extract the configuration.
434//! pub figment: Figment,
435//! }
436//!
437//! impl App {
438//! pub fn new() -> Result<App, Error> {
439//! App::custom(Config::figment())
440//! }
441//!
442//! pub fn custom<T: Provider>(provider: T) -> Result<App, Error> {
443//! let figment = Figment::from(provider);
444//! Ok(App { config: Config::from(&figment)?, figment })
445//! }
446//! }
447//! ```
448//!
449//! # For Application Authors
450//!
451//! As an application author, you'll need to make at least the following
452//! decisions:
453//!
454//! 1. The sources you'll accept configuration from.
455//! 2. The precedence you'll apply to each source.
456//! 3. Whether you'll use profiles or not.
457//!
458//! For special sources, you may find yourself needing to implement a custom
459//! [`Provider`]. As with libraries, you'll likely want to provide default
460//! values where possible either by providing it to the figment or by using
461//! [serde's defaults](https://serde.rs/attr-default.html). Then, it's simply a
462//! matter of declaring a figment and extracting the configuration from it.
463//!
464//! A reasonable starting point might be:
465//!
466//! ```rust
467//! use serde::{Serialize, Deserialize};
468//! use figment::{Figment, providers::{Env, Format, Toml, Serialized}};
469//!
470//! #[derive(Deserialize, Serialize)]
471//! struct Config {
472//! key: String,
473//! another: u32
474//! }
475//!
476//! impl Default for Config {
477//! fn default() -> Config {
478//! Config {
479//! key: "default".into(),
480//! another: 100,
481//! }
482//! }
483//! }
484//!
485//! Figment::from(Serialized::defaults(Config::default()))
486//! .merge(Toml::file("App.toml"))
487//! .merge(Env::prefixed("APP_"));
488//! ```
489//!
490//! # For CLI Application Authors
491//!
492//! As an author of an application with a CLI, you may want to use Figment in
493//! combination with a library like [`clap`] if:
494//!
495//! * You want to read configuration from sources outside of the CLI.
496//! * You want flexibility in how configuration sources are combined.
497//! * You want great error messages irrespective of how the application is
498//! configured.
499//!
500//! [`clap`]: https://docs.rs/clap/latest/clap/
501//!
502//! If any of these conditions apply, Figment is a great choice.
503//!
504//! If you are already using a library like [`clap`], you'll likely have a
505//! configuration structure defined:
506//!
507//! ```rust
508//! use clap::Parser;
509//!
510//! #[derive(Parser, Debug)]
511//! struct Config {
512//! /// Name of the person to greet.
513//! #[clap(short, long, value_parser)]
514//! name: String,
515//!
516//! /// Number of times to greet
517//! #[clap(short, long, value_parser, default_value_t = 1)]
518//! count: u8,
519//! }
520//! ```
521//!
522//! To enable the structure to be combined with other Figment sources, derive
523//! `Serialize` and `Deserialize` for the structure:
524//!
525//! ```diff
526//! + use serde::{Serialize, Deserialize};
527//!
528//! - #[derive(Parser, Debug)]
529//! + #[derive(Parser, Debug, Serialize, Deserialize)]
530//! struct Config {
531//! ```
532//!
533//! It can then be combined with other sources via the
534//! [`Serialized`](providers::Serialized) provider:
535//!
536//! ```rust
537//! use clap::Parser;
538//! use figment::{Figment, providers::{Serialized, Toml, Env, Format}};
539//! use serde::{Serialize, Deserialize};
540//!
541//! #[derive(Parser, Debug, Serialize, Deserialize)]
542//! struct Config {
543//! // ...
544//! }
545//!
546//! # figment::Jail::try_with(|_| {
547//! // Parse CLI arguments. Override CLI config values with those in
548//! // `Config.toml` and `APP_`-prefixed environment variables.
549//! let config: Config = Figment::new()
550//! .merge(Serialized::defaults(Config::parse()))
551//! .merge(Toml::file("Config.toml"))
552//! .merge(Env::prefixed("APP_"))
553//! .extract()?;
554//! # Ok(())
555//! # });
556//! ```
557//!
558//! See [For Application Authors](#for-application-authors) for further, general
559//! guidance on using Figment for application configuration.
560//!
561//! # Tips
562//!
563//! Some things to remember when working with Figment:
564//!
565//! * Merging and joining are _eager_: sources are read immediately. It's
566//! useful to define a function that returns a `Figment`.
567//! * The [`util`] modules contains helpful serialize and deserialize
568//! implementations for defining `Config` structures.
569//! * The [`Format`] trait makes implementing data-format based [`Provider`]s
570//! straight-forward.
571//! * [`Magic`](value::magic) values can significantly reduce the need to
572//! inspect a `Figment` directly.
573//! * [`Jail`] makes testing configurations straight-forward and much less
574//! error-prone.
575//! * [`Error`] may contain more than one error: iterate over it to retrieve
576//! all errors.
577//! * Using `#[serde(flatten)]` [can break error attribution], so it's best to
578//! avoid using it when possible.
579//!
580//! [can break error attribution]:
581//! https://github.com/SergioBenitez/Figment/issues/80#issuecomment-1701946622
582
583pub mod value;
584pub mod providers;
585pub mod error;
586pub mod util;
587mod figment;
588mod profile;
589mod coalesce;
590mod metadata;
591mod provider;
592
593#[cfg(any(test, feature = "test"))] mod jail;
594#[cfg(any(test, feature = "test"))] pub use jail::Jail;
595
596#[doc(inline)]
597pub use error::{Error, Result};
598pub use self::figment::Figment;
599pub use profile::Profile;
600pub use provider::*;
601pub use metadata::*;