Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CodableFirebase/Decoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class _FirebaseDecoder : Decoder {
struct _Options {
let dateDecodingStrategy: FirebaseDecoder.DateDecodingStrategy?
let dataDecodingStrategy: FirebaseDecoder.DataDecodingStrategy?
let skipGeoPointAndReference: Bool
let userInfo: [CodingUserInfoKey : Any]
}

Expand Down Expand Up @@ -1229,6 +1230,8 @@ extension _FirebaseDecoder {
} else if T.self == Decimal.self || T.self == NSDecimalNumber.self {
guard let decimal = try self.unbox(value, as: Decimal.self) else { return nil }
decoded = decimal as! T
} else if options.skipGeoPointAndReference && (T.self is GeoPointType.Type || T.self is DocumentReferenceType.Type) {
decoded = value as! T
} else {
self.storage.push(container: value)
decoded = try T(from: self)
Expand Down
6 changes: 6 additions & 0 deletions CodableFirebase/Encoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class _FirebaseEncoder : Encoder {
struct _Options {
let dateEncodingStrategy: FirebaseEncoder.DateEncodingStrategy?
let dataEncodingStrategy: FirebaseEncoder.DataEncodingStrategy?
let skipGeoPointAndReference: Bool
let userInfo: [CodingUserInfoKey : Any]
}

Expand Down Expand Up @@ -382,6 +383,11 @@ extension _FirebaseEncoder {
return try self.box((value as! Data))
} else if T.self == URL.self || T.self == NSURL.self {
return self.box((value as! URL).absoluteString)
} else if options.skipGeoPointAndReference && (value is GeoPointType || value is DocumentReferenceType) {
guard let value = value as? NSObject else {
throw DocumentReferenceError.typeIsNotNSObject
}
return value
}

// The value should request a container from the _FirebaseEncoder.
Expand Down
1 change: 1 addition & 0 deletions CodableFirebase/FirebaseDecoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ open class FirebaseDecoder {
let options = _FirebaseDecoder._Options(
dateDecodingStrategy: dateDecodingStrategy,
dataDecodingStrategy: dataDecodingStrategy,
skipGeoPointAndReference: false,
userInfo: userInfo
)
let decoder = _FirebaseDecoder(referencing: container, options: options)
Expand Down
1 change: 1 addition & 0 deletions CodableFirebase/FirebaseEncoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ open class FirebaseEncoder {
let options = _FirebaseEncoder._Options(
dateEncodingStrategy: dateEncodingStrategy,
dataEncodingStrategy: dataEncodingStrategy,
skipGeoPointAndReference: false,
userInfo: userInfo
)
let encoder = _FirebaseEncoder(options: options)
Expand Down
49 changes: 48 additions & 1 deletion CodableFirebase/FirestoreDecoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,26 @@

import Foundation

public protocol GeoPointType: Codable {
var latitude: Double { get }
var longitude: Double { get }
init(latitude: Double, longitude: Double)
}

public protocol DocumentReferenceType: Codable {}

open class FirestoreDecoder {
public init() {}

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

open func decode<T : Decodable>(_ type: T.Type, from container: [String: Any]) throws -> T {
let options = _FirebaseDecoder._Options(dateDecodingStrategy: nil, dataDecodingStrategy: nil, userInfo: userInfo)
let options = _FirebaseDecoder._Options(
dateDecodingStrategy: nil,
dataDecodingStrategy: nil,
skipGeoPointAndReference: true,
userInfo: userInfo
)
let decoder = _FirebaseDecoder(referencing: container, options: options)
guard let value = try decoder.unbox(container, as: T.self) else {
throw DecodingError.valueNotFound(T.self, DecodingError.Context(codingPath: [], debugDescription: "The given dictionary was invalid"))
Expand All @@ -23,3 +36,37 @@ open class FirestoreDecoder {
return value
}
}

enum GeoPointKeys: CodingKey {
case latitude, longitude
}

extension GeoPointType {
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: GeoPointKeys.self)
let latitude = try container.decode(Double.self, forKey: .latitude)
let longitude = try container.decode(Double.self, forKey: .longitude)
self.init(latitude: latitude, longitude: longitude)
}

public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: GeoPointKeys.self)
try container.encode(latitude, forKey: .latitude)
try container.encode(longitude, forKey: .longitude)
}
}

enum DocumentReferenceError: Error {
case typeIsNotSupported
case typeIsNotNSObject
}

extension DocumentReferenceType {
public init(from decoder: Decoder) throws {
throw DocumentReferenceError.typeIsNotSupported
}

public func encode(to encoder: Encoder) throws {
throw DocumentReferenceError.typeIsNotSupported
}
}
7 changes: 6 additions & 1 deletion CodableFirebase/FirestoreEncoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,12 @@ open class FirestoreEncoder {
}

internal func encodeToTopLevelContainer<Value : Encodable>(_ value: Value) throws -> Any {
let options = _FirebaseEncoder._Options(dateEncodingStrategy: nil, dataEncodingStrategy: nil, userInfo: userInfo)
let options = _FirebaseEncoder._Options(
dateEncodingStrategy: nil,
dataEncodingStrategy: nil,
skipGeoPointAndReference: true,
userInfo: userInfo
)
let encoder = _FirebaseEncoder(options: options)
guard let topLevel = try encoder.box_(value) else {
throw EncodingError.invalidValue(value,
Expand Down
26 changes: 26 additions & 0 deletions CodableFirebaseTests/TestCodableFirebase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,19 @@ class TestCodableFirebase: XCTestCase {
_testRoundTrip(of: 3 as Double)
}

// MARK: - GeoPoint
func testEncodingGeoPoint() {
let point = Point(latitude: 2, longitude: 2)
XCTAssertEqual((try? FirebaseEncoder().encode(point)) as? NSDictionary, ["latitude": 2, "longitude": 2])
XCTAssertEqual(try? FirebaseDecoder().decode(Point.self, from: ["latitude": 2, "longitude": 2]), point)
}

// MARK: - Document Reference
func testEncodingDocumentReference() {
XCTAssertThrowsError(try FirebaseEncoder().encode(DocumentReference()))
XCTAssertThrowsError(try FirebaseDecoder().decode(DocumentReference.self, from: []))
}

// MARK: - Helper Functions
private var _emptyDictionary: [String: Any] = [:]

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

// MARK: - GeoPoint
struct Point: GeoPointType, Equatable {
let latitude: Double
let longitude: Double

static func == (lhs: Point, rhs: Point) -> Bool {
return lhs.latitude == rhs.latitude && lhs.longitude == rhs.longitude
}
}

// MARK: - ReferenceType
fileprivate struct DocumentReference: DocumentReferenceType {}

// MARK: - Empty Types
fileprivate struct EmptyStruct : Codable, Equatable {
static func ==(_ lhs: EmptyStruct, _ rhs: EmptyStruct) -> Bool {
Expand Down
33 changes: 33 additions & 0 deletions CodableFirebaseTests/TestCodableFirestore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,21 @@ class TestCodableFirestore: XCTestCase {
_testRoundTrip(of: TopLevelWrapper(date), expected: ["value": date])
}

// MARK: - GeoPoint & Document Reference
func testEncodingGeoPoint() {
let point = GeoPoint(latitude: 2, longitude: 2)
let wrapper = TopLevelWrapper(point)
XCTAssertEqual((try? FirestoreEncoder().encode(wrapper)) as NSDictionary?, ["value": point])
XCTAssertEqual(try? FirestoreDecoder().decode(TopLevelWrapper<GeoPoint>.self, from: ["value": point]), wrapper)
XCTAssertThrowsError(try FirestoreEncoder().encode(TopLevelWrapper(Point(latitude: 2, longitude: 2))))
}

func testEncodingDocumentReference() {
let val = TopLevelWrapper(DocumentReference())
XCTAssertEqual((try? FirestoreEncoder().encode(val)) as NSDictionary?, ["value": val.value])
XCTAssertEqual(try? FirestoreDecoder().decode(TopLevelWrapper<DocumentReference>.self, from: ["value": val.value]), val)
}

private func _testEncodeFailure<T : Encodable>(of value: T) {
do {
let _ = try FirestoreEncoder().encode(value)
Expand Down Expand Up @@ -164,3 +179,21 @@ func expectEqualPaths(_ lhs: [CodingKey], _ rhs: [CodingKey], _ prefix: String)
XCTAssertEqual(key1.stringValue, key2.stringValue, "\(prefix) CodingKey.stringValue mismatch: \(type(of: key1))('\(key1.stringValue)') != \(type(of: key2))('\(key2.stringValue)')")
}
}

// MARK: - GeioPoint
fileprivate class GeoPoint: NSObject, GeoPointType {
let latitude: Double
let longitude: Double

required init(latitude: Double, longitude: Double) {
self.latitude = latitude
self.longitude = longitude
}

static func == (lhs: Point, rhs: Point) -> Bool {
return lhs.latitude == rhs.latitude && lhs.longitude == rhs.longitude
}
}

// MARK: - ReferenceType
fileprivate class DocumentReference: NSObject, DocumentReferenceType {}
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,17 @@ Firestore.firestore().collection("data").document("one").getDocument { (document
}
```

### How to use `GeoPoint` and `DocumentRefence` in Firestore

In order to use these 2 types with `Firestore`, you need to add the following code somewhere in your app:

```swift
extension DocumentReference: DocumentReferenceType {}
extension GeoPoint: GeoPointType {}
```

and now they become `Codable` and can be used properly with `FirestoreEncoder` and `FirestoreDecoder`.

## Integration

### CocoaPods (iOS 9+)
Expand Down