diff --git a/Cargo.lock b/Cargo.lock index 3ad68678b..1a016c49f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4651,6 +4651,17 @@ dependencies = [ "variantly", ] +[[package]] +name = "spk-workspace" +version = "0.42.0" +dependencies = [ + "glob", + "rstest 0.18.2", + "serde", + "serde_json", + "spk-solve", +] + [[package]] name = "static_assertions" version = "1.1.0" diff --git a/Cargo.toml b/Cargo.toml index ad01238ae..fca9b084e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ members = [ "crates/spk-solve", "crates/spk-solve/crates/*", "crates/spk-storage", + "crates/spk-workspace", "crates/spk", ] resolver = "2" diff --git a/crates/spk-workspace/Cargo.toml b/crates/spk-workspace/Cargo.toml new file mode 100644 index 000000000..052877405 --- /dev/null +++ b/crates/spk-workspace/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "spk-workspace" +authors = { workspace = true } +edition = { workspace = true } +version = { workspace = true } +license-file = { workspace = true } +homepage = { workspace = true } +repository = { workspace = true } +readme = { workspace = true } +description = { workspace = true } + +[lints] +workspace = true + +[features] +sentry = ["spk-solve/sentry"] + +[dependencies] +serde = { workspace = true, features = ["derive"] } +glob = { workspace = true } +spk-solve = { workspace = true } + +[dev-dependencies] +rstest = { workspace = true } +serde_json = { workspace = true } diff --git a/crates/spk-workspace/src/lib.rs b/crates/spk-workspace/src/lib.rs new file mode 100644 index 000000000..9e3d3cbc2 --- /dev/null +++ b/crates/spk-workspace/src/lib.rs @@ -0,0 +1 @@ +mod spec; diff --git a/crates/spk-workspace/src/spec.rs b/crates/spk-workspace/src/spec.rs new file mode 100644 index 000000000..70272223f --- /dev/null +++ b/crates/spk-workspace/src/spec.rs @@ -0,0 +1,52 @@ +use serde::{Deserialize, Serialize}; + +#[cfg(test)] +#[path = "spec_test.rs"] +mod spec_test; + +#[derive(Debug, Clone, Hash, PartialEq, Eq, Ord, PartialOrd, Deserialize, Serialize)] +pub struct Workspace { + #[serde(default, skip_serializing_if = "Vec::is_empty", with = "glob_from_str")] + pub recipes: Vec, +} + +mod glob_from_str { + use serde::{Deserializer, Serialize, Serializer}; + + pub fn serialize(patterns: &Vec, serializer: S) -> Result + where + S: Serializer, + { + let patterns: Vec<_> = patterns.iter().map(|p| p.as_str()).collect(); + patterns.serialize(serializer) + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + /// Visits a serialized string, decoding it as a digest + struct PatternVisitor; + + impl<'de> serde::de::Visitor<'de> for PatternVisitor { + type Value = Vec; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a glob pattern") + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: serde::de::SeqAccess<'de>, + { + let mut patterns = Vec::with_capacity(seq.size_hint().unwrap_or(0)); + while let Some(pattern) = seq.next_element()? { + let pattern = glob::Pattern::new(pattern).map_err(serde::de::Error::custom)?; + patterns.push(pattern); + } + Ok(patterns) + } + } + deserializer.deserialize_seq(PatternVisitor) + } +} diff --git a/crates/spk-workspace/src/spec_test.rs b/crates/spk-workspace/src/spec_test.rs new file mode 100644 index 000000000..ff1ee8f77 --- /dev/null +++ b/crates/spk-workspace/src/spec_test.rs @@ -0,0 +1,18 @@ +use rstest::rstest; + +use super::Workspace; + +#[rstest] +fn test_workspace_roundtrip() { + let workspace = Workspace { + recipes: vec![ + glob::Pattern::new("packages/*/*.spk.yml").unwrap(), + glob::Pattern::new("platforms/*/*.spk.yml").unwrap(), + ], + }; + + let serialized = serde_json::to_string(&workspace).unwrap(); + let deserialized: Workspace = serde_json::from_str(&serialized).unwrap(); + + assert_eq!(workspace, deserialized); +}