Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 29 additions & 12 deletions sphinx_needs/schema/config_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,51 +241,68 @@ def resolve_refs(
curr_item: Any,
circular_refs_guard: set[str],
is_defs: bool = False,
path: str = "",
) -> None:
"""Recursively resolve and replace $ref entries in schemas."""
"""Recursively resolve and replace $ref entries in schemas.

``path`` accumulates the JSON location of ``curr_item`` from the root of
``needs_schema_definitions`` (e.g. ``schemas[0].validate.network.links.contains.local``
or ``$defs.safe-impl.allOf[1]``) and is appended to ``$ref`` error messages so a
faulty reference can be located without hunting through the whole config.
"""
loc = f" (at {path})" if path else ""
if isinstance(curr_item, dict):
if "$ref" in curr_item:
if len(curr_item) == 1:
ref_raw = curr_item["$ref"]
if not isinstance(ref_raw, str):
raise NeedsConfigException(
f"Invalid $ref value: {ref_raw}, expected a string."
f"Invalid $ref value: {ref_raw}, expected a string{loc}."
)
if not ref_raw.startswith("#/$defs/"):
raise NeedsConfigException(
f"Invalid $ref value: {ref_raw}, expected to start with '#/$defs/'."
f"Invalid $ref value: {ref_raw}, expected to start with '#/$defs/'{loc}."
)
ref = ref_raw[8:] # Remove '#/$defs/' prefix
if ref not in defs:
raise NeedsConfigException(
f"Reference '{ref}' not found in definitions."
f"Reference '{ref}' not found in definitions{loc}."
)
if ref in circular_refs_guard:
raise NeedsConfigException(
f"Circular reference detected for '{ref}'."
f"Circular reference detected for '{ref}'{loc}."
)
circular_refs_guard.add(ref)
curr_item.clear()
curr_item.update(defs[ref])
resolve_refs(defs, curr_item, circular_refs_guard)
resolve_refs(defs, curr_item, circular_refs_guard, path=path)
circular_refs_guard.remove(ref)
else:
raise NeedsConfigException(
f"Invalid $ref entry, expected a single $ref key: {curr_item}"
f"Invalid $ref entry, expected a single $ref key: {curr_item}{loc}"
)
for key, value in curr_item.items():
child_path = f"{path}.{key}" if path else str(key)
if isinstance(value, dict):
if is_defs:
circular_refs_guard.add(key)
resolve_refs(defs, value, circular_refs_guard, is_defs=(key == "$defs"))
resolve_refs(
defs,
value,
circular_refs_guard,
is_defs=(key == "$defs"),
path=child_path,
)
if is_defs:
circular_refs_guard.remove(key)
if isinstance(value, list):
for item in value:
resolve_refs(defs, item, circular_refs_guard)
for idx, item in enumerate(value):
resolve_refs(
defs, item, circular_refs_guard, path=f"{child_path}[{idx}]"
)
elif isinstance(curr_item, list):
for item in curr_item:
resolve_refs(defs, item, circular_refs_guard)
for idx, item in enumerate(curr_item):
resolve_refs(defs, item, circular_refs_guard, path=f"{path}[{idx}]")


def populate_field_type(
Expand Down
10 changes: 5 additions & 5 deletions tests/schema/__snapshots__/test_schema.ambr
Original file line number Diff line number Diff line change
Expand Up @@ -579,19 +579,19 @@
"Schema '[0]' defines an unknown network link type 'links2'."
# ---
# name: test_schema_config[ref_error]
"Reference 'not-exist' not found in definitions."
"Reference 'not-exist' not found in definitions (at schemas[0].validate.local)."
# ---
# name: test_schema_config[ref_mixed_error]
"Invalid $ref entry, expected a single $ref key: {'$ref': '#/$defs/not-exist', 'required': ['type']}"
"Invalid $ref entry, expected a single $ref key: {'$ref': '#/$defs/not-exist', 'required': ['type']} (at schemas[0].validate.local)"
# ---
# name: test_schema_config[ref_not_defs_prefix]
"Invalid $ref value: type-impl, expected to start with '#/$defs/'."
"Invalid $ref value: type-impl, expected to start with '#/$defs/' (at schemas[0].validate.local)."
# ---
# name: test_schema_config[ref_recursive_error]
"Circular reference detected for 'type-impl'."
"Circular reference detected for 'type-impl' (at $defs.type-impl.allOf[0])."
# ---
# name: test_schema_config[ref_value_not_string]
'Invalid $ref value: 123, expected a string.'
'Invalid $ref value: 123, expected a string (at schemas[0].validate.local).'
# ---
# name: test_schema_config[schemas_select_pattern_unsafe_error]
'''
Expand Down
Loading