use jsonschema::Validator;
use serde_json::Value;
use crate::error::WasmError;
pub struct ConfigSchema {
validator: Validator,
}
impl ConfigSchema {
pub fn from_json(schema_json: &str) -> Result<Self, WasmError> {
let schema: Value =
serde_json::from_str(schema_json).map_err(|e| WasmError::SchemaParse(e.to_string()))?;
let validator = Validator::new(&schema)
.map_err(|e| WasmError::SchemaParse(format!("invalid JSON Schema: {}", e)))?;
Ok(Self { validator })
}
pub fn from_value(schema: &Value) -> Result<Self, WasmError> {
let validator = Validator::new(schema)
.map_err(|e| WasmError::SchemaParse(format!("invalid JSON Schema: {}", e)))?;
Ok(Self { validator })
}
pub fn validate(&self, config: &Value) -> Result<(), WasmError> {
self.validator
.validate(config)
.map_err(|e| WasmError::ConfigValidation(e.to_string()))
}
pub fn empty() -> Result<Self, WasmError> {
Self::from_json(r#"{"type": "object", "additionalProperties": false}"#)
}
pub fn any() -> Result<Self, WasmError> {
Self::from_json("{}")
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn validate_against_schema() {
let schema = ConfigSchema::from_json(
r#"{
"type": "object",
"required": ["quota", "window"],
"properties": {
"quota": { "type": "integer", "minimum": 1 },
"window": { "type": "integer", "minimum": 1 }
}
}"#,
)
.unwrap();
let valid = json!({"quota": 100, "window": 60});
assert!(schema.validate(&valid).is_ok());
let missing = json!({"quota": 100});
assert!(schema.validate(&missing).is_err());
let wrong_type = json!({"quota": "100", "window": 60});
assert!(schema.validate(&wrong_type).is_err());
let too_low = json!({"quota": 0, "window": 60});
assert!(schema.validate(&too_low).is_err());
}
#[test]
fn empty_schema_rejects_properties() {
let schema = ConfigSchema::empty().unwrap();
assert!(schema.validate(&json!({})).is_ok());
assert!(schema.validate(&json!({"foo": "bar"})).is_err());
}
#[test]
fn any_schema_accepts_anything() {
let schema = ConfigSchema::any().unwrap();
assert!(schema.validate(&json!({})).is_ok());
assert!(schema.validate(&json!({"anything": "goes"})).is_ok());
assert!(schema.validate(&json!(null)).is_ok());
assert!(schema.validate(&json!(42)).is_ok());
}
#[test]
fn from_value() {
let schema_value = json!({
"type": "object",
"properties": {
"name": { "type": "string" }
}
});
let schema = ConfigSchema::from_value(&schema_value).unwrap();
assert!(schema.validate(&json!({"name": "test"})).is_ok());
}
#[test]
fn invalid_schema() {
let result = ConfigSchema::from_json(r#"{"type": "not-a-type"}"#);
match result {
Ok(_) => {} Err(e) => {
assert!(e.to_string().contains("Schema") || e.to_string().contains("invalid"))
}
}
}
}