Skip to content

Commit 1ec7913

Browse files
committed
instead of a slightly cryptic error about which include failed to parse, use ordinal numbers and add a total include count to the message.
1 parent f64ef95 commit 1ec7913

6 files changed

Lines changed: 510 additions & 26 deletions

File tree

Package.resolved

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Sources/JSONAPI/Document/Includes.swift

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,14 +76,15 @@ extension Includes: Decodable where I: Decodable {
7676
}
7777
}
7878
guard errors.count == error.individualTypeFailures.count else {
79-
throw IncludesDecodingError(error: error, idx: idx)
79+
throw IncludesDecodingError(error: error, idx: idx, totalIncludesCount: container.count ?? 0)
8080
}
8181
throw IncludesDecodingError(
8282
error: IncludeDecodingError(failures: errors),
83-
idx: idx
83+
idx: idx,
84+
totalIncludesCount: container.count ?? 0
8485
)
8586
} catch let error {
86-
throw IncludesDecodingError(error: error, idx: idx)
87+
throw IncludesDecodingError(error: error, idx: idx, totalIncludesCount: container.count ?? 0)
8788
}
8889
}
8990

@@ -208,7 +209,13 @@ extension Includes where I: _Poly11 {
208209
// MARK: - DecodingError
209210
public struct IncludesDecodingError: Swift.Error, Equatable {
210211
public let error: Swift.Error
212+
/// The zero-based index of the include that failed to decode.
211213
public let idx: Int
214+
/// The total count of includes in the document that failed to decode.
215+
///
216+
/// In other words, "of `totalIncludesCount` includes, the `(idx + 1)`th
217+
/// include failed to decode.
218+
public let totalIncludesCount: Int
212219

213220
public static func ==(lhs: Self, rhs: Self) -> Bool {
214221
return lhs.idx == rhs.idx
@@ -218,7 +225,25 @@ public struct IncludesDecodingError: Swift.Error, Equatable {
218225

219226
extension IncludesDecodingError: CustomStringConvertible {
220227
public var description: String {
221-
return "Include \(idx + 1) failed to parse: \(error)"
228+
let ordinalSuffix: String
229+
if (idx % 100) + 1 > 9 && (idx % 100) + 1 < 20 {
230+
// the teens
231+
ordinalSuffix = "th"
232+
} else {
233+
switch ((idx % 10) + 1) {
234+
case 1:
235+
ordinalSuffix = "st"
236+
case 2:
237+
ordinalSuffix = "nd"
238+
case 3:
239+
ordinalSuffix = "rd"
240+
default:
241+
ordinalSuffix = "th"
242+
}
243+
}
244+
let ordinalDescription = "\(idx + 1)\(ordinalSuffix)"
245+
246+
return "Out of \(totalIncludesCount) includes, the \(ordinalDescription) one failed to parse: \(error)"
222247
}
223248
}
224249

Tests/JSONAPITests/Document/DocumentDecodingErrorTests.swift

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ final class DocumentDecodingErrorTests: XCTestCase {
7979
}
8080

8181
func test_include_failure() {
82+
// test that if there is only one possible include, we just find out on one line what expecation failed.
8283
XCTAssertThrowsError(
8384
try testDecoder.decode(
8485
Document<SingleResourceBody<Article>, NoMetadata, NoLinks, Include1<Author>, NoAPIDescription, UnknownJSONAPIError>.self,
@@ -91,11 +92,12 @@ final class DocumentDecodingErrorTests: XCTestCase {
9192
return
9293
}
9394

94-
XCTAssertEqual(String(describing: error), #"Include 3 failed to parse: found JSON:API type "not_an_author" but expected "authors""#)
95+
XCTAssertEqual(String(describing: error), #"Out of 3 includes, the 3rd one failed to parse: found JSON:API type "not_an_author" but expected "authors""#)
9596
}
9697
}
9798

9899
func test_include_failure2() {
100+
// test that if there are two possiblie includes, we find out why each of them was not possible to decode.
99101
XCTAssertThrowsError(
100102
try testDecoder.decode(
101103
Document<SingleResourceBody<Article>, NoMetadata, NoLinks, Include2<Article, Author>, NoAPIDescription, UnknownJSONAPIError>.self,
@@ -108,15 +110,44 @@ final class DocumentDecodingErrorTests: XCTestCase {
108110
return
109111
}
110112

111-
XCTAssertEqual(String(describing: error),
112-
#"""
113-
Include 3 failed to parse:
114-
Could not have been Include Type 1 because:
115-
found JSON:API type "not_an_author" but expected "articles"
113+
XCTAssertEqual(
114+
String(describing: error),
115+
#"""
116+
Out of 3 includes, the 3rd one failed to parse:
117+
Could not have been Include Type 1 because:
118+
found JSON:API type "not_an_author" but expected "articles"
116119
117-
Could not have been Include Type 2 because:
118-
found JSON:API type "not_an_author" but expected "authors"
119-
"""#
120+
Could not have been Include Type 2 because:
121+
found JSON:API type "not_an_author" but expected "authors"
122+
"""#
123+
)
124+
}
125+
}
126+
127+
func test_include_failure3() {
128+
// test that if the failed include is at a different index, the other index is reported correctly.
129+
XCTAssertThrowsError(
130+
try testDecoder.decode(
131+
Document<SingleResourceBody<Article>, NoMetadata, NoLinks, Include2<Article, Author>, NoAPIDescription, UnknownJSONAPIError>.self,
132+
from: single_document_some_includes_wrong_type2
133+
)
134+
) { error in
135+
guard let docError = error as? DocumentDecodingError,
136+
case .includes = docError else {
137+
XCTFail("Expected primary resource document error. Got \(error)")
138+
return
139+
}
140+
141+
XCTAssertEqual(
142+
String(describing: error),
143+
#"""
144+
Out of 3 includes, the 2nd one failed to parse:
145+
Could not have been Include Type 1 because:
146+
found JSON:API type "not_an_author" but expected "articles"
147+
148+
Could not have been Include Type 2 because:
149+
found JSON:API type "not_an_author" but expected "authors"
150+
"""#
120151
)
121152
}
122153
}

Tests/JSONAPITests/Document/stubs/DocumentStubs.swift

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,37 @@ let single_document_some_includes_wrong_type = """
289289
}
290290
""".data(using: .utf8)!
291291

292+
let single_document_some_includes_wrong_type2 = """
293+
{
294+
"data": {
295+
"id": "1",
296+
"type": "articles",
297+
"relationships": {
298+
"author": {
299+
"data": {
300+
"type": "authors",
301+
"id": "33"
302+
}
303+
}
304+
}
305+
},
306+
"included": [
307+
{
308+
"id": "30",
309+
"type": "authors"
310+
},
311+
{
312+
"id": "31",
313+
"type": "not_an_author"
314+
},
315+
{
316+
"id": "33",
317+
"type": "authors"
318+
}
319+
]
320+
}
321+
""".data(using: .utf8)!
322+
292323
let single_document_some_includes_with_api_description = """
293324
{
294325
"data": {

Tests/JSONAPITests/Includes/IncludesDecodingErrorTests.swift

Lines changed: 57 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import JSONAPI
1010

1111
final class IncludesDecodingErrorTests: XCTestCase {
1212
func test_unexpectedIncludeType() {
13-
var error1: Error!
1413
XCTAssertThrowsError(try testDecoder.decode(Includes<Include2<TestEntity, TestEntity2>>.self, from: three_different_type_includes)) { (error: Error) -> Void in
1514
XCTAssertEqual(
1615
(error as? IncludesDecodingError)?.idx,
@@ -19,23 +18,69 @@ final class IncludesDecodingErrorTests: XCTestCase {
1918

2019
XCTAssertEqual(
2120
(error as? IncludesDecodingError).map(String.init(describing:)),
22-
"""
23-
Include 3 failed to parse: \nCould not have been Include Type 1 because:
24-
found JSON:API type "test_entity4" but expected "test_entity1"
21+
"""
22+
Out of 3 includes, the 3rd one failed to parse: \nCould not have been Include Type 1 because:
23+
found JSON:API type "test_entity4" but expected "test_entity1"
2524
26-
Could not have been Include Type 2 because:
27-
found JSON:API type "test_entity4" but expected "test_entity2"
28-
"""
25+
Could not have been Include Type 2 because:
26+
found JSON:API type "test_entity4" but expected "test_entity2"
27+
"""
2928
)
30-
31-
error1 = error
3229
}
3330

34-
// now test that we get the same error from a different test stub
31+
// now test that we get the same error with a different total include count from a different test stub
3532
XCTAssertThrowsError(try testDecoder.decode(Includes<Include2<TestEntity, TestEntity2>>.self, from: four_different_type_includes)) { (error2: Error) -> Void in
3633
XCTAssertEqual(
37-
error1 as? IncludesDecodingError,
38-
error2 as? IncludesDecodingError
34+
(error2 as? IncludesDecodingError).map(String.init(describing:)),
35+
"""
36+
Out of 4 includes, the 3rd one failed to parse: \nCould not have been Include Type 1 because:
37+
found JSON:API type "test_entity4" but expected "test_entity1"
38+
39+
Could not have been Include Type 2 because:
40+
found JSON:API type "test_entity4" but expected "test_entity2"
41+
"""
42+
)
43+
}
44+
45+
// and with six total includes
46+
XCTAssertThrowsError(try testDecoder.decode(Includes<Include2<TestEntity, TestEntity2>>.self, from: six_includes_one_bad_type)) { (error2: Error) -> Void in
47+
XCTAssertEqual(
48+
(error2 as? IncludesDecodingError).map(String.init(describing:)),
49+
"""
50+
Out of 6 includes, the 5th one failed to parse: \nCould not have been Include Type 1 because:
51+
found JSON:API type "test_entity4" but expected "test_entity1"
52+
53+
Could not have been Include Type 2 because:
54+
found JSON:API type "test_entity4" but expected "test_entity2"
55+
"""
56+
)
57+
}
58+
59+
// and with a number of total includes between 10 and 19
60+
XCTAssertThrowsError(try testDecoder.decode(Includes<Include2<TestEntity, TestEntity2>>.self, from: eleven_includes_one_bad_type)) { (error2: Error) -> Void in
61+
XCTAssertEqual(
62+
(error2 as? IncludesDecodingError).map(String.init(describing:)),
63+
"""
64+
Out of 11 includes, the 10th one failed to parse: \nCould not have been Include Type 1 because:
65+
found JSON:API type "test_entity4" but expected "test_entity1"
66+
67+
Could not have been Include Type 2 because:
68+
found JSON:API type "test_entity4" but expected "test_entity2"
69+
"""
70+
)
71+
}
72+
73+
// and finally with a larger number of total includes
74+
XCTAssertThrowsError(try testDecoder.decode(Includes<Include2<TestEntity, TestEntity2>>.self, from: twenty_two_includes_one_bad_type)) { (error2: Error) -> Void in
75+
XCTAssertEqual(
76+
(error2 as? IncludesDecodingError).map(String.init(describing:)),
77+
"""
78+
Out of 22 includes, the 21st one failed to parse: \nCould not have been Include Type 1 because:
79+
found JSON:API type "test_entity4" but expected "test_entity1"
80+
81+
Could not have been Include Type 2 because:
82+
found JSON:API type "test_entity4" but expected "test_entity2"
83+
"""
3984
)
4085
}
4186
}

0 commit comments

Comments
 (0)