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 SyncResolver for Resolver {
136 fn resolve_variable(&self, key: &str) -> Result<String> {
137 let var = self
138 .variables
139 .get(key)
140 .ok_or_else(|| Error::InvalidName(key.to_string()))?;
142
143 var.default.clone().ok_or_else(|| {
144 Error::Provider(anyhow::anyhow!(
145 "no provider resolved required variable {key:?}"
146 ))
147 })
148 }
149}
150
151impl Resolver {
152 pub fn new(variables: impl IntoIterator<Item = (String, Variable)>) -> Result<Self> {
154 let variables: HashMap<_, _> = variables.into_iter().collect();
155 variables.keys().try_for_each(|key| Key::validate(key))?;
157 Ok(Self {
158 variables,
159 component_configs: Default::default(),
160 })
161 }
162
163 pub fn add_component_variables(
165 &mut self,
166 component_id: impl Into<String>,
167 variables: impl IntoIterator<Item = (String, String)>,
168 ) -> Result<()> {
169 let component_id = component_id.into();
170 let templates = variables
171 .into_iter()
172 .map(|(key, val)| {
173 Key::validate(&key)?;
175 let template = self.validate_template(val)?;
176 Ok((key, template))
177 })
178 .collect::<Result<_>>()?;
179
180 self.component_configs.insert(component_id, templates);
181
182 Ok(())
183 }
184
185 pub fn resolve(&self, component_id: &str, key: Key<'_>) -> Result<String> {
187 let template = self.get_template(component_id, key)?;
188 self.resolve_template(template)
189 }
190
191 pub fn resolve_template(&self, template: &Template) -> Result<String> {
193 let mut resolved_parts: Vec<Cow<str>> = Vec::with_capacity(template.parts().len());
194 for part in template.parts() {
195 resolved_parts.push(match part {
196 Part::Lit(lit) => lit.as_ref().into(),
197 Part::Expr(var) => self.resolve_variable(var)?.into(),
198 });
199 }
200 Ok(resolved_parts.concat())
201 }
202
203 fn get_template(&self, component_id: &str, key: Key<'_>) -> Result<&Template> {
205 let configs = self.component_configs.get(component_id).ok_or_else(|| {
206 Error::Undefined(format!("no variable for component {component_id:?}"))
207 })?;
208 let key = key.as_ref();
209 let template = configs
210 .get(key)
211 .ok_or_else(|| Error::Undefined(format!("no variable for {component_id:?}.{key:?}")))?;
212 Ok(template)
213 }
214
215 fn validate_template(&self, template: String) -> Result<Template> {
216 let template = Template::new(template)?;
217 template.parts().try_for_each(|part| match part {
219 Part::Expr(var) if !self.variables.contains_key(var.as_ref()) => {
220 Err(Error::InvalidTemplate(format!("unknown variable {var:?}")))
221 }
222 _ => Ok(()),
223 })?;
224 Ok(template)
225 }
226
227 fn required_variables(&self) -> impl Iterator<Item = &str> {
228 self.variables
229 .iter()
230 .filter_map(|(name, variable)| variable.default.is_none().then_some(name.as_str()))
231 }
232}
233
234pub trait SyncResolver {
240 fn resolve_template(&self, template: &Template) -> Result<String> {
244 let mut resolved_parts: Vec<Cow<str>> = Vec::with_capacity(template.parts().len());
245 for part in template.parts() {
246 resolved_parts.push(match part {
247 Part::Lit(lit) => lit.as_ref().into(),
248 Part::Expr(var) => self.resolve_variable(var)?.into(),
249 });
250 }
251 Ok(resolved_parts.concat())
252 }
253
254 fn resolve_variable(&self, key: &str) -> Result<String>;
259}
260
261#[derive(Default)]
263pub struct PreparedResolver {
264 variables: HashMap<String, String>,
265}
266
267impl SyncResolver for PreparedResolver {
268 fn resolve_variable(&self, key: &str) -> Result<String> {
269 self.variables
270 .get(key)
271 .cloned()
272 .ok_or(Error::InvalidName(key.to_string()))
273 }
274}
275
276#[derive(Debug, PartialEq, Eq)]
278pub struct Key<'a>(&'a str);
279
280impl<'a> Key<'a> {
281 pub fn new(key: &'a str) -> Result<Self> {
283 Self::validate(key)?;
284 Ok(Self(key))
285 }
286
287 pub fn as_str(&self) -> &str {
288 self.0
289 }
290
291 fn validate(key: &str) -> Result<()> {
296 {
297 if key.is_empty() {
298 Err("must not be empty".to_string())
299 } else if let Some(invalid) = key
300 .chars()
301 .find(|c| !(c.is_ascii_lowercase() || c.is_ascii_digit() || c == &'_'))
302 {
303 Err(format!("invalid character {invalid:?}. Variable names may contain only lower-case letters, numbers, and underscores."))
304 } else if !key.bytes().next().unwrap().is_ascii_lowercase() {
305 Err("must start with a lowercase ASCII letter".to_string())
306 } else if !key.bytes().last().unwrap().is_ascii_alphanumeric() {
307 Err("must end with a lowercase ASCII letter or digit".to_string())
308 } else if key.contains("__") {
309 Err("must not contain multiple consecutive underscores".to_string())
310 } else {
311 Ok(())
312 }
313 }
314 .map_err(|reason| Error::InvalidName(format!("{key:?}: {reason}")))
315 }
316}
317
318impl<'a> TryFrom<&'a str> for Key<'a> {
319 type Error = Error;
320
321 fn try_from(value: &'a str) -> std::prelude::v1::Result<Self, Self::Error> {
322 Self::new(value)
323 }
324}
325
326impl AsRef<str> for Key<'_> {
327 fn as_ref(&self) -> &str {
328 self.0
329 }
330}
331
332pub type Result<T> = std::result::Result<T, Error>;
333
334#[derive(Debug, thiserror::Error)]
336pub enum Error {
337 #[error("invalid variable name: {0}")]
339 InvalidName(String),
340
341 #[error("invalid variable template: {0}")]
343 InvalidTemplate(String),
344
345 #[error("provider error: {0:?}")]
347 Provider(#[source] anyhow::Error),
348
349 #[error("undefined variable: {0}")]
351 Undefined(String),
352}
353
354#[cfg(test)]
355mod tests {
356 use async_trait::async_trait;
357
358 use super::*;
359
360 #[derive(Debug)]
361 struct TestProvider;
362
363 #[async_trait]
364 impl Provider for TestProvider {
365 async fn get(&self, key: &Key) -> anyhow::Result<Option<String>> {
366 match key.as_ref() {
367 "required" => Ok(Some("provider-value".to_string())),
368 "broken" => anyhow::bail!("broken"),
369 _ => Ok(None),
370 }
371 }
372
373 fn may_resolve(&self, key: &Key) -> bool {
374 key.as_ref() == "required"
375 }
376 }
377
378 async fn test_resolve(template: &str) -> Result<String> {
379 let mut resolver = ProviderResolver::new([
380 (
381 "required".into(),
382 Variable {
383 description: None,
384 default: None,
385 secret: false,
386 },
387 ),
388 (
389 "default".into(),
390 Variable {
391 description: None,
392 default: Some("default-value".into()),
393 secret: false,
394 },
395 ),
396 ])
397 .unwrap();
398 resolver
399 .add_component_variables("test-component", [("test_key".into(), template.into())])
400 .unwrap();
401 resolver.add_provider(Box::new(TestProvider));
402 resolver.resolve("test-component", Key("test_key")).await
403 }
404
405 #[tokio::test]
406 async fn resolve_static() {
407 assert_eq!(test_resolve("static-value").await.unwrap(), "static-value");
408 }
409
410 #[tokio::test]
411 async fn resolve_variable_default() {
412 assert_eq!(
413 test_resolve("prefix-{{ default }}-suffix").await.unwrap(),
414 "prefix-default-value-suffix"
415 );
416 }
417
418 #[tokio::test]
419 async fn resolve_variable_provider() {
420 assert_eq!(
421 test_resolve("prefix-{{ required }}-suffix").await.unwrap(),
422 "prefix-provider-value-suffix"
423 );
424 }
425
426 #[test]
427 fn keys_good() {
428 for key in ["a", "abc", "a1b2c3", "a_1", "a_1_b_3"] {
429 Key::new(key).expect(key);
430 }
431 }
432
433 #[test]
434 fn keys_bad() {
435 for key in ["", "aX", "1bc", "_x", "x.y", "x_", "a__b", "x-y"] {
436 Key::new(key).expect_err(key);
437 }
438 }
439
440 #[test]
441 fn template_literal() {
442 assert!(Template::new("hello").unwrap().is_literal());
443 assert!(!Template::new("hello {{ world }}").unwrap().is_literal());
444 }
445}