30 Commits
0.0.3 ... 0.0.7

Author SHA1 Message Date
Oleksii Dykan
3cae6e90d9 Bump version number 2018-02-20 23:13:02 +01:00
Oleksii Dykan
222b080693 Merge pull request #24 from alickbass/update-encoder
Update Encoder
2018-02-20 23:11:31 +01:00
Oleksii Dykan
5b8627dbbe If the value pushed a container before throwing, pop it back off to restore state 2018-02-20 23:00:29 +01:00
Oleksii Dykan
1384814716 Bump version number 2018-02-11 11:44:17 +01:00
Oleksii Dykan
b0e86866a0 Merge pull request #21 from alickbass/fix-decimal-encoding
Fix handling decimal value
2018-02-11 11:41:33 +01:00
Oleksii Dykan
0c60f70d91 Fix handling decimal value 2018-02-11 11:29:42 +01:00
Oleksii Dykan
df9dfe1dda Fix cocoapods link 2018-01-30 10:56:48 +01:00
Oleksii Dykan
2afa8c101c Bump version number 2018-01-29 11:59:22 +01:00
Oleksii Dykan
ec22c1649e Merge pull request #17 from alickbass/field-type-support
Field type support
2018-01-29 11:52:38 +01:00
Oleksii Dykan
8279c70901 Update README 2018-01-29 11:32:46 +01:00
Oleksii Dykan
c4ed0ec8eb Refactor to FirestoreDecodable and FirestoreEncodable protocols 2018-01-29 11:25:25 +01:00
Oleksii Dykan
fcfb7b8b1e Fix setting option for skipping values 2018-01-29 11:19:33 +01:00
Oleksii Dykan
747d540d77 Rename bool option to skip firestore types 2018-01-29 11:18:27 +01:00
Oleksii Dykan
e6947deb6b Bump version number 2018-01-26 12:53:08 +01:00
Oleksii Dykan
eaf809498a Merge pull request #14 from alickbass/geopoint-and-reference-encoding
Geopoint and reference encoding
2018-01-26 12:39:06 +01:00
Oleksii Dykan
3a08c9e650 Update README 2018-01-26 11:42:35 +01:00
Oleksii Dykan
4cfd13702b Cover firestore types with tests 2018-01-26 11:12:33 +01:00
Oleksii Dykan
3c61d54654 Fix checking for protocol conformance 2018-01-26 10:55:02 +01:00
Oleksii Dykan
2528453f9b Cover firebase database geopoint and reference with tests 2018-01-26 10:20:19 +01:00
Oleksii Dykan
587db265cd Make protocols public 2018-01-26 10:20:03 +01:00
Oleksii Dykan
04facd2f6d Fix skipping encoding 2018-01-26 10:19:44 +01:00
Oleksii Dykan
f3eed29a7d Add encoding the geo point and document reference 2018-01-25 17:18:29 +01:00
Oleksii Dykan
7aff81a267 Add geopointtype and documentreference protocols 2018-01-25 17:14:11 +01:00
Oleksii Dykan
9fa47dd610 Add skip geo and reference flag 2018-01-25 16:33:32 +01:00
Oleksii Dykan
31ecb57f14 Add enum example to README 2018-01-18 14:23:43 +01:00
Oleksii Dykan
6a6fcc1d07 Update README.md 2018-01-16 16:41:55 +01:00
Oleksii Dykan
02cd074714 Update README.md 2018-01-10 17:20:02 +01:00
Oleksii Dykan
69d0a835be Update README.md 2017-12-29 14:55:57 +01:00
Oleksii Dykan
e0b61e57e5 Update README.md 2017-12-28 12:16:28 +01:00
Oleksii Dykan
e32bd8f4d6 Update README.md 2017-12-28 12:12:36 +01:00
11 changed files with 197 additions and 9 deletions

View File

@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = "CodableFirebase"
s.version = "0.0.3"
s.version = "0.0.7"
s.summary = "Use Codable with Firebase"
s.description = "This library helps you use your custom models that conform to Codable protocol with Firebase Realtime Database and Firestore"
s.homepage = "https://github.com/alickbass/CodableFirebase"

View File

