Skip to content

Commit 3937825

Browse files
feat: Makes all request and response types and fields public
1 parent 77b4cee commit 3937825

6 files changed

Lines changed: 181 additions & 154 deletions

File tree

Package.resolved

Lines changed: 31 additions & 30 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Sources/GraphQLTransportWS/Client.swift

Lines changed: 5 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -37,40 +37,15 @@ public class Client<InitPayload: Equatable & Codable> {
3737
return
3838
}
3939

40-
let response: Response
41-
do {
42-
response = try self.decoder.decode(Response.self, from: json)
43-
} catch {
44-
try await self.error(.noType())
45-
return
46-
}
47-
48-
switch response.type {
49-
case .connectionAck:
50-
guard let connectionAckResponse = try? self.decoder.decode(ConnectionAckResponse.self, from: json) else {
51-
try await self.error(.invalidResponseFormat(messageType: .connectionAck))
52-
return
53-
}
40+
if let connectionAckResponse = try? self.decoder.decode(ConnectionAckResponse.self, from: json) {
5441
try await self.onConnectionAck(connectionAckResponse, self)
55-
case .next:
56-
guard let nextResponse = try? self.decoder.decode(NextResponse.self, from: json) else {
57-
try await self.error(.invalidResponseFormat(messageType: .next))
58-
return
59-
}
42+
} else if let nextResponse = try? self.decoder.decode(NextResponse.self, from: json) {
6043
try await self.onNext(nextResponse, self)
61-
case .error:
62-
guard let errorResponse = try? self.decoder.decode(ErrorResponse.self, from: json) else {
63-
try await self.error(.invalidResponseFormat(messageType: .error))
64-
return
65-
}
44+
} else if let errorResponse = try? self.decoder.decode(ErrorResponse.self, from: json) {
6645
try await self.onError(errorResponse, self)
67-
case .complete:
68-
guard let completeResponse = try? self.decoder.decode(CompleteResponse.self, from: json) else {
69-
try await self.error(.invalidResponseFormat(messageType: .complete))
70-
return
71-
}
46+
} else if let completeResponse = try? self.decoder.decode(CompleteResponse.self, from: json) {
7247
try await self.onComplete(completeResponse, self)
73-
case .unknown:
48+
} else {
7449
try await self.error(.invalidType())
7550
}
7651
}
Lines changed: 76 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,91 @@
11
import Foundation
22
import GraphQL
33

4-
/// We also require that an 'authToken' field is provided in the 'payload' during the connection
5-
/// init message. For example:
6-
/// ```
7-
/// {
8-
/// "type": 'connection_init',
9-
/// "payload": {
10-
/// "authToken": "eyJhbGciOiJIUz..."
11-
/// }
12-
/// }
13-
/// ```
14-
15-
/// A general request. This object's type is used to triage to other, more specific request objects.
16-
struct Request: Equatable, JsonEncodable {
17-
let type: RequestMessageType
18-
}
19-
204
/// A websocket `connection_init` request from the client to the server
21-
struct ConnectionInitRequest<InitPayload: Codable & Equatable>: Equatable, JsonEncodable {
22-
var type = RequestMessageType.connectionInit
23-
let payload: InitPayload
5+
public struct ConnectionInitRequest<InitPayload: Codable & Equatable>: Equatable, JsonEncodable {
6+
public let type: RequestMessageType = .connectionInit
7+
public let payload: InitPayload
8+
9+
public init(payload: InitPayload) {
10+
self.payload = payload
11+
}
12+
13+
public init(from decoder: any Decoder) throws {
14+
let container = try decoder.container(keyedBy: Self.CodingKeys.self)
15+
if try container.decode(RequestMessageType.self, forKey: .type) != .connectionInit {
16+
throw DecodingError.dataCorrupted(.init(codingPath: decoder.codingPath, debugDescription: "type must be `connection_init`"))
17+
}
18+
payload = try container.decode(InitPayload.self, forKey: .payload)
19+
}
2420
}
2521

2622
/// A websocket `subscribe` request from the client to the server
27-
struct SubscribeRequest: Equatable, JsonEncodable {
28-
var type = RequestMessageType.subscribe
29-
let payload: GraphQLRequest
30-
let id: String
23+
public struct SubscribeRequest: Equatable, JsonEncodable {
24+
public let type = RequestMessageType.subscribe
25+
public let payload: GraphQLRequest
26+
public let id: String
27+
28+
public init(payload: GraphQLRequest, id: String) {
29+
self.payload = payload
30+
self.id = id
31+
}
32+
33+
public init(from decoder: any Decoder) throws {
34+
let container = try decoder.container(keyedBy: Self.CodingKeys.self)
35+
if try container.decode(RequestMessageType.self, forKey: .type) != .subscribe {
36+
throw DecodingError.dataCorrupted(.init(codingPath: decoder.codingPath, debugDescription: "type must be `subscribe`"))
37+
}
38+
payload = try container.decode(GraphQLRequest.self, forKey: .payload)
39+
id = try container.decode(String.self, forKey: .id)
40+
}
3141
}
3242

3343
/// A websocket `complete` request from the client to the server
34-
struct CompleteRequest: Equatable, JsonEncodable {
35-
var type = RequestMessageType.complete
36-
let id: String
44+
public struct CompleteRequest: Equatable, JsonEncodable {
45+
public let type = RequestMessageType.complete
46+
public let id: String
47+
48+
public init(id: String) {
49+
self.id = id
50+
}
51+
52+
public init(from decoder: any Decoder) throws {
53+
let container = try decoder.container(keyedBy: Self.CodingKeys.self)
54+
if try container.decode(RequestMessageType.self, forKey: .type) != .complete {
55+
throw DecodingError.dataCorrupted(.init(codingPath: decoder.codingPath, debugDescription: "type must be `complete`"))
56+
}
57+
id = try container.decode(String.self, forKey: .id)
58+
}
3759
}
3860

3961
/// The supported websocket request message types from the client to the server
40-
enum RequestMessageType: String, Codable {
41-
case connectionInit = "connection_init"
42-
case subscribe
43-
case complete
44-
case unknown
45-
46-
init(from decoder: Decoder) throws {
47-
guard let value = try? decoder.singleValueContainer().decode(String.self) else {
48-
self = .unknown
49-
return
50-
}
51-
self = RequestMessageType(rawValue: value) ?? .unknown
62+
public struct RequestMessageType: Equatable, Codable, Sendable {
63+
// This is implemented as a struct with only public static properties, backed by an internal enum
64+
// in order to grow the list of accepted request types in a non-breaking way.
65+
66+
let type: RequestType
67+
68+
init(type: RequestType) {
69+
self.type = type
70+
}
71+
72+
public init(from decoder: any Decoder) throws {
73+
let container = try decoder.singleValueContainer()
74+
type = try container.decode(RequestType.self)
75+
}
76+
77+
public func encode(to encoder: any Encoder) throws {
78+
var container = encoder.singleValueContainer()
79+
try container.encode(type)
80+
}
81+
82+
public static let connectionInit: Self = .init(type: .connectionInit)
83+
public static let subscribe: Self = .init(type: .subscribe)
84+
public static let complete: Self = .init(type: .complete)
85+
86+
enum RequestType: String, Codable {
87+
case connectionInit = "connection_init"
88+
case subscribe
89+
case complete
5290
}
5391
}

Sources/GraphQLTransportWS/Responses.swift

Lines changed: 62 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,66 @@
11
import Foundation
22
import GraphQL
33

4-
/// A general response. This object's type is used to triage to other, more specific response objects.
5-
struct Response: Equatable, JsonEncodable {
6-
let type: ResponseMessageType
7-
}
8-
94
/// A websocket `connection_ack` response from the server to the client
105
public struct ConnectionAckResponse: Equatable, JsonEncodable {
11-
let type: ResponseMessageType
6+
public let type: ResponseMessageType = .connectionAck
127
public let payload: [String: Map]?
138

14-
init(_ payload: [String: Map]? = nil) {
15-
type = .connectionAck
9+
public init(payload: [String: Map]? = nil) {
1610
self.payload = payload
1711
}
12+
13+
public init(from decoder: any Decoder) throws {
14+
let container = try decoder.container(keyedBy: Self.CodingKeys.self)
15+
if try container.decode(ResponseMessageType.self, forKey: .type) != .connectionAck {
16+
throw DecodingError.dataCorrupted(.init(codingPath: decoder.codingPath, debugDescription: "type must be `connection_ack`"))
17+
}
18+
payload = try container.decodeIfPresent([String: Map].self, forKey: .payload)
19+
}
1820
}
1921

2022
/// A websocket `next` response from the server to the client
2123
public struct NextResponse: Equatable, JsonEncodable {
22-
let type: ResponseMessageType
24+
public let type: ResponseMessageType = .next
2325
public let payload: GraphQLResult?
2426
public let id: String
2527

26-
init(_ payload: GraphQLResult? = nil, id: String) {
27-
type = .next
28+
public init(payload: GraphQLResult?, id: String) {
2829
self.payload = payload
2930
self.id = id
3031
}
32+
33+
public init(from decoder: any Decoder) throws {
34+
let container = try decoder.container(keyedBy: Self.CodingKeys.self)
35+
if try container.decode(ResponseMessageType.self, forKey: .type) != .next {
36+
throw DecodingError.dataCorrupted(.init(codingPath: decoder.codingPath, debugDescription: "type must be `next`"))
37+
}
38+
payload = try container.decodeIfPresent(GraphQLResult.self, forKey: .payload)
39+
id = try container.decode(String.self, forKey: .id)
40+
}
3141
}
3242

3343
/// A websocket `complete` response from the server to the client
3444
public struct CompleteResponse: Equatable, JsonEncodable {
35-
let type: ResponseMessageType
45+
public let type: ResponseMessageType = .complete
3646
public let id: String
3747

38-
init(id: String) {
39-
type = .complete
48+
public init(id: String) {
4049
self.id = id
4150
}
51+
52+
public init(from decoder: any Decoder) throws {
53+
let container = try decoder.container(keyedBy: Self.CodingKeys.self)
54+
if try container.decode(ResponseMessageType.self, forKey: .type) != .complete {
55+
throw DecodingError.dataCorrupted(.init(codingPath: decoder.codingPath, debugDescription: "type must be `complete`"))
56+
}
57+
id = try container.decode(String.self, forKey: .id)
58+
}
4259
}
4360

4461
/// A websocket `error` response from the server to the client
4562
public struct ErrorResponse: Equatable, JsonEncodable {
46-
let type: ResponseMessageType
63+
public let type: ResponseMessageType
4764
public let payload: [GraphQLError]
4865
public let id: String
4966

@@ -63,19 +80,36 @@ public struct ErrorResponse: Equatable, JsonEncodable {
6380
}
6481

6582
/// The supported websocket response message types from the server to the client
66-
enum ResponseMessageType: String, Codable {
67-
case connectionAck = "connection_ack"
68-
case next
69-
case error
70-
case complete
71-
case unknown
72-
73-
init(from decoder: Decoder) throws {
74-
guard let value = try? decoder.singleValueContainer().decode(String.self) else {
75-
self = .unknown
76-
return
77-
}
78-
self = ResponseMessageType(rawValue: value) ?? .unknown
83+
public struct ResponseMessageType: Equatable, Codable, Sendable {
84+
// This is implemented as a struct with only public static properties, backed by an internal enum
85+
// in order to grow the list of accepted response types in a non-breaking way.
86+
87+
let type: ResponseType
88+
89+
init(type: ResponseType) {
90+
self.type = type
91+
}
92+
93+
public init(from decoder: any Decoder) throws {
94+
let container = try decoder.singleValueContainer()
95+
type = try container.decode(ResponseType.self)
96+
}
97+
98+
public func encode(to encoder: any Encoder) throws {
99+
var container = encoder.singleValueContainer()
100+
try container.encode(type)
101+
}
102+
103+
public static let connectionAck: Self = .init(type: .connectionAck)
104+
public static let next: Self = .init(type: .next)
105+
public static let complete: Self = .init(type: .complete)
106+
public static let error: Self = .init(type: .error)
107+
108+
enum ResponseType: String, Codable {
109+
case connectionAck = "connection_ack"
110+
case next
111+
case complete
112+
case error
79113
}
80114
}
81115

0 commit comments

Comments
 (0)