Skip to content

Commit 56a5cab

Browse files
feat: Improve formatting error reporting
1 parent 76fb126 commit 56a5cab

5 files changed

Lines changed: 126 additions & 38 deletions

File tree

Sources/GraphQLWS/Client.swift

Lines changed: 37 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -68,60 +68,77 @@ public actor Client<InitPayload: Equatable & Codable> {
6868

6969
switch response.type {
7070
case .GQL_CONNECTION_ERROR:
71-
guard
72-
let connectionErrorResponse = try? decoder.decode(
71+
let connectionErrorResponse: ConnectionErrorResponse
72+
do {
73+
connectionErrorResponse = try decoder.decode(
7374
ConnectionErrorResponse.self,
7475
from: message
7576
)
76-
else {
77+
} catch {
7778
try await messenger.error(
78-
.invalidResponseFormat(messageType: .GQL_CONNECTION_ERROR)
79+
.invalidResponseFormat(messageType: .GQL_CONNECTION_ERROR, error: error)
7980
)
8081
return
8182
}
8283
try await onConnectionError(connectionErrorResponse, self)
8384
case .GQL_CONNECTION_ACK:
84-
guard
85-
let connectionAckResponse = try? decoder.decode(
85+
let connectionAckResponse: ConnectionAckResponse
86+
do {
87+
connectionAckResponse = try decoder.decode(
8688
ConnectionAckResponse.self,
8789
from: message
8890
)
89-
else {
91+
} catch {
9092
try await messenger.error(
91-
.invalidResponseFormat(messageType: .GQL_CONNECTION_ERROR)
93+
.invalidResponseFormat(messageType: .GQL_CONNECTION_ERROR, error: error)
9294
)
9395
return
9496
}
9597
try await onConnectionAck(connectionAckResponse, self)
9698
case .GQL_CONNECTION_KEEP_ALIVE:
97-
guard
98-
let connectionKeepAliveResponse = try? decoder.decode(
99+
let connectionKeepAliveResponse: ConnectionKeepAliveResponse
100+
do {
101+
connectionKeepAliveResponse = try decoder.decode(
99102
ConnectionKeepAliveResponse.self,
100103
from: message
101104
)
102-
else {
105+
} catch {
103106
try await messenger.error(
104-
.invalidResponseFormat(messageType: .GQL_CONNECTION_KEEP_ALIVE)
107+
.invalidResponseFormat(messageType: .GQL_CONNECTION_KEEP_ALIVE, error: error)
105108
)
106109
return
107110
}
108111
try await onConnectionKeepAlive(connectionKeepAliveResponse, self)
109112
case .GQL_DATA:
110-
guard let nextResponse = try? decoder.decode(DataResponse.self, from: message) else {
111-
try await messenger.error(.invalidResponseFormat(messageType: .GQL_DATA))
113+
let dataResponse: DataResponse
114+
do {
115+
dataResponse = try decoder.decode(DataResponse.self, from: message)
116+
} catch {
117+
try await messenger.error(
118+
.invalidResponseFormat(messageType: .GQL_DATA, error: error)
119+
)
112120
return
113121
}
114-
try await onData(nextResponse, self)
122+
try await onData(dataResponse, self)
115123
case .GQL_ERROR:
116-
guard let errorResponse = try? decoder.decode(ErrorResponse.self, from: message) else {
117-
try await messenger.error(.invalidResponseFormat(messageType: .GQL_ERROR))
124+
let errorResponse: ErrorResponse
125+
do {
126+
errorResponse = try decoder.decode(ErrorResponse.self, from: message)
127+
} catch {
128+
try await messenger.error(
129+
.invalidResponseFormat(messageType: .GQL_ERROR, error: error)
130+
)
118131
return
119132
}
120133
try await onError(errorResponse, self)
121134
case .GQL_COMPLETE:
122-
guard let completeResponse = try? decoder.decode(CompleteResponse.self, from: message)
123-
else {
124-
try await messenger.error(.invalidResponseFormat(messageType: .GQL_COMPLETE))
135+
let completeResponse: CompleteResponse
136+
do {
137+
completeResponse = try decoder.decode(CompleteResponse.self, from: message)
138+
} catch {
139+
try await messenger.error(
140+
.invalidResponseFormat(messageType: .GQL_COMPLETE, error: error)
141+
)
125142
return
126143
}
127144
try await onComplete(completeResponse, self)

Sources/GraphQLWS/GraphQLWSError.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,16 +58,16 @@ struct GraphQLWSError: Error {
5858
)
5959
}
6060

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

68-
static func invalidResponseFormat(messageType: ResponseMessageType) -> Self {
68+
static func invalidResponseFormat(messageType: ResponseMessageType, error: Error) -> Self {
6969
return self.init(
70-
"Response message doesn't match '\(messageType.type.rawValue)' JSON format",
70+
"Response message doesn't match '\(messageType.type.rawValue)' JSON format: \(error)",
7171
code: .invalidResponseFormat
7272
)
7373
}

Sources/GraphQLWS/Requests.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,7 @@ public struct StopRequest: Equatable, Codable {
6767

6868
public init(from decoder: any Decoder) throws {
6969
let container = try decoder.container(keyedBy: Self.CodingKeys.self)
70-
if try container.decode(RequestMessageType.self, forKey: .type) != .GQL_CONNECTION_TERMINATE
71-
{
70+
if try container.decode(RequestMessageType.self, forKey: .type) != .GQL_STOP {
7271
throw DecodingError.dataCorrupted(
7372
.init(
7473
codingPath: decoder.codingPath,

Sources/GraphQLWS/Server.swift

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -78,37 +78,51 @@ where
7878
// handle incoming message
7979
switch request.type {
8080
case .GQL_CONNECTION_INIT:
81-
guard
82-
let connectionInitRequest = try? decoder.decode(
81+
let connectionInitRequest: ConnectionInitRequest<InitPayload>
82+
do {
83+
connectionInitRequest = try decoder.decode(
8384
ConnectionInitRequest<InitPayload>.self,
8485
from: message
8586
)
86-
else {
87-
try await messenger.error(.invalidRequestFormat(messageType: .GQL_CONNECTION_INIT))
87+
} catch {
88+
try await messenger.error(
89+
.invalidRequestFormat(messageType: .GQL_CONNECTION_INIT, error: error)
90+
)
8891
return
8992
}
9093
try await onConnectionInit(connectionInitRequest, messenger)
9194
case .GQL_START:
92-
guard let startRequest = try? decoder.decode(StartRequest.self, from: message) else {
93-
try await messenger.error(.invalidRequestFormat(messageType: .GQL_START))
95+
let startRequest: StartRequest
96+
do {
97+
startRequest = try decoder.decode(StartRequest.self, from: message)
98+
} catch {
99+
try await messenger.error(
100+
.invalidRequestFormat(messageType: .GQL_START, error: error)
101+
)
94102
return
95103
}
96104
try await onStart(startRequest, messenger)
97105
case .GQL_STOP:
98-
guard let stopRequest = try? decoder.decode(StopRequest.self, from: message) else {
99-
try await messenger.error(.invalidRequestFormat(messageType: .GQL_STOP))
106+
let stopRequest: StopRequest
107+
do {
108+
stopRequest = try decoder.decode(StopRequest.self, from: message)
109+
} catch {
110+
try await messenger.error(
111+
.invalidRequestFormat(messageType: .GQL_STOP, error: error)
112+
)
100113
return
101114
}
102115
try await onStop(stopRequest)
103116
case .GQL_CONNECTION_TERMINATE:
104-
guard
105-
let connectionTerminateRequest = try? decoder.decode(
117+
let connectionTerminateRequest: ConnectionTerminateRequest
118+
do {
119+
connectionTerminateRequest = try decoder.decode(
106120
ConnectionTerminateRequest.self,
107121
from: message
108122
)
109-
else {
123+
} catch {
110124
try await messenger.error(
111-
.invalidRequestFormat(messageType: .GQL_CONNECTION_TERMINATE)
125+
.invalidRequestFormat(messageType: .GQL_CONNECTION_TERMINATE, error: error)
112126
)
113127
return
114128
}

Tests/GraphQLWSTests/GraphQLWSTests.swift

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,64 @@ struct GraphqlTransportWSTests {
259259
)
260260
}
261261

262+
/// Tests malformed requests include decoder details in the transport error
263+
@Test func malformedRequestIncludesDecodingDetails() async throws {
264+
let api = TestAPI()
265+
let context = TestContext()
266+
let server = Server<TokenInitPayload, Void, AsyncThrowingStream<GraphQLResult, Error>>(
267+
messenger: serverMessenger,
268+
onInit: { _ in },
269+
onExecute: { graphQLRequest, _ in
270+
try await api.execute(
271+
request: graphQLRequest.query,
272+
context: context
273+
)
274+
},
275+
onSubscribe: { graphQLRequest, _ in
276+
try await api.subscribe(
277+
request: graphQLRequest.query,
278+
context: context
279+
).get()
280+
}
281+
)
282+
let (incoming, continuation) = AsyncThrowingStream<Data, any Error>.makeStream()
283+
284+
continuation.yield(Data(#"{"type":"stop"}"#.utf8))
285+
continuation.finish()
286+
287+
try await server.listen(to: incoming)
288+
289+
let error = await #expect(throws: TestMessengerError.self) {
290+
for try await _ in serverMessenger.stream {}
291+
}
292+
#expect(error?.code == 4413)
293+
#expect(error?.message.contains("Request message doesn't match 'stop' JSON format") == true)
294+
#expect(error?.message.contains("keyNotFound") == true)
295+
#expect(error?.message.contains(#""id""#) == true)
296+
}
297+
298+
/// Tests malformed responses include decoder details in the transport error
299+
@Test func malformedResponseIncludesDecodingDetails() async throws {
300+
let messenger = TestMessenger()
301+
let client = Client<TokenInitPayload>(messenger: messenger)
302+
let (incoming, continuation) = AsyncThrowingStream<Data, any Error>.makeStream()
303+
304+
continuation.yield(Data(#"{"type":"data"}"#.utf8))
305+
continuation.finish()
306+
307+
try await client.listen(to: incoming)
308+
309+
let error = await #expect(throws: TestMessengerError.self) {
310+
for try await _ in messenger.stream {}
311+
}
312+
#expect(error?.code == 4414)
313+
#expect(
314+
error?.message.contains("Response message doesn't match 'data' JSON format") == true
315+
)
316+
#expect(error?.message.contains("keyNotFound") == true)
317+
#expect(error?.message.contains(#""id""#) == true)
318+
}
319+
262320
enum TestError: Error {
263321
case couldBeAnything
264322
}

0 commit comments

Comments
 (0)