1pub mod provider;
2mod template;
3
4use std::{borrow::Cow, collections::HashMap, fmt::Debug};
5
6use spin_locked_app::Variable;
7
8pub use async_trait;
9
10pub use provider::Provider;
11use template::Part;
12pub use template::Template;
13
14pub type SharedPreparedResolver =
16 std::sync::Arc<std::sync::OnceLock<std::sync::Arc<PreparedResolver>>>;
17
18#[derive(Debug, Default)]
20pub struct ProviderResolver {
21 internal: Resolver,
22 providers: Vec<Box<dyn Provider>>,
23}
24
25impl ProviderResolver {
26 pub fn new(variables: impl IntoIterator<Item = (String, Variable)>) -> Result<Self> {
28 Ok(Self {
29 internal: Resolver::new(variables)?,
30 providers: Default::default(),
31 })
32 }
33
34 pub fn add_component_variables(
36 &mut self,
37 component_id: impl Into<String>,
38 variables: impl IntoIterator<Item = (String, String)>,
39 ) -> Result<()> {
40 self.internal
41 .add_component_variables(component_id, variables)
42 }
43
44 pub fn add_provider(&mut self, provider: Box<dyn Provider>) {
46 self.providers.push(provider);
47 }
48
49 pub async fn resolve(&self, component_id: &str, key: Key<'_>) -> Result<String> {
51 let template = self.internal.get_template(component_id, key)?;
52 self.resolve_template(template).await
53 }
54
55 pub async fn resolve_all(&self, component_id: &str) -> Result<Vec<(String, String)>> {
57 use futures::FutureExt;
58
59 let Some(keys2templates) = self.internal.component_configs.get(component_id) else {
60 return Ok(vec![]);
61 };
62
63 let resolve_futs = keys2templates.iter().map(|(key, template)| {
64 self.resolve_template(template)
65 .map(|r| r.map(|value| (key.to_string(), value)))
66 });
67
68 futures::future::try_join_all(resolve_futs).await
69 }
70
71 pub async fn resolve_template(&self, template: &Template) -> Result<String> {
73 let mut resolved_parts: Vec<Cow<str>> = Vec::with_capacity(template.parts().len());
74 for part in template.parts() {
75 resolved_parts.push(match part {
76 Part::Lit(lit) => lit.as_ref().into(),
77 Part::Expr(var) => self.resolve_variable(var).await?.into(),
78 });
79 }
80 Ok(resolved_parts.concat())
81 }
82
83 pub async fn prepare(&self) -> Result<PreparedResolver> {
85 let mut variables = HashMap::new();
86 for name in self.internal.variables.keys() {
87 let value = self.resolve_variable(name).await?;
88 variables.insert(name.clone(), value);
89 }
90 Ok(PreparedResolver { variables })
91 }
92
93 async fn resolve_variable(&self, key: &str) -> Result<String> {
94 for provider in &self.providers {
95 if let Some(value) = provider.get(&Key(key)).await.map_err(Error::Provider)? {
96 return Ok(value);
97 }
98 }
99 self.internal.resolve_variable(key)
100 }
101}
102
103#[derive(Debug, Default)]
105pub struct Resolver {
106 variables: HashMap<String, Variable>,
108 component_configs: HashMap<String, HashMap<String, Template>>,
110}
111
112impl Resolver {
113 pub fn new(variables: impl IntoIterator<Item = (String, Variable)>) -> Result<Self> {
115 let variables: HashMap<_, _> = variables.into_iter().collect();
116 variables.keys().try_for_each(|key| Key::validate(key))?;
118 Ok(Self {
119 variables,
120 component_configs: Default::default(),
121 })
122 }
123
124 pub fn add_component_variables(
126 &mut self,
127 component_id: impl Into<String>,
128 variables: impl IntoIterator<Item = (String, String)>,
129 ) -> Result<()> {
130 let component_id = component_id.into();
131 let templates = variables
132 .into_iter()
133 .map(|(key, val)| {
134 Key::validate(&key)?;
136 let template = self.validate_template(val)?;
137 Ok((key, template))
138 })
139 .collect::<Result<_>>()?;
140
141 self.component_configs.insert(component_id, templates);
142
143 Ok(())
144 }
145
146 pub fn resolve(&self, component_id: &str, key: Key<'_>) -> Result<String> {
148 let template = self.get_template(component_id, key)?;
149 self.resolve_template(template)
150 }
151
152 fn resolve_template(&self, template: &Template) -> Result<String> {
154 let mut resolved_parts: Vec<Cow<str>> = Vec::with_capacity(template.parts().len());
155 for part in template.parts() {
156 resolved_parts.push(match part {
157 Part::Lit(lit) => lit.as_ref().into(),
158 Part::Expr(var) => self.resolve_variable(var)?.into(),
159 });
160 }
161 Ok(resolved_parts.concat())
162 }
163
164 fn get_template(&self, component_id: &str, key: Key<'_>) -> Result<&Template> {
166 let configs = self.component_configs.get(component_id).ok_or_else(|| {
167 Error::Undefined(format!("no variable for component {component_id:?}"))
168 })?;
169 let key = key.as_ref();
170 let template = configs
171 .get(key)
172 .ok_or_else(|| Error::Undefined(format!("no variable for {component_id:?}.{key:?}")))?;
173 Ok(template)
174 }
175
176 fn resolve_variable(&self, key: &str) -> Result<String> {
177 let var = self
178 .variables
179 .get(key)
180 .ok_or_else(|| Error::InvalidName(key.to_string()))?;
182
183 var.default.clone().ok_or_else(|| {
184 Error::Provider(anyhow::anyhow!(
185 "no provider resolved required variable {key:?}"
186 ))
187 })
188 }
189
190 fn validate_template(&self, template: String) -> Result<Template> {
191 let template = Template::new(template)?;
192 template.parts().try_for_each(|part| match part {
194 Part::Expr(var) if !self.variables.contains_key(var.as_ref()) => {
195 Err(Error::InvalidTemplate(format!("unknown variable {var:?}")))
196 }
197 _ => Ok(()),
198 })?;
199 Ok(template)
200 }
201}
202
203#[derive(Default)]
205pub struct PreparedResolver {
206 variables: HashMap<String, String>,
207}
208
209impl PreparedResolver {
210 pub fn resolve_template(&self, template: &Template) -> Result<String> {
212 let mut resolved_parts: Vec<Cow<str>> = Vec::with_capacity(template.parts().len());
213 for part in template.parts() {
214 resolved_parts.push(match part {
215 Part::Lit(lit) => lit.as_ref().into(),
216 Part::Expr(var) => self.resolve_variable(var)?.into(),
217 });
218 }
219 Ok(resolved_parts.concat())
220 }
221
222 fn resolve_variable(&self, key: &str) -> Result<String> {
223 self.variables
224 .get(key)
225 .cloned()
226 .ok_or(Error::InvalidName(key.to_string()))
227 }
228}
229
230#[derive(Debug, PartialEq, Eq)]
232pub struct Key<'a>(&'a str);
233
234impl<'a> Key<'a> {
235 pub fn new(key: &'a str) -> Result<Self> {
237 Self::validate(key)?;
238 Ok(Self(key))
239 }
240
241 pub fn as_str(&self) -> &str {
242 self.0
243 }
244
245 fn validate(key: &str) -> Result<()> {
250 {
251 if key.is_empty() {
252 Err("must not be empty".to_string())
253 } else if let Some(invalid) = key
254 .chars()
255 .find(|c| !(c.is_ascii_lowercase() || c.is_ascii_digit() || c == &'_'))
256 {
257 Err(format!("invalid character {:?}. Variable names may contain only lower-case letters, numbers, and underscores.", invalid))
258 } else if !key.bytes().next().unwrap().is_ascii_lowercase() {
259 Err("must start with a lowercase ASCII letter".to_string())
260 } else if !key.bytes().last().unwrap().is_ascii_alphanumeric() {
261 Err("must end with a lowercase ASCII letter or digit".to_string())
262 } else if key.contains("__") {
263 Err("must not contain multiple consecutive underscores".to_string())
264 } else {
265 Ok(())
266 }
267 }
268 .map_err(|reason| Error::InvalidName(format!("{key:?}: {reason}")))
269 }
270}
271
272impl<'a> TryFrom<&'a str> for Key<'a> {
273 type Error = Error;
274
275 fn try_from(value: &'a str) -> std::prelude::v1::Result<Self, Self::Error> {
276 Self::new(value)
277 }
278}
279
280impl AsRef<str> for Key<'_> {
281 fn as_ref(&self) -> &str {
282 self.0
283 }
284}
285
286pub type Result<T> = std::result::Result<T, Error>;
287
288#[derive(Debug, thiserror::Error)]
290pub enum Error {
291 #[error("invalid variable name: {0}")]
293 InvalidName(String),
294
295 #[error("invalid variable template: {0}")]
297 InvalidTemplate(String),
298
299 #[error("provider error: {0:?}")]
301 Provider(#[source] anyhow::Error),
302
303 #[error("undefined variable: {0}")]
305 Undefined(String),
306}
307
308#[cfg(test)]
309mod tests {
310 use async_trait::async_trait;
311
312 use super::*;
313
314 #[derive(Debug)]
315 struct TestProvider;
316
317 #[async_trait]
318 impl Provider for TestProvider {
319 async fn get(&self, key: &Key) -> anyhow::Result<Option<String>> {
320 match key.as_ref() {
321 "required" => Ok(Some("provider-value".to_string())),
322 "broken" => anyhow::bail!("broken"),
323 _ => Ok(None),
324 }
325 }
326 }
327
328 async fn test_resolve(template: &str) -> Result<String> {
329 let mut resolver = ProviderResolver::new([
330 (
331 "required".into(),
332 Variable {
333 default: None,
334 secret: false,
335 },
336 ),
337 (
338 "default".into(),
339 Variable {
340 default: Some("default-value".into()),
341 secret: false,
342 },
343 ),
344 ])
345 .unwrap();
346 resolver
347 .add_component_variables("test-component", [("test_key".into(), template.into())])
348 .unwrap();
349 resolver.add_provider(Box::new(TestProvider));
350 resolver.resolve("test-component", Key("test_key")).await
351 }
352
353 #[tokio::test]
354 async fn resolve_static() {
355 assert_eq!(test_resolve("static-value").await.unwrap(), "static-value");
356 }
357
358 #[tokio::test]
359 async fn resolve_variable_default() {
360 assert_eq!(
361 test_resolve("prefix-{{ default }}-suffix").await.unwrap(),
362 "prefix-default-value-suffix"
363 );
364 }
365
366 #[tokio::test]
367 async fn resolve_variable_provider() {
368 assert_eq!(
369 test_resolve("prefix-{{ required }}-suffix").await.unwrap(),
370 "prefix-provider-value-suffix"
371 );
372 }
373
374 #[test]
375 fn keys_good() {
376 for key in ["a", "abc", "a1b2c3", "a_1", "a_1_b_3"] {
377 Key::new(key).expect(key);
378 }
379 }
380
381 #[test]
382 fn keys_bad() {
383 for key in ["", "aX", "1bc", "_x", "x.y", "x_", "a__b", "x-y"] {
384 Key::new(key).expect_err(key);
385 }
386 }
387
388 #[test]
389 fn template_literal() {
390 assert!(Template::new("hello").unwrap().is_literal());
391 assert!(!Template::new("hello {{ world }}").unwrap().is_literal());
392 }
393}