1use jsonschema::Validator;
7use serde_json::Value;
8
9use crate::error::WasmError;
10
11pub struct ConfigSchema {
13 validator: Validator,
14}
15
16impl ConfigSchema {
17 pub fn from_json(schema_json: &str) -> Result<Self, WasmError> {
19 let schema: Value =
20 serde_json::from_str(schema_json).map_err(|e| WasmError::SchemaParse(e.to_string()))?;
21
22 let validator = Validator::new(&schema)
23 .map_err(|e| WasmError::SchemaParse(format!("invalid JSON Schema: {}", e)))?;
24
25 Ok(Self { validator })
26 }
27
28 pub fn from_value(schema: &Value) -> Result<Self, WasmError> {
30 let validator = Validator::new(schema)
31 .map_err(|e| WasmError::SchemaParse(format!("invalid JSON Schema: {}", e)))?;
32
33 Ok(Self { validator })
34 }
35
36 pub fn validate(&self, config: &Value) -> Result<(), WasmError> {
38 self.validator
39 .validate(config)
40 .map_err(|e| WasmError::ConfigValidation(e.to_string()))
41 }
42
43 pub fn empty() -> Result<Self, WasmError> {
50 Self::from_json(r#"{"type": "object", "additionalProperties": false}"#)
51 }
52
53 pub fn any() -> Result<Self, WasmError> {
57 Self::from_json("{}")
58 }
59}
60
61#[cfg(test)]
62mod tests {
63 use super::*;
64 use serde_json::json;
65
66 #[test]
67 fn validate_against_schema() {
68 let schema = ConfigSchema::from_json(
69 r#"{
70 "type": "object",
71 "required": ["quota", "window"],
72 "properties": {
73 "quota": { "type": "integer", "minimum": 1 },
74 "window": { "type": "integer", "minimum": 1 }
75 }
76 }"#,
77 )
78 .unwrap();
79
80 let valid = json!({"quota": 100, "window": 60});
82 assert!(schema.validate(&valid).is_ok());
83
84 let missing = json!({"quota": 100});
86 assert!(schema.validate(&missing).is_err());
87
88 let wrong_type = json!({"quota": "100", "window": 60});
90 assert!(schema.validate(&wrong_type).is_err());
91
92 let too_low = json!({"quota": 0, "window": 60});
94 assert!(schema.validate(&too_low).is_err());
95 }
96
97 #[test]
98 fn empty_schema_rejects_properties() {
99 let schema = ConfigSchema::empty().unwrap();
100
101 assert!(schema.validate(&json!({})).is_ok());
103
104 assert!(schema.validate(&json!({"foo": "bar"})).is_err());
106 }
107
108 #[test]
109 fn any_schema_accepts_anything() {
110 let schema = ConfigSchema::any().unwrap();
111
112 assert!(schema.validate(&json!({})).is_ok());
113 assert!(schema.validate(&json!({"anything": "goes"})).is_ok());
114 assert!(schema.validate(&json!(null)).is_ok());
115 assert!(schema.validate(&json!(42)).is_ok());
116 }
117
118 #[test]
119 fn from_value() {
120 let schema_value = json!({
121 "type": "object",
122 "properties": {
123 "name": { "type": "string" }
124 }
125 });
126
127 let schema = ConfigSchema::from_value(&schema_value).unwrap();
128 assert!(schema.validate(&json!({"name": "test"})).is_ok());
129 }
130
131 #[test]
132 fn invalid_schema() {
133 let result = ConfigSchema::from_json(r#"{"type": "not-a-type"}"#);
135 match result {
138 Ok(_) => {} Err(e) => {
140 assert!(e.to_string().contains("Schema") || e.to_string().contains("invalid"))
141 }
142 }
143 }
144}