Skip to content

Commit b7fb272

Browse files
authored
Merge pull request #2458 from fermyon/non-async-resolver
Refactor expressions `Resolver` to provide sync API
2 parents 19c3017 + c94cd14 commit b7fb272

File tree

2 files changed

+111
-55
lines changed

2 files changed

+111
-55
lines changed

crates/expressions/src/lib.rs

Lines changed: 105 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -9,24 +9,88 @@ pub use provider::Provider;
99
use template::Part;
1010
pub use template::Template;
1111

12+
/// A [`ProviderResolver`] that can be shared.
13+
pub type SharedPreparedResolver =
14+
std::sync::Arc<std::sync::OnceLock<std::sync::Arc<PreparedResolver>>>;
15+
16+
/// A [`Resolver`] which is extended by [`Provider`]s.
17+
#[derive(Debug, Default)]
18+
pub struct ProviderResolver {
19+
internal: Resolver,
20+
providers: Vec<Box<dyn Provider>>,
21+
}
22+
23+
impl ProviderResolver {
24+
/// Creates a Resolver for the given Tree.
25+
pub fn new(variables: impl IntoIterator<Item = (String, Variable)>) -> Result<Self> {
26+
Ok(Self {
27+
internal: Resolver::new(variables)?,
28+
providers: Default::default(),
29+
})
30+
}
31+
32+
/// Adds component variable values to the Resolver.
33+
pub fn add_component_variables(
34+
&mut self,
35+
component_id: impl Into<String>,
36+
variables: impl IntoIterator<Item = (String, String)>,
37+
) -> Result<()> {
38+
self.internal
39+
.add_component_variables(component_id, variables)
40+
}
41+
42+
/// Adds a variable Provider to the Resolver.
43+
pub fn add_provider(&mut self, provider: Box<dyn Provider>) {
44+
self.providers.push(provider);
45+
}
46+
47+
/// Resolves a variable value for the given path.
48+
pub async fn resolve(&self, component_id: &str, key: Key<'_>) -> Result<String> {
49+
let template = self.internal.get_template(component_id, key)?;
50+
self.resolve_template(template).await
51+
}
52+
53+
/// Resolves the given template.
54+
pub async fn resolve_template(&self, template: &Template) -> Result<String> {
55+
let mut resolved_parts: Vec<Cow<str>> = Vec::with_capacity(template.parts().len());
56+
for part in template.parts() {
57+
resolved_parts.push(match part {
58+
Part::Lit(lit) => lit.as_ref().into(),
59+
Part::Expr(var) => self.resolve_variable(var).await?.into(),
60+
});
61+
}
62+
Ok(resolved_parts.concat())
63+
}
64+
65+
/// Fully resolve all variables into a [`PreparedResolver`].
66+
pub async fn prepare(&self) -> Result<PreparedResolver> {
67+
let mut variables = HashMap::new();
68+
for name in self.internal.variables.keys() {
69+
let value = self.resolve_variable(name).await?;
70+
variables.insert(name.clone(), value);
71+
}
72+
Ok(PreparedResolver { variables })
73+
}
74+
75+
async fn resolve_variable(&self, key: &str) -> Result<String> {
76+
for provider in &self.providers {
77+
if let Some(value) = provider.get(&Key(key)).await.map_err(Error::Provider)? {
78+
return Ok(value);
79+
}
80+
}
81+
self.internal.resolve_variable(key)
82+
}
83+
}
84+
1285
/// A variable resolver.
1386
#[derive(Debug, Default)]
1487
pub struct Resolver {
1588
// variable key -> variable
1689
variables: HashMap<String, Variable>,
1790
// component ID -> variable key -> variable value template
1891
component_configs: HashMap<String, HashMap<String, Template>>,
19-
providers: Vec<Box<dyn Provider>>,
20-
}
21-
22-
#[derive(Default)]
23-
pub struct PreparedResolver {
24-
variables: HashMap<String, String>,
2592
}
2693

27-
pub type SharedPreparedResolver =
28-
std::sync::Arc<std::sync::OnceLock<std::sync::Arc<PreparedResolver>>>;
29-
3094
impl Resolver {
3195
/// Creates a Resolver for the given Tree.
3296
pub fn new(variables: impl IntoIterator<Item = (String, Variable)>) -> Result<Self> {
@@ -36,7 +100,6 @@ impl Resolver {
36100
Ok(Self {
37101
variables,
38102
component_configs: Default::default(),
39-
providers: Default::default(),
40103
})
41104
}
42105

@@ -62,58 +125,43 @@ impl Resolver {
62125
Ok(())
63126
}
64127

65-
/// Adds a variable Provider to the Resolver.
66-
pub fn add_provider(&mut self, provider: Box<dyn Provider>) {
67-
self.providers.push(provider);
68-
}
69-
70128
/// Resolves a variable value for the given path.
71-
pub async fn resolve(&self, component_id: &str, key: Key<'_>) -> Result<String> {
72-
let configs = self.component_configs.get(component_id).ok_or_else(|| {
73-
Error::Undefined(format!("no variable for component {component_id:?}"))
74-
})?;
75-
76-
let key = key.as_ref();
77-
let template = configs
78-
.get(key)
79-
.ok_or_else(|| Error::Undefined(format!("no variable for {component_id:?}.{key:?}")))?;
80-
81-
self.resolve_template(template).await
129+
pub fn resolve(&self, component_id: &str, key: Key<'_>) -> Result<String> {
130+
let template = self.get_template(component_id, key)?;
131+
self.resolve_template(template)
82132
}
83133

84-
pub async fn resolve_template(&self, template: &Template) -> Result<String> {
134+
/// Resolves the given template.
135+
fn resolve_template(&self, template: &Template) -> Result<String> {
85136
let mut resolved_parts: Vec<Cow<str>> = Vec::with_capacity(template.parts().len());
86137
for part in template.parts() {
87138
resolved_parts.push(match part {
88139
Part::Lit(lit) => lit.as_ref().into(),
89-
Part::Expr(var) => self.resolve_variable(var).await?.into(),
140+
Part::Expr(var) => self.resolve_variable(var)?.into(),
90141
});
91142
}
92143
Ok(resolved_parts.concat())
93144
}
94145

95-
pub async fn prepare(&self) -> Result<PreparedResolver> {
96-
let mut variables = HashMap::new();
97-
for name in self.variables.keys() {
98-
let value = self.resolve_variable(name).await?;
99-
variables.insert(name.clone(), value);
100-
}
101-
Ok(PreparedResolver { variables })
146+
/// Gets a template for the given path.
147+
fn get_template(&self, component_id: &str, key: Key<'_>) -> Result<&Template> {
148+
let configs = self.component_configs.get(component_id).ok_or_else(|| {
149+
Error::Undefined(format!("no variable for component {component_id:?}"))
150+
})?;
151+
let key = key.as_ref();
152+
let template = configs
153+
.get(key)
154+
.ok_or_else(|| Error::Undefined(format!("no variable for {component_id:?}.{key:?}")))?;
155+
Ok(template)
102156
}
103157

104-
async fn resolve_variable(&self, key: &str) -> Result<String> {
158+
fn resolve_variable(&self, key: &str) -> Result<String> {
105159
let var = self
106160
.variables
107161
.get(key)
108162
// This should have been caught by validate_template
109163
.ok_or_else(|| Error::InvalidName(key.to_string()))?;
110164

111-
for provider in &self.providers {
112-
if let Some(value) = provider.get(&Key(key)).await.map_err(Error::Provider)? {
113-
return Ok(value);
114-
}
115-
}
116-
117165
var.default.clone().ok_or_else(|| {
118166
Error::Provider(anyhow::anyhow!(
119167
"no provider resolved required variable {key:?}"
@@ -134,14 +182,14 @@ impl Resolver {
134182
}
135183
}
136184

137-
impl PreparedResolver {
138-
fn resolve_variable(&self, key: &str) -> Result<String> {
139-
self.variables
140-
.get(key)
141-
.cloned()
142-
.ok_or(Error::InvalidName(key.to_string()))
143-
}
185+
/// A resolver who has resolved all variables.
186+
#[derive(Default)]
187+
pub struct PreparedResolver {
188+
variables: HashMap<String, String>,
189+
}
144190

191+
impl PreparedResolver {
192+
/// Resolves a the given template.
145193
pub fn resolve_template(&self, template: &Template) -> Result<String> {
146194
let mut resolved_parts: Vec<Cow<str>> = Vec::with_capacity(template.parts().len());
147195
for part in template.parts() {
@@ -152,6 +200,13 @@ impl PreparedResolver {
152200
}
153201
Ok(resolved_parts.concat())
154202
}
203+
204+
fn resolve_variable(&self, key: &str) -> Result<String> {
205+
self.variables
206+
.get(key)
207+
.cloned()
208+
.ok_or(Error::InvalidName(key.to_string()))
209+
}
155210
}
156211

157212
/// A variable key
@@ -245,7 +300,7 @@ mod tests {
245300
}
246301

247302
async fn test_resolve(template: &str) -> Result<String> {
248-
let mut resolver = Resolver::new([
303+
let mut resolver = ProviderResolver::new([
249304
(
250305
"required".into(),
251306
Variable {

crates/variables/src/host_component.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ use spin_core::{async_trait, HostComponent};
77
use spin_world::v1::config::Error as V1ConfigError;
88
use spin_world::v2::variables;
99

10-
use spin_expressions::{Error, Key, Provider, Resolver};
10+
use spin_expressions::{Error, Key, Provider, ProviderResolver};
1111

1212
pub struct VariablesHostComponent {
1313
providers: Mutex<Vec<Box<dyn Provider>>>,
14-
resolver: Arc<OnceCell<Resolver>>,
14+
resolver: Arc<OnceCell<ProviderResolver>>,
1515
}
1616

1717
impl VariablesHostComponent {
@@ -55,8 +55,9 @@ impl DynamicHostComponent for VariablesHostComponent {
5555
pub fn make_resolver(
5656
app: &spin_app::App,
5757
providers: impl IntoIterator<Item = Box<dyn Provider>>,
58-
) -> anyhow::Result<Resolver> {
59-
let mut resolver = Resolver::new(app.variables().map(|(key, var)| (key.clone(), var.clone())))?;
58+
) -> anyhow::Result<ProviderResolver> {
59+
let mut resolver =
60+
ProviderResolver::new(app.variables().map(|(key, var)| (key.clone(), var.clone())))?;
6061
for component in app.components() {
6162
resolver.add_component_variables(
6263
component.id(),
@@ -71,7 +72,7 @@ pub fn make_resolver(
7172

7273
/// A component variables interface implementation.
7374
pub struct ComponentVariables {
74-
resolver: Arc<OnceCell<Resolver>>,
75+
resolver: Arc<OnceCell<ProviderResolver>>,
7576
component_id: Option<String>,
7677
}
7778

0 commit comments

Comments
 (0)