diff --git a/Sources/OpenAPIKit/Validator/Validation+Builtins.swift b/Sources/OpenAPIKit/Validator/Validation+Builtins.swift index 8e22affe5..2da6e10c6 100644 --- a/Sources/OpenAPIKit/Validator/Validation+Builtins.swift +++ b/Sources/OpenAPIKit/Validator/Validation+Builtins.swift @@ -408,6 +408,28 @@ extension Validation { ) } + /// Validate that all non-external Callbacks references are found in the document's + /// components dictionary. + /// + /// - Important: This is included in validation by default. + /// + public static var callbacksReferencesAreValid: Validation> { + .init( + description: "Callbacks reference can be found in components/callbacks", + check: { context in + guard case let .internal(internalReference) = context.subject.jsonReference, + case .component = internalReference else { + // don't make assertions about external references + // TODO: could make a stronger assertion including + // internal references outside of components given + // some way to resolve those references. + return true + } + return context.document.components.contains(internalReference) + } + ) + } + /// Validate that all non-external PathItem references are found in the document's /// components dictionary. /// diff --git a/Sources/OpenAPIKit/Validator/Validator.swift b/Sources/OpenAPIKit/Validator/Validator.swift index 251183d16..21373a22d 100644 --- a/Sources/OpenAPIKit/Validator/Validator.swift +++ b/Sources/OpenAPIKit/Validator/Validator.swift @@ -190,6 +190,7 @@ public final class Validator { .init(.requestReferencesAreValid), .init(.headerReferencesAreValid), .init(.linkReferencesAreValid), + .init(.callbacksReferencesAreValid), .init(.pathItemReferencesAreValid), .init(.serverVariableEnumIsValid), .init(.serverVariableDefaultExistsInEnum) diff --git a/Sources/OpenAPIKit30/Validator/Validation+Builtins.swift b/Sources/OpenAPIKit30/Validator/Validation+Builtins.swift index d880181df..0a13fc789 100644 --- a/Sources/OpenAPIKit30/Validator/Validation+Builtins.swift +++ b/Sources/OpenAPIKit30/Validator/Validation+Builtins.swift @@ -397,6 +397,28 @@ extension Validation { ) } + /// Validate that all non-external Callbacks references are found in the document's + /// components dictionary. + /// + /// - Important: This is included in validation by default. + /// + public static var callbacksReferencesAreValid: Validation> { + .init( + description: "Callbacks reference can be found in components/callbacks", + check: { context in + guard case let .internal(internalReference) = context.subject, + case .component = internalReference else { + // don't make assertions about external references + // TODO: could make a stronger assertion including + // internal references outside of components given + // some way to resolve those references. + return true + } + return context.document.components.contains(internalReference) + } + ) + } + /// Validate the OpenAPI Document's `Links` with operationIds refer to /// Operations that exist in the document. /// diff --git a/Sources/OpenAPIKit30/Validator/Validator.swift b/Sources/OpenAPIKit30/Validator/Validator.swift index c52a86918..b636529fa 100644 --- a/Sources/OpenAPIKit30/Validator/Validator.swift +++ b/Sources/OpenAPIKit30/Validator/Validator.swift @@ -186,7 +186,8 @@ public final class Validator { .init(.exampleReferencesAreValid), .init(.requestReferencesAreValid), .init(.headerReferencesAreValid), - .init(.linkReferencesAreValid) + .init(.linkReferencesAreValid), + .init(.callbacksReferencesAreValid) ]) } diff --git a/Tests/OpenAPIKit30Tests/Validator/BuiltinValidationTests.swift b/Tests/OpenAPIKit30Tests/Validator/BuiltinValidationTests.swift index 4a950c25e..5cdc98d79 100644 --- a/Tests/OpenAPIKit30Tests/Validator/BuiltinValidationTests.swift +++ b/Tests/OpenAPIKit30Tests/Validator/BuiltinValidationTests.swift @@ -638,6 +638,9 @@ final class BuiltinValidationTests: XCTestCase { ], links: ["linky": .reference(.component(named: "link1"))] ) + ], + callbacks: [ + "callbacks1": .reference(.component(named: "callbacks1")) ] ) ) @@ -654,7 +657,7 @@ final class BuiltinValidationTests: XCTestCase { // NOTE this is part of default validation XCTAssertThrowsError(try document.validate()) { error in let error = error as? ValidationErrorCollection - XCTAssertEqual(error?.values.count, 7) + XCTAssertEqual(error?.values.count, 8) XCTAssertEqual(error?.values[0].reason, "Failed to satisfy: Parameter reference can be found in components/parameters") XCTAssertEqual(error?.values[0].codingPathString, ".paths['/hello'].get.parameters[0]") XCTAssertEqual(error?.values[1].reason, "Failed to satisfy: Request reference can be found in components/requestBodies") @@ -669,6 +672,8 @@ final class BuiltinValidationTests: XCTestCase { XCTAssertEqual(error?.values[5].codingPathString, ".paths['/hello'].get.responses.404.content['application/xml'].schema") XCTAssertEqual(error?.values[6].reason, "Failed to satisfy: Link reference can be found in components/links") XCTAssertEqual(error?.values[6].codingPathString, ".paths['/hello'].get.responses.404.links.linky") + XCTAssertEqual(error?.values[7].reason, "Failed to satisfy: Callbacks reference can be found in components/callbacks") + XCTAssertEqual(error?.values[7].codingPathString, ".paths['/hello'].get.callbacks.callbacks1") } } @@ -711,6 +716,9 @@ final class BuiltinValidationTests: XCTestCase { "linky2": .reference(.external(URL(string: "https://linky.com")!)) ] ) + ], + callbacks: [ + "callbacks1": .reference(.component(named: "callbacks1")) ] ) ) @@ -742,6 +750,9 @@ final class BuiltinValidationTests: XCTestCase { ], links: [ "link1": .init(operationId: "op 1") + ], + callbacks: [ + "callbacks1": .init() ] ) ) diff --git a/Tests/OpenAPIKitCompatTests/DocumentConversionTests.swift b/Tests/OpenAPIKitCompatTests/DocumentConversionTests.swift index 99617df42..ee1126b12 100644 --- a/Tests/OpenAPIKitCompatTests/DocumentConversionTests.swift +++ b/Tests/OpenAPIKitCompatTests/DocumentConversionTests.swift @@ -202,6 +202,9 @@ final class DocumentConversionTests: XCTestCase { components: .init( parameters: [ "test": .init(name: "referencedParam", context: .query, schema: .string) + ], + callbacks: [ + "other_callback": callbacks ] ) ) diff --git a/Tests/OpenAPIKitTests/Validator/BuiltinValidationTests.swift b/Tests/OpenAPIKitTests/Validator/BuiltinValidationTests.swift index f74c77a34..641d0f9b8 100644 --- a/Tests/OpenAPIKitTests/Validator/BuiltinValidationTests.swift +++ b/Tests/OpenAPIKitTests/Validator/BuiltinValidationTests.swift @@ -741,6 +741,9 @@ final class BuiltinValidationTests: XCTestCase { ], links: ["linky": .reference(.component(named: "link1"))] ) + ], + callbacks: [ + "callbacks1": .reference(.component(named: "callbacks1")) ] ) ) @@ -758,7 +761,7 @@ final class BuiltinValidationTests: XCTestCase { // NOTE this is part of default validation XCTAssertThrowsError(try document.validate()) { error in let error = error as? ValidationErrorCollection - XCTAssertEqual(error?.values.count, 8) + XCTAssertEqual(error?.values.count, 9) XCTAssertEqual(error?.values[0].reason, "Failed to satisfy: Parameter reference can be found in components/parameters") XCTAssertEqual(error?.values[0].codingPathString, ".paths['/hello'].get.parameters[0]") XCTAssertEqual(error?.values[1].reason, "Failed to satisfy: Request reference can be found in components/requestBodies") @@ -773,8 +776,10 @@ final class BuiltinValidationTests: XCTestCase { XCTAssertEqual(error?.values[5].codingPathString, ".paths['/hello'].get.responses.404.content['application/xml'].schema") XCTAssertEqual(error?.values[6].reason, "Failed to satisfy: Link reference can be found in components/links") XCTAssertEqual(error?.values[6].codingPathString, ".paths['/hello'].get.responses.404.links.linky") - XCTAssertEqual(error?.values[7].reason, "Failed to satisfy: PathItem reference can be found in components/pathItems") - XCTAssertEqual(error?.values[7].codingPathString, ".paths['/world']") + XCTAssertEqual(error?.values[7].reason, "Failed to satisfy: Callbacks reference can be found in components/callbacks") + XCTAssertEqual(error?.values[7].codingPathString, ".paths['/hello'].get.callbacks.callbacks1") + XCTAssertEqual(error?.values[8].reason, "Failed to satisfy: PathItem reference can be found in components/pathItems") + XCTAssertEqual(error?.values[8].codingPathString, ".paths['/world']") } } @@ -817,6 +822,9 @@ final class BuiltinValidationTests: XCTestCase { "linky2": .reference(.external(URL(string: "https://linky.com")!)) ] ) + ], + callbacks: [ + "callbacks1": .reference(.component(named: "callbacks1")) ] ) ) @@ -851,6 +859,9 @@ final class BuiltinValidationTests: XCTestCase { links: [ "link1": .init(operationId: "op 1") ], + callbacks: [ + "callbacks1": .init() + ], pathItems: [ "path1": .init() ]