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 source_key: String,
134
135 provider_key: &'a str,
137
138 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}