Skip to content

Commit 7b22017

Browse files
committed
chore: add ResourceObjectWithOptionalDataInRelationships protocol
1 parent 9b6392a commit 7b22017

File tree

4 files changed

+60
-7
lines changed

4 files changed

+60
-7
lines changed

Sources/JSONAPI/Resource/Relationship.swift

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ extension Optional: JSONAPIIdentifiable, OptionalRelatable, JSONTyped where Wrap
222222
}
223223

224224
// MARK: Codable
225-
private enum ResourceLinkageCodingKeys: String, CodingKey {
225+
enum ResourceLinkageCodingKeys: String, CodingKey {
226226
case data = "data"
227227
case meta = "meta"
228228
case links = "links"
@@ -401,9 +401,14 @@ extension ToManyRelationship: Codable {
401401
links = try container.decode(LinksType.self, forKey: .links)
402402
}
403403

404-
guard container.contains(.data) else {
405-
idsWithMeta = []
406-
return
404+
let hasData = container.contains(.data)
405+
var canHaveNoDataInRelationships: Bool = false
406+
if let relatableType = Relatable.self as? ResourceObjectWithOptionalDataInRelationships.Type {
407+
canHaveNoDataInRelationships = relatableType.canHaveNoDataInRelationships
408+
}
409+
guard hasData || !canHaveNoDataInRelationships else {
410+
idsWithMeta = []
411+
return
407412
}
408413

409414
var identifiers: UnkeyedDecodingContainer

Sources/JSONAPI/Resource/Resource Object/ResourceObject.swift

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,20 @@ public protocol ResourceObjectProxyDescription: JSONTyped {
5858
associatedtype Relationships: Equatable
5959
}
6060

61+
/// A flagging protocol for `ResourceObjectProxyDescription` objects.
62+
/// Indicates an object with varying behavior when it's being decoded from resources and the `data` key is missing.
63+
public protocol ResourceObjectWithOptionalDataInRelationships {
64+
/// A Boolean flag indicating that instances while decoding from relationships can be decoded without `data`
65+
/// key and have only links and meta information.
66+
///
67+
/// Default value: `false`.
68+
static var canHaveNoDataInRelationships: Bool { get }
69+
}
70+
71+
extension ResourceObjectWithOptionalDataInRelationships {
72+
public static var canHaveNoDataInRelationships: Bool { false }
73+
}
74+
6175
/// A `ResourceObjectDescription` describes a JSON API
6276
/// Resource Object. The Resource Object
6377
/// itself is encoded and decoded as an
@@ -244,6 +258,12 @@ public extension ResourceObject where EntityRawIdType: CreatableRawIdType {
244258
}
245259
}
246260

261+
// Conformance to the protocol so we can access the `canHaveNoDataInRelationships` flag from
262+
// `ToManyRelationship` in type-erasured `Relatable`.
263+
extension ResourceObject: ResourceObjectWithOptionalDataInRelationships where Description: ResourceObjectWithOptionalDataInRelationships {
264+
public static var canHaveNoDataInRelationships: Bool { Description.canHaveNoDataInRelationships }
265+
}
266+
247267
// MARK: - Attribute Access
248268
public extension ResourceObjectProxy {
249269
// MARK: Dynaminc Member Keypath Lookup

Tests/JSONAPITests/Relationships/RelationshipTests.swift

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
//
77

88
import XCTest
9-
import JSONAPI
9+
@testable import JSONAPI
1010

1111
class RelationshipTests: XCTestCase {
1212

@@ -236,12 +236,34 @@ extension RelationshipTests {
236236
data: to_many_relationship_with_meta_and_links)
237237
}
238238

239-
func test_ToManyRelationshipWithMetaNoData() {
239+
func test_ToManyRelationshipWithMetaNoDataOmittable() {
240+
TestEntityType1.canOmmitDataInRelationships = true
240241
let relationship = decoded(type: ToManyWithMeta.self,
241242
data: to_many_relationship_with_meta_no_data)
242243

243244
XCTAssertEqual(relationship.ids, [])
244245
XCTAssertEqual(relationship.meta.a, "hello")
246+
247+
TestEntityType1.canOmmitDataInRelationships = false
248+
}
249+
250+
func test_ToManyRelationshipWithMetaNoDataNotOmittable() {
251+
TestEntityType1.canOmmitDataInRelationships = false
252+
253+
do {
254+
_ = try decodedThrows(type: ToManyWithMeta.self,
255+
data: to_many_relationship_with_meta_no_data)
256+
XCTFail("Expected decoding to fail.")
257+
} catch let error as DecodingError {
258+
switch error {
259+
case .keyNotFound(ResourceLinkageCodingKeys.data, _):
260+
break
261+
default:
262+
XCTFail("Expected error to be DecodingError.keyNotFound(.data), but got \(error)")
263+
}
264+
} catch {
265+
XCTFail("Expected to have DecodingError.keyNotFound(.data), but got \(error)")
266+
}
245267
}
246268
}
247269

@@ -285,12 +307,14 @@ extension RelationshipTests {
285307

286308
// MARK: - Test types
287309
extension RelationshipTests {
288-
enum TestEntityType1: ResourceObjectDescription {
310+
enum TestEntityType1: ResourceObjectDescription, ResourceObjectWithOptionalDataInRelationships {
289311
typealias Attributes = NoAttributes
290312

291313
typealias Relationships = NoRelationships
292314

293315
public static var jsonType: String { return "test_entity1" }
316+
317+
static var canOmmitDataInRelationships: Bool = false
294318
}
295319

296320
typealias TestEntity1 = BasicEntity<TestEntityType1>

Tests/JSONAPITests/Test Helpers/EncodeDecode.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ func decoded<T: Decodable>(type: T.Type, data: Data) -> T {
2424
return try! testDecoder.decode(T.self, from: data)
2525
}
2626

27+
func decodedThrows<T: Decodable>(type: T.Type, data: Data) throws -> T {
28+
return try testDecoder.decode(T.self, from: data)
29+
}
30+
2731
func encoded<T: Encodable>(value: T) -> Data {
2832
return try! testEncoder.encode(value)
2933
}

0 commit comments

Comments
 (0)