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