Add more errors for the resource object type property

This commit is contained in:
Mathew Polzin
2019-11-12 18:34:34 -08:00
parent 8c3a82ec23
commit 54551617b4
7 changed files with 193 additions and 20 deletions
@@ -40,14 +40,12 @@ public enum DocumentDecodingError: Swift.Error, Equatable {
private enum Location: Equatable {
case data
case other
init(_ context: DecodingError.Context) {
if context.codingPath.contains(where: { $0.stringValue == "data" }) {
self = .data
} else {
self = .other
init?(_ context: DecodingError.Context) {
guard context.codingPath.contains(where: { $0.stringValue == "data" }) else {
return nil
}
self = .data
}
}
}
@@ -414,7 +414,13 @@ public extension ResourceObject {
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: ResourceObjectCodingKeys.self)
let type = try container.decode(String.self, forKey: .type)
let type: String
do {
type = try container.decode(String.self, forKey: .type)
} catch let error as DecodingError {
throw ResourceObjectDecodingError(error)
?? error
}
guard ResourceObject.jsonType == type else {
throw ResourceObjectDecodingError(
@@ -102,13 +102,18 @@ public struct ResourceObjectDecodingError: Swift.Error, Equatable {
extension ResourceObjectDecodingError: CustomStringConvertible {
public var description: String {
switch cause {
case .keyNotFound where subjectName == ResourceObjectDecodingError.entireObject:
return "\(location) object is required and missing."
case .keyNotFound where location == .type:
return "'type' (a.k.a. JSON:API type name) is required and missing."
case .keyNotFound:
if subjectName == ResourceObjectDecodingError.entireObject {
return "\(location) object is required and missing."
}
return "'\(subjectName)' \(location.singular) is required and missing."
case .valueNotFound where location == .type:
return "'type' (a.k.a. JSON:API type name) is not nullable but null was found."
case .valueNotFound:
return "'\(subjectName)' \(location.singular) is not nullable but null."
return "'\(subjectName)' \(location.singular) is not nullable but null was found."
case .typeMismatch(expectedTypeName: let expected) where location == .type:
return "'type' (a.k.a. the JSON:API type name) is not a \(expected) as expected."
case .typeMismatch(expectedTypeName: let expected):
return "'\(subjectName)' \(location.singular) is not a \(expected) as expected."
case .jsonTypeMismatch(expectedType: let expected, foundType: let found) where location == .type:
@@ -11,7 +11,6 @@ import JSONAPITesting
final class ResourceObjectCompareTests: XCTestCase {
func test_same() {
print(test1.compare(to: test1).differences)
XCTAssertTrue(test1.compare(to: test1).differences.isEmpty)
XCTAssertTrue(test2.compare(to: test2).differences.isEmpty)
}
@@ -68,7 +68,7 @@ final class ResourceObjectDecodingErrorTests: XCTestCase {
XCTAssertEqual(
(error as? ResourceObjectDecodingError)?.description,
"'required' relationship is not nullable but null."
"'required' relationship is not nullable but null was found."
)
}
}
@@ -89,7 +89,7 @@ final class ResourceObjectDecodingErrorTests: XCTestCase {
XCTAssertEqual(
(error as? ResourceObjectDecodingError)?.description,
"'required' relationship is not nullable but null."
"'required' relationship is not nullable but null was found."
)
}
}
@@ -99,7 +99,6 @@ final class ResourceObjectDecodingErrorTests: XCTestCase {
TestEntity.self,
from: entity_relationship_is_wrong_type
)) { error in
print(error)
XCTAssertEqual(
error as? ResourceObjectDecodingError,
ResourceObjectDecodingError(
@@ -218,7 +217,7 @@ extension ResourceObjectDecodingErrorTests {
XCTAssertEqual(
(error as? ResourceObjectDecodingError)?.description,
"'required' attribute is not nullable but null."
"'required' attribute is not nullable but null was found."
)
}
}
@@ -285,11 +284,44 @@ extension ResourceObjectDecodingErrorTests {
)
}
}
func test_transformed_attribute() {
XCTAssertThrowsError(try testDecoder.decode(
TestEntity2.self,
from: entity_attribute_is_wrong_type4
)) { error in
XCTAssertEqual(
error as? ResourceObjectDecodingError,
ResourceObjectDecodingError(
subjectName: "transformed",
cause: .typeMismatch(expectedTypeName: String(describing: Int.self)),
location: .attributes
)
)
XCTAssertEqual(
(error as? ResourceObjectDecodingError)?.description,
"'transformed' attribute is not a Int as expected."
)
}
}
func test_transformed_attribute2() {
XCTAssertThrowsError(try testDecoder.decode(
TestEntity2.self,
from: entity_attribute_always_fails
)) { error in
XCTAssertEqual(
String(describing: error),
"Error: Always Fails"
)
}
}
}
// MARK: - JSON:API Type
extension ResourceObjectDecodingErrorTests {
func test_wrongType() {
func test_wrongJSONAPIType() {
XCTAssertThrowsError(try testDecoder.decode(
TestEntity2.self,
from: entity_is_wrong_type
@@ -309,6 +341,69 @@ extension ResourceObjectDecodingErrorTests {
)
}
}
func test_wrongDecodedType() {
XCTAssertThrowsError(try testDecoder.decode(
TestEntity2.self,
from: entity_type_is_wrong_type
)) { error in
XCTAssertEqual(
error as? ResourceObjectDecodingError,
ResourceObjectDecodingError(
subjectName: "type",
cause: .typeMismatch(expectedTypeName: String(describing: String.self)),
location: .type
)
)
XCTAssertEqual(
(error as? ResourceObjectDecodingError)?.description,
#"'type' (a.k.a. the JSON:API type name) is not a String as expected."#
)
}
}
func test_type_missing() {
XCTAssertThrowsError(try testDecoder.decode(
TestEntity2.self,
from: entity_type_is_missing
)) { error in
XCTAssertEqual(
error as? ResourceObjectDecodingError,
ResourceObjectDecodingError(
subjectName: "type",
cause: .keyNotFound,
location: .type
)
)
XCTAssertEqual(
(error as? ResourceObjectDecodingError)?.description,
#"'type' (a.k.a. JSON:API type name) is required and missing."#
)
}
}
func test_type_null() {
XCTAssertThrowsError(try testDecoder.decode(
TestEntity2.self,
from: entity_type_is_null
)) { error in
XCTAssertEqual(
error as? ResourceObjectDecodingError,
ResourceObjectDecodingError(
subjectName: "type",
cause: .valueNotFound,
location: .type
)
)
XCTAssertEqual(
(error as? ResourceObjectDecodingError)?.description,
#"'type' (a.k.a. JSON:API type name) is not nullable but null was found."#
)
}
}
}
// MARK: - Test Types
@@ -335,10 +430,30 @@ extension ResourceObjectDecodingErrorTests {
let required: Attribute<String>
let other: Attribute<Int>?
let yetAnother: Attribute<Bool?>?
let transformed: TransformedAttribute<Int, IntToString>?
let transformed2: TransformedAttribute<String, AlwaysFails>?
}
typealias Relationships = NoRelationships
}
typealias TestEntity2 = BasicEntity<TestEntityType2>
enum IntToString: Transformer {
static func transform(_ value: Int) throws -> String {
return "\(value)"
}
typealias From = Int
typealias To = String
}
enum AlwaysFails: Transformer {
static func transform(_ value: String) throws -> String {
throw Error()
}
struct Error: Swift.Error, CustomStringConvertible {
let description: String = "Error: Always Fails"
}
}
}
@@ -533,6 +533,35 @@ let entity_attribute_is_wrong_type3 = """
}
""".data(using: .utf8)!
let entity_attribute_is_wrong_type4 = """
{
"id": "1",
"type": "fourteenth_test_entities",
"attributes": {
"required": "hello",
"transformed": "world"
}
}
""".data(using: .utf8)!
let entity_attribute_always_fails = """
{
"id": "1",
"type": "fourteenth_test_entities",
"attributes": {
"required": "hello",
"transformed2": "world"
}
}
""".data(using: .utf8)!
let entity_attributes_entirely_missing = """
{
"id": "1",
"type": "fourteenth_test_entities"
}
""".data(using: .utf8)!
let entity_is_wrong_type = """
{
"id": "1",
@@ -544,10 +573,32 @@ let entity_is_wrong_type = """
}
""".data(using: .utf8)!
let entity_attributes_entirely_missing = """
let entity_type_is_wrong_type = """
{
"id": "1",
"type": "fourteenth_test_entities"
"type": 10,
"attributes": {
"required": "hello"
}
}
""".data(using: .utf8)!
let entity_type_is_missing = """
{
"id": "1",
"attributes": {
"required": "hello"
}
}
""".data(using: .utf8)!
let entity_type_is_null = """
{
"id": "1",
"type": null,
"attributes": {
"required": "hello"
}
}
""".data(using: .utf8)!
@@ -17,7 +17,6 @@ class SparseFieldEncoderTests: XCTestCase {
do {
let _ = try encoder.encode(Wrapper())
} catch let err as Wrapper.OuterFail.FailError {
print(err.path)
XCTAssertEqual(err.path.first as? Wrapper.OuterFail.CodingKeys, Wrapper.OuterFail.CodingKeys.inner)
} catch {
XCTFail("received unexpected error during test")