Skip to main content

pan_common/
pipeline.rs

1use crate::schema::CommandSchema;
2use serde::Serialize;
3
4/// Description of an operation for dry-run output.
5#[derive(Debug, Clone, Serialize)]
6pub struct OperationDescription {
7    pub operation: String,
8    pub params: serde_json::Value,
9    pub description: String,
10}
11
12/// Trait for processing operations, generic over data type and error type.
13pub trait Operation<T, E>: Send + Sync {
14    /// Human-readable name.
15    fn name(&self) -> &str;
16
17    /// Apply the operation to the input data.
18    fn apply(&self, input: T) -> Result<T, E>;
19
20    /// Describe what this operation will do (for dry-run).
21    fn describe(&self) -> OperationDescription;
22
23    /// Parameter schema for this operation.
24    fn schema() -> CommandSchema
25    where
26        Self: Sized;
27}
28
29/// Dry-run plan output.
30#[derive(Debug, Serialize)]
31pub struct PipelinePlan {
32    pub steps: Vec<OperationDescription>,
33}
34
35/// A pipeline of operations to be applied in order.
36pub struct Pipeline<T, E> {
37    operations: Vec<Box<dyn Operation<T, E>>>,
38}
39
40impl<T, E> Pipeline<T, E> {
41    pub fn new() -> Self {
42        Self {
43            operations: Vec::new(),
44        }
45    }
46
47    pub fn push<O: Operation<T, E> + 'static>(mut self, op: O) -> Self {
48        self.operations.push(Box::new(op));
49        self
50    }
51
52    pub fn push_boxed(mut self, op: Box<dyn Operation<T, E>>) -> Self {
53        self.operations.push(op);
54        self
55    }
56
57    /// Execute all operations in order on the given input.
58    pub fn execute(&self, mut input: T) -> Result<T, E> {
59        for op in &self.operations {
60            input = op.apply(input)?;
61        }
62        Ok(input)
63    }
64
65    /// Return a plan of what would be executed (for --dry-run).
66    pub fn describe(&self) -> PipelinePlan {
67        PipelinePlan {
68            steps: self.operations.iter().map(|op| op.describe()).collect(),
69        }
70    }
71
72    pub fn len(&self) -> usize {
73        self.operations.len()
74    }
75
76    pub fn is_empty(&self) -> bool {
77        self.operations.is_empty()
78    }
79}
80
81impl<T, E> Default for Pipeline<T, E> {
82    fn default() -> Self {
83        Self::new()
84    }
85}