@@ -13,6 +13,7 @@ class _FirebaseDecoder : Decoder {
struct _Options {
let dateDecodingStrategy: FirebaseDecoder.DateDecodingStrategy?
let dataDecodingStrategy: FirebaseDecoder.DataDecodingStrategy?
let skipFirestoreTypes: Bool
let userInfo: [CodingUserInfoKey : Any]
}
@@ -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.skipFirestoreTypes && (T.self is FirestoreDecodable.Type) {
decoded = value as! T
} else {
self.storage.push(container: value)
decoded = try T(from: self)

View File

@@ -13,6 +13,7 @@ class _FirebaseEncoder : Encoder {
struct _Options {
let dateEncodingStrategy: FirebaseEncoder.DateEncodingStrategy?
let dataEncodingStrategy: FirebaseEncoder.DataEncodingStrategy?
let skipFirestoreTypes: Bool
let userInfo: [CodingUserInfoKey : Any]
}
@@ -382,14 +383,30 @@ 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 T.self == Decimal.self || T.self == NSDecimalNumber.self {
return (value as! NSDecimalNumber)
} else if options.skipFirestoreTypes && (value is FirestoreEncodable) {
guard let value = value as? NSObject else {
throw DocumentReferenceError.typeIsNotNSObject
}
return value
}
// The value should request a container from the _FirebaseEncoder.
let depth = storage.count
try value.encode(to: self)
let depth = self.storage.count
do {
try value.encode(to: self)
} catch {
// If the value pushed a container before throwing, pop it back off to restore state.
if self.storage.count > depth {
let _ = self.storage.popContainer()
}
throw error
}
// The top container should be a new container.
guard storage.count > depth else {
guard self.storage.count > depth else {
return nil
}

View File

@@ -53,6 +53,7 @@ open class FirebaseDecoder {
let options = _FirebaseDecoder._Options(
dateDecodingStrategy: dateDecodingStrategy,
dataDecodingStrategy: dataDecodingStrategy,
skipFirestoreTypes: false,
userInfo: userInfo
)
let decoder = _FirebaseDecoder(referencing: container, options: options)

View File

@@ -57,6 +57,7 @@ open class FirebaseEncoder {
let options = _FirebaseEncoder._Options(
dateEncodingStrategy: dateEncodingStrategy,
dataEncodingStrategy: dataEncodingStrategy,
skipFirestoreTypes: false,
userInfo: userInfo
)
let encoder = _FirebaseEncoder(options: options)

View File

@@ -8,13 +8,30 @@
import Foundation
public protocol FirestoreDecodable: Decodable {}
public protocol FirestoreEncodable: Encodable {}
public typealias DocumentReferenceType = FirestoreDecodable & FirestoreEncodable
public typealias FieldValueType = FirestoreEncodable
public protocol GeoPointType: FirestoreDecodable, FirestoreEncodable {
var latitude: Double { get }
var longitude: Double { get }
init(latitude: Double, longitude: Double)
}
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,
skipFirestoreTypes: 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"))
@@ -23,3 +40,39 @@ 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 FirestoreDecodable {
public init(from decoder: Decoder) throws {
throw DocumentReferenceError.typeIsNotSupported
}
}
extension FirestoreEncodable {
public func encode(to encoder: Encoder) throws {
throw DocumentReferenceError.typeIsNotSupported
}
}

View File

@@ -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,
skipFirestoreTypes: true,
userInfo: userInfo
)
let encoder = _FirebaseEncoder(options: options)
guard let topLevel = try encoder.box_(value) else {
throw EncodingError.invalidValue(value,

View File

@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>0.0.3</string>
<string>0.0.7</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSPrincipalClass</key>

View File

@@ -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] = [:]
@@ -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 {

View File

@@ -109,6 +109,26 @@ class TestCodableFirestore: XCTestCase {
_testRoundTrip(of: TopLevelWrapper(date), expected: ["value": date])
}
func testDecimalValue() {
let value = Decimal(2)
_testRoundTrip(of: TopLevelWrapper(value), expected: ["value": value])
}
// 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)
@@ -164,3 +184,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 {}

View File

@@ -1,16 +1,20 @@
# CodableFirebase
Use [Codable](https://developer.apple.com/documentation/swift/codable) with [Firebase](https://firebase.google.com)
[![CocoaPods](https://img.shields.io/cocoapods/p/CodableFirebase.svg)](https://github.com/alickbass/CodableFirebase)
[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)
[![Build Status](https://travis-ci.org/alickbass/CodableFirebase.svg?branch=master)](https://travis-ci.org/alickbass/CodableFirebase)
[![codecov](https://codecov.io/gh/alickbass/CodableFirebase/branch/master/graph/badge.svg)](https://codecov.io/gh/alickbass/CodableFirebase)
## Overview
This library helps you to use your custom type that conform to `Codable` protocol with Firebase. Here's an example of model:
This library helps you to use your custom types that conform to `Codable` protocol with Firebase. Here's an example of a custom model:
```swift
struct Model: Codable {
enum MyEnum: Int, Codable {
case one, two, three
}
let stringExample: String
let booleanExample: Bool
let numberExample: Double
@@ -18,6 +22,7 @@ struct Model: Codable {
let arrayExample: [String]
let nullExample: Int?
let objectExample: [String: String]
let myEnum: MyEnum
}
```
@@ -80,3 +85,42 @@ Firestore.firestore().collection("data").document("one").getDocument { (document
}
}
```
### How to use `GeoPoint`, `DocumentRefence`, `FieldValue` 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 {}
extension FieldValue: FieldValueType {}
```
and now they become `Codable` and can be used properly with `FirestoreEncoder` and `FirestoreDecoder`.
***PLEASE NOTE*** that as `FieldValue` is only used to [`setData()` and `updateData()`](https://firebase.google.com/docs/reference/swift/firebasefirestore/api/reference/Classes/FieldValue), it only adopts the `Encodable` protocol.
## Integration
### CocoaPods (iOS 9+)
You can use CocoaPods to install CodableFirebase by adding it to your Podfile:
```swift
platform :ios, '9.0'
use_frameworks!
target 'MyApp' do
pod 'CodableFirebase'
end
```
Note that this requires CocoaPods version 36, and your iOS deployment target to be at least 9.0:
### Carthage (iOS 9+)
You can use Carthage to install CodableFirebase by adding it to your Cartfile:
```swift
github "alickbass/CodableFirebase"
```