|
1 | 1 | import Foundation |
2 | 2 | import GraphQL |
3 | 3 |
|
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 | 4 | /// 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 |
18 | 7 | } |
19 | 8 |
|
20 | 9 | /// A websocket `connection_init` request from the client to the server |
21 | 10 | 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 | + } |
24 | 28 | } |
25 | 29 |
|
26 | 30 | /// 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 | + } |
31 | 52 | } |
32 | 53 |
|
33 | 54 | /// 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 | + } |
37 | 73 | } |
38 | 74 |
|
39 | 75 | /// 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 | + } |
42 | 90 | } |
43 | 91 |
|
44 | 92 | /// 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" |
58 | 123 | } |
59 | 124 | } |
0 commit comments