schematic/config/configs.rs
1use super::error::ConfigError;
2#[cfg(feature = "extends")]
3use super::extender::ExtendsFrom;
4#[cfg(feature = "validate")]
5use super::validator::*;
6use schematic_types::Schematic;
7use serde::{Serialize, de::DeserializeOwned};
8use std::collections::BTreeMap;
9
10/// Represents a partial configuration of the base [`Config`], with all settings marked as optional
11/// by wrapping the values in [`Option`].
12pub trait PartialConfig:
13 Clone + Default + DeserializeOwned + Schematic + Serialize + Sized
14{
15 type Context: Default;
16
17 /// Return a partial configuration with values populated with default values for settings
18 /// marked with `#[setting(default)]`. Unmarked settings will be [`None`].
19 ///
20 /// If a default value fails to parse or cast into the correct type, an error is returned.
21 fn default_values(context: &Self::Context) -> Result<Option<Self>, ConfigError>;
22
23 /// Return a partial configuration with values populated from environment variables
24 /// for settings marked with `#[setting(env)]`. Unmarked settings will be [`None`].
25 ///
26 /// If an environment variable does not exist, the value will be [`None`]. If
27 /// the variable fails to parse or cast into the correct type, an error is returned.
28 #[cfg(feature = "env")]
29 fn env_values() -> Result<Option<Self>, ConfigError>;
30
31 /// When a setting is marked as extendable with `#[setting(extend)]`, this returns
32 /// [`ExtendsFrom`] with the extended sources, either a list of strings or a single string.
33 /// When no setting is extendable, this returns [`None`].
34 #[cfg(feature = "extends")]
35 fn extends_from(&self) -> Option<ExtendsFrom>;
36
37 /// Finalize the partial configuration by consuming it and populating all fields with a value.
38 /// Defaults values from [`PartialConfig::default_values`] will be applied first, followed
39 /// by merging the current partial, and lastly environment variable values from
40 /// [`PartialConfig::env_values`].
41 fn finalize(self, context: &Self::Context) -> Result<Self, ConfigError>;
42
43 /// Merge another partial configuration into this one and clone values when applicable. The
44 /// following merge strategies are applied:
45 ///
46 /// - Current [`None`] values are replaced with the next value if [`Some`].
47 /// - Current [`Some`] values are merged with the next value if [`Some`],
48 /// using the merge function from `#[setting(merge)]`.
49 fn merge(&mut self, context: &Self::Context, next: Self) -> Result<(), ConfigError>;
50
51 /// Recursively validate the configuration with the provided context.
52 /// Validation should be done on the final state, after merging partials.
53 #[cfg(feature = "validate")]
54 fn validate(&self, context: &Self::Context, finalize: bool) -> Result<(), ConfigError> {
55 if let Err(errors) =
56 self.validate_with_path(context, finalize, super::path::Path::default())
57 {
58 return Err(ConfigError::Validator {
59 location: String::new(),
60 error: Box::new(ValidatorError { errors }),
61 help: None,
62 });
63 }
64
65 Ok(())
66 }
67
68 /// Internal use only, use [`validate`] instead.
69 #[cfg(feature = "validate")]
70 #[doc(hidden)]
71 fn validate_with_path(
72 &self,
73 _context: &Self::Context,
74 _finalize: bool,
75 _path: super::path::Path,
76 ) -> Result<(), Vec<ValidateError>> {
77 Ok(())
78 }
79}
80
81/// Represents the final configuration, with all settings populated with a value.
82pub trait Config: Sized + Schematic {
83 type Partial: PartialConfig;
84
85 /// Convert a partial configuration into a full configuration, with all values populated.
86 fn from_partial(partial: Self::Partial) -> Self;
87
88 /// Return a map of all settings and their metadata for the configuration.
89 fn settings() -> ConfigSettingMap {
90 BTreeMap::default()
91 }
92}
93
94/// Represents an enumerable setting for use within a [`Config`].
95pub trait ConfigEnum: Sized + Schematic {
96 /// Return a list of all variants for the enum. Only unit variants are supported.
97 fn variants() -> Vec<Self>;
98}
99
100/// Represents metadata about a setting within a configuration.
101#[derive(Clone, Debug, Default)]
102pub struct ConfigSetting {
103 pub env_key: Option<String>,
104 pub nested: Option<ConfigSettingMap>,
105 pub type_alias: String,
106}
107
108pub type ConfigSettingMap = BTreeMap<String, ConfigSetting>;