Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: allow absent data in ToManyRelationship #110

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions Sources/JSONAPI/Resource/Relationship.swift
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,18 @@ extension ToManyRelationship: Codable {
links = try container.decode(LinksType.self, forKey: .links)
}

let hasData = container.contains(.data)
let canHaveNoDataInRelationships: Bool
if let relatableType = Relatable.self as? ResourceObjectWithOptionalDataInRelationships.Type {
canHaveNoDataInRelationships = relatableType.canHaveNoDataInRelationships
} else {
canHaveNoDataInRelationships = false
}
guard hasData || !canHaveNoDataInRelationships else {
idsWithMeta = []
return
}

var identifiers: UnkeyedDecodingContainer
do {
identifiers = try container.nestedUnkeyedContainer(forKey: .data)
Expand Down
22 changes: 21 additions & 1 deletion Sources/JSONAPI/Resource/Resource Object/ResourceObject.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,25 @@ public protocol JSONTyped {

/// A `ResourceObjectProxyDescription` is an `ResourceObjectDescription`
/// without Codable conformance.
public protocol ResourceObjectProxyDescription: JSONTyped {
public protocol ResourceObjectProxyDescription: JSONTyped, ResourceObjectWithOptionalDataInRelationships {
associatedtype Attributes: Equatable
associatedtype Relationships: Equatable
}

/// A flagging protocol for `ResourceObjectProxyDescription` objects.
/// Indicates an object with varying behavior when it's being decoded from resources and the `data` key is missing.
public protocol ResourceObjectWithOptionalDataInRelationships {
/// A Boolean flag indicating that instances while decoding from relationships can be decoded without `data`
/// key and have only links and meta information.
///
/// Default value: `false`.
static var canHaveNoDataInRelationships: Bool { get }
}

extension ResourceObjectWithOptionalDataInRelationships {
public static var canHaveNoDataInRelationships: Bool { false }
}

/// A `ResourceObjectDescription` describes a JSON API
/// Resource Object. The Resource Object
/// itself is encoded and decoded as an
Expand Down Expand Up @@ -244,6 +258,12 @@ public extension ResourceObject where EntityRawIdType: CreatableRawIdType {
}
}

// Conformance to the protocol so we can access the `canHaveNoDataInRelationships` flag from
// `ToManyRelationship` in type-erasured `Relatable`.
extension ResourceObject: ResourceObjectWithOptionalDataInRelationships where Description: ResourceObjectWithOptionalDataInRelationships {
public static var canHaveNoDataInRelationships: Bool { Description.canHaveNoDataInRelationships }
}

// MARK: - Attribute Access
public extension ResourceObjectProxy {
// MARK: Dynaminc Member Keypath Lookup
Expand Down
34 changes: 33 additions & 1 deletion Tests/JSONAPITests/Relationships/RelationshipTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,36 @@ extension RelationshipTests {
test_DecodeEncodeEquality(type: ToManyWithMetaAndLinks.self,
data: to_many_relationship_with_meta_and_links)
}

func test_ToManyRelationshipWithMetaNoDataOmittable() {
TestEntityType1.canHaveNoDataInRelationships = true

let relationship = decoded(type: ToManyWithMeta.self,
data: to_many_relationship_with_meta_no_data)

XCTAssertEqual(relationship.ids, [])
XCTAssertEqual(relationship.meta.a, "hello")

TestEntityType1.canHaveNoDataInRelationships = false
}

func test_ToManyRelationshipWithMetaNoDataNotOmittable() {
TestEntityType1.canHaveNoDataInRelationships = false

XCTAssertThrowsError(
try decodedThrows(type: ToManyWithMeta.self,
data: to_many_relationship_with_meta_no_data)
) { error in
let oldLinuxFoundationMsg = "The operation could not be completed. (SwiftError error 0.)"
let newLinuxFoundationMsg = "The operation could not be completed. The data is missing."
let newDesirableMsg = "The data couldn’t be read because it is missing."
XCTAssert(
error.localizedDescription == newDesirableMsg
|| error.localizedDescription == newLinuxFoundationMsg
|| error.localizedDescription == oldLinuxFoundationMsg
)
}
}
}

// MARK: Nullable
Expand Down Expand Up @@ -277,12 +307,14 @@ extension RelationshipTests {

// MARK: - Test types
extension RelationshipTests {
enum TestEntityType1: ResourceObjectDescription {
enum TestEntityType1: ResourceObjectDescription, ResourceObjectWithOptionalDataInRelationships {
typealias Attributes = NoAttributes

typealias Relationships = NoRelationships

public static var jsonType: String { return "test_entity1" }

static var canHaveNoDataInRelationships: Bool = false
}

typealias TestEntity1 = BasicEntity<TestEntityType1>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -241,3 +241,11 @@ let to_many_relationship_type_mismatch = """
]
}
""".data(using: .utf8)!

let to_many_relationship_with_meta_no_data = """
{
"meta": {
"a": "hello"
}
}
""".data(using: .utf8)!
4 changes: 4 additions & 0 deletions Tests/JSONAPITests/Test Helpers/EncodeDecode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ func decoded<T: Decodable>(type: T.Type, data: Data) -> T {
return try! testDecoder.decode(T.self, from: data)
}

func decodedThrows<T: Decodable>(type: T.Type, data: Data) throws -> T {
return try testDecoder.decode(T.self, from: data)
}

func encoded<T: Encodable>(value: T) -> Data {
return try! testEncoder.encode(value)
}
Expand Down
Loading