_keywords.py 15 KB

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