Skip to content

Commit ea10e2a

Browse files
Merge pull request #756 from apollographql/enhance/errorz
Better Handling of `HTTPResponseError` with json + Tests
2 parents 5501dcc + 73d1477 commit ea10e2a

3 files changed

Lines changed: 147 additions & 4 deletions

File tree

Apollo.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
54DDB0921EA045870009DD99 /* InMemoryNormalizedCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54DDB0911EA045870009DD99 /* InMemoryNormalizedCache.swift */; };
1111
5AC6CA4322AAF7B200B7C94D /* GraphQLHTTPMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AC6CA4222AAF7B200B7C94D /* GraphQLHTTPMethod.swift */; };
1212
9B708AAD2305884500604A11 /* ApolloClientProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B708AAC2305884500604A11 /* ApolloClientProtocol.swift */; };
13+
9B78C71E2326E86E000C8C32 /* ErrorGenerationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B78C71B2326E859000C8C32 /* ErrorGenerationTests.swift */; };
1314
9B95EDC022CAA0B000702BB2 /* GETTransformerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B95EDBF22CAA0AF00702BB2 /* GETTransformerTests.swift */; };
1415
9BA1244A22D8A8EA00BF1D24 /* JSONSerialziation+Sorting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BA1244922D8A8EA00BF1D24 /* JSONSerialziation+Sorting.swift */; };
1516
9BA1245E22DE116B00BF1D24 /* Result+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BA1245D22DE116B00BF1D24 /* Result+Helpers.swift */; };
@@ -264,6 +265,7 @@
264265
90690D2422433C8000FC2E54 /* Apollo-Target-PerformanceTests.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Apollo-Target-PerformanceTests.xcconfig"; sourceTree = "<group>"; };
265266
90690D2522433CAF00FC2E54 /* Apollo-Target-TestSupport.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Apollo-Target-TestSupport.xcconfig"; sourceTree = "<group>"; };
266267
9B708AAC2305884500604A11 /* ApolloClientProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApolloClientProtocol.swift; sourceTree = "<group>"; };
268+
9B78C71B2326E859000C8C32 /* ErrorGenerationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorGenerationTests.swift; sourceTree = "<group>"; };
267269
9B8D864E22E7A846001F6D50 /* RepoURL.graphql */ = {isa = PBXFileReference; lastKnownFileType = text; path = RepoURL.graphql; sourceTree = "<group>"; };
268270
9B95EDBF22CAA0AF00702BB2 /* GETTransformerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GETTransformerTests.swift; sourceTree = "<group>"; };
269271
9BA1244922D8A8EA00BF1D24 /* JSONSerialziation+Sorting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "JSONSerialziation+Sorting.swift"; sourceTree = "<group>"; };
@@ -653,6 +655,7 @@
653655
9F438D0B1E6C494C007BDC1A /* BatchedLoadTests.swift */,
654656
9FC9A9C71E2EFE6E0023C4D5 /* CacheKeyForFieldTests.swift */,
655657
9FADC8531E6B86D900C677E6 /* DataLoaderTests.swift */,
658+
9B78C71B2326E859000C8C32 /* ErrorGenerationTests.swift */,
656659
9F8622F91EC2117C00C38162 /* FragmentConstructionAndConversionTests.swift */,
657660
9B95EDBF22CAA0AF00702BB2 /* GETTransformerTests.swift */,
658661
9BF1A94C22CA54F9005292C2 /* HTTPTransportTests.swift */,
@@ -1236,6 +1239,7 @@
12361239
isa = PBXSourcesBuildPhase;
12371240
buildActionMask = 2147483647;
12381241
files = (
1242+
9B78C71E2326E86E000C8C32 /* ErrorGenerationTests.swift in Sources */,
12391243
9FC9A9C81E2EFE6E0023C4D5 /* CacheKeyForFieldTests.swift in Sources */,
12401244
9F91CF8F1F6C0DB2008DD0BE /* MutatingResultsTests.swift in Sources */,
12411245
9F19D8461EED8D3B00C57247 /* ResultOrPromiseTests.swift in Sources */,

Sources/Apollo/GraphQLHTTPResponseError.swift

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,26 +21,49 @@ public struct GraphQLHTTPResponseError: Error, LocalizedError {
2121
/// Information about the response as provided by the server.
2222
public let response: HTTPURLResponse
2323
public let kind: ErrorKind
24+
private let serializationFormat = JSONSerializationFormat.self
2425

25-
public init(body: Data? = nil, response: HTTPURLResponse, kind: ErrorKind) {
26+
public init(body: Data? = nil,
27+
response: HTTPURLResponse,
28+
kind: ErrorKind) {
2629
self.body = body
2730
self.response = response
2831
self.kind = kind
2932
}
3033

34+
/// Any graphQL errors that could be parsed from the response, or nil if none could be parsed.
35+
public var graphQLErrors: [GraphQLError]? {
36+
guard
37+
let data = self.body,
38+
let json = try? self.serializationFormat.deserialize(data: data) as? JSONObject,
39+
let errorArray = json["errors"] as? [JSONObject] else {
40+
return nil
41+
}
42+
43+
let parsedErrors = errorArray.compactMap { GraphQLError($0) }
44+
return parsedErrors
45+
}
46+
3147
public var bodyDescription: String {
3248
guard let body = body else {
33-
return "Empty response body"
49+
return "[Empty response body]"
3450
}
3551

3652
guard let description = String(data: body, encoding: response.textEncoding ?? .utf8) else {
37-
return "Unreadable response body"
53+
return "[Unreadable response body]"
3854
}
3955

4056
return description
4157
}
4258

4359
public var errorDescription: String? {
44-
return "\(kind.description) (\(response.statusCode) \(response.statusCodeDescription)): \(bodyDescription)"
60+
if let errorArray = self.graphQLErrors {
61+
let descriptions = errorArray.map { $0.localizedDescription }
62+
let description = descriptions.joined(separator: "\n")
63+
64+
return "\(kind.description): \(description)"
65+
} else {
66+
return "\(kind.description) (\(response.statusCode) \(response.statusCodeDescription)): \(bodyDescription)"
67+
}
4568
}
4669
}
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
//
2+
// ErrorGenerationTests.swift
3+
// Apollo
4+
//
5+
// Created by Ellen Shapiro on 9/9/19.
6+
// Copyright © 2019 Apollo GraphQL. All rights reserved.
7+
//
8+
9+
import Apollo
10+
import XCTest
11+
12+
class ErrorGenerationTests: XCTestCase {
13+
14+
func testLocalizedStringFromErrorResponse() {
15+
let json = """
16+
{
17+
"errors": [
18+
{
19+
"message": "Invalid client auth token.",
20+
"extensions": {
21+
"code": "INTERNAL_SERVER_ERROR"
22+
}
23+
}
24+
]
25+
}
26+
"""
27+
28+
let response = HTTPURLResponse(url: URL(string: "https://www.fake.com")!,
29+
statusCode: 403,
30+
httpVersion: nil,
31+
headerFields: nil)!
32+
33+
guard let data = json.data(using: .utf8) else {
34+
XCTFail("Couldn't create json data")
35+
return
36+
}
37+
38+
let httpResponseError = GraphQLHTTPResponseError(body: data,
39+
response: response,
40+
kind: .errorResponse)
41+
XCTAssertEqual(httpResponseError.graphQLErrors?.count, 1)
42+
XCTAssertEqual(httpResponseError.localizedDescription, "Received error response: Invalid client auth token.")
43+
}
44+
45+
func testLocalizedStringFromErrorResponseWithMultipleErrors() {
46+
let json = """
47+
{
48+
"errors": [
49+
{
50+
"message": "Invalid client auth token.",
51+
"extensions": {
52+
"code": "INTERNAL_SERVER_ERROR"
53+
}
54+
},
55+
{
56+
"message": "Server is having a sad.",
57+
"extensions": {
58+
"code": "INTERNAL_SERVER_ERROR"
59+
}
60+
}
61+
]
62+
}
63+
"""
64+
65+
let response = HTTPURLResponse(url: URL(string: "https://www.fake.com")!,
66+
statusCode: 403,
67+
httpVersion: nil,
68+
headerFields: nil)!
69+
70+
guard let data = json.data(using: .utf8) else {
71+
XCTFail("Couldn't create json data")
72+
return
73+
}
74+
75+
let httpResponseError = GraphQLHTTPResponseError(body: data,
76+
response: response,
77+
kind: .errorResponse)
78+
XCTAssertEqual(httpResponseError.graphQLErrors?.count, 2)
79+
XCTAssertEqual(httpResponseError.localizedDescription, "Received error response: Invalid client auth token.\nServer is having a sad.")
80+
}
81+
82+
func testLocalizedStringFromPlaintextResponse() {
83+
let text = "The server is having a sad."
84+
85+
let response = HTTPURLResponse(url: URL(string: "https://www.fake.com")!,
86+
statusCode: 500,
87+
httpVersion: nil,
88+
headerFields: nil)!
89+
90+
guard let data = text.data(using: .utf8) else {
91+
XCTFail("Couldn't create text data")
92+
return
93+
}
94+
95+
let httpResponseError = GraphQLHTTPResponseError(body: data,
96+
response: response,
97+
kind: .errorResponse)
98+
99+
XCTAssertNil(httpResponseError.graphQLErrors)
100+
XCTAssertEqual(httpResponseError.localizedDescription, "Received error response (500 internal server error): The server is having a sad.")
101+
}
102+
103+
func testLocalizedStringFromNullDataResponse() {
104+
let response = HTTPURLResponse(url: URL(string: "https://www.fake.com")!,
105+
statusCode: 500,
106+
httpVersion: nil,
107+
headerFields: nil)!
108+
109+
let httpResponseError = GraphQLHTTPResponseError(body: nil,
110+
response: response,
111+
kind: .errorResponse)
112+
113+
XCTAssertNil(httpResponseError.graphQLErrors)
114+
XCTAssertEqual(httpResponseError.localizedDescription, "Received error response (500 internal server error): [Empty response body]")
115+
}
116+
}

0 commit comments

Comments
 (0)