@@ -85,7 +85,7 @@ def _discoverable_subresources(
8585
8686
8787def _scoped_resource (
88- contents : Any , * , id_already_applied : bool = True
88+ contents : Any , * , id_already_applied : bool
8989) -> Resource [Any ]:
9090 # Crawling a resource registered at its resolved URI must not apply
9191 # its relative id again. Ignore malformed metadata during discovery.
@@ -114,19 +114,27 @@ def id_of(schema: Any) -> str | None:
114114 return specification .create_resource (contents )
115115
116116
117- def prepare_oas30_resolver (resolver : Any , scoped : Any , schema : Any ) -> Any :
117+ def prepare_oas30_resolver (
118+ resolver : Any ,
119+ scoped : Any ,
120+ schema : Any ,
121+ * ,
122+ id_already_applied : bool = False ,
123+ ) -> Any :
118124 uri = resolver ._base_uri
119125 resource = scoped ._registry .get (uri )
126+ # Replacing a crawled entry would apply its id a second time.
120127 if (
121128 resource is None
122129 or resource .contents is not schema
123130 or resource ._specification is not DRAFT4
131+ or uri not in scoped ._registry ._uncrawled
124132 ):
125133 return scoped
126- # A registry entry may be a retrieval alias for a different canonical id.
127- identifier = _discoverable_id ( schema ). rstrip ( "#" )
134+ # Existing resources may be registered at retrieval aliases, even if
135+ # the alias equals the raw relative id. Only owned scopes suppress it.
128136 registry = scoped ._registry .with_resource (
129- uri , _scoped_resource (schema , id_already_applied = uri == identifier )
137+ uri , _scoped_resource (schema , id_already_applied = id_already_applied )
130138 )
131139 return scoped ._evolve (base_uri = scoped ._base_uri , registry = registry )
132140
@@ -138,7 +146,9 @@ def _register_scope(resolver: Any, scoped: Any, contents: Any) -> Any:
138146
139147 # Register the reached scope so local lookups can use it without
140148 # discovering resources in ignored siblings.
141- registry = scoped ._registry .with_resource (uri , _scoped_resource (contents ))
149+ registry = scoped ._registry .with_resource (
150+ uri , _scoped_resource (contents , id_already_applied = True )
151+ )
142152 return scoped ._evolve (base_uri = uri , registry = registry )
143153
144154
@@ -210,6 +220,30 @@ def declares_other_schema_dialect(schema: Any) -> bool:
210220 )
211221
212222
223+ def _oas30_schema_children (
224+ schema : Mapping [str , Any ],
225+ ) -> Iterator [Mapping [str , Any ]]:
226+ for child in DRAFT4 .subresources_of (schema ):
227+ if isinstance (child , Mapping ) and not declares_other_schema_dialect (
228+ child
229+ ):
230+ yield child
231+
232+
233+ def _mark_checked_schemas (schema : Any , checked : set [int ]) -> None :
234+ # The metaschema checks ordinary schema children recursively, but stops
235+ # at Reference Objects. Other dialects are outside the target check.
236+ pending = [schema ]
237+ while pending :
238+ current = pending .pop ()
239+ identity = id (current )
240+ if identity in checked :
241+ continue
242+ checked .add (identity )
243+ if isinstance (current , Mapping ) and "$ref" not in current :
244+ pending .extend (_oas30_schema_children (current ))
245+
246+
213247def check_oas30_local_references (
214248 schema : Mapping [str , Any ],
215249 check_schema : Callable [[Any ], None ],
@@ -223,11 +257,14 @@ def check_oas30_local_references(
223257 resolver = local_registry .resolver_with_root (
224258 DRAFT4 .create_resource (schema )
225259 )
226- resolver = prepare_oas30_resolver (resolver , resolver , schema )
260+ resolver = prepare_oas30_resolver (
261+ resolver , resolver , schema , id_already_applied = True
262+ )
227263 root_uri = resolver ._base_uri
228264 pending = [(schema , resolver )]
229265 visited : set [tuple [int , str ]] = set ()
230- checked = {id (schema )}
266+ checked : set [int ] = set ()
267+ _mark_checked_schemas (schema , checked )
231268
232269 while pending :
233270 current , resolver = pending .pop ()
@@ -254,14 +291,12 @@ def check_oas30_local_references(
254291 continue
255292 if id (target .contents ) not in checked :
256293 check_schema (target .contents )
257- checked . add ( id ( target .contents ) )
294+ _mark_checked_schemas ( target .contents , checked )
258295 pending .append ((target .contents , target .resolver ))
259296 else :
260297 pending .extend (
261298 (child , _enter_subresource (resolver , child ))
262- for child in DRAFT4 .subresources_of (current )
263- if isinstance (child , Mapping )
264- and not declares_other_schema_dialect (child )
299+ for child in _oas30_schema_children (current )
265300 )
266301
267302
0 commit comments