Skip to content

Commit eaf8094

Browse files
author
Oleksii Dykan
authored
Merge pull request #14 from alickbass/geopoint-and-reference-encoding
Geopoint and reference encoding
2 parents 31ecb57 + 3a08c9e commit eaf8094

File tree

9 files changed

+135
-2
lines changed

9 files changed

+135
-2
lines changed

CodableFirebase/Decoder.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ class _FirebaseDecoder : Decoder {
1313
struct _Options {
1414
let dateDecodingStrategy: FirebaseDecoder.DateDecodingStrategy?
1515
let dataDecodingStrategy: FirebaseDecoder.DataDecodingStrategy?
16+
let skipGeoPointAndReference: Bool
1617
let userInfo: [CodingUserInfoKey : Any]
1718
}
1819

@@ -1229,6 +1230,8 @@ extension _FirebaseDecoder {
12291230
} else if T.self == Decimal.self || T.self == NSDecimalNumber.self {
12301231
guard let decimal = try self.unbox(value, as: Decimal.self) else { return nil }
12311232
decoded = decimal as! T
1233+
} else if options.skipGeoPointAndReference && (T.self is GeoPointType.Type || T.self is DocumentReferenceType.Type) {
1234+
decoded = value as! T
12321235
} else {
12331236
self.storage.push(container: value)
12341237
decoded = try T(from: self)

CodableFirebase/Encoder.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ class _FirebaseEncoder : Encoder {
1313
struct _Options {
1414
let dateEncodingStrategy: FirebaseEncoder.DateEncodingStrategy?
1515
let dataEncodingStrategy: FirebaseEncoder.DataEncodingStrategy?
16+
let skipGeoPointAndReference: Bool
1617
let userInfo: [CodingUserInfoKey : Any]
1718
}
1819

@@ -382,6 +383,11 @@ extension _FirebaseEncoder {
382383
return try self.box((value as! Data))
383384
} else if T.self == URL.self || T.self == NSURL.self {
384385
return self.box((value as! URL).absoluteString)
386+
} else if options.skipGeoPointAndReference && (value is GeoPointType || value is DocumentReferenceType) {
387+
guard let value = value as? NSObject else {
388+
throw DocumentReferenceError.typeIsNotNSObject
389+
}
390+
return value
385391
}
386392

387393
// The value should request a container from the _FirebaseEncoder.

CodableFirebase/FirebaseDecoder.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ open class FirebaseDecoder {
5353
let options = _FirebaseDecoder._Options(
5454
dateDecodingStrategy: dateDecodingStrategy,
5555
dataDecodingStrategy: dataDecodingStrategy,
56+
skipGeoPointAndReference: false,
5657
userInfo: userInfo
5758
)
5859
let decoder = _FirebaseDecoder(referencing: container, options: options)

CodableFirebase/FirebaseEncoder.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ open class FirebaseEncoder {
5757
let options = _FirebaseEncoder._Options(
5858
dateEncodingStrategy: dateEncodingStrategy,
5959
dataEncodingStrategy: dataEncodingStrategy,
60+
skipGeoPointAndReference: false,
6061
userInfo: userInfo
6162
)
6263
let encoder = _FirebaseEncoder(options: options)

CodableFirebase/FirestoreDecoder.swift

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,26 @@
88

99
import Foundation
1010

11+
public protocol GeoPointType: Codable {
12+
var latitude: Double { get }
13+
var longitude: Double { get }
14+
init(latitude: Double, longitude: Double)
15+
}
16+
17+
public protocol DocumentReferenceType: Codable {}
18+
1119
open class FirestoreDecoder {
1220
public init() {}
1321

1422
open var userInfo: [CodingUserInfoKey : Any] = [:]
1523

1624
open func decode<T : Decodable>(_ type: T.Type, from container: [String: Any]) throws -> T {
17-
let options = _FirebaseDecoder._Options(dateDecodingStrategy: nil, dataDecodingStrategy: nil, userInfo: userInfo)
25+
let options = _FirebaseDecoder._Options(
26+
dateDecodingStrategy: nil,
27+
dataDecodingStrategy: nil,
28+
skipGeoPointAndReference: true,
29+
userInfo: userInfo
30+
)
1831
let decoder = _FirebaseDecoder(referencing: container, options: options)
1932
guard let value = try decoder.unbox(container, as: T.self) else {
2033
throw DecodingError.valueNotFound(T.self, DecodingError.Context(codingPath: [], debugDescription: "The given dictionary was invalid"))
@@ -23,3 +36,37 @@ open class FirestoreDecoder {
2336
return value
2437
}
2538
}
39+
40+
enum GeoPointKeys: CodingKey {
41+
case latitude, longitude
42+
}
43+
44+
extension GeoPointType {
45+
public init(from decoder: Decoder) throws {
46+
let container = try decoder.container(keyedBy: GeoPointKeys.self)
47+
let latitude = try container.decode(Double.self, forKey: .latitude)
48+
let longitude = try container.decode(Double.self, forKey: .longitude)
49+
self.init(latitude: latitude, longitude: longitude)
50+
}
51+
52+
public func encode(to encoder: Encoder) throws {
53+
var container = encoder.container(keyedBy: GeoPointKeys.self)
54+
try container.encode(latitude, forKey: .latitude)
55+
try container.encode(longitude, forKey: .longitude)
56+
}
57+
}
58+
59+
enum DocumentReferenceError: Error {
60+
case typeIsNotSupported
61+
case typeIsNotNSObject
62+
}
63+
64+
extension DocumentReferenceType {
65+
public init(from decoder: Decoder) throws {
66+
throw DocumentReferenceError.typeIsNotSupported
67+
}
68+
69+
public func encode(to encoder: Encoder) throws {
70+
throw DocumentReferenceError.typeIsNotSupported
71+
}
72+
}

CodableFirebase/FirestoreEncoder.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,12 @@ open class FirestoreEncoder {
2626
}
2727

2828
internal func encodeToTopLevelContainer<Value : Encodable>(_ value: Value) throws -> Any {
29-
let options = _FirebaseEncoder._Options(dateEncodingStrategy: nil, dataEncodingStrategy: nil, userInfo: userInfo)
29+
let options = _FirebaseEncoder._Options(
30+
dateEncodingStrategy: nil,
31+
dataEncodingStrategy: nil,
32+
skipGeoPointAndReference: true,
33+
userInfo: userInfo
34+
)
3035
let encoder = _FirebaseEncoder(options: options)
3136
guard let topLevel = try encoder.box_(value) else {
3237
throw EncodingError.invalidValue(value,

CodableFirebaseTests/TestCodableFirebase.swift

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,19 @@ class TestCodableFirebase: XCTestCase {
369369
_testRoundTrip(of: 3 as Double)
370370
}
371371

372+
// MARK: - GeoPoint
373+
func testEncodingGeoPoint() {
374+
let point = Point(latitude: 2, longitude: 2)
375+
XCTAssertEqual((try? FirebaseEncoder().encode(point)) as? NSDictionary, ["latitude": 2, "longitude": 2])
376+
XCTAssertEqual(try? FirebaseDecoder().decode(Point.self, from: ["latitude": 2, "longitude": 2]), point)
377+
}
378+
379+
// MARK: - Document Reference
380+
func testEncodingDocumentReference() {
381+
XCTAssertThrowsError(try FirebaseEncoder().encode(DocumentReference()))
382+
XCTAssertThrowsError(try FirebaseDecoder().decode(DocumentReference.self, from: []))
383+
}
384+
372385
// MARK: - Helper Functions
373386
private var _emptyDictionary: [String: Any] = [:]
374387

@@ -415,6 +428,19 @@ class TestCodableFirebase: XCTestCase {
415428
// MARK: - Test Types
416429
/* FIXME: Import from %S/Inputs/Coding/SharedTypes.swift somehow. */
417430

431+
// MARK: - GeoPoint
432+
struct Point: GeoPointType, Equatable {
433+
let latitude: Double
434+
let longitude: Double
435+
436+
static func == (lhs: Point, rhs: Point) -> Bool {
437+
return lhs.latitude == rhs.latitude && lhs.longitude == rhs.longitude
438+
}
439+
}
440+
441+
// MARK: - ReferenceType
442+
fileprivate struct DocumentReference: DocumentReferenceType {}
443+
418444
// MARK: - Empty Types
419445
fileprivate struct EmptyStruct : Codable, Equatable {
420446
static func ==(_ lhs: EmptyStruct, _ rhs: EmptyStruct) -> Bool {

CodableFirebaseTests/TestCodableFirestore.swift

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,21 @@ class TestCodableFirestore: XCTestCase {
109109
_testRoundTrip(of: TopLevelWrapper(date), expected: ["value": date])
110110
}
111111

112+
// MARK: - GeoPoint & Document Reference
113+
func testEncodingGeoPoint() {
114+
let point = GeoPoint(latitude: 2, longitude: 2)
115+
let wrapper = TopLevelWrapper(point)
116+
XCTAssertEqual((try? FirestoreEncoder().encode(wrapper)) as NSDictionary?, ["value": point])
117+
XCTAssertEqual(try? FirestoreDecoder().decode(TopLevelWrapper<GeoPoint>.self, from: ["value": point]), wrapper)
118+
XCTAssertThrowsError(try FirestoreEncoder().encode(TopLevelWrapper(Point(latitude: 2, longitude: 2))))
119+
}
120+
121+
func testEncodingDocumentReference() {
122+
let val = TopLevelWrapper(DocumentReference())
123+
XCTAssertEqual((try? FirestoreEncoder().encode(val)) as NSDictionary?, ["value": val.value])
124+
XCTAssertEqual(try? FirestoreDecoder().decode(TopLevelWrapper<DocumentReference>.self, from: ["value": val.value]), val)
125+
}
126+
112127
private func _testEncodeFailure<T : Encodable>(of value: T) {
113128
do {
114129
let _ = try FirestoreEncoder().encode(value)
@@ -164,3 +179,21 @@ func expectEqualPaths(_ lhs: [CodingKey], _ rhs: [CodingKey], _ prefix: String)
164179
XCTAssertEqual(key1.stringValue, key2.stringValue, "\(prefix) CodingKey.stringValue mismatch: \(type(of: key1))('\(key1.stringValue)') != \(type(of: key2))('\(key2.stringValue)')")
165180
}
166181
}
182+
183+
// MARK: - GeioPoint
184+
fileprivate class GeoPoint: NSObject, GeoPointType {
185+
let latitude: Double
186+
let longitude: Double
187+
188+
required init(latitude: Double, longitude: Double) {
189+
self.latitude = latitude
190+
self.longitude = longitude
191+
}
192+
193+
static func == (lhs: Point, rhs: Point) -> Bool {
194+
return lhs.latitude == rhs.latitude && lhs.longitude == rhs.longitude
195+
}
196+
}
197+
198+
// MARK: - ReferenceType
199+
fileprivate class DocumentReference: NSObject, DocumentReferenceType {}

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,17 @@ Firestore.firestore().collection("data").document("one").getDocument { (document
8686
}
8787
```
8888

89+
### How to use `GeoPoint` and `DocumentRefence` in Firestore
90+
91+
In order to use these 2 types with `Firestore`, you need to add the following code somewhere in your app:
92+
93+
```swift
94+
extension DocumentReference: DocumentReferenceType {}
95+
extension GeoPoint: GeoPointType {}
96+
```
97+
98+
and now they become `Codable` and can be used properly with `FirestoreEncoder` and `FirestoreDecoder`.
99+
89100
## Integration
90101

91102
### CocoaPods (iOS 9+)

0 commit comments

Comments
 (0)