use alloc::boxed::Box;
use alloc::string::String;
use alloc::vec::Vec;
use serde_json::Value;
use crate::error::{ErrorIterator, ValidationError, ValidationErrorBuilder, ValidationErrorKind};
use crate::node::SchemaNode;
use crate::paths::{LazyLocation, Location};
use super::content::ContentEncoding;
use super::{Validate, ValidationContext};
pub struct ContentSchemaValidator {
schema: SchemaNode,
encoding: Option<ContentEncoding>,
media_type: Option<String>,
schema_path: Location,
}
impl ContentSchemaValidator {
#[must_use]
pub fn new(
schema: SchemaNode,
encoding: Option<ContentEncoding>,
media_type: Option<String>,
schema_path: Location,
) -> Self {
Self {
schema,
encoding,
media_type,
schema_path,
}
}
}
impl Validate for ContentSchemaValidator {
fn is_valid(&self, instance: &Value, ctx: &mut ValidationContext) -> bool {
let Value::String(s) = instance else {
return true;
};
let Some(content_value) = self.decode_and_parse(s) else {
return false;
};
self.schema.is_valid(&content_value, ctx)
}
fn validate(
&self,
instance: &Value,
instance_path: &LazyLocation<'_>,
ctx: &mut ValidationContext,
) -> Result<(), ValidationError> {
let Value::String(s) = instance else {
return Ok(());
};
let Some(content_value) = self.decode_and_parse(s) else {
return Err(ValidationErrorBuilder::new(
instance_path.materialize(),
self.schema_path.clone(),
)
.build(ValidationErrorKind::ContentSchema));
};
self.schema
.validate(&content_value, instance_path, ctx)
.map_err(|_| {
ValidationErrorBuilder::new(instance_path.materialize(), self.schema_path.clone())
.build(ValidationErrorKind::ContentSchema)
})
}
fn iter_errors(
&self,
instance: &Value,
instance_path: &LazyLocation<'_>,
ctx: &mut ValidationContext,
) -> ErrorIterator {
let Value::String(s) = instance else {
return Box::new(core::iter::empty());
};
let Some(content_value) = self.decode_and_parse(s) else {
let err =
ValidationErrorBuilder::new(instance_path.materialize(), self.schema_path.clone())
.build(ValidationErrorKind::ContentSchema);
return Box::new(core::iter::once(err));
};
self.schema.iter_errors(&content_value, instance_path, ctx)
}
}
impl ContentSchemaValidator {
fn decode_and_parse(&self, s: &str) -> Option<Value> {
let bytes: Vec<u8> = match &self.encoding {
Some(enc) => enc.decode(s)?,
None => s.as_bytes().to_vec(),
};
if self.media_type.as_deref() == Some("application/json") || self.encoding.is_none() {
let decoded_str = core::str::from_utf8(&bytes).ok()?;
serde_json::from_str::<Value>(decoded_str).ok()
} else {
let decoded_str = core::str::from_utf8(&bytes).ok()?;
Some(Value::String(decoded_str.to_string()))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::keywords::type_::TypeValidator;
use crate::paths::{LazyLocation, Location};
use crate::types::JsonType;
use crate::types::JsonTypeSet;
use serde_json::json;
fn ctx() -> ValidationContext {
ValidationContext::new()
}
fn string_type_schema() -> SchemaNode {
let mut types = JsonTypeSet::new();
types.insert(JsonType::String);
SchemaNode::Validators {
validators: vec![Box::new(TypeValidator::new(types, Location::new()))],
schema_path: Location::new(),
}
}
fn object_type_schema() -> SchemaNode {
let mut types = JsonTypeSet::new();
types.insert(JsonType::Object);
SchemaNode::Validators {
validators: vec![Box::new(TypeValidator::new(types, Location::new()))],
schema_path: Location::new(),
}
}
#[test]
fn content_schema_standalone_valid_json() {
let v = ContentSchemaValidator::new(object_type_schema(), None, None, Location::new());
assert!(v.is_valid(&json!({"name": "test"}), &mut ctx()));
}
#[test]
fn content_schema_standalone_invalid_json() {
let v = ContentSchemaValidator::new(object_type_schema(), None, None, Location::new());
assert!(!v.is_valid(&json!("not json at all"), &mut ctx()));
}
#[test]
fn content_schema_standalone_json_wrong_type() {
let v = ContentSchemaValidator::new(object_type_schema(), None, None, Location::new());
assert!(!v.is_valid(&json!("[1,2,3]"), &mut ctx()));
}
#[test]
fn content_schema_base64_encoded_json() {
let encoded = "eyJhbnN3ZXIiOjQyfQ==";
let v = ContentSchemaValidator::new(
object_type_schema(),
Some(ContentEncoding::Base64),
Some("application/json".into()),
Location::new(),
);
assert!(v.is_valid(&json!(encoded), &mut ctx()));
}
#[test]
fn content_schema_base64_wrong_schema() {
let encoded = "aGVsbG8=";
let v = ContentSchemaValidator::new(
object_type_schema(),
Some(ContentEncoding::Base64),
Some("application/json".into()),
Location::new(),
);
assert!(!v.is_valid(&json!(encoded), &mut ctx()));
}
#[test]
fn content_schema_base64_bad_encoding() {
let v = ContentSchemaValidator::new(
object_type_schema(),
Some(ContentEncoding::Base64),
None,
Location::new(),
);
assert!(!v.is_valid(&json!("not!!base64"), &mut ctx()));
}
#[test]
fn content_schema_non_string_valid() {
let v = ContentSchemaValidator::new(object_type_schema(), None, None, Location::new());
assert!(v.is_valid(&json!(42), &mut ctx()));
assert!(v.is_valid(&json!([1, 2]), &mut ctx()));
}
#[test]
fn content_schema_iter_errors() {
let v = ContentSchemaValidator::new(object_type_schema(), None, None, Location::new());
let errors: Vec<_> = v
.iter_errors(&json!("[1,2]"), &LazyLocation::new(), &mut ctx())
.collect();
assert!(!errors.is_empty());
}
}