mirror of
https://github.com/encounter/JSONAPI.git
synced 2026-03-30 11:18:38 -07:00
Add ability to merge Document.Body.Data values
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -50,6 +50,16 @@ public struct Includes<I: Include>: Codable, Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
extension Includes {
|
||||
public func appending(_ other: Includes<I>) -> Includes {
|
||||
return Includes(values: values + other.values)
|
||||
}
|
||||
}
|
||||
|
||||
public func +<I: Include>(_ left: Includes<I>, _ right: Includes<I>) -> Includes<I> {
|
||||
return left.appending(right)
|
||||
}
|
||||
|
||||
extension Includes: CustomStringConvertible {
|
||||
public var description: String {
|
||||
return "Includes(\(String(describing: values))"
|
||||
|
||||
@@ -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 +<R: AppendableResourceBody>(_ left: R, right: R) -> R {
|
||||
return left.appending(right)
|
||||
}
|
||||
|
||||
public struct SingleResourceBody<Entity: JSONAPI.MaybePrimaryResource>: ResourceBody {
|
||||
public let value: Entity
|
||||
|
||||
@@ -27,12 +35,16 @@ public struct SingleResourceBody<Entity: JSONAPI.MaybePrimaryResource>: Resource
|
||||
}
|
||||
}
|
||||
|
||||
public struct ManyResourceBody<Entity: JSONAPI.PrimaryResource>: ResourceBody {
|
||||
public struct ManyResourceBody<Entity: JSONAPI.PrimaryResource>: 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
|
||||
|
||||
@@ -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<ManyResourceBody<Article>, NoMetadata, NoLinks, NoIncludes, NoAPIDescription, UnknownJSONAPIError>.Body.Data(primary: .init(entities: [entity1]),
|
||||
includes: .none,
|
||||
meta: .none,
|
||||
links: .none)
|
||||
let bodyData2 = Document<ManyResourceBody<Article>, 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<ManyResourceBody<Article>, TestPageMetadata, NoLinks, Include1<Author>, 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<ManyResourceBody<Article>, TestPageMetadata, NoLinks, Include1<Author>, 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 {
|
||||
|
||||
@@ -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<Include2<TestEntity8, TestEntity9>>(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<Include2<TestEntity8, TestEntity9>>(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 {
|
||||
|
||||
@@ -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" }
|
||||
|
||||
|
||||
@@ -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),
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user