mirror of
https://github.com/microsoft/regorus.git
synced 2026-08-05 02:16:11 +00:00
Builds on #57. Swap Value::Object's payload from Rc<BTreeMap<Value, Value>> to Rc<Object> and migrate all call sites to the Object API. as_object / as_object_mut keep their names but return &Object / &mut Object. The mutable accessor handles Rc::make_mut internally, so callers no longer do it themselves. Object grows into_value() and From<Object> for Value. Value's serializer now delegates to Object::serialize, dropping a duplicate non-string-key stringification path. RVM IterationState::Object is rewritten around ObjectCursor: O(log n) steps over a shared Rc<Object>, no eager pair snapshot. Snapshot independence is preserved by Rc copy-on-write; setup_next_iteration advances the cursor inline and advance() becomes a no-op for this variant. A new iteration_state_object_is_snapshot_independent_of_source test covers CoW against a mutated alias. Value::Set still wraps Rc<BTreeSet<Value>>; the matching Set abstraction and its swap ship in follow-up PRs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2797 lines
82 KiB
Rust
2797 lines
82 KiB
Rust
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT License.
|
|
|
|
#![allow(
|
|
clippy::panic,
|
|
clippy::unwrap_used,
|
|
clippy::expect_used,
|
|
clippy::indexing_slicing,
|
|
clippy::shadow_unrelated,
|
|
clippy::semicolon_if_nothing_returned,
|
|
clippy::pattern_type_mismatch,
|
|
clippy::assertions_on_result_states,
|
|
clippy::as_conversions
|
|
)] // schema suite tests panic/unwrap to assert specific error shapes
|
|
|
|
use super::super::*;
|
|
use crate::{format, vec};
|
|
use serde_json::json;
|
|
|
|
#[test]
|
|
fn test_deserialize_any() {
|
|
let schema = json!({
|
|
"type": "any",
|
|
"description": "any type",
|
|
"default": 42
|
|
});
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::Any {
|
|
description,
|
|
default,
|
|
} => {
|
|
assert_eq!(description.as_deref(), Some("any type"));
|
|
assert_eq!(default, &Some(Value::from(42)));
|
|
}
|
|
_ => panic!("Expected Type::Any"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_integer_all_fields() {
|
|
let schema = json!({
|
|
"type": "integer",
|
|
"description": "an integer",
|
|
"minimum": 1,
|
|
"maximum": 10,
|
|
"default": 5
|
|
});
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::Integer {
|
|
description,
|
|
minimum,
|
|
maximum,
|
|
default,
|
|
} => {
|
|
assert_eq!(description.as_deref(), Some("an integer"));
|
|
assert_eq!(minimum, &Some(1));
|
|
assert_eq!(maximum, &Some(10));
|
|
assert_eq!(default, &Some(Value::from(5)));
|
|
}
|
|
_ => panic!("Expected Type::Integer"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_integer_no_minimum() {
|
|
let schema = json!({
|
|
"type": "integer",
|
|
"description": "no min",
|
|
"maximum": 100,
|
|
"default": 50
|
|
});
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::Integer {
|
|
description,
|
|
minimum,
|
|
maximum,
|
|
default,
|
|
} => {
|
|
assert_eq!(description.as_deref(), Some("no min"));
|
|
assert_eq!(minimum, &None);
|
|
assert_eq!(maximum, &Some(100));
|
|
assert_eq!(default, &Some(Value::from(50)));
|
|
}
|
|
_ => panic!("Expected Type::Integer"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_integer_no_maximum() {
|
|
let schema = json!({
|
|
"type": "integer",
|
|
"description": "no max",
|
|
"minimum": -10,
|
|
"default": 0
|
|
});
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::Integer {
|
|
description,
|
|
minimum,
|
|
maximum,
|
|
default,
|
|
} => {
|
|
assert_eq!(description.as_deref(), Some("no max"));
|
|
assert_eq!(minimum, &Some(-10));
|
|
assert_eq!(maximum, &None);
|
|
assert_eq!(default, &Some(Value::from(0)));
|
|
}
|
|
_ => panic!("Expected Type::Integer"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_integer_only_required() {
|
|
let schema = json!({
|
|
"type": "integer"
|
|
});
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::Integer {
|
|
description,
|
|
minimum,
|
|
maximum,
|
|
default,
|
|
} => {
|
|
assert_eq!(description, &None);
|
|
assert_eq!(minimum, &None);
|
|
assert_eq!(maximum, &None);
|
|
assert_eq!(default, &None);
|
|
}
|
|
_ => panic!("Expected Type::Integer"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_integer_minimum_equals_maximum() {
|
|
let schema = json!({
|
|
"type": "integer",
|
|
"minimum": 5,
|
|
"maximum": 5
|
|
});
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::Integer {
|
|
minimum, maximum, ..
|
|
} => {
|
|
assert_eq!(minimum, &Some(5));
|
|
assert_eq!(maximum, &Some(5));
|
|
}
|
|
_ => panic!("Expected Type::Integer"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_integer_negative_minimum_and_maximum() {
|
|
let schema = json!({
|
|
"type": "integer",
|
|
"minimum": -100,
|
|
"maximum": -1
|
|
});
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::Integer {
|
|
minimum, maximum, ..
|
|
} => {
|
|
assert_eq!(minimum, &Some(-100));
|
|
assert_eq!(maximum, &Some(-1));
|
|
}
|
|
_ => panic!("Expected Type::Integer"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_integer_zero_minimum() {
|
|
let schema = json!({
|
|
"type": "integer",
|
|
"minimum": 0
|
|
});
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::Integer {
|
|
minimum, maximum, ..
|
|
} => {
|
|
assert_eq!(minimum, &Some(0));
|
|
assert_eq!(maximum, &None);
|
|
}
|
|
_ => panic!("Expected Type::Integer"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_integer_large_values() {
|
|
let schema = json!({
|
|
"type": "integer",
|
|
"minimum": i64::MIN,
|
|
"maximum": i64::MAX
|
|
});
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::Integer {
|
|
minimum, maximum, ..
|
|
} => {
|
|
assert_eq!(minimum, &Some(i64::MIN));
|
|
assert_eq!(maximum, &Some(i64::MAX));
|
|
}
|
|
_ => panic!("Expected Type::Integer"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_integer_invalid_minimum_type() {
|
|
let schema = json!({
|
|
"type": "integer",
|
|
"minimum": "not_an_integer"
|
|
});
|
|
let result = Schema::from_serde_json_value(schema);
|
|
assert!(result.is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_integer_invalid_maximum_type() {
|
|
let schema = json!({
|
|
"type": "integer",
|
|
"maximum": [1, 2, 3]
|
|
});
|
|
let result = Schema::from_serde_json_value(schema);
|
|
assert!(result.is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_integer_minimum_greater_than_maximum() {
|
|
let schema = json!({
|
|
"type": "integer",
|
|
"minimum": 10,
|
|
"maximum": 5
|
|
});
|
|
// This will not error at deserialization, but you may want to add validation logic elsewhere.
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::Integer {
|
|
minimum, maximum, ..
|
|
} => {
|
|
assert_eq!(minimum, &Some(10));
|
|
assert_eq!(maximum, &Some(5));
|
|
}
|
|
_ => panic!("Expected Type::Integer"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_number_all_fields() {
|
|
let schema = json!({
|
|
"type": "number",
|
|
"description": "a number",
|
|
"minimum": 1.5,
|
|
"maximum": 10.5,
|
|
"default": 5.5
|
|
});
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::Number {
|
|
description,
|
|
minimum,
|
|
maximum,
|
|
default,
|
|
} => {
|
|
assert_eq!(description.as_deref(), Some("a number"));
|
|
assert_eq!(minimum, &Some(1.5));
|
|
assert_eq!(maximum, &Some(10.5));
|
|
assert_eq!(default, &Some(Value::from(5.5)));
|
|
}
|
|
_ => panic!("Expected Type::Number"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_number_no_minimum() {
|
|
let schema = json!({
|
|
"type": "number",
|
|
"description": "no min",
|
|
"maximum": 100.0,
|
|
"default": 50.0
|
|
});
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::Number {
|
|
description,
|
|
minimum,
|
|
maximum,
|
|
default,
|
|
} => {
|
|
assert_eq!(description.as_deref(), Some("no min"));
|
|
assert_eq!(minimum, &None);
|
|
assert_eq!(maximum, &Some(100.0));
|
|
assert_eq!(default, &Some(Value::from(50.0)));
|
|
}
|
|
_ => panic!("Expected Type::Number"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_number_no_maximum() {
|
|
let schema = json!({
|
|
"type": "number",
|
|
"description": "no max",
|
|
"minimum": -10.0,
|
|
"default": 0.0
|
|
});
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::Number {
|
|
description,
|
|
minimum,
|
|
maximum,
|
|
default,
|
|
} => {
|
|
assert_eq!(description.as_deref(), Some("no max"));
|
|
assert_eq!(minimum, &Some(-10.0));
|
|
assert_eq!(maximum, &None);
|
|
assert_eq!(default, &Some(Value::from(0.0)));
|
|
}
|
|
_ => panic!("Expected Type::Number"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_number_only_required() {
|
|
let schema = json!({
|
|
"type": "number"
|
|
});
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::Number {
|
|
description,
|
|
minimum,
|
|
maximum,
|
|
default,
|
|
} => {
|
|
assert_eq!(description, &None);
|
|
assert_eq!(minimum, &None);
|
|
assert_eq!(maximum, &None);
|
|
assert_eq!(default, &None);
|
|
}
|
|
_ => panic!("Expected Type::Number"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_number_minimum_equals_maximum() {
|
|
let schema = json!({
|
|
"type": "number",
|
|
"minimum": 5.5,
|
|
"maximum": 5.5
|
|
});
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::Number {
|
|
minimum, maximum, ..
|
|
} => {
|
|
assert_eq!(minimum, &Some(5.5));
|
|
assert_eq!(maximum, &Some(5.5));
|
|
}
|
|
_ => panic!("Expected Type::Number"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_number_negative_minimum_and_maximum() {
|
|
let schema = json!({
|
|
"type": "number",
|
|
"minimum": -100.25,
|
|
"maximum": -1.75
|
|
});
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::Number {
|
|
minimum, maximum, ..
|
|
} => {
|
|
assert_eq!(minimum, &Some(-100.25));
|
|
assert_eq!(maximum, &Some(-1.75));
|
|
}
|
|
_ => panic!("Expected Type::Number"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_number_zero_minimum() {
|
|
let schema = json!({
|
|
"type": "number",
|
|
"minimum": 0.0
|
|
});
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::Number {
|
|
minimum, maximum, ..
|
|
} => {
|
|
assert_eq!(minimum, &Some(0.0));
|
|
assert_eq!(maximum, &None);
|
|
}
|
|
_ => panic!("Expected Type::Number"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_number_large_values() {
|
|
let schema = json!({
|
|
"type": "number",
|
|
"minimum": f64::MIN,
|
|
"maximum": f64::MAX
|
|
});
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::Number {
|
|
minimum, maximum, ..
|
|
} => {
|
|
assert_eq!(minimum, &Some(f64::MIN));
|
|
assert_eq!(maximum, &Some(f64::MAX));
|
|
}
|
|
_ => panic!("Expected Type::Number"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_number_invalid_minimum_type() {
|
|
let schema = json!({
|
|
"type": "number",
|
|
"minimum": "not_a_number"
|
|
});
|
|
let result = Schema::from_serde_json_value(schema);
|
|
assert!(result.is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_number_invalid_maximum_type() {
|
|
let schema = json!({
|
|
"type": "number",
|
|
"maximum": [1, 2, 3]
|
|
});
|
|
let result = Schema::from_serde_json_value(schema);
|
|
assert!(result.is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_number_minimum_greater_than_maximum() {
|
|
let schema = json!({
|
|
"type": "number",
|
|
"minimum": 10.0,
|
|
"maximum": 5.0
|
|
});
|
|
// This will not error at deserialization, but you may want to add validation logic elsewhere.
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::Number {
|
|
minimum, maximum, ..
|
|
} => {
|
|
assert_eq!(minimum, &Some(10.0));
|
|
assert_eq!(maximum, &Some(5.0));
|
|
}
|
|
_ => panic!("Expected Type::Number"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_boolean_all_fields() {
|
|
let schema = json!({
|
|
"type": "boolean",
|
|
"description": "a boolean",
|
|
"default": true
|
|
});
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::Boolean {
|
|
description,
|
|
default,
|
|
} => {
|
|
assert_eq!(description.as_deref(), Some("a boolean"));
|
|
assert_eq!(default, &Some(Value::from(true)));
|
|
}
|
|
_ => panic!("Expected Type::Boolean"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_boolean_no_description() {
|
|
let schema = json!({
|
|
"type": "boolean",
|
|
"default": false
|
|
});
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::Boolean {
|
|
description,
|
|
default,
|
|
} => {
|
|
assert_eq!(description, &None);
|
|
assert_eq!(default, &Some(Value::from(false)));
|
|
}
|
|
_ => panic!("Expected Type::Boolean"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_boolean_no_default() {
|
|
let schema = json!({
|
|
"type": "boolean",
|
|
"description": "no default"
|
|
});
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::Boolean {
|
|
description,
|
|
default,
|
|
} => {
|
|
assert_eq!(description.as_deref(), Some("no default"));
|
|
assert_eq!(default, &None);
|
|
}
|
|
_ => panic!("Expected Type::Boolean"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_boolean_only_required() {
|
|
let schema = json!({
|
|
"type": "boolean"
|
|
});
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::Boolean {
|
|
description,
|
|
default,
|
|
} => {
|
|
assert_eq!(description, &None);
|
|
assert_eq!(default, &None);
|
|
}
|
|
_ => panic!("Expected Type::Boolean"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_null_all_fields() {
|
|
let schema = json!({
|
|
"type": "null",
|
|
"description": "a null"
|
|
});
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::Null { description } => {
|
|
assert_eq!(description.as_deref(), Some("a null"));
|
|
}
|
|
_ => panic!("Expected Type::Null"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_null_no_description() {
|
|
let schema = json!({
|
|
"type": "null"
|
|
});
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::Null { description } => {
|
|
assert_eq!(description, &None);
|
|
}
|
|
_ => panic!("Expected Type::Null"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_string_all_fields() {
|
|
let schema = json!({
|
|
"type": "string",
|
|
"description": "a string",
|
|
"minLength": 2,
|
|
"maxLength": 10,
|
|
"pattern": "^abc",
|
|
"default": "abc"
|
|
});
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::String {
|
|
description,
|
|
min_length,
|
|
max_length,
|
|
pattern,
|
|
default,
|
|
} => {
|
|
assert_eq!(description.as_deref(), Some("a string"));
|
|
assert_eq!(min_length, &Some(2));
|
|
assert_eq!(max_length, &Some(10));
|
|
assert_eq!(pattern.as_deref(), Some("^abc"));
|
|
assert_eq!(default, &Some(Value::from("abc")));
|
|
}
|
|
_ => panic!("Expected Type::String"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_string_no_min_max_pattern() {
|
|
let schema = json!({
|
|
"type": "string",
|
|
"description": "no min/max/pattern",
|
|
"default": "foo"
|
|
});
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::String {
|
|
description,
|
|
min_length,
|
|
max_length,
|
|
pattern,
|
|
default,
|
|
} => {
|
|
assert_eq!(description.as_deref(), Some("no min/max/pattern"));
|
|
assert_eq!(min_length, &None);
|
|
assert_eq!(max_length, &None);
|
|
assert_eq!(pattern, &None);
|
|
assert_eq!(default, &Some(Value::from("foo")));
|
|
}
|
|
_ => panic!("Expected Type::String"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_string_only_required() {
|
|
let schema = json!({
|
|
"type": "string"
|
|
});
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::String {
|
|
description,
|
|
min_length,
|
|
max_length,
|
|
pattern,
|
|
default,
|
|
} => {
|
|
assert_eq!(description, &None);
|
|
assert_eq!(min_length, &None);
|
|
assert_eq!(max_length, &None);
|
|
assert_eq!(pattern, &None);
|
|
assert_eq!(default, &None);
|
|
}
|
|
_ => panic!("Expected Type::String"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_string_min_equals_max() {
|
|
let schema = json!({
|
|
"type": "string",
|
|
"minLength": 5,
|
|
"maxLength": 5
|
|
});
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::String {
|
|
min_length,
|
|
max_length,
|
|
..
|
|
} => {
|
|
assert_eq!(min_length, &Some(5));
|
|
assert_eq!(max_length, &Some(5));
|
|
}
|
|
_ => panic!("Expected Type::String"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_string_zero_min_length() {
|
|
let schema = json!({
|
|
"type": "string",
|
|
"minLength": 0
|
|
});
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::String { min_length, .. } => {
|
|
assert_eq!(min_length, &Some(0));
|
|
}
|
|
_ => panic!("Expected Type::String"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_string_invalid_min_length_type() {
|
|
let schema = json!({
|
|
"type": "string",
|
|
"minLength": "not_a_number"
|
|
});
|
|
let result = Schema::from_serde_json_value(schema);
|
|
assert!(result.is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_string_invalid_max_length_type() {
|
|
let schema = json!({
|
|
"type": "string",
|
|
"maxLength": [1, 2, 3]
|
|
});
|
|
let result = Schema::from_serde_json_value(schema);
|
|
assert!(result.is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_string_empty_pattern() {
|
|
let schema = json!({
|
|
"type": "string",
|
|
"pattern": ""
|
|
});
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::String { pattern, .. } => {
|
|
assert_eq!(pattern.as_deref(), Some(""));
|
|
}
|
|
_ => panic!("Expected Type::String"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_string_unicode_pattern() {
|
|
let schema = json!({
|
|
"type": "string",
|
|
"pattern": "^[\\u4e00-\\u9fa5]+$"
|
|
});
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::String { pattern, .. } => {
|
|
assert_eq!(pattern.as_deref(), Some("^[\\u4e00-\\u9fa5]+$"));
|
|
}
|
|
_ => panic!("Expected Type::String"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_string_large_min_max_length() {
|
|
let schema = json!({
|
|
"type": "string",
|
|
"minLength": usize::MAX,
|
|
"maxLength": usize::MAX
|
|
});
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::String {
|
|
min_length,
|
|
max_length,
|
|
..
|
|
} => {
|
|
assert_eq!(min_length, &Some(usize::MAX));
|
|
assert_eq!(max_length, &Some(usize::MAX));
|
|
}
|
|
_ => panic!("Expected Type::String"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_string_invalid_pattern_type() {
|
|
let schema = json!({
|
|
"type": "string",
|
|
"pattern": 123
|
|
});
|
|
let result = Schema::from_serde_json_value(schema);
|
|
assert!(result.is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_array_only_required() {
|
|
let schema = json!({
|
|
"type": "array",
|
|
"items": { "type": "integer" }
|
|
});
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::Array {
|
|
description,
|
|
items,
|
|
min_items,
|
|
max_items,
|
|
default,
|
|
} => {
|
|
assert_eq!(description, &None);
|
|
assert_eq!(min_items, &None);
|
|
assert_eq!(max_items, &None);
|
|
assert_eq!(default, &None);
|
|
// Assert items fields individually
|
|
match items.as_type() {
|
|
Type::Integer {
|
|
description,
|
|
minimum,
|
|
maximum,
|
|
default,
|
|
} => {
|
|
assert_eq!(description, &None);
|
|
assert_eq!(minimum, &None);
|
|
assert_eq!(maximum, &None);
|
|
assert_eq!(default, &None);
|
|
}
|
|
_ => panic!("Expected items to be Type::Integer"),
|
|
}
|
|
}
|
|
_ => panic!("Expected Type::Array"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_array_min_equals_max() {
|
|
let schema = json!({
|
|
"type": "array",
|
|
"items": { "type": "boolean" },
|
|
"minItems": 3,
|
|
"maxItems": 3
|
|
});
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::Array {
|
|
min_items,
|
|
max_items,
|
|
items,
|
|
..
|
|
} => {
|
|
assert_eq!(min_items, &Some(3));
|
|
assert_eq!(max_items, &Some(3));
|
|
match items.as_type() {
|
|
Type::Boolean {
|
|
description,
|
|
default,
|
|
} => {
|
|
assert_eq!(description, &None);
|
|
assert_eq!(default, &None);
|
|
}
|
|
_ => panic!("Expected items to be Type::Boolean"),
|
|
}
|
|
}
|
|
_ => panic!("Expected Type::Array"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_array_zero_min_items() {
|
|
let schema = json!({
|
|
"type": "array",
|
|
"items": { "type": "null" },
|
|
"minItems": 0
|
|
});
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::Array {
|
|
min_items, items, ..
|
|
} => {
|
|
assert_eq!(min_items, &Some(0));
|
|
match items.as_type() {
|
|
Type::Null { description } => {
|
|
assert_eq!(description, &None);
|
|
}
|
|
_ => panic!("Expected items to be Type::Null"),
|
|
}
|
|
}
|
|
_ => panic!("Expected Type::Array"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_array_min_greater_than_max() {
|
|
let schema = json!({
|
|
"type": "array",
|
|
"items": { "type": "string" },
|
|
"minItems": 10,
|
|
"maxItems": 5
|
|
});
|
|
// This will not error at deserialization, but you may want to add validation logic elsewhere.
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::Array {
|
|
min_items,
|
|
max_items,
|
|
items,
|
|
..
|
|
} => {
|
|
assert_eq!(min_items, &Some(10));
|
|
assert_eq!(max_items, &Some(5));
|
|
match items.as_type() {
|
|
Type::String { .. } => {}
|
|
_ => panic!("Expected items to be Type::String"),
|
|
}
|
|
}
|
|
_ => panic!("Expected Type::Array"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_array_large_min_max() {
|
|
let schema = json!({
|
|
"type": "array",
|
|
"items": { "type": "number" },
|
|
"minItems": usize::MAX,
|
|
"maxItems": usize::MAX
|
|
});
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::Array {
|
|
min_items,
|
|
max_items,
|
|
items,
|
|
..
|
|
} => {
|
|
assert_eq!(min_items, &Some(usize::MAX));
|
|
assert_eq!(max_items, &Some(usize::MAX));
|
|
match items.as_type() {
|
|
Type::Number { .. } => {}
|
|
_ => panic!("Expected items to be Type::Number"),
|
|
}
|
|
}
|
|
_ => panic!("Expected Type::Array"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_array_invalid_min_items_type() {
|
|
let schema = json!({
|
|
"type": "array",
|
|
"items": { "type": "string" },
|
|
"minItems": "not_a_number"
|
|
});
|
|
let result = Schema::from_serde_json_value(schema);
|
|
assert!(result.is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_array_invalid_max_items_type() {
|
|
let schema = json!({
|
|
"type": "array",
|
|
"items": { "type": "string" },
|
|
"maxItems": [1, 2, 3]
|
|
});
|
|
let result = Schema::from_serde_json_value(schema);
|
|
assert!(result.is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_array_invalid_items_type() {
|
|
let schema = json!({
|
|
"type": "array",
|
|
"items": 123
|
|
});
|
|
let result = Schema::from_serde_json_value(schema);
|
|
assert!(result.is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_array_items_with_fields() {
|
|
let schema = json!({
|
|
"type": "array",
|
|
"items": {
|
|
"type": "string",
|
|
"description": "inner string",
|
|
"minLength": 1,
|
|
"maxLength": 8,
|
|
"pattern": "foo",
|
|
"default": "bar"
|
|
},
|
|
"minItems": 1,
|
|
"maxItems": 2,
|
|
"description": "outer array",
|
|
"default": ["bar"]
|
|
});
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::Array {
|
|
description,
|
|
items,
|
|
min_items,
|
|
max_items,
|
|
default,
|
|
} => {
|
|
assert_eq!(description.as_deref(), Some("outer array"));
|
|
assert_eq!(min_items, &Some(1));
|
|
assert_eq!(max_items, &Some(2));
|
|
assert_eq!(
|
|
default,
|
|
&Some(Value::Array(Rc::new(vec![Value::from("bar")])))
|
|
);
|
|
match items.as_type() {
|
|
Type::String {
|
|
description,
|
|
min_length,
|
|
max_length,
|
|
pattern,
|
|
default,
|
|
} => {
|
|
assert_eq!(description.as_deref(), Some("inner string"));
|
|
assert_eq!(min_length, &Some(1));
|
|
assert_eq!(max_length, &Some(8));
|
|
assert_eq!(pattern.as_deref(), Some("foo"));
|
|
assert_eq!(default, &Some(Value::from("bar")));
|
|
}
|
|
_ => panic!("Expected items to be Type::String"),
|
|
}
|
|
}
|
|
_ => panic!("Expected Type::Array"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_array_min_items_none_max_items_some() {
|
|
let schema = json!({
|
|
"type": "array",
|
|
"items": { "type": "string" },
|
|
"maxItems": 7
|
|
});
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::Array {
|
|
min_items,
|
|
max_items,
|
|
items,
|
|
..
|
|
} => {
|
|
assert_eq!(min_items, &None);
|
|
assert_eq!(max_items, &Some(7));
|
|
match items.as_type() {
|
|
Type::String { .. } => {}
|
|
_ => panic!("Expected items to be Type::String"),
|
|
}
|
|
}
|
|
_ => panic!("Expected Type::Array"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_array_min_items_some_max_items_none() {
|
|
let schema = json!({
|
|
"type": "array",
|
|
"items": { "type": "boolean" },
|
|
"minItems": 2
|
|
});
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::Array {
|
|
min_items,
|
|
max_items,
|
|
items,
|
|
..
|
|
} => {
|
|
assert_eq!(min_items, &Some(2));
|
|
assert_eq!(max_items, &None);
|
|
match items.as_type() {
|
|
Type::Boolean { .. } => {}
|
|
_ => panic!("Expected items to be Type::Boolean"),
|
|
}
|
|
}
|
|
_ => panic!("Expected Type::Array"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_array_min_items_zero_max_items_zero() {
|
|
let schema = json!({
|
|
"type": "array",
|
|
"items": { "type": "null" },
|
|
"minItems": 0,
|
|
"maxItems": 0
|
|
});
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::Array {
|
|
min_items,
|
|
max_items,
|
|
items,
|
|
..
|
|
} => {
|
|
assert_eq!(min_items, &Some(0));
|
|
assert_eq!(max_items, &Some(0));
|
|
match items.as_type() {
|
|
Type::Null { .. } => {}
|
|
_ => panic!("Expected items to be Type::Null"),
|
|
}
|
|
}
|
|
_ => panic!("Expected Type::Array"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_array_min_items_greater_than_max_items() {
|
|
let schema = json!({
|
|
"type": "array",
|
|
"items": { "type": "number" },
|
|
"minItems": 5,
|
|
"maxItems": 2
|
|
});
|
|
// This is a corner case: minItems > maxItems, should not error at deserialization.
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::Array {
|
|
min_items,
|
|
max_items,
|
|
items,
|
|
..
|
|
} => {
|
|
assert_eq!(min_items, &Some(5));
|
|
assert_eq!(max_items, &Some(2));
|
|
match items.as_type() {
|
|
Type::Number { .. } => {}
|
|
_ => panic!("Expected items to be Type::Number"),
|
|
}
|
|
}
|
|
_ => panic!("Expected Type::Array"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_array_items_with_nested_array() {
|
|
let schema = json!({
|
|
"type": "array",
|
|
"items": {
|
|
"type": "array",
|
|
"items": { "type": "integer" },
|
|
"minItems": 1,
|
|
"maxItems": 2
|
|
},
|
|
"minItems": 2,
|
|
"maxItems": 3
|
|
});
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::Array {
|
|
min_items,
|
|
max_items,
|
|
items,
|
|
..
|
|
} => {
|
|
assert_eq!(min_items, &Some(2));
|
|
assert_eq!(max_items, &Some(3));
|
|
match items.as_type() {
|
|
Type::Array {
|
|
min_items: inner_min,
|
|
max_items: inner_max,
|
|
items: inner_items,
|
|
..
|
|
} => {
|
|
assert_eq!(inner_min, &Some(1));
|
|
assert_eq!(inner_max, &Some(2));
|
|
match inner_items.as_type() {
|
|
Type::Integer { .. } => {}
|
|
_ => panic!("Expected nested items to be Type::Integer"),
|
|
}
|
|
}
|
|
_ => panic!("Expected items to be Type::Array"),
|
|
}
|
|
}
|
|
_ => panic!("Expected Type::Array"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_object_all_fields() {
|
|
let schema = json!({
|
|
"type": "object",
|
|
"description": "an object",
|
|
"properties": {
|
|
"foo": { "type": "string" },
|
|
"bar": { "type": "integer" }
|
|
},
|
|
"required": ["foo"],
|
|
"additionalProperties": { "type": "boolean" },
|
|
"name": "MyObject",
|
|
"default": { "foo": "abc", "bar": 42 }
|
|
});
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::Object {
|
|
description,
|
|
properties,
|
|
required,
|
|
additional_properties,
|
|
name,
|
|
default,
|
|
discriminated_subobject,
|
|
} => {
|
|
assert_eq!(description.as_deref(), Some("an object"));
|
|
assert_eq!(name.as_deref(), Some("MyObject"));
|
|
assert_eq!(
|
|
default,
|
|
&Some(Value::Object(Rc::new(
|
|
[
|
|
("foo".into(), Value::from("abc")),
|
|
("bar".into(), Value::from(42))
|
|
]
|
|
.iter()
|
|
.cloned()
|
|
.collect()
|
|
)))
|
|
);
|
|
// properties
|
|
assert_eq!(properties.len(), 2);
|
|
match properties.get("foo").unwrap().as_type() {
|
|
Type::String { .. } => {}
|
|
_ => panic!("Expected foo to be Type::String"),
|
|
}
|
|
match properties.get("bar").unwrap().as_type() {
|
|
Type::Integer { .. } => {}
|
|
_ => panic!("Expected bar to be Type::Integer"),
|
|
}
|
|
// required
|
|
let req = required.as_ref().expect("required should be present");
|
|
assert_eq!(req.len(), 1);
|
|
assert_eq!(req[0].as_ref(), "foo");
|
|
// additional_properties
|
|
match additional_properties.as_ref().unwrap().as_type() {
|
|
Type::Boolean { .. } => {}
|
|
_ => panic!("Expected additionalProperties to be Type::Boolean"),
|
|
}
|
|
// discriminated_subobject
|
|
assert!(discriminated_subobject.is_none());
|
|
}
|
|
_ => panic!("Expected Type::Object"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_object_only_required() {
|
|
let schema = json!({
|
|
"type": "object",
|
|
"properties": {}
|
|
});
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::Object {
|
|
description,
|
|
properties,
|
|
required,
|
|
additional_properties,
|
|
name,
|
|
default,
|
|
discriminated_subobject,
|
|
} => {
|
|
assert_eq!(description, &None);
|
|
assert_eq!(properties.len(), 0);
|
|
assert!(required.is_none());
|
|
assert!(matches!(
|
|
additional_properties.as_ref().unwrap().as_type(),
|
|
Type::Any { .. }
|
|
));
|
|
assert!(name.is_none());
|
|
assert!(default.is_none());
|
|
assert!(discriminated_subobject.is_none());
|
|
}
|
|
_ => panic!("Expected Type::Object"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_object_with_required_and_additional_properties() {
|
|
let schema = json!({
|
|
"type": "object",
|
|
"properties": {
|
|
"x": { "type": "number" }
|
|
},
|
|
"required": ["x"],
|
|
"additionalProperties": { "type": "string" }
|
|
});
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::Object {
|
|
properties,
|
|
required,
|
|
additional_properties,
|
|
..
|
|
} => {
|
|
assert_eq!(properties.len(), 1);
|
|
match properties.get("x").unwrap().as_type() {
|
|
Type::Number { .. } => {}
|
|
_ => panic!("Expected x to be Type::Number"),
|
|
}
|
|
let req = required.as_ref().expect("required should be present");
|
|
assert_eq!(req.len(), 1);
|
|
assert_eq!(req[0].as_ref(), "x");
|
|
match additional_properties.as_ref().unwrap().as_type() {
|
|
Type::String { .. } => {}
|
|
_ => panic!("Expected additionalProperties to be Type::String"),
|
|
}
|
|
}
|
|
_ => panic!("Expected Type::Object"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_object_with_discriminated_subobject() {
|
|
let schema = json!({
|
|
"type": "object",
|
|
"properties": {},
|
|
"allOf": [
|
|
{
|
|
"if": { "properties": { "kind": { "const": "foo" } } },
|
|
"then": {
|
|
"properties": { "foo": { "type": "string" } },
|
|
"required": ["foo"]
|
|
}
|
|
},
|
|
{
|
|
"if": { "properties": { "kind": { "const": "bar" } } },
|
|
"then": {
|
|
"properties": { "bar": { "type": "integer" } },
|
|
"required": ["bar"]
|
|
}
|
|
}
|
|
]
|
|
});
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::Object {
|
|
discriminated_subobject,
|
|
..
|
|
} => {
|
|
let dso = discriminated_subobject
|
|
.as_ref()
|
|
.expect("should have discriminated_subobject");
|
|
assert_eq!(dso.discriminator.as_ref(), "kind");
|
|
assert_eq!(dso.variants.len(), 2);
|
|
let foo_variant = dso.variants.get("foo").expect("foo variant");
|
|
assert!(foo_variant.properties.contains_key("foo"));
|
|
let bar_variant = dso.variants.get("bar").expect("bar variant");
|
|
assert!(bar_variant.properties.contains_key("bar"));
|
|
}
|
|
_ => panic!("Expected Type::Object"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_object_invalid_properties_type() {
|
|
let schema = json!({
|
|
"type": "object",
|
|
"properties": 123
|
|
});
|
|
let result = Schema::from_serde_json_value(schema);
|
|
assert!(result.is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_object_invalid_required_type() {
|
|
let schema = json!({
|
|
"type": "object",
|
|
"properties": {},
|
|
"required": "not_an_array"
|
|
});
|
|
let result = Schema::from_serde_json_value(schema);
|
|
assert!(result.is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_object_invalid_additional_properties_type() {
|
|
let schema = json!({
|
|
"type": "object",
|
|
"properties": {},
|
|
"additionalProperties": 123
|
|
});
|
|
let result = Schema::from_serde_json_value(schema);
|
|
assert!(result.is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_object_empty_properties_and_required() {
|
|
let schema = json!({
|
|
"type": "object",
|
|
"properties": {},
|
|
"required": []
|
|
});
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::Object {
|
|
properties,
|
|
required,
|
|
..
|
|
} => {
|
|
assert_eq!(properties.len(), 0);
|
|
let req = required.as_ref().expect("required should be present");
|
|
assert!(req.is_empty());
|
|
}
|
|
_ => panic!("Expected Type::Object"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_object_required_not_in_properties() {
|
|
let schema = json!({
|
|
"type": "object",
|
|
"properties": { "x": { "type": "string" } },
|
|
"required": ["y"]
|
|
});
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::Object {
|
|
properties,
|
|
required,
|
|
..
|
|
} => {
|
|
assert_eq!(properties.len(), 1);
|
|
let req = required.as_ref().expect("required should be present");
|
|
assert_eq!(req.len(), 1);
|
|
assert_eq!(req[0].as_ref(), "y");
|
|
}
|
|
_ => panic!("Expected Type::Object"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_object_additional_properties_none() {
|
|
let schema = json!({
|
|
"type": "object",
|
|
"properties": { "foo": { "type": "string" } }
|
|
});
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::Object {
|
|
additional_properties,
|
|
..
|
|
} => {
|
|
// When no additionalProperties field is specified, the default behavior
|
|
// is to allow additional properties of any type
|
|
match additional_properties.as_ref().unwrap().as_type() {
|
|
Type::Any { .. } => {}
|
|
_ => panic!("Expected additionalProperties to default to Type::Any"),
|
|
}
|
|
}
|
|
_ => panic!("Expected Type::Object"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_object_additional_properties_false() {
|
|
let schema = json!({
|
|
"type": "object",
|
|
"properties": { "foo": { "type": "string" } },
|
|
"additionalProperties": false
|
|
});
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::Object {
|
|
additional_properties,
|
|
..
|
|
} => {
|
|
// When additionalProperties is false, no additional properties are allowed
|
|
assert!(
|
|
additional_properties.is_none(),
|
|
"Expected additionalProperties to be None when set to false"
|
|
);
|
|
}
|
|
_ => panic!("Expected Type::Object"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_object_additional_properties_true() {
|
|
let schema = json!({
|
|
"type": "object",
|
|
"properties": { "foo": { "type": "string" } },
|
|
"additionalProperties": true
|
|
});
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::Object {
|
|
additional_properties,
|
|
..
|
|
} => {
|
|
// When additionalProperties is true, additional properties of any type are allowed
|
|
match additional_properties.as_ref().unwrap().as_type() {
|
|
Type::Any { .. } => {}
|
|
_ => panic!("Expected additionalProperties to be Type::Any when set to true"),
|
|
}
|
|
}
|
|
_ => panic!("Expected Type::Object"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_object_name_field_only() {
|
|
let schema = json!({
|
|
"type": "object",
|
|
"properties": {},
|
|
"name": "OnlyName"
|
|
});
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::Object { name, .. } => {
|
|
assert_eq!(name.as_deref(), Some("OnlyName"));
|
|
}
|
|
_ => panic!("Expected Type::Object"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_object_default_empty_object() {
|
|
let schema = json!({
|
|
"type": "object",
|
|
"properties": {},
|
|
"default": {}
|
|
});
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::Object { default, .. } => {
|
|
assert_eq!(default, &Some(Value::new_object()));
|
|
}
|
|
_ => panic!("Expected Type::Object"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_object_with_empty_discriminated_subobject() {
|
|
let schema = json!({
|
|
"type": "object",
|
|
"properties": {},
|
|
"allOf": []
|
|
});
|
|
let result = Schema::from_serde_json_value(schema);
|
|
assert!(result.is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_object_nested_2_levels() {
|
|
let schema = json!({
|
|
"type": "object",
|
|
"properties": {
|
|
"level1": {
|
|
"type": "object",
|
|
"properties": {
|
|
"level2": { "type": "string" }
|
|
}
|
|
}
|
|
}
|
|
});
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::Object { properties, .. } => {
|
|
let level1 = properties.get("level1").expect("level1 property");
|
|
match level1.as_type() {
|
|
Type::Object {
|
|
properties: props2, ..
|
|
} => {
|
|
let level2 = props2.get("level2").expect("level2 property");
|
|
match level2.as_type() {
|
|
Type::String { .. } => {}
|
|
_ => panic!("Expected level2 to be Type::String"),
|
|
}
|
|
}
|
|
_ => panic!("Expected level1 to be Type::Object"),
|
|
}
|
|
}
|
|
_ => panic!("Expected Type::Object"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_object_nested_3_levels() {
|
|
let schema = json!({
|
|
"type": "object",
|
|
"properties": {
|
|
"a": {
|
|
"type": "object",
|
|
"properties": {
|
|
"b": {
|
|
"type": "object",
|
|
"properties": {
|
|
"c": { "type": "integer" }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::Object { properties, .. } => {
|
|
let a = properties.get("a").expect("a property");
|
|
match a.as_type() {
|
|
Type::Object {
|
|
properties: props_b,
|
|
..
|
|
} => {
|
|
let b = props_b.get("b").expect("b property");
|
|
match b.as_type() {
|
|
Type::Object {
|
|
properties: props_c,
|
|
..
|
|
} => {
|
|
let c = props_c.get("c").expect("c property");
|
|
match c.as_type() {
|
|
Type::Integer { .. } => {}
|
|
_ => panic!("Expected c to be Type::Integer"),
|
|
}
|
|
}
|
|
_ => panic!("Expected b to be Type::Object"),
|
|
}
|
|
}
|
|
_ => panic!("Expected a to be Type::Object"),
|
|
}
|
|
}
|
|
_ => panic!("Expected Type::Object"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_enum_all_fields() {
|
|
let schema = json!({
|
|
"enum": ["foo", "bar", 42, true, null],
|
|
"description": "an enum"
|
|
});
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::Enum {
|
|
description,
|
|
values,
|
|
} => {
|
|
assert_eq!(description.as_deref(), Some("an enum"));
|
|
assert_eq!(values.len(), 5);
|
|
assert_eq!(values[0], Value::from("foo"));
|
|
assert_eq!(values[1], Value::from("bar"));
|
|
assert_eq!(values[2], Value::from(42));
|
|
assert_eq!(values[3], Value::from(true));
|
|
assert_eq!(values[4], Value::Null);
|
|
}
|
|
_ => panic!("Expected Type::Enum"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_enum_only_required() {
|
|
let schema = json!({
|
|
"enum": ["a", "b"]
|
|
});
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::Enum {
|
|
description,
|
|
values,
|
|
} => {
|
|
assert_eq!(description, &None);
|
|
assert_eq!(values.len(), 2);
|
|
assert_eq!(values[0], Value::from("a"));
|
|
assert_eq!(values[1], Value::from("b"));
|
|
}
|
|
_ => panic!("Expected Type::Enum"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_enum_empty_values() {
|
|
let schema = json!({
|
|
"enum": [],
|
|
});
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::Enum { values, .. } => {
|
|
assert!(values.is_empty());
|
|
}
|
|
_ => panic!("Expected Type::Enum"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_enum_single_value() {
|
|
let schema = json!({
|
|
"enum": [123]
|
|
});
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::Enum { values, .. } => {
|
|
assert_eq!(values.len(), 1);
|
|
assert_eq!(values[0], Value::from(123));
|
|
}
|
|
_ => panic!("Expected Type::Enum"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_enum_mixed_types() {
|
|
let schema = json!({
|
|
"enum": ["x", 1, false, null, {"foo": "bar"}, [1,2,3]]
|
|
});
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::Enum { values, .. } => {
|
|
assert_eq!(values.len(), 6);
|
|
assert_eq!(values[0], Value::from("x"));
|
|
assert_eq!(values[1], Value::from(1));
|
|
assert_eq!(values[2], Value::from(false));
|
|
assert_eq!(values[3], Value::Null);
|
|
// Object and array types
|
|
match &values[4] {
|
|
Value::Object(obj) => {
|
|
assert_eq!(obj.get(&Value::from("foo")), Some(&Value::from("bar")))
|
|
}
|
|
_ => panic!("Expected object in enum values"),
|
|
}
|
|
match &values[5] {
|
|
Value::Array(arr) => {
|
|
assert_eq!(arr.len(), 3);
|
|
assert_eq!(arr[0], Value::from(1));
|
|
assert_eq!(arr[1], Value::from(2));
|
|
assert_eq!(arr[2], Value::from(3));
|
|
}
|
|
_ => panic!("Expected array in enum values"),
|
|
}
|
|
}
|
|
_ => panic!("Expected Type::Enum"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_enum_invalid_values_type() {
|
|
let schema = json!({
|
|
"enum": "not_an_array"
|
|
});
|
|
let result = Schema::from_serde_json_value(schema);
|
|
assert!(result.is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_enum_with_duplicate_values() {
|
|
let schema = json!({
|
|
"enum": ["dup", "dup", 1, 1, null, null]
|
|
});
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::Enum { values, .. } => {
|
|
assert_eq!(values.len(), 6);
|
|
assert_eq!(values[0], Value::from("dup"));
|
|
assert_eq!(values[1], Value::from("dup"));
|
|
assert_eq!(values[2], Value::from(1));
|
|
assert_eq!(values[3], Value::from(1));
|
|
assert_eq!(values[4], Value::Null);
|
|
assert_eq!(values[5], Value::Null);
|
|
}
|
|
_ => panic!("Expected Type::Enum"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_enum_all_values_null() {
|
|
let schema = json!({
|
|
"enum": [null, null, null]
|
|
});
|
|
let s: Schema = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::Enum { values, .. } => {
|
|
assert_eq!(values.len(), 3);
|
|
assert!(values.iter().all(|v| *v == Value::Null));
|
|
}
|
|
_ => panic!("Expected Type::Enum"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_enum_large_number_of_values() {
|
|
let values: Vec<_> = (0..1000).map(Value::from).collect();
|
|
let schema = json!({
|
|
"enum": values
|
|
});
|
|
let s: Schema = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::Enum { values, .. } => {
|
|
assert_eq!(values.len(), 1000);
|
|
assert_eq!(values[0], Value::from(0));
|
|
assert_eq!(values[999], Value::from(999));
|
|
}
|
|
_ => panic!("Expected Type::Enum"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_enum_values_with_object_non_string_keys() {
|
|
// JSON keys are always strings, but serde_json will parse them as such.
|
|
let schema = json!({
|
|
"enum": [
|
|
{ "1": "one", "true": "bool" }
|
|
]
|
|
});
|
|
let s: Schema = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::Enum { values, .. } => match &values[0] {
|
|
Value::Object(obj) => {
|
|
assert_eq!(*obj.get(&Value::from("1")).expect("1"), Value::from("one"));
|
|
assert_eq!(
|
|
*obj.get(&Value::from("true")).expect("true"),
|
|
Value::from("bool")
|
|
);
|
|
}
|
|
_ => panic!("Expected object in enum values"),
|
|
},
|
|
_ => panic!("Expected Type::Enum"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_enum_values_with_deeply_nested_structures() {
|
|
let schema = json!({
|
|
"enum": [
|
|
{
|
|
"a": [
|
|
{ "b": [1, 2, { "c": null }] }
|
|
]
|
|
}
|
|
]
|
|
});
|
|
let s: Schema = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::Enum { values, .. } => match &values[0] {
|
|
Value::Object(obj) => {
|
|
let a = obj.get(&Value::from("a")).expect("a");
|
|
match a {
|
|
Value::Array(arr) => match &arr[0] {
|
|
Value::Object(inner) => {
|
|
let b = inner.get(&Value::from("b")).expect("b");
|
|
match b {
|
|
Value::Array(barr) => {
|
|
assert_eq!(barr[0], Value::from(1));
|
|
assert_eq!(barr[1], Value::from(2));
|
|
match &barr[2] {
|
|
Value::Object(cobj) => {
|
|
assert_eq!(
|
|
*cobj.get(&Value::from("c")).expect("c"),
|
|
Value::Null
|
|
);
|
|
}
|
|
_ => panic!("Expected object for 'c'"),
|
|
}
|
|
}
|
|
_ => panic!("Expected array for 'b'"),
|
|
}
|
|
}
|
|
_ => panic!("Expected object in 'a' array"),
|
|
},
|
|
_ => panic!("Expected array for 'a'"),
|
|
}
|
|
}
|
|
_ => panic!("Expected object in enum values"),
|
|
},
|
|
_ => panic!("Expected Type::Enum"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_const_all_fields() {
|
|
let schema = json!({
|
|
"const": 42,
|
|
"description": "a constant",
|
|
});
|
|
let s: Schema = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::Const { description, value } => {
|
|
assert_eq!(description.as_deref(), Some("a constant"));
|
|
assert_eq!(value, &Value::from(42));
|
|
}
|
|
_ => panic!("Expected Type::Const"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_const_only_required() {
|
|
let schema = json!({
|
|
"const": "foo"
|
|
});
|
|
let s: Schema = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::Const { description, value } => {
|
|
assert_eq!(description, &None);
|
|
assert_eq!(value, &Value::from("foo"));
|
|
}
|
|
_ => panic!("Expected Type::Const"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_const_value_null() {
|
|
let schema = json!({
|
|
"const": null
|
|
});
|
|
let s: Schema = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::Const { value, .. } => {
|
|
assert_eq!(value, &Value::Null);
|
|
}
|
|
_ => panic!("Expected Type::Const"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_const_value_object() {
|
|
let schema = json!({
|
|
"const": { "foo": "bar", "baz": 1 }
|
|
});
|
|
let s: Schema = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::Const { value, .. } => match value {
|
|
Value::Object(ref obj) => {
|
|
assert_eq!(
|
|
*obj.get(&Value::from("foo")).expect("foo"),
|
|
Value::from("bar")
|
|
);
|
|
assert_eq!(*obj.get(&Value::from("baz")).expect("baz"), Value::from(1));
|
|
}
|
|
_ => panic!("Expected object for const value"),
|
|
},
|
|
_ => panic!("Expected Type::Const"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_const_value_array() {
|
|
let schema = json!({
|
|
"const": [1, 2, 3]
|
|
});
|
|
let s: Schema = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::Const { value, .. } => match value {
|
|
Value::Array(ref arr) => {
|
|
assert_eq!(arr.len(), 3);
|
|
assert_eq!(arr[0], Value::from(1));
|
|
assert_eq!(arr[1], Value::from(2));
|
|
assert_eq!(arr[2], Value::from(3));
|
|
}
|
|
_ => panic!("Expected array for const value"),
|
|
},
|
|
_ => panic!("Expected Type::Const"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_const_value_boolean() {
|
|
let schema = json!({
|
|
"const": true
|
|
});
|
|
let s: Schema = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::Const { value, .. } => {
|
|
assert_eq!(value, &Value::from(true));
|
|
}
|
|
_ => panic!("Expected Type::Const"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_const_value_deeply_nested() {
|
|
let schema = json!({
|
|
"const": {
|
|
"a": [1, { "b": [null, false] }]
|
|
}
|
|
});
|
|
let s: Schema = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::Const { value, .. } => match value {
|
|
Value::Object(ref obj) => {
|
|
let a = obj.get(&Value::from("a")).expect("a");
|
|
match a {
|
|
Value::Array(arr) => {
|
|
assert_eq!(arr[0], Value::from(1));
|
|
match &arr[1] {
|
|
Value::Object(inner) => {
|
|
let b = inner.get(&Value::from("b")).expect("b");
|
|
match b {
|
|
Value::Array(barr) => {
|
|
assert_eq!(barr[0], Value::Null);
|
|
assert_eq!(barr[1], Value::from(false));
|
|
}
|
|
_ => panic!("Expected array for 'b'"),
|
|
}
|
|
}
|
|
_ => panic!("Expected object in 'a' array"),
|
|
}
|
|
}
|
|
_ => panic!("Expected array for 'a'"),
|
|
}
|
|
}
|
|
_ => panic!("Expected object for const value"),
|
|
},
|
|
_ => panic!("Expected Type::Const"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_const_invalid_missing_value() {
|
|
let schema = json!({
|
|
"type": "const"
|
|
});
|
|
let result = Schema::from_serde_json_value(schema);
|
|
assert!(result.is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_anyof_basic() {
|
|
let schema = json!({
|
|
"anyOf": [
|
|
{ "type": "string" },
|
|
{ "type": "integer" }
|
|
]
|
|
});
|
|
let s: Schema = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::AnyOf(variants) => {
|
|
assert_eq!(variants.len(), 2);
|
|
match variants[0].as_type() {
|
|
Type::String { .. } => {}
|
|
_ => panic!("Expected first variant to be Type::String"),
|
|
}
|
|
match variants[1].as_type() {
|
|
Type::Integer { .. } => {}
|
|
_ => panic!("Expected second variant to be Type::Integer"),
|
|
}
|
|
}
|
|
_ => panic!("Expected Type::AnyOf"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_anyof_single_variant() {
|
|
let schema = json!({
|
|
"anyOf": [
|
|
{ "type": "string" }
|
|
]
|
|
});
|
|
let s: Schema = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::AnyOf(variants) => {
|
|
assert_eq!(variants.len(), 1);
|
|
match variants[0].as_type() {
|
|
Type::String { .. } => {}
|
|
_ => panic!("Expected variant to be Type::String"),
|
|
}
|
|
}
|
|
_ => panic!("Expected Type::AnyOf"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_anyof_empty() {
|
|
let schema = json!({
|
|
"anyOf": []
|
|
});
|
|
let s: Schema = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::AnyOf(variants) => {
|
|
assert!(variants.is_empty());
|
|
}
|
|
_ => panic!("Expected Type::AnyOf"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_anyof_mixed_types() {
|
|
let schema = json!({
|
|
"anyOf": [
|
|
{ "type": "string" },
|
|
{ "type": "integer" },
|
|
{ "type": "array", "items": { "type": "boolean" } },
|
|
{ "type": "object", "properties": { "foo": { "type": "null" } } }
|
|
]
|
|
});
|
|
let s: Schema = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::AnyOf(variants) => {
|
|
assert_eq!(variants.len(), 4);
|
|
match variants[0].as_type() {
|
|
Type::String { .. } => {}
|
|
_ => panic!("Expected variant 0 to be Type::String"),
|
|
}
|
|
match variants[1].as_type() {
|
|
Type::Integer { .. } => {}
|
|
_ => panic!("Expected variant 1 to be Type::Integer"),
|
|
}
|
|
match variants[2].as_type() {
|
|
Type::Array { items, .. } => match items.as_type() {
|
|
Type::Boolean { .. } => {}
|
|
_ => panic!("Expected array items to be Type::Boolean"),
|
|
},
|
|
_ => panic!("Expected variant 2 to be Type::Array"),
|
|
}
|
|
match variants[3].as_type() {
|
|
Type::Object { properties, .. } => {
|
|
assert!(properties.contains_key("foo"));
|
|
}
|
|
_ => panic!("Expected variant 3 to be Type::Object"),
|
|
}
|
|
}
|
|
_ => panic!("Expected Type::AnyOf"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_anyof_invalid_variants_type() {
|
|
let schema = json!({
|
|
"anyOf": "not_an_array"
|
|
});
|
|
let result: Result<Schema, _> = Schema::from_serde_json_value(schema);
|
|
assert!(result.is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_anyof_missing_anyof() {
|
|
let schema = json!({});
|
|
let result: Result<Schema, _> = Schema::from_serde_json_value(schema);
|
|
assert!(result.is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_anyof_with_invalid_variant() {
|
|
let schema = json!({
|
|
"anyOf": [
|
|
{ "type": "string" },
|
|
123
|
|
]
|
|
});
|
|
let result: Result<Schema, _> = Schema::from_serde_json_value(schema);
|
|
assert!(result.is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_anyof_deeply_nested() {
|
|
let schema = json!({
|
|
"anyOf": [
|
|
{
|
|
"anyOf": [
|
|
{ "type": "string" },
|
|
{ "type": "integer" }
|
|
]
|
|
},
|
|
{ "type": "boolean" }
|
|
]
|
|
});
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::AnyOf(variants) => {
|
|
assert_eq!(variants.len(), 2);
|
|
match variants[0].as_type() {
|
|
Type::AnyOf(inner) => {
|
|
assert_eq!(inner.len(), 2);
|
|
match inner[0].as_type() {
|
|
Type::String { .. } => {}
|
|
_ => panic!("Expected inner variant 0 to be Type::String"),
|
|
}
|
|
match inner[1].as_type() {
|
|
Type::Integer { .. } => {}
|
|
_ => panic!("Expected inner variant 1 to be Type::Integer"),
|
|
}
|
|
}
|
|
_ => panic!("Expected first variant to be Type::AnyOf"),
|
|
}
|
|
match variants[1].as_type() {
|
|
Type::Boolean { .. } => {}
|
|
_ => panic!("Expected second variant to be Type::Boolean"),
|
|
}
|
|
}
|
|
_ => panic!("Expected Type::AnyOf"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_anyof_all_null() {
|
|
let schema = json!({
|
|
"anyOf": [null, null, null]
|
|
});
|
|
let result = Schema::from_serde_json_value(schema);
|
|
// Should error: null is not a valid schema
|
|
assert!(result.is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_anyof_with_duplicates() {
|
|
let schema = json!({
|
|
"anyOf": [
|
|
{ "type": "string" },
|
|
{ "type": "string" },
|
|
{ "type": "integer" },
|
|
{ "type": "integer" }
|
|
]
|
|
});
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::AnyOf(variants) => {
|
|
assert_eq!(variants.len(), 4);
|
|
match variants[0].as_type() {
|
|
Type::String { .. } => {}
|
|
_ => panic!("Expected variant 0 to be Type::String"),
|
|
}
|
|
match variants[1].as_type() {
|
|
Type::String { .. } => {}
|
|
_ => panic!("Expected variant 1 to be Type::String"),
|
|
}
|
|
match variants[2].as_type() {
|
|
Type::Integer { .. } => {}
|
|
_ => panic!("Expected variant 2 to be Type::Integer"),
|
|
}
|
|
match variants[3].as_type() {
|
|
Type::Integer { .. } => {}
|
|
_ => panic!("Expected variant 3 to be Type::Integer"),
|
|
}
|
|
}
|
|
_ => panic!("Expected Type::AnyOf"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_anyof_large_number_of_variants() {
|
|
let variants: Vec<_> = (0..100)
|
|
.map(|i| json!({ "type": "integer", "default": i }))
|
|
.collect();
|
|
let schema = json!({
|
|
"anyOf": variants
|
|
});
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::AnyOf(variants) => {
|
|
assert_eq!(variants.len(), 100);
|
|
for (i, v) in variants.iter().enumerate() {
|
|
match v.as_type() {
|
|
Type::Integer { default, .. } => {
|
|
assert_eq!(default, &Some(Value::from(i as i64)));
|
|
}
|
|
_ => panic!("Expected all variants to be Type::Integer"),
|
|
}
|
|
}
|
|
}
|
|
_ => panic!("Expected Type::AnyOf"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_anyof_deeply_nested_multiple_levels() {
|
|
let schema = json!({
|
|
"anyOf": [
|
|
{
|
|
"anyOf": [
|
|
{
|
|
"anyOf": [
|
|
{ "type": "string" }
|
|
]
|
|
}
|
|
]
|
|
}
|
|
]
|
|
});
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::AnyOf(outer) => {
|
|
assert_eq!(outer.len(), 1);
|
|
match outer[0].as_type() {
|
|
Type::AnyOf(middle) => {
|
|
assert_eq!(middle.len(), 1);
|
|
match middle[0].as_type() {
|
|
Type::AnyOf(inner) => {
|
|
assert_eq!(inner.len(), 1);
|
|
match inner[0].as_type() {
|
|
Type::String { .. } => {}
|
|
_ => panic!("Expected innermost to be Type::String"),
|
|
}
|
|
}
|
|
_ => panic!("Expected middle to be Type::AnyOf"),
|
|
}
|
|
}
|
|
_ => panic!("Expected outer to be Type::AnyOf"),
|
|
}
|
|
}
|
|
_ => panic!("Expected Type::AnyOf"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_anyof_with_empty_object_variant() {
|
|
let schema = json!({
|
|
"anyOf": [
|
|
{}
|
|
]
|
|
});
|
|
let result = Schema::from_serde_json_value(schema);
|
|
// Should error: empty object is not a valid schema
|
|
assert!(result.is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_anyof_with_invalid_schema_in_list() {
|
|
let schema = json!({
|
|
"anyOf": [
|
|
{ "type": "string" },
|
|
{ "foo": "bar" }
|
|
]
|
|
});
|
|
let result = Schema::from_serde_json_value(schema);
|
|
// Should error: { "foo": "bar" } is not a valid schema
|
|
assert!(result.is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_anyof_all_same_variant() {
|
|
let schema = json!({
|
|
"anyOf": [
|
|
{ "type": "boolean" },
|
|
{ "type": "boolean" },
|
|
{ "type": "boolean" }
|
|
]
|
|
});
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::AnyOf(variants) => {
|
|
assert_eq!(variants.len(), 3);
|
|
for v in variants.iter() {
|
|
match v.as_type() {
|
|
Type::Boolean { .. } => {}
|
|
_ => panic!("Expected all variants to be Type::Boolean"),
|
|
}
|
|
}
|
|
}
|
|
_ => panic!("Expected Type::AnyOf"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_anyof_with_nested_types() {
|
|
let schema = json!({
|
|
"anyOf": [
|
|
{ "type": "string", "minLength": 2 },
|
|
{ "type": "integer", "minimum": 0 },
|
|
{
|
|
"type": "array",
|
|
"items": { "type": "boolean" },
|
|
"minItems": 1
|
|
},
|
|
{
|
|
"type": "object",
|
|
"properties": {
|
|
"foo": { "type": "string" },
|
|
"bar": { "type": "number" }
|
|
},
|
|
"required": ["foo"]
|
|
}
|
|
]
|
|
});
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::AnyOf(variants) => {
|
|
assert_eq!(variants.len(), 4);
|
|
match &variants[0].as_type() {
|
|
Type::String { min_length, .. } => assert_eq!(min_length, &Some(2)),
|
|
_ => panic!("Expected first variant to be Type::String"),
|
|
}
|
|
match &variants[1].as_type() {
|
|
Type::Integer { minimum, .. } => assert_eq!(minimum, &Some(0)),
|
|
_ => panic!("Expected second variant to be Type::Integer"),
|
|
}
|
|
match &variants[2].as_type() {
|
|
Type::Array {
|
|
items, min_items, ..
|
|
} => {
|
|
assert_eq!(min_items, &Some(1));
|
|
match items.as_type() {
|
|
Type::Boolean { .. } => {}
|
|
_ => panic!("Expected array items to be Type::Boolean"),
|
|
}
|
|
}
|
|
_ => panic!("Expected third variant to be Type::Array"),
|
|
}
|
|
match &variants[3].as_type() {
|
|
Type::Object {
|
|
properties,
|
|
required,
|
|
..
|
|
} => {
|
|
assert!(properties.contains_key("foo"));
|
|
assert!(properties.contains_key("bar"));
|
|
let req = required.as_ref().expect("required should be present");
|
|
assert_eq!(req[0].as_ref(), "foo");
|
|
}
|
|
_ => panic!("Expected fourth variant to be Type::Object"),
|
|
}
|
|
}
|
|
_ => panic!("Expected Type::AnyOf"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_anyof_with_deeply_nested_anyof() {
|
|
let schema = json!({
|
|
"anyOf": [
|
|
{
|
|
"anyOf": [
|
|
{ "type": "string" },
|
|
{ "type": "null" }
|
|
]
|
|
},
|
|
{ "type": "integer" }
|
|
]
|
|
});
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::AnyOf(variants) => {
|
|
assert_eq!(variants.len(), 2);
|
|
match &variants[0].as_type() {
|
|
Type::AnyOf(inner_variants) => {
|
|
assert_eq!(inner_variants.len(), 2);
|
|
match inner_variants[0].as_type() {
|
|
Type::String { .. } => {}
|
|
_ => panic!("Expected inner variant 0 to be Type::String"),
|
|
}
|
|
match inner_variants[1].as_type() {
|
|
Type::Null { .. } => {}
|
|
_ => panic!("Expected inner variant 1 to be Type::Null"),
|
|
}
|
|
}
|
|
_ => panic!("Expected first variant to be Type::AnyOf"),
|
|
}
|
|
match &variants[1].as_type() {
|
|
Type::Integer { .. } => {}
|
|
_ => panic!("Expected second variant to be Type::Integer"),
|
|
}
|
|
}
|
|
_ => panic!("Expected Type::AnyOf"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_anyof_with_enum_and_const() {
|
|
let schema = json!({
|
|
"anyOf": [
|
|
{
|
|
"enum": ["a", "b", "c"]
|
|
},
|
|
{
|
|
"const": 42
|
|
}
|
|
]
|
|
});
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::AnyOf(variants) => {
|
|
assert_eq!(variants.len(), 2);
|
|
match &variants[0].as_type() {
|
|
Type::Enum { values, .. } => {
|
|
assert_eq!(
|
|
values.as_ref(),
|
|
&vec![Value::from("a"), Value::from("b"), Value::from("c")]
|
|
);
|
|
}
|
|
_ => panic!("Expected first variant to be Type::Enum"),
|
|
}
|
|
match &variants[1].as_type() {
|
|
Type::Const { value, .. } => {
|
|
assert_eq!(value, &Value::from(42));
|
|
}
|
|
_ => panic!("Expected second variant to be Type::Const"),
|
|
}
|
|
}
|
|
_ => panic!("Expected Type::AnyOf"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_anyof_with_complex_nesting() {
|
|
let schema = json!({
|
|
"anyOf": [
|
|
{
|
|
"type": "object",
|
|
"properties": {
|
|
"x": {
|
|
"anyOf": [
|
|
{ "type": "string" },
|
|
{ "type": "integer" }
|
|
]
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"type": "array",
|
|
"items": {
|
|
"anyOf": [
|
|
{ "type": "boolean" },
|
|
{ "type": "null" }
|
|
]
|
|
}
|
|
}
|
|
]
|
|
});
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::AnyOf(variants) => {
|
|
assert_eq!(variants.len(), 2);
|
|
match &variants[0].as_type() {
|
|
Type::Object { properties, .. } => {
|
|
let x = properties.get("x").expect("x property");
|
|
match x.as_type() {
|
|
Type::AnyOf(inner) => {
|
|
assert_eq!(inner.len(), 2);
|
|
match inner[0].as_type() {
|
|
Type::String { .. } => {}
|
|
_ => panic!("Expected x.anyOf[0] to be Type::String"),
|
|
}
|
|
match inner[1].as_type() {
|
|
Type::Integer { .. } => {}
|
|
_ => panic!("Expected x.anyOf[1] to be Type::Integer"),
|
|
}
|
|
}
|
|
_ => panic!("Expected x to be Type::AnyOf"),
|
|
}
|
|
}
|
|
_ => panic!("Expected first variant to be Type::Object"),
|
|
}
|
|
match &variants[1].as_type() {
|
|
Type::Array { items, .. } => match items.as_type() {
|
|
Type::AnyOf(inner) => {
|
|
assert_eq!(inner.len(), 2);
|
|
match inner[0].as_type() {
|
|
Type::Boolean { .. } => {}
|
|
_ => panic!("Expected items.anyOf[0] to be Type::Boolean"),
|
|
}
|
|
match inner[1].as_type() {
|
|
Type::Null { .. } => {}
|
|
_ => panic!("Expected items.anyOf[1] to be Type::Null"),
|
|
}
|
|
}
|
|
_ => panic!("Expected items to be Type::AnyOf"),
|
|
},
|
|
_ => panic!("Expected second variant to be Type::Array"),
|
|
}
|
|
}
|
|
_ => panic!("Expected Type::AnyOf"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_anyof_with_duplicate_and_null_variants() {
|
|
let schema = json!({
|
|
"anyOf": [
|
|
{ "type": "null" },
|
|
{ "type": "null" },
|
|
{ "type": "string" },
|
|
{ "type": "string" }
|
|
]
|
|
});
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::AnyOf(variants) => {
|
|
assert_eq!(variants.len(), 4);
|
|
assert!(matches!(variants[0].as_type(), Type::Null { .. }));
|
|
assert!(matches!(variants[1].as_type(), Type::Null { .. }));
|
|
assert!(matches!(variants[2].as_type(), Type::String { .. }));
|
|
assert!(matches!(variants[3].as_type(), Type::String { .. }));
|
|
}
|
|
_ => panic!("Expected Type::AnyOf"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_anyof_with_large_number_of_variants() {
|
|
let variants: Vec<_> = (0..100)
|
|
.map(|i| json!({ "type": "integer", "minimum": i }))
|
|
.collect();
|
|
let schema = json!({ "anyOf": variants });
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::AnyOf(variants) => {
|
|
assert_eq!(variants.len(), 100);
|
|
for (i, v) in variants.iter().enumerate() {
|
|
match v.as_type() {
|
|
Type::Integer { minimum, .. } => assert_eq!(minimum, &Some(i as i64)),
|
|
_ => panic!("Expected variant {} to be Type::Integer", i),
|
|
}
|
|
}
|
|
}
|
|
_ => panic!("Expected Type::AnyOf"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_object_with_mixed_discriminators_error() {
|
|
// This test verifies that using different discriminator fields in the same
|
|
// discriminated subobject results in an error
|
|
let schema = json!({
|
|
"type": "object",
|
|
"peekoo": 1,
|
|
"properties": {
|
|
"name": { "type": "string" }
|
|
},
|
|
"allOf": [
|
|
{
|
|
"if": {
|
|
"properties": {
|
|
"type": { "const": "user" }
|
|
}
|
|
},
|
|
"then": {
|
|
"properties": {
|
|
"email": { "type": "string" }
|
|
},
|
|
"required": ["email"]
|
|
}
|
|
},
|
|
{
|
|
"if": {
|
|
"properties": {
|
|
"kind": { "const": "admin" } // Different discriminator field
|
|
}
|
|
},
|
|
"then": {
|
|
"properties": {
|
|
"permissions": {
|
|
"type": "array",
|
|
"items": { "type": "string" }
|
|
}
|
|
},
|
|
"required": ["permissions"]
|
|
}
|
|
}
|
|
]
|
|
});
|
|
|
|
let result = Schema::from_serde_json_value(schema);
|
|
assert!(result.is_err());
|
|
|
|
// Verify the error message mentions single discriminator requirement
|
|
let error_msg = format!("{}", result.unwrap_err());
|
|
assert!(error_msg.contains("single discriminator"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_object_with_multiple_discriminator_properties_error() {
|
|
// This test verifies that having multiple properties in a discriminator specification
|
|
// results in an error
|
|
let schema = json!({
|
|
"type": "object",
|
|
"properties": {
|
|
"name": { "type": "string" }
|
|
},
|
|
"allOf": [
|
|
{
|
|
"if": {
|
|
"properties": {
|
|
"type": { "const": "user" },
|
|
"category": { "const": "external" } // Multiple discriminator properties
|
|
}
|
|
},
|
|
"then": {
|
|
"properties": {
|
|
"email": { "type": "string" }
|
|
}
|
|
}
|
|
}
|
|
]
|
|
});
|
|
|
|
let result = Schema::from_serde_json_value(schema);
|
|
assert!(result.is_err());
|
|
|
|
// Verify the error message mentions exactly one property requirement
|
|
let error_msg = format!("{}", result.unwrap_err());
|
|
assert!(error_msg.contains("exactly one property"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_object_with_no_discriminator_properties_error() {
|
|
// This test verifies that having no properties in a discriminator specification
|
|
// results in an error
|
|
let schema = json!({
|
|
"type": "object",
|
|
"properties": {
|
|
"name": { "type": "string" }
|
|
},
|
|
"allOf": [
|
|
{
|
|
"if": {
|
|
"properties": {} // No discriminator properties
|
|
},
|
|
"then": {
|
|
"properties": {
|
|
"email": { "type": "string" }
|
|
}
|
|
}
|
|
}
|
|
]
|
|
});
|
|
|
|
let result = Schema::from_serde_json_value(schema);
|
|
assert!(result.is_err());
|
|
|
|
// Verify the error message mentions exactly one property requirement
|
|
let error_msg = format!("{}", result.unwrap_err());
|
|
assert!(error_msg.contains("exactly one property"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_object_with_consistent_discriminator_success() {
|
|
// This test verifies that using the same discriminator field consistently works
|
|
let schema = json!({
|
|
"type": "object",
|
|
"properties": {
|
|
"name": { "type": "string" },
|
|
"entityType": { "type": "string" }
|
|
},
|
|
"required": ["name", "entityType"],
|
|
"allOf": [
|
|
{
|
|
"if": {
|
|
"properties": {
|
|
"entityType": { "const": "user" }
|
|
}
|
|
},
|
|
"then": {
|
|
"properties": {
|
|
"email": { "type": "string" },
|
|
"lastLogin": { "type": "string" }
|
|
},
|
|
"required": ["email"]
|
|
}
|
|
},
|
|
{
|
|
"if": {
|
|
"properties": {
|
|
"entityType": { "const": "group" }
|
|
}
|
|
},
|
|
"then": {
|
|
"properties": {
|
|
"members": {
|
|
"type": "array",
|
|
"items": { "type": "string" }
|
|
},
|
|
"permissions": {
|
|
"type": "array",
|
|
"items": { "type": "string" }
|
|
}
|
|
},
|
|
"required": ["members"]
|
|
}
|
|
},
|
|
{
|
|
"if": {
|
|
"properties": {
|
|
"entityType": { "const": "service" }
|
|
}
|
|
},
|
|
"then": {
|
|
"properties": {
|
|
"endpoint": { "type": "string" },
|
|
"healthCheck": { "type": "string" },
|
|
"version": { "type": "string" }
|
|
},
|
|
"required": ["endpoint", "version"]
|
|
}
|
|
}
|
|
]
|
|
});
|
|
|
|
let s = Schema::from_serde_json_value(schema).unwrap();
|
|
match s.as_type() {
|
|
Type::Object {
|
|
discriminated_subobject,
|
|
properties,
|
|
required,
|
|
..
|
|
} => {
|
|
// Verify base properties
|
|
assert!(properties.contains_key("name"));
|
|
assert!(properties.contains_key("entityType"));
|
|
|
|
let req = required.as_ref().expect("required should be present");
|
|
assert!(req.contains(&"name".into()));
|
|
assert!(req.contains(&"entityType".into()));
|
|
|
|
// Verify discriminated subobject
|
|
let dso = discriminated_subobject
|
|
.as_ref()
|
|
.expect("should have discriminated_subobject");
|
|
assert_eq!(dso.discriminator.as_ref(), "entityType");
|
|
assert_eq!(dso.variants.len(), 3);
|
|
|
|
// Verify user variant
|
|
let user_variant = dso.variants.get("user").expect("user variant");
|
|
assert!(user_variant.properties.contains_key("email"));
|
|
assert!(user_variant.properties.contains_key("lastLogin"));
|
|
let user_req = user_variant.required.as_ref().expect("user required");
|
|
assert!(user_req.contains(&"email".into()));
|
|
|
|
// Verify group variant
|
|
let group_variant = dso.variants.get("group").expect("group variant");
|
|
assert!(group_variant.properties.contains_key("members"));
|
|
assert!(group_variant.properties.contains_key("permissions"));
|
|
let group_req = group_variant.required.as_ref().expect("group required");
|
|
assert!(group_req.contains(&"members".into()));
|
|
|
|
// Verify service variant
|
|
let service_variant = dso.variants.get("service").expect("service variant");
|
|
assert!(service_variant.properties.contains_key("endpoint"));
|
|
assert!(service_variant.properties.contains_key("healthCheck"));
|
|
assert!(service_variant.properties.contains_key("version"));
|
|
let service_req = service_variant.required.as_ref().expect("service required");
|
|
assert!(service_req.contains(&"endpoint".into()));
|
|
assert!(service_req.contains(&"version".into()));
|
|
}
|
|
_ => panic!("Expected Type::Object"),
|
|
}
|
|
}
|