codekingpro/portable-devtools
114k
1from __future__ import annotations2 3from collections import deque, namedtuple4from contextlib import contextmanager5from decimal import Decimal6from io import BytesIO7from typing import Any8from unittest import TestCase, mock9from urllib.request import pathname2url10import json11import os12import sys13import tempfile14import warnings15 16from attrs import define, field17from referencing.jsonschema import DRAFT20201218import referencing.exceptions19 20from jsonschema import (21 FormatChecker,22 TypeChecker,23 exceptions,24 protocols,25 validators,26)27 28 29def fail(validator, errors, instance, schema):30 for each in errors:31 each.setdefault("message", "You told me to fail!")32 yield exceptions.ValidationError(**each)33 34 35class TestCreateAndExtend(TestCase):36 def setUp(self):37 self.addCleanup(38 self.assertEqual,39 validators._META_SCHEMAS,40 dict(validators._META_SCHEMAS),41 )42 self.addCleanup(43 self.assertEqual,44 validators._VALIDATORS,45 dict(validators._VALIDATORS),46 )47 48 self.meta_schema = {"$id": "some://meta/schema"}49 self.validators = {"fail": fail}50 self.type_checker = TypeChecker()51 self.Validator = validators.create(52 meta_schema=self.meta_schema,53 validators=self.validators,54 type_checker=self.type_checker,55 )56 57 def test_attrs(self):58 self.assertEqual(59 (60 self.Validator.VALIDATORS,61 self.Validator.META_SCHEMA,62 self.Validator.TYPE_CHECKER,63 ), (64 self.validators,65 self.meta_schema,66 self.type_checker,67 ),68 )69 70 def test_init(self):71 schema = {"fail": []}72 self.assertEqual(self.Validator(schema).schema, schema)73 74 def test_iter_errors_successful(self):75 schema = {"fail": []}76 validator = self.Validator(schema)77 78 errors = list(validator.iter_errors("hello"))79 self.assertEqual(errors, [])80 81 def test_iter_errors_one_error(self):82 schema = {"fail": [{"message": "Whoops!"}]}83 validator = self.Validator(schema)84 85 expected_error = exceptions.ValidationError(86 "Whoops!",87 instance="goodbye",88 schema=schema,89 validator="fail",90 validator_value=[{"message": "Whoops!"}],91 schema_path=deque(["fail"]),92 )93 94 errors = list(validator.iter_errors("goodbye"))95 self.assertEqual(len(errors), 1)96 self.assertEqual(errors[0]._contents(), expected_error._contents())97 98 def test_iter_errors_multiple_errors(self):99 schema = {100 "fail": [101 {"message": "First"},102 {"message": "Second!", "validator": "asdf"},103 {"message": "Third"},104 ],105 }106 validator = self.Validator(schema)107 108 errors = list(validator.iter_errors("goodbye"))109 self.assertEqual(len(errors), 3)110 111 def test_if_a_version_is_provided_it_is_registered(self):112 Validator = validators.create(113 meta_schema={"$id": "something"},114 version="my version",115 )116 self.addCleanup(validators._META_SCHEMAS.pop, "something")117 self.addCleanup(validators._VALIDATORS.pop, "my version")118 self.assertEqual(Validator.__name__, "MyVersionValidator")119 self.assertEqual(Validator.__qualname__, "MyVersionValidator")120 121 def test_repr(self):122 Validator = validators.create(123 meta_schema={"$id": "something"},124 version="my version",125 )126 self.addCleanup(validators._META_SCHEMAS.pop, "something")127 self.addCleanup(validators._VALIDATORS.pop, "my version")128 self.assertEqual(129 repr(Validator({})),130 "MyVersionValidator(schema={}, format_checker=None)",131 )132 133 def test_long_repr(self):134 Validator = validators.create(135 meta_schema={"$id": "something"},136 version="my version",137 )138 self.addCleanup(validators._META_SCHEMAS.pop, "something")139 self.addCleanup(validators._VALIDATORS.pop, "my version")140 self.assertEqual(141 repr(Validator({"a": list(range(1000))})), (142 "MyVersionValidator(schema={'a': [0, 1, 2, 3, 4, 5, ...]}, "143 "format_checker=None)"144 ),145 )146 147 def test_repr_no_version(self):148 Validator = validators.create(meta_schema={})149 self.assertEqual(150 repr(Validator({})),151 "Validator(schema={}, format_checker=None)",152 )153 154 def test_dashes_are_stripped_from_validator_names(self):155 Validator = validators.create(156 meta_schema={"$id": "something"},157 version="foo-bar",158 )159 self.addCleanup(validators._META_SCHEMAS.pop, "something")160 self.addCleanup(validators._VALIDATORS.pop, "foo-bar")161 self.assertEqual(Validator.__qualname__, "FooBarValidator")162 163 def test_if_a_version_is_not_provided_it_is_not_registered(self):164 original = dict(validators._META_SCHEMAS)165 validators.create(meta_schema={"id": "id"})166 self.assertEqual(validators._META_SCHEMAS, original)167 168 def test_validates_registers_meta_schema_id(self):169 meta_schema_key = "meta schema id"170 my_meta_schema = {"id": meta_schema_key}171 172 validators.create(173 meta_schema=my_meta_schema,174 version="my version",175 id_of=lambda s: s.get("id", ""),176 )177 self.addCleanup(validators._META_SCHEMAS.pop, meta_schema_key)178 self.addCleanup(validators._VALIDATORS.pop, "my version")179 180 self.assertIn(meta_schema_key, validators._META_SCHEMAS)181 182 def test_validates_registers_meta_schema_draft6_id(self):183 meta_schema_key = "meta schema $id"184 my_meta_schema = {"$id": meta_schema_key}185 186 validators.create(187 meta_schema=my_meta_schema,188 version="my version",189 )190 self.addCleanup(validators._META_SCHEMAS.pop, meta_schema_key)191 self.addCleanup(validators._VALIDATORS.pop, "my version")192 193 self.assertIn(meta_schema_key, validators._META_SCHEMAS)194 195 def test_create_default_types(self):196 Validator = validators.create(meta_schema={}, validators=())197 self.assertTrue(198 all(199 Validator({}).is_type(instance=instance, type=type)200 for type, instance in [201 ("array", []),202 ("boolean", True),203 ("integer", 12),204 ("null", None),205 ("number", 12.0),206 ("object", {}),207 ("string", "foo"),208 ]209 ),210 )211 212 def test_check_schema_with_different_metaschema(self):213 """214 One can create a validator class whose metaschema uses a different215 dialect than itself.216 """217 218 NoEmptySchemasValidator = validators.create(219 meta_schema={220 "$schema": validators.Draft202012Validator.META_SCHEMA["$id"],221 "not": {"const": {}},222 },223 )224 NoEmptySchemasValidator.check_schema({"foo": "bar"})225 226 with self.assertRaises(exceptions.SchemaError):227 NoEmptySchemasValidator.check_schema({})228 229 NoEmptySchemasValidator({"foo": "bar"}).validate("foo")230 231 def test_check_schema_with_different_metaschema_defaults_to_self(self):232 """233 A validator whose metaschema doesn't declare $schema defaults to its234 own validation behavior, not the latest "normal" specification.235 """236 237 NoEmptySchemasValidator = validators.create(238 meta_schema={"fail": [{"message": "Meta schema whoops!"}]},239 validators={"fail": fail},240 )241 with self.assertRaises(exceptions.SchemaError):242 NoEmptySchemasValidator.check_schema({})243 244 def test_extend(self):245 original = dict(self.Validator.VALIDATORS)246 new = object()247 248 Extended = validators.extend(249 self.Validator,250 validators={"new": new},251 )252 self.assertEqual(253 (254 Extended.VALIDATORS,255 Extended.META_SCHEMA,256 Extended.TYPE_CHECKER,257 self.Validator.VALIDATORS,258 ), (259 dict(original, new=new),260 self.Validator.META_SCHEMA,261 self.Validator.TYPE_CHECKER,262 original,263 ),264 )265 266 def test_extend_idof(self):267 """268 Extending a validator preserves its notion of schema IDs.269 """270 def id_of(schema):271 return schema.get("__test__", self.Validator.ID_OF(schema))272 correct_id = "the://correct/id/"273 meta_schema = {274 "$id": "the://wrong/id/",275 "__test__": correct_id,276 }277 Original = validators.create(278 meta_schema=meta_schema,279 validators=self.validators,280 type_checker=self.type_checker,281 id_of=id_of,282 )283 self.assertEqual(Original.ID_OF(Original.META_SCHEMA), correct_id)284 285 Derived = validators.extend(Original)286 self.assertEqual(Derived.ID_OF(Derived.META_SCHEMA), correct_id)287 288 def test_extend_applicable_validators(self):289 """290 Extending a validator preserves its notion of applicable validators.291 """292 293 schema = {294 "$defs": {"test": {"type": "number"}},295 "$ref": "#/$defs/test",296 "maximum": 1,297 }298 299 draft4 = validators.Draft4Validator(schema)300 self.assertTrue(draft4.is_valid(37)) # as $ref ignores siblings301 302 Derived = validators.extend(validators.Draft4Validator)303 self.assertTrue(Derived(schema).is_valid(37))304 305 306class TestValidationErrorMessages(TestCase):307 def message_for(self, instance, schema, *args, **kwargs):308 cls = kwargs.pop("cls", validators._LATEST_VERSION)309 cls.check_schema(schema)310 validator = cls(schema, *args, **kwargs)311 errors = list(validator.iter_errors(instance))312 self.assertTrue(errors, msg=f"No errors were raised for {instance!r}")313 self.assertEqual(314 len(errors),315 1,316 msg=f"Expected exactly one error, found {errors!r}",317 )318 return errors[0].message319 320 def test_single_type_failure(self):321 message = self.message_for(instance=1, schema={"type": "string"})322 self.assertEqual(message, "1 is not of type 'string'")323 324 def test_single_type_list_failure(self):325 message = self.message_for(instance=1, schema={"type": ["string"]})326 self.assertEqual(message, "1 is not of type 'string'")327 328 def test_multiple_type_failure(self):329 types = "string", "object"330 message = self.message_for(instance=1, schema={"type": list(types)})331 self.assertEqual(message, "1 is not of type 'string', 'object'")332 333 def test_object_with_named_type_failure(self):334 schema = {"type": [{"name": "Foo", "minimum": 3}]}335 message = self.message_for(336 instance=1,337 schema=schema,338 cls=validators.Draft3Validator,339 )340 self.assertEqual(message, "1 is not of type 'Foo'")341 342 def test_minimum(self):343 message = self.message_for(instance=1, schema={"minimum": 2})344 self.assertEqual(message, "1 is less than the minimum of 2")345 346 def test_maximum(self):347 message = self.message_for(instance=1, schema={"maximum": 0})348 self.assertEqual(message, "1 is greater than the maximum of 0")349 350 def test_dependencies_single_element(self):351 depend, on = "bar", "foo"352 schema = {"dependencies": {depend: on}}353 message = self.message_for(354 instance={"bar": 2},355 schema=schema,356 cls=validators.Draft3Validator,357 )358 self.assertEqual(message, "'foo' is a dependency of 'bar'")359 360 def test_object_without_title_type_failure_draft3(self):361 type = {"type": [{"minimum": 3}]}362 message = self.message_for(363 instance=1,364 schema={"type": [type]},365 cls=validators.Draft3Validator,366 )367 self.assertEqual(368 message,369 "1 is not of type {'type': [{'minimum': 3}]}",370 )371 372 def test_dependencies_list_draft3(self):373 depend, on = "bar", "foo"374 schema = {"dependencies": {depend: [on]}}375 message = self.message_for(376 instance={"bar": 2},377 schema=schema,378 cls=validators.Draft3Validator,379 )380 self.assertEqual(message, "'foo' is a dependency of 'bar'")381 382 def test_dependencies_list_draft7(self):383 depend, on = "bar", "foo"384 schema = {"dependencies": {depend: [on]}}385 message = self.message_for(386 instance={"bar": 2},387 schema=schema,388 cls=validators.Draft7Validator,389 )390 self.assertEqual(message, "'foo' is a dependency of 'bar'")391 392 def test_additionalItems_single_failure(self):393 message = self.message_for(394 instance=[2],395 schema={"items": [], "additionalItems": False},396 cls=validators.Draft3Validator,397 )398 self.assertIn("(2 was unexpected)", message)399 400 def test_additionalItems_multiple_failures(self):401 message = self.message_for(402 instance=[1, 2, 3],403 schema={"items": [], "additionalItems": False},404 cls=validators.Draft3Validator,405 )406 self.assertIn("(1, 2, 3 were unexpected)", message)407 408 def test_additionalProperties_single_failure(self):409 additional = "foo"410 schema = {"additionalProperties": False}411 message = self.message_for(instance={additional: 2}, schema=schema)412 self.assertIn("('foo' was unexpected)", message)413 414 def test_additionalProperties_multiple_failures(self):415 schema = {"additionalProperties": False}416 message = self.message_for(417 instance=dict.fromkeys(["foo", "bar"]),418 schema=schema,419 )420 421 self.assertIn(repr("foo"), message)422 self.assertIn(repr("bar"), message)423 self.assertIn("were unexpected)", message)424 425 def test_const(self):426 schema = {"const": 12}427 message = self.message_for(428 instance={"foo": "bar"},429 schema=schema,430 )431 self.assertIn("12 was expected", message)432 433 def test_contains_draft_6(self):434 schema = {"contains": {"const": 12}}435 message = self.message_for(436 instance=[2, {}, []],437 schema=schema,438 cls=validators.Draft6Validator,439 )440 self.assertEqual(441 message,442 "None of [2, {}, []] are valid under the given schema",443 )444 445 def test_invalid_format_default_message(self):446 checker = FormatChecker(formats=())447 checker.checks("thing")(lambda value: False)448 449 schema = {"format": "thing"}450 message = self.message_for(451 instance="bla",452 schema=schema,453 format_checker=checker,454 )455 456 self.assertIn(repr("bla"), message)457 self.assertIn(repr("thing"), message)458 self.assertIn("is not a", message)459 460 def test_additionalProperties_false_patternProperties(self):461 schema = {"type": "object",462 "additionalProperties": False,463 "patternProperties": {464 "^abc$": {"type": "string"},465 "^def$": {"type": "string"},466 }}467 message = self.message_for(468 instance={"zebra": 123},469 schema=schema,470 cls=validators.Draft4Validator,471 )472 self.assertEqual(473 message,474 "{} does not match any of the regexes: {}, {}".format(475 repr("zebra"), repr("^abc$"), repr("^def$"),476 ),477 )478 message = self.message_for(479 instance={"zebra": 123, "fish": 456},480 schema=schema,481 cls=validators.Draft4Validator,482 )483 self.assertEqual(484 message,485 "{}, {} do not match any of the regexes: {}, {}".format(486 repr("fish"), repr("zebra"), repr("^abc$"), repr("^def$"),487 ),488 )489 490 def test_False_schema(self):491 message = self.message_for(492 instance="something",493 schema=False,494 )495 self.assertEqual(message, "False schema does not allow 'something'")496 497 def test_multipleOf(self):498 message = self.message_for(499 instance=3,500 schema={"multipleOf": 2},501 )502 self.assertEqual(message, "3 is not a multiple of 2")503 504 def test_minItems(self):505 message = self.message_for(instance=[], schema={"minItems": 2})506 self.assertEqual(message, "[] is too short")507 508 def test_maxItems(self):509 message = self.message_for(instance=[1, 2, 3], schema={"maxItems": 2})510 self.assertEqual(message, "[1, 2, 3] is too long")511 512 def test_minItems_1(self):513 message = self.message_for(instance=[], schema={"minItems": 1})514 self.assertEqual(message, "[] should be non-empty")515 516 def test_maxItems_0(self):517 message = self.message_for(instance=[1, 2, 3], schema={"maxItems": 0})518 self.assertEqual(message, "[1, 2, 3] is expected to be empty")519 520 def test_minLength(self):521 message = self.message_for(522 instance="",523 schema={"minLength": 2},524 )525 self.assertEqual(message, "'' is too short")526 527 def test_maxLength(self):528 message = self.message_for(529 instance="abc",530 schema={"maxLength": 2},531 )532 self.assertEqual(message, "'abc' is too long")533 534 def test_minLength_1(self):535 message = self.message_for(instance="", schema={"minLength": 1})536 self.assertEqual(message, "'' should be non-empty")537 538 def test_maxLength_0(self):539 message = self.message_for(instance="abc", schema={"maxLength": 0})540 self.assertEqual(message, "'abc' is expected to be empty")541 542 def test_minProperties(self):543 message = self.message_for(instance={}, schema={"minProperties": 2})544 self.assertEqual(message, "{} does not have enough properties")545 546 def test_maxProperties(self):547 message = self.message_for(548 instance={"a": {}, "b": {}, "c": {}},549 schema={"maxProperties": 2},550 )551 self.assertEqual(552 message,553 "{'a': {}, 'b': {}, 'c': {}} has too many properties",554 )555 556 def test_minProperties_1(self):557 message = self.message_for(instance={}, schema={"minProperties": 1})558 self.assertEqual(message, "{} should be non-empty")559 560 def test_maxProperties_0(self):561 message = self.message_for(562 instance={1: 2},563 schema={"maxProperties": 0},564 )565 self.assertEqual(message, "{1: 2} is expected to be empty")566 567 def test_prefixItems_with_items(self):568 message = self.message_for(569 instance=[1, 2, "foo"],570 schema={"items": False, "prefixItems": [{}, {}]},571 )572 self.assertEqual(573 message,574 "Expected at most 2 items but found 1 extra: 'foo'",575 )576 577 def test_prefixItems_with_multiple_extra_items(self):578 message = self.message_for(579 instance=[1, 2, "foo", 5],580 schema={"items": False, "prefixItems": [{}, {}]},581 )582 self.assertEqual(583 message,584 "Expected at most 2 items but found 2 extra: ['foo', 5]",585 )586 587 def test_pattern(self):588 message = self.message_for(589 instance="bbb",590 schema={"pattern": "^a*$"},591 )592 self.assertEqual(message, "'bbb' does not match '^a*$'")593 594 def test_does_not_contain(self):595 message = self.message_for(596 instance=[],597 schema={"contains": {"type": "string"}},598 )599 self.assertEqual(600 message,601 "[] does not contain items matching the given schema",602 )603 604 def test_contains_too_few(self):605 message = self.message_for(606 instance=["foo", 1],607 schema={"contains": {"type": "string"}, "minContains": 2},608 )609 self.assertEqual(610 message,611 "Too few items match the given schema "612 "(expected at least 2 but only 1 matched)",613 )614 615 def test_contains_too_few_both_constrained(self):616 message = self.message_for(617 instance=["foo", 1],618 schema={619 "contains": {"type": "string"},620 "minContains": 2,621 "maxContains": 4,622 },623 )624 self.assertEqual(625 message,626 "Too few items match the given schema (expected at least 2 but "627 "only 1 matched)",628 )629 630 def test_contains_too_many(self):631 message = self.message_for(632 instance=["foo", "bar", "baz"],633 schema={"contains": {"type": "string"}, "maxContains": 2},634 )635 self.assertEqual(636 message,637 "Too many items match the given schema (expected at most 2)",638 )639 640 def test_contains_too_many_both_constrained(self):641 message = self.message_for(642 instance=["foo"] * 5,643 schema={644 "contains": {"type": "string"},645 "minContains": 2,646 "maxContains": 4,647 },648 )649 self.assertEqual(650 message,651 "Too many items match the given schema (expected at most 4)",652 )653 654 def test_exclusiveMinimum(self):655 message = self.message_for(656 instance=3,657 schema={"exclusiveMinimum": 5},658 )659 self.assertEqual(660 message,661 "3 is less than or equal to the minimum of 5",662 )663 664 def test_exclusiveMaximum(self):665 message = self.message_for(instance=3, schema={"exclusiveMaximum": 2})666 self.assertEqual(667 message,668 "3 is greater than or equal to the maximum of 2",669 )670 671 def test_required(self):672 message = self.message_for(instance={}, schema={"required": ["foo"]})673 self.assertEqual(message, "'foo' is a required property")674 675 def test_dependentRequired(self):676 message = self.message_for(677 instance={"foo": {}},678 schema={"dependentRequired": {"foo": ["bar"]}},679 )680 self.assertEqual(message, "'bar' is a dependency of 'foo'")681 682 def test_oneOf_matches_none(self):683 message = self.message_for(instance={}, schema={"oneOf": [False]})684 self.assertEqual(685 message,686 "{} is not valid under any of the given schemas",687 )688 689 def test_oneOf_matches_too_many(self):690 message = self.message_for(instance={}, schema={"oneOf": [True, True]})691 self.assertEqual(message, "{} is valid under each of True, True")692 693 def test_unevaluated_items(self):694 schema = {"type": "array", "unevaluatedItems": False}695 message = self.message_for(instance=["foo", "bar"], schema=schema)696 self.assertIn(697 message,698 "Unevaluated items are not allowed ('foo', 'bar' were unexpected)",699 )700 701 def test_unevaluated_items_on_invalid_type(self):702 schema = {"type": "array", "unevaluatedItems": False}703 message = self.message_for(instance="foo", schema=schema)704 self.assertEqual(message, "'foo' is not of type 'array'")705 706 def test_unevaluated_properties_invalid_against_subschema(self):707 schema = {708 "properties": {"foo": {"type": "string"}},709 "unevaluatedProperties": {"const": 12},710 }711 message = self.message_for(712 instance={713 "foo": "foo",714 "bar": "bar",715 "baz": 12,716 },717 schema=schema,718 )719 self.assertEqual(720 message,721 "Unevaluated properties are not valid under the given schema "722 "('bar' was unevaluated and invalid)",723 )724 725 def test_unevaluated_properties_disallowed(self):726 schema = {"type": "object", "unevaluatedProperties": False}727 message = self.message_for(728 instance={729 "foo": "foo",730 "bar": "bar",731 },732 schema=schema,733 )734 self.assertEqual(735 message,736 "Unevaluated properties are not allowed "737 "('bar', 'foo' were unexpected)",738 )739 740 def test_unevaluated_properties_on_invalid_type(self):741 schema = {"type": "object", "unevaluatedProperties": False}742 message = self.message_for(instance="foo", schema=schema)743 self.assertEqual(message, "'foo' is not of type 'object'")744 745 def test_single_item(self):746 schema = {"prefixItems": [{}], "items": False}747 message = self.message_for(748 instance=["foo", "bar", "baz"],749 schema=schema,750 )751 self.assertEqual(752 message,753 "Expected at most 1 item but found 2 extra: ['bar', 'baz']",754 )755 756 def test_heterogeneous_additionalItems_with_Items(self):757 schema = {"items": [{}], "additionalItems": False}758 message = self.message_for(759 instance=["foo", "bar", 37],760 schema=schema,761 cls=validators.Draft7Validator,762 )763 self.assertEqual(764 message,765 "Additional items are not allowed ('bar', 37 were unexpected)",766 )767 768 def test_heterogeneous_items_prefixItems(self):769 schema = {"prefixItems": [{}], "items": False}770 message = self.message_for(771 instance=["foo", "bar", 37],772 schema=schema,773 )774 self.assertEqual(775 message,776 "Expected at most 1 item but found 2 extra: ['bar', 37]",777 )778 779 def test_heterogeneous_unevaluatedItems_prefixItems(self):780 schema = {"prefixItems": [{}], "unevaluatedItems": False}781 message = self.message_for(782 instance=["foo", "bar", 37],783 schema=schema,784 )785 self.assertEqual(786 message,787 "Unevaluated items are not allowed ('bar', 37 were unexpected)",788 )789 790 def test_heterogeneous_properties_additionalProperties(self):791 """792 Not valid deserialized JSON, but this should not blow up.793 """794 schema = {"properties": {"foo": {}}, "additionalProperties": False}795 message = self.message_for(796 instance={"foo": {}, "a": "baz", 37: 12},797 schema=schema,798 )799 self.assertEqual(800 message,801 "Additional properties are not allowed (37, 'a' were unexpected)",802 )803 804 def test_heterogeneous_properties_unevaluatedProperties(self):805 """806 Not valid deserialized JSON, but this should not blow up.807 """808 schema = {"properties": {"foo": {}}, "unevaluatedProperties": False}809 message = self.message_for(810 instance={"foo": {}, "a": "baz", 37: 12},811 schema=schema,812 )813 self.assertEqual(814 message,815 "Unevaluated properties are not allowed (37, 'a' were unexpected)",816 )817 818 819class TestValidationErrorDetails(TestCase):820 # TODO: These really need unit tests for each individual keyword, rather821 # than just these higher level tests.822 def test_anyOf(self):823 instance = 5824 schema = {825 "anyOf": [826 {"minimum": 20},827 {"type": "string"},828 ],829 }830 831 validator = validators.Draft4Validator(schema)832 errors = list(validator.iter_errors(instance))833 self.assertEqual(len(errors), 1)834 e = errors[0]835 836 self.assertEqual(e.validator, "anyOf")837 self.assertEqual(e.validator_value, schema["anyOf"])838 self.assertEqual(e.instance, instance)839 self.assertEqual(e.schema, schema)840 self.assertIsNone(e.parent)841 842 self.assertEqual(e.path, deque([]))843 self.assertEqual(e.relative_path, deque([]))844 self.assertEqual(e.absolute_path, deque([]))845 self.assertEqual(e.json_path, "$")846 847 self.assertEqual(e.schema_path, deque(["anyOf"]))848 self.assertEqual(e.relative_schema_path, deque(["anyOf"]))849 self.assertEqual(e.absolute_schema_path, deque(["anyOf"]))850 851 self.assertEqual(len(e.context), 2)852 853 e1, e2 = sorted_errors(e.context)854 855 self.assertEqual(e1.validator, "minimum")856 self.assertEqual(e1.validator_value, schema["anyOf"][0]["minimum"])857 self.assertEqual(e1.instance, instance)858 self.assertEqual(e1.schema, schema["anyOf"][0])859 self.assertIs(e1.parent, e)860 861 self.assertEqual(e1.path, deque([]))862 self.assertEqual(e1.absolute_path, deque([]))863 self.assertEqual(e1.relative_path, deque([]))864 self.assertEqual(e1.json_path, "$")865 866 self.assertEqual(e1.schema_path, deque([0, "minimum"]))867 self.assertEqual(e1.relative_schema_path, deque([0, "minimum"]))868 self.assertEqual(869 e1.absolute_schema_path, deque(["anyOf", 0, "minimum"]),870 )871 872 self.assertFalse(e1.context)873 874 self.assertEqual(e2.validator, "type")875 self.assertEqual(e2.validator_value, schema["anyOf"][1]["type"])876 self.assertEqual(e2.instance, instance)877 self.assertEqual(e2.schema, schema["anyOf"][1])878 self.assertIs(e2.parent, e)879 880 self.assertEqual(e2.path, deque([]))881 self.assertEqual(e2.relative_path, deque([]))882 self.assertEqual(e2.absolute_path, deque([]))883 self.assertEqual(e2.json_path, "$")884 885 self.assertEqual(e2.schema_path, deque([1, "type"]))886 self.assertEqual(e2.relative_schema_path, deque([1, "type"]))887 self.assertEqual(e2.absolute_schema_path, deque(["anyOf", 1, "type"]))888 889 self.assertEqual(len(e2.context), 0)890 891 def test_type(self):892 instance = {"foo": 1}893 schema = {894 "type": [895 {"type": "integer"},896 {897 "type": "object",898 "properties": {"foo": {"enum": [2]}},899 },900 ],901 }902 903 validator = validators.Draft3Validator(schema)904 errors = list(validator.iter_errors(instance))905 self.assertEqual(len(errors), 1)906 e = errors[0]907 908 self.assertEqual(e.validator, "type")909 self.assertEqual(e.validator_value, schema["type"])910 self.assertEqual(e.instance, instance)911 self.assertEqual(e.schema, schema)912 self.assertIsNone(e.parent)913 914 self.assertEqual(e.path, deque([]))915 self.assertEqual(e.relative_path, deque([]))916 self.assertEqual(e.absolute_path, deque([]))917 self.assertEqual(e.json_path, "$")918 919 self.assertEqual(e.schema_path, deque(["type"]))920 self.assertEqual(e.relative_schema_path, deque(["type"]))921 self.assertEqual(e.absolute_schema_path, deque(["type"]))922 923 self.assertEqual(len(e.context), 2)924 925 e1, e2 = sorted_errors(e.context)926 927 self.assertEqual(e1.validator, "type")928 self.assertEqual(e1.validator_value, schema["type"][0]["type"])929 self.assertEqual(e1.instance, instance)930 self.assertEqual(e1.schema, schema["type"][0])931 self.assertIs(e1.parent, e)932 933 self.assertEqual(e1.path, deque([]))934 self.assertEqual(e1.relative_path, deque([]))935 self.assertEqual(e1.absolute_path, deque([]))936 self.assertEqual(e1.json_path, "$")937 938 self.assertEqual(e1.schema_path, deque([0, "type"]))939 self.assertEqual(e1.relative_schema_path, deque([0, "type"]))940 self.assertEqual(e1.absolute_schema_path, deque(["type", 0, "type"]))941 942 self.assertFalse(e1.context)943 944 self.assertEqual(e2.validator, "enum")945 self.assertEqual(e2.validator_value, [2])946 self.assertEqual(e2.instance, 1)947 self.assertEqual(e2.schema, {"enum": [2]})948 self.assertIs(e2.parent, e)949 950 self.assertEqual(e2.path, deque(["foo"]))951 self.assertEqual(e2.relative_path, deque(["foo"]))952 self.assertEqual(e2.absolute_path, deque(["foo"]))953 self.assertEqual(e2.json_path, "$.foo")954 955 self.assertEqual(956 e2.schema_path, deque([1, "properties", "foo", "enum"]),957 )958 self.assertEqual(959 e2.relative_schema_path, deque([1, "properties", "foo", "enum"]),960 )961 self.assertEqual(962 e2.absolute_schema_path,963 deque(["type", 1, "properties", "foo", "enum"]),964 )965 966 self.assertFalse(e2.context)967 968 def test_single_nesting(self):969 instance = {"foo": 2, "bar": [1], "baz": 15, "quux": "spam"}970 schema = {971 "properties": {972 "foo": {"type": "string"},973 "bar": {"minItems": 2},974 "baz": {"maximum": 10, "enum": [2, 4, 6, 8]},975 },976 }977 978 validator = validators.Draft3Validator(schema)979 errors = validator.iter_errors(instance)980 e1, e2, e3, e4 = sorted_errors(errors)981 982 self.assertEqual(e1.path, deque(["bar"]))983 self.assertEqual(e2.path, deque(["baz"]))984 self.assertEqual(e3.path, deque(["baz"]))985 self.assertEqual(e4.path, deque(["foo"]))986 987 self.assertEqual(e1.relative_path, deque(["bar"]))988 self.assertEqual(e2.relative_path, deque(["baz"]))989 self.assertEqual(e3.relative_path, deque(["baz"]))990 self.assertEqual(e4.relative_path, deque(["foo"]))991 992 self.assertEqual(e1.absolute_path, deque(["bar"]))993 self.assertEqual(e2.absolute_path, deque(["baz"]))994 self.assertEqual(e3.absolute_path, deque(["baz"]))995 self.assertEqual(e4.absolute_path, deque(["foo"]))996 997 self.assertEqual(e1.json_path, "$.bar")998 self.assertEqual(e2.json_path, "$.baz")999 self.assertEqual(e3.json_path, "$.baz")1000 self.assertEqual(e4.json_path, "$.foo")1001 1002 self.assertEqual(e1.validator, "minItems")1003 self.assertEqual(e2.validator, "enum")1004 self.assertEqual(e3.validator, "maximum")1005 self.assertEqual(e4.validator, "type")1006 1007 def test_multiple_nesting(self):1008 instance = [1, {"foo": 2, "bar": {"baz": [1]}}, "quux"]1009 schema = {1010 "type": "string",1011 "items": {1012 "type": ["string", "object"],1013 "properties": {1014 "foo": {"enum": [1, 3]},1015 "bar": {1016 "type": "array",1017 "properties": {1018 "bar": {"required": True},1019 "baz": {"minItems": 2},1020 },1021 },1022 },1023 },1024 }1025 1026 validator = validators.Draft3Validator(schema)1027 errors = validator.iter_errors(instance)1028 e1, e2, e3, e4, e5, e6 = sorted_errors(errors)1029 1030 self.assertEqual(e1.path, deque([]))1031 self.assertEqual(e2.path, deque([0]))1032 self.assertEqual(e3.path, deque([1, "bar"]))1033 self.assertEqual(e4.path, deque([1, "bar", "bar"]))1034 self.assertEqual(e5.path, deque([1, "bar", "baz"]))1035 self.assertEqual(e6.path, deque([1, "foo"]))1036 1037 self.assertEqual(e1.json_path, "$")1038 self.assertEqual(e2.json_path, "$[0]")1039 self.assertEqual(e3.json_path, "$[1].bar")1040 self.assertEqual(e4.json_path, "$[1].bar.bar")1041 self.assertEqual(e5.json_path, "$[1].bar.baz")1042 self.assertEqual(e6.json_path, "$[1].foo")1043 1044 self.assertEqual(e1.schema_path, deque(["type"]))1045 self.assertEqual(e2.schema_path, deque(["items", "type"]))1046 self.assertEqual(1047 list(e3.schema_path), ["items", "properties", "bar", "type"],1048 )1049 self.assertEqual(1050 list(e4.schema_path),1051 ["items", "properties", "bar", "properties", "bar", "required"],1052 )1053 self.assertEqual(1054 list(e5.schema_path),1055 ["items", "properties", "bar", "properties", "baz", "minItems"],1056 )1057 self.assertEqual(1058 list(e6.schema_path), ["items", "properties", "foo", "enum"],1059 )1060 1061 self.assertEqual(e1.validator, "type")1062 self.assertEqual(e2.validator, "type")1063 self.assertEqual(e3.validator, "type")1064 self.assertEqual(e4.validator, "required")1065 self.assertEqual(e5.validator, "minItems")1066 self.assertEqual(e6.validator, "enum")1067 1068 def test_recursive(self):1069 schema = {1070 "definitions": {1071 "node": {1072 "anyOf": [{1073 "type": "object",1074 "required": ["name", "children"],1075 "properties": {1076 "name": {1077 "type": "string",1078 },1079 "children": {1080 "type": "object",1081 "patternProperties": {1082 "^.*$": {1083 "$ref": "#/definitions/node",1084 },1085 },1086 },1087 },1088 }],1089 },1090 },1091 "type": "object",1092 "required": ["root"],1093 "properties": {"root": {"$ref": "#/definitions/node"}},1094 }1095 1096 instance = {1097 "root": {1098 "name": "root",1099 "children": {1100 "a": {1101 "name": "a",1102 "children": {1103 "ab": {1104 "name": "ab",1105 # missing "children"1106 },1107 },1108 },1109 },1110 },1111 }1112 validator = validators.Draft4Validator(schema)1113 1114 e, = validator.iter_errors(instance)1115 self.assertEqual(e.absolute_path, deque(["root"]))1116 self.assertEqual(1117 e.absolute_schema_path, deque(["properties", "root", "anyOf"]),1118 )1119 self.assertEqual(e.json_path, "$.root")1120 1121 e1, = e.context1122 self.assertEqual(e1.absolute_path, deque(["root", "children", "a"]))1123 self.assertEqual(1124 e1.absolute_schema_path, deque(1125 [1126 "properties",1127 "root",1128 "anyOf",1129 0,1130 "properties",1131 "children",1132 "patternProperties",1133 "^.*$",1134 "anyOf",1135 ],1136 ),1137 )1138 self.assertEqual(e1.json_path, "$.root.children.a")1139 1140 e2, = e1.context1141 self.assertEqual(1142 e2.absolute_path, deque(1143 ["root", "children", "a", "children", "ab"],1144 ),1145 )1146 self.assertEqual(1147 e2.absolute_schema_path, deque(1148 [1149 "properties",1150 "root",1151 "anyOf",1152 0,1153 "properties",1154 "children",1155 "patternProperties",1156 "^.*$",1157 "anyOf",1158 0,1159 "properties",1160 "children",1161 "patternProperties",1162 "^.*$",1163 "anyOf",1164 ],1165 ),1166 )1167 self.assertEqual(e2.json_path, "$.root.children.a.children.ab")1168 1169 def test_additionalProperties(self):1170 instance = {"bar": "bar", "foo": 2}1171 schema = {"additionalProperties": {"type": "integer", "minimum": 5}}1172 1173 validator = validators.Draft3Validator(schema)1174 errors = validator.iter_errors(instance)1175 e1, e2 = sorted_errors(errors)1176 1177 self.assertEqual(e1.path, deque(["bar"]))1178 self.assertEqual(e2.path, deque(["foo"]))1179 1180 self.assertEqual(e1.json_path, "$.bar")1181 self.assertEqual(e2.json_path, "$.foo")1182 1183 self.assertEqual(e1.validator, "type")1184 self.assertEqual(e2.validator, "minimum")1185 1186 def test_patternProperties(self):1187 instance = {"bar": 1, "foo": 2}1188 schema = {1189 "patternProperties": {1190 "bar": {"type": "string"},1191 "foo": {"minimum": 5},1192 },1193 }1194 1195 validator = validators.Draft3Validator(schema)1196 errors = validator.iter_errors(instance)1197 e1, e2 = sorted_errors(errors)1198 1199 self.assertEqual(e1.path, deque(["bar"]))1200 self.assertEqual(e2.path, deque(["foo"]))