Skip to main content

agentic_reality/validation/
mod.rs

1//! Validation module — strict input validation with zero silent fallbacks.
2
3use crate::types::error::{RealityError, RealityResult};
4use serde_json::Value;
5
6/// MCP-specific validator for tool inputs.
7pub struct McpValidator;
8
9impl McpValidator {
10    /// Require a string field from JSON params.
11    pub fn require_string(params: &Value, field: &str) -> RealityResult<String> {
12        params
13            .get(field)
14            .and_then(|v| v.as_str())
15            .map(|s| s.to_string())
16            .ok_or_else(|| RealityError::MissingField(field.to_string()))
17    }
18
19    /// Require an optional string field.
20    pub fn optional_string(params: &Value, field: &str) -> Option<String> {
21        params
22            .get(field)
23            .and_then(|v| v.as_str())
24            .map(|s| s.to_string())
25    }
26
27    /// Require a u64 field.
28    pub fn require_u64(params: &Value, field: &str) -> RealityResult<u64> {
29        params
30            .get(field)
31            .and_then(|v| v.as_u64())
32            .ok_or_else(|| RealityError::MissingField(field.to_string()))
33    }
34
35    /// Require a f64 field.
36    pub fn require_f64(params: &Value, field: &str) -> RealityResult<f64> {
37        params
38            .get(field)
39            .and_then(|v| v.as_f64())
40            .ok_or_else(|| RealityError::MissingField(field.to_string()))
41    }
42
43    /// Optional f64 field.
44    pub fn optional_f64(params: &Value, field: &str) -> Option<f64> {
45        params.get(field).and_then(|v| v.as_f64())
46    }
47
48    /// Require a boolean field.
49    pub fn require_bool(params: &Value, field: &str) -> RealityResult<bool> {
50        params
51            .get(field)
52            .and_then(|v| v.as_bool())
53            .ok_or_else(|| RealityError::MissingField(field.to_string()))
54    }
55
56    /// Optional boolean field.
57    pub fn optional_bool(params: &Value, field: &str) -> Option<bool> {
58        params.get(field).and_then(|v| v.as_bool())
59    }
60
61    /// Validate an operation string against allowed values.
62    pub fn validate_operation(operation: &str, allowed: &[&str]) -> RealityResult<()> {
63        if allowed.contains(&operation) {
64            Ok(())
65        } else {
66            Err(RealityError::InvalidOperation(format!(
67                "unknown operation '{}', expected one of: {}",
68                operation,
69                allowed.join(", ")
70            )))
71        }
72    }
73
74    /// Validate a string is non-empty.
75    pub fn validate_non_empty(value: &str, field: &str) -> RealityResult<()> {
76        if value.trim().is_empty() {
77            Err(RealityError::InvalidParameter {
78                field: field.to_string(),
79                reason: "must not be empty".to_string(),
80            })
81        } else {
82            Ok(())
83        }
84    }
85
86    /// Validate a float is in range [0.0, 1.0].
87    pub fn validate_probability(value: f64, field: &str) -> RealityResult<()> {
88        if !(0.0..=1.0).contains(&value) {
89            Err(RealityError::InvalidParameter {
90                field: field.to_string(),
91                reason: format!("must be between 0.0 and 1.0, got {}", value),
92            })
93        } else {
94            Ok(())
95        }
96    }
97}