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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"description": "OAS (Swagger v3) API Definition for Template API",
"scripts": {
"lint-oas": "redocly lint specification/e-referrals-service-api.yaml",
"publish": "mkdir -p build && redocly bundle specification/e-referrals-service-api.yaml --dereferenced --remove-unused-components --ext json | poetry run python scripts/set_version.py | poetry run python scripts/populate_placeholders.py > build/e-referrals-service-api.json",
"publish": "mkdir -p build && redocly bundle specification/e-referrals-service-api.yaml --dereferenced --remove-unused-components --ext json | poetry run python scripts/resolve_example_refs.py | poetry run python scripts/set_version.py | poetry run python scripts/populate_placeholders.py > build/e-referrals-service-api.json",
"serve": "redocly preview-docs -p 5000 build/e-referrals-service-api.json",
"check-licenses": "node_modules/.bin/license-checker --failOn GPL --failOn LGPL"
},
Expand Down
95 changes: 95 additions & 0 deletions scripts/resolve_example_refs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
#!/usr/bin/env python3
"""
resolve_example_refs.py

Reads a bundled OpenAPI spec on stdin and resolves any remaining $ref
objects found inside example 'value' fields. This is needed because
Redocly CLI v2 no longer dereferences $ref inside example values
during bundling.

The script walks the entire spec looking for objects of the form:
{"$ref": "some/path/to/file.json"}

that appear inside example value contexts, loads the referenced JSON
file from disk relative to the specification directory, and replaces
the $ref object with the actual file content.

Comment on lines +5 to +16
Prints the resolved spec on stdout.
"""
import json
import os
import sys

SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
REPO_ROOT = os.path.abspath(os.path.join(SCRIPT_DIR, ".."))
SPEC_COMPONENTS_DIR = os.path.join(REPO_ROOT, "specification", "components")


def resolve_ref_path(ref_path):
"""
Resolve a relative $ref path from the bundled output to an
absolute file path on disk.

In the source YAML, $refs are relative to the file they appear in:
- From schemas/endpoints/: ../../examples/... (up 2 = stu3/)
- From schemas/responses/: ../../../examples/... (up 3 = stu3/)

All paths contain 'examples/' which maps to
specification/components/{stu3,r4}/examples/.

We extract the part starting from 'examples/' and look for it
under both stu3 and r4 component directories.
"""
# Find the 'examples/' segment in the path
examples_idx = ref_path.find("examples/")
if examples_idx == -1:
return None

relative_from_examples = ref_path[examples_idx:]

# Try both stu3 and r4 directories
for subdir in ["stu3", "r4"]:
candidate = os.path.join(SPEC_COMPONENTS_DIR, subdir, relative_from_examples)
if os.path.isfile(candidate):
return candidate
Comment on lines +50 to +54

return None


def resolve_refs(obj):
"""
Recursively walk the parsed JSON object. When we find a dict that
is exactly {"$ref": "<path>"} where the path points to a .json
file, load that file and return its content instead.
"""
if isinstance(obj, dict):
if list(obj.keys()) == ["$ref"] and obj["$ref"].endswith(".json"):
ref_path = obj["$ref"]
abs_path = resolve_ref_path(ref_path)
if abs_path:
with open(abs_path, "r") as f:
return json.load(f)
else:
print(
f"Warning: Could not resolve $ref: {ref_path}",
file=sys.stderr,
)
return obj
else:
return {k: resolve_refs(v) for k, v in obj.items()}
elif isinstance(obj, list):
return [resolve_refs(item) for item in obj]
else:
return obj
Comment on lines +59 to +83


def main():
"""Main entrypoint"""
data = json.loads(sys.stdin.read())
resolved = resolve_refs(data)
sys.stdout.write(json.dumps(resolved, indent=2))
sys.stdout.close()


if __name__ == "__main__":
main()