ergo_runtime/trigger/
registry.rs1use 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) = ¶meter.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 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;