Skip to main content

ergo_runtime/trigger/
registry.rs

1use std::collections::HashMap;
2
3use semver::Version;
4
5use super::{
6    OutputSpec, TriggerKind, TriggerPrimitive, TriggerPrimitiveManifest, TriggerValidationError,
7    TriggerValueType,
8};
9use crate::common::is_valid_id;
10
11pub struct TriggerRegistry {
12    primitives: HashMap<String, Box<dyn TriggerPrimitive>>,
13}
14
15impl TriggerRegistry {
16    pub fn new() -> Self {
17        Self {
18            primitives: HashMap::new(),
19        }
20    }
21
22    pub fn validate_manifest(
23        manifest: &TriggerPrimitiveManifest,
24    ) -> Result<(), TriggerValidationError> {
25        if !is_valid_id(&manifest.id) {
26            return Err(TriggerValidationError::InvalidId {
27                id: manifest.id.clone(),
28            });
29        }
30
31        if Version::parse(&manifest.version).is_err() {
32            return Err(TriggerValidationError::InvalidVersion {
33                version: manifest.version.clone(),
34            });
35        }
36
37        if manifest.kind != TriggerKind::Trigger {
38            return Err(TriggerValidationError::WrongKind {
39                expected: TriggerKind::Trigger,
40                got: manifest.kind.clone(),
41            });
42        }
43
44        if manifest.inputs.is_empty() {
45            return Err(TriggerValidationError::NoInputsDeclared {
46                trigger: manifest.id.clone(),
47            });
48        }
49
50        let mut seen_inputs: HashMap<&str, usize> = HashMap::new();
51        for (index, input) in manifest.inputs.iter().enumerate() {
52            if let Some(&first_index) = seen_inputs.get(input.name.as_str()) {
53                return Err(TriggerValidationError::DuplicateInput {
54                    name: input.name.clone(),
55                    first_index,
56                    second_index: index,
57                });
58            }
59            seen_inputs.insert(&input.name, index);
60
61            match input.value_type {
62                TriggerValueType::Number
63                | TriggerValueType::Series
64                | TriggerValueType::Bool
65                | TriggerValueType::Event => {}
66            }
67
68            if input.cardinality != super::Cardinality::Single {
69                return Err(TriggerValidationError::InvalidInputCardinality {
70                    input: input.name.clone(),
71                    got: format!("{:?}", input.cardinality),
72                });
73            }
74        }
75
76        for parameter in &manifest.parameters {
77            if let Some(default) = &parameter.default {
78                let got = default.value_type();
79                if got != parameter.value_type {
80                    return Err(TriggerValidationError::InvalidParameterType {
81                        parameter: parameter.name.clone(),
82                        expected: parameter.value_type.clone(),
83                        got,
84                    });
85                }
86            }
87        }
88
89        if manifest.side_effects {
90            return Err(TriggerValidationError::SideEffectsNotAllowed);
91        }
92
93        if !manifest.execution.deterministic {
94            return Err(TriggerValidationError::NonDeterministicExecution);
95        }
96
97        // TRG-STATE-1: Triggers must be stateless.
98        // Temporal patterns requiring memory must be implemented as clusters.
99        if manifest.state.allowed {
100            return Err(TriggerValidationError::StatefulTriggerNotAllowed {
101                trigger_id: manifest.id.clone(),
102            });
103        }
104
105        Self::validate_outputs(&manifest.outputs)?;
106
107        Ok(())
108    }
109
110    fn validate_outputs(outputs: &[OutputSpec]) -> Result<(), TriggerValidationError> {
111        if outputs.len() != 1 {
112            return Err(TriggerValidationError::TriggerWrongOutputCount { got: outputs.len() });
113        }
114
115        let output = &outputs[0];
116        if output.value_type != TriggerValueType::Event {
117            return Err(TriggerValidationError::InvalidOutputType {
118                output: output.name.clone(),
119                expected: TriggerValueType::Event,
120                got: output.value_type.clone(),
121            });
122        }
123
124        Ok(())
125    }
126
127    pub fn register(
128        &mut self,
129        primitive: Box<dyn TriggerPrimitive>,
130    ) -> Result<(), TriggerValidationError> {
131        let manifest = primitive.manifest();
132
133        Self::validate_manifest(manifest)?;
134
135        if self.primitives.contains_key(&manifest.id) {
136            return Err(TriggerValidationError::DuplicateId(manifest.id.clone()));
137        }
138
139        self.primitives.insert(manifest.id.clone(), primitive);
140        Ok(())
141    }
142
143    pub fn get(&self, id: &str) -> Option<&dyn TriggerPrimitive> {
144        self.primitives.get(id).map(|b| b.as_ref())
145    }
146
147    pub fn keys(&self) -> Vec<(String, String)> {
148        let mut keys: Vec<(String, String)> = self
149            .primitives
150            .values()
151            .map(|primitive| {
152                let manifest = primitive.manifest();
153                (manifest.id.clone(), manifest.version.clone())
154            })
155            .collect();
156        keys.sort();
157        keys
158    }
159}
160
161impl Default for TriggerRegistry {
162    fn default() -> Self {
163        Self::new()
164    }
165}
166
167#[cfg(test)]
168mod tests;