450 lines
15 KiB
Python
450 lines
15 KiB
Python
import re
|
|
|
|
from referencing.jsonschema import lookup_recursive_ref
|
|
|
|
from jsonschema import _utils
|
|
from jsonschema.exceptions import ValidationError
|
|
|
|
|
|
def ignore_ref_siblings(schema):
|
|
"""
|
|
Ignore siblings of ``$ref`` if it is present.
|
|
|
|
Otherwise, return all keywords.
|
|
|
|
Suitable for use with `create`'s ``applicable_validators`` argument.
|
|
"""
|
|
ref = schema.get("$ref")
|
|
if ref is not None:
|
|
return [("$ref", ref)]
|
|
else:
|
|
return schema.items()
|
|
|
|
|
|
def dependencies_draft3(validator, dependencies, instance, schema):
|
|
if not validator.is_type(instance, "object"):
|
|
return
|
|
|
|
for property, dependency in dependencies.items():
|
|
if property not in instance:
|
|
continue
|
|
|
|
if validator.is_type(dependency, "object"):
|
|
yield from validator.descend(
|
|
instance, dependency, schema_path=property,
|
|
)
|
|
elif validator.is_type(dependency, "string"):
|
|
if dependency not in instance:
|
|
message = f"{dependency!r} is a dependency of {property!r}"
|
|
yield ValidationError(message)
|
|
else:
|
|
for each in dependency:
|
|
if each not in instance:
|
|
message = f"{each!r} is a dependency of {property!r}"
|
|
yield ValidationError(message)
|
|
|
|
|
|
def dependencies_draft4_draft6_draft7(
|
|
validator,
|
|
dependencies,
|
|
instance,
|
|
schema,
|
|
):
|
|
"""
|
|
Support for the ``dependencies`` keyword from pre-draft 2019-09.
|
|
|
|
In later drafts, the keyword was split into separate
|
|
``dependentRequired`` and ``dependentSchemas`` validators.
|
|
"""
|
|
if not validator.is_type(instance, "object"):
|
|
return
|
|
|
|
for property, dependency in dependencies.items():
|
|
if property not in instance:
|
|
continue
|
|
|
|
if validator.is_type(dependency, "array"):
|
|
for each in dependency:
|
|
if each not in instance:
|
|
message = f"{each!r} is a dependency of {property!r}"
|
|
yield ValidationError(message)
|
|
else:
|
|
yield from validator.descend(
|
|
instance, dependency, schema_path=property,
|
|
)
|
|
|
|
|
|
def disallow_draft3(validator, disallow, instance, schema):
|
|
for disallowed in _utils.ensure_list(disallow):
|
|
if validator.evolve(schema={"type": [disallowed]}).is_valid(instance):
|
|
message = f"{disallowed!r} is disallowed for {instance!r}"
|
|
yield ValidationError(message)
|
|
|
|
|
|
def extends_draft3(validator, extends, instance, schema):
|
|
if validator.is_type(extends, "object"):
|
|
yield from validator.descend(instance, extends)
|
|
return
|
|
for index, subschema in enumerate(extends):
|
|
yield from validator.descend(instance, subschema, schema_path=index)
|
|
|
|
|
|
def items_draft3_draft4(validator, items, instance, schema):
|
|
if not validator.is_type(instance, "array"):
|
|
return
|
|
|
|
if validator.is_type(items, "object"):
|
|
for index, item in enumerate(instance):
|
|
yield from validator.descend(item, items, path=index)
|
|
else:
|
|
for (index, item), subschema in zip(enumerate(instance), items):
|
|
yield from validator.descend(
|
|
item, subschema, path=index, schema_path=index,
|
|
)
|
|
|
|
|
|
def additionalItems(validator, aI, instance, schema):
|
|
if (
|
|
not validator.is_type(instance, "array")
|
|
or validator.is_type(schema.get("items", {}), "object")
|
|
):
|
|
return
|
|
|
|
len_items = len(schema.get("items", []))
|
|
if validator.is_type(aI, "object"):
|
|
for index, item in enumerate(instance[len_items:], start=len_items):
|
|
yield from validator.descend(item, aI, path=index)
|
|
elif not aI and len(instance) > len(schema.get("items", [])):
|
|
error = "Additional items are not allowed (%s %s unexpected)"
|
|
yield ValidationError(
|
|
error % _utils.extras_msg(instance[len(schema.get("items", [])):]),
|
|
)
|
|
|
|
|
|
def items_draft6_draft7_draft201909(validator, items, instance, schema):
|
|
if not validator.is_type(instance, "array"):
|
|
return
|
|
|
|
if validator.is_type(items, "array"):
|
|
for (index, item), subschema in zip(enumerate(instance), items):
|
|
yield from validator.descend(
|
|
item, subschema, path=index, schema_path=index,
|
|
)
|
|
else:
|
|
for index, item in enumerate(instance):
|
|
yield from validator.descend(item, items, path=index)
|
|
|
|
|
|
def minimum_draft3_draft4(validator, minimum, instance, schema):
|
|
if not validator.is_type(instance, "number"):
|
|
return
|
|
|
|
if schema.get("exclusiveMinimum", False):
|
|
failed = instance <= minimum
|
|
cmp = "less than or equal to"
|
|
else:
|
|
failed = instance < minimum
|
|
cmp = "less than"
|
|
|
|
if failed:
|
|
message = f"{instance!r} is {cmp} the minimum of {minimum!r}"
|
|
yield ValidationError(message)
|
|
|
|
|
|
def maximum_draft3_draft4(validator, maximum, instance, schema):
|
|
if not validator.is_type(instance, "number"):
|
|
return
|
|
|
|
if schema.get("exclusiveMaximum", False):
|
|
failed = instance >= maximum
|
|
cmp = "greater than or equal to"
|
|
else:
|
|
failed = instance > maximum
|
|
cmp = "greater than"
|
|
|
|
if failed:
|
|
message = f"{instance!r} is {cmp} the maximum of {maximum!r}"
|
|
yield ValidationError(message)
|
|
|
|
|
|
def properties_draft3(validator, properties, instance, schema):
|
|
if not validator.is_type(instance, "object"):
|
|
return
|
|
|
|
for property, subschema in properties.items():
|
|
if property in instance:
|
|
yield from validator.descend(
|
|
instance[property],
|
|
subschema,
|
|
path=property,
|
|
schema_path=property,
|
|
)
|
|
elif subschema.get("required", False):
|
|
error = ValidationError(f"{property!r} is a required property")
|
|
error._set(
|
|
validator="required",
|
|
validator_value=subschema["required"],
|
|
instance=instance,
|
|
schema=schema,
|
|
)
|
|
error.path.appendleft(property)
|
|
error.schema_path.extend([property, "required"])
|
|
yield error
|
|
|
|
|
|
def type_draft3(validator, types, instance, schema):
|
|
types = _utils.ensure_list(types)
|
|
|
|
all_errors = []
|
|
for index, type in enumerate(types):
|
|
if validator.is_type(type, "object"):
|
|
errors = list(validator.descend(instance, type, schema_path=index))
|
|
if not errors:
|
|
return
|
|
all_errors.extend(errors)
|
|
elif validator.is_type(instance, type):
|
|
return
|
|
|
|
reprs = []
|
|
for type in types:
|
|
try:
|
|
reprs.append(repr(type["name"]))
|
|
except Exception: # noqa: BLE001
|
|
reprs.append(repr(type))
|
|
yield ValidationError(
|
|
f"{instance!r} is not of type {', '.join(reprs)}",
|
|
context=all_errors,
|
|
)
|
|
|
|
|
|
def contains_draft6_draft7(validator, contains, instance, schema):
|
|
if not validator.is_type(instance, "array"):
|
|
return
|
|
|
|
if not any(
|
|
validator.evolve(schema=contains).is_valid(element)
|
|
for element in instance
|
|
):
|
|
yield ValidationError(
|
|
f"None of {instance!r} are valid under the given schema",
|
|
)
|
|
|
|
|
|
def recursiveRef(validator, recursiveRef, instance, schema):
|
|
resolved = lookup_recursive_ref(validator._resolver)
|
|
yield from validator.descend(
|
|
instance,
|
|
resolved.contents,
|
|
resolver=resolved.resolver,
|
|
)
|
|
|
|
|
|
def find_evaluated_item_indexes_by_schema(validator, instance, schema):
|
|
"""
|
|
Get all indexes of items that get evaluated under the current schema.
|
|
|
|
Covers all keywords related to unevaluatedItems: items, prefixItems, if,
|
|
then, else, contains, unevaluatedItems, allOf, oneOf, anyOf
|
|
"""
|
|
if validator.is_type(schema, "boolean"):
|
|
return []
|
|
evaluated_indexes = []
|
|
|
|
ref = schema.get("$ref")
|
|
if ref is not None:
|
|
resolved = validator._resolver.lookup(ref)
|
|
evaluated_indexes.extend(
|
|
find_evaluated_item_indexes_by_schema(
|
|
validator.evolve(
|
|
schema=resolved.contents,
|
|
_resolver=resolved.resolver,
|
|
),
|
|
instance,
|
|
resolved.contents,
|
|
),
|
|
)
|
|
|
|
if "$recursiveRef" in schema:
|
|
resolved = lookup_recursive_ref(validator._resolver)
|
|
evaluated_indexes.extend(
|
|
find_evaluated_item_indexes_by_schema(
|
|
validator.evolve(
|
|
schema=resolved.contents,
|
|
_resolver=resolved.resolver,
|
|
),
|
|
instance,
|
|
resolved.contents,
|
|
),
|
|
)
|
|
|
|
if "items" in schema:
|
|
if "additionalItems" in schema:
|
|
return list(range(len(instance)))
|
|
|
|
if validator.is_type(schema["items"], "object"):
|
|
return list(range(len(instance)))
|
|
evaluated_indexes += list(range(len(schema["items"])))
|
|
|
|
if "if" in schema:
|
|
if validator.evolve(schema=schema["if"]).is_valid(instance):
|
|
evaluated_indexes += find_evaluated_item_indexes_by_schema(
|
|
validator, instance, schema["if"],
|
|
)
|
|
if "then" in schema:
|
|
evaluated_indexes += find_evaluated_item_indexes_by_schema(
|
|
validator, instance, schema["then"],
|
|
)
|
|
elif "else" in schema:
|
|
evaluated_indexes += find_evaluated_item_indexes_by_schema(
|
|
validator, instance, schema["else"],
|
|
)
|
|
|
|
for keyword in ["contains", "unevaluatedItems"]:
|
|
if keyword in schema:
|
|
for k, v in enumerate(instance):
|
|
if validator.evolve(schema=schema[keyword]).is_valid(v):
|
|
evaluated_indexes.append(k)
|
|
|
|
for keyword in ["allOf", "oneOf", "anyOf"]:
|
|
if keyword in schema:
|
|
for subschema in schema[keyword]:
|
|
errs = next(validator.descend(instance, subschema), None)
|
|
if errs is None:
|
|
evaluated_indexes += find_evaluated_item_indexes_by_schema(
|
|
validator, instance, subschema,
|
|
)
|
|
|
|
return evaluated_indexes
|
|
|
|
|
|
def unevaluatedItems_draft2019(validator, unevaluatedItems, instance, schema):
|
|
if not validator.is_type(instance, "array"):
|
|
return
|
|
evaluated_item_indexes = find_evaluated_item_indexes_by_schema(
|
|
validator, instance, schema,
|
|
)
|
|
unevaluated_items = [
|
|
item for index, item in enumerate(instance)
|
|
if index not in evaluated_item_indexes
|
|
]
|
|
if unevaluated_items:
|
|
error = "Unevaluated items are not allowed (%s %s unexpected)"
|
|
yield ValidationError(error % _utils.extras_msg(unevaluated_items))
|
|
|
|
|
|
def find_evaluated_property_keys_by_schema(validator, instance, schema):
|
|
if validator.is_type(schema, "boolean"):
|
|
return []
|
|
evaluated_keys = []
|
|
|
|
ref = schema.get("$ref")
|
|
if ref is not None:
|
|
resolved = validator._resolver.lookup(ref)
|
|
evaluated_keys.extend(
|
|
find_evaluated_property_keys_by_schema(
|
|
validator.evolve(
|
|
schema=resolved.contents,
|
|
_resolver=resolved.resolver,
|
|
),
|
|
instance,
|
|
resolved.contents,
|
|
),
|
|
)
|
|
|
|
if "$recursiveRef" in schema:
|
|
resolved = lookup_recursive_ref(validator._resolver)
|
|
evaluated_keys.extend(
|
|
find_evaluated_property_keys_by_schema(
|
|
validator.evolve(
|
|
schema=resolved.contents,
|
|
_resolver=resolved.resolver,
|
|
),
|
|
instance,
|
|
resolved.contents,
|
|
),
|
|
)
|
|
|
|
for keyword in [
|
|
"properties", "additionalProperties", "unevaluatedProperties",
|
|
]:
|
|
if keyword in schema:
|
|
schema_value = schema[keyword]
|
|
if validator.is_type(schema_value, "boolean") and schema_value:
|
|
evaluated_keys += instance.keys()
|
|
|
|
elif validator.is_type(schema_value, "object"):
|
|
for property in schema_value:
|
|
if property in instance:
|
|
evaluated_keys.append(property)
|
|
|
|
if "patternProperties" in schema:
|
|
for property in instance:
|
|
for pattern in schema["patternProperties"]:
|
|
if re.search(pattern, property):
|
|
evaluated_keys.append(property)
|
|
|
|
if "dependentSchemas" in schema:
|
|
for property, subschema in schema["dependentSchemas"].items():
|
|
if property not in instance:
|
|
continue
|
|
evaluated_keys += find_evaluated_property_keys_by_schema(
|
|
validator, instance, subschema,
|
|
)
|
|
|
|
for keyword in ["allOf", "oneOf", "anyOf"]:
|
|
if keyword in schema:
|
|
for subschema in schema[keyword]:
|
|
errs = next(validator.descend(instance, subschema), None)
|
|
if errs is None:
|
|
evaluated_keys += find_evaluated_property_keys_by_schema(
|
|
validator, instance, subschema,
|
|
)
|
|
|
|
if "if" in schema:
|
|
if validator.evolve(schema=schema["if"]).is_valid(instance):
|
|
evaluated_keys += find_evaluated_property_keys_by_schema(
|
|
validator, instance, schema["if"],
|
|
)
|
|
if "then" in schema:
|
|
evaluated_keys += find_evaluated_property_keys_by_schema(
|
|
validator, instance, schema["then"],
|
|
)
|
|
elif "else" in schema:
|
|
evaluated_keys += find_evaluated_property_keys_by_schema(
|
|
validator, instance, schema["else"],
|
|
)
|
|
|
|
return evaluated_keys
|
|
|
|
|
|
def unevaluatedProperties_draft2019(validator, uP, instance, schema):
|
|
if not validator.is_type(instance, "object"):
|
|
return
|
|
evaluated_keys = find_evaluated_property_keys_by_schema(
|
|
validator, instance, schema,
|
|
)
|
|
unevaluated_keys = []
|
|
for property in instance:
|
|
if property not in evaluated_keys:
|
|
for _ in validator.descend(
|
|
instance[property],
|
|
uP,
|
|
path=property,
|
|
schema_path=property,
|
|
):
|
|
# FIXME: Include context for each unevaluated property
|
|
# indicating why it's invalid under the subschema.
|
|
unevaluated_keys.append(property) # noqa: PERF401
|
|
|
|
if unevaluated_keys:
|
|
if uP is False:
|
|
error = "Unevaluated properties are not allowed (%s %s unexpected)"
|
|
extras = sorted(unevaluated_keys, key=str)
|
|
yield ValidationError(error % _utils.extras_msg(extras))
|
|
else:
|
|
error = (
|
|
"Unevaluated properties are not valid under "
|
|
"the given schema (%s %s unevaluated and invalid)"
|
|
)
|
|
yield ValidationError(error % _utils.extras_msg(unevaluated_keys))
|