Skip to content

Commit 4b5f646

Browse files
authored
Merge pull request #122 from mattpolzin/ordered-dict-subscript-ambiguity
Address ambiguity with StatusCode-keyed OrderedDictionary.
2 parents e0b5e64 + d04f739 commit 4b5f646

File tree

11 files changed

+82
-20
lines changed

11 files changed

+82
-20
lines changed

Sources/OpenAPIKit/Operation/Operation.swift

+40
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,46 @@ extension OpenAPI {
1717
public var operationId: String?
1818
public var parameters: Parameter.Array
1919
public var requestBody: Either<JSONReference<OpenAPI.Request>, OpenAPI.Request>?
20+
/// The possible responses for this operation, keyed by status code.
21+
///
22+
/// The status code keys can be integer values, ranges, or even the
23+
/// `default` which just refers to the response to expect where no
24+
/// other respones apply.
25+
///
26+
/// Because the map is ordered, you can access responses by either
27+
/// status code or index. Notice that the values of this dictionary are actually
28+
/// `Either` an inline `Response` or a reference to a `Response` that is
29+
/// defined elsewhere.
30+
///
31+
/// **Example:**
32+
///
33+
/// let firstResponse: (OpenAPI.Response.StatusCode, Either<JSONReference<OpenAPI.Response>, OpenAPI.Response>)
34+
/// firstResponse = operation.responses[0]!
35+
///
36+
/// // literally documented as "200" status code:
37+
/// let successResponse: Either<JSONReference<OpenAPI.Response>, OpenAPI.Response>
38+
/// successResponse = operation.responses[status: 200]!
39+
///
40+
/// // documented as "2XX" status code:
41+
/// let successResponse2: Either<JSONReference<OpenAPI.Response>, OpenAPI.Response>
42+
/// successResponse2 = operation.responses[.range(.success)]!
43+
///
44+
/// If you want to access the response (assuming it is inlined) you need to grab
45+
/// it out of the `Either`.
46+
///
47+
/// **Example:**
48+
///
49+
/// let inlinedResponse = successResponse.responseValue
50+
///
51+
/// You can also look the response up in the `Components`. For convenience, you
52+
/// can ask to have the `Either` looked up and the result will be the `Response`
53+
/// regardless of whether the `Response` was inlined or found in the `Components`.
54+
///
55+
/// **Example:**
56+
///
57+
/// let foundResponse: OpenAPI.Response
58+
/// foundResponse = document.components.dereference(successResponse)!
59+
///
2060
public var responses: OpenAPI.Response.Map
2161
// public let callbacks:
2262

Sources/OpenAPIKit/Response/Response.swift

+22
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,28 @@ extension OpenAPI.Response {
4040
public typealias Map = OrderedDictionary<StatusCode, Either<JSONReference<OpenAPI.Response>, OpenAPI.Response>>
4141
}
4242

43+
extension OrderedDictionary where Key == OpenAPI.Response.StatusCode {
44+
/// This subscript makes it possible to disambiguate the call to the integer-based
45+
/// (indexed) subscript and the key-based (hashed) subscript of the `OrderedDictionary`
46+
/// for `StatusCode` keys.
47+
///
48+
/// A problem of ambiguity arises from the fact that `StatusCode` is `ExpressibleByIntegerLiteral`
49+
/// so both `OrderedDictionary` unlabled subscript accessors are applicable.
50+
///
51+
/// **Example**:
52+
///
53+
/// let successfulDeleteOperation = document.paths["/hello/world"]?.delete?.responses[status: 204]
54+
///
55+
public subscript(status status: OpenAPI.Response.StatusCode) -> Value? {
56+
get {
57+
return self[status]
58+
}
59+
set {
60+
self[status] = newValue
61+
}
62+
}
63+
}
64+
4365
// MARK: - Status Code
4466
extension OpenAPI.Response {
4567
/// An HTTP Status code or status code range.

Tests/OpenAPIKitCompatibilitySuite/PetStoreAPITests.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ final class PetStoreAPICampatibilityTests: XCTestCase {
121121
let dereferencedDoc = try apiDoc.locallyDereferenced()
122122

123123
// Pet schema is a $ref to Components Object
124-
XCTAssertEqual(dereferencedDoc.paths["/pet"]?.post?.responses[.status(code: 200)]?.content[.json]?.schema.objectContext?.properties["name"]?.underlyingJSONSchema, try JSONSchema.string.with(example: "doggie"))
124+
XCTAssertEqual(dereferencedDoc.paths["/pet"]?.post?.responses[status: 200]?.content[.json]?.schema.objectContext?.properties["name"]?.underlyingJSONSchema, try JSONSchema.string.with(example: "doggie"))
125125
}
126126

127127
func test_resolveDocument() throws {

Tests/OpenAPIKitCompatibilitySuite/TomTomAPITests.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ final class TomTomAPICampatibilityTests: XCTestCase {
109109
let dereferencedDoc = try apiDoc.locallyDereferenced()
110110

111111
// response is a $ref to Components Object
112-
XCTAssertEqual(dereferencedDoc.paths["/search/{versionNumber}/cS/{category}.{ext}"]?.get?.responses[.status(code: 200)]?.description, "OK: the search successfully returned zero or more results.")
112+
XCTAssertEqual(dereferencedDoc.paths["/search/{versionNumber}/cS/{category}.{ext}"]?.get?.responses[status: 200]?.description, "OK: the search successfully returned zero or more results.")
113113
}
114114

115115
func test_resolveDocument() throws {

Tests/OpenAPIKitTests/Document/DereferencedDocumentTests.swift

+3-3
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ final class DereferencedDocumentTests: XCTestCase {
3939
).locallyDereferenced()
4040

4141
XCTAssertEqual(t1.paths.count, 1)
42-
XCTAssertEqual(t1.paths["/hello/world"]?.get?.responses[.status(code: 200)]?.description, "success")
42+
XCTAssertEqual(t1.paths["/hello/world"]?.get?.responses[status: 200]?.description, "success")
4343
}
4444

4545
func test_noSecurityReferencedResponseInPath() throws {
@@ -64,7 +64,7 @@ final class DereferencedDocumentTests: XCTestCase {
6464
).locallyDereferenced()
6565

6666
XCTAssertEqual(t1.paths.count, 1)
67-
XCTAssertEqual(t1.paths["/hello/world"]?.get?.responses[.status(code: 200)]?.description, "success")
67+
XCTAssertEqual(t1.paths["/hello/world"]?.get?.responses[status: 200]?.description, "success")
6868

6969
XCTAssertEqual(t1.routes.first?.path, "/hello/world")
7070
XCTAssertNotNil(t1.routes.first?.pathItem.get)
@@ -96,7 +96,7 @@ final class DereferencedDocumentTests: XCTestCase {
9696
).locallyDereferenced()
9797

9898
XCTAssertEqual(t1.paths.count, 1)
99-
XCTAssertEqual(t1.paths["/hello/world"]?.get?.responses[.status(code: 200)]?.description, "success")
99+
XCTAssertEqual(t1.paths["/hello/world"]?.get?.responses[status: 200]?.description, "success")
100100

101101
XCTAssertEqual(t1.security.count, 1)
102102
XCTAssertEqual(t1.security.first?.schemes["test"]?.securityScheme.type, .apiKey(name: "Api-Key", location: .header))

Tests/OpenAPIKitTests/EaseOfUseTests.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -454,7 +454,7 @@ final class DeclarativeEaseOfUseTests: XCTestCase {
454454
let document = testDocument
455455

456456
let endpoint = document.paths["/widgets/{id}"]?.get
457-
let response = endpoint?.responses[.status(code: 200)]?.responseValue
457+
let response = endpoint?.responses[status: 200]?.responseValue
458458
let responseSchemaReference = response?.content[.json]?.schema
459459
// this response schema is a reference found in the Components Object. We dereference
460460
// it to get at the schema.

Tests/OpenAPIKitTests/Operation/DereferencedOperationTests.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ final class DereferencedOperationTests: XCTestCase {
4343
XCTAssertEqual(t1.parameters.count, 1)
4444
XCTAssertEqual(t1.requestBody?.underlyingRequest, OpenAPI.Request(content: [.json: .init(schema: .string)]))
4545
XCTAssertEqual(t1.responses.count, 1)
46-
XCTAssertEqual(t1.responseOutcomes.first?.response, t1.responses[.status(code: 200)])
46+
XCTAssertEqual(t1.responseOutcomes.first?.response, t1.responses[status: 200])
4747
XCTAssertEqual(t1.responseOutcomes.first?.status, 200)
4848
}
4949

@@ -137,7 +137,7 @@ final class DereferencedOperationTests: XCTestCase {
137137
resolvingIn: components
138138
)
139139
XCTAssertEqual(
140-
t1.responses[.status(code: 200)]?.underlyingResponse,
140+
t1.responses[status: 200]?.underlyingResponse,
141141
.init(description: "test")
142142
)
143143
}

Tests/OpenAPIKitTests/Operation/ResolvedEndpointTests.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ final class ResolvedEndpointTests: XCTestCase {
5757
XCTAssertEqual(endpoints.first?.method, .get)
5858
XCTAssertEqual(endpoints.first?.path, "/hello/world")
5959
XCTAssertEqual(endpoints.first?.requestBody?.description, "requestBody")
60-
XCTAssertEqual(endpoints.first?.responses[.status(code: 200)]?.description, "hello world")
60+
XCTAssertEqual(endpoints.first?.responses[status: 200]?.description, "hello world")
6161
XCTAssertEqual(endpoints.first?.deprecated, true)
6262

6363
let t2 = try OpenAPI.Document(

Tests/OpenAPIKitTests/Path Item/DereferencedPathItemTests.swift

+8-8
Original file line numberDiff line numberDiff line change
@@ -125,21 +125,21 @@ final class DereferencedPathItemTests: XCTestCase {
125125

126126
XCTAssertEqual(t1.endpoints.count, 8)
127127
XCTAssertEqual(t1[.delete]?.tags, ["delete op"])
128-
XCTAssertEqual(t1[.delete]?.responses[.status(code: 200)]?.description, "delete resp")
128+
XCTAssertEqual(t1[.delete]?.responses[status: 200]?.description, "delete resp")
129129
XCTAssertEqual(t1[.get]?.tags, ["get op"])
130-
XCTAssertEqual(t1[.get]?.responses[.status(code: 200)]?.description, "get resp")
130+
XCTAssertEqual(t1[.get]?.responses[status: 200]?.description, "get resp")
131131
XCTAssertEqual(t1[.head]?.tags, ["head op"])
132-
XCTAssertEqual(t1[.head]?.responses[.status(code: 200)]?.description, "head resp")
132+
XCTAssertEqual(t1[.head]?.responses[status: 200]?.description, "head resp")
133133
XCTAssertEqual(t1[.options]?.tags, ["options op"])
134-
XCTAssertEqual(t1[.options]?.responses[.status(code: 200)]?.description, "options resp")
134+
XCTAssertEqual(t1[.options]?.responses[status: 200]?.description, "options resp")
135135
XCTAssertEqual(t1[.patch]?.tags, ["patch op"])
136-
XCTAssertEqual(t1[.patch]?.responses[.status(code: 200)]?.description, "patch resp")
136+
XCTAssertEqual(t1[.patch]?.responses[status: 200]?.description, "patch resp")
137137
XCTAssertEqual(t1[.post]?.tags, ["post op"])
138-
XCTAssertEqual(t1[.post]?.responses[.status(code: 200)]?.description, "post resp")
138+
XCTAssertEqual(t1[.post]?.responses[status: 200]?.description, "post resp")
139139
XCTAssertEqual(t1[.put]?.tags, ["put op"])
140-
XCTAssertEqual(t1[.put]?.responses[.status(code: 200)]?.description, "put resp")
140+
XCTAssertEqual(t1[.put]?.responses[status: 200]?.description, "put resp")
141141
XCTAssertEqual(t1[.trace]?.tags, ["trace op"])
142-
XCTAssertEqual(t1[.trace]?.responses[.status(code: 200)]?.description, "trace resp")
142+
XCTAssertEqual(t1[.trace]?.responses[status: 200]?.description, "trace resp")
143143
}
144144

145145
func test_missingReferencedGetResp() {

Tests/OpenAPIKitTests/Validator/ValidatorTests.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -1173,7 +1173,7 @@ final class ValidatorTests: XCTestCase {
11731173

11741174
let successResponseBodyContainsNameAndId = Validation(
11751175
check: unwrap(
1176-
\OpenAPI.Response.Map[.status(code: 201)]?.responseValue,
1176+
\OpenAPI.Response.Map[status: 201]?.responseValue,
11771177
into: responseBodyContainsNameAndId,
11781178
description: "201 status response value"
11791179
)
@@ -1301,7 +1301,7 @@ final class ValidatorTests: XCTestCase {
13011301

13021302
let successResponseBodyContainsNameAndId = Validation(
13031303
check: unwrap(
1304-
\OpenAPI.Response.Map[.status(code: 201)]?.responseValue,
1304+
\OpenAPI.Response.Map[status: 201]?.responseValue,
13051305
into: responseBodyContainsNameAndId,
13061306
description: "201 status response value"
13071307
)

documentation/validation.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -496,7 +496,7 @@ let responseBodyContainsNameAndId = Validation(
496496
// a missing `201` response definition.
497497
let successResponseBodyContainsNameAndId = Validation(
498498
check: unwrap(
499-
\OpenAPI.Response.Map[.status(code: 201)]?.responseValue,
499+
\OpenAPI.Response.Map[status: 201]?.responseValue,
500500
into: responseBodyContainsNameAndId,
501501
description: "201 status response value"
502502
)

0 commit comments

Comments
 (0)