1from copy import deepcopy
2from typing import Any
3from typing import Dict
4from typing import Hashable
5from typing import ItemsView
6from typing import Iterator
7from typing import List
8from typing import Mapping
9from typing import Union
10
11from jsonschema._keywords import allOf as _allOf
12from jsonschema._keywords import anyOf as _anyOf
13from jsonschema._keywords import oneOf as _oneOf
14from jsonschema._utils import extras_msg
15from jsonschema._utils import find_additional_properties
16from jsonschema.exceptions import FormatError
17from jsonschema.exceptions import ValidationError
18from jsonschema.protocols import Validator
19
20
21def handle_discriminator(
22 validator: Validator, _: Any, instance: Any, schema: Mapping[Hashable, Any]
23) -> Iterator[ValidationError]:
24 """
25 Handle presence of discriminator in anyOf, oneOf and allOf.
26 The behaviour is the same in all 3 cases because at most 1 schema will match.
27 """
28 discriminator = schema["discriminator"]
29 prop_name = discriminator["propertyName"]
30 prop_value = instance.get(prop_name)
31 if not prop_value:
32 # instance is missing $propertyName
33 yield ValidationError(
34 f"{instance!r} does not contain discriminating property {prop_name!r}",
35 context=[],
36 )
37 return
38
39 # Use explicit mapping if available, otherwise try implicit value
40 ref = (
41 discriminator.get("mapping", {}).get(prop_value)
42 or f"#/components/schemas/{prop_value}"
43 )
44
45 if not isinstance(ref, str):
46 # this is a schema error
47 yield ValidationError(
48 "{!r} mapped value for {!r} should be a string, was {!r}".format(
49 instance, prop_value, ref
50 ),
51 context=[],
52 )
53 return
54
55 try:
56 validator._validate_reference(ref=ref, instance=instance)
57 except:
58 yield ValidationError(
59 f"{instance!r} reference {ref!r} could not be resolved",
60 context=[],
61 )
62 return
63
64 yield from validator.descend(instance, {"$ref": ref})
65
66
67def anyOf(
68 validator: Validator,
69 anyOf: List[Mapping[Hashable, Any]],
70 instance: Any,
71 schema: Mapping[Hashable, Any],
72) -> Iterator[ValidationError]:
73 if "discriminator" not in schema:
74 yield from _anyOf(validator, anyOf, instance, schema)
75 else:
76 yield from handle_discriminator(validator, anyOf, instance, schema)
77
78
79def oneOf(
80 validator: Validator,
81 oneOf: List[Mapping[Hashable, Any]],
82 instance: Any,
83 schema: Mapping[Hashable, Any],
84) -> Iterator[ValidationError]:
85 if "discriminator" not in schema:
86 yield from _oneOf(validator, oneOf, instance, schema)
87 else:
88 yield from handle_discriminator(validator, oneOf, instance, schema)
89
90
91def allOf(
92 validator: Validator,
93 allOf: List[Mapping[Hashable, Any]],
94 instance: Any,
95 schema: Mapping[Hashable, Any],
96) -> Iterator[ValidationError]:
97 if "discriminator" not in schema:
98 yield from _allOf(validator, allOf, instance, schema)
99 else:
100 yield from handle_discriminator(validator, allOf, instance, schema)
101
102
103def type(
104 validator: Validator,
105 data_type: str,
106 instance: Any,
107 schema: Mapping[Hashable, Any],
108) -> Iterator[ValidationError]:
109 if instance is None:
110 # nullable implementation based on OAS 3.0.3
111 # * nullable is only meaningful if its value is true
112 # * nullable: true is only meaningful in combination with a type
113 # assertion specified in the same Schema Object.
114 # * nullable: true operates within a single Schema Object
115 if "nullable" in schema and schema["nullable"] == True:
116 return
117 yield ValidationError("None for not nullable")
118
119 if not validator.is_type(instance, data_type):
120 data_repr = repr(data_type)
121 yield ValidationError(f"{instance!r} is not of type {data_repr}")
122
123
124def format(
125 validator: Validator,
126 format: str,
127 instance: Any,
128 schema: Mapping[Hashable, Any],
129) -> Iterator[ValidationError]:
130 if instance is None:
131 return
132
133 if validator.format_checker is not None:
134 try:
135 validator.format_checker.check(instance, format)
136 except FormatError as error:
137 yield ValidationError(str(error), cause=error.cause)
138
139
140def items(
141 validator: Validator,
142 items: Mapping[Hashable, Any],
143 instance: Any,
144 schema: Mapping[Hashable, Any],
145) -> Iterator[ValidationError]:
146 if not validator.is_type(instance, "array"):
147 return
148
149 for index, item in enumerate(instance):
150 yield from validator.descend(item, items, path=index)
151
152
153def required(
154 validator: Validator,
155 required: List[str],
156 instance: Any,
157 schema: Mapping[Hashable, Any],
158) -> Iterator[ValidationError]:
159 if not validator.is_type(instance, "object"):
160 return
161 for property in required:
162 if property not in instance:
163 prop_schema = schema.get("properties", {}).get(property)
164 if prop_schema:
165 read_only = prop_schema.get("readOnly", False)
166 write_only = prop_schema.get("writeOnly", False)
167 if (
168 getattr(validator, "write", True)
169 and read_only
170 or getattr(validator, "read", True)
171 and write_only
172 ):
173 continue
174 yield ValidationError(f"{property!r} is a required property")
175
176
177def read_required(
178 validator: Validator,
179 required: List[str],
180 instance: Any,
181 schema: Mapping[Hashable, Any],
182) -> Iterator[ValidationError]:
183 if not validator.is_type(instance, "object"):
184 return
185 for property in required:
186 if property not in instance:
187 prop_schema = schema.get("properties", {}).get(property)
188 if prop_schema:
189 write_only = prop_schema.get("writeOnly", False)
190 if getattr(validator, "read", True) and write_only:
191 continue
192 yield ValidationError(f"{property!r} is a required property")
193
194
195def write_required(
196 validator: Validator,
197 required: List[str],
198 instance: Any,
199 schema: Mapping[Hashable, Any],
200) -> Iterator[ValidationError]:
201 if not validator.is_type(instance, "object"):
202 return
203 for property in required:
204 if property not in instance:
205 prop_schema = schema.get("properties", {}).get(property)
206 if prop_schema:
207 read_only = prop_schema.get("readOnly", False)
208 if read_only:
209 continue
210 yield ValidationError(f"{property!r} is a required property")
211
212
213def additionalProperties(
214 validator: Validator,
215 aP: Union[Mapping[Hashable, Any], bool],
216 instance: Any,
217 schema: Mapping[Hashable, Any],
218) -> Iterator[ValidationError]:
219 if not validator.is_type(instance, "object"):
220 return
221
222 extras = set(find_additional_properties(instance, schema))
223
224 if not extras:
225 return
226
227 if validator.is_type(aP, "object"):
228 for extra in extras:
229 for error in validator.descend(instance[extra], aP, path=extra):
230 yield error
231 elif validator.is_type(aP, "boolean"):
232 if not aP:
233 error = "Additional properties are not allowed (%s %s unexpected)"
234 yield ValidationError(error % extras_msg(extras))
235
236
237def write_readOnly(
238 validator: Validator,
239 ro: bool,
240 instance: Any,
241 schema: Mapping[Hashable, Any],
242) -> Iterator[ValidationError]:
243 yield ValidationError(f"Tried to write read-only property with {instance}")
244
245
246def read_writeOnly(
247 validator: Validator,
248 wo: bool,
249 instance: Any,
250 schema: Mapping[Hashable, Any],
251) -> Iterator[ValidationError]:
252 yield ValidationError(f"Tried to read write-only property with {instance}")
253
254
255def not_implemented(
256 validator: Validator,
257 value: Any,
258 instance: Any,
259 schema: Mapping[Hashable, Any],
260) -> Iterator[ValidationError]:
261 return
262 yield