1import re
2
3from referencing.jsonschema import lookup_recursive_ref
4
5from jsonschema import _utils
6from jsonschema.exceptions import ValidationError
7
8
9def ignore_ref_siblings(schema):
10 """
11 Ignore siblings of ``$ref`` if it is present.
12
13 Otherwise, return all keywords.
14
15 Suitable for use with `create`'s ``applicable_validators`` argument.
16 """
17 ref = schema.get("$ref")
18 if ref is not None:
19 return [("$ref", ref)]
20 else:
21 return schema.items()
22
23
24def dependencies_draft3(validator, dependencies, instance, schema):
25 if not validator.is_type(instance, "object"):
26 return
27
28 for property, dependency in dependencies.items():
29 if property not in instance:
30 continue
31
32 if validator.is_type(dependency, "object"):
33 yield from validator.descend(
34 instance, dependency, schema_path=property,
35 )
36 elif validator.is_type(dependency, "string"):
37 if dependency not in instance:
38 message = f"{dependency!r} is a dependency of {property!r}"
39 yield ValidationError(message)
40 else:
41 for each in dependency:
42 if each not in instance:
43 message = f"{each!r} is a dependency of {property!r}"
44 yield ValidationError(message)
45
46
47def dependencies_draft4_draft6_draft7(
48 validator,
49 dependencies,
50 instance,
51 schema,
52):
53 """
54 Support for the ``dependencies`` keyword from pre-draft 2019-09.
55
56 In later drafts, the keyword was split into separate
57 ``dependentRequired`` and ``dependentSchemas`` validators.
58 """
59 if not validator.is_type(instance, "object"):
60 return
61
62 for property, dependency in dependencies.items():
63 if property not in instance:
64 continue
65
66 if validator.is_type(dependency, "array"):
67 for each in dependency:
68 if each not in instance:
69 message = f"{each!r} is a dependency of {property!r}"
70 yield ValidationError(message)
71 else:
72 yield from validator.descend(
73 instance, dependency, schema_path=property,
74 )
75
76
77def disallow_draft3(validator, disallow, instance, schema):
78 for disallowed in _utils.ensure_list(disallow):
79 if validator.evolve(schema={"type": [disallowed]}).is_valid(instance):
80 message = f"{disallowed!r} is disallowed for {instance!r}"
81 yield ValidationError(message)
82
83
84def extends_draft3(validator, extends, instance, schema):
85 if validator.is_type(extends, "object"):
86 yield from validator.descend(instance, extends)
87 return
88 for index, subschema in enumerate(extends):
89 yield from validator.descend(instance, subschema, schema_path=index)
90
91
92def items_draft3_draft4(validator, items, instance, schema):
93 if not validator.is_type(instance, "array"):
94 return
95
96 if validator.is_type(items, "object"):
97 for index, item in enumerate(instance):
98 yield from validator.descend(item, items, path=index)
99 else:
100 for (index, item), subschema in zip(enumerate(instance), items):
101 yield from validator.descend(
102 item, subschema, path=index, schema_path=index,
103 )
104
105
106def additionalItems(validator, aI, instance, schema):
107 if (
108 not validator.is_type(instance, "array")
109 or validator.is_type(schema.get("items", {}), "object")
110 ):
111 return
112
113 len_items = len(schema.get("items", []))
114 if validator.is_type(aI, "object"):
115 for index, item in enumerate(instance[len_items:], start=len_items):
116 yield from validator.descend(item, aI, path=index)
117 elif not aI and len(instance) > len(schema.get("items", [])):
118 error = "Additional items are not allowed (%s %s unexpected)"
119 yield ValidationError(
120 error % _utils.extras_msg(instance[len(schema.get("items", [])):]),
121 )
122
123
124def items_draft6_draft7_draft201909(validator, items, instance, schema):
125 if not validator.is_type(instance, "array"):
126 return
127
128 if validator.is_type(items, "array"):
129 for (index, item), subschema in zip(enumerate(instance), items):
130 yield from validator.descend(
131 item, subschema, path=index, schema_path=index,
132 )
133 else:
134 for index, item in enumerate(instance):
135 yield from validator.descend(item, items, path=index)
136
137
138def minimum_draft3_draft4(validator, minimum, instance, schema):
139 if not validator.is_type(instance, "number"):
140 return
141
142 if schema.get("exclusiveMinimum", False):
143 failed = instance <= minimum
144 cmp = "less than or equal to"
145 else:
146 failed = instance < minimum
147 cmp = "less than"
148
149 if failed:
150 message = f"{instance!r} is {cmp} the minimum of {minimum!r}"
151 yield ValidationError(message)
152
153
154def maximum_draft3_draft4(validator, maximum, instance, schema):
155 if not validator.is_type(instance, "number"):
156 return
157
158 if schema.get("exclusiveMaximum", False):
159 failed = instance >= maximum
160 cmp = "greater than or equal to"
161 else:
162 failed = instance > maximum
163 cmp = "greater than"
164
165 if failed:
166 message = f"{instance!r} is {cmp} the maximum of {maximum!r}"
167 yield ValidationError(message)
168
169
170def properties_draft3(validator, properties, instance, schema):
171 if not validator.is_type(instance, "object"):
172 return
173
174 for property, subschema in properties.items():
175 if property in instance:
176 yield from validator.descend(
177 instance[property],
178 subschema,
179 path=property,
180 schema_path=property,
181 )
182 elif subschema.get("required", False):
183 error = ValidationError(f"{property!r} is a required property")
184 error._set(
185 validator="required",
186 validator_value=subschema["required"],
187 instance=instance,
188 schema=schema,
189 )
190 error.path.appendleft(property)
191 error.schema_path.extend([property, "required"])
192 yield error
193
194
195def type_draft3(validator, types, instance, schema):
196 types = _utils.ensure_list(types)
197
198 all_errors = []
199 for index, type in enumerate(types):
200 if validator.is_type(type, "object"):
201 errors = list(validator.descend(instance, type, schema_path=index))
202 if not errors:
203 return
204 all_errors.extend(errors)
205 elif validator.is_type(instance, type):
206 return
207
208 reprs = []
209 for type in types:
210 try:
211 reprs.append(repr(type["name"]))
212 except Exception: # noqa: BLE001
213 reprs.append(repr(type))
214 yield ValidationError(
215 f"{instance!r} is not of type {', '.join(reprs)}",
216 context=all_errors,
217 )
218
219
220def contains_draft6_draft7(validator, contains, instance, schema):
221 if not validator.is_type(instance, "array"):
222 return
223
224 if not any(
225 validator.evolve(schema=contains).is_valid(element)
226 for element in instance
227 ):
228 yield ValidationError(
229 f"None of {instance!r} are valid under the given schema",
230 )
231
232
233def recursiveRef(validator, recursiveRef, instance, schema):
234 resolved = lookup_recursive_ref(validator._resolver)
235 yield from validator.descend(
236 instance,
237 resolved.contents,
238 resolver=resolved.resolver,
239 )
240
241
242def find_evaluated_item_indexes_by_schema(validator, instance, schema):
243 """
244 Get all indexes of items that get evaluated under the current schema.
245
246 Covers all keywords related to unevaluatedItems: items, prefixItems, if,
247 then, else, contains, unevaluatedItems, allOf, oneOf, anyOf
248 """
249 if validator.is_type(schema, "boolean"):
250 return []
251 evaluated_indexes = []
252
253 ref = schema.get("$ref")
254 if ref is not None:
255 resolved = validator._resolver.lookup(ref)
256 evaluated_indexes.extend(
257 find_evaluated_item_indexes_by_schema(
258 validator.evolve(
259 schema=resolved.contents,
260 _resolver=resolved.resolver,
261 ),
262 instance,
263 resolved.contents,
264 ),
265 )
266
267 if "$recursiveRef" in schema:
268 resolved = lookup_recursive_ref(validator._resolver)
269 evaluated_indexes.extend(
270 find_evaluated_item_indexes_by_schema(
271 validator.evolve(
272 schema=resolved.contents,
273 _resolver=resolved.resolver,
274 ),
275 instance,
276 resolved.contents,
277 ),
278 )
279
280 if "items" in schema:
281 if "additionalItems" in schema:
282 return list(range(len(instance)))
283
284 if validator.is_type(schema["items"], "object"):
285 return list(range(len(instance)))
286 evaluated_indexes += list(range(len(schema["items"])))
287
288 if "if" in schema:
289 if validator.evolve(schema=schema["if"]).is_valid(instance):
290 evaluated_indexes += find_evaluated_item_indexes_by_schema(
291 validator, instance, schema["if"],
292 )
293 if "then" in schema:
294 evaluated_indexes += find_evaluated_item_indexes_by_schema(
295 validator, instance, schema["then"],
296 )
297 elif "else" in schema:
298 evaluated_indexes += find_evaluated_item_indexes_by_schema(
299 validator, instance, schema["else"],
300 )
301
302 for keyword in ["contains", "unevaluatedItems"]:
303 if keyword in schema:
304 for k, v in enumerate(instance):
305 if validator.evolve(schema=schema[keyword]).is_valid(v):
306 evaluated_indexes.append(k)
307
308 for keyword in ["allOf", "oneOf", "anyOf"]:
309 if keyword in schema:
310 for subschema in schema[keyword]:
311 errs = next(validator.descend(instance, subschema), None)
312 if errs is None:
313 evaluated_indexes += find_evaluated_item_indexes_by_schema(
314 validator, instance, subschema,
315 )
316
317 return evaluated_indexes
318
319
320def unevaluatedItems_draft2019(validator, unevaluatedItems, instance, schema):
321 if not validator.is_type(instance, "array"):
322 return
323 evaluated_item_indexes = find_evaluated_item_indexes_by_schema(
324 validator, instance, schema,
325 )
326 unevaluated_items = [
327 item for index, item in enumerate(instance)
328 if index not in evaluated_item_indexes
329 ]
330 if unevaluated_items:
331 error = "Unevaluated items are not allowed (%s %s unexpected)"
332 yield ValidationError(error % _utils.extras_msg(unevaluated_items))
333
334
335def find_evaluated_property_keys_by_schema(validator, instance, schema):
336 if validator.is_type(schema, "boolean"):
337 return []
338 evaluated_keys = []
339
340 ref = schema.get("$ref")
341 if ref is not None:
342 resolved = validator._resolver.lookup(ref)
343 evaluated_keys.extend(
344 find_evaluated_property_keys_by_schema(
345 validator.evolve(
346 schema=resolved.contents,
347 _resolver=resolved.resolver,
348 ),
349 instance,
350 resolved.contents,
351 ),
352 )
353
354 if "$recursiveRef" in schema:
355 resolved = lookup_recursive_ref(validator._resolver)
356 evaluated_keys.extend(
357 find_evaluated_property_keys_by_schema(
358 validator.evolve(
359 schema=resolved.contents,
360 _resolver=resolved.resolver,
361 ),
362 instance,
363 resolved.contents,
364 ),
365 )
366
367 for keyword in [
368 "properties", "additionalProperties", "unevaluatedProperties",
369 ]:
370 if keyword in schema:
371 schema_value = schema[keyword]
372 if validator.is_type(schema_value, "boolean") and schema_value:
373 evaluated_keys += instance.keys()
374
375 elif validator.is_type(schema_value, "object"):
376 for property in schema_value:
377 if property in instance:
378 evaluated_keys.append(property)
379
380 if "patternProperties" in schema:
381 for property in instance:
382 for pattern in schema["patternProperties"]:
383 if re.search(pattern, property):
384 evaluated_keys.append(property)
385
386 if "dependentSchemas" in schema:
387 for property, subschema in schema["dependentSchemas"].items():
388 if property not in instance:
389 continue
390 evaluated_keys += find_evaluated_property_keys_by_schema(
391 validator, instance, subschema,
392 )
393
394 for keyword in ["allOf", "oneOf", "anyOf"]:
395 if keyword in schema:
396 for subschema in schema[keyword]:
397 errs = next(validator.descend(instance, subschema), None)
398 if errs is None:
399 evaluated_keys += find_evaluated_property_keys_by_schema(
400 validator, instance, subschema,
401 )
402
403 if "if" in schema:
404 if validator.evolve(schema=schema["if"]).is_valid(instance):
405 evaluated_keys += find_evaluated_property_keys_by_schema(
406 validator, instance, schema["if"],
407 )
408 if "then" in schema:
409 evaluated_keys += find_evaluated_property_keys_by_schema(
410 validator, instance, schema["then"],
411 )
412 elif "else" in schema:
413 evaluated_keys += find_evaluated_property_keys_by_schema(
414 validator, instance, schema["else"],
415 )
416
417 return evaluated_keys
418
419
420def unevaluatedProperties_draft2019(validator, uP, instance, schema):
421 if not validator.is_type(instance, "object"):
422 return
423 evaluated_keys = find_evaluated_property_keys_by_schema(
424 validator, instance, schema,
425 )
426 unevaluated_keys = []
427 for property in instance:
428 if property not in evaluated_keys:
429 for _ in validator.descend(
430 instance[property],
431 uP,
432 path=property,
433 schema_path=property,
434 ):
435 # FIXME: Include context for each unevaluated property
436 # indicating why it's invalid under the subschema.
437 unevaluated_keys.append(property) # noqa: PERF401
438
439 if unevaluated_keys:
440 if uP is False:
441 error = "Unevaluated properties are not allowed (%s %s unexpected)"
442 extras = sorted(unevaluated_keys, key=str)
443 yield ValidationError(error % _utils.extras_msg(extras))
444 else:
445 error = (
446 "Unevaluated properties are not valid under "
447 "the given schema (%s %s unevaluated and invalid)"
448 )
449 yield ValidationError(error % _utils.extras_msg(unevaluated_keys))