smart_config/lib.rs
1//! `smart-config` – schema-driven layered configuration system with support of multiple configuration formats.
2//!
3//! # Overview
4//!
5//! *See [extra docs](_docs) for deep dive into library features.*
6//!
7//! The task solved by the library is merging configuration input from a variety of prioritized [sources](ConfigSource)
8//! (JSON and YAML files, env variables, command-line args etc.) and converting this input to strongly typed
9//! representation (i.e., config structs or enums). As with other config systems, config input follows the JSON object model
10//! (see [`Value`](value::Value)), with each value enriched with its [origin](value::ValueOrigin) (e.g., a path in a specific JSON file,
11//! or a specific env var). This allows attributing errors during deserialization.
12//!
13//! The defining feature of `smart-config` is its schema-driven design. Each config type has associated [metadata](ConfigMetadata)
14//! defined with the help of the [`DescribeConfig`](macro@DescribeConfig) derive macro; deserialization is handled
15//! by the accompanying [`DeserializeConfig`](macro@DeserializeConfig) macro.
16//! Metadata includes a variety of info extracted from the config type:
17//!
18//! - [Parameter info](metadata::ParamMetadata): name (including aliases and renaming), help (extracted from doc comments),
19//! type, [deserializer for the param](de::DeserializeParam) etc.
20//! - [Nested configurations](metadata::NestedConfigMetadata).
21//!
22//! Multiple configurations are collected into a global [`ConfigSchema`]. Each configuration is *mounted* at a specific path.
23//! E.g., if a large app has an HTTP server component, it may be mounted at `api.http`. Multiple config types may be mounted
24//! at the same path (e.g., flattened configs); conversely, a single config type may be mounted at multiple places.
25//! As a result, there doesn't need to be a god object uniting all configs in the app; they may be dynamically collected and deserialized
26//! inside relevant components.
27//!
28//! This information provides rich human-readable info about configs. It also assists when preprocessing and merging config inputs.
29//! For example, env vars are a flat string -> string map; with the help of a schema, it's possible to:
30//!
31//! - Correctly nest vars (e.g., transform the `API_HTTP_PORT` var into a `port` var inside `http` object inside `api` object)
32//! - Transform value types from strings to expected types.
33//!
34//! Preprocessing and merging config sources is encapsulated in [`ConfigRepository`].
35//!
36//! # TL;DR
37//!
38//! - Rich, self-documenting configuration schema.
39//! - Utilizes the schema to enrich configuration sources and intelligently merge them.
40//! - Doesn't require a god object uniting all configs in the app; they may be dynamically collected and deserialized
41//! inside relevant components.
42//! - Supports lazy parsing for complex / multi-component apps (only the used configs are parsed; other configs are not required).
43//! - Supports multiple configuration formats and programmable source priorities (e.g., `base.yml` + overrides from the
44//! `overrides/` dir in the alphabetic order + env vars).
45//! - Rich and complete deserialization errors including locations and value origins.
46//! - [Built-in support for secret params](de#secrets).
47//!
48//! # Crate features
49//!
50//! ## `primitive-types`
51//!
52//! *(Off by default)*
53//!
54//! Implements deserialization for basic Ethereum types like [`H256`](primitive_types::H256) (32-byte hash)
55//! and [`U256`](primitive_types::U256) (256-bit unsigned integer).
56//!
57//! ## `alloy`
58//!
59//! *(Off by default)*
60//!
61//! Implements deserialization for basic alloy primitive types like [`B256`](alloy::primitives::B256) (32-byte hash)
62//! and [`U256`](alloy::primitives::U256) (256-bit unsigned integer).
63//!
64//! # Examples
65//!
66//! ## Basic workflow
67//!
68//! ```
69//! use smart_config::{
70//! config, ConfigSchema, ConfigRepository, DescribeConfig, DeserializeConfig, Yaml, Environment,
71//! };
72//!
73//! #[derive(Debug, DescribeConfig, DeserializeConfig)]
74//! pub struct TestConfig {
75//! pub port: u16,
76//! #[config(default_t = "test".into())]
77//! pub name: String,
78//! #[config(default_t = true)]
79//! pub tracing: bool,
80//! }
81//!
82//! let schema = ConfigSchema::new(&TestConfig::DESCRIPTION, "test");
83//! // Assume we use two config sources: a YAML file and env vars,
84//! // the latter having higher priority.
85//! let yaml = r"
86//! test:
87//! port: 4000
88//! name: app
89//! ";
90//! let yaml = Yaml::new("test.yml", serde_yaml::from_str(yaml)?)?;
91//! let env = Environment::from_iter("APP_", [("APP_TEST_PORT", "8000")]);
92//! // Add both sources to a repo.
93//! let repo = ConfigRepository::new(&schema).with(yaml).with(env);
94//! // Get the parser for the config.
95//! let parser = repo.single::<TestConfig>()?;
96//! let config = parser.parse()?;
97//! assert_eq!(config.port, 8_000); // from the env var
98//! assert_eq!(config.name, "app"); // from YAML
99//! assert!(config.tracing); // from the default value
100//! # anyhow::Ok(())
101//! ```
102//!
103//! ## Declaring type as well-known
104//!
105//! ```
106//! use std::collections::HashMap;
107//! use smart_config::{
108//! de::{Serde, WellKnown, WellKnownOption}, metadata::BasicTypes,
109//! DescribeConfig, DeserializeConfig,
110//! };
111//!
112//! #[derive(Debug, serde::Serialize, serde::Deserialize)]
113//! enum CustomEnum {
114//! First,
115//! Second,
116//! }
117//!
118//! impl WellKnown for CustomEnum {
119//! // signals that the type should be deserialized via `serde`
120//! // and the expected input is a string
121//! type Deserializer = Serde![str];
122//! const DE: Self::Deserializer = Serde![str];
123//! }
124//!
125//! // Signals that the type can be used with an `Option<_>`
126//! impl WellKnownOption for CustomEnum {}
127//!
128//! // Then, the type can be used in configs basically everywhere:
129//! #[derive(Debug, DescribeConfig, DeserializeConfig)]
130//! struct TestConfig {
131//! value: CustomEnum,
132//! optional: Option<CustomEnum>,
133//! repeated: Vec<CustomEnum>,
134//! map: HashMap<String, CustomEnum>,
135//! }
136//! ```
137
138// Documentation settings
139#![doc(html_root_url = "https://docs.rs/smart-config/0.4.0-pre.2")] // x-release-please-version
140#![cfg_attr(docsrs, feature(doc_cfg))]
141// TODO: restore the attribute once supported by the nightly toolchain used by ZKsync OS
142// #![cfg_attr(docsrs, doc(auto_cfg(hide(feature = "_docs"))))]
143// Linter settings
144#![warn(missing_docs)]
145
146pub use smart_config_derive::{DescribeConfig, DeserializeConfig, ExampleConfig};
147
148pub use self::{
149 de::DeserializeConfig,
150 error::{DeserializeConfigError, ErrorWithOrigin, ParseError, ParseErrorCategory, ParseErrors},
151 schema::{ConfigMut, ConfigRef, ConfigSchema},
152 source::{
153 ConfigParser, ConfigRepository, ConfigSource, ConfigSourceKind, ConfigSources, Environment,
154 Flat, Hierarchical, Json, Prefixed, SerializerOptions, SourceInfo, Yaml,
155 },
156 types::{ByteSize, EtherAmount},
157};
158use self::{metadata::ConfigMetadata, visit::VisitConfig};
159
160#[cfg(feature = "_docs")]
161pub mod _docs;
162pub mod de;
163mod error;
164pub mod fallback;
165pub mod metadata;
166pub mod pat;
167mod schema;
168mod source;
169pub mod testing;
170#[cfg(test)]
171mod testonly;
172mod types;
173mod utils;
174pub mod validation;
175pub mod value;
176pub mod visit;
177
178/// Describes a configuration (i.e., a group of related parameters).
179pub trait DescribeConfig: 'static + VisitConfig {
180 /// Provides the config description.
181 const DESCRIPTION: ConfigMetadata;
182}
183
184/// Provides an example for this configuration. The produced config can be used in tests etc.
185///
186/// For struct configs, this can be derived via [the corresponding proc macro](macro@ExampleConfig).
187pub trait ExampleConfig {
188 /// Constructs an example configuration.
189 fn example_config() -> Self;
190}
191
192impl<T: ExampleConfig> ExampleConfig for Option<T> {
193 fn example_config() -> Self {
194 Some(T::example_config())
195 }
196}
197
198#[cfg(doctest)]
199doc_comment::doctest!("../README.md");