From c46d0f3ee247d0764be2985700a91a2aa88cac8c Mon Sep 17 00:00:00 2001 From: Ryan Bottriell Date: Fri, 13 Dec 2024 15:17:51 -0800 Subject: [PATCH] Support string or mapping in recipes list Signed-off-by: Ryan Bottriell --- crates/spk-workspace/src/builder.rs | 2 +- crates/spk-workspace/src/file.rs | 61 +++++++++++++++------------ crates/spk-workspace/src/file_test.rs | 28 ++++++------ 3 files changed, 50 insertions(+), 41 deletions(-) diff --git a/crates/spk-workspace/src/builder.rs b/crates/spk-workspace/src/builder.rs index 474dd4421..d434abb4e 100644 --- a/crates/spk-workspace/src/builder.rs +++ b/crates/spk-workspace/src/builder.rs @@ -67,7 +67,7 @@ impl WorkspaceBuilder { let mut glob_results = file .recipes .iter() - .map(|pattern| glob::glob(pattern.as_str())) + .map(|pattern| glob::glob(pattern.path.as_str())) .flatten_ok() .flatten_ok(); while let Some(path) = glob_results.next().transpose()? { diff --git a/crates/spk-workspace/src/file.rs b/crates/spk-workspace/src/file.rs index c74b1472c..9b14f67c7 100644 --- a/crates/spk-workspace/src/file.rs +++ b/crates/spk-workspace/src/file.rs @@ -4,7 +4,7 @@ use std::path::Path; -use serde::{Deserialize, Serialize}; +use serde::Deserialize; use spk_schema::foundation::FromYaml; use crate::error::LoadWorkspaceFileError; @@ -19,10 +19,10 @@ mod file_test; /// and where to find data, usually loaded from a file on disk. /// It must still be fully validated and loaded into a /// [`super::Workspace`] to be operated on. -#[derive(Debug, Clone, Hash, PartialEq, Eq, Ord, PartialOrd, Deserialize, Serialize)] +#[derive(Debug, Clone, Hash, PartialEq, Eq, Ord, PartialOrd, Deserialize)] pub struct WorkspaceFile { - #[serde(default, skip_serializing_if = "Vec::is_empty", with = "glob_from_str")] - pub recipes: Vec, + #[serde(default)] + pub recipes: Vec, } impl WorkspaceFile { @@ -72,43 +72,48 @@ impl WorkspaceFile { } } -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) - } +#[derive(Debug, Clone, Hash, PartialEq, Eq, Ord, PartialOrd)] +pub struct RecipesItem { + pub path: glob::Pattern, +} - pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> +impl<'de> serde::de::Deserialize<'de> for RecipesItem { + fn deserialize(deserializer: D) -> Result where - D: Deserializer<'de>, + D: serde::de::Deserializer<'de>, { - /// Visits a serialized string, decoding it as a digest - struct PatternVisitor; + struct RecipeCollectorVisitor; - impl<'de> serde::de::Visitor<'de> for PatternVisitor { - type Value = Vec; + impl<'de> serde::de::Visitor<'de> for RecipeCollectorVisitor { + type Value = RecipesItem; 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 + fn visit_str(self, v: &str) -> Result where - A: serde::de::SeqAccess<'de>, + E: serde::de::Error, { - 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); + let path = glob::Pattern::new(v).map_err(serde::de::Error::custom)?; + Ok(RecipesItem { path }) + } + + fn visit_map(self, map: A) -> Result + where + A: serde::de::MapAccess<'de>, + { + #[derive(Deserialize)] + struct RawRecipeItem { + path: String, } - Ok(patterns) + + let raw_recipe = + RawRecipeItem::deserialize(serde::de::value::MapAccessDeserializer::new(map))?; + self.visit_str(&raw_recipe.path) } } - deserializer.deserialize_seq(PatternVisitor) + + deserializer.deserialize_any(RecipeCollectorVisitor) } } diff --git a/crates/spk-workspace/src/file_test.rs b/crates/spk-workspace/src/file_test.rs index 8f6b4074a..ef32214cf 100644 --- a/crates/spk-workspace/src/file_test.rs +++ b/crates/spk-workspace/src/file_test.rs @@ -20,18 +20,22 @@ recipes: [] "#; #[rstest] -fn test_workspace_roundtrip() { - let workspace = WorkspaceFile { - 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: WorkspaceFile = serde_json::from_str(&serialized).unwrap(); - - assert_eq!(workspace, deserialized); +#[case( + r#" +api: v0/workspace +recipes: + - packages/**/*.spk.yaml + - path: packages/python/python2.spk.yaml + versions: [2.7.18] + - path: packages/python/python3.spk.yaml + versions: + - '3.7.{0..17}' + - '3.8.{0..20}' + - '3.9.{0..21}' +"# +)] +fn test_workspace_from_yaml(#[case] yaml: &str) { + let _deserialized: WorkspaceFile = serde_yaml::from_str(yaml).unwrap(); } #[rstest]