agileplus_trace_validator/
nodes.rs1use crate::{ValidationError, ValidationResult};
4use agileplus_domain::intent_graph::*;
5use regex::Regex;
6
7pub fn validate_node_completeness(graph: &IntentGraph) -> ValidationResult {
9 let mut errors = Vec::new();
10 let re = Regex::new(r"^[A-Z][a-z]+#[a-z0-9\-]+$").unwrap();
11
12 for node in &graph.nodes {
13 if !re.is_match(&node.id.0) {
15 errors.push(ValidationError::InvalidNodeId(format!(
16 "Node ID '{}' does not match required pattern ^[A-Z][a-z]+#[a-z0-9\\-]+$",
17 node.id
18 )));
19 }
20
21 if node.title.trim().is_empty() {
23 errors.push(ValidationError::MissingRequiredField(format!(
24 "Node {} is missing title",
25 node.id
26 )));
27 }
28
29 if node.meta.source.trim().is_empty() {
30 errors.push(ValidationError::MissingRequiredField(format!(
31 "Node {} meta.source is empty",
32 node.id
33 )));
34 }
35
36 if node.meta.timestamp.trim().is_empty() {
37 errors.push(ValidationError::MissingRequiredField(format!(
38 "Node {} meta.timestamp is empty",
39 node.id
40 )));
41 }
42
43 if let Some(c) = node.meta.confidence {
45 if !(0.0..=1.0).contains(&c) {
46 errors.push(ValidationError::MissingRequiredField(format!(
47 "Node {} meta.confidence {} is out of range [0.0, 1.0]",
48 node.id, c
49 )));
50 }
51 }
52 }
53
54 if errors.is_empty() {
55 Ok(())
56 } else {
57 Err(errors)
58 }
59}
60
61pub fn validate_node_ids_only(graph: &IntentGraph) -> ValidationResult {
63 let mut errors = Vec::new();
64 let re = Regex::new(r"^[A-Z][a-z]+#[a-z0-9\-]+$").unwrap();
65
66 for node in &graph.nodes {
67 if !re.is_match(&node.id.0) {
68 errors.push(ValidationError::InvalidNodeId(format!(
69 "Node ID '{}' does not match required pattern",
70 node.id
71 )));
72 }
73 }
74
75 if errors.is_empty() {
76 Ok(())
77 } else {
78 Err(errors)
79 }
80}