Skip to content

Commit

Permalink
Build inline components
Browse files Browse the repository at this point in the history
Signed-off-by: itowlson <[email protected]>
  • Loading branch information
itowlson committed Mar 24, 2024
1 parent a400e3f commit e8db5ac
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 1 deletion.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/build/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ edition = { workspace = true }
[dependencies]
anyhow = "1.0.57"
futures = "0.3.21"
indexmap = { version = "1", features = ["serde"] }
serde = { version = "1.0", features = [ "derive" ] }
spin-common = { path = "../common" }
spin-manifest = { path = "../manifest" }
Expand Down
13 changes: 13 additions & 0 deletions crates/build/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,4 +138,17 @@ mod tests {
let bad_trigger_file = test_data_root().join("bad_trigger.toml");
build(&bad_trigger_file, &[]).await.unwrap();
}

#[tokio::test]
async fn can_load_inline_components() -> anyhow::Result<()> {
let inline_comp_file = test_data_root().join("inline.toml");
let build_configs = component_build_configs(&inline_comp_file).await?;
assert_eq!(1, build_configs.len());
assert!(build_configs[0].build.is_some());
assert_eq!(
"echo done",
build_configs[0].build.as_ref().unwrap().command
);
Ok(())
}
}
90 changes: 89 additions & 1 deletion crates/build/src/manifest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,87 @@ pub async fn component_build_configs(
}
ManifestVersion::V2 => {
let v2: ManifestV2BuildInfo = toml::from_str(&manifest_text)?;
let inlines = v2.triggers.values().flat_map(|triggers| {
triggers
.iter()
.flat_map(|tr| tr.component_specs())
.filter_map(|spec| spec.buildinfo())
});
v2.components
.into_iter()
.map(|(id, mut c)| {
c.id = id;
c
})
.chain(inlines)
.collect()
}
})
}

#[derive(Deserialize)]
pub struct TriggerBuildInfo {
#[serde(default, skip_serializing_if = "String::is_empty")]
pub id: String,
/// `component = ...`
#[serde(default, skip_serializing_if = "Option::is_none")]
pub component: Option<ComponentSpec>,
/// `components = { ... }`
#[serde(default, skip_serializing_if = "Map::is_empty")]
pub components: indexmap::IndexMap<String, OneOrManyComponentSpecs>,
}

impl TriggerBuildInfo {
fn component_specs(&self) -> Vec<&ComponentSpec> {
match &self.component {
Some(spec) => vec![spec],
None => self
.components
.values()
.flat_map(|specs| &specs.0)
.collect(),
}
}
}

/// One or many `ComponentSpec`(s)
#[derive(Deserialize)]
#[serde(transparent)]
pub struct OneOrManyComponentSpecs(#[serde(with = "one_or_many")] pub Vec<ComponentSpec>);

/// Component reference or inline definition
#[derive(Deserialize)]
#[serde(untagged, try_from = "toml::Value")]
pub enum ComponentSpec {
/// `"component-id"`
Reference(String),
/// `{ ... }`
Inline(Box<ComponentBuildInfo>),
}

impl ComponentSpec {
fn buildinfo(&self) -> Option<ComponentBuildInfo> {
match self {
Self::Reference(_) => None, // Will be picked up from `components` section
Self::Inline(cbi) => Some(*cbi.clone()),
}
}
}

impl TryFrom<toml::Value> for ComponentSpec {
type Error = toml::de::Error;

fn try_from(value: toml::Value) -> Result<Self, Self::Error> {
match value.as_str() {
Some(s) => Ok(ComponentSpec::Reference(s.to_string())),
None => Ok(ComponentSpec::Inline(Box::new(
ComponentBuildInfo::deserialize(value)?,
))),
}
}
}

#[derive(Clone, Deserialize)]
pub struct ComponentBuildInfo {
#[serde(default)]
pub id: String,
Expand All @@ -43,6 +112,25 @@ struct ManifestV1BuildInfo {

#[derive(Deserialize)]
struct ManifestV2BuildInfo {
#[serde(rename = "component")]
#[serde(rename = "trigger")]
pub triggers: indexmap::IndexMap<String, Vec<TriggerBuildInfo>>,
#[serde(default, rename = "component")]
components: BTreeMap<String, ComponentBuildInfo>,
}

mod one_or_many {
use serde::{Deserialize, Deserializer};

pub fn deserialize<'de, T, D>(deserializer: D) -> Result<Vec<T>, D::Error>
where
T: Deserialize<'de>,
D: Deserializer<'de>,
{
let value = toml::Value::deserialize(deserializer)?;
if let Ok(val) = T::deserialize(value.clone()) {
Ok(vec![val])
} else {
Vec::deserialize(value).map_err(serde::de::Error::custom)
}
}
}
8 changes: 8 additions & 0 deletions crates/build/tests/inline.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
spin_manifest_version = 2

[application]
name = "inline"

[[trigger.http]]
route = "/..."
component = { source = "test.wasm", build = { command = "echo done" } }

0 comments on commit e8db5ac

Please sign in to comment.