Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions Sources/OpenAPIKit/Schema Object/DereferencedJSONSchema.swift
Original file line number Diff line number Diff line change
Expand Up @@ -675,12 +675,12 @@ extension JSONSchema: ExternallyDereferenceable {
newSchema = .init(
schema: .reference(newReference, core)
)
case .dynamicReference:
// TODO: external dereferencing of `$dynamicRef` is not implemented;
// deferred alongside local dynamic-scope resolution (see #359).
newComponents = .noComponents
newSchema = self
newMessages = []
case .dynamicReference(let dynamicRef, let core):
// Delegate to the wrapped JSONReference's external deref (same path as $ref).
let (newReference, components, messages) = try await dynamicRef.jsonReference.externallyDereferenced(with: loader)
newComponents = components
newMessages = messages
newSchema = .init(schema: .dynamicReference(JSONDynamicReference(newReference), core))
case .fragment(_):
newComponents = .noComponents
newSchema = self
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,3 +208,36 @@ final class JSONSchemaDynamicReferenceTests: XCTestCase {
}
}
}

#if ExternalLoading
extension JSONSchemaDynamicReferenceTests {
func test_externalDeref_dynamicReference_external() async throws {
// An external `$dynamicRef` is dereferenced through its underlying
// `JSONReference` -- same path as `$ref`: fetch + convert to an
// internal component reference.
let schema = JSONSchema.dynamicReference(
JSONDynamicReference(.external(.init(string: "./schema.json")!))
)

let (newSchema, components, messages) = try await schema.externallyDereferenced(with: JSONReferenceTests.SchemaLoader.self)

XCTAssertTrue(newSchema.isDynamicReference)
XCTAssertEqual(newSchema.dynamicReference?.name, "__schema_json")
XCTAssertEqual(components, .init(schemas: ["__schema_json": .string]))
XCTAssertEqual(messages, ["./schema.json"])
}

func test_externalDeref_dynamicReference_internal_noop() async throws {
// An internal `$dynamicRef` (anchor) is not external; external
// dereferencing leaves it unchanged.
let schema = JSONSchema.dynamicReference(.anchor("node"))

let (newSchema, components, messages) = try await schema.externallyDereferenced(with: JSONReferenceTests.SchemaLoader.self)

XCTAssertTrue(newSchema.isDynamicReference)
XCTAssertEqual(newSchema.dynamicReference?.absoluteString, "#node")
XCTAssertTrue(components.schemas.isEmpty)
XCTAssertEqual(messages, [])
}
}
#endif
20 changes: 12 additions & 8 deletions documentation/migration_guides/v7_migration_guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,18 @@ encodes/decodes the `$dynamicRef` keyword. Schemas whose only attribute is
`$dynamicRef` now decode as `.dynamicReference` instead of decoding as an empty
`.fragment` with an "unsupported attributes" warning.

### Local dereferencing fails on `$dynamicRef`

A `DereferencedJSONSchema` must not contain references. Until dynamic-scope
resolution is added (tracked in #359), `locallyDereferenced()` and
`JSONSchema.dereferenced(in:)` **throw** when they encounter a `$dynamicRef`
they cannot inline, mirroring how unresolvable static `$ref` values fail. The
raw `JSONSchema` AST still carries `.dynamicReference` for tools that read
schemas without dereferencing.
### Dereferencing `$dynamicRef`

`locallyDereferenced()` and `JSONSchema.dereferenced(in:)` **throw** on a
`$dynamicRef` — a `DereferencedJSONSchema` must not contain references, and
dynamic-scope resolution is tracked in #359. The raw `JSONSchema` AST still
carries `.dynamicReference` for tools that read schemas without dereferencing.

External dereferencing (`externallyDereferenced(with:)`, under the
`ExternalLoading` trait) resolves an external `$dynamicRef` the same way it
resolves an external `$ref`: fetch via the `ExternalLoader` and rewrite to an
internal component. The result stays `.dynamicReference` (still serializes as
`$dynamicRef`); cross-document dynamic-scope resolution is out of scope.

### `$ref` with a plain fragment now round-trips verbatim

Expand Down