greentic_dev/dev_runner/
runner.rs1use std::collections::HashMap;
2use std::fs;
3use std::path::{Path, PathBuf};
4
5use serde_yaml_bw::Value as YamlValue;
6
7use super::registry::DescribeRegistry;
8use super::schema::{schema_id_from_json, validate_yaml_against_schema};
9
10#[derive(Clone, Debug, Default)]
11pub struct ComponentSchema {
12 pub node_schema: Option<String>,
13}
14
15pub trait ComponentDescriber {
16 fn describe(&self, component: &str) -> Result<ComponentSchema, String>;
17}
18
19#[derive(Debug, Clone)]
20pub struct StaticComponentDescriber {
21 schemas: HashMap<String, ComponentSchema>,
22 fallback: ComponentSchema,
23}
24
25impl StaticComponentDescriber {
26 pub fn new() -> Self {
27 Self {
28 schemas: HashMap::new(),
29 fallback: ComponentSchema::default(),
30 }
31 }
32
33 pub fn with_fallback(mut self, fallback_schema: ComponentSchema) -> Self {
34 self.fallback = fallback_schema;
35 self
36 }
37
38 pub fn register_schema<S: Into<String>>(
39 &mut self,
40 component: S,
41 schema: ComponentSchema,
42 ) -> &mut Self {
43 self.schemas.insert(component.into(), schema);
44 self
45 }
46}
47
48impl ComponentDescriber for StaticComponentDescriber {
49 fn describe(&self, component: &str) -> Result<ComponentSchema, String> {
50 if let Some(schema) = self.schemas.get(component) {
51 Ok(schema.clone())
52 } else {
53 Ok(self.fallback.clone())
54 }
55 }
56}
57
58impl Default for StaticComponentDescriber {
59 fn default() -> Self {
60 Self::new()
61 }
62}
63
64pub struct FlowValidator<D> {
65 describer: D,
66 registry: DescribeRegistry,
67}
68
69#[derive(Clone, Debug)]
70pub struct ValidatedNode {
71 pub component: String,
72 pub node_config: YamlValue,
73 pub schema_json: Option<String>,
74 pub schema_id: Option<String>,
75 pub defaults: Option<YamlValue>,
76}
77
78impl<D> FlowValidator<D>
79where
80 D: ComponentDescriber,
81{
82 pub fn new(describer: D, registry: DescribeRegistry) -> Self {
83 Self {
84 describer,
85 registry,
86 }
87 }
88
89 pub fn validate_file<P>(&self, path: P) -> Result<Vec<ValidatedNode>, FlowValidationError>
90 where
91 P: AsRef<Path>,
92 {
93 let path_ref = path.as_ref();
94 let source = fs::read_to_string(path_ref).map_err(|error| FlowValidationError::Io {
95 path: path_ref.to_path_buf(),
96 error,
97 })?;
98 self.validate_str(&source)
99 }
100
101 pub fn validate_str(
102 &self,
103 yaml_source: &str,
104 ) -> Result<Vec<ValidatedNode>, FlowValidationError> {
105 let document: YamlValue = serde_yaml_bw::from_str(yaml_source).map_err(|error| {
106 FlowValidationError::YamlParse {
107 error: error.to_string(),
108 }
109 })?;
110 self.validate_document(&document)
111 }
112
113 pub fn validate_document(
114 &self,
115 document: &YamlValue,
116 ) -> Result<Vec<ValidatedNode>, FlowValidationError> {
117 let nodes = match nodes_from_document(document) {
118 Some(nodes) => nodes,
119 None => {
120 return Err(FlowValidationError::MissingNodes);
121 }
122 };
123
124 let mut validated_nodes = Vec::with_capacity(nodes.len());
125
126 for (index, node) in nodes.iter().enumerate() {
127 let node_mapping = match node.as_mapping() {
128 Some(mapping) => mapping,
129 None => {
130 return Err(FlowValidationError::NodeNotMapping { index });
131 }
132 };
133
134 let component = component_name(node_mapping)
135 .ok_or(FlowValidationError::MissingComponent { index })?;
136
137 let schema = self.describer.describe(component).map_err(|error| {
138 FlowValidationError::DescribeFailed {
139 component: component.to_owned(),
140 error,
141 }
142 })?;
143
144 let schema_json = self
145 .registry
146 .get_schema(component)
147 .map(|schema| schema.to_owned())
148 .or_else(|| schema.node_schema.clone());
149
150 let schema_id = schema_json.as_deref().and_then(schema_id_from_json);
151
152 if let Some(schema_json) = schema_json.as_deref() {
153 validate_yaml_against_schema(node, schema_json).map_err(|message| {
154 FlowValidationError::SchemaValidation {
155 component: component.to_owned(),
156 index,
157 message,
158 }
159 })?;
160 }
161
162 let defaults = self.registry.get_defaults(component).cloned();
163
164 validated_nodes.push(ValidatedNode {
165 component: component.to_owned(),
166 node_config: node.clone(),
167 schema_json,
168 schema_id,
169 defaults,
170 });
171 }
172
173 Ok(validated_nodes)
174 }
175}
176
177fn nodes_from_document(document: &YamlValue) -> Option<&Vec<YamlValue>> {
178 if let Some(sequence) = document.as_sequence() {
179 return Some(&**sequence);
180 }
181
182 let mapping = document.as_mapping()?;
183 mapping
184 .get("nodes")
185 .and_then(|value| value.as_sequence().map(|sequence| &**sequence))
186}
187
188fn component_name(mapping: &serde_yaml_bw::Mapping) -> Option<&str> {
189 mapping
190 .get("component")
191 .and_then(|value| value.as_str())
192 .or_else(|| mapping.get("type").and_then(|value| value.as_str()))
193}
194
195#[derive(Debug)]
196pub enum FlowValidationError {
197 Io {
198 path: PathBuf,
199 error: std::io::Error,
200 },
201 YamlParse {
202 error: String,
203 },
204 MissingNodes,
205 NodeNotMapping {
206 index: usize,
207 },
208 MissingComponent {
209 index: usize,
210 },
211 DescribeFailed {
212 component: String,
213 error: String,
214 },
215 SchemaValidation {
216 component: String,
217 index: usize,
218 message: String,
219 },
220}