1use serde::{Deserialize, Serialize};
33use serde_json::Value;
34use std::collections::HashMap;
35
36#[derive(Debug, Clone, PartialEq, Eq)]
38pub struct ValidationError {
39 pub path: String,
41
42 pub message: String,
44
45 pub kind: ValidationErrorKind,
47}
48
49impl ValidationError {
50 pub fn new(
52 path: impl Into<String>,
53 message: impl Into<String>,
54 kind: ValidationErrorKind,
55 ) -> Self {
56 Self {
57 path: path.into(),
58 message: message.into(),
59 kind,
60 }
61 }
62}
63
64impl std::fmt::Display for ValidationError {
65 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66 write!(f, "{}: {} ({})", self.path, self.message, self.kind)
67 }
68}
69
70impl std::error::Error for ValidationError {}
71
72#[derive(Debug, Clone, Copy, PartialEq, Eq)]
74pub enum ValidationErrorKind {
75 MissingField,
77 InvalidType,
79 OutOfRange,
81 InvalidFormat,
83 Duplicate,
85 InvalidReference,
87}
88
89impl std::fmt::Display for ValidationErrorKind {
90 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
91 match self {
92 Self::MissingField => write!(f, "missing field"),
93 Self::InvalidType => write!(f, "invalid type"),
94 Self::OutOfRange => write!(f, "out of range"),
95 Self::InvalidFormat => write!(f, "invalid format"),
96 Self::Duplicate => write!(f, "duplicate"),
97 Self::InvalidReference => write!(f, "invalid reference"),
98 }
99 }
100}
101
102pub struct SchemaValidator {
104 node_schemas: HashMap<String, NodeSchema>,
106}
107
108impl SchemaValidator {
109 pub fn new() -> Self {
111 Self {
112 node_schemas: HashMap::new(),
113 }
114 }
115
116 pub fn register_node_schema(&mut self, node_type: impl Into<String>, schema: NodeSchema) {
118 self.node_schemas.insert(node_type.into(), schema);
119 }
120
121 pub fn validate_preset(&self, json: &str) -> Result<(), Vec<ValidationError>> {
131 let value: Value = match serde_json::from_str(json) {
132 Ok(v) => v,
133 Err(e) => {
134 return Err(vec![ValidationError::new(
135 "/",
136 format!("Invalid JSON: {}", e),
137 ValidationErrorKind::InvalidFormat,
138 )]);
139 }
140 };
141
142 let mut errors = Vec::new();
143
144 if !value.is_object() {
146 errors.push(ValidationError::new(
147 "/",
148 "Root must be an object",
149 ValidationErrorKind::InvalidType,
150 ));
151 return Err(errors);
152 }
153
154 let obj = value.as_object().unwrap();
155
156 if !obj.contains_key("name") {
158 errors.push(ValidationError::new(
159 "/name",
160 "Missing required field 'name'",
161 ValidationErrorKind::MissingField,
162 ));
163 }
164
165 if !obj.contains_key("nodes") {
166 errors.push(ValidationError::new(
167 "/nodes",
168 "Missing required field 'nodes'",
169 ValidationErrorKind::MissingField,
170 ));
171 }
172
173 if !obj.contains_key("connections") {
174 errors.push(ValidationError::new(
175 "/connections",
176 "Missing required field 'connections'",
177 ValidationErrorKind::MissingField,
178 ));
179 }
180
181 if let Some(nodes) = obj.get("nodes") {
183 if !nodes.is_array() {
184 errors.push(ValidationError::new(
185 "/nodes",
186 "Field 'nodes' must be an array",
187 ValidationErrorKind::InvalidType,
188 ));
189 } else {
190 let nodes_array = nodes.as_array().unwrap();
191 let mut node_ids = std::collections::HashSet::new();
192
193 for (i, node) in nodes_array.iter().enumerate() {
194 let path = format!("/nodes/{}", i);
195
196 if !node.is_object() {
197 errors.push(ValidationError::new(
198 &path,
199 "Node must be an object",
200 ValidationErrorKind::InvalidType,
201 ));
202 continue;
203 }
204
205 let node_obj = node.as_object().unwrap();
206
207 if let Some(id) = node_obj.get("id") {
209 if let Some(id_num) = id.as_u64() {
210 if !node_ids.insert(id_num) {
211 errors.push(ValidationError::new(
212 format!("{}/id", path),
213 format!("Duplicate node ID: {}", id_num),
214 ValidationErrorKind::Duplicate,
215 ));
216 }
217 } else {
218 errors.push(ValidationError::new(
219 format!("{}/id", path),
220 "Node ID must be a number",
221 ValidationErrorKind::InvalidType,
222 ));
223 }
224 } else {
225 errors.push(ValidationError::new(
226 format!("{}/id", path),
227 "Missing required field 'id'",
228 ValidationErrorKind::MissingField,
229 ));
230 }
231
232 if !node_obj.contains_key("node_type") {
234 errors.push(ValidationError::new(
235 format!("{}/node_type", path),
236 "Missing required field 'node_type'",
237 ValidationErrorKind::MissingField,
238 ));
239 }
240 }
241 }
242 }
243
244 if let Some(connections) = obj.get("connections") {
246 if !connections.is_array() {
247 errors.push(ValidationError::new(
248 "/connections",
249 "Field 'connections' must be an array",
250 ValidationErrorKind::InvalidType,
251 ));
252 } else {
253 let connections_array = connections.as_array().unwrap();
254
255 for (i, conn) in connections_array.iter().enumerate() {
256 let path = format!("/connections/{}", i);
257
258 if !conn.is_object() {
259 errors.push(ValidationError::new(
260 &path,
261 "Connection must be an object",
262 ValidationErrorKind::InvalidType,
263 ));
264 continue;
265 }
266
267 let conn_obj = conn.as_object().unwrap();
268
269 for field in &["from_node", "from_output", "to_node", "to_input"] {
271 if !conn_obj.contains_key(*field) {
272 errors.push(ValidationError::new(
273 format!("{}/{}", path, field),
274 format!("Missing required field '{}'", field),
275 ValidationErrorKind::MissingField,
276 ));
277 }
278 }
279 }
280 }
281 }
282
283 if errors.is_empty() {
284 Ok(())
285 } else {
286 Err(errors)
287 }
288 }
289
290 pub fn validate_node_config(
292 &self,
293 node_type: &str,
294 config: &Value,
295 ) -> Result<(), Vec<ValidationError>> {
296 let mut errors = Vec::new();
297
298 if let Some(schema) = self.node_schemas.get(node_type) {
299 if let Some(params) = config.get("params") {
301 if let Some(params_obj) = params.as_object() {
302 for (param_name, param_value) in params_obj {
303 if let Some(param_schema) = schema.params.get(param_name) {
304 if let Some(value) = param_value.as_f64() {
306 let value_f32 = value as f32;
307 if value_f32 < param_schema.min || value_f32 > param_schema.max {
308 errors.push(ValidationError::new(
309 format!("/params/{}", param_name),
310 format!(
311 "Value {} out of range [{}, {}]",
312 value_f32, param_schema.min, param_schema.max
313 ),
314 ValidationErrorKind::OutOfRange,
315 ));
316 }
317 } else {
318 errors.push(ValidationError::new(
319 format!("/params/{}", param_name),
320 "Parameter value must be a number",
321 ValidationErrorKind::InvalidType,
322 ));
323 }
324 }
325 }
326 }
327 }
328 }
329
330 if errors.is_empty() {
331 Ok(())
332 } else {
333 Err(errors)
334 }
335 }
336}
337
338impl Default for SchemaValidator {
339 fn default() -> Self {
340 Self::new()
341 }
342}
343
344#[derive(Debug, Clone, Serialize, Deserialize)]
346pub struct NodeSchema {
347 pub node_type: String,
349
350 pub params: HashMap<String, ParamSchema>,
352
353 pub num_inputs: usize,
355
356 pub num_outputs: usize,
358}
359
360impl NodeSchema {
361 pub fn new(node_type: impl Into<String>, num_inputs: usize, num_outputs: usize) -> Self {
363 Self {
364 node_type: node_type.into(),
365 params: HashMap::new(),
366 num_inputs,
367 num_outputs,
368 }
369 }
370
371 pub fn add_param(&mut self, name: impl Into<String>, schema: ParamSchema) {
373 self.params.insert(name.into(), schema);
374 }
375}
376
377#[derive(Debug, Clone, Serialize, Deserialize)]
379pub struct ParamSchema {
380 pub name: String,
382
383 pub min: f32,
385
386 pub max: f32,
388
389 pub default: f32,
391
392 pub unit: Option<String>,
394}
395
396impl ParamSchema {
397 pub fn new(name: impl Into<String>, min: f32, max: f32, default: f32) -> Self {
399 Self {
400 name: name.into(),
401 min,
402 max,
403 default,
404 unit: None,
405 }
406 }
407
408 pub fn with_unit(mut self, unit: impl Into<String>) -> Self {
410 self.unit = Some(unit.into());
411 self
412 }
413}
414
415#[cfg(test)]
416mod tests {
417 use super::*;
418
419 #[test]
420 fn test_validate_preset_valid() {
421 let validator = SchemaValidator::new();
422
423 let json = r#"{
424 "name": "Test Preset",
425 "nodes": [
426 {
427 "id": 0,
428 "node_type": "Oscillator",
429 "params": {}
430 }
431 ],
432 "connections": []
433 }"#;
434
435 let result = validator.validate_preset(json);
436 assert!(result.is_ok());
437 }
438
439 #[test]
440 fn test_validate_preset_missing_name() {
441 let validator = SchemaValidator::new();
442
443 let json = r#"{
444 "nodes": [],
445 "connections": []
446 }"#;
447
448 let result = validator.validate_preset(json);
449 assert!(result.is_err());
450
451 let errors = result.unwrap_err();
452 assert_eq!(errors.len(), 1);
453 assert_eq!(errors[0].kind, ValidationErrorKind::MissingField);
454 assert!(errors[0].path.contains("name"));
455 }
456
457 #[test]
458 fn test_validate_preset_duplicate_node_id() {
459 let validator = SchemaValidator::new();
460
461 let json = r#"{
462 "name": "Test",
463 "nodes": [
464 {"id": 0, "node_type": "Oscillator"},
465 {"id": 0, "node_type": "Filter"}
466 ],
467 "connections": []
468 }"#;
469
470 let result = validator.validate_preset(json);
471 assert!(result.is_err());
472
473 let errors = result.unwrap_err();
474 assert!(errors
475 .iter()
476 .any(|e| e.kind == ValidationErrorKind::Duplicate));
477 }
478
479 #[test]
480 fn test_validate_preset_invalid_json() {
481 let validator = SchemaValidator::new();
482
483 let json = "{ invalid json }";
484
485 let result = validator.validate_preset(json);
486 assert!(result.is_err());
487
488 let errors = result.unwrap_err();
489 assert_eq!(errors.len(), 1);
490 assert_eq!(errors[0].kind, ValidationErrorKind::InvalidFormat);
491 }
492
493 #[test]
494 fn test_validate_node_config_param_out_of_range() {
495 let mut validator = SchemaValidator::new();
496
497 let mut schema = NodeSchema::new("Oscillator", 0, 1);
498 schema.add_param(
499 "frequency",
500 ParamSchema::new("frequency", 20.0, 20000.0, 440.0),
501 );
502 validator.register_node_schema("Oscillator", schema);
503
504 let config = serde_json::json!({
505 "params": {
506 "frequency": 30000.0
507 }
508 });
509
510 let result = validator.validate_node_config("Oscillator", &config);
511 assert!(result.is_err());
512
513 let errors = result.unwrap_err();
514 assert_eq!(errors.len(), 1);
515 assert_eq!(errors[0].kind, ValidationErrorKind::OutOfRange);
516 }
517
518 #[test]
519 fn test_node_schema_builder() {
520 let mut schema = NodeSchema::new("Filter", 1, 1);
521 schema.add_param(
522 "cutoff",
523 ParamSchema::new("cutoff", 20.0, 20000.0, 1000.0).with_unit("Hz"),
524 );
525 schema.add_param("resonance", ParamSchema::new("resonance", 0.0, 1.0, 0.5));
526
527 assert_eq!(schema.node_type, "Filter");
528 assert_eq!(schema.num_inputs, 1);
529 assert_eq!(schema.num_outputs, 1);
530 assert_eq!(schema.params.len(), 2);
531 assert!(schema.params.contains_key("cutoff"));
532 assert!(schema.params.contains_key("resonance"));
533 }
534
535 #[test]
536 fn test_validation_error_display() {
537 let error = ValidationError::new(
538 "/nodes/0/id",
539 "Duplicate node ID: 5",
540 ValidationErrorKind::Duplicate,
541 );
542
543 let display = format!("{}", error);
544 assert!(display.contains("/nodes/0/id"));
545 assert!(display.contains("Duplicate node ID: 5"));
546 assert!(display.contains("duplicate"));
547 }
548}