Skip to content

Commit 33ee3fa

Browse files
committed
chore: add ResourceObjectWithOptionalDataInRelationships protocol
1 parent 9b6392a commit 33ee3fa

File tree

4 files changed

+61
-7
lines changed

4 files changed

+61
-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: 28 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,35 @@ extension RelationshipTests {
236236
data: to_many_relationship_with_meta_and_links)
237237
}
238238

239-
func test_ToManyRelationshipWithMetaNoData() {
239+
func test_ToManyRelationshipWithMetaNoDataOmittable() {
240+
TestEntityType1.canHaveNoDataInRelationships = true
241+
240242
let relationship = decoded(type: ToManyWithMeta.self,
241243
data: to_many_relationship_with_meta_no_data)
242244

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

@@ -285,12 +308,14 @@ extension RelationshipTests {
285308

286309
// MARK: - Test types
287310
extension RelationshipTests {
288-
enum TestEntityType1: ResourceObjectDescription {
311+
enum TestEntityType1: ResourceObjectDescription, ResourceObjectWithOptionalDataInRelationships {
289312
typealias Attributes = NoAttributes
290313

291314
typealias Relationships = NoRelationships
292315

293316
public static var jsonType: String { return "test_entity1" }
317+
318+
static var canHaveNoDataInRelationships: Bool = false
294319
}
295320

296321
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)