diff --git a/Sources/OpenAPIKit/Schema Object/DereferencedJSONSchema.swift b/Sources/OpenAPIKit/Schema Object/DereferencedJSONSchema.swift index d98a36ca2..02f8986b8 100644 --- a/Sources/OpenAPIKit/Schema Object/DereferencedJSONSchema.swift +++ b/Sources/OpenAPIKit/Schema Object/DereferencedJSONSchema.swift @@ -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 diff --git a/Tests/OpenAPIKitTests/Schema Object/JSONSchemaDynamicReferenceTests.swift b/Tests/OpenAPIKitTests/Schema Object/JSONSchemaDynamicReferenceTests.swift index 2454ce807..36f1cd131 100644 --- a/Tests/OpenAPIKitTests/Schema Object/JSONSchemaDynamicReferenceTests.swift +++ b/Tests/OpenAPIKitTests/Schema Object/JSONSchemaDynamicReferenceTests.swift @@ -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 diff --git a/documentation/migration_guides/v7_migration_guide.md b/documentation/migration_guides/v7_migration_guide.md index a10bebb54..9acaee25d 100644 --- a/documentation/migration_guides/v7_migration_guide.md +++ b/documentation/migration_guides/v7_migration_guide.md @@ -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