use postmortem::{JsonPath, Schema, SchemaLike, ValueValidator};
use serde_json::json;
use stillwater::Validation;
fn boxed<T: ValueValidator + 'static>(schema: T) -> Box<dyn ValueValidator> {
Box::new(schema)
}
#[test]
fn test_one_of_exactly_one_match() {
let schema = Schema::one_of(vec![
boxed(Schema::string().min_len(1)),
boxed(Schema::integer().positive()),
]);
let result = schema.validate(&json!("hello"), &JsonPath::root());
assert!(result.is_success());
let result = schema.validate(&json!(42), &JsonPath::root());
assert!(result.is_success());
}
#[test]
fn test_one_of_no_matches() {
let schema = Schema::one_of(vec![
boxed(Schema::string().min_len(5)),
boxed(Schema::integer().positive()),
]);
let result = schema.validate(&json!(""), &JsonPath::root());
assert!(result.is_failure());
if let Validation::Failure(errors) = result {
let error = errors.iter().next().unwrap();
assert_eq!(error.code, "one_of_none_matched");
assert!(error.message.contains("did not match any of 2 schemas"));
}
}
#[test]
fn test_one_of_multiple_matches() {
let schema = Schema::one_of(vec![
boxed(Schema::string()),
boxed(Schema::string().min_len(1)),
]);
let result = schema.validate(&json!("hello"), &JsonPath::root());
assert!(result.is_failure());
if let Validation::Failure(errors) = result {
let error = errors.iter().next().unwrap();
assert_eq!(error.code, "one_of_multiple_matched");
assert!(error.message.contains("matched 2 schemas"));
assert!(error.message.contains("expected exactly one"));
}
}
#[test]
fn test_one_of_discriminated_union() {
let circle = Schema::object()
.field("type", Schema::string())
.field("radius", Schema::integer().positive());
let rectangle = Schema::object()
.field("type", Schema::string())
.field("width", Schema::integer().positive())
.field("height", Schema::integer().positive());
let shape = Schema::one_of(vec![boxed(circle), boxed(rectangle)]);
let result = shape.validate(
&json!({
"type": "circle",
"radius": 5
}),
&JsonPath::root(),
);
assert!(result.is_success());
let result = shape.validate(
&json!({
"type": "rectangle",
"width": 10,
"height": 20
}),
&JsonPath::root(),
);
assert!(result.is_success());
let result = shape.validate(
&json!({
"type": "circle"
}),
&JsonPath::root(),
);
assert!(result.is_failure());
}
#[test]
fn test_any_of_first_match() {
let schema = Schema::any_of(vec![
boxed(Schema::string().min_len(1)),
boxed(Schema::integer().positive()),
]);
let result = schema.validate(&json!("hello"), &JsonPath::root());
assert!(result.is_success());
}
#[test]
fn test_any_of_later_match() {
let schema = Schema::any_of(vec![
boxed(Schema::string().min_len(1)),
boxed(Schema::integer().positive()),
]);
let result = schema.validate(&json!(42), &JsonPath::root());
assert!(result.is_success());
}
#[test]
fn test_any_of_no_matches() {
let schema = Schema::any_of(vec![
boxed(Schema::string().min_len(5)),
boxed(Schema::integer().positive()),
]);
let result = schema.validate(&json!(""), &JsonPath::root());
assert!(result.is_failure());
if let Validation::Failure(errors) = result {
let error = errors.iter().next().unwrap();
assert_eq!(error.code, "any_of_none_matched");
assert!(error.message.contains("did not match any of 2 schemas"));
}
}
#[test]
fn test_any_of_flexible_id() {
let id = Schema::any_of(vec![
boxed(Schema::string().min_len(1)),
boxed(Schema::integer().positive()),
]);
let result = id.validate(&json!("abc-123"), &JsonPath::root());
assert!(result.is_success());
let result = id.validate(&json!(42), &JsonPath::root());
assert!(result.is_success());
let result = id.validate(&json!(-5), &JsonPath::root());
assert!(result.is_failure());
}
#[test]
fn test_all_of_all_passing() {
let schema = Schema::all_of(vec![
boxed(Schema::string()),
boxed(Schema::string().min_len(1)),
boxed(Schema::string().max_len(10)),
]);
let result = schema.validate(&json!("hello"), &JsonPath::root());
assert!(result.is_success());
}
#[test]
fn test_all_of_some_failing() {
let schema = Schema::all_of(vec![
boxed(Schema::string()),
boxed(Schema::string().min_len(10)), boxed(Schema::string().max_len(3)), ]);
let result = schema.validate(&json!("hello"), &JsonPath::root());
assert!(result.is_failure());
if let Validation::Failure(errors) = result {
assert_eq!(errors.len(), 2);
}
}
#[test]
fn test_all_of_schema_composition() {
let named = Schema::object().field("name", Schema::string().min_len(1));
let timestamped = Schema::object().field("created_at", Schema::string());
let entity = Schema::all_of(vec![boxed(named), boxed(timestamped)]);
let result = entity.validate(
&json!({
"name": "Alice",
"created_at": "2025-01-01"
}),
&JsonPath::root(),
);
assert!(result.is_success());
let result = entity.validate(
&json!({
"name": "Alice"
}),
&JsonPath::root(),
);
assert!(result.is_failure());
let result = entity.validate(
&json!({
"created_at": "2025-01-01"
}),
&JsonPath::root(),
);
assert!(result.is_failure());
}
#[test]
fn test_optional_with_null() {
let schema = Schema::optional(boxed(Schema::string().min_len(1)));
let result = schema.validate(&json!(null), &JsonPath::root());
assert!(result.is_success());
}
#[test]
fn test_optional_with_valid_value() {
let schema = Schema::optional(boxed(Schema::string().min_len(1)));
let result = schema.validate(&json!("hello"), &JsonPath::root());
assert!(result.is_success());
}
#[test]
fn test_optional_with_invalid_value() {
let schema = Schema::optional(boxed(Schema::string().min_len(5)));
let result = schema.validate(&json!("hi"), &JsonPath::root());
assert!(result.is_failure());
}
#[test]
fn test_nested_any_of_in_one_of() {
let flexible_number = Schema::any_of(vec![
boxed(Schema::integer().positive()),
boxed(Schema::string()), ]);
let circle = Schema::object()
.field("type", Schema::string())
.field("radius", flexible_number);
let rectangle = Schema::object()
.field("type", Schema::string())
.field("width", Schema::integer().positive())
.field("height", Schema::integer().positive());
let shape = Schema::one_of(vec![boxed(circle), boxed(rectangle)]);
let result = shape.validate(
&json!({
"type": "circle",
"radius": 5
}),
&JsonPath::root(),
);
assert!(result.is_success());
let result = shape.validate(
&json!({
"type": "circle",
"radius": "5"
}),
&JsonPath::root(),
);
assert!(result.is_success());
}
#[test]
fn test_optional_in_object() {
let user = Schema::object()
.field("email", Schema::string())
.optional("nickname", Schema::string().min_len(1));
let result = user.validate(
&json!({
"email": "alice@example.com",
"nickname": "alice"
}),
&JsonPath::root(),
);
assert!(result.is_success());
let result = user.validate(
&json!({
"email": "alice@example.com"
}),
&JsonPath::root(),
);
assert!(result.is_success());
let result = user.validate(
&json!({
"email": "alice@example.com",
"nickname": null
}),
&JsonPath::root(),
);
assert!(result.is_failure());
let result = user.validate(
&json!({
"email": "alice@example.com",
"nickname": ""
}),
&JsonPath::root(),
);
assert!(result.is_failure());
}
#[test]
fn test_one_of_single_schema() {
let schema = Schema::one_of(vec![boxed(Schema::string())]);
let result = schema.validate(&json!("hello"), &JsonPath::root());
assert!(result.is_success());
let result = schema.validate(&json!(42), &JsonPath::root());
assert!(result.is_failure());
}
#[test]
fn test_any_of_single_schema() {
let schema = Schema::any_of(vec![boxed(Schema::string())]);
let result = schema.validate(&json!("hello"), &JsonPath::root());
assert!(result.is_success());
let result = schema.validate(&json!(42), &JsonPath::root());
assert!(result.is_failure());
}
#[test]
fn test_all_of_single_schema() {
let schema = Schema::all_of(vec![boxed(Schema::string())]);
let result = schema.validate(&json!("hello"), &JsonPath::root());
assert!(result.is_success());
let result = schema.validate(&json!(42), &JsonPath::root());
assert!(result.is_failure());
}
#[test]
fn test_deeply_nested_combinators() {
let strict_string = Schema::all_of(vec![
boxed(Schema::string()),
boxed(Schema::string().min_len(3)),
boxed(Schema::string().max_len(10)),
]);
let value = Schema::any_of(vec![
boxed(strict_string),
boxed(Schema::integer().positive()),
]);
let result = value.validate(&json!("hello"), &JsonPath::root());
assert!(result.is_success());
let result = value.validate(&json!(42), &JsonPath::root());
assert!(result.is_success());
let result = value.validate(&json!("hi"), &JsonPath::root());
assert!(result.is_failure());
let result = value.validate(&json!(-5), &JsonPath::root());
assert!(result.is_failure());
}
#[test]
fn test_combinator_error_paths() {
let schema = Schema::object().field(
"id",
Schema::any_of(vec![
boxed(Schema::string().min_len(1)),
boxed(Schema::integer().positive()),
]),
);
let result = schema.validate(
&json!({
"id": -5
}),
&JsonPath::root(),
);
assert!(result.is_failure());
if let Validation::Failure(errors) = result {
let error = errors.iter().next().unwrap();
assert_eq!(error.path.to_string(), "id");
}
}