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: &serde_json::Value,
instance: &serde_json::Value,
expected_output: &serde_json::Value,
) {
let validator = jsonschema::validator_for(schema).unwrap();
let output = serde_json::to_value(validator.apply(instance).basic()).unwrap();
assert_eq!(&output, expected_output);
}
#[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: &serde_json::Value,
instance: &serde_json::Value,
expected: &serde_json::Value,
) {
let validator = jsonschema::validator_for(schema).unwrap();
let output = serde_json::to_value(validator.apply(instance).basic()).unwrap();
if &output != expected {
let expected_str = serde_json::to_string_pretty(expected).unwrap();
let actual_str = serde_json::to_string_pretty(&output).unwrap();
panic!("\nExpected:\n{}\n\nGot:\n{}\n", expected_str, actual_str);
}
}