|
| 1 | +import re |
| 2 | + |
1 | 3 | from referencing.jsonschema import lookup_recursive_ref
|
2 | 4 |
|
3 | 5 | from jsonschema import _utils
|
@@ -249,8 +251,22 @@ def find_evaluated_item_indexes_by_schema(validator, instance, schema):
|
249 | 251 | return []
|
250 | 252 | evaluated_indexes = []
|
251 | 253 |
|
252 |
| - if "$ref" in schema: |
253 |
| - resolved = validator._resolver.lookup(schema["$ref"]) |
| 254 | + ref = schema.get("$ref") |
| 255 | + if ref is not None: |
| 256 | + resolved = validator._resolver.lookup(ref) |
| 257 | + evaluated_indexes.extend( |
| 258 | + find_evaluated_item_indexes_by_schema( |
| 259 | + validator.evolve( |
| 260 | + schema=resolved.contents, |
| 261 | + _resolver=resolved.resolver, |
| 262 | + ), |
| 263 | + instance, |
| 264 | + resolved.contents, |
| 265 | + ), |
| 266 | + ) |
| 267 | + |
| 268 | + if "$recursiveRef" in schema: |
| 269 | + resolved = lookup_recursive_ref(validator._resolver) |
254 | 270 | evaluated_indexes.extend(
|
255 | 271 | find_evaluated_item_indexes_by_schema(
|
256 | 272 | validator.evolve(
|
@@ -316,3 +332,121 @@ def unevaluatedItems_draft2019(validator, unevaluatedItems, instance, schema):
|
316 | 332 | if unevaluated_items:
|
317 | 333 | error = "Unevaluated items are not allowed (%s %s unexpected)"
|
318 | 334 | yield ValidationError(error % _utils.extras_msg(unevaluated_items))
|
| 335 | + |
| 336 | + |
| 337 | +def find_evaluated_property_keys_by_schema(validator, instance, schema): |
| 338 | + if validator.is_type(schema, "boolean"): |
| 339 | + return [] |
| 340 | + evaluated_keys = [] |
| 341 | + |
| 342 | + ref = schema.get("$ref") |
| 343 | + if ref is not None: |
| 344 | + resolved = validator._resolver.lookup(ref) |
| 345 | + evaluated_keys.extend( |
| 346 | + find_evaluated_property_keys_by_schema( |
| 347 | + validator.evolve( |
| 348 | + schema=resolved.contents, |
| 349 | + _resolver=resolved.resolver, |
| 350 | + ), |
| 351 | + instance, |
| 352 | + resolved.contents, |
| 353 | + ), |
| 354 | + ) |
| 355 | + |
| 356 | + if "$recursiveRef" in schema: |
| 357 | + resolved = lookup_recursive_ref(validator._resolver) |
| 358 | + evaluated_keys.extend( |
| 359 | + find_evaluated_property_keys_by_schema( |
| 360 | + validator.evolve( |
| 361 | + schema=resolved.contents, |
| 362 | + _resolver=resolved.resolver, |
| 363 | + ), |
| 364 | + instance, |
| 365 | + resolved.contents, |
| 366 | + ), |
| 367 | + ) |
| 368 | + |
| 369 | + for keyword in [ |
| 370 | + "properties", "additionalProperties", "unevaluatedProperties", |
| 371 | + ]: |
| 372 | + if keyword in schema: |
| 373 | + schema_value = schema[keyword] |
| 374 | + if validator.is_type(schema_value, "boolean") and schema_value: |
| 375 | + evaluated_keys += instance.keys() |
| 376 | + |
| 377 | + elif validator.is_type(schema_value, "object"): |
| 378 | + for property in schema_value: |
| 379 | + if property in instance: |
| 380 | + evaluated_keys.append(property) |
| 381 | + |
| 382 | + if "patternProperties" in schema: |
| 383 | + for property in instance: |
| 384 | + for pattern in schema["patternProperties"]: |
| 385 | + if re.search(pattern, property): |
| 386 | + evaluated_keys.append(property) |
| 387 | + |
| 388 | + if "dependentSchemas" in schema: |
| 389 | + for property, subschema in schema["dependentSchemas"].items(): |
| 390 | + if property not in instance: |
| 391 | + continue |
| 392 | + evaluated_keys += find_evaluated_property_keys_by_schema( |
| 393 | + validator, instance, subschema, |
| 394 | + ) |
| 395 | + |
| 396 | + for keyword in ["allOf", "oneOf", "anyOf"]: |
| 397 | + if keyword in schema: |
| 398 | + for subschema in schema[keyword]: |
| 399 | + errs = next(validator.descend(instance, subschema), None) |
| 400 | + if errs is None: |
| 401 | + evaluated_keys += find_evaluated_property_keys_by_schema( |
| 402 | + validator, instance, subschema, |
| 403 | + ) |
| 404 | + |
| 405 | + if "if" in schema: |
| 406 | + if validator.evolve(schema=schema["if"]).is_valid(instance): |
| 407 | + evaluated_keys += find_evaluated_property_keys_by_schema( |
| 408 | + validator, instance, schema["if"], |
| 409 | + ) |
| 410 | + if "then" in schema: |
| 411 | + evaluated_keys += find_evaluated_property_keys_by_schema( |
| 412 | + validator, instance, schema["then"], |
| 413 | + ) |
| 414 | + else: |
| 415 | + if "else" in schema: |
| 416 | + evaluated_keys += find_evaluated_property_keys_by_schema( |
| 417 | + validator, instance, schema["else"], |
| 418 | + ) |
| 419 | + |
| 420 | + return evaluated_keys |
| 421 | + |
| 422 | + |
| 423 | +def unevaluatedProperties_draft2019(validator, uP, instance, schema): |
| 424 | + if not validator.is_type(instance, "object"): |
| 425 | + return |
| 426 | + evaluated_keys = find_evaluated_property_keys_by_schema( |
| 427 | + validator, instance, schema, |
| 428 | + ) |
| 429 | + unevaluated_keys = [] |
| 430 | + for property in instance: |
| 431 | + if property not in evaluated_keys: |
| 432 | + for _ in validator.descend( |
| 433 | + instance[property], |
| 434 | + uP, |
| 435 | + path=property, |
| 436 | + schema_path=property, |
| 437 | + ): |
| 438 | + # FIXME: Include context for each unevaluated property |
| 439 | + # indicating why it's invalid under the subschema. |
| 440 | + unevaluated_keys.append(property) |
| 441 | + |
| 442 | + if unevaluated_keys: |
| 443 | + if uP is False: |
| 444 | + error = "Unevaluated properties are not allowed (%s %s unexpected)" |
| 445 | + extras = sorted(unevaluated_keys, key=str) |
| 446 | + yield ValidationError(error % _utils.extras_msg(extras)) |
| 447 | + else: |
| 448 | + error = ( |
| 449 | + "Unevaluated properties are not valid under " |
| 450 | + "the given schema (%s %s unevaluated and invalid)" |
| 451 | + ) |
| 452 | + yield ValidationError(error % _utils.extras_msg(unevaluated_keys)) |
0 commit comments