Skip to content

Commit 66b2570

Browse files
Merge pull request #7 from GraphQLSwift/feat/public-request-response
Makes all request & response types and fields public
2 parents 4342aaa + 31a2761 commit 66b2570

File tree

6 files changed

+225
-79
lines changed

6 files changed

+225
-79
lines changed

Sources/GraphQLWS/Client.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ public class Client<InitPayload: Equatable & Codable> {
8484
return
8585
}
8686
try await self.onComplete(completeResponse, self)
87-
case .unknown:
87+
default:
8888
try await self.error(.invalidType())
8989
}
9090
}

Sources/GraphQLWS/GraphQLWSError.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,14 +60,14 @@ struct GraphQLWSError: 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
}

Sources/GraphQLWS/Requests.swift

Lines changed: 102 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,124 @@
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
2110
public struct ConnectionInitRequest<InitPayload: Codable & Equatable>: Equatable, JsonEncodable {
22-
var type = RequestMessageType.GQL_CONNECTION_INIT
23-
let payload: InitPayload
11+
public let type: RequestMessageType = .GQL_CONNECTION_INIT
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) != .GQL_CONNECTION_INIT {
21+
throw DecodingError.dataCorrupted(.init(
22+
codingPath: decoder.codingPath,
23+
debugDescription: "type must be `\(RequestMessageType.GQL_CONNECTION_INIT.type)`"
24+
))
25+
}
26+
payload = try container.decode(InitPayload.self, forKey: .payload)
27+
}
2428
}
2529

2630
/// A websocket `start` request from the client to the server
27-
struct StartRequest: Equatable, JsonEncodable {
28-
var type = RequestMessageType.GQL_START
29-
let payload: GraphQLRequest
30-
let id: String
31+
public struct StartRequest: Equatable, JsonEncodable {
32+
public let type: RequestMessageType = .GQL_START
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) != .GQL_START {
44+
throw DecodingError.dataCorrupted(.init(
45+
codingPath: decoder.codingPath,
46+
debugDescription: "type must be `\(RequestMessageType.GQL_START.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 `stop` request from the client to the server
34-
struct StopRequest: Equatable, JsonEncodable {
35-
var type = RequestMessageType.GQL_STOP
36-
let id: String
55+
public struct StopRequest: Equatable, JsonEncodable {
56+
public let type: RequestMessageType = .GQL_STOP
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) != .GQL_CONNECTION_TERMINATE {
66+
throw DecodingError.dataCorrupted(.init(
67+
codingPath: decoder.codingPath,
68+
debugDescription: "type must be `\(RequestMessageType.GQL_STOP.type)`"
69+
))
70+
}
71+
id = try container.decode(String.self, forKey: .id)
72+
}
3773
}
3874

3975
/// A websocket `connection_terminate` request from the client to the server
40-
struct ConnectionTerminateRequest: Equatable, JsonEncodable {
41-
var type = RequestMessageType.GQL_CONNECTION_TERMINATE
76+
public struct ConnectionTerminateRequest: Equatable, JsonEncodable {
77+
public let type: RequestMessageType = .GQL_CONNECTION_TERMINATE
78+
79+
public init() {}
80+
81+
public init(from decoder: any Decoder) throws {
82+
let container = try decoder.container(keyedBy: Self.CodingKeys.self)
83+
if try container.decode(RequestMessageType.self, forKey: .type) != .GQL_CONNECTION_TERMINATE {
84+
throw DecodingError.dataCorrupted(.init(
85+
codingPath: decoder.codingPath,
86+
debugDescription: "type must be `\(RequestMessageType.GQL_CONNECTION_TERMINATE.type)`"
87+
))
88+
}
89+
}
4290
}
4391

4492
/// The supported websocket request message types from the client to the server
45-
enum RequestMessageType: String, Codable {
46-
case GQL_CONNECTION_INIT = "connection_init"
47-
case GQL_START = "start"
48-
case GQL_STOP = "stop"
49-
case GQL_CONNECTION_TERMINATE = "connection_terminate"
50-
case unknown
51-
52-
init(from decoder: Decoder) throws {
53-
guard let value = try? decoder.singleValueContainer().decode(String.self) else {
54-
self = .unknown
55-
return
56-
}
57-
self = RequestMessageType(rawValue: value) ?? .unknown
93+
public struct RequestMessageType: Equatable, Codable, Sendable {
94+
// This is implemented as a struct with only public static properties, backed by an internal enum
95+
// in order to grow the list of accepted response types in a non-breaking way.
96+
97+
let type: RequestType
98+
99+
init(type: RequestType) {
100+
self.type = type
101+
}
102+
103+
public init(from decoder: any Decoder) throws {
104+
let container = try decoder.singleValueContainer()
105+
type = try container.decode(RequestType.self)
106+
}
107+
108+
public func encode(to encoder: any Encoder) throws {
109+
var container = encoder.singleValueContainer()
110+
try container.encode(type)
111+
}
112+
113+
public static let GQL_CONNECTION_INIT: Self = .init(type: .GQL_CONNECTION_INIT)
114+
public static let GQL_START: Self = .init(type: .GQL_START)
115+
public static let GQL_STOP: Self = .init(type: .GQL_STOP)
116+
public static let GQL_CONNECTION_TERMINATE: Self = .init(type: .GQL_CONNECTION_TERMINATE)
117+
118+
enum RequestType: String, Codable {
119+
case GQL_CONNECTION_INIT = "connection_init"
120+
case GQL_START = "start"
121+
case GQL_STOP = "stop"
122+
case GQL_CONNECTION_TERMINATE = "connection_terminate"
58123
}
59124
}

0 commit comments

Comments
 (0)