use serde_json::Value;
use crate::error::{ResolveError, SchemaError, ValidateError};
use crate::resolver::resolve;
use crate::types::ResolveOptions;
pub fn validate(
schema: &Value,
payload: &Value,
options: &ResolveOptions,
) -> Result<(), ValidateError> {
let resolved = resolve(schema, options)?;
validate_against_schema(&resolved, payload)
}
pub fn validate_against_schema(schema: &Value, payload: &Value) -> Result<(), ValidateError> {
let validator = jsonschema::validator_for(schema).map_err(|e| {
ValidateError::Resolve(ResolveError::InvalidSchema {
message: e.to_string(),
})
})?;
let errors: Vec<SchemaError> = validator
.iter_errors(payload)
.map(|e| SchemaError {
path: e.instance_path.to_string(),
message: e.to_string(),
})
.collect();
if errors.is_empty() {
Ok(())
} else {
Err(ValidateError::Invalid { errors })
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::Direction;
use serde_json::json;
#[test]
fn validate_valid_payload() {
let schema = json!({
"type": "object",
"properties": {
"name": { "type": "string" }
},
"required": ["name"]
});
let payload = json!({ "name": "test" });
let options = ResolveOptions::new(Direction::Request, "create");
let result = validate(&schema, &payload, &options);
assert!(result.is_ok());
}
#[test]
fn validate_missing_required_field() {
let schema = json!({
"type": "object",
"properties": {
"name": { "type": "string", "ucp_request": "required" }
}
});
let payload = json!({});
let options = ResolveOptions::new(Direction::Request, "create");
let result = validate(&schema, &payload, &options);
assert!(matches!(result, Err(ValidateError::Invalid { .. })));
}
#[test]
fn validate_wrong_type() {
let schema = json!({
"type": "object",
"properties": {
"name": { "type": "string" }
}
});
let payload = json!({ "name": 123 });
let options = ResolveOptions::new(Direction::Request, "create");
let result = validate(&schema, &payload, &options);
assert!(matches!(result, Err(ValidateError::Invalid { .. })));
}
#[test]
fn validate_omitted_field_rejected() {
let schema = json!({
"type": "object",
"additionalProperties": false,
"properties": {
"id": { "type": "string", "ucp_request": "omit" },
"name": { "type": "string" }
}
});
let payload = json!({ "name": "test", "id": "123" });
let options = ResolveOptions::new(Direction::Request, "create");
let result = validate(&schema, &payload, &options);
assert!(matches!(result, Err(ValidateError::Invalid { .. })));
}
#[test]
fn validate_collects_multiple_errors() {
let schema = json!({
"type": "object",
"properties": {
"name": { "type": "string", "ucp_request": "required" },
"age": { "type": "number", "ucp_request": "required" }
}
});
let payload = json!({});
let options = ResolveOptions::new(Direction::Request, "create");
let result = validate(&schema, &payload, &options);
match result {
Err(ValidateError::Invalid { errors }) => {
assert_eq!(errors.len(), 2);
}
_ => panic!("expected validation error with 2 errors"),
}
}
}