Skip to content

Commit a628992

Browse files
committed
Make Attribute a Functor to make computed attributes easier to write.
1 parent cf47f88 commit a628992

3 files changed

Lines changed: 93 additions & 2 deletions

File tree

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//
2+
// Attribute+Functor.swift
3+
// JSONAPI
4+
//
5+
// Created by Mathew Polzin on 11/28/18.
6+
//
7+
8+
public extension TransformedAttribute {
9+
/// Map an Attribute to a new wrapped type.
10+
/// Note that the resulting Attribute will have no transformer, even if the
11+
/// source Attribute has a transformer.
12+
/// You are mapping the output of the source transform into
13+
/// the RawValue of a new transformerless Attribute.
14+
///
15+
/// Generally, this is the most useful operation. The transformer gives you
16+
/// control over the decoding of the Attribute, but once the Attribute exists,
17+
/// mapping on it is most useful for creating computed Attribute properties.
18+
public func map<T: Codable>(_ transform: (Transformer.To) throws -> T) rethrows -> Attribute<T> {
19+
return Attribute<T>(value: try transform(value))
20+
}
21+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
//
2+
// Attribute+FunctorTests.swift
3+
// JSONAPITests
4+
//
5+
// Created by Mathew Polzin on 11/28/18.
6+
//
7+
8+
import XCTest
9+
import JSONAPI
10+
import JSONAPITestLib
11+
12+
class Attribute_FunctorTests: XCTestCase {
13+
func test_mapGuaranteed() {
14+
let entity = try? TestType(attributes: .init(name: "Frankie", number: .init(rawValue: 22.0)))
15+
16+
XCTAssertNotNil(entity)
17+
18+
XCTAssertEqual(entity?[\.computedString], "Frankie2")
19+
}
20+
21+
func test_mapOptionalSuccess() {
22+
let entity = try? TestType(attributes: .init(name: "Frankie", number: .init(rawValue: 22.0)))
23+
24+
XCTAssertNotNil(entity)
25+
26+
XCTAssertEqual(entity?[\.computedNumber], 22)
27+
}
28+
29+
func test_mapOptionalFailure() {
30+
let entity = try? TestType(attributes: .init(name: "Frankie", number: .init(rawValue: 22.5)))
31+
32+
XCTAssertNotNil(entity)
33+
34+
XCTAssertNil(entity?[\.computedNumber])
35+
}
36+
}
37+
38+
// MARK: Test types
39+
extension Attribute_FunctorTests {
40+
enum TestTypeDescription: EntityDescription {
41+
public static var type: String { return "test" }
42+
43+
public struct Attributes: JSONAPI.Attributes {
44+
let name: Attribute<String>
45+
let number: TransformedAttribute<Double, DoubleToString>
46+
var computedString: Attribute<String> {
47+
return name.map { $0 + "2" }
48+
}
49+
var computedNumber: Attribute<Int>? {
50+
return try? number.map { string in
51+
let num = Double(string).flatMap { Int(exactly: $0) }
52+
guard let ret = num else {
53+
throw DecodingError.typeMismatch(Int.self, .init(codingPath: [], debugDescription: "String was not an Int."))
54+
}
55+
return ret
56+
}
57+
}
58+
}
59+
60+
public typealias Relationships = NoRelationships
61+
}
62+
63+
typealias TestType = Entity<TestTypeDescription>
64+
65+
enum DoubleToString: Transformer {
66+
public static func transform(_ from: Double) -> String {
67+
return String(from)
68+
}
69+
}
70+
}

Tests/JSONAPITests/Computed Properties/ComputedPropertiesTests.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class ComputedPropertiesTests: XCTestCase {
2626
func test_ComputedAttributeAccess() {
2727
let entity = decoded(type: TestType.self, data: computed_property_attribute)
2828

29-
XCTAssertEqual(entity[\.computed], "Sarah")
29+
XCTAssertEqual(entity[\.computed], "Sarah2")
3030
}
3131

3232
func test_ComputedRelationshipAccess() {
@@ -44,7 +44,7 @@ extension ComputedPropertiesTests {
4444
public struct Attributes: JSONAPI.Attributes {
4545
public let name: Attribute<String>
4646
public var computed: Attribute<String> {
47-
return name
47+
return name.map { $0 + "2" }
4848
}
4949
}
5050

0 commit comments

Comments
 (0)