diff --git a/JSONAPI.playground/Pages/PATCHing.xcplaygroundpage/Contents.swift b/JSONAPI.playground/Pages/PATCHing.xcplaygroundpage/Contents.swift index 937b873..f916054 100644 --- a/JSONAPI.playground/Pages/PATCHing.xcplaygroundpage/Contents.swift +++ b/JSONAPI.playground/Pages/PATCHing.xcplaygroundpage/Contents.swift @@ -12,25 +12,6 @@ import JSONAPI ********/ -// Mapping functions (will be included in future version of library) -extension JSONAPI.ResourceObject { - func mapAttributes(_ transform: (Description.Attributes) -> Description.Attributes) -> Self { - return Self(id: id, - attributes: transform(attributes), - relationships: relationships, - meta: meta, - links: links) - } - - func mapRelationships(_ transform: (Description.Relationships) -> Description.Relationships) -> Self { - return Self(id: id, - attributes: attributes, - relationships: transform(relationships), - meta: meta, - links: links) - } -} - // Mock up a server response let mockDogData = """ { @@ -62,11 +43,7 @@ var dog = parsedResponse.body.primaryResource!.value print("Received dog named: \(dog.name)") // change the dog's name -let changedDog = dog.mapAttributes { currentAttributes in - var ret = currentAttributes - ret.name = .init(value: "Julia") - return ret -} +let changedDog = dog.tappingAttributes { $0.name = .init(value: "Julia") } // create a document to be used as a request body for a PATCH request let patchRequest = MutableDogDocument(apiDescription: .none, @@ -97,7 +74,7 @@ var dog2 = parsedResponse2.body.primaryResource!.value print("Received dog named: \(dog2.name)") // change the dog's name -let changedDog2 = dog2.mapAttributes { _ in +let changedDog2 = dog2.replacingAttributes { _ in return .init(name: .init(value: "Nigel")) } @@ -130,7 +107,7 @@ var dog3 = parsedResponse2.body.primaryResource!.value print("Received dog with owner: \(dog3 ~> \.owner)") // give the dog an owner -let changedDog3 = dog3.mapRelationships { _ in +let changedDog3 = dog3.replacingRelationships { _ in return .init(owner: .init(id: Id(rawValue: "1"))) } diff --git a/Sources/JSONAPI/Resource/Resource Object/ResourceObject+Replacing.swift b/Sources/JSONAPI/Resource/Resource Object/ResourceObject+Replacing.swift new file mode 100644 index 0000000..98ef37c --- /dev/null +++ b/Sources/JSONAPI/Resource/Resource Object/ResourceObject+Replacing.swift @@ -0,0 +1,74 @@ +// +// ResourceObject+Replacing.swift +// JSONAPI +// +// Created by Mathew Polzin on 10/12/19. +// + +public extension JSONAPI.ResourceObject { + /// Return a new `ResourceObject`, having replaced `self`'s + /// `attributes` with the attributes returned by the given + /// replacement function. + /// + /// - important: `self` is not mutated. A copy of self is returned. + /// + /// - parameters: + /// - replacement: A function that takes the existing `attributes` and returns the replacement. + func replacingAttributes(_ replacement: (Description.Attributes) -> Description.Attributes) -> Self { + return Self(id: id, + attributes: replacement(attributes), + relationships: relationships, + meta: meta, + links: links) + } + + /// Return a new `ResourceObject`, having updated `self`'s + /// `attributes` with the tap function given. + /// + /// - important: `self` is not mutated. A copy of self is returned. + /// + /// - parameters: + /// - tap: A function that takes a copy of the existing `attributes` and mutates them. + func tappingAttributes(_ tap: (inout Description.Attributes) -> Void) -> Self { + var newAttributes = attributes + tap(&newAttributes) + return Self(id: id, + attributes: newAttributes, + relationships: relationships, + meta: meta, + links: links) + } + + /// Return a new `ResourceObject`, having replaced `self`'s + /// `relationships` with the `relationships` returned by the given + /// replacement function. + /// + /// - important: `self` is not mutated. A copy of self is returned. + /// + /// - parameters: + /// - replacement: A function that takes the existing relationships and returns the replacement. + func replacingRelationships(_ replacement: (Description.Relationships) -> Description.Relationships) -> Self { + return Self(id: id, + attributes: attributes, + relationships: replacement(relationships), + meta: meta, + links: links) + } + + /// Return a new `ResourceObject`, having updated `self`'s + /// `relationships` with the tap function given. + /// + /// - important: `self` is not mutated. A copy of self is returned. + /// + /// - parameters: + /// - tap: A function that takes a copy of the existing `relationships` and mutates them. + func tappingRelationships(_ tap: (inout Description.Relationships) -> Void) -> Self { + var newRelationships = relationships + tap(&newRelationships) + return Self(id: id, + attributes: attributes, + relationships: newRelationships, + meta: meta, + links: links) + } +} diff --git a/Sources/JSONAPI/Resource/ResourceObject.swift b/Sources/JSONAPI/Resource/Resource Object/ResourceObject.swift similarity index 100% rename from Sources/JSONAPI/Resource/ResourceObject.swift rename to Sources/JSONAPI/Resource/Resource Object/ResourceObject.swift diff --git a/Tests/JSONAPITests/ResourceObject/ResourceObject+ReplacingTests.swift b/Tests/JSONAPITests/ResourceObject/ResourceObject+ReplacingTests.swift new file mode 100644 index 0000000..337a5b6 --- /dev/null +++ b/Tests/JSONAPITests/ResourceObject/ResourceObject+ReplacingTests.swift @@ -0,0 +1,155 @@ +// +// ResourceObject+ReplacingTests.swift +// JSONAPITests +// +// Created by Mathew Polzin on 10/12/19. +// + +import XCTest +import JSONAPI + +final class ResourceObjectReplacingTests: XCTestCase { + func test_replaceMutableAttributes() { + let testResource = MutableTestType(attributes: .init(name: .init(value: "Matt")), + relationships: .init(other: .init(id: .init(rawValue: "2"))), + meta: .none, + links: .none) + + let mutatedResource = testResource + .replacingAttributes { + var newAttributes = $0 + newAttributes.name = .init(value: "Matt 2") + return newAttributes + } + + XCTAssertEqual(testResource.name, "Matt") + XCTAssertEqual(mutatedResource.name, "Matt 2") + } + + func test_tapMutableAttributes() { + let testResource = MutableTestType(attributes: .init(name: .init(value: "Matt")), + relationships: .init(other: .init(id: .init(rawValue: "2"))), + meta: .none, + links: .none) + + let mutatedResource = testResource + .tappingAttributes { $0.name = .init(value: "Matt 2") } + + XCTAssertEqual(testResource.name, "Matt") + XCTAssertEqual(mutatedResource.name, "Matt 2") + } + + func test_replaceImmutableAttributes() { + let testResource = ImmutableTestType(attributes: .init(name: .init(value: "Matt")), + relationships: .init(other: .init(id: .init(rawValue: "2"))), + meta: .none, + links: .none) + + let mutatedResource = testResource + .replacingAttributes { + return .init(name: $0.name.map { $0 + " 2" }) + } + + XCTAssertEqual(testResource.name, "Matt") + XCTAssertEqual(mutatedResource.name, "Matt 2") + } + + func test_tapImmutableAttributes() { + let testResource = ImmutableTestType(attributes: .init(name: .init(value: "Matt")), + relationships: .init(other: .init(id: .init(rawValue: "2"))), + meta: .none, + links: .none) + + let mutatedResource = testResource + .tappingAttributes { $0 = .init(name: $0.name.map { $0 + " 2" }) } + + XCTAssertEqual(testResource.name, "Matt") + XCTAssertEqual(mutatedResource.name, "Matt 2") + } + + func test_replaceMutableRelationships() { + let testResource = MutableTestType(attributes: .init(name: .init(value: "Matt")), + relationships: .init(other: .init(id: .init(rawValue: "2"))), + meta: .none, + links: .none) + + let mutatedResource = testResource + .replacingRelationships { + var newRelationships = $0 + newRelationships.other = .init(id: .init(rawValue: "3")) + return newRelationships + } + + XCTAssertEqual(testResource ~> \.other, "2") + XCTAssertEqual(mutatedResource ~> \.other, "3") + } + + func test_tapMutableRelationships() { + let testResource = MutableTestType(attributes: .init(name: .init(value: "Matt")), + relationships: .init(other: .init(id: .init(rawValue: "2"))), + meta: .none, + links: .none) + + let mutatedResource = testResource + .tappingRelationships { $0.other = .init(id: .init(rawValue: "3")) } + + XCTAssertEqual(testResource ~> \.other, "2") + XCTAssertEqual(mutatedResource ~> \.other, "3") + } + + func test_replaceImmutableRelationships() { + let testResource = ImmutableTestType(attributes: .init(name: .init(value: "Matt")), + relationships: .init(other: .init(id: .init(rawValue: "2"))), + meta: .none, + links: .none) + + let mutatedResource = testResource + .replacingRelationships { _ in + return .init(other: .init(id: .init(rawValue: "3"))) + } + + XCTAssertEqual(testResource ~> \.other, "2") + XCTAssertEqual(mutatedResource ~> \.other, "3") + } + + func test_tapImmutableRelationships() { + let testResource = ImmutableTestType(attributes: .init(name: .init(value: "Matt")), + relationships: .init(other: .init(id: .init(rawValue: "2"))), + meta: .none, + links: .none) + + let mutatedResource = testResource + .tappingRelationships { $0 = .init(other: .init(id: .init(rawValue: "3"))) } + + XCTAssertEqual(testResource ~> \.other, "2") + XCTAssertEqual(mutatedResource ~> \.other, "3") + } +} + +private enum MutableTestDescription: JSONAPI.ResourceObjectDescription { + static let jsonType: String = "test" + + struct Attributes: JSONAPI.Attributes { + var name: Attribute + } + + struct Relationships: JSONAPI.Relationships { + var other: ToOneRelationship + } +} + +private typealias MutableTestType = JSONAPI.ResourceObject + +private enum ImmutableTestDescription: JSONAPI.ResourceObjectDescription { + static let jsonType: String = "test2" + + struct Attributes: JSONAPI.Attributes { + let name: Attribute + } + + struct Relationships: JSONAPI.Relationships { + let other: ToOneRelationship + } +} + +private typealias ImmutableTestType = JSONAPI.ResourceObject