Skip to content

Commit 54d4ae9

Browse files
committed
Major reworking of oasReferences
Use 0/$ref everywhere it can happen, as the substitution by Reference Objects is type-specific rather than context-specific. This means that the reftype always applies to the object being processed. Fix paths and pointers in an attempt to better encapsulate jschon's JSON Pointer classes. Still not entirely clear if the encapsualtion is practical / desirable, but it feels more consistent where it works. Update jschon to include a pre-0.10.3 patch (which has been accepted and 0.10.3 will be released soon) fixing improper partial wrapping of annotation values in jschon.JSON instances. The jschon git branch including the above fix also includes a PR submitted upstream to enable an OAS-specific JSON class that can mix JSONSchema and non-JSONSchema in the same document. See pyproject.toml for references to jschon issues and PRs to track this idea. Also added intermediate grouping nodes for arrays such as Tags, Servers, etc. They make the visualizations more clear.
1 parent 7dfd163 commit 54d4ae9

File tree

4 files changed

+414
-336
lines changed

4 files changed

+414
-336
lines changed

oascomply/apidescription.py

+28-27
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ class ApiDescription:
6969

7070
def __init__(
7171
self,
72-
data: Any,
72+
document: Any,
7373
uri: str,
7474
*,
7575
path: Optional[Path] = None,
@@ -87,12 +87,12 @@ def __init__(
8787

8888
self._test_mode = test_mode
8989

90-
if 'openapi' not in data:
90+
if 'openapi' not in document:
9191
raise ValueError(
9292
"Initial API description must include `openapi` field!"
9393
f"{path} <{uri}>"
9494
)
95-
self._version = data['openapi']
95+
self._version = document['openapi']
9696
if not self._version.startswith('3.0.'):
9797
if self._version.startswith('3.1.'):
9898
raise NotImplementedError("OAS v3.1 support stil in progress")
@@ -120,15 +120,15 @@ def __init__(
120120
self._validated = set()
121121

122122
self.add_resource(
123-
data=data,
123+
document=document,
124124
uri=self._primary_uri,
125125
path=path,
126126
sourcemap=sourcemap,
127127
)
128128

129129
def add_resource(
130130
self,
131-
data: Any,
131+
document: Any,
132132
uri: Union[str, rid.Iri],
133133
*,
134134
path: Optional[Path] = None,
@@ -146,33 +146,33 @@ def add_resource(
146146
if not isinstance(uri, rid.Iri):
147147
# TODO: URI vs IRI usage
148148
uri = rid.Iri(uri)
149+
assert uri.fragment is None, "Only complete documenets can be added."
149150

150151
# The jschon.JSON class keeps track of JSON Pointer values for
151152
# every data entry, as well as providing parent links and type
152153
# information.
153-
self._contents[uri] = jschon.JSON(data)
154+
self._contents[uri] = jschon.JSON(document)
154155
if sourcemap:
155156
self._sources[uri] = sourcemap
156157
self._g.add_resource(url, uri, filename=path.name)
157158

158-
def get(self, uri: Union[str, rid.Iri]) -> Optional[Any]:
159-
if isinstance(uri, str):
159+
def get_resource(self, uri: Union[str, rid.Iri]) -> Optional[Any]:
160+
if not isinstance(uri, rid.IriWithJsonPtr):
160161
# TODO: IRI vs URI
161-
uri = rid.Iri(uri)
162+
# TODO: Non-JSON Pointer fragments in 3.1
163+
uri = rid.IriWithJsonPtr(uri)
164+
document_uri = uri.to_absolute()
165+
data_ptr = uri.fragment
162166
try:
163-
return self._contents[uri], self._sources.get(uri)
164-
except KeyError:
165-
try:
166-
data = self._contents[uri.to_absolute()]
167-
return (
168-
rid.JsonPtr.parse_uri_fragment(
169-
fragment
170-
).evaluate(data),
171-
self._sources.get(uri),
172-
)
173-
174-
except (KeyError, jschon.JSONPointerError):
175-
return None, None
167+
document = self._contents[document_uri]
168+
return (
169+
document,
170+
document if data_ptr is None else data_ptr.evaluate(document),
171+
self._sources.get(uri),
172+
)
173+
except (KeyError, jschon.JSONPointerError):
174+
logger.warning(f"Could not find resource {uri}")
175+
return None, None, None
176176

177177
def validate(self, resource_uri=None, oastype='OpenAPI'):
178178
sp = SchemaParser.get_parser({}, annotations=ANNOT_ORDER)
@@ -181,22 +181,23 @@ def validate(self, resource_uri=None, oastype='OpenAPI'):
181181
resource_uri = self._primary_uri
182182
elif isinstance(resource_uri, str):
183183
# TODO: IRI vs URI
184-
resource_uri = rid.Iri(resource_uri)
184+
# TODO: Non-JSON Pointer fragments in 3.1
185+
resource_uri = rid.IriWithJsonPtr(resource_uri)
185186

186-
data, sourcemap = self.get(resource_uri)
187-
assert data is not None
187+
document, data, sourcemap = self.get_resource(resource_uri)
188+
assert None not in (document, data)
188189

189190
output = sp.parse(data, oastype)
190191
to_validate = {}
191192
by_method = defaultdict(list)
192193
for unit in output['annotations']:
193-
ann=Annotation(unit, instance_base=resource_uri)
194+
ann=Annotation(unit, instance_base=resource_uri.to_absolute())
194195
method = f'add_{ann.keyword.lower()}'
195196

196197
# Using a try/except here can result in confusion if something
197198
# else produces an AttributeError, so use hasattr()
198199
if hasattr(self._g, method):
199-
by_method[method].append((ann, data, sourcemap))
200+
by_method[method].append((ann, document, data, sourcemap))
200201
else:
201202
raise ValueError(f"Unexpected annotation {ann.keyword!r}")
202203
self._validated.add(resource_uri)

0 commit comments

Comments
 (0)