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

Optional paths #275

Merged
merged 5 commits into from
Sep 4, 2023
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
6 changes: 4 additions & 2 deletions Sources/OpenAPIKit/Document/Document.swift
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,9 @@ extension OpenAPI.Document: Encodable {
try encodeSecurity(requirements: security, to: &container, forKey: .security)
}

try container.encode(paths, forKey: .paths)
if !paths.isEmpty {
try container.encode(paths, forKey: .paths)
}

try encodeExtensions(to: &container)

Expand Down Expand Up @@ -429,7 +431,7 @@ extension OpenAPI.Document: Decodable {
let webhooks = try container.decodeIfPresent(OrderedDictionary<String, Either<OpenAPI.Reference<OpenAPI.PathItem>, OpenAPI.PathItem>>.self, forKey: .webhooks) ?? [:]
self.webhooks = webhooks

let paths = try container.decode(OpenAPI.PathItem.Map.self, forKey: .paths)
let paths = try container.decodeIfPresent(OpenAPI.PathItem.Map.self, forKey: .paths) ?? [:]
self.paths = paths
try validateSecurityRequirements(in: paths, against: components)

Expand Down
8 changes: 4 additions & 4 deletions Sources/OpenAPIKit/Validator/Validation+Builtins.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@ extension Validation {
///
/// The OpenAPI Specifcation does not require that the document
/// contain any paths for [security reasons](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md#security-filtering)
/// but documentation that is public in nature might only ever have
/// an empty `PathItem.Map` in error.
/// or even because it only contains webhooks, but authors may still
/// want to protect against an empty `PathItem.Map` in some cases.
///
/// - Important: This is not an included validation by default.
public static var documentContainsPaths: Validation<OpenAPI.PathItem.Map> {
public static var documentContainsPaths: Validation<OpenAPI.Document> {
.init(
description: "Document contains at least one path",
check: \.count > 0
check: \.paths.count > 0
)
}

Expand Down
18 changes: 0 additions & 18 deletions Tests/OpenAPIKitErrorReportingTests/PathsErrorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,6 @@ import OpenAPIKit
import Yams

final class PathsErrorTests: XCTestCase {
func test_missingPaths() {
let documentYML =
"""
openapi: "3.1.0"
info:
title: test
version: 1.0
"""

XCTAssertThrowsError(try testDecoder.decode(OpenAPI.Document.self, from: documentYML)) { error in

let openAPIError = OpenAPI.Error(from: error)

XCTAssertEqual(openAPIError.localizedDescription, "Expected to find `paths` key in the root Document object but it is missing.")
XCTAssertEqual(openAPIError.codingPath.map { $0.stringValue }, [])
}
}

func test_badPathReference() {
let documentYML =
"""
Expand Down
2 changes: 1 addition & 1 deletion Tests/OpenAPIKitTests/Document/DocumentInfoTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -482,7 +482,7 @@ extension DocumentInfoTests {
"version" : "1.0"
}
""".data(using: .utf8)!
XCTAssertThrowsError( try orderUnstableDecode(OpenAPI.Document.Info.self, from: infoData)) { error in
XCTAssertThrowsError(try orderUnstableDecode(OpenAPI.Document.Info.self, from: infoData)) { error in
XCTAssertEqual(OpenAPI.Error(from: error).localizedDescription, "Inconsistency encountered when parsing `termsOfService`: If specified, must be a valid URL.")
}
}
Expand Down
144 changes: 117 additions & 27 deletions Tests/OpenAPIKitTests/Document/DocumentTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -401,10 +401,7 @@ extension DocumentTests {
"title" : "API",
"version" : "1.0"
},
"openapi" : "3.1.0",
"paths" : {

}
"openapi" : "3.1.0"
}
"""
)
Expand Down Expand Up @@ -455,10 +452,7 @@ extension DocumentTests {
"title" : "API",
"version" : "1.0"
},
"openapi" : "3.1.0",
"paths" : {

}
"openapi" : "3.1.0"
}
"""
)
Expand Down Expand Up @@ -510,9 +504,6 @@ extension DocumentTests {
"version" : "1.0"
},
"openapi" : "3.1.0",
"paths" : {

},
"servers" : [
{
"url" : "http:\\/\\/google.com"
Expand Down Expand Up @@ -642,9 +633,6 @@ extension DocumentTests {
"version" : "1.0"
},
"openapi" : "3.1.0",
"paths" : {

},
"security" : [
{
"security" : [
Expand Down Expand Up @@ -722,9 +710,6 @@ extension DocumentTests {
"version" : "1.0"
},
"openapi" : "3.1.0",
"paths" : {

},
"tags" : [
{
"name" : "hi"
Expand Down Expand Up @@ -789,10 +774,7 @@ extension DocumentTests {
"title" : "API",
"version" : "1.0"
},
"openapi" : "3.1.0",
"paths" : {

}
"openapi" : "3.1.0"
}
"""
)
Expand Down Expand Up @@ -852,9 +834,6 @@ extension DocumentTests {
"version" : "1.0"
},
"openapi" : "3.1.0",
"paths" : {

},
"x-specialFeature" : [
"hello",
"world"
Expand Down Expand Up @@ -932,9 +911,6 @@ extension DocumentTests {
"version" : "1.0"
},
"openapi" : "3.1.0",
"paths" : {

},
"webhooks" : {
"webhook-test" : {
"delete" : {
Expand Down Expand Up @@ -1044,4 +1020,118 @@ extension DocumentTests {
)
)
}

func test_webhooks_noPaths_encode() throws {
let op = OpenAPI.Operation(responses: [:])
let pathItem: OpenAPI.PathItem = .init(get: op, put: op, post: op, delete: op, options: op, head: op, patch: op, trace: op)
let pathItemTest: Either<OpenAPI.Reference<OpenAPI.PathItem>, OpenAPI.PathItem> = .pathItem(pathItem)

let document = OpenAPI.Document(
info: .init(title: "API", version: "1.0"),
servers: [],
paths: [:],
webhooks: [
"webhook-test": pathItemTest
],
components: .noComponents,
externalDocs: .init(url: URL(string: "http://google.com")!)
)
let encodedDocument = try orderUnstableTestStringFromEncoding(of: document)

let documentJSON: String? =
"""
{
"externalDocs" : {
"url" : "http:\\/\\/google.com"
},
"info" : {
"title" : "API",
"version" : "1.0"
},
"openapi" : "3.1.0",
"webhooks" : {
"webhook-test" : {
"delete" : {

},
"get" : {

},
"head" : {

},
"options" : {

},
"patch" : {

},
"post" : {

},
"put" : {

},
"trace" : {

}
}
}
}
"""

assertJSONEquivalent(encodedDocument, documentJSON)
}

func test_webhooks_noPaths_decode() throws {
let documentData =
"""
{
"externalDocs": {
"url": "http:\\/\\/google.com"
},
"info": {
"title": "API",
"version": "1.0"
},
"openapi": "3.1.0",
"webhooks": {
"webhook-test": {
"delete": {
},
"get": {
},
"head": {
},
"options": {
},
"patch": {
},
"post": {
},
"put": {
},
"trace": {
}
}
}
}
""".data(using: .utf8)!
let document = try orderUnstableDecode(OpenAPI.Document.self, from: documentData)

let op = OpenAPI.Operation(responses: [:])
XCTAssertEqual(
document,
OpenAPI.Document(
info: .init(title: "API", version: "1.0"),
servers: [],
paths: [:],
webhooks: [
"webhook-test": .pathItem(.init(get: op, put: op, post: op, delete: op, options: op, head: op, patch: op, trace: op))
],
components: .noComponents,
externalDocs: .init(url: URL(string: "http://google.com")!)
)
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ final class BuiltinValidationTests: XCTestCase {
XCTAssertThrowsError(try document.validate(using: validator)) { error in
let error = error as? ValidationErrorCollection
XCTAssertEqual(error?.values.first?.reason, "Failed to satisfy: Document contains at least one path")
XCTAssertEqual(error?.values.first?.codingPath.map { $0.stringValue }, ["paths"])
XCTAssertEqual(error?.values.first?.codingPath.map { $0.stringValue }, [])
}
}

Expand Down
1 change: 1 addition & 0 deletions documentation/validation.md
Original file line number Diff line number Diff line change
Expand Up @@ -579,6 +579,7 @@ Here's a table of Array/Map types for which this quirk is relevant and which mod
| `OpenAPI.Components.securitySchemes` | `ComponentDictionary` | x | x |
| `OpenAPI.Document.components` | `Components` | x | x |
| `OpenAPI.Document.security` | `[SecurityRequirement]` | x | x |
| `OpenAPI.Document.paths` | `PathItem.Map` | | x |
| `OpenAPI.Document.servers` | `[Server]` | x | x |
| `OpenAPI.Document.webhooks` | `OrderedDictionary` | | x |
| `OpenAPI.Link.parameters` | `OrderedDictionary` | x | x |
Expand Down