diff --git a/Sources/JSONAPI/Document/Document.swift b/Sources/JSONAPI/Document/Document.swift index 850d74a..ac9a12e 100644 --- a/Sources/JSONAPI/Document/Document.swift +++ b/Sources/JSONAPI/Document/Document.swift @@ -189,6 +189,26 @@ extension Document where IncludeType == NoIncludes, MetaType == NoMetadata, Link } */ +extension Document.Body.Data where PrimaryResourceBody: AppendableResourceBody { + public func merging(_ other: Document.Body.Data, + combiningMetaWith metaMerge: (MetaType, MetaType) -> MetaType, + combiningLinksWith linksMerge: (LinksType, LinksType) -> LinksType) -> Document.Body.Data { + return Document.Body.Data(primary: primary.appending(other.primary), + includes: includes.appending(other.includes), + meta: metaMerge(meta, other.meta), + links: linksMerge(links, other.links)) + } +} + +extension Document.Body.Data where PrimaryResourceBody: AppendableResourceBody, MetaType == NoMetadata, LinksType == NoLinks { + public func merging(_ other: Document.Body.Data) -> Document.Body.Data { + return merging(other, + combiningMetaWith: { _, _ in .none }, + combiningLinksWith: { _, _ in .none }) + } +} + +// MARK: - Codable extension Document { private enum RootCodingKeys: String, CodingKey { case data diff --git a/Sources/JSONAPI/Document/Includes.swift b/Sources/JSONAPI/Document/Includes.swift index b19e252..c3a9ac4 100644 --- a/Sources/JSONAPI/Document/Includes.swift +++ b/Sources/JSONAPI/Document/Includes.swift @@ -50,6 +50,16 @@ public struct Includes: Codable, Equatable { } } +extension Includes { + public func appending(_ other: Includes) -> Includes { + return Includes(values: values + other.values) + } +} + +public func +(_ left: Includes, _ right: Includes) -> Includes { + return left.appending(right) +} + extension Includes: CustomStringConvertible { public var description: String { return "Includes(\(String(describing: values))" diff --git a/Sources/JSONAPI/Document/ResourceBody.swift b/Sources/JSONAPI/Document/ResourceBody.swift index 3f7522a..62c5224 100644 --- a/Sources/JSONAPI/Document/ResourceBody.swift +++ b/Sources/JSONAPI/Document/ResourceBody.swift @@ -19,6 +19,14 @@ extension Optional: MaybePrimaryResource where Wrapped: PrimaryResource {} public protocol ResourceBody: Codable, Equatable { } +public protocol AppendableResourceBody: ResourceBody { + func appending(_ other: Self) -> Self +} + +public func +(_ left: R, right: R) -> R { + return left.appending(right) +} + public struct SingleResourceBody: ResourceBody { public let value: Entity @@ -27,12 +35,16 @@ public struct SingleResourceBody: Resource } } -public struct ManyResourceBody: ResourceBody { +public struct ManyResourceBody: AppendableResourceBody { public let values: [Entity] public init(entities: [Entity]) { values = entities } + + public func appending(_ other: ManyResourceBody) -> ManyResourceBody { + return ManyResourceBody(entities: values + other.values) + } } /// Use NoResourceBody to indicate you expect a JSON API document to not diff --git a/Tests/JSONAPITests/Document/DocumentTests.swift b/Tests/JSONAPITests/Document/DocumentTests.swift index e57766e..9f4972f 100644 --- a/Tests/JSONAPITests/Document/DocumentTests.swift +++ b/Tests/JSONAPITests/Document/DocumentTests.swift @@ -961,6 +961,55 @@ extension DocumentTests { } } +// MARK: - Merging +extension DocumentTests { + public func test_MergeBodyDataBasic(){ + let entity1 = Article(attributes: .none, relationships: .init(author: "2"), meta: .none, links: .none) + let entity2 = Article(attributes: .none, relationships: .init(author: "3"), meta: .none, links: .none) + + let bodyData1 = Document, NoMetadata, NoLinks, NoIncludes, NoAPIDescription, UnknownJSONAPIError>.Body.Data(primary: .init(entities: [entity1]), + includes: .none, + meta: .none, + links: .none) + let bodyData2 = Document, NoMetadata, NoLinks, NoIncludes, NoAPIDescription, UnknownJSONAPIError>.Body.Data(primary: .init(entities: [entity2]), + includes: .none, + meta: .none, + links: .none) + let combined = bodyData1.merging(bodyData2) + + XCTAssertEqual(combined.primary.values, bodyData1.primary.values + bodyData2.primary.values) + } + + public func test_MergeBodyDataWithMergeFunctions() { + let article1 = Article(attributes: .none, relationships: .init(author: "2"), meta: .none, links: .none) + let author1 = Author(id: "2", attributes: .none, relationships: .none, meta: .none, links: .none) + let article2 = Article(attributes: .none, relationships: .init(author: "3"), meta: .none, links: .none) + let author2 = Author(id: "3", attributes: .none, relationships: .none, meta: .none, links: .none) + + let bodyData1 = Document, TestPageMetadata, NoLinks, Include1, NoAPIDescription, UnknownJSONAPIError>.Body.Data(primary: .init(entities: [article1]), + includes: .init(values: [.init(author1)]), + meta: .init(total: 50, limit: 5, offset: 5), + links: .none) + let bodyData2 = Document, TestPageMetadata, NoLinks, Include1, NoAPIDescription, UnknownJSONAPIError>.Body.Data(primary: .init(entities: [article2]), + includes: .init(values: [.init(author2)]), + meta: .init(total: 60, limit: 5, offset: 5), + links: .none) + + let combined = bodyData1.merging(bodyData2, + combiningMetaWith: { (meta1, meta2) in + return .init(total: max(meta1.total, meta2.total), limit: max(meta1.limit, meta2.limit), offset: max(meta1.offset, meta2.offset)) + }, + combiningLinksWith: { _, _ in .none }) + + XCTAssertEqual(combined.meta.total, bodyData2.meta.total) + XCTAssertEqual(combined.meta.limit, bodyData2.meta.limit) + XCTAssertEqual(combined.meta.offset, bodyData1.meta.offset) + + XCTAssertEqual(combined.includes, bodyData1.includes + bodyData2.includes) + XCTAssertEqual(combined.primary, bodyData1.primary + bodyData2.primary) + } +} + // MARK: - Test Types extension DocumentTests { enum AuthorType: EntityDescription { diff --git a/Tests/JSONAPITests/Includes/IncludeTests.swift b/Tests/JSONAPITests/Includes/IncludeTests.swift index bb1db23..59b4746 100644 --- a/Tests/JSONAPITests/Includes/IncludeTests.swift +++ b/Tests/JSONAPITests/Includes/IncludeTests.swift @@ -1,6 +1,6 @@ import XCTest -import JSONAPI +@testable import JSONAPI class IncludedTests: XCTestCase { @@ -180,6 +180,18 @@ class IncludedTests: XCTestCase { } } +extension IncludedTests { + func test_appending() { + let include1 = Includes>(values: [.init(TestEntity8(attributes: .none, relationships: .none, meta: .none, links: .none)), .init(TestEntity9(attributes: .none, relationships: .none, meta: .none, links: .none)), .init(TestEntity8(attributes: .none, relationships: .none, meta: .none, links: .none))]) + + let include2 = Includes>(values: [.init(TestEntity8(attributes: .none, relationships: .none, meta: .none, links: .none)), .init(TestEntity9(attributes: .none, relationships: .none, meta: .none, links: .none)), .init(TestEntity8(attributes: .none, relationships: .none, meta: .none, links: .none))]) + + let combined = include1 + include2 + + XCTAssertEqual(combined.values, include1.values + include2.values) + } +} + // MARK: - Test types extension IncludedTests { enum TestEntityType: EntityDescription { diff --git a/Tests/JSONAPITests/ResourceBody/ResourceBodyTests.swift b/Tests/JSONAPITests/ResourceBody/ResourceBodyTests.swift index 004fb4a..64dc447 100644 --- a/Tests/JSONAPITests/ResourceBody/ResourceBodyTests.swift +++ b/Tests/JSONAPITests/ResourceBody/ResourceBodyTests.swift @@ -61,6 +61,31 @@ class ResourceBodyTests: XCTestCase { data: many_resource_body_empty) } + func test_manyResourceBodyMerge() { + let body1 = ManyResourceBody(entities: [ + Article(attributes: .init(title: "hello"), + relationships: .none, + meta: .none, + links: .none), + Article(attributes: .init(title: "world"), + relationships: .none, + meta: .none, + links: .none) + ]) + + let body2 = ManyResourceBody(entities: [ + Article(attributes: .init(title: "once more"), + relationships: .none, + meta: .none, + links: .none) + ]) + + let combined = body1 + body2 + + XCTAssertEqual(combined.values.count, 3) + XCTAssertEqual(combined.values, body1.values + body2.values) + } + enum ArticleType: EntityDescription { public static var type: String { return "articles" } diff --git a/Tests/JSONAPITests/XCTestManifests.swift b/Tests/JSONAPITests/XCTestManifests.swift index b7e3322..a358a1b 100644 --- a/Tests/JSONAPITests/XCTestManifests.swift +++ b/Tests/JSONAPITests/XCTestManifests.swift @@ -97,6 +97,8 @@ extension DocumentTests { ("test_manyDocumentSomeIncludes_encode", test_manyDocumentSomeIncludes_encode), ("test_manyDocumentSomeIncludesWithAPIDescription", test_manyDocumentSomeIncludesWithAPIDescription), ("test_manyDocumentSomeIncludesWithAPIDescription_encode", test_manyDocumentSomeIncludesWithAPIDescription_encode), + ("test_MergeBodyDataBasic", test_MergeBodyDataBasic), + ("test_MergeBodyDataWithMergeFunctions", test_MergeBodyDataWithMergeFunctions), ("test_metaDataDocument", test_metaDataDocument), ("test_metaDataDocument_encode", test_metaDataDocument_encode), ("test_metaDataDocumentFailsIfMissingAPIDescription", test_metaDataDocumentFailsIfMissingAPIDescription), @@ -269,6 +271,7 @@ extension Id_LiteralTests { extension IncludedTests { static let __allTests = [ + ("test_appending", test_appending), ("test_EightDifferentIncludes", test_EightDifferentIncludes), ("test_EightDifferentIncludes_encode", test_EightDifferentIncludes_encode), ("test_FiveDifferentIncludes", test_FiveDifferentIncludes), @@ -405,6 +408,7 @@ extension ResourceBodyTests { ("test_manyResourceBody_encode", test_manyResourceBody_encode), ("test_manyResourceBodyEmpty", test_manyResourceBodyEmpty), ("test_manyResourceBodyEmpty_encode", test_manyResourceBodyEmpty_encode), + ("test_manyResourceBodyMerge", test_manyResourceBodyMerge), ("test_singleResourceBody", test_singleResourceBody), ("test_singleResourceBody_encode", test_singleResourceBody_encode), ]