confique/
lib.rs

1//! Confique is a type-safe, layered, light-weight, `serde`-based configuration library.
2//!
3//! The core of the library is the [`Config`] trait and [its derive-macro][macro@Config].
4//! You define your configuration value as one or more structs, each of which has
5//! to `#[derive(Config)]`. Then you can use different ways of loading an instance
6//! of your root configuration struct.
7//!
8//!
9//! # How to use
10//!
11//! Add `confique` as dependency to your `Cargo.toml` and remember to enable the
12//! crate features for file formats you are interested in. For example:
13//! `cargo add confique --features=toml`.
14//!
15//! ## Defining your configuration with structs
16//!
17//! First, define some structs that describe all your configuration values. Use
18//! the types you want to use in your code. For example, if you have a `port`
19//! config and your code needs that value, it should be of type `u16`,
20//! and *not* `Option<u16>` or `String`. That way, the code using that value is
21//! cleanest.
22//!
23//! Small example:
24//!
25//! ```
26//! use confique::Config;
27//!
28//! #[derive(Config)]
29//! struct Conf {
30//!     // A required value. Since it's not `Option<_>`, it has to be specified when
31//!     // loading the configuration, or else loading returns an error.
32//!     username: String,
33//!
34//!     // An optional value.
35//!     welcome_message: Option<String>,
36//!
37//!     // A required value with default value. If no other value is specified
38//!     // (e.g. in a config file), the default value is used.
39//!     #[config(default = 8080)]
40//!     port: u16,
41//! }
42//! # fn main() {}
43//! ```
44//!
45//! As your application grows, oftentimes you want to split the configuration
46//! into multiple structs. This has the added benefit that your config files
47//! are somewhat structured or have sections. You can do that by including
48//! other types that implement `Config` with `#[config(nested)]`.
49//!
50//! ```
51//! use std::path::PathBuf;
52//! use confique::Config;
53//!
54//! #[derive(Config)]
55//! struct Conf {
56//!     username: String,
57//!
58//!     #[config(nested)]
59//!     log: LogConf,
60//!
61//!     #[config(nested)]
62//!     db: DbConf,
63//! }
64//!
65//! #[derive(Config)]
66//! struct LogConf {
67//!     #[config(default = true)]
68//!     stdout: bool,
69//!
70//!     file: Option<PathBuf>,
71//! }
72//!
73//! #[derive(Config)]
74//! struct DbConf {
75//!     // ...
76//! }
77//! # fn main() {}
78//! ```
79//!
80//! You can also attach some other attributes to fields. For example, with
81//! `#[config(env = "KEY")]`, you can load a value from an environment variable.
82//! With `#[config(validate = ...)]` you can add validation checks. For more
83//! information, see the [docs for the derive macro][macro@Config].
84//!
85//!
86//! ## Loading the configuration
87//!
88//! Here, you have multiple options. Most of the time, you can probably use the
89//! provided high-level methods of [`Config`], like [`Config::from_file`] and
90//! [`Config::builder`].
91//!
92//! ```
93//! use confique::Config;
94//!
95//! # #[derive(Config)]
96//! # struct Conf {}
97//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
98//! // Load from a single file only.
99//! # #[cfg(feature = "toml")]
100//! let config = Conf::from_file("config.toml")?;
101//!
102//! // Or load from multiple sources (higher priority sources are listed first).
103//! # #[cfg(feature = "toml")]
104//! let config = Conf::builder()
105//!     .env()
106//!     .file("config.toml")
107//!     .file("/etc/myapp/config.toml")
108//!     .load()?;
109//! # Ok(())
110//! # }
111//! ```
112//!
113//! But you can also assemble your configuration yourself. That's what
114//! the *partial* types are for (i.e. [`Config::Partial`]). These implement
115//! `serde::Deserialize` and can thus be loaded from a vast number of sources.
116//! One of those sources is the built-in [`File`] which gives you a bit more
117//! control when loading configuration from files. And you can always simply
118//! create an instance of the partial type by writing all values in Rust code
119//! with struct initializer syntax!
120//!
121//! Once you have all your layers (partial types) collected, you have to combine
122//! them via [`Partial::with_fallback`] and convert them to the actual config
123//! type via [`Config::from_partial`]. And you probably also want to use
124//! [`Partial::default_values`] as the last layer.
125//!
126//! ```no_run
127//! # #[cfg(not(feature = "toml"))]
128//! # fn main() {}
129//! # #[cfg(feature = "toml")]
130//! # fn main() -> Result<(), confique::Error> {
131//! use confique::{Config, File, FileFormat, Partial};
132//!
133//! #[derive(Config)]
134//! struct Conf {
135//!     foo: f32,
136//! }
137//!
138//! type PartialConf = <Conf as Config>::Partial;
139//! let from_file: PartialConf = File::with_format("/etc/foo/config", FileFormat::Toml)
140//!     .required()
141//!     .load()?;
142//! let manual = PartialConf {
143//!     // Remember: all fields in the partial types are `Option`s!
144//!     foo: Some(3.14),
145//! };
146//! let defaults = PartialConf::default_values();
147//!
148//! let merged = from_file.with_fallback(manual).with_fallback(defaults);
149//! let config = Conf::from_partial(merged)?;
150//! # Ok(())
151//! # }
152//! ```
153//!
154//! ## Using your configuration
155//!
156//! Well, this is the simple part: the loaded configuration is just an instance
157//! of your struct. And you already know how to access fields of structs!
158//!
159//!
160//! # Cargo features
161//!
162//! This crate has a Cargo feature for each supported file format. These are not
163//! enabled by default, so you have to specify which file formats you are
164//! interested in.
165//!
166//! ```toml
167//! confique = { version = "...", features = ["toml"] }
168//! ```
169//!
170//! All crate features:
171//!
172//! - `toml`: enables TOML support and adds the `toml` dependency.
173//! - `yaml`: enables YAML support and adds the `serde_yaml` dependency.
174//! - `json5`: enables JSON5 support and adds the `json5` dependency.
175
176use serde::Deserialize;
177
178#[doc(hidden)]
179pub mod internal;
180
181mod builder;
182pub mod env;
183mod error;
184pub mod meta;
185
186#[cfg(any(feature = "toml", feature = "yaml", feature = "json5"))]
187mod file;
188
189#[cfg(any(feature = "toml", feature = "yaml", feature = "json5"))]
190mod template;
191
192#[cfg(feature = "json5")]
193pub mod json5;
194
195#[cfg(feature = "toml")]
196pub mod toml;
197
198#[cfg(feature = "yaml")]
199pub mod yaml;
200
201#[cfg(test)]
202mod test_utils;
203
204
205pub use serde;
206pub use self::{
207    builder::Builder,
208    error::Error,
209};
210
211#[cfg(any(feature = "toml", feature = "yaml", feature = "json5"))]
212pub use crate::{
213    file::{File, FileFormat},
214    template::FormatOptions,
215};
216
217
218/// Derives (automatically implements) [`Config`] for a struct.
219///
220/// This only works for structs with named fields (i.e. not for tuple structs,
221/// unit structs, enums, or unions). This macro only works sometimes inside of
222/// functions (as it generates a module and symbol resolution is weird in that
223/// case); if you get weird errors "symbol not found", just move the struct
224/// definition outside of the function.
225///
226/// # Quick example
227///
228/// ```
229/// use confique::Config;
230/// use std::net::IpAddr;
231///
232/// #[derive(Config)]
233/// struct Conf {
234///     color: Option<String>,
235///
236///     #[config(nested)]
237///     http: HttpConf,
238/// }
239///
240/// #[derive(Config)]
241/// struct HttpConf {
242///     #[config(env = "APP_PORT")]
243///     port: u16,
244///
245///     #[config(default = "127.0.0.1")]
246///     bind: IpAddr,
247///
248///     #[config(default = ["x-user", "x-password"])]
249///     headers: Vec<String>,
250/// }
251/// # fn main() {}
252/// ```
253///
254/// This derives `Config` for the two structs.
255///
256/// - `HttpConf::port` can be loaded from the environment variable `APP_PORT`.
257/// - `HttpConf::bind` has a default value of `127.0.0.1` (the string is turned
258///   into the `IpAddr` via its `Deserialize` impl). Thus a value for this
259///   field does not need to be present when loading configuration.
260/// - `Conf::color` is optional and does not need to be present when loading the
261///   configuration.
262///
263///
264/// # How to use
265///
266/// There are two types of fields distinguished by this macro: nested and leaf
267/// fields.
268///
269/// - **Nested fields**: they have to be annotated with `#[config(nested)]` and
270///   contain a nested configuration object. The type of this field must
271///   implement `Config`. As implied by the previous statement, `Option<_>` as
272///   type for nested fields is not allowed.
273///
274/// - **Leaf fields**: all fields *not* annotated with `#[config(nested)]`,
275///   these contain your actual values. The type of such a field has to
276///   implement `serde::Deserialize` or you have to add a `deserialize_with`
277///   attribute.
278///
279/// Doc comments on the struct and the individual fields are interpreted and
280/// stored in [`Meta`][meta::Meta]. They are used in the formatting functions
281/// (e.g. `toml::format`).
282///
283/// ## Special types for leaf fields
284///
285/// These types give a different meaning/semantic to the field. Please note that
286/// due to the limitations of derive macros, the type is checked *literally*.
287/// So it won't work if you rename symbols or use full paths.
288///
289/// - **`Option<T>`**: this marks the field as an optional field. All other
290///   fields are non-optional and will raise an error if while loading the
291///   configuration, no value has been set for them. Optional fields cannot have
292///   a `#[config(default = ...)]` attribute as that would not make sense.
293///
294///
295/// ## Field Attributes
296///
297/// The following attributes can be attached to struct fields.
298///
299/// ### `default`
300///
301/// ```ignore
302/// #[config(default = ...)]
303/// ```
304///
305/// Sets a default value for this field. This is returned by
306/// [`Partial::default_values`] and, in most circumstances, used as a
307/// last "layer" to pull values from that have not been set in a layer of
308/// higher-priority. Currently, the following expressions are allowed:
309///
310/// - Booleans, e.g. `default = true`
311/// - Integers, e.g. `default = 900`
312/// - Floats, e.g. `default = 3.14`
313/// - Strings, e.g. `default = "fox"`
314/// - Arrays, e.g. `default = ["foo", "bar"]`
315/// - Key value maps, e.g. `default = { "cat": 3.14, "bear": 9.0 }`
316///
317/// Map keys can be Booleans, integers, floats, and strings. For array and map
318/// values, you can use any of the expressions in the list above (i.e. you
319/// can nest arrays/maps).
320///
321/// The field value is deserialized from the specified default value
322/// (via `serde::de::IntoDeserializer`). So the expression after `default =`
323/// is often not the same Rust type as your field. For example, you can have
324/// `#[config(default = "/foo/bar")]` on the field `path: PathBuf`. This
325/// works fine as `PathBuf` can be deserialized from a string. (Also see the
326/// `IpAddr` field in the example above.)
327///
328/// If you use an integer or float literal without type suffix, `confique` has
329/// to infer the exact type from the type of the field. This should work in
330/// most cases (`u8`, `f32`, `Vec<i16>`, `[f64; 3]`, ...), but this type
331/// inference is very basic, not even close to what Rust can do. If confique
332/// cannot figure out the type, it defaults to `i32` for integers and `f64`
333/// for floats (like Rust does). If that causes problems for you, just add a
334/// type suffix, e.g. `default = 800u32`.
335///
336/// ### `env`
337///
338/// ```ignore
339/// #[config(env = "KEY")]
340/// ```
341///
342/// Assigns an environment variable to this field. In [`Partial::from_env`], the
343/// variable is checked and deserialized into the field if present.
344///
345/// If the env var is set to an empty string and if the field fails to
346/// parse/deserialize/validate, it is treated as unset.
347///
348/// ### `parse_env`
349///
350/// ```ignore
351/// #[config(parse_env = path::to::function)]
352/// ```
353///
354/// Function used to parse environment variables. Mostly useful if you need to
355/// parse lists or other complex objects from env vars. Function needs
356/// signature `fn(&str) -> Result<T, impl std::error::Error>` where `T` is the
357/// type of the field. Can only be present if the `env` attribute is present.
358/// Also see [`env::parse`].
359///
360/// #### `deserialize_with`
361///
362/// ```ignore
363/// #[config(deserialize_with = path::to::function)]
364/// ```
365///
366/// Like [serde's `deserialize_with` attribute][serde-deser].
367///
368/// [serde-deser]: https://serde.rs/field-attrs.html#deserialize_with
369///
370/// #### `validate`
371///
372/// ```ignore
373/// #[config(validate = path::to::function)]
374/// // or
375/// #[config(validate(<expr>, "msg"))]
376/// ```
377///
378/// Adds a validation to the field, i.e. a check that must suceed to be able to
379/// load the configuration. The validator is called as part of the
380/// deserialization, and is thus executed for all layers, not just for the
381/// merged configuration.
382///
383/// > *Note*: remember ["Parse, don't validate"][parse-not-validate]! If you can
384///    reasonably represent your validation logic as a type, you should use
385///    that type instead of validating a weakly-typed field. Example: if your
386///    config value is an IP-address, use the dedicated `std::net::IpAddr` as
387///    field type (can be deserialized from strings) instead of a `String`
388///    field with a `validate` function making sure it's a valid IP-address.
389/// >
390/// > ```ignore
391/// > // GOOD
392/// > addr: std::net::IpAddr,
393/// >
394/// > // BAD
395/// > #[config(validate(addr.parse::<std::net::IpAddr>().is_ok(), "not a valid IP-address"))]
396/// > addr: String,
397/// > ```
398///
399/// [parse-not-validate]: https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/
400///
401/// The `validate = path::to::function` syntax expects a function that is
402/// callable as `Fn(&T) -> Result<(), E>` where `T` is the (non-optional) type
403/// of the field and `E` can be any type implementing `fmt::Display` (e.g. just
404/// `&str`). Example:
405///
406/// ```
407/// use confique::Config;
408///
409/// #[derive(Config)]
410/// struct Conf {
411///     #[config(validate = is_valid_user)]
412///     user: Option<String>,
413/// }
414///
415/// fn is_valid_user(user: &String) -> Result<(), &'static str> {
416///     if user == "root" {
417///         return Err("user 'root' is not allowed");
418///     }
419///     if !user.is_ascii() {
420///         return Err("user must be an ASCII string");
421///     }
422///     Ok(())
423/// }
424/// # fn main() {}
425/// ```
426///
427/// The `validate(<expr>, "msg")` syntax is only for convenience and intended
428/// for simple cases. It works similar to the `assert!` macro as it expects an
429/// expression validating to `bool` and a string error message. The expression
430/// can access the field value by reference via the field's name. If the
431/// expression validates to `false`, this is treated as a validation error.
432/// Examples:
433///
434/// ```
435/// use confique::Config;
436///
437/// #[derive(Config)]
438/// struct Conf {
439///     #[config(validate(!name.is_empty(), "name must not be empty"))]
440///     name: String,
441///
442///     #[config(validate(*port >= 1024, "cannot use ports < 1024 as non-root user"))]
443///     port: Option<u16>,
444/// }
445/// ```
446///
447///
448/// ## Struct attributes
449///
450/// The following attributes can be attached to the struct itself.
451///
452/// #### `validate`
453///
454/// ```ignore
455/// #[config(validate = path::to::function)]
456/// ```
457///
458/// Adds a validation to the config struct, i.e. a check that must suceed to be
459/// able to load the configuration. The validator is called inside
460/// `Config::from_partial`, i.e. only after all layers have been merged.
461///
462/// The given`path::to::function` is expected to be a function callable as
463/// `Fn(&T) -> Result<(), E>` where `T` is the struct type (`Self`) and `E` can
464/// be any type implementing `fmt::Display` (e.g. just `&str`). Example:
465///
466/// ```
467/// use confique::Config;
468///
469/// #[derive(Config)]
470/// #[config(validate = Self::validate)]
471/// struct ColorMixConfig {
472///     source_weight: f32,
473///     target_weight: f32,
474/// }
475///
476/// impl ColorMixConfig {
477///     fn validate(&self) -> Result<(), &'static str> {
478///         if self.source_weight + self.target_weight > 1.0 {
479///             return Err("sum of weights must not exceed 1");
480///         }
481///         Ok(())
482///     }
483/// }
484/// # fn main() {}
485/// ```
486///
487/// ### `partial_attr`
488///
489/// ```ignore
490/// #[config(partial_attr(...))]
491/// ```
492///
493/// Specify attributes that should be attached to the partial struct definition.
494/// For example, `#[config(partial_attr(derive(Clone)))]` can be used to make
495/// the partial type implement `Clone`.
496///
497/// This attribute can also be applied to struct fields.
498///
499///
500/// # What the macro generates
501///
502/// This macro emits one `impl confique::Config for … { … }` block. But in order
503/// to implement that trait, a *partial type* of your struct is also generated.
504/// That partial type lives in its own module and derives
505/// `serde::Deserialize`.
506///
507/// The example in the "Quick example" section above would expand to something
508/// like this:
509///
510/// ```ignore
511/// // ----- Generated for `Conf` -----
512/// impl confique::Config for Conf {
513///     type Partial = confique_partial_conf::PartialConf;
514///     ...
515/// }
516/// mod confique_partial_conf {
517///     #[derive(serde::Deserialize)]
518///     pub(super) struct PartialConf {
519///         pub(super) color: Option<String>,
520///
521///         #[serde(default = "confique::Partial::empty")]
522///         pub(super) http: <HttpConf as confique::Config>::Partial,
523///     }
524///
525///     impl confique::Partial for PartialConf { ... }
526/// }
527///
528/// // ----- Generated for `HttpConf` -----
529/// impl confique::Config for HttpConf {
530///     type Partial = confique_partial_http_conf::PartialHttpConf;
531///     ...
532/// }
533/// mod confique_partial_http_conf {
534///     #[derive(serde::Deserialize)]
535///     pub(super) struct PartialHttpConf {
536///         pub(super) port: Option<u16>,
537///         pub(super) bind: Option<IpAddr>,
538///         pub(super) headers: Option<Vec<String>>,
539///     }
540///
541///     impl confique::Partial for PartialHttpConf { ... }
542/// }
543/// ```
544pub use confique_macro::Config;
545
546
547/// A configuration object that can be deserialized in layers via `serde`.
548///
549/// You would usually derive this trait for your own type and then load the
550/// configuration with one of the provided methods, like
551/// [`from_file`][Self::from_file] or [`builder`](Self::builder).
552///
553/// # Deriving
554///
555/// This trait is usually derived as implementing it manually usually entails
556/// writing some repetitive boilerplate code, that goes against the "don't
557/// repeat yourself" principle. See [the documentation of the derive
558/// macro][macro@Config] for more information!
559pub trait Config: Sized {
560    /// A version of `Self` that represents a potetially partial configuration.
561    ///
562    /// This type is supposed to have the exact same fields as this one, but
563    /// with every field being optional. Its main use is to have a layered
564    /// configuration from multiple sources where each layer might not contain
565    /// all required values. The only thing that matters is that combining all
566    /// layers will result in a configuration object that has all required
567    /// values defined.
568    type Partial: Partial;
569
570    /// A description of this configuration.
571    ///
572    /// This is a runtime representation from the struct definition of your
573    /// configuration type.
574    const META: meta::Meta;
575
576    /// Tries to create `Self` from a potentially partial object and validates
577    /// itself.
578    ///
579    /// An [`Error`] is returned if:
580    /// - any required values are not defined in `partial`, or
581    /// - the struct validation fails (see `validate` attribute on derive macro)
582    fn from_partial(partial: Self::Partial) -> Result<Self, Error>;
583
584    /// Convenience builder to configure, load and merge multiple configuration
585    /// sources. **Sources specified earlier have a higher priority**; later
586    /// sources only fill in the gaps. After all sources have been loaded, the
587    /// default values (usually specified with `#[default = ...]`) are merged
588    /// (with the lowest priority).
589    ///
590    /// # Example
591    ///
592    /// In the following example, configuration is first loaded from environment
593    /// variables, then from `app.toml`, then from `/etc/app/config.toml` and
594    /// finally from the configured default values. Values found earlier in
595    /// this list have precedence.
596    ///
597    /// ```
598    /// use confique::Config;
599    ///
600    /// #[derive(Config)]
601    /// struct Conf {
602    ///     #[config(env = "APP_PORT", default = 8080)]
603    ///     port: u16,
604    /// }
605    ///
606    /// #[cfg(feature = "toml")]
607    /// let conf = Conf::builder()
608    ///     .env()
609    ///     .file("app.toml")
610    ///     .file("/etc/app/config.toml")
611    ///     .load();
612    /// ```
613    fn builder() -> Builder<Self> {
614        Builder::new()
615    }
616
617
618    /// Load the configuration from a single file.
619    ///
620    /// If you rather want to load from multiple sources, use
621    /// [`Config::builder`]. Infers the file format from the file extension.
622    /// Returns an error in these cases:
623    ///
624    /// - The path does not have a known file extension.
625    /// - Loading the file fails.
626    /// - The file does not specify all required configuration values.
627    ///
628    /// # Example
629    ///
630    /// ```
631    /// use confique::Config;
632    ///
633    /// #[derive(Config)]
634    /// struct Conf {
635    ///     port: u16,
636    /// }
637    ///
638    /// let conf = Conf::from_file("config.toml");
639    /// ```
640    #[cfg(any(feature = "toml", feature = "yaml", feature = "json5"))]
641    fn from_file(path: impl Into<std::path::PathBuf>) -> Result<Self, Error> {
642        let default_values = Self::Partial::default_values();
643        let mut file = File::new(path)?;
644        if !default_values.is_complete() {
645            file = file.required();
646        }
647
648        Self::from_partial(file.load::<Self::Partial>()?.with_fallback(default_values))
649    }
650}
651
652/// A potentially partial configuration object that can be directly deserialized
653/// via `serde`.
654pub trait Partial: for<'de> Deserialize<'de> {
655    /// Returns `Self` where all fields/values are `None` or empty.
656    fn empty() -> Self;
657
658    /// Returns an object containing all default values (i.e. set via
659    /// `#[config(default = ...)]` when deriving `Config`) with all remaining
660    /// values/fields set to `None`/being empty.
661    fn default_values() -> Self;
662
663    /// Loads values from environment variables. This is only relevant for
664    /// fields annotated with `#[config(env = "...")]`: all fields not
665    /// annotated `env` will be `None`.
666    ///
667    /// If the env variable corresponding to a field is not set, that field is
668    /// `None`. If it is set and non-empty, but it failed to deserialize into
669    /// the target type, an error is returned. If set to an empty string *and*
670    /// if it fails to deserialize, it's treated as not set.
671    fn from_env() -> Result<Self, Error>;
672
673    /// Combines two partial configuration objects. `self` has a higher
674    /// priority; missing values in `self` are filled with values in `fallback`,
675    /// if they exist. The semantics of this method is basically like in
676    /// [`Option::or`].
677    fn with_fallback(self, fallback: Self) -> Self;
678
679    /// Returns `true` if all values are unspecified/`None`.
680    fn is_empty(&self) -> bool;
681
682    /// Returns `true` if all required (non-optional) values in this
683    /// configuration are set. If this returns `true`, `Config::from_partial`
684    /// will not return an error.
685    fn is_complete(&self) -> bool;
686}