_legacy_keywords.py 15 KB

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