vtcode_core/mcp/
schema.rs1use anyhow::Result;
6use serde_json::{Value, json};
7
8pub fn validate_against_schema(schema: &Value, input: &Value) -> Result<()> {
27 if input.is_null() {
28 anyhow::bail!("Input cannot be null");
29 }
30
31 jsonschema::validate(schema, input)
33 .map_err(|err| anyhow::anyhow!("Schema validation failed: {}", err))
34}
35
36pub fn validate_tool_input(input_schema: Option<&Value>, input: &Value) -> Result<()> {
40 if input.is_null() {
41 anyhow::bail!("Tool input cannot be null");
42 }
43
44 if let Some(schema) = input_schema {
46 validate_against_schema(schema, input)?;
47 }
48
49 Ok(())
50}
51
52pub fn simple_schema() -> Value {
54 json!({
55 "type": "object",
56 "properties": {},
57 "$schema": "https://json-schema.org/draft/2020-12/schema"
58 })
59}
60
61#[cfg(test)]
62mod tests {
63 use super::*;
64
65 #[test]
66 fn test_validate_simple_object() {
67 let schema = json!({
68 "type": "object",
69 "properties": {
70 "name": { "type": "string" }
71 }
72 });
73
74 let valid_input = json!({"name": "test"});
75 validate_against_schema(&schema, &valid_input).unwrap();
76
77 let invalid_input = json!({"name": 123});
78 assert!(validate_against_schema(&schema, &invalid_input).is_err());
79 }
80
81 #[test]
82 fn test_validate_required_properties() {
83 let schema = json!({
84 "type": "object",
85 "properties": {
86 "name": { "type": "string" },
87 "age": { "type": "integer" }
88 },
89 "required": ["name"]
90 });
91
92 let valid = json!({"name": "John"});
93 validate_against_schema(&schema, &valid).unwrap();
94
95 let invalid = json!({"age": 30});
96 assert!(validate_against_schema(&schema, &invalid).is_err());
97 }
98
99 #[test]
100 fn test_validate_string_length_constraints() {
101 let schema = json!({
102 "type": "object",
103 "properties": {
104 "name": {
105 "type": "string",
106 "minLength": 1,
107 "maxLength": 50
108 }
109 }
110 });
111
112 let valid = json!({"name": "John"});
113 validate_against_schema(&schema, &valid).unwrap();
114
115 let invalid_empty = json!({"name": ""});
116 assert!(validate_against_schema(&schema, &invalid_empty).is_err());
117
118 let invalid_long = json!({"name": "x".repeat(51)});
119 assert!(validate_against_schema(&schema, &invalid_long).is_err());
120 }
121
122 #[test]
123 fn test_validate_enum_values() {
124 let schema = json!({
125 "type": "object",
126 "properties": {
127 "status": {
128 "type": "string",
129 "enum": ["active", "inactive", "pending"]
130 }
131 }
132 });
133
134 let valid = json!({"status": "active"});
135 validate_against_schema(&schema, &valid).unwrap();
136
137 let invalid = json!({"status": "unknown"});
138 assert!(validate_against_schema(&schema, &invalid).is_err());
139 }
140
141 #[test]
142 fn test_validate_array_items() {
143 let schema = json!({
144 "type": "object",
145 "properties": {
146 "tags": {
147 "type": "array",
148 "items": { "type": "string" }
149 }
150 }
151 });
152
153 let valid = json!({"tags": ["rust", "mcp"]});
154 validate_against_schema(&schema, &valid).unwrap();
155
156 let invalid = json!({"tags": ["rust", 123]});
157 assert!(validate_against_schema(&schema, &invalid).is_err());
158 }
159
160 #[test]
161 fn test_validate_nested_objects() {
162 let schema = json!({
163 "type": "object",
164 "properties": {
165 "user": {
166 "type": "object",
167 "properties": {
168 "name": { "type": "string" },
169 "age": { "type": "integer" }
170 },
171 "required": ["name"]
172 }
173 }
174 });
175
176 let valid = json!({"user": {"name": "John", "age": 30}});
177 validate_against_schema(&schema, &valid).unwrap();
178
179 let invalid = json!({"user": {"age": 30}});
180 assert!(validate_against_schema(&schema, &invalid).is_err());
181 }
182
183 #[test]
184 fn test_validate_tool_input_with_no_schema() {
185 let input = json!({"any": "value"});
186 validate_tool_input(None, &input).unwrap();
187 }
188
189 #[test]
190 fn test_validate_tool_input_with_schema() {
191 let schema = json!({
192 "type": "object",
193 "properties": {
194 "path": { "type": "string" }
195 },
196 "required": ["path"]
197 });
198
199 let valid = json!({"path": "/home"});
200 validate_tool_input(Some(&schema), &valid).unwrap();
201
202 let invalid = json!({});
203 assert!(validate_tool_input(Some(&schema), &invalid).is_err());
204 }
205
206 #[test]
207 fn test_simple_schema() {
208 let schema = simple_schema();
209 let input = json!({});
210 validate_against_schema(&schema, &input).unwrap();
211 }
212
213 #[test]
214 fn test_null_input_rejection() {
215 let schema = json!({"type": "object"});
216 let null_input = json!(null);
217 assert!(validate_against_schema(&schema, &null_input).is_err());
218 }
219}