use crate::{Result, ResultContext};
use serde::{Deserialize, Serialize};
use std::path::Path;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct WorkflowEntry {
pub id: String,
pub name: String,
}
impl WorkflowEntry {
pub fn new(id: impl Into<String>, name: impl Into<String>) -> Self {
Self {
id: id.into(),
name: name.into(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct WorkflowsManifest {
pub workflows: Vec<WorkflowEntry>,
}
impl WorkflowsManifest {
pub fn new() -> Self {
Self {
workflows: Vec::new(),
}
}
pub fn with_workflows(workflows: Vec<WorkflowEntry>) -> Self {
Self { workflows }
}
pub fn add_workflow(&mut self, workflow: WorkflowEntry) -> bool {
if !self.workflows.iter().any(|w| w.id == workflow.id) {
self.workflows.push(workflow);
true
} else {
false
}
}
pub fn remove_workflow_by_id(&mut self, workflow_id: &str) -> bool {
if let Some(pos) = self.workflows.iter().position(|w| w.id == workflow_id) {
self.workflows.remove(pos);
true
} else {
false
}
}
pub fn remove_workflow_by_name(&mut self, workflow_name: &str) -> bool {
if let Some(pos) = self.workflows.iter().position(|w| w.name == workflow_name) {
self.workflows.remove(pos);
true
} else {
false
}
}
pub fn contains_id(&self, workflow_id: &str) -> bool {
self.workflows.iter().any(|w| w.id == workflow_id)
}
pub fn contains_name(&self, workflow_name: &str) -> bool {
self.workflows.iter().any(|w| w.name == workflow_name)
}
pub fn get_by_id(&self, workflow_id: &str) -> Option<&WorkflowEntry> {
self.workflows.iter().find(|w| w.id == workflow_id)
}
pub fn get_by_name(&self, workflow_name: &str) -> Option<&WorkflowEntry> {
self.workflows.iter().find(|w| w.name == workflow_name)
}
pub fn count(&self) -> usize {
self.workflows.len()
}
pub fn read(path: impl AsRef<Path>) -> Result<Self> {
let content = std::fs::read_to_string(path.as_ref()).with_context(|| {
format!(
"Failed to read workflows manifest: {}",
path.as_ref().display()
)
})?;
let manifest: Self = yaml_serde::from_str(&content)
.with_context(|| "Failed to parse workflows manifest YAML")?;
Ok(manifest)
}
pub fn write(&self, path: impl AsRef<Path>) -> Result<()> {
if let Some(parent) = path.as_ref().parent() {
std::fs::create_dir_all(parent)?;
}
let yaml = yaml_serde::to_string(self)
.with_context(|| "Failed to serialize workflows manifest to YAML")?;
std::fs::write(path.as_ref(), yaml).with_context(|| {
format!(
"Failed to write workflows manifest: {}",
path.as_ref().display()
)
})?;
Ok(())
}
}
impl Default for WorkflowsManifest {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
#[test]
fn test_new_manifest() {
let manifest = WorkflowsManifest::new();
assert_eq!(manifest.count(), 0);
}
#[test]
fn test_with_workflows() {
let manifest = WorkflowsManifest::with_workflows(vec![
WorkflowEntry::new("wf1", "workflow1"),
WorkflowEntry::new("wf2", "workflow2"),
]);
assert_eq!(manifest.count(), 2);
assert!(manifest.contains_id("wf1"));
assert!(manifest.contains_name("workflow1"));
assert!(manifest.contains_id("wf2"));
assert!(manifest.contains_name("workflow2"));
}
#[test]
fn test_add_workflow() {
let mut manifest = WorkflowsManifest::new();
manifest.add_workflow(WorkflowEntry::new("test-id", "test"));
assert_eq!(manifest.count(), 1);
assert!(manifest.contains_id("test-id"));
assert!(manifest.contains_name("test"));
manifest.add_workflow(WorkflowEntry::new("test-id", "test"));
assert_eq!(manifest.count(), 1);
}
#[test]
fn test_remove_workflow_by_id() {
let mut manifest = WorkflowsManifest::with_workflows(vec![
WorkflowEntry::new("wf1", "workflow1"),
WorkflowEntry::new("wf2", "workflow2"),
]);
assert!(manifest.remove_workflow_by_id("wf1"));
assert_eq!(manifest.count(), 1);
assert!(!manifest.contains_id("wf1"));
assert!(!manifest.remove_workflow_by_id("nonexistent"));
}
#[test]
fn test_remove_workflow_by_name() {
let mut manifest = WorkflowsManifest::with_workflows(vec![
WorkflowEntry::new("wf1", "workflow1"),
WorkflowEntry::new("wf2", "workflow2"),
]);
assert!(manifest.remove_workflow_by_name("workflow1"));
assert_eq!(manifest.count(), 1);
assert!(!manifest.contains_name("workflow1"));
assert!(!manifest.remove_workflow_by_name("nonexistent"));
}
#[test]
fn test_get_by_id() {
let manifest = WorkflowsManifest::with_workflows(vec![
WorkflowEntry::new("wf1", "workflow1"),
WorkflowEntry::new("wf2", "workflow2"),
]);
let entry = manifest.get_by_id("wf1").unwrap();
assert_eq!(entry.id, "wf1");
assert_eq!(entry.name, "workflow1");
assert!(manifest.get_by_id("nonexistent").is_none());
}
#[test]
fn test_get_by_name() {
let manifest = WorkflowsManifest::with_workflows(vec![
WorkflowEntry::new("wf1", "workflow1"),
WorkflowEntry::new("wf2", "workflow2"),
]);
let entry = manifest.get_by_name("workflow1").unwrap();
assert_eq!(entry.id, "wf1");
assert_eq!(entry.name, "workflow1");
assert!(manifest.get_by_name("nonexistent").is_none());
}
#[test]
fn test_read_write() {
let temp_dir = TempDir::new().unwrap();
let manifest_path = temp_dir.path().join("manifest").join("workflows.yml");
let original = WorkflowsManifest::with_workflows(vec![
WorkflowEntry::new("wf1", "workflow1"),
WorkflowEntry::new("wf2", "workflow2"),
WorkflowEntry::new("wf3", "workflow3"),
]);
original.write(&manifest_path).unwrap();
assert!(manifest_path.exists());
let loaded = WorkflowsManifest::read(&manifest_path).unwrap();
assert_eq!(loaded, original);
assert_eq!(loaded.count(), 3);
}
#[test]
fn test_yaml_format() {
let manifest = WorkflowsManifest::with_workflows(vec![
WorkflowEntry::new("wf1", "workflow1"),
WorkflowEntry::new("wf2", "workflow2"),
]);
let yaml = yaml_serde::to_string(&manifest).unwrap();
assert!(yaml.contains("workflows:"));
assert!(yaml.contains("id: wf1"));
assert!(yaml.contains("name: workflow1"));
assert!(yaml.contains("id: wf2"));
assert!(yaml.contains("name: workflow2"));
}
}