Skip to main content

agileplus_trace_validator/
nodes.rs

1//! Node-level validation: completeness checks, ID format, required fields.
2
3use crate::{ValidationError, ValidationResult};
4use agileplus_domain::intent_graph::*;
5use regex::Regex;
6
7/// Validate that every node has required fields, correct ID format, and valid status.
8pub 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        // Required ID format
14        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        // Required fields
22        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        // Confidence range check
44        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
61/// Validate only node IDs without full graph traversal. Fast check.
62pub 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}