Skip to content

Commit 2f600be

Browse files
committed
feat: externally dereference $dynamicRef
Delegate the .dynamicReference case of externallyDereferenced to the wrapped JSONReference's external deref -- the same path $ref uses. External $dynamicRef targets are fetched via the loader and rewritten to internal component references; internal dynamic refs are unchanged. The result stays a .dynamicReference (serializes as $dynamicRef, preserving the dynamic semantic). Part of #359.
1 parent ed64a6d commit 2f600be

3 files changed

Lines changed: 51 additions & 14 deletions

File tree

Sources/OpenAPIKit/Schema Object/DereferencedJSONSchema.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -675,12 +675,12 @@ extension JSONSchema: ExternallyDereferenceable {
675675
newSchema = .init(
676676
schema: .reference(newReference, core)
677677
)
678-
case .dynamicReference:
679-
// TODO: external dereferencing of `$dynamicRef` is not implemented;
680-
// deferred alongside local dynamic-scope resolution (see #359).
681-
newComponents = .noComponents
682-
newSchema = self
683-
newMessages = []
678+
case .dynamicReference(let dynamicRef, let core):
679+
// Delegate to the wrapped JSONReference's external deref (same path as $ref).
680+
let (newReference, components, messages) = try await dynamicRef.jsonReference.externallyDereferenced(with: loader)
681+
newComponents = components
682+
newMessages = messages
683+
newSchema = .init(schema: .dynamicReference(JSONDynamicReference(newReference), core))
684684
case .fragment(_):
685685
newComponents = .noComponents
686686
newSchema = self

Tests/OpenAPIKitTests/Schema Object/JSONSchemaDynamicReferenceTests.swift

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,3 +208,36 @@ final class JSONSchemaDynamicReferenceTests: XCTestCase {
208208
}
209209
}
210210
}
211+
212+
#if ExternalLoading
213+
extension JSONSchemaDynamicReferenceTests {
214+
func test_externalDeref_dynamicReference_external() async throws {
215+
// An external `$dynamicRef` is dereferenced through its underlying
216+
// `JSONReference` -- same path as `$ref`: fetch + convert to an
217+
// internal component reference.
218+
let schema = JSONSchema.dynamicReference(
219+
JSONDynamicReference(.external(.init(string: "./schema.json")!))
220+
)
221+
222+
let (newSchema, components, messages) = try await schema.externallyDereferenced(with: JSONReferenceTests.SchemaLoader.self)
223+
224+
XCTAssertTrue(newSchema.isDynamicReference)
225+
XCTAssertEqual(newSchema.dynamicReference?.name, "__schema_json")
226+
XCTAssertEqual(components, .init(schemas: ["__schema_json": .string]))
227+
XCTAssertEqual(messages, ["./schema.json"])
228+
}
229+
230+
func test_externalDeref_dynamicReference_internal_noop() async throws {
231+
// An internal `$dynamicRef` (anchor) is not external; external
232+
// dereferencing leaves it unchanged.
233+
let schema = JSONSchema.dynamicReference(.anchor("node"))
234+
235+
let (newSchema, components, messages) = try await schema.externallyDereferenced(with: JSONReferenceTests.SchemaLoader.self)
236+
237+
XCTAssertTrue(newSchema.isDynamicReference)
238+
XCTAssertEqual(newSchema.dynamicReference?.absoluteString, "#node")
239+
XCTAssertTrue(components.schemas.isEmpty)
240+
XCTAssertEqual(messages, [])
241+
}
242+
}
243+
#endif

documentation/migration_guides/v7_migration_guide.md

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,18 @@ encodes/decodes the `$dynamicRef` keyword. Schemas whose only attribute is
1717
`$dynamicRef` now decode as `.dynamicReference` instead of decoding as an empty
1818
`.fragment` with an "unsupported attributes" warning.
1919

20-
### Local dereferencing fails on `$dynamicRef`
21-
22-
A `DereferencedJSONSchema` must not contain references. Until dynamic-scope
23-
resolution is added (tracked in #359), `locallyDereferenced()` and
24-
`JSONSchema.dereferenced(in:)` **throw** when they encounter a `$dynamicRef`
25-
they cannot inline, mirroring how unresolvable static `$ref` values fail. The
26-
raw `JSONSchema` AST still carries `.dynamicReference` for tools that read
27-
schemas without dereferencing.
20+
### Dereferencing `$dynamicRef`
21+
22+
`locallyDereferenced()` and `JSONSchema.dereferenced(in:)` **throw** on a
23+
`$dynamicRef` — a `DereferencedJSONSchema` must not contain references, and
24+
dynamic-scope resolution is tracked in #359. The raw `JSONSchema` AST still
25+
carries `.dynamicReference` for tools that read schemas without dereferencing.
26+
27+
External dereferencing (`externallyDereferenced(with:)`, under the
28+
`ExternalLoading` trait) resolves an external `$dynamicRef` the same way it
29+
resolves an external `$ref`: fetch via the `ExternalLoader` and rewrite to an
30+
internal component. The result stays `.dynamicReference` (still serializes as
31+
`$dynamicRef`); cross-document dynamic-scope resolution is out of scope.
2832

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

0 commit comments

Comments
 (0)