shiny_configuration/
configuration_builder.rs

1use std::collections::{HashMap, HashSet};
2use thiserror::Error;
3
4use crate::Configuration;
5use crate::configuration_provider::ConfigurationProvider;
6use crate::value::Value;
7
8pub struct ConfigurationBuilder {
9    value: Value,
10    providers: HashMap<String, Value>,
11}
12
13impl ConfigurationBuilder {
14    pub(crate) fn new(provider: impl ConfigurationProvider) -> Self {
15        ConfigurationBuilder {
16            value: provider.provide(),
17            providers: HashMap::new(),
18        }
19    }
20
21    pub fn set_provider(&mut self, key: impl ToString, provider: impl ConfigurationProvider) {
22        self.providers.insert(key.to_string(), provider.provide());
23    }
24
25    pub fn with_provider(mut self, key: impl ToString, provider: impl ConfigurationProvider) -> Self {
26        self.set_provider(key, provider);
27        self
28    }
29
30    pub fn build(self) -> Result<Configuration, BuildConfigurationError> {
31        let mut hashset: HashSet<String> = HashSet::new();
32
33        let resolve_ref_ctx = ResolveRefCtx {
34            key: KeyInfo::None,
35            hashset: &mut hashset,
36        };
37
38        Ok(Configuration::new(resolver_refs(
39            self.value,
40            &self.providers,
41            resolve_ref_ctx,
42        )?))
43    }
44}
45
46#[derive(Debug, Error)]
47pub enum BuildConfigurationError {
48    #[error("Circular reference: {}", .refs.join(" -> "))]
49    CircularReference { refs: Vec<String> },
50
51    #[error("Invalid reference [reference = `{0}`]")]
52    InvalidReference(String),
53
54    #[error("Reference does not exists [reference = `{0}`]")]
55    ReferenceDoesNotExists(String),
56
57    #[error("Provider does not exists [provider = `{0}`]")]
58    ProviderDoesNotExists(String),
59}
60
61fn resolver_refs(
62    value: Value,
63    providers: &HashMap<String, Value>,
64    mut ctx: ResolveRefCtx,
65) -> Result<Value, BuildConfigurationError> {
66    Ok(match value {
67        Value::String(string) => {
68            if let Some((provider_key, key)) = is_ref(&string)? {
69                match providers.get(&provider_key) {
70                    None => return Err(BuildConfigurationError::ProviderDoesNotExists(provider_key)),
71                    Some(provider) => match provider.get(&key) {
72                        None => return Err(BuildConfigurationError::ReferenceDoesNotExists(key)),
73                        Some(value) => resolver_refs(
74                            value.clone(),
75                            providers,
76                            ctx.enter_ref(&provider_key, &key)?,
77                        )?,
78                    },
79                }
80            } else {
81                Value::String(string)
82            }
83        }
84        Value::Bool(bool) => Value::Bool(bool),
85        Value::Number(number) => Value::Number(number),
86        Value::None => Value::None,
87        Value::Map(map) => Value::Map(
88            map.into_iter()
89                .map(|(k, v)| {
90                    let value = resolver_refs(v, providers, ctx.enter_key(&k))?;
91                    Ok((k, value))
92                })
93                .collect::<Result<HashMap<_, _>, BuildConfigurationError>>()?,
94        ),
95        Value::Array(array) => Value::Array(
96            array
97                .into_iter()
98                .enumerate()
99                .map(|(i, v)| {
100                    resolver_refs(v, providers, ctx.enter_key(&i.to_string()))
101                })
102                .collect::<Result<Vec<_>, BuildConfigurationError>>()?,
103        ),
104    })
105}
106
107fn is_ref(str: &str) -> Result<Option<(String, String)>, BuildConfigurationError> {
108    let Some(inner) = str.strip_prefix("{{") else { return Ok(None); };
109    let Some(inner) = inner.strip_suffix("}}") else { return Ok(None); };
110
111    let Some((provider, key)) = inner.split_once('.') else {
112        return Err(BuildConfigurationError::ReferenceDoesNotExists(inner.to_string()));
113    };
114
115    Ok(Some((provider.trim().to_string(), key.trim().to_string())))
116}
117
118struct ResolveRefCtx<'a> {
119    key: KeyInfo<'a>,
120    hashset: &'a mut HashSet<String>,
121}
122
123enum KeyInfo<'a> {
124    None,
125    Key {
126        prev: &'a KeyInfo<'a>,
127        key: &'a str,
128    },
129    Ref {
130        prev: &'a KeyInfo<'a>,
131
132        // key of reference: d.b in d.b: "{{ a.b.c }}"
133        source_key: String,
134
135        // provider key: a in d.b: "{{ a.b.c }}"
136        provider_key: &'a str,
137
138        // reference key: b.c in d.b: "{{ a.b.c }}"
139        reference_key: &'a str,
140    },
141}
142
143impl<'a> ResolveRefCtx<'a> {
144    pub fn enter_key<'b>(&'b mut self, key: &'b str) -> ResolveRefCtx<'b> {
145        ResolveRefCtx {
146            key: KeyInfo::Key {
147                prev: &self.key,
148                key,
149            },
150            hashset: self.hashset,
151        }
152    }
153
154    pub fn enter_ref<'b>(
155        &'b mut self,
156        provider_key: &'b str,
157        key: &'b str,
158    ) -> Result<ResolveRefCtx<'b>, BuildConfigurationError> {
159        let mut current_key: Vec<&str> = Vec::new();
160
161        let mut current = &self.key;
162
163        loop {
164            match current {
165                KeyInfo::None => break,
166                KeyInfo::Key {
167                    prev: prev_ref,
168                    key,
169                } => {
170                    current_key.push(key);
171                    current = prev_ref;
172                }
173                KeyInfo::Ref {
174                    provider_key, reference_key: key, ..
175                } => {
176                    current_key.push(provider_key);
177                    current_key.push(".");
178                    current_key.push(key);
179                    break;
180                }
181            }
182        }
183
184        current_key.reverse();
185
186        let current_key = current_key.join(".");
187
188        if !self.hashset.insert(current_key.clone()) {
189            let refs = self.collect_keys();
190            return Err(BuildConfigurationError::CircularReference { refs });
191        };
192
193        Ok(ResolveRefCtx {
194            key: KeyInfo::Ref {
195                prev: &self.key,
196                source_key: current_key,
197                provider_key,
198                reference_key: key,
199            },
200            hashset: self.hashset,
201        })
202    }
203
204    fn collect_keys(&self) -> Vec<String> {
205        let mut out = Vec::new();
206        let mut current: Vec<&str> = Vec::new();
207
208        let mut key_ref = &self.key;
209
210        loop {
211            match key_ref {
212                KeyInfo::None => {
213                    current.reverse();
214                    out.push(current.join("."));
215
216                    out.reverse();
217                    return out;
218                }
219                KeyInfo::Key {
220                    key,
221                    prev: prev_ref,
222                } => {
223                    current.push(key);
224                    key_ref = prev_ref;
225                }
226                KeyInfo::Ref {
227                    prev: prev_ref,
228                    reference_key: key,
229                    provider_key,
230                    ..
231                } => {
232                    current.push(key);
233                    current.push(provider_key);
234                    current.reverse();
235
236                    out.push(current.join("."));
237                    current.clear();
238
239                    key_ref = prev_ref;
240                }
241            }
242        }
243    }
244}
245
246impl<'a> Drop for ResolveRefCtx<'a> {
247    fn drop(&mut self) {
248        if let KeyInfo::Ref { source_key: ref_key, .. } = &self.key {
249            self.hashset.remove(ref_key);
250        }
251    }
252}