1pub mod provider;
2mod template;
3
4use std::{borrow::Cow, collections::HashMap, fmt::Debug, vec};
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 pub fn ensure_required_variables_resolvable(&self) -> Result<()> {
95 let mut unresolvable_keys = vec![];
96 for key in self.internal.required_variables() {
97 let key = Key::new(key)?;
98 let resolvable = self
99 .providers
100 .iter()
101 .any(|provider| provider.may_resolve(&key));
102 if !resolvable {
103 unresolvable_keys.push(key);
104 }
105 }
106
107 if unresolvable_keys.is_empty() {
108 Ok(())
109 } else {
110 Err(Error::Provider(anyhow::anyhow!(
111 "no provider resolved required variable(s): {unresolvable_keys:?}",
112 )))
113 }
114 }
115
116 async fn resolve_variable(&self, key: &str) -> Result<String> {
117 for provider in &self.providers {
118 if let Some(value) = provider.get(&Key(key)).await.map_err(Error::Provider)? {
119 return Ok(value);
120 }
121 }
122 self.internal.resolve_variable(key)
123 }
124}
125
126#[derive(Debug, Default)]
128pub struct Resolver {
129 variables: HashMap<String, Variable>,
131 component_configs: HashMap<String, HashMap<String, Template>>,
133}
134
135impl Resolver {
136 pub fn new(variables: impl IntoIterator<Item = (String, Variable)>) -> Result<Self> {
138 let variables: HashMap<_, _> = variables.into_iter().collect();
139 variables.keys().try_for_each(|key| Key::validate(key))?;
141 Ok(Self {
142 variables,
143 component_configs: Default::default(),
144 })
145 }
146
147 pub fn add_component_variables(
149 &mut self,
150 component_id: impl Into<String>,
151 variables: impl IntoIterator<Item = (String, String)>,
152 ) -> Result<()> {
153 let component_id = component_id.into();
154 let templates = variables
155 .into_iter()
156 .map(|(key, val)| {
157 Key::validate(&key)?;
159 let template = self.validate_template(val)?;
160 Ok((key, template))
161 })
162 .collect::<Result<_>>()?;
163
164 self.component_configs.insert(component_id, templates);
165
166 Ok(())
167 }
168
169 pub fn resolve(&self, component_id: &str, key: Key<'_>) -> Result<String> {
171 let template = self.get_template(component_id, key)?;
172 self.resolve_template(template)
173 }
174
175 pub fn resolve_template(&self, template: &Template) -> Result<String> {
177 let mut resolved_parts: Vec<Cow<str>> = Vec::with_capacity(template.parts().len());
178 for part in template.parts() {
179 resolved_parts.push(match part {
180 Part::Lit(lit) => lit.as_ref().into(),
181 Part::Expr(var) => self.resolve_variable(var)?.into(),
182 });
183 }
184 Ok(resolved_parts.concat())
185 }
186
187 fn get_template(&self, component_id: &str, key: Key<'_>) -> Result<&Template> {
189 let configs = self.component_configs.get(component_id).ok_or_else(|| {
190 Error::Undefined(format!("no variable for component {component_id:?}"))
191 })?;
192 let key = key.as_ref();
193 let template = configs
194 .get(key)
195 .ok_or_else(|| Error::Undefined(format!("no variable for {component_id:?}.{key:?}")))?;
196 Ok(template)
197 }
198
199 fn resolve_variable(&self, key: &str) -> Result<String> {
200 let var = self
201 .variables
202 .get(key)
203 .ok_or_else(|| Error::InvalidName(key.to_string()))?;
205
206 var.default.clone().ok_or_else(|| {
207 Error::Provider(anyhow::anyhow!(
208 "no provider resolved required variable {key:?}"
209 ))
210 })
211 }
212
213 fn validate_template(&self, template: String) -> Result<Template> {
214 let template = Template::new(template)?;
215 template.parts().try_for_each(|part| match part {
217 Part::Expr(var) if !self.variables.contains_key(var.as_ref()) => {
218 Err(Error::InvalidTemplate(format!("unknown variable {var:?}")))
219 }
220 _ => Ok(()),
221 })?;
222 Ok(template)
223 }
224
225 fn required_variables(&self) -> impl Iterator<Item = &str> {
226 self.variables
227 .iter()
228 .filter_map(|(name, variable)| variable.default.is_none().then_some(name.as_str()))
229 }
230}
231
232#[derive(Default)]
234pub struct PreparedResolver {
235 variables: HashMap<String, String>,
236}
237
238impl PreparedResolver {
239 pub fn resolve_template(&self, template: &Template) -> Result<String> {
241 let mut resolved_parts: Vec<Cow<str>> = Vec::with_capacity(template.parts().len());
242 for part in template.parts() {
243 resolved_parts.push(match part {
244 Part::Lit(lit) => lit.as_ref().into(),
245 Part::Expr(var) => self.resolve_variable(var)?.into(),
246 });
247 }
248 Ok(resolved_parts.concat())
249 }
250
251 fn resolve_variable(&self, key: &str) -> Result<String> {
252 self.variables
253 .get(key)
254 .cloned()
255 .ok_or(Error::InvalidName(key.to_string()))
256 }
257}
258
259#[derive(Debug, PartialEq, Eq)]
261pub struct Key<'a>(&'a str);
262
263impl<'a> Key<'a> {
264 pub fn new(key: &'a str) -> Result<Self> {
266 Self::validate(key)?;
267 Ok(Self(key))
268 }
269
270 pub fn as_str(&self) -> &str {
271 self.0
272 }
273
274 fn validate(key: &str) -> Result<()> {
279 {
280 if key.is_empty() {
281 Err("must not be empty".to_string())
282 } else if let Some(invalid) = key
283 .chars()
284 .find(|c| !(c.is_ascii_lowercase() || c.is_ascii_digit() || c == &'_'))
285 {
286 Err(format!("invalid character {invalid:?}. Variable names may contain only lower-case letters, numbers, and underscores."))
287 } else if !key.bytes().next().unwrap().is_ascii_lowercase() {
288 Err("must start with a lowercase ASCII letter".to_string())
289 } else if !key.bytes().last().unwrap().is_ascii_alphanumeric() {
290 Err("must end with a lowercase ASCII letter or digit".to_string())
291 } else if key.contains("__") {
292 Err("must not contain multiple consecutive underscores".to_string())
293 } else {
294 Ok(())
295 }
296 }
297 .map_err(|reason| Error::InvalidName(format!("{key:?}: {reason}")))
298 }
299}
300
301impl<'a> TryFrom<&'a str> for Key<'a> {
302 type Error = Error;
303
304 fn try_from(value: &'a str) -> std::prelude::v1::Result<Self, Self::Error> {
305 Self::new(value)
306 }
307}
308
309impl AsRef<str> for Key<'_> {
310 fn as_ref(&self) -> &str {
311 self.0
312 }
313}
314
315pub type Result<T> = std::result::Result<T, Error>;
316
317#[derive(Debug, thiserror::Error)]
319pub enum Error {
320 #[error("invalid variable name: {0}")]
322 InvalidName(String),
323
324 #[error("invalid variable template: {0}")]
326 InvalidTemplate(String),
327
328 #[error("provider error: {0:?}")]
330 Provider(#[source] anyhow::Error),
331
332 #[error("undefined variable: {0}")]
334 Undefined(String),
335}
336
337#[cfg(test)]
338mod tests {
339 use async_trait::async_trait;
340
341 use super::*;
342
343 #[derive(Debug)]
344 struct TestProvider;
345
346 #[async_trait]
347 impl Provider for TestProvider {
348 async fn get(&self, key: &Key) -> anyhow::Result<Option<String>> {
349 match key.as_ref() {
350 "required" => Ok(Some("provider-value".to_string())),
351 "broken" => anyhow::bail!("broken"),
352 _ => Ok(None),
353 }
354 }
355
356 fn may_resolve(&self, key: &Key) -> bool {
357 key.as_ref() == "required"
358 }
359 }
360
361 async fn test_resolve(template: &str) -> Result<String> {
362 let mut resolver = ProviderResolver::new([
363 (
364 "required".into(),
365 Variable {
366 description: None,
367 default: None,
368 secret: false,
369 },
370 ),
371 (
372 "default".into(),
373 Variable {
374 description: None,
375 default: Some("default-value".into()),
376 secret: false,
377 },
378 ),
379 ])
380 .unwrap();
381 resolver
382 .add_component_variables("test-component", [("test_key".into(), template.into())])
383 .unwrap();
384 resolver.add_provider(Box::new(TestProvider));
385 resolver.resolve("test-component", Key("test_key")).await
386 }
387
388 #[tokio::test]
389 async fn resolve_static() {
390 assert_eq!(test_resolve("static-value").await.unwrap(), "static-value");
391 }
392
393 #[tokio::test]
394 async fn resolve_variable_default() {
395 assert_eq!(
396 test_resolve("prefix-{{ default }}-suffix").await.unwrap(),
397 "prefix-default-value-suffix"
398 );
399 }
400
401 #[tokio::test]
402 async fn resolve_variable_provider() {
403 assert_eq!(
404 test_resolve("prefix-{{ required }}-suffix").await.unwrap(),
405 "prefix-provider-value-suffix"
406 );
407 }
408
409 #[test]
410 fn keys_good() {
411 for key in ["a", "abc", "a1b2c3", "a_1", "a_1_b_3"] {
412 Key::new(key).expect(key);
413 }
414 }
415
416 #[test]
417 fn keys_bad() {
418 for key in ["", "aX", "1bc", "_x", "x.y", "x_", "a__b", "x-y"] {
419 Key::new(key).expect_err(key);
420 }
421 }
422
423 #[test]
424 fn template_literal() {
425 assert!(Template::new("hello").unwrap().is_literal());
426 assert!(!Template::new("hello {{ world }}").unwrap().is_literal());
427 }
428}