Skip to content

Commit 0cdd48c

Browse files
authored
Merge pull request #275 from noamalffasy/optional-paths
Optional paths
2 parents 16ea2ca + f5879f0 commit 0cdd48c

File tree

7 files changed

+128
-53
lines changed

7 files changed

+128
-53
lines changed

Sources/OpenAPIKit/Document/Document.swift

+4-2
Original file line numberDiff line numberDiff line change
@@ -400,7 +400,9 @@ extension OpenAPI.Document: Encodable {
400400
try encodeSecurity(requirements: security, to: &container, forKey: .security)
401401
}
402402

403-
try container.encode(paths, forKey: .paths)
403+
if !paths.isEmpty {
404+
try container.encode(paths, forKey: .paths)
405+
}
404406

405407
try encodeExtensions(to: &container)
406408

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

432-
let paths = try container.decode(OpenAPI.PathItem.Map.self, forKey: .paths)
434+
let paths = try container.decodeIfPresent(OpenAPI.PathItem.Map.self, forKey: .paths) ?? [:]
433435
self.paths = paths
434436
try validateSecurityRequirements(in: paths, against: components)
435437

Sources/OpenAPIKit/Validator/Validation+Builtins.swift

+4-4
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,14 @@ extension Validation {
1515
///
1616
/// The OpenAPI Specifcation does not require that the document
1717
/// contain any paths for [security reasons](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md#security-filtering)
18-
/// but documentation that is public in nature might only ever have
19-
/// an empty `PathItem.Map` in error.
18+
/// or even because it only contains webhooks, but authors may still
19+
/// want to protect against an empty `PathItem.Map` in some cases.
2020
///
2121
/// - Important: This is not an included validation by default.
22-
public static var documentContainsPaths: Validation<OpenAPI.PathItem.Map> {
22+
public static var documentContainsPaths: Validation<OpenAPI.Document> {
2323
.init(
2424
description: "Document contains at least one path",
25-
check: \.count > 0
25+
check: \.paths.count > 0
2626
)
2727
}
2828

Tests/OpenAPIKitErrorReportingTests/PathsErrorTests.swift

-18
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,6 @@ import OpenAPIKit
1111
import Yams
1212

1313
final class PathsErrorTests: XCTestCase {
14-
func test_missingPaths() {
15-
let documentYML =
16-
"""
17-
openapi: "3.1.0"
18-
info:
19-
title: test
20-
version: 1.0
21-
"""
22-
23-
XCTAssertThrowsError(try testDecoder.decode(OpenAPI.Document.self, from: documentYML)) { error in
24-
25-
let openAPIError = OpenAPI.Error(from: error)
26-
27-
XCTAssertEqual(openAPIError.localizedDescription, "Expected to find `paths` key in the root Document object but it is missing.")
28-
XCTAssertEqual(openAPIError.codingPath.map { $0.stringValue }, [])
29-
}
30-
}
31-
3214
func test_badPathReference() {
3315
let documentYML =
3416
"""

Tests/OpenAPIKitTests/Document/DocumentInfoTests.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -482,7 +482,7 @@ extension DocumentInfoTests {
482482
"version" : "1.0"
483483
}
484484
""".data(using: .utf8)!
485-
XCTAssertThrowsError( try orderUnstableDecode(OpenAPI.Document.Info.self, from: infoData)) { error in
485+
XCTAssertThrowsError(try orderUnstableDecode(OpenAPI.Document.Info.self, from: infoData)) { error in
486486
XCTAssertEqual(OpenAPI.Error(from: error).localizedDescription, "Inconsistency encountered when parsing `termsOfService`: If specified, must be a valid URL.")
487487
}
488488
}

Tests/OpenAPIKitTests/Document/DocumentTests.swift

+117-27
Original file line numberDiff line numberDiff line change
@@ -401,10 +401,7 @@ extension DocumentTests {
401401
"title" : "API",
402402
"version" : "1.0"
403403
},
404-
"openapi" : "3.1.0",
405-
"paths" : {
406-
407-
}
404+
"openapi" : "3.1.0"
408405
}
409406
"""
410407
)
@@ -455,10 +452,7 @@ extension DocumentTests {
455452
"title" : "API",
456453
"version" : "1.0"
457454
},
458-
"openapi" : "3.1.0",
459-
"paths" : {
460-
461-
}
455+
"openapi" : "3.1.0"
462456
}
463457
"""
464458
)
@@ -510,9 +504,6 @@ extension DocumentTests {
510504
"version" : "1.0"
511505
},
512506
"openapi" : "3.1.0",
513-
"paths" : {
514-
515-
},
516507
"servers" : [
517508
{
518509
"url" : "http:\\/\\/google.com"
@@ -642,9 +633,6 @@ extension DocumentTests {
642633
"version" : "1.0"
643634
},
644635
"openapi" : "3.1.0",
645-
"paths" : {
646-
647-
},
648636
"security" : [
649637
{
650638
"security" : [
@@ -722,9 +710,6 @@ extension DocumentTests {
722710
"version" : "1.0"
723711
},
724712
"openapi" : "3.1.0",
725-
"paths" : {
726-
727-
},
728713
"tags" : [
729714
{
730715
"name" : "hi"
@@ -789,10 +774,7 @@ extension DocumentTests {
789774
"title" : "API",
790775
"version" : "1.0"
791776
},
792-
"openapi" : "3.1.0",
793-
"paths" : {
794-
795-
}
777+
"openapi" : "3.1.0"
796778
}
797779
"""
798780
)
@@ -852,9 +834,6 @@ extension DocumentTests {
852834
"version" : "1.0"
853835
},
854836
"openapi" : "3.1.0",
855-
"paths" : {
856-
857-
},
858837
"x-specialFeature" : [
859838
"hello",
860839
"world"
@@ -932,9 +911,6 @@ extension DocumentTests {
932911
"version" : "1.0"
933912
},
934913
"openapi" : "3.1.0",
935-
"paths" : {
936-
937-
},
938914
"webhooks" : {
939915
"webhook-test" : {
940916
"delete" : {
@@ -1044,4 +1020,118 @@ extension DocumentTests {
10441020
)
10451021
)
10461022
}
1023+
1024+
func test_webhooks_noPaths_encode() throws {
1025+
let op = OpenAPI.Operation(responses: [:])
1026+
let pathItem: OpenAPI.PathItem = .init(get: op, put: op, post: op, delete: op, options: op, head: op, patch: op, trace: op)
1027+
let pathItemTest: Either<OpenAPI.Reference<OpenAPI.PathItem>, OpenAPI.PathItem> = .pathItem(pathItem)
1028+
1029+
let document = OpenAPI.Document(
1030+
info: .init(title: "API", version: "1.0"),
1031+
servers: [],
1032+
paths: [:],
1033+
webhooks: [
1034+
"webhook-test": pathItemTest
1035+
],
1036+
components: .noComponents,
1037+
externalDocs: .init(url: URL(string: "http://google.com")!)
1038+
)
1039+
let encodedDocument = try orderUnstableTestStringFromEncoding(of: document)
1040+
1041+
let documentJSON: String? =
1042+
"""
1043+
{
1044+
"externalDocs" : {
1045+
"url" : "http:\\/\\/google.com"
1046+
},
1047+
"info" : {
1048+
"title" : "API",
1049+
"version" : "1.0"
1050+
},
1051+
"openapi" : "3.1.0",
1052+
"webhooks" : {
1053+
"webhook-test" : {
1054+
"delete" : {
1055+
1056+
},
1057+
"get" : {
1058+
1059+
},
1060+
"head" : {
1061+
1062+
},
1063+
"options" : {
1064+
1065+
},
1066+
"patch" : {
1067+
1068+
},
1069+
"post" : {
1070+
1071+
},
1072+
"put" : {
1073+
1074+
},
1075+
"trace" : {
1076+
1077+
}
1078+
}
1079+
}
1080+
}
1081+
"""
1082+
1083+
assertJSONEquivalent(encodedDocument, documentJSON)
1084+
}
1085+
1086+
func test_webhooks_noPaths_decode() throws {
1087+
let documentData =
1088+
"""
1089+
{
1090+
"externalDocs": {
1091+
"url": "http:\\/\\/google.com"
1092+
},
1093+
"info": {
1094+
"title": "API",
1095+
"version": "1.0"
1096+
},
1097+
"openapi": "3.1.0",
1098+
"webhooks": {
1099+
"webhook-test": {
1100+
"delete": {
1101+
},
1102+
"get": {
1103+
},
1104+
"head": {
1105+
},
1106+
"options": {
1107+
},
1108+
"patch": {
1109+
},
1110+
"post": {
1111+
},
1112+
"put": {
1113+
},
1114+
"trace": {
1115+
}
1116+
}
1117+
}
1118+
}
1119+
""".data(using: .utf8)!
1120+
let document = try orderUnstableDecode(OpenAPI.Document.self, from: documentData)
1121+
1122+
let op = OpenAPI.Operation(responses: [:])
1123+
XCTAssertEqual(
1124+
document,
1125+
OpenAPI.Document(
1126+
info: .init(title: "API", version: "1.0"),
1127+
servers: [],
1128+
paths: [:],
1129+
webhooks: [
1130+
"webhook-test": .pathItem(.init(get: op, put: op, post: op, delete: op, options: op, head: op, patch: op, trace: op))
1131+
],
1132+
components: .noComponents,
1133+
externalDocs: .init(url: URL(string: "http://google.com")!)
1134+
)
1135+
)
1136+
}
10471137
}

Tests/OpenAPIKitTests/Validator/BuiltinValidationTests.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ final class BuiltinValidationTests: XCTestCase {
2424
XCTAssertThrowsError(try document.validate(using: validator)) { error in
2525
let error = error as? ValidationErrorCollection
2626
XCTAssertEqual(error?.values.first?.reason, "Failed to satisfy: Document contains at least one path")
27-
XCTAssertEqual(error?.values.first?.codingPath.map { $0.stringValue }, ["paths"])
27+
XCTAssertEqual(error?.values.first?.codingPath.map { $0.stringValue }, [])
2828
}
2929
}
3030

documentation/validation.md

+1
Original file line numberDiff line numberDiff line change
@@ -579,6 +579,7 @@ Here's a table of Array/Map types for which this quirk is relevant and which mod
579579
| `OpenAPI.Components.securitySchemes` | `ComponentDictionary` | x | x |
580580
| `OpenAPI.Document.components` | `Components` | x | x |
581581
| `OpenAPI.Document.security` | `[SecurityRequirement]` | x | x |
582+
| `OpenAPI.Document.paths` | `PathItem.Map` | | x |
582583
| `OpenAPI.Document.servers` | `[Server]` | x | x |
583584
| `OpenAPI.Document.webhooks` | `OrderedDictionary` | | x |
584585
| `OpenAPI.Link.parameters` | `OrderedDictionary` | x | x |

0 commit comments

Comments
 (0)