Skip to content

Commit 02ef632

Browse files
Merge pull request #2826 from NHSDigital/feature/RAA-8779
[RAA-8779]-[TH]-[Added in script to resolve all reference after Redoc…
2 parents 2256c18 + 5355ae5 commit 02ef632

2 files changed

Lines changed: 96 additions & 1 deletion

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"description": "OAS (Swagger v3) API Definition for Template API",
55
"scripts": {
66
"lint-oas": "redocly lint specification/e-referrals-service-api.yaml",
7-
"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",
7+
"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",
88
"serve": "redocly preview-docs -p 5000 build/e-referrals-service-api.json",
99
"check-licenses": "node_modules/.bin/license-checker --failOn GPL --failOn LGPL"
1010
},

scripts/resolve_example_refs.py

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
#!/usr/bin/env python3
2+
"""
3+
resolve_example_refs.py
4+
5+
Reads a bundled OpenAPI spec on stdin and resolves any remaining $ref
6+
objects found inside example 'value' fields. This is needed because
7+
Redocly CLI v2 no longer dereferences $ref inside example values
8+
during bundling.
9+
10+
The script walks the entire spec looking for objects of the form:
11+
{"$ref": "some/path/to/file.json"}
12+
13+
that appear inside example value contexts, loads the referenced JSON
14+
file from disk relative to the specification directory, and replaces
15+
the $ref object with the actual file content.
16+
17+
Prints the resolved spec on stdout.
18+
"""
19+
import json
20+
import os
21+
import sys
22+
23+
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
24+
REPO_ROOT = os.path.abspath(os.path.join(SCRIPT_DIR, ".."))
25+
SPEC_COMPONENTS_DIR = os.path.join(REPO_ROOT, "specification", "components")
26+
27+
28+
def resolve_ref_path(ref_path):
29+
"""
30+
Resolve a relative $ref path from the bundled output to an
31+
absolute file path on disk.
32+
33+
In the source YAML, $refs are relative to the file they appear in:
34+
- From schemas/endpoints/: ../../examples/... (up 2 = stu3/)
35+
- From schemas/responses/: ../../../examples/... (up 3 = stu3/)
36+
37+
All paths contain 'examples/' which maps to
38+
specification/components/{stu3,r4}/examples/.
39+
40+
We extract the part starting from 'examples/' and look for it
41+
under both stu3 and r4 component directories.
42+
"""
43+
# Find the 'examples/' segment in the path
44+
examples_idx = ref_path.find("examples/")
45+
if examples_idx == -1:
46+
return None
47+
48+
relative_from_examples = ref_path[examples_idx:]
49+
50+
# Try both stu3 and r4 directories
51+
for subdir in ["stu3", "r4"]:
52+
candidate = os.path.join(SPEC_COMPONENTS_DIR, subdir, relative_from_examples)
53+
if os.path.isfile(candidate):
54+
return candidate
55+
56+
return None
57+
58+
59+
def resolve_refs(obj):
60+
"""
61+
Recursively walk the parsed JSON object. When we find a dict that
62+
is exactly {"$ref": "<path>"} where the path points to a .json
63+
file, load that file and return its content instead.
64+
"""
65+
if isinstance(obj, dict):
66+
if list(obj.keys()) == ["$ref"] and obj["$ref"].endswith(".json"):
67+
ref_path = obj["$ref"]
68+
abs_path = resolve_ref_path(ref_path)
69+
if abs_path:
70+
with open(abs_path, "r") as f:
71+
return json.load(f)
72+
else:
73+
print(
74+
f"Warning: Could not resolve $ref: {ref_path}",
75+
file=sys.stderr,
76+
)
77+
return obj
78+
else:
79+
return {k: resolve_refs(v) for k, v in obj.items()}
80+
elif isinstance(obj, list):
81+
return [resolve_refs(item) for item in obj]
82+
else:
83+
return obj
84+
85+
86+
def main():
87+
"""Main entrypoint"""
88+
data = json.loads(sys.stdin.read())
89+
resolved = resolve_refs(data)
90+
sys.stdout.write(json.dumps(resolved, indent=2))
91+
sys.stdout.close()
92+
93+
94+
if __name__ == "__main__":
95+
main()

0 commit comments

Comments
 (0)