<#3552 bug: `resolve_schema_references` does not r...
# meltano-repo-activity
g
#3552 bug: `resolve_schema_references` does not recurse into `additionalProperties`, leaving `$ref`s unresolved Issue created by edgarrmondragon Summary
resolve_schema_references
(in
singer_sdk/singerlib/schema.py
) recursively resolves
$ref
values inside
properties
,
patternProperties
,
items
,
anyOf
,
allOf
, and
oneOf
— but not
additionalProperties
. Any
$ref
nested under
additionalProperties
is left unresolved in the returned schema. When
OpenAPISchema.fetch_schema
is used and a component schema contains
additionalProperties: { "$ref": "..." }
, the resulting schema is not self-contained. Downstream validators (e.g.
jsonschema
) then fail with
PointerToNowhere
because the
$ref
points into
#/components/schemas/…
which no longer exists in the extracted standalone schema. Steps to reproduce Given an OpenAPI spec with a component like: "WorkspaceResource": { "type": "object", "properties": { "_links": { "type": "object", "additionalProperties": { "$ref": "#/components/schemas/Link" } } } } Fetching the schema via
OpenAPISchema.fetch_schema("WorkspaceResource")
returns: { "type": "object", "properties": { "_links": { "type": "object", "additionalProperties": { "$ref": "#/components/schemas/Link" # ← still unresolved } } } } Validating a real record against this schema with
jsonschema
then raises:
Copy code
referencing.exceptions.PointerToNowhere: '/components/schemas/Link' does not exist within {...}
This manifests in practice when running the SDK's own built-in tap tests (
get_tap_test_class
) against a tap that uses
OpenAPISchema
—
test_tap_stream_record_matches_stream_schema
fails for every stream whose resource schema has
additionalProperties: { "$ref": "..." }
. Root cause
_resolve_schema_references
in
singer_sdk/singerlib/schema.py
defines a
_SchemaKey
class and recurses only into the keywords listed there: class _SchemaKey: ref = "$ref" items = "items" properties = "properties" pattern_properties = "patternProperties" any_of = "anyOf" all_of = "allOf" one_of = "oneOf"
additionalProperties
is absent. The JSON Schema specification also allows
$ref
to appear in
not
, `if`/`then`/`else`,
prefixItems
,
contains
,
propertyNames
, and `unevaluatedProperties`/`unevaluatedItems`, which are similarly unhandled — but
additionalProperties
is by far the most common in real-world OpenAPI specs. Expected behaviour
resolve_schema_references
should recurse into
additionalProperties
(and ideally the other missing applicator keywords) so the returned schema is fully self-contained. Proposed fix Add
additionalProperties
to `_resolve_schema_references`: if "additionalProperties" in schema and isinstance(schema["additionalProperties"], dict): schema["additionalProperties"] = _resolve_schema_references( schema["additionalProperties"], resolver, visited_refs, ) A more complete fix would also cover
not
,
if
,
then
,
else
,
prefixItems
,
contains
,
propertyNames
,
unevaluatedProperties
, and
unevaluatedItems
. Environment • Singer SDK version:
0.53.5
• Python version:
3.14.3
• Reproduced with a tap using
OpenAPISchema
+
StreamSchema
against a Spring HATEOAS API whose resource schemas use
additionalProperties: { "$ref": "..." }
for HAL link maps. meltano/sdk