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::*;