cfg_rs/source/
mod.rs

1//! Configuration sources module, see the [examples](https://github.com/leptonyu/cfg-rs/tree/main/examples) for general usage information.
2use crate::*;
3
4#[allow(unused_imports)]
5use self::file::FileLoader;
6use std::path::PathBuf;
7
8/// Config key module.
9pub mod key {
10    pub use crate::key::{CacheKey, PartialKey, PartialKeyCollector};
11}
12pub use super::configuration::ManualSource;
13pub use memory::ConfigSourceBuilder;
14
15pub(crate) mod cargo;
16pub(crate) mod environment;
17pub(crate) mod file;
18pub(crate) mod memory;
19#[doc(hidden)]
20#[cfg(feature = "rand")]
21#[cfg_attr(docsrs, doc(cfg(feature = "rand")))]
22pub(crate) mod random;
23
24#[allow(dead_code)]
25#[derive(Debug, FromConfig)]
26pub(crate) struct EnabledOption {
27    #[config(default = true)]
28    pub(crate) enabled: bool,
29}
30
31macro_rules! file_block {
32    ($($nm:ident.$name:literal.$file:literal: $($k:pat)|* => $x:path,)+) => {
33$(
34#[doc(hidden)]
35#[cfg(feature = $name)]
36#[cfg_attr(docsrs, doc(cfg(feature = $name)))]
37pub mod $nm;
38)+
39
40#[derive(Debug, FromConfig)]
41#[config(prefix = "app.sources")]
42pub(crate) struct SourceOption {
43    #[cfg(feature = "rand")]
44    pub(crate) random: EnabledOption,
45    $(
46    #[cfg(feature = $name)]
47    $nm: EnabledOption,
48    )+
49}
50
51#[inline]
52#[allow(unreachable_code, unused_variables, unused_mut)]
53pub(crate) fn register_by_ext(
54    mut config: Configuration,
55    path: PathBuf,
56    required: bool,
57) -> Result<Configuration, ConfigError> {
58    let ext = path
59        .extension()
60        .and_then(|x| x.to_str())
61        .ok_or_else(|| ConfigError::ConfigFileNotSupported(path.clone()))?;
62        match ext {
63            $(
64                #[cfg(feature = $name)]
65                $($k)|* => {
66                    config = config.register_source(<FileLoader<$x>>::new(
67                        path.clone(),
68                        required,
69                        true,
70                    ))?;
71                }
72            )+
73            _ => return Err(ConfigError::ConfigFileNotSupported(path)),
74        }
75    Ok(config)
76}
77
78#[allow(unused_mut, unused_variables)]
79pub(crate) fn register_files(
80    mut config: Configuration,
81    option: &SourceOption,
82    path: PathBuf,
83    has_ext: bool,
84) -> Result<Configuration, ConfigError> {
85    $(
86    #[cfg(feature = $name)]
87    if option.$nm.enabled {
88        config =
89            config.register_source(<FileLoader<$x>>::new(path.clone(), false, has_ext))?;
90    }
91    )+
92    Ok(config)
93}
94
95
96#[cfg(test)]
97mod test {
98    $(
99    #[test]
100    #[cfg(not(feature = $name))]
101    fn $nm() {
102        use super::memory::HashSource;
103        use crate::*;
104
105        let _v: Result<HashSource, ConfigError> = inline_source!($file);
106        match _v {
107          Err(ConfigError::ConfigFileNotSupported(_)) =>{}
108          _ => assert_eq!(true, false),
109        }
110    }
111    )+
112}
113    };
114}
115
116file_block!(
117    toml."toml"."../../app.toml" : "toml" | "tml" => toml::Toml,
118    yaml."yaml"."../../app.yaml" : "yaml" | "yml" => yaml::Yaml,
119    json."json"."../../app.json" : "json" => json::Json,
120    ini."ini"."../../app.ini" : "ini" => ini::Ini,
121);
122
123/// Inline config file in repo, see [Supported File Formats](index.html#supported-file-format).
124#[macro_export]
125macro_rules! inline_source {
126    ($path:literal) => {
127        $crate::inline_source_internal!(
128        $path:
129        toml."toml": "toml" | "tml" => $crate::source::toml::Toml,
130        yaml."yaml": "yaml" | "yml" => $crate::source::yaml::Yaml,
131        json."json": "json" => $crate::source::json::Json,
132        ini."ini": "ini" => $crate::source::ini::Ini,
133        )
134    };
135}
136
137#[doc(hidden)]
138#[macro_export]
139macro_rules! inline_source_internal {
140    ($path:literal: $($nm:ident.$name:literal: $($k:pat)|* => $x:path,)+) => {
141        match $path.rsplit_once(".") {
142            Some((_, ext)) => {
143                let _name = format!("inline:{}", $path);
144                let _content = include_str!($path);
145                match ext {
146                    $(
147                    #[cfg(feature = $name)]
148                    $($k)|*  => $crate::inline_source_config::<$x>(_name, _content),
149                    )+
150                    _ => Err($crate::ConfigError::ConfigFileNotSupported($path.into()))
151                }
152            }
153            _ => Err($crate::ConfigError::ConfigFileNotSupported($path.into()))
154        }
155    };
156}
157
158/// Config source adaptor is an intermediate representation of config source.
159/// It can convert to [`ConfigSource`]. We have toml, yaml and json values implement this trait.
160///
161/// Config source adaptor examples:
162/// * Toml format.
163/// * Yaml format.
164/// * Json format.
165/// * Ini  format.
166/// * ...
167pub trait ConfigSourceAdaptor {
168    /// Convert adaptor to standard config source.
169    fn convert_source(self, builder: &mut ConfigSourceBuilder<'_>) -> Result<(), ConfigError>;
170}
171
172/// Parse config source from string.
173pub trait ConfigSourceParser: Send {
174    /// Config source adaptor.
175    type Adaptor: ConfigSourceAdaptor;
176
177    /// Parse config source.
178    fn parse_source(_: &str) -> Result<Self::Adaptor, ConfigError>;
179
180    /// File extenstions.
181    fn file_extensions() -> Vec<&'static str>;
182}
183
184/// Config source.
185///
186/// Config source examples:
187/// * Load from programming.
188/// * Load from environment.
189/// * Load from file.
190/// * Load from network.
191/// * ...
192pub trait ConfigSource: Send {
193    /// Config source name.
194    fn name(&self) -> &str;
195
196    /// Load config source.
197    fn load(&self, builder: &mut ConfigSourceBuilder<'_>) -> Result<(), ConfigError>;
198
199    /// If this config source can be refreshed.
200    fn allow_refresh(&self) -> bool {
201        false
202    }
203
204    /// Check if config source is refreshable.
205    ///
206    /// Implementor should notice that everytime this method is called, the refreshable state **must** be reset to **false**.
207    fn refreshable(&self) -> Result<bool, ConfigError> {
208        Ok(false)
209    }
210}