1from fractions import Fraction
2import re
3
4from jsonschema._utils import (
5 ensure_list,
6 equal,
7 extras_msg,
8 find_additional_properties,
9 find_evaluated_item_indexes_by_schema,
10 find_evaluated_property_keys_by_schema,
11 uniq,
12)
13from jsonschema.exceptions import FormatError, ValidationError
14
15
16def patternProperties(validator, patternProperties, instance, schema):
17 if not validator.is_type(instance, "object"):
18 return
19
20 for pattern, subschema in patternProperties.items():
21 for k, v in instance.items():
22 if re.search(pattern, k):
23 yield from validator.descend(
24 v, subschema, path=k, schema_path=pattern,
25 )
26
27
28def propertyNames(validator, propertyNames, instance, schema):
29 if not validator.is_type(instance, "object"):
30 return
31
32 for property in instance:
33 yield from validator.descend(instance=property, schema=propertyNames)
34
35
36def additionalProperties(validator, aP, instance, schema):
37 if not validator.is_type(instance, "object"):
38 return
39
40 extras = set(find_additional_properties(instance, schema))
41
42 if validator.is_type(aP, "object"):
43 for extra in extras:
44 yield from validator.descend(instance[extra], aP, path=extra)
45 elif not aP and extras:
46 if "patternProperties" in schema:
47 verb = "does" if len(extras) == 1 else "do"
48 joined = ", ".join(repr(each) for each in sorted(extras))
49 patterns = ", ".join(
50 repr(each) for each in sorted(schema["patternProperties"])
51 )
52 error = f"{joined} {verb} not match any of the regexes: {patterns}"
53 yield ValidationError(error)
54 else:
55 error = "Additional properties are not allowed (%s %s unexpected)"
56 yield ValidationError(error % extras_msg(sorted(extras, key=str)))
57
58
59def items(validator, items, instance, schema):
60 if not validator.is_type(instance, "array"):
61 return
62
63 prefix = len(schema.get("prefixItems", []))
64 total = len(instance)
65 extra = total - prefix
66 if extra <= 0:
67 return
68
69 if items is False:
70 rest = instance[prefix:] if extra != 1 else instance[prefix]
71 item = "items" if prefix != 1 else "item"
72 yield ValidationError(
73 f"Expected at most {prefix} {item} but found {extra} "
74 f"extra: {rest!r}",
75 )
76 else:
77 for index in range(prefix, total):
78 yield from validator.descend(
79 instance=instance[index],
80 schema=items,
81 path=index,
82 )
83
84
85def const(validator, const, instance, schema):
86 if not equal(instance, const):
87 yield ValidationError(f"{const!r} was expected")
88
89
90def contains(validator, contains, instance, schema):
91 if not validator.is_type(instance, "array"):
92 return
93
94 matches = 0
95 min_contains = schema.get("minContains", 1)
96 max_contains = schema.get("maxContains", len(instance))
97
98 contains_validator = validator.evolve(schema=contains)
99
100 for each in instance:
101 if contains_validator.is_valid(each):
102 matches += 1
103 if matches > max_contains:
104 yield ValidationError(
105 "Too many items match the given schema "
106 f"(expected at most {max_contains})",
107 validator="maxContains",
108 validator_value=max_contains,
109 )
110 return
111
112 if matches < min_contains:
113 if not matches:
114 yield ValidationError(
115 f"{instance!r} does not contain items "
116 "matching the given schema",
117 )
118 else:
119 yield ValidationError(
120 "Too few items match the given schema (expected at least "
121 f"{min_contains} but only {matches} matched)",
122 validator="minContains",
123 validator_value=min_contains,
124 )
125
126
127def exclusiveMinimum(validator, minimum, instance, schema):
128 if not validator.is_type(instance, "number"):
129 return
130
131 if instance <= minimum:
132 yield ValidationError(
133 f"{instance!r} is less than or equal to "
134 f"the minimum of {minimum!r}",
135 )
136
137
138def exclusiveMaximum(validator, maximum, instance, schema):
139 if not validator.is_type(instance, "number"):
140 return
141
142 if instance >= maximum:
143 yield ValidationError(
144 f"{instance!r} is greater than or equal "
145 f"to the maximum of {maximum!r}",
146 )
147
148
149def minimum(validator, minimum, instance, schema):
150 if not validator.is_type(instance, "number"):
151 return
152
153 if instance < minimum:
154 message = f"{instance!r} is less than the minimum of {minimum!r}"
155 yield ValidationError(message)
156
157
158def maximum(validator, maximum, instance, schema):
159 if not validator.is_type(instance, "number"):
160 return
161
162 if instance > maximum:
163 message = f"{instance!r} is greater than the maximum of {maximum!r}"
164 yield ValidationError(message)
165
166
167def multipleOf(validator, dB, instance, schema):
168 if not validator.is_type(instance, "number"):
169 return
170
171 if isinstance(dB, float):
172 quotient = instance / dB
173 try:
174 failed = int(quotient) != quotient
175 except OverflowError:
176 # When `instance` is large and `dB` is less than one,
177 # quotient can overflow to infinity; and then casting to int
178 # raises an error.
179 #
180 # In this case we fall back to Fraction logic, which is
181 # exact and cannot overflow. The performance is also
182 # acceptable: we try the fast all-float option first, and
183 # we know that fraction(dB) can have at most a few hundred
184 # digits in each part. The worst-case slowdown is therefore
185 # for already-slow enormous integers or Decimals.
186 failed = (Fraction(instance) / Fraction(dB)).denominator != 1
187 else:
188 failed = instance % dB
189
190 if failed:
191 yield ValidationError(f"{instance!r} is not a multiple of {dB}")
192
193
194def minItems(validator, mI, instance, schema):
195 if validator.is_type(instance, "array") and len(instance) < mI:
196 message = "should be non-empty" if mI == 1 else "is too short"
197 yield ValidationError(f"{instance!r} {message}")
198
199
200def maxItems(validator, mI, instance, schema):
201 if validator.is_type(instance, "array") and len(instance) > mI:
202 message = "is expected to be empty" if mI == 0 else "is too long"
203 yield ValidationError(f"{instance!r} {message}")
204
205
206def uniqueItems(validator, uI, instance, schema):
207 if (
208 uI
209 and validator.is_type(instance, "array")
210 and not uniq(instance)
211 ):
212 yield ValidationError(f"{instance!r} has non-unique elements")
213
214
215def pattern(validator, patrn, instance, schema):
216 if (
217 validator.is_type(instance, "string")
218 and not re.search(patrn, instance)
219 ):
220 yield ValidationError(f"{instance!r} does not match {patrn!r}")
221
222
223def format(validator, format, instance, schema):
224 if validator.format_checker is not None:
225 try:
226 validator.format_checker.check(instance, format)
227 except FormatError as error:
228 yield ValidationError(error.message, cause=error.cause)
229
230
231def minLength(validator, mL, instance, schema):
232 if validator.is_type(instance, "string") and len(instance) < mL:
233 message = "should be non-empty" if mL == 1 else "is too short"
234 yield ValidationError(f"{instance!r} {message}")
235
236
237def maxLength(validator, mL, instance, schema):
238 if validator.is_type(instance, "string") and len(instance) > mL:
239 message = "is expected to be empty" if mL == 0 else "is too long"
240 yield ValidationError(f"{instance!r} {message}")
241
242
243def dependentRequired(validator, dependentRequired, instance, schema):
244 if not validator.is_type(instance, "object"):
245 return
246
247 for property, dependency in dependentRequired.items():
248 if property not in instance:
249 continue
250
251 for each in dependency:
252 if each not in instance:
253 message = f"{each!r} is a dependency of {property!r}"
254 yield ValidationError(message)
255
256
257def dependentSchemas(validator, dependentSchemas, instance, schema):
258 if not validator.is_type(instance, "object"):
259 return
260
261 for property, dependency in dependentSchemas.items():
262 if property not in instance:
263 continue
264 yield from validator.descend(
265 instance, dependency, schema_path=property,
266 )
267
268
269def enum(validator, enums, instance, schema):
270 if all(not equal(each, instance) for each in enums):
271 yield ValidationError(f"{instance!r} is not one of {enums!r}")
272
273
274def ref(validator, ref, instance, schema):
275 yield from validator._validate_reference(ref=ref, instance=instance)
276
277
278def dynamicRef(validator, dynamicRef, instance, schema):
279 yield from validator._validate_reference(ref=dynamicRef, instance=instance)
280
281
282def type(validator, types, instance, schema):
283 types = ensure_list(types)
284
285 if not any(validator.is_type(instance, type) for type in types):
286 reprs = ", ".join(repr(type) for type in types)
287 yield ValidationError(f"{instance!r} is not of type {reprs}")
288
289
290def properties(validator, properties, instance, schema):
291 if not validator.is_type(instance, "object"):
292 return
293
294 for property, subschema in properties.items():
295 if property in instance:
296 yield from validator.descend(
297 instance[property],
298 subschema,
299 path=property,
300 schema_path=property,
301 )
302
303
304def required(validator, required, instance, schema):
305 if not validator.is_type(instance, "object"):
306 return
307 for property in required:
308 if property not in instance:
309 yield ValidationError(f"{property!r} is a required property")
310
311
312def minProperties(validator, mP, instance, schema):
313 if validator.is_type(instance, "object") and len(instance) < mP:
314 message = (
315 "should be non-empty" if mP == 1
316 else "does not have enough properties"
317 )
318 yield ValidationError(f"{instance!r} {message}")
319
320
321def maxProperties(validator, mP, instance, schema):
322 if not validator.is_type(instance, "object"):
323 return
324 if validator.is_type(instance, "object") and len(instance) > mP:
325 message = (
326 "is expected to be empty" if mP == 0
327 else "has too many properties"
328 )
329 yield ValidationError(f"{instance!r} {message}")
330
331
332def allOf(validator, allOf, instance, schema):
333 for index, subschema in enumerate(allOf):
334 yield from validator.descend(instance, subschema, schema_path=index)
335
336
337def anyOf(validator, anyOf, instance, schema):
338 all_errors = []
339 for index, subschema in enumerate(anyOf):
340 errs = list(validator.descend(instance, subschema, schema_path=index))
341 if not errs:
342 break
343 all_errors.extend(errs)
344 else:
345 yield ValidationError(
346 f"{instance!r} is not valid under any of the given schemas",
347 context=all_errors,
348 )
349
350
351def oneOf(validator, oneOf, instance, schema):
352 subschemas = enumerate(oneOf)
353 all_errors = []
354 for index, subschema in subschemas:
355 errs = list(validator.descend(instance, subschema, schema_path=index))
356 if not errs:
357 first_valid = subschema
358 break
359 all_errors.extend(errs)
360 else:
361 yield ValidationError(
362 f"{instance!r} is not valid under any of the given schemas",
363 context=all_errors,
364 )
365
366 more_valid = [
367 each for _, each in subschemas
368 if validator.evolve(schema=each).is_valid(instance)
369 ]
370 if more_valid:
371 more_valid.append(first_valid)
372 reprs = ", ".join(repr(schema) for schema in more_valid)
373 yield ValidationError(f"{instance!r} is valid under each of {reprs}")
374
375
376def not_(validator, not_schema, instance, schema):
377 if validator.evolve(schema=not_schema).is_valid(instance):
378 message = f"{instance!r} should not be valid under {not_schema!r}"
379 yield ValidationError(message)
380
381
382def if_(validator, if_schema, instance, schema):
383 if validator.evolve(schema=if_schema).is_valid(instance):
384 if "then" in schema:
385 then = schema["then"]
386 yield from validator.descend(instance, then, schema_path="then")
387 elif "else" in schema:
388 else_ = schema["else"]
389 yield from validator.descend(instance, else_, schema_path="else")
390
391
392def unevaluatedItems(validator, unevaluatedItems, instance, schema):
393 if not validator.is_type(instance, "array"):
394 return
395 evaluated_item_indexes = find_evaluated_item_indexes_by_schema(
396 validator, instance, schema,
397 )
398 unevaluated_items = [
399 item for index, item in enumerate(instance)
400 if index not in evaluated_item_indexes
401 ]
402 if unevaluated_items:
403 error = "Unevaluated items are not allowed (%s %s unexpected)"
404 yield ValidationError(error % extras_msg(unevaluated_items))
405
406
407def unevaluatedProperties(validator, unevaluatedProperties, instance, schema):
408 if not validator.is_type(instance, "object"):
409 return
410 evaluated_keys = find_evaluated_property_keys_by_schema(
411 validator, instance, schema,
412 )
413 unevaluated_keys = []
414 for property in instance:
415 if property not in evaluated_keys:
416 for _ in validator.descend(
417 instance[property],
418 unevaluatedProperties,
419 path=property,
420 schema_path=property,
421 ):
422 # FIXME: Include context for each unevaluated property
423 # indicating why it's invalid under the subschema.
424 unevaluated_keys.append(property) # noqa: PERF401
425
426 if unevaluated_keys:
427 if unevaluatedProperties is False:
428 error = "Unevaluated properties are not allowed (%s %s unexpected)"
429 extras = sorted(unevaluated_keys, key=str)
430 yield ValidationError(error % extras_msg(extras))
431 else:
432 error = (
433 "Unevaluated properties are not valid under "
434 "the given schema (%s %s unevaluated and invalid)"
435 )
436 yield ValidationError(error % extras_msg(unevaluated_keys))
437
438
439def prefixItems(validator, prefixItems, instance, schema):
440 if not validator.is_type(instance, "array"):
441 return
442
443 for (index, item), subschema in zip(enumerate(instance), prefixItems):
444 yield from validator.descend(
445 instance=item,
446 schema=subschema,
447 schema_path=index,
448 path=index,
449 )