Skip to content

Commit

Permalink
Merge pull request #2458 from fermyon/non-async-resolver
Browse files Browse the repository at this point in the history
Refactor expressions `Resolver` to provide sync API
  • Loading branch information
rylev authored Apr 24, 2024
2 parents 19c3017 + c94cd14 commit b7fb272
Show file tree
Hide file tree
Showing 2 changed files with 111 additions and 55 deletions.
155 changes: 105 additions & 50 deletions crates/expressions/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,88 @@ pub use provider::Provider;
use template::Part;
pub use template::Template;

/// A [`ProviderResolver`] that can be shared.
pub type SharedPreparedResolver =
std::sync::Arc<std::sync::OnceLock<std::sync::Arc<PreparedResolver>>>;

/// A [`Resolver`] which is extended by [`Provider`]s.
#[derive(Debug, Default)]
pub struct ProviderResolver {
internal: Resolver,
providers: Vec<Box<dyn Provider>>,
}

impl ProviderResolver {
/// Creates a Resolver for the given Tree.
pub fn new(variables: impl IntoIterator<Item = (String, Variable)>) -> Result<Self> {
Ok(Self {
internal: Resolver::new(variables)?,
providers: Default::default(),
})
}

/// Adds component variable values to the Resolver.
pub fn add_component_variables(
&mut self,
component_id: impl Into<String>,
variables: impl IntoIterator<Item = (String, String)>,
) -> Result<()> {
self.internal
.add_component_variables(component_id, variables)
}

/// Adds a variable Provider to the Resolver.
pub fn add_provider(&mut self, provider: Box<dyn Provider>) {
self.providers.push(provider);
}

/// Resolves a variable value for the given path.
pub async fn resolve(&self, component_id: &str, key: Key<'_>) -> Result<String> {
let template = self.internal.get_template(component_id, key)?;
self.resolve_template(template).await
}

/// Resolves the given template.
pub async fn resolve_template(&self, template: &Template) -> Result<String> {
let mut resolved_parts: Vec<Cow<str>> = Vec::with_capacity(template.parts().len());
for part in template.parts() {
resolved_parts.push(match part {
Part::Lit(lit) => lit.as_ref().into(),
Part::Expr(var) => self.resolve_variable(var).await?.into(),
});
}
Ok(resolved_parts.concat())
}

/// Fully resolve all variables into a [`PreparedResolver`].
pub async fn prepare(&self) -> Result<PreparedResolver> {
let mut variables = HashMap::new();
for name in self.internal.variables.keys() {
let value = self.resolve_variable(name).await?;
variables.insert(name.clone(), value);
}
Ok(PreparedResolver { variables })
}

async fn resolve_variable(&self, key: &str) -> Result<String> {
for provider in &self.providers {
if let Some(value) = provider.get(&Key(key)).await.map_err(Error::Provider)? {
return Ok(value);
}
}
self.internal.resolve_variable(key)
}
}

/// A variable resolver.
#[derive(Debug, Default)]
pub struct Resolver {
// variable key -> variable
variables: HashMap<String, Variable>,
// component ID -> variable key -> variable value template
component_configs: HashMap<String, HashMap<String, Template>>,
providers: Vec<Box<dyn Provider>>,
}

#[derive(Default)]
pub struct PreparedResolver {
variables: HashMap<String, String>,
}

pub type SharedPreparedResolver =
std::sync::Arc<std::sync::OnceLock<std::sync::Arc<PreparedResolver>>>;

