Skip to content

Commit 4c23327

Browse files
committed
feat(transformer): enhance count_references with refs_only support
Add `refs_only` parameter to `count_references` and related utilities to allow selective counting of `$ref` fields exclusively. Update `iter_url_fields` and introduce `iter_ref_fields` to iterate over relevant fields based on `refs_only`. Extend test coverage to validate new functionality across various scenarios.
1 parent ab6b356 commit 4c23327

File tree

8 files changed

+1321
-21
lines changed

8 files changed

+1321
-21
lines changed

package-lock.json

Lines changed: 0 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/jentic-openapi-transformer/src/jentic/apitools/openapi/transformer/core/references.py

Lines changed: 37 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,12 @@
1818
"RewriteOptions",
1919
"rewrite_urls_inplace",
2020
"iter_url_fields",
21+
"iter_ref_fields",
2122
"count_references",
2223
]
2324

2425

25-
def count_references(root: Any) -> Tuple[int, int, int, int]:
26+
def count_references(root: Any, refs_only: bool = False) -> Tuple[int, int, int, int]:
2627
"""
2728
Counts the number of references in a given data structure based on their type.
2829
@@ -34,6 +35,8 @@ def count_references(root: Any) -> Tuple[int, int, int, int]:
3435
Args:
3536
root: Any
3637
The root data structure containing references to be analyzed.
38+
refs_only: bool, optional (default=False)
39+
If True, only count references in $ref fields.
3740
3841
Returns:
3942
Tuple[int, int, int, int]: A tuple containing the following counts:
@@ -47,7 +50,8 @@ def count_references(root: Any) -> Tuple[int, int, int, int]:
4750
total_refs_count = 0
4851
local_refs_count = 0
4952

50-
for path, _parent, key, value in iter_url_fields(root):
53+
iterator = iter_ref_fields if refs_only else iter_url_fields
54+
for path, _parent, key, value in iterator(root):
5155
assert isinstance(key, str)
5256
total_refs_count += 1
5357
if key == "$ref" and is_fragment_only_uri(value):
@@ -60,14 +64,15 @@ def count_references(root: Any) -> Tuple[int, int, int, int]:
6064
return total_refs_count, local_refs_count, relative_refs_count, absolute_http_refs_count
6165

6266

63-
def find_relative_urls(root: Any) -> List[Tuple[JSONPath, str, str]]:
67+
def find_relative_urls(root: Any, refs_only: bool = False) -> List[Tuple[JSONPath, str, str]]:
6468
"""
65-
Return a list of (json_path, key, value) for any URL-like field
66-
(including $ref) whose value is relative (e.g., 'schemas/a.yaml', '../b', '/c')
69+
Return a list of (json_path, key, value) for any URL-like field (refs_only=False) or $ref field (refs_only=True)
70+
whose value is relative (e.g., 'schemas/a.yaml', '../b', '/c')
6771
and not a pure fragment '#/...' .
6872
"""
6973
out: List[Tuple[JSONPath, str, str]] = []
70-
for path, _parent, key, value in iter_url_fields(root):
74+
iterator = iter_ref_fields if refs_only else iter_url_fields
75+
for path, _parent, key, value in iterator(root):
7176
assert isinstance(key, str)
7277
if key == "$ref" and is_fragment_only_uri(value):
7378
continue
@@ -77,13 +82,14 @@ def find_relative_urls(root: Any) -> List[Tuple[JSONPath, str, str]]:
7782
return out
7883

7984

80-
def find_absolute_http_urls(root: Any) -> List[Tuple[JSONPath, str, str]]:
85+
def find_absolute_http_urls(root: Any, refs_only: bool = False) -> List[Tuple[JSONPath, str, str]]:
8186
"""
82-
Return a list of (json_path, key, value) for any URL-like field
83-
(including $ref) whose value is absolute and http
87+
Return a list of (json_path, key, value) for any URL-like field (refs_only=False) or $ref field (refs_only=True)
88+
whose value is absolute and http
8489
"""
8590
out: List[Tuple[JSONPath, str, str]] = []
86-
for path, _parent, key, value in iter_url_fields(root):
91+
iterator = iter_ref_fields if refs_only else iter_url_fields
92+
for path, _parent, key, value in iterator(root):
8793
assert isinstance(key, str)
8894
if key == "$ref" and is_fragment_only_uri(value):
8995
continue
@@ -100,24 +106,27 @@ class RewriteOptions:
100106
- original_base_url: If provided together with include_absolute_urls=True, we'll
101107
retarget absolute URLs that begin with original_base_url to base_url.
102108
- include_absolute_urls: If True (and original_base_url given), retarget absolute URLs too.
109+
- refs_only: If True, only rewrite $ref fields.
103110
"""
104111

105-
__slots__ = ("base_url", "original_base_url", "include_absolute_urls")
112+
__slots__ = ("base_url", "original_base_url", "include_absolute_urls", "refs_only")
106113

107114
def __init__(
108115
self,
109116
base_url: str,
110117
original_base_url: str | None = None,
111118
include_absolute_urls: bool = False,
119+
refs_only: bool = False,
112120
) -> None:
113121
self.base_url = base_url
114122
self.original_base_url = original_base_url
115123
self.include_absolute_urls = include_absolute_urls
124+
self.refs_only = refs_only
116125

117126

118127
def rewrite_urls_inplace(root: Any, opts: RewriteOptions) -> int:
119128
"""
120-
Rewrite $ref and other URL-bearing fields in-place.
129+
Rewrite $ref and other URL-bearing fields (refs_only=False) in-place.
121130
Rules:
122131
- Relative values (except fragment-only) are absolutized against opts.base_url.
123132
- If opts.include_absolute_urls and opts.original_base_url are set,
@@ -126,7 +135,8 @@ def rewrite_urls_inplace(root: Any, opts: RewriteOptions) -> int:
126135
Returns the number of fields changed.
127136
"""
128137
changed = 0
129-
for _path, parent, key, value in iter_url_fields(root):
138+
iterator = iter_ref_fields if opts.refs_only else iter_url_fields
139+
for _path, parent, key, value in iterator(root):
130140
# value is str by iter_url_fields contract
131141
if key == "$ref" and is_fragment_only_uri(value):
132142
continue # keep pure fragments
@@ -159,6 +169,20 @@ def iter_url_fields(root: Any) -> Iterator[Tuple[JSONPath, MutableMapping[str, A
159169
yield node.path, node.parent, node.segment, node.value
160170

161171

172+
def iter_ref_fields(root: Any) -> Iterator[Tuple[JSONPath, MutableMapping[str, Any], str, str]]:
173+
"""
174+
Iterate over all $ref fields
175+
"""
176+
for node in traverse(root):
177+
if (
178+
isinstance(node.parent, MutableMapping)
179+
and isinstance(node.segment, str)
180+
and isinstance(node.value, str)
181+
and node.segment == "$ref"
182+
):
183+
yield node.path, node.parent, node.segment, node.value
184+
185+
162186
# Keys that *by spec* or convention carry URL/URI references (OpenAPI 3.0/3.1).
163187
_URL_KEYS_EXPLICIT: frozenset[str] = frozenset(
164188
{

0 commit comments

Comments
 (0)