jsonschema-rs/jsonschema/tests/output.rs

1021 lines
28 KiB
Rust

use jsonschema::JSONSchema;
use serde_json::json;
use test_case::test_case;
#[test_case{
&json!({"allOf": [{"type": "string", "typeannotation": "value"}, {"maxLength": 20, "lengthannotation": "value"}]}),
&json!{"some string"},
&json!({
"valid": true,
"annotations": [
{
"keywordLocation": "/allOf/0",
"instanceLocation": "",
"annotations": {
"typeannotation": "value"
}
},
{
"keywordLocation": "/allOf/1",
"instanceLocation": "",
"annotations": { "lengthannotation": "value" } }
]
}); "valid allOf"
}]
#[test_case{
&json!({"allOf": [{"type": "array"}, {"maxLength": 4}]}),
&json!{"some string"},
&json!({
"valid": false,
"errors": [
{
"keywordLocation": "/allOf/0/type",
"instanceLocation": "",
"error": "\"some string\" is not of type \"array\""
},
{
"keywordLocation": "/allOf/1/maxLength",
"instanceLocation": "",
"error": "\"some string\" is longer than 4 characters"
}
]
}); "invalid allOf"
}]
#[test_case{
&json!({"allOf": [{"type": "string", "typeannotation": "value"}]}),
&json!{"some string"},
&json!({
"valid": true,
"annotations": [
{
"keywordLocation": "/allOf/0",
"instanceLocation": "",
"annotations": {
"typeannotation": "value"
}
}
]
}); "valid single value allOf"
}]
#[test_case{
&json!({"allOf": [{"type": "array"}]}),
&json!{"some string"},
&json!({
"valid": false,
"errors": [
{
"keywordLocation": "/allOf/0/type",
"instanceLocation": "",
"error": "\"some string\" is not of type \"array\""
}
]
}); "invalid single value allOf"
}]
#[test_case{
&json!({"anyOf": [{"type": "string", "someannotation": "value"}, {"maxLength": 4}, {"minLength": 1}]}),
&json!{"some string"},
&json!({
"valid": true,
"annotations": [
{
"keywordLocation": "/anyOf/0",
"instanceLocation": "",
"annotations": {
"someannotation": "value"
}
}
]
}); "valid anyOf"
}]
#[test_case{
&json!({"anyOf": [{"type": "object"}, {"maxLength": 4}]}),
&json!{"some string"},
&json!({
"valid": false,
"errors": [
{
"keywordLocation": "/anyOf/0/type",
"instanceLocation": "",
"error": "\"some string\" is not of type \"object\""
},
{
"keywordLocation": "/anyOf/1/maxLength",
"instanceLocation": "",
"error": "\"some string\" is longer than 4 characters"
}
]
}); "invalid anyOf"
}]
#[test_case{
&json!({"oneOf": [{"type": "object", "someannotation": "somevalue"}, {"type": "string"}]}),
&json!{{"somekey": "some value"}},
&json!({
"valid": true,
"annotations": [
{
"keywordLocation": "/oneOf/0",
"instanceLocation": "",
"annotations": {
"someannotation": "somevalue"
}
}
]
}); "valid oneOf"
}]
#[test_case{
&json!({"oneOf": [{"type": "object"}, {"maxLength": 4}]}),
&json!{"some string"},
&json!({
"valid": false,
"errors": [
{
"keywordLocation": "/oneOf/0/type",
"instanceLocation": "",
"error": "\"some string\" is not of type \"object\""
},
{
"keywordLocation": "/oneOf/1/maxLength",
"instanceLocation": "",
"error": "\"some string\" is longer than 4 characters"
}
]
}); "invalid oneOf"
}]
#[test_case{
&json!({"oneOf": [{"type": "string"}, {"maxLength": 40}]}),
&json!{"some string"},
&json!({
"valid": false,
"errors": [
{
"keywordLocation": "/oneOf",
"instanceLocation": "",
"error": "more than one subschema succeeded"
},
]
}); "invalid oneOf multiple successes"
}]
#[test_case{
&json!({
"if": {"type": "string", "ifannotation": "ifvalue"},
"then": {"maxLength": 20, "thenannotation": "thenvalue"}
}),
&json!{"some string"},
&json!({
"valid": true,
"annotations": [
{
"keywordLocation": "/if",
"instanceLocation": "",
"annotations": {
"ifannotation": "ifvalue"
}
},
{
"keywordLocation": "/then",
"instanceLocation": "",
"annotations": {
"thenannotation": "thenvalue"
}
},
]
}); "valid if-then"
}]
#[test_case{
&json!({
"if": {"type": "string", "ifannotation": "ifvalue"},
"then": {"maxLength": 4, "thenannotation": "thenvalue"}
}),
&json!{"some string"},
&json!({
"valid": false,
"errors": [
{
"keywordLocation": "/then/maxLength",
"instanceLocation": "",
"error": "\"some string\" is longer than 4 characters"
},
]
}); "invalid if-then"
}]
#[test_case{
&json!({
"if": {"type": "object", "ifannotation": "ifvalue"},
"else": {"maxLength": 20, "elseannotation": "elsevalue"}
}),
&json!{"some string"},
&json!({
"valid": true,
"annotations": [
{
"keywordLocation": "/else",
"instanceLocation": "",
"annotations": {
"elseannotation": "elsevalue"
}
},
]
}); "valid if-else"
}]
#[test_case{
&json!({
"if": {"type": "string", "ifannotation": "ifvalue"},
"else": {"type": "array", "elseannotation": "elsevalue"}
}),
&json!{{"some": "object"}},
&json!({
"valid": false,
"errors": [
{
"keywordLocation": "/else/type",
"instanceLocation": "",
"error": "{\"some\":\"object\"} is not of type \"array\""
},
]
}); "invalid if-else"
}]
#[test_case{
&json!({
"if": {"type": "string", "ifannotation": "ifvalue"},
"then": {"maxLength": 20, "thenannotation": "thenvalue"},
"else": {"type": "number", "elseannotation": "elsevalue"}
}),
&json!{"some string"},
&json!({
"valid": true,
"annotations": [
{
"keywordLocation": "/if",
"instanceLocation": "",
"annotations": {
"ifannotation": "ifvalue"
}
},
{
"keywordLocation": "/then",
"instanceLocation": "",
"annotations": {
"thenannotation": "thenvalue"
}
},
]
}); "valid if-then-else then-branch"
}]
#[test_case{
&json!({
"if": {"type": "string", "ifannotation": "ifvalue"},
"then": {"maxLength": 20, "thenannotation": "thenvalue"},
"else": {"type": "number", "elseannotation": "elsevalue"}
}),
&json!{12},
&json!({
"valid": true,
"annotations": [
{
"keywordLocation": "/else",
"instanceLocation": "",
"annotations": {
"elseannotation": "elsevalue"
}
},
]
}); "valid if-then-else else-branch"
}]
#[test_case{
&json!({
"if": {"type": "string", "ifannotation": "ifvalue"},
"then": {"maxLength": 4, "thenannotation": "thenvalue"},
"else": {"type": "number", "elseannotation": "elsevalue"}
}),
&json!{"12345"},
&json!({
"valid": false,
"errors": [
{
"keywordLocation": "/then/maxLength",
"instanceLocation": "",
"error": "\"12345\" is longer than 4 characters"
},
] }); "invalid if-then-else then branch"
}]
#[test_case{
&json!({
"if": {"type": "string", "ifannotation": "ifvalue"},
"then": {"maxLength": 20, "thenannotation": "thenvalue"},
"else": {"type": "number", "elseannotation": "elsevalue"}
}),
&json!{{"some": "object"}},
&json!({
"valid": false,
"errors": [
{
"keywordLocation": "/else/type",
"instanceLocation": "",
"error": "{\"some\":\"object\"} is not of type \"number\""
},
]
}); "invalid if-then-else else branch"
}]
#[test_case{
&json!({
"type": "array",
"items": {
"type": "number",
"annotation": "value"
}
}),
&json!{[1,2]},
&json!({
"valid": true,
"annotations": [
{
"keywordLocation": "/items",
"instanceLocation": "",
"annotations": true
},
{
"keywordLocation": "/items",
"instanceLocation": "/0",
"annotations": {
"annotation": "value"
}
},
{
"keywordLocation": "/items",
"instanceLocation": "/1",
"annotations": {
"annotation": "value"
}
},
]
}); "valid items"
}]
#[test_case{
&json!({
"type": "array",
"items": {
"type": "number",
"annotation": "value"
}
}),
&json!{[]},
&json!({
"valid": true,
"annotations": [
{
"keywordLocation": "/items",
"instanceLocation": "",
"annotations": false
},
]
}); "valid items empty array"
}]
#[test_case{
&json!({
"type": "array",
"items": {
"type": "string",
"annotation": "value"
}
}),
&json!{[1,2,"3"]},
&json!({
"valid": false,
"errors": [
{
"keywordLocation": "/items/type",
"instanceLocation": "/0",
"error": "1 is not of type \"string\""
},
{
"keywordLocation": "/items/type",
"instanceLocation": "/1",
"error": "2 is not of type \"string\""
},
]
}); "invalid items"
}]
#[test_case{
&json!({
"contains": {
"type": "number",
"annotation": "value",
"maximum": 2
}
}),
&json!{[1,3,2]},
&json!({
"valid": true,
"annotations": [
{
"keywordLocation": "/contains",
"instanceLocation": "",
"annotations": [0, 2]
},
{
"keywordLocation": "/contains",
"instanceLocation": "/0",
"annotations": {
"annotation": "value"
}
},
{
"keywordLocation": "/contains",
"instanceLocation": "/2",
"annotations": {
"annotation": "value"
}
}
]
}); "valid contains"
}]
#[test_case{
&json!({
"contains": {
"type": "number",
"annotation": "value",
"maximum": 2
}
}),
&json!{["one"]},
&json!({
"valid": false,
"errors": [
{
"keywordLocation": "/contains",
"instanceLocation": "",
"error": "None of [\"one\"] are valid under the given schema",
},
]
}); "invalid contains"
}]
#[test_case{
&json!({
"properties": {
"name": {"type": "string", "some": "subannotation"},
"age": {"type": "number"}
}
}),
&json!{{
"name": "some name",
"age": 10
}},
&json!({
"valid": true,
"annotations": [
{
"keywordLocation": "/properties",
"instanceLocation": "",
"annotations": [
"age",
"name"
]
},
{
"keywordLocation": "/properties/name",
"instanceLocation": "/name",
"annotations": {
"some": "subannotation"
}
}
]
}); "valid properties"
}]
#[test_case{
&json!({
"patternProperties": {
"numProp(\\d+)": {"type": "number", "some": "subannotation"},
"stringProp(\\d+)": {"type": "string"},
"unmatchedProp\\S": {"type": "object"},
}
}),
&json!{{
"numProp1": 1,
"numProp2": 2,
"stringProp1": "1"
}},
&json!({
"valid": true,
"annotations": [
{
"keywordLocation": "/patternProperties",
"instanceLocation": "",
"annotations": [
"numProp1",
"numProp2",
"stringProp1"
]
},
{
"keywordLocation": "/patternProperties/numProp(\\d+)",
"instanceLocation": "/numProp1",
"annotations": {
"some": "subannotation"
}
},
{
"keywordLocation": "/patternProperties/numProp(\\d+)",
"instanceLocation": "/numProp2",
"annotations": {
"some": "subannotation"
}
}
]
}); "valid patternProperties"
}]
#[test_case{
&json!({
"patternProperties": {
"numProp(\\d+)": {"type": "number", "some": "subannotation"}
}
}),
&json!{{
"numProp1": 1,
"numProp2": 2,
"stringProp1": "1"
}},
&json!({
"valid": true,
"annotations": [
{
"keywordLocation": "/patternProperties",
"instanceLocation": "",
"annotations": [
"numProp1",
"numProp2",
]
},
{
"keywordLocation": "/patternProperties/numProp(\\d+)",
"instanceLocation": "/numProp1",
"annotations": {
"some": "subannotation"
}
},
{
"keywordLocation": "/patternProperties/numProp(\\d+)",
"instanceLocation": "/numProp2",
"annotations": {
"some": "subannotation"
}
}
]
}); "valid single value patternProperties"
}]
#[test_case{
&json!({
"propertyNames": {"maxLength": 10, "some": "annotation"}
}),
&json!{{
"name": "some name",
}},
&json!({
"valid": true,
"annotations": [
{
"keywordLocation": "/propertyNames",
"instanceLocation": "",
"annotations": {"some": "annotation"}
},
]
}); "valid propertyNames"
}]
fn test_basic_output(
schema_json: &serde_json::Value,
instance: &serde_json::Value,
expected_output: &serde_json::Value,
) {
let schema = JSONSchema::options().compile(schema_json).unwrap();
let output_json = serde_json::to_value(schema.apply(instance).basic()).unwrap();
assert_eq!(&output_json, expected_output);
}
/// These tests are separated from the rest of the basic output tests for convenience, there's
/// nothing different about them but they are all tests of the additionalProperties keyword, which
/// is complicated by the fact that there are eight different implementations based on the
/// interaction between the properties, patternProperties, and additionalProperties keywords.
/// Specifically there are these implementations:
///
/// - AdditionalPropertiesValidator
/// - AdditionalPropertiesFalseValidator
/// - AdditionalPropertiesNotEmptyFalseValidator
/// - AdditionalPropertiesNotEmptyValidator
/// - AdditionalPropertiesWithPatternsValidator
/// - AdditionalPropertiesWithPatternsFalseValidator
/// - AdditionalPropertiesWithPatternsNotEmptyValidator
/// - AdditionalPropertiesWithPatternsNotEmptyFalseValidator
///
/// For each of these we need two test cases, one for errors and one for annotations
#[test_case{
&json!({
"additionalProperties": {"type": "number" }
}),
&json!{{
"name": "somename",
"otherprop": "one"
}},
&json!({
"valid": false,
"errors": [
{
"keywordLocation": "/additionalProperties/type",
"instanceLocation": "/name",
"error": "\"somename\" is not of type \"number\""
},
{
"keywordLocation": "/additionalProperties/type",
"instanceLocation": "/otherprop",
"error": "\"one\" is not of type \"number\""
},
]
}); "invalid AdditionalPropertiesValidator"
}]
#[test_case{
&json!({
"additionalProperties": {"type": "number", "some": "annotation" }
}),
&json!{{
"name": 1,
"otherprop": 2
}},
&json!({
"valid": true,
"annotations": [
{
"keywordLocation": "/additionalProperties",
"instanceLocation": "",
"annotations": ["name", "otherprop"]
},
{
"keywordLocation": "/additionalProperties",
"instanceLocation": "/name",
"annotations": {
"some": "annotation"
}
},
{
"keywordLocation": "/additionalProperties",
"instanceLocation": "/otherprop",
"annotations": {
"some": "annotation"
}
},
]
}); "valid AdditionalPropertiesValidator"
}]
#[test_case{
&json!({
"additionalProperties": false
}),
&json!{{
"name": "somename",
}},
&json!({
"valid": false,
"errors": [
{
"keywordLocation": "/additionalProperties",
"instanceLocation": "",
"error": "False schema does not allow \"somename\""
},
]
}); "invalid AdditionalPropertiesFalseValidator"
}]
#[test_case{
&json!({
"additionalProperties": false
}),
&json!{{}},
&json!({
"valid": true,
"annotations": []
}); "valid AdditionalPropertiesFalseValidator"
}]
#[test_case{
&json!({
"additionalProperties": false,
"properties": {
"name": {"type": "string", "prop": "annotation"}
}
}),
&json!{{
"name": "somename",
}},
&json!({
"valid": true,
"annotations": [
{
"keywordLocation": "/properties/name",
"instanceLocation": "/name",
"annotations": {"prop": "annotation"}
}
]
}); "valid AdditionalPropertiesNotEmptyFalseValidator"
}]
#[test_case{
&json!({
"additionalProperties": false,
"properties": {
"name": {"type": "string", "prop": "annotation"}
}
}),
&json!{{
"name": "somename",
"other": "prop"
}},
&json!({
"valid": false,
"errors": [
{
"keywordLocation": "/additionalProperties",
"instanceLocation": "",
"error": "Additional properties are not allowed ('other' was unexpected)"
}
]
}); "invalid AdditionalPropertiesNotEmptyFalseValidator"
}]
#[test_case{
&json!({
"additionalProperties": {"type": "integer", "other": "annotation"},
"properties": {
"name": {"type": "string", "prop": "annotation"}
}
}),
&json!{{
"name": "somename",
"otherprop": 1
}},
&json!({
"valid": true,
"annotations": [
{
"keywordLocation": "/additionalProperties",
"instanceLocation": "",
"annotations": ["otherprop"]
},
{
"keywordLocation": "/properties/name",
"instanceLocation": "/name",
"annotations": {"prop": "annotation"}
},
{
"keywordLocation": "/additionalProperties",
"instanceLocation": "/otherprop",
"annotations": {"other": "annotation"}
}
]
}); "valid AdditionalPropertiesNotEmptyValidator"
}]
#[test_case{
&json!({
"additionalProperties": {"type": "integer", "other": "annotation"},
"properties": {
"name": {"type": "string", "prop": "annotation"}
}
}),
&json!{{
"name": "somename",
"otherprop": "one"
}},
&json!({
"valid": false,
"errors": [
{
"keywordLocation": "/additionalProperties/type",
"instanceLocation": "/otherprop",
"error": "\"one\" is not of type \"integer\""
},
]
}); "invalid AdditionalPropertiesNotEmptyValidator"
}]
#[test_case{
&json!({
"additionalProperties": {"type": "string", "other": "annotation"},
"patternProperties": {
"^x-": {"type": "integer", "minimum": 5, "patternio": "annotation"},
}
}),
&json!{{
"otherprop": "one",
"x-foo": 7
}},
&json!({
"valid": true,
"annotations": [
{
"keywordLocation": "/additionalProperties",
"instanceLocation": "",
"annotations": ["otherprop"]
},
{
"keywordLocation": "/additionalProperties",
"instanceLocation": "/otherprop",
"annotations": {"other": "annotation"}
},
{
"keywordLocation": "/patternProperties/^x-",
"instanceLocation": "/x-foo",
"annotations": {"patternio": "annotation"}
},
{
"keywordLocation": "/patternProperties",
"instanceLocation": "",
"annotations": ["x-foo"]
}
]
}); "valid AdditionalPropertiesWithPatternsValidator"
}]
#[test_case{
&json!({
"additionalProperties": {"type": "string" },
"patternProperties": {
"^x-": {"type": "integer", "minimum": 5 },
}
}),
&json!{{
"otherprop":1,
"x-foo": 3
}},
&json!({
"valid": false,
"errors": [
{
"keywordLocation": "/additionalProperties/type",
"instanceLocation": "/otherprop",
"error": "1 is not of type \"string\""
},
{
"keywordLocation": "/patternProperties/^x-/minimum",
"instanceLocation": "/x-foo",
"error": "3 is less than the minimum of 5"
},
]
}); "invalid AdditionalPropertiesWithPatternsValidator"
}]
#[test_case{
&json!({
"properties": {
"name": {"type": "string"}
},
"patternProperties": {
"stringProp(\\d+)": {"type": "string" }
},
"additionalProperties": {"type": "number" }
}),
&json!{{
"name": "somename",
"otherprop": "one"
}},
&json!({
"valid": false,
"errors": [
{
"keywordLocation": "/additionalProperties/type",
"instanceLocation": "/otherprop",
"error": "\"one\" is not of type \"number\""
},
]
}); "invalid AdditionalPropertiesWithPatternsNotEmptyValidator"
}]
#[test_case{
&json!({
"properties": {
"name": {"type": "string"}
},
"patternProperties": {
"stringProp(\\d+)": {"type": "string" }
},
"additionalProperties": {"type": "number" }
}),
&json!{{
"name": "somename",
"otherprop": 1
}},
&json!({
"valid": true,
"annotations": [
{
"keywordLocation": "/additionalProperties",
"instanceLocation": "",
"annotations": ["otherprop"]
}
]
}); "valid AdditionalPropertiesWithPatternsNotEmptyValidator"
}]
#[test_case{
&json!({
"properties": {
"name": {"type": "string", "prop": "annotation"}
},
"patternProperties": {
"stringProp(\\d+)": {"type": "string" }
},
"additionalProperties": false
}),
&json!{{
"name": "somename",
"stringProp1": "one"
}},
&json!({
"valid": true,
"annotations": [
{
"keywordLocation": "/properties/name",
"instanceLocation": "/name",
"annotations": {
"prop": "annotation"
}
}
]
}); "valid AdditionalPropertiesWithPatternsNotEmptyFalseValidator"
}]
#[test_case{
&json!({
"properties": {
"name": {"type": "string", "prop": "annotation"}
},
"patternProperties": {
"stringProp(\\d+)": {"type": "string" }
},
"additionalProperties": false
}),
&json!{{
"name": "somename",
"stringProp1": "one",
"otherprop": "something"
}},
&json!({
"valid": false,
"errors": [
{
"keywordLocation": "/additionalProperties",
"instanceLocation": "",
"error": "Additional properties are not allowed ('otherprop' was unexpected)"
}
]
}); "invalid AdditionalPropertiesWithPatternsNotEmptyFalseValidator"
}]
#[test_case{
&json!({
"patternProperties": {
"stringProp(\\d+)": {"type": "string", "some": "annotation"}
},
"additionalProperties": false
}),
&json!{{
"stringProp1": "one",
}},
&json!({
"valid": true,
"annotations": [
{
"keywordLocation": "/patternProperties/stringProp(\\d+)",
"instanceLocation": "/stringProp1",
"annotations": {
"some": "annotation"
}
},
{
"keywordLocation": "/patternProperties",
"instanceLocation": "",
"annotations": ["stringProp1"]
},
]
}); "valid AdditionalPropertiesWithPatternsFalseValidator"
}]
#[test_case{
&json!({
"patternProperties": {
"stringProp(\\d+)": {"type": "string" }
},
"additionalProperties": false
}),
&json!{{
"stringProp1": "one",
"otherprop": "something"
}},
&json!({
"valid": false,
"errors": [
{
"keywordLocation": "/additionalProperties",
"instanceLocation": "",
"error": "Additional properties are not allowed ('otherprop' was unexpected)"
}
]
}); "invalid AdditionalPropertiesWithPatternsFalseValidator"
}]
fn test_additional_properties_basic_output(
schema_json: &serde_json::Value,
instance: &serde_json::Value,
expected_output: &serde_json::Value,
) {
let schema = JSONSchema::options().compile(schema_json).unwrap();
let output_json = serde_json::to_value(schema.apply(instance).basic()).unwrap();
if &output_json != expected_output {
let expected_str = serde_json::to_string_pretty(expected_output).unwrap();
let actual_str = serde_json::to_string_pretty(&output_json).unwrap();
panic!("\nExpected:\n{}\n\nGot:\n{}\n", expected_str, actual_str);
}
}