Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added a Validation that checks all operationIds in Link objects are valid #404

Merged
Merged
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
24 changes: 24 additions & 0 deletions Sources/OpenAPIKit/Validator/Validation+Builtins.swift
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,30 @@ extension Validation {
public static var serverVarialbeDefaultExistsInEnum : Validation<OpenAPI.Server.Variable> {
return serverVariableDefaultExistsInEnum
}

/// Validate the OpenAPI Document's `Links` with operationIds refer to
/// Operations that exist in the document.
///
/// This validation ensures that Link Objects using operationIds have corresponding
/// Operations in the document that have those IDs.
///
/// - Important: This is not an included validation by default.
public static var linkOperationsExist: Validation<OpenAPI.Link> {
.init(
description: "Links with operationIds have corresponding Operations",
check: { context in
guard case let .b(operationId) = context.subject.operation else {
// don't make assertions about Links that don't have operationIds
return true
}

// Collect all operation IDs from the document
let operationIds = context.document.allOperationIds

return operationIds.contains(operationId)
}
)
}
}

/// Used by both the Path Item parameter check and the
Expand Down
24 changes: 24 additions & 0 deletions Sources/OpenAPIKit30/Validator/Validation+Builtins.swift
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,30 @@ extension Validation {
}
)
}

/// Validate the OpenAPI Document's `Links` with operationIds refer to
/// Operations that exist in the document.
///
/// This validation ensures that Link Objects using operationIds have corresponding
/// Operations in the document that have those IDs.
///
/// - Important: This is not an included validation by default.
public static var linkOperationsExist: Validation<OpenAPI.Link> {
.init(
description: "Links with operationIds have corresponding Operations",
check: { context in
guard case let .b(operationId) = context.subject.operation else {
// don't make assertions about Links that don't have operationIds
return true
}

// Use the allOperationIds helper to get all operation IDs from the document
let operationIds = context.document.allOperationIds

return operationIds.contains(operationId)
}
)
}
}

/// Used by both the Path Item parameter check and the
Expand Down
59 changes: 59 additions & 0 deletions Tests/OpenAPIKit30Tests/Validator/BuiltinValidationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -749,4 +749,63 @@ final class BuiltinValidationTests: XCTestCase {
// NOTE this is part of default validation
try document.validate()
}

func test_linkOperationsExist_validates() throws {
// Create a link with an operationId that exists in the document
let link = OpenAPI.Link(operationId: "testOperation")

// Create a document with an operation using that ID
let document = OpenAPI.Document(
info: .init(title: "test", version: "1.0"),
servers: [],
paths: [
"/hello": .init(
get: .init(
operationId: "testOperation",
responses: [:]
)
)
],
components: .init(
links: [
"testLink": link
]
)
)

let validator = Validator.blank.validating(.linkOperationsExist)
try document.validate(using: validator)
}

func test_linkOperationsExist_fails() throws {
// Create a link with an operationId that doesn't exist in the document
let link = OpenAPI.Link(operationId: "nonExistentOperation")

// Create a document with an operation using a different ID
let document = OpenAPI.Document(
info: .init(title: "test", version: "1.0"),
servers: [],
paths: [
"/hello": .init(
get: .init(
operationId: "testOperation",
responses: [:]
)
)
],
components: .init(
links: [
"testLink": link
]
)
)

let validator = Validator.blank.validating(.linkOperationsExist)

XCTAssertThrowsError(try document.validate(using: validator)) { error in
let errorCollection = error as? ValidationErrorCollection
XCTAssertEqual(errorCollection?.values.first?.reason, "Failed to satisfy: Links with operationIds have corresponding Operations")
XCTAssertTrue((errorCollection?.values.first?.codingPath.map { $0.stringValue }.joined(separator: ".") ?? "").contains("testLink"))
}
}
}
80 changes: 80 additions & 0 deletions Tests/OpenAPIKitTests/Validator/BuiltinValidationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -860,4 +860,84 @@ final class BuiltinValidationTests: XCTestCase {
// NOTE this is part of default validation
try document.validate()
}

func test_pathItemsTopLevelReferencesReferencingPathItemComponentsSuccess() throws {
let document = OpenAPI.Document(
info: .init(title: "test", version: "1.0"),
servers: [],
paths: [
"/hello": .reference(.component(named: "hello")),
"/world": .reference(.component(named: "world"))
],
components: .init(
pathItems: [
"hello": .init(),
"world": .init()
]
)
)

let validator = Validator.blank.validating(.pathItemReferencesAreValid)

try document.validate(using: validator)
}

func test_linkOperationsExist_validates() throws {
// Create a link with an operationId that exists in the document
let link = OpenAPI.Link(operationId: "testOperation")

// Create a document with an operation using that ID
let document = OpenAPI.Document(
info: .init(title: "test", version: "1.0"),
servers: [],
paths: [
"/hello": .init(
get: .init(
operationId: "testOperation",
responses: [:]
)
)
],
components: .init(
links: [
"testLink": link
]
)
)

let validator = Validator.blank.validating(.linkOperationsExist)
try document.validate(using: validator)
}

func test_linkOperationsExist_fails() throws {
// Create a link with an operationId that doesn't exist in the document
let link = OpenAPI.Link(operationId: "nonExistentOperation")

// Create a document with an operation using a different ID
let document = OpenAPI.Document(
info: .init(title: "test", version: "1.0"),
servers: [],
paths: [
"/hello": .init(
get: .init(
operationId: "testOperation",
responses: [:]
)
)
],
components: .init(
links: [
"testLink": link
]
)
)

let validator = Validator.blank.validating(.linkOperationsExist)

XCTAssertThrowsError(try document.validate(using: validator)) { error in
let errorCollection = error as? ValidationErrorCollection
XCTAssertEqual(errorCollection?.values.first?.reason, "Failed to satisfy: Links with operationIds have corresponding Operations")
XCTAssertTrue((errorCollection?.values.first?.codingPath.map { $0.stringValue }.joined(separator: ".") ?? "").contains("testLink"))
}
}
}