impl Resolver {
/// Creates a Resolver for the given Tree.
pub fn new(variables: impl IntoIterator<Item = (String, Variable)>) -> Result<Self> {
Expand All @@ -36,7 +100,6 @@ impl Resolver {
Ok(Self {
variables,
component_configs: Default::default(),
providers: Default::default(),
})
}

Expand All @@ -62,58 +125,43 @@ impl Resolver {
Ok(())
}

/// Adds a variable Provider to the Resolver.
pub fn add_provider(&mut self, provider: Box<dyn Provider>) {
self.providers.push(provider);
}

/// Resolves a variable value for the given path.
pub async fn resolve(&self, component_id: &str, key: Key<'_>) -> Result<String> {
let configs = self.component_configs.get(component_id).ok_or_else(|| {
Error::Undefined(format!("no variable for component {component_id:?}"))
})?;

let key = key.as_ref();
let template = configs
.get(key)
.ok_or_else(|| Error::Undefined(format!("no variable for {component_id:?}.{key:?}")))?;

self.resolve_template(template).await
pub fn resolve(&self, component_id: &str, key: Key<'_>) -> Result<String> {
let template = self.get_template(component_id, key)?;
self.resolve_template(template)
}

pub async fn resolve_template(&self, template: &Template) -> Result<String> {
/// Resolves the given template.
fn resolve_template(&self, template: &Template) -> Result<String> {
let mut resolved_parts: Vec<Cow<str>> = Vec::with_capacity(template.parts().len());
for part in template.parts() {
resolved_parts.push(match part {
Part::Lit(lit) => lit.as_ref().into(),
Part::Expr(var) => self.resolve_variable(var).await?.into(),
Part::Expr(var) => self.resolve_variable(var)?.into(),
});
}
Ok(resolved_parts.concat())
}

pub async fn prepare(&self) -> Result<PreparedResolver> {
let mut variables = HashMap::new();
for name in self.variables.keys() {
let value = self.resolve_variable(name).await?;
variables.insert(name.clone(), value);
}
Ok(PreparedResolver { variables })
/// Gets a template for the given path.
fn get_template(&self, component_id: &str, key: Key<'_>) -> Result<&Template> {
let configs = self.component_configs.get(component_id).ok_or_else(|| {
Error::Undefined(format!("no variable for component {component_id:?}"))
})?;
let key = key.as_ref();
let template = configs
.get(key)
.ok_or_else(|| Error::Undefined(format!("no variable for {component_id:?}.{key:?}")))?;
Ok(template)
}

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

for provider in &self.providers {
if let Some(value) = provider.get(&Key(key)).await.map_err(Error::Provider)? {
return Ok(value);
}
}

var.default.clone().ok_or_else(|| {
Error::Provider(anyhow::anyhow!(
"no provider resolved required variable {key:?}"
Expand All @@ -134,14 +182,14 @@ impl Resolver {
}
}

impl PreparedResolver {
fn resolve_variable(&self, key: &str) -> Result<String> {
self.variables
.get(key)
.cloned()
.ok_or(Error::InvalidName(key.to_string()))
}
/// A resolver who has resolved all variables.
#[derive(Default)]
pub struct PreparedResolver {
variables: HashMap<String, String>,
}

impl PreparedResolver {
/// Resolves a the given template.
pub fn resolve_template(&self, template: &Template) -> Result<String> {
let mut resolved_parts: Vec<Cow<str>> = Vec::with_capacity(template.parts().len());
for part in template.parts() {
Expand All @@ -152,6 +200,13 @@ impl PreparedResolver {
}
Ok(resolved_parts.concat())
}

fn resolve_variable(&self, key: &str) -> Result<String> {
self.variables
.get(key)
.cloned()
.ok_or(Error::InvalidName(key.to_string()))
}
}

/// A variable key
Expand Down Expand Up @@ -245,7 +300,7 @@ mod tests {
}

async fn test_resolve(template: &str) -> Result<String> {
let mut resolver = Resolver::new([
let mut resolver = ProviderResolver::new([
(
"required".into(),
Variable {
Expand Down
11 changes: 6 additions & 5 deletions crates/variables/src/host_component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ use spin_core::{async_trait, HostComponent};
use spin_world::v1::config::Error as V1ConfigError;
use spin_world::v2::variables;

use spin_expressions::{Error, Key, Provider, Resolver};
use spin_expressions::{Error, Key, Provider, ProviderResolver};

pub struct VariablesHostComponent {
providers: Mutex<Vec<Box<dyn Provider>>>,
resolver: Arc<OnceCell<Resolver>>,
resolver: Arc<OnceCell<ProviderResolver>>,
}

impl VariablesHostComponent {
Expand Down Expand Up @@ -55,8 +55,9 @@ impl DynamicHostComponent for VariablesHostComponent {
pub fn make_resolver(
app: &spin_app::App,
providers: impl IntoIterator<Item = Box<dyn Provider>>,
) -> anyhow::Result<Resolver> {
let mut resolver = Resolver::new(app.variables().map(|(key, var)| (key.clone(), var.clone())))?;
) -> anyhow::Result<ProviderResolver> {
let mut resolver =
ProviderResolver::new(app.variables().map(|(key, var)| (key.clone(), var.clone())))?;
for component in app.components() {
resolver.add_component_variables(
component.id(),
Expand All @@ -71,7 +72,7 @@ pub fn make_resolver(

/// A component variables interface implementation.
pub struct ComponentVariables {
resolver: Arc<OnceCell<Resolver>>,
resolver: Arc<OnceCell<ProviderResolver>>,
component_id: Option<String>,
}

Expand Down

0 comments on commit b7fb272

Please sign in to comment.