Skip to main content

kibana_sync/kibana/workflows/
manifest.rs

1//! Workflows manifest management
2//!
3//! The workflows manifest is stored as `manifest/workflows.yml` and contains
4//! a list of workflows with their IDs and names.
5//!
6//! Example format:
7//! ```yaml
8//! workflows:
9//!   - id: workflow-123
10//!     name: my-workflow
11//!   - id: workflow-456
12//!     name: alert-workflow
13//!   - id: workflow-789
14//!     name: data-pipeline
15//! ```
16
17use crate::{Result, ResultContext};
18use serde::{Deserialize, Serialize};
19use std::path::Path;
20
21/// Workflow entry in the manifest
22#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
23pub struct WorkflowEntry {
24    /// Workflow ID (used for API calls)
25    pub id: String,
26    /// Workflow name (used for file storage)
27    pub name: String,
28}
29
30impl WorkflowEntry {
31    /// Create a new workflow entry
32    pub fn new(id: impl Into<String>, name: impl Into<String>) -> Self {
33        Self {
34            id: id.into(),
35            name: name.into(),
36        }
37    }
38}
39
40/// Workflows manifest structure
41#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
42pub struct WorkflowsManifest {
43    /// List of workflows to manage
44    pub workflows: Vec<WorkflowEntry>,
45}
46
47impl WorkflowsManifest {
48    /// Create a new empty workflows manifest
49    pub fn new() -> Self {
50        Self {
51            workflows: Vec::new(),
52        }
53    }
54
55    /// Create a manifest with specified workflows
56    pub fn with_workflows(workflows: Vec<WorkflowEntry>) -> Self {
57        Self { workflows }
58    }
59
60    /// Add a workflow to the manifest
61    ///
62    /// Returns true if workflow was added, false if it already exists
63    pub fn add_workflow(&mut self, workflow: WorkflowEntry) -> bool {
64        // Check if workflow with same ID already exists
65        if !self.workflows.iter().any(|w| w.id == workflow.id) {
66            self.workflows.push(workflow);
67            true
68        } else {
69            false
70        }
71    }
72
73    /// Remove a workflow by ID from the manifest
74    pub fn remove_workflow_by_id(&mut self, workflow_id: &str) -> bool {
75        if let Some(pos) = self.workflows.iter().position(|w| w.id == workflow_id) {
76            self.workflows.remove(pos);
77            true
78        } else {
79            false
80        }
81    }
82
83    /// Remove a workflow by name from the manifest
84    pub fn remove_workflow_by_name(&mut self, workflow_name: &str) -> bool {
85        if let Some(pos) = self.workflows.iter().position(|w| w.name == workflow_name) {
86            self.workflows.remove(pos);
87            true
88        } else {
89            false
90        }
91    }
92
93    /// Check if a workflow ID is in the manifest
94    pub fn contains_id(&self, workflow_id: &str) -> bool {
95        self.workflows.iter().any(|w| w.id == workflow_id)
96    }
97
98    /// Check if a workflow name is in the manifest
99    pub fn contains_name(&self, workflow_name: &str) -> bool {
100        self.workflows.iter().any(|w| w.name == workflow_name)
101    }
102
103    /// Get workflow entry by ID
104    pub fn get_by_id(&self, workflow_id: &str) -> Option<&WorkflowEntry> {
105        self.workflows.iter().find(|w| w.id == workflow_id)
106    }
107
108    /// Get workflow entry by name
109    pub fn get_by_name(&self, workflow_name: &str) -> Option<&WorkflowEntry> {
110        self.workflows.iter().find(|w| w.name == workflow_name)
111    }
112
113    /// Get the number of workflows in the manifest
114    pub fn count(&self) -> usize {
115        self.workflows.len()
116    }
117
118    /// Read manifest from YAML file
119    pub fn read(path: impl AsRef<Path>) -> Result<Self> {
120        let content = std::fs::read_to_string(path.as_ref()).with_context(|| {
121            format!(
122                "Failed to read workflows manifest: {}",
123                path.as_ref().display()
124            )
125        })?;
126
127        let manifest: Self = yaml_serde::from_str(&content)
128            .with_context(|| "Failed to parse workflows manifest YAML")?;
129
130        Ok(manifest)
131    }
132
133    /// Write manifest to YAML file
134    pub fn write(&self, path: impl AsRef<Path>) -> Result<()> {
135        // Create parent directory if needed
136        if let Some(parent) = path.as_ref().parent() {
137            std::fs::create_dir_all(parent)?;
138        }
139
140        let yaml = yaml_serde::to_string(self)
141            .with_context(|| "Failed to serialize workflows manifest to YAML")?;
142
143        std::fs::write(path.as_ref(), yaml).with_context(|| {
144            format!(
145                "Failed to write workflows manifest: {}",
146                path.as_ref().display()
147            )
148        })?;
149
150        Ok(())
151    }
152}
153
154impl Default for WorkflowsManifest {
155    fn default() -> Self {
156        Self::new()
157    }
158}
159
160#[cfg(test)]
161mod tests {
162    use super::*;
163    use tempfile::TempDir;
164
165    #[test]
166    fn test_new_manifest() {
167        let manifest = WorkflowsManifest::new();
168        assert_eq!(manifest.count(), 0);
169    }
170
171    #[test]
172    fn test_with_workflows() {
173        let manifest = WorkflowsManifest::with_workflows(vec![
174            WorkflowEntry::new("wf1", "workflow1"),
175            WorkflowEntry::new("wf2", "workflow2"),
176        ]);
177        assert_eq!(manifest.count(), 2);
178        assert!(manifest.contains_id("wf1"));
179        assert!(manifest.contains_name("workflow1"));
180        assert!(manifest.contains_id("wf2"));
181        assert!(manifest.contains_name("workflow2"));
182    }
183
184    #[test]
185    fn test_add_workflow() {
186        let mut manifest = WorkflowsManifest::new();
187        manifest.add_workflow(WorkflowEntry::new("test-id", "test"));
188        assert_eq!(manifest.count(), 1);
189        assert!(manifest.contains_id("test-id"));
190        assert!(manifest.contains_name("test"));
191
192        // Adding duplicate should not increase count
193        manifest.add_workflow(WorkflowEntry::new("test-id", "test"));
194        assert_eq!(manifest.count(), 1);
195    }
196
197    #[test]
198    fn test_remove_workflow_by_id() {
199        let mut manifest = WorkflowsManifest::with_workflows(vec![
200            WorkflowEntry::new("wf1", "workflow1"),
201            WorkflowEntry::new("wf2", "workflow2"),
202        ]);
203
204        assert!(manifest.remove_workflow_by_id("wf1"));
205        assert_eq!(manifest.count(), 1);
206        assert!(!manifest.contains_id("wf1"));
207
208        // Removing non-existent workflow returns false
209        assert!(!manifest.remove_workflow_by_id("nonexistent"));
210    }
211
212    #[test]
213    fn test_remove_workflow_by_name() {
214        let mut manifest = WorkflowsManifest::with_workflows(vec![
215            WorkflowEntry::new("wf1", "workflow1"),
216            WorkflowEntry::new("wf2", "workflow2"),
217        ]);
218
219        assert!(manifest.remove_workflow_by_name("workflow1"));
220        assert_eq!(manifest.count(), 1);
221        assert!(!manifest.contains_name("workflow1"));
222
223        // Removing non-existent workflow returns false
224        assert!(!manifest.remove_workflow_by_name("nonexistent"));
225    }
226
227    #[test]
228    fn test_get_by_id() {
229        let manifest = WorkflowsManifest::with_workflows(vec![
230            WorkflowEntry::new("wf1", "workflow1"),
231            WorkflowEntry::new("wf2", "workflow2"),
232        ]);
233
234        let entry = manifest.get_by_id("wf1").unwrap();
235        assert_eq!(entry.id, "wf1");
236        assert_eq!(entry.name, "workflow1");
237
238        assert!(manifest.get_by_id("nonexistent").is_none());
239    }
240
241    #[test]
242    fn test_get_by_name() {
243        let manifest = WorkflowsManifest::with_workflows(vec![
244            WorkflowEntry::new("wf1", "workflow1"),
245            WorkflowEntry::new("wf2", "workflow2"),
246        ]);
247
248        let entry = manifest.get_by_name("workflow1").unwrap();
249        assert_eq!(entry.id, "wf1");
250        assert_eq!(entry.name, "workflow1");
251
252        assert!(manifest.get_by_name("nonexistent").is_none());
253    }
254
255    #[test]
256    fn test_read_write() {
257        let temp_dir = TempDir::new().unwrap();
258        let manifest_path = temp_dir.path().join("manifest").join("workflows.yml");
259
260        let original = WorkflowsManifest::with_workflows(vec![
261            WorkflowEntry::new("wf1", "workflow1"),
262            WorkflowEntry::new("wf2", "workflow2"),
263            WorkflowEntry::new("wf3", "workflow3"),
264        ]);
265
266        // Write
267        original.write(&manifest_path).unwrap();
268        assert!(manifest_path.exists());
269
270        // Read
271        let loaded = WorkflowsManifest::read(&manifest_path).unwrap();
272        assert_eq!(loaded, original);
273        assert_eq!(loaded.count(), 3);
274    }
275
276    #[test]
277    fn test_yaml_format() {
278        let manifest = WorkflowsManifest::with_workflows(vec![
279            WorkflowEntry::new("wf1", "workflow1"),
280            WorkflowEntry::new("wf2", "workflow2"),
281        ]);
282        let yaml = yaml_serde::to_string(&manifest).unwrap();
283
284        assert!(yaml.contains("workflows:"));
285        assert!(yaml.contains("id: wf1"));
286        assert!(yaml.contains("name: workflow1"));
287        assert!(yaml.contains("id: wf2"));
288        assert!(yaml.contains("name: workflow2"));
289    }
290}