1use crate::error::{Error, Result};
4use serde_json::{json, Value};
5
6pub struct SchemaBuilder;
8
9impl SchemaBuilder {
10 pub fn string() -> Value {
12 json!({ "type": "string" })
13 }
14
15 pub fn string_enum(values: &[&str]) -> Value {
17 json!({
18 "type": "string",
19 "enum": values
20 })
21 }
22
23 pub fn number() -> Value {
25 json!({ "type": "number" })
26 }
27
28 pub fn integer() -> Value {
30 json!({ "type": "integer" })
31 }
32
33 pub fn boolean() -> Value {
35 json!({ "type": "boolean" })
36 }
37
38 pub fn array(items: Value) -> Value {
40 json!({
41 "type": "array",
42 "items": items
43 })
44 }
45
46 pub fn object(properties: Value, required: &[&str]) -> Value {
48 json!({
49 "type": "object",
50 "properties": properties,
51 "required": required
52 })
53 }
54
55 pub fn with_description(mut schema: Value, description: &str) -> Value {
57 if let Some(obj) = schema.as_object_mut() {
58 obj.insert("description".to_string(), json!(description));
59 }
60 schema
61 }
62}
63
64pub fn validate(value: &Value, schema: &Value) -> Result<()> {
75 if let Some(schema_type) = schema.get("type").and_then(|t| t.as_str()) {
77 match schema_type {
78 "string" => {
79 if !value.is_string() {
80 return Err(Error::ValidationFailed(format!(
81 "Expected string, got {:?}",
82 value
83 )));
84 }
85
86 if let Some(enum_values) = schema.get("enum") {
88 if let Some(enum_array) = enum_values.as_array() {
89 if !enum_array.contains(value) {
90 return Err(Error::ValidationFailed(format!(
91 "Value {:?} not in allowed enum values",
92 value
93 )));
94 }
95 }
96 }
97 }
98 "number" => {
99 if !value.is_f64() && !value.is_i64() && !value.is_u64() {
100 return Err(Error::ValidationFailed(format!(
101 "Expected number, got {:?}",
102 value
103 )));
104 }
105 }
106 "integer" => {
107 if !value.is_i64() && !value.is_u64() {
108 return Err(Error::ValidationFailed(format!(
109 "Expected integer, got {:?}",
110 value
111 )));
112 }
113 }
114 "boolean" => {
115 if !value.is_boolean() {
116 return Err(Error::ValidationFailed(format!(
117 "Expected boolean, got {:?}",
118 value
119 )));
120 }
121 }
122 "array" => {
123 if !value.is_array() {
124 return Err(Error::ValidationFailed(format!(
125 "Expected array, got {:?}",
126 value
127 )));
128 }
129
130 if let Some(items_schema) = schema.get("items") {
132 if let Some(array) = value.as_array() {
133 for item in array {
134 validate(item, items_schema)?;
135 }
136 }
137 }
138 }
139 "object" => {
140 if !value.is_object() {
141 return Err(Error::ValidationFailed(format!(
142 "Expected object, got {:?}",
143 value
144 )));
145 }
146
147 if let Some(required) = schema.get("required") {
149 if let Some(required_array) = required.as_array() {
150 if let Some(obj) = value.as_object() {
151 for req_prop in required_array {
152 if let Some(prop_name) = req_prop.as_str() {
153 if !obj.contains_key(prop_name) {
154 return Err(Error::ValidationFailed(format!(
155 "Missing required property: {}",
156 prop_name
157 )));
158 }
159 }
160 }
161 }
162 }
163 }
164
165 if let Some(properties) = schema.get("properties") {
167 if let Some(props_obj) = properties.as_object() {
168 if let Some(value_obj) = value.as_object() {
169 for (prop_name, prop_value) in value_obj {
170 if let Some(prop_schema) = props_obj.get(prop_name) {
171 validate(prop_value, prop_schema)?;
172 }
173 }
174 }
175 }
176 }
177 }
178 _ => {
179 }
181 }
182 }
183
184 Ok(())
185}
186
187#[cfg(test)]
188mod tests {
189 use super::*;
190
191 #[test]
192 fn test_schema_builder() {
193 let schema = SchemaBuilder::string();
194 assert_eq!(schema["type"], "string");
195
196 let schema = SchemaBuilder::string_enum(&["positive", "negative", "neutral"]);
197 assert_eq!(schema["type"], "string");
198 assert!(schema["enum"].is_array());
199 assert_eq!(schema["enum"].as_array().unwrap().len(), 3);
200
201 let schema = SchemaBuilder::object(
202 json!({
203 "name": SchemaBuilder::string(),
204 "age": SchemaBuilder::integer()
205 }),
206 &["name"],
207 );
208 assert_eq!(schema["type"], "object");
209 assert!(schema["properties"].is_object());
210 assert_eq!(schema["required"].as_array().unwrap().len(), 1);
211 }
212
213 #[test]
214 fn test_with_description() {
215 let schema = SchemaBuilder::with_description(
216 SchemaBuilder::string(),
217 "A user's name",
218 );
219 assert_eq!(schema["description"], "A user's name");
220 }
221
222 #[test]
223 fn test_validate_string() {
224 let schema = SchemaBuilder::string();
225 let value = json!("hello");
226
227 assert!(validate(&value, &schema).is_ok());
228
229 let invalid = json!(123);
230 assert!(validate(&invalid, &schema).is_err());
231 }
232
233 #[test]
234 fn test_validate_enum() {
235 let schema = SchemaBuilder::string_enum(&["positive", "negative", "neutral"]);
236 let value = json!("positive");
237
238 assert!(validate(&value, &schema).is_ok());
239
240 let invalid = json!("unknown");
241 assert!(validate(&invalid, &schema).is_err());
242 }
243
244 #[test]
245 fn test_validate_integer() {
246 let schema = SchemaBuilder::integer();
247 let value = json!(42);
248
249 assert!(validate(&value, &schema).is_ok());
250
251 let invalid = json!(3.14);
252 assert!(validate(&invalid, &schema).is_err());
253 }
254
255 #[test]
256 fn test_validate_array() {
257 let schema = SchemaBuilder::array(SchemaBuilder::string());
258 let value = json!(["a", "b", "c"]);
259
260 assert!(validate(&value, &schema).is_ok());
261
262 let invalid_type = json!("not an array");
263 assert!(validate(&invalid_type, &schema).is_err());
264
265 let invalid_items = json!([1, 2, 3]);
266 assert!(validate(&invalid_items, &schema).is_err());
267 }
268
269 #[test]
270 fn test_validate_object() {
271 let schema = SchemaBuilder::object(
272 json!({
273 "name": SchemaBuilder::string(),
274 "age": SchemaBuilder::integer()
275 }),
276 &["name"],
277 );
278
279 let value = json!({
280 "name": "Alice",
281 "age": 30
282 });
283 assert!(validate(&value, &schema).is_ok());
284
285 let missing_required = json!({
287 "age": 30
288 });
289 assert!(validate(&missing_required, &schema).is_err());
290
291 let wrong_type = json!({
293 "name": "Alice",
294 "age": "thirty"
295 });
296 assert!(validate(&wrong_type, &schema).is_err());
297 }
298
299 #[test]
300 fn test_validate_nested() {
301 let schema = SchemaBuilder::object(
302 json!({
303 "user": SchemaBuilder::object(
304 json!({
305 "name": SchemaBuilder::string(),
306 "tags": SchemaBuilder::array(SchemaBuilder::string())
307 }),
308 &["name"]
309 )
310 }),
311 &["user"],
312 );
313
314 let value = json!({
315 "user": {
316 "name": "Bob",
317 "tags": ["admin", "user"]
318 }
319 });
320 assert!(validate(&value, &schema).is_ok());
321
322 let invalid = json!({
323 "user": {
324 "name": "Bob",
325 "tags": [1, 2, 3]
326 }
327 });
328 assert!(validate(&invalid, &schema).is_err());
329 }
330}