Skip to main content

agentic_forge_core/validation/
mod.rs

1//! MCP parameter validation utilities.
2
3use crate::types::{ForgeError, ForgeResult};
4use serde_json::Value;
5
6pub struct McpValidator;
7
8impl McpValidator {
9    pub fn require_string(params: &Value, field: &str) -> ForgeResult<String> {
10        params
11            .get(field)
12            .and_then(|v| v.as_str())
13            .map(|s| s.to_string())
14            .ok_or_else(|| ForgeError::MissingField(field.to_string()))
15    }
16
17    pub fn optional_string(params: &Value, field: &str) -> Option<String> {
18        params
19            .get(field)
20            .and_then(|v| v.as_str())
21            .map(|s| s.to_string())
22    }
23
24    pub fn require_u64(params: &Value, field: &str) -> ForgeResult<u64> {
25        params
26            .get(field)
27            .and_then(|v| v.as_u64())
28            .ok_or_else(|| ForgeError::MissingField(field.to_string()))
29    }
30
31    pub fn optional_u64(params: &Value, field: &str) -> Option<u64> {
32        params.get(field).and_then(|v| v.as_u64())
33    }
34
35    pub fn require_f64(params: &Value, field: &str) -> ForgeResult<f64> {
36        params
37            .get(field)
38            .and_then(|v| v.as_f64())
39            .ok_or_else(|| ForgeError::MissingField(field.to_string()))
40    }
41
42    pub fn optional_f64(params: &Value, field: &str) -> Option<f64> {
43        params.get(field).and_then(|v| v.as_f64())
44    }
45
46    pub fn require_bool(params: &Value, field: &str) -> ForgeResult<bool> {
47        params
48            .get(field)
49            .and_then(|v| v.as_bool())
50            .ok_or_else(|| ForgeError::MissingField(field.to_string()))
51    }
52
53    pub fn optional_bool(params: &Value, field: &str) -> Option<bool> {
54        params.get(field).and_then(|v| v.as_bool())
55    }
56
57    pub fn require_array<'a>(params: &'a Value, field: &str) -> ForgeResult<&'a Vec<Value>> {
58        params
59            .get(field)
60            .and_then(|v| v.as_array())
61            .ok_or_else(|| ForgeError::MissingField(field.to_string()))
62    }
63
64    pub fn optional_array<'a>(params: &'a Value, field: &str) -> Option<&'a Vec<Value>> {
65        params.get(field).and_then(|v| v.as_array())
66    }
67
68    pub fn require_object<'a>(
69        params: &'a Value,
70        field: &str,
71    ) -> ForgeResult<&'a serde_json::Map<String, Value>> {
72        params
73            .get(field)
74            .and_then(|v| v.as_object())
75            .ok_or_else(|| ForgeError::MissingField(field.to_string()))
76    }
77}
78
79#[cfg(test)]
80mod tests {
81    use super::*;
82    use serde_json::json;
83
84    #[test]
85    fn test_require_string() {
86        let params = json!({"name": "test"});
87        assert_eq!(
88            McpValidator::require_string(&params, "name").unwrap(),
89            "test"
90        );
91        assert!(McpValidator::require_string(&params, "missing").is_err());
92    }
93
94    #[test]
95    fn test_optional_string() {
96        let params = json!({"name": "test"});
97        assert_eq!(
98            McpValidator::optional_string(&params, "name"),
99            Some("test".into())
100        );
101        assert_eq!(McpValidator::optional_string(&params, "missing"), None);
102    }
103
104    #[test]
105    fn test_require_u64() {
106        let params = json!({"count": 42});
107        assert_eq!(McpValidator::require_u64(&params, "count").unwrap(), 42);
108        assert!(McpValidator::require_u64(&params, "missing").is_err());
109    }
110
111    #[test]
112    fn test_require_f64() {
113        let params = json!({"score": 3.14});
114        let val = McpValidator::require_f64(&params, "score").unwrap();
115        assert!((val - 3.14).abs() < f64::EPSILON);
116    }
117
118    #[test]
119    fn test_require_bool() {
120        let params = json!({"active": true});
121        assert!(McpValidator::require_bool(&params, "active").unwrap());
122        assert!(McpValidator::require_bool(&params, "missing").is_err());
123    }
124
125    #[test]
126    fn test_require_array() {
127        let params = json!({"items": [1, 2, 3]});
128        assert_eq!(
129            McpValidator::require_array(&params, "items").unwrap().len(),
130            3
131        );
132    }
133
134    #[test]
135    fn test_require_object() {
136        let params = json!({"meta": {"key": "value"}});
137        let obj = McpValidator::require_object(&params, "meta").unwrap();
138        assert!(obj.contains_key("key"));
139    }
140
141    #[test]
142    fn test_optional_u64() {
143        let params = json!({"count": 5});
144        assert_eq!(McpValidator::optional_u64(&params, "count"), Some(5));
145        assert_eq!(McpValidator::optional_u64(&params, "missing"), None);
146    }
147
148    #[test]
149    fn test_optional_f64() {
150        let params = json!({"score": 2.5});
151        assert!(McpValidator::optional_f64(&params, "score").is_some());
152        assert!(McpValidator::optional_f64(&params, "missing").is_none());
153    }
154
155    #[test]
156    fn test_optional_bool() {
157        let params = json!({"flag": false});
158        assert_eq!(McpValidator::optional_bool(&params, "flag"), Some(false));
159    }
160
161    #[test]
162    fn test_optional_array() {
163        let params = json!({"tags": ["a", "b"]});
164        assert!(McpValidator::optional_array(&params, "tags").is_some());
165        assert!(McpValidator::optional_array(&params, "missing").is_none());
166    }
167}