Skip to content

Commit 43a64cb

Browse files
Merge pull request #8 from GraphQLSwift/feat/public-request-response
Makes all request & response types and fields public
2 parents 77b4cee + 8d03422 commit 43a64cb

7 files changed

Lines changed: 210 additions & 98 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: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ public class Client<InitPayload: Equatable & Codable> {
7070
return
7171
}
7272
try await self.onComplete(completeResponse, self)
73-
case .unknown:
73+
default:
7474
try await self.error(.invalidType())
7575
}
7676
}

Sources/GraphQLTransportWS/GraphqlTransportWSError.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,14 +60,14 @@ struct GraphQLTransportWSError: Error {
6060

6161
static func invalidRequestFormat(messageType: RequestMessageType) -> Self {
6262
return self.init(
63-
"Request message doesn't match '\(messageType.rawValue)' JSON format",
63+
"Request message doesn't match '\(messageType.type.rawValue)' JSON format",
6464
code: .invalidRequestFormat
6565
)
6666
}
6767

6868
static func invalidResponseFormat(messageType: ResponseMessageType) -> Self {
6969
return self.init(
70-
"Response message doesn't match '\(messageType.rawValue)' JSON format",
70+
"Response message doesn't match '\(messageType.type.rawValue)' JSON format",
7171
code: .invalidResponseFormat
7272
)
7373
}
Lines changed: 87 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,105 @@
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-
154
/// 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
5+
public struct Request: Equatable, JsonEncodable {
6+
public let type: RequestMessageType
187
}
198

209
/// 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
10+
public struct ConnectionInitRequest<InitPayload: Codable & Equatable>: Equatable, JsonEncodable {
11+
public let type: RequestMessageType = .connectionInit
12+
public let payload: InitPayload
13+
14+
public init(payload: InitPayload) {
15+
self.payload = payload
16+
}
17+
18+
public init(from decoder: any Decoder) throws {
19+
let container = try decoder.container(keyedBy: Self.CodingKeys.self)
20+
if try container.decode(RequestMessageType.self, forKey: .type) != .connectionInit {
21+
throw DecodingError.dataCorrupted(.init(
22+
codingPath: decoder.codingPath,
23+
debugDescription: "type must be `\(RequestMessageType.connectionInit.type)`"
24+
))
25+
}
26+
payload = try container.decode(InitPayload.self, forKey: .payload)
27+
}
2428
}
2529

2630
/// 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
31+
public struct SubscribeRequest: Equatable, JsonEncodable {
32+
public let type = RequestMessageType.subscribe
33+
public let payload: GraphQLRequest
34+
public let id: String
35+
36+
public init(payload: GraphQLRequest, id: String) {
37+
self.payload = payload
38+
self.id = id
39+
}
40+
41+
public init(from decoder: any Decoder) throws {
42+
let container = try decoder.container(keyedBy: Self.CodingKeys.self)
43+
if try container.decode(RequestMessageType.self, forKey: .type) != .subscribe {
44+
throw DecodingError.dataCorrupted(.init(
45+
codingPath: decoder.codingPath,
46+
debugDescription: "type must be `\(RequestMessageType.subscribe.type)`"
47+
))
48+
}
49+
payload = try container.decode(GraphQLRequest.self, forKey: .payload)
50+
id = try container.decode(String.self, forKey: .id)
51+
}
3152
}
3253

3354
/// A websocket `complete` request from the client to the server
34-
struct CompleteRequest: Equatable, JsonEncodable {
35-
var type = RequestMessageType.complete
36-
let id: String
55+
public struct CompleteRequest: Equatable, JsonEncodable {
56+
public let type = RequestMessageType.complete
57+
public let id: String
58+
59+
public init(id: String) {
60+
self.id = id
61+
}
62+
63+
public init(from decoder: any Decoder) throws {
64+
let container = try decoder.container(keyedBy: Self.CodingKeys.self)
65+
if try container.decode(RequestMessageType.self, forKey: .type) != .complete {
66+
throw DecodingError.dataCorrupted(.init(
67+
codingPath: decoder.codingPath,
68+
debugDescription: "type must be `\(RequestMessageType.complete.type)`"
69+
))
70+
}
71+
id = try container.decode(String.self, forKey: .id)
72+
}
3773
}
3874

3975
/// 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
76+
public struct RequestMessageType: Equatable, Codable, Sendable {
77+
// This is implemented as a struct with only public static properties, backed by an internal enum
78+
// in order to grow the list of accepted request types in a non-breaking way.
79+
80+
let type: RequestType
81+
82+
init(type: RequestType) {
83+
self.type = type
84+
}
85+
86+
public init(from decoder: any Decoder) throws {
87+
let container = try decoder.singleValueContainer()
88+
type = try container.decode(RequestType.self)
89+
}
90+
91+
public func encode(to encoder: any Encoder) throws {
92+
var container = encoder.singleValueContainer()
93+
try container.encode(type)
94+
}
95+
96+
public static let connectionInit: Self = .init(type: .connectionInit)
97+
public static let subscribe: Self = .init(type: .subscribe)
98+
public static let complete: Self = .init(type: .complete)
99+
100+
enum RequestType: String, Codable {
101+
case connectionInit = "connection_init"
102+
case subscribe
103+
case complete
52104
}
53105
}

Sources/GraphQLTransportWS/Responses.swift

Lines changed: 85 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,48 +2,79 @@ import Foundation
22
import GraphQL
33

44
/// 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
5+
public struct Response: Equatable, JsonEncodable {
6+
public let type: ResponseMessageType
77
}
88

99
/// A websocket `connection_ack` response from the server to the client
1010
public struct ConnectionAckResponse: Equatable, JsonEncodable {
11-
let type: ResponseMessageType
11+
public let type: ResponseMessageType = .connectionAck
1212
public let payload: [String: Map]?
1313

14-
init(_ payload: [String: Map]? = nil) {
15-
type = .connectionAck
14+
public init(payload: [String: Map]? = nil) {
1615
self.payload = payload
1716
}
17+
18+
public init(from decoder: any Decoder) throws {
19+
let container = try decoder.container(keyedBy: Self.CodingKeys.self)
20+
if try container.decode(ResponseMessageType.self, forKey: .type) != .connectionAck {
21+
throw DecodingError.dataCorrupted(.init(
22+
codingPath: decoder.codingPath,
23+
debugDescription: "type must be `\(ResponseMessageType.connectionAck.type)`"
24+
))
25+
}
26+
payload = try container.decodeIfPresent([String: Map].self, forKey: .payload)
27+
}
1828
}
1929

2030
/// A websocket `next` response from the server to the client
2131
public struct NextResponse: Equatable, JsonEncodable {
22-
let type: ResponseMessageType
32+
public let type: ResponseMessageType = .next
2333
public let payload: GraphQLResult?
2434
public let id: String
2535

26-
init(_ payload: GraphQLResult? = nil, id: String) {
27-
type = .next
36+
public init(payload: GraphQLResult?, id: String) {
2837
self.payload = payload
2938
self.id = id
3039
}
40+
41+
public init(from decoder: any Decoder) throws {
42+
let container = try decoder.container(keyedBy: Self.CodingKeys.self)
43+
if try container.decode(ResponseMessageType.self, forKey: .type) != .next {
44+
throw DecodingError.dataCorrupted(.init(
45+
codingPath: decoder.codingPath,
46+
debugDescription: "type must be `\(ResponseMessageType.next.type)`"
47+
))
48+
}
49+
payload = try container.decodeIfPresent(GraphQLResult.self, forKey: .payload)
50+
id = try container.decode(String.self, forKey: .id)
51+
}
3152
}
3253

3354
/// A websocket `complete` response from the server to the client
3455
public struct CompleteResponse: Equatable, JsonEncodable {
35-
let type: ResponseMessageType
56+
public let type: ResponseMessageType = .complete
3657
public let id: String
3758

38-
init(id: String) {
39-
type = .complete
59+
public init(id: String) {
4060
self.id = id
4161
}
62+
63+
public init(from decoder: any Decoder) throws {
64+
let container = try decoder.container(keyedBy: Self.CodingKeys.self)
65+
if try container.decode(ResponseMessageType.self, forKey: .type) != .complete {
66+
throw DecodingError.dataCorrupted(.init(
67+
codingPath: decoder.codingPath,
68+
debugDescription: "type must be `\(ResponseMessageType.complete.type)`"
69+
))
70+
}
71+
id = try container.decode(String.self, forKey: .id)
72+
}
4273
}
4374

4475
/// A websocket `error` response from the server to the client
4576
public struct ErrorResponse: Equatable, JsonEncodable {
46-
let type: ResponseMessageType
77+
public let type: ResponseMessageType = .error
4778
public let payload: [GraphQLError]
4879
public let id: String
4980

@@ -56,26 +87,54 @@ public struct ErrorResponse: Equatable, JsonEncodable {
5687
return GraphQLError(error)
5788
}
5889
}
59-
type = .error
6090
payload = graphQLErrors
6191
self.id = id
6292
}
93+
94+
public init(from decoder: any Decoder) throws {
95+
let container = try decoder.container(keyedBy: Self.CodingKeys.self)
96+
if try container.decode(ResponseMessageType.self, forKey: .type) != .error {
97+
throw DecodingError.dataCorrupted(.init(
98+
codingPath: decoder.codingPath,
99+
debugDescription: "type must be `\(ResponseMessageType.error.type)`"
100+
))
101+
}
102+
payload = try container.decode([GraphQLError].self, forKey: .payload)
103+
id = try container.decode(String.self, forKey: .id)
104+
}
63105
}
64106

65107
/// 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
108+
public struct ResponseMessageType: Equatable, Codable, Sendable {
109+
// This is implemented as a struct with only public static properties, backed by an internal enum
110+
// in order to grow the list of accepted response types in a non-breaking way.
111+
112+
let type: ResponseType
113+
114+
init(type: ResponseType) {
115+
self.type = type
116+
}
117+
118+
public init(from decoder: any Decoder) throws {
119+
let container = try decoder.singleValueContainer()
120+
type = try container.decode(ResponseType.self)
121+
}
122+
123+
public func encode(to encoder: any Encoder) throws {
124+
var container = encoder.singleValueContainer()
125+
try container.encode(type)
126+
}
127+
128+
public static let connectionAck: Self = .init(type: .connectionAck)
129+
public static let next: Self = .init(type: .next)
130+
public static let complete: Self = .init(type: .complete)
131+
public static let error: Self = .init(type: .error)
132+
133+
enum ResponseType: String, Codable {
134+
case connectionAck = "connection_ack"
135+
case next
136+
case complete
137+
case error
79138
}
80139
}
81140

0 commit comments

Comments
 (0)