2019-06-19 10:00:17 -04:00
/*
* Copyright 2019 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Foundation
import FirebaseFirestore
import FirebaseFirestoreSwift
import XCTest
class FirestoreEncoderTests : XCTestCase {
func testInt () {
struct Model : Codable , Equatable {
let x : Int
}
let model = Model ( x : 42 )
let dict = [ "x" : 42 ]
2019-10-01 10:04:52 -07:00
assertThat ( model ). roundTrips ( to : dict )
2019-06-19 10:00:17 -04:00
}
func testEmpty () {
struct Model : Codable , Equatable {}
2019-10-01 10:04:52 -07:00
assertThat ( Model ()). roundTrips ( to : [ String : Any ]())
2019-06-19 10:00:17 -04:00
}
2019-10-01 10:04:52 -07:00
func testString () throws {
2019-06-19 10:00:17 -04:00
struct Model : Codable , Equatable {
let s : String
}
2019-10-01 10:04:52 -07:00
assertThat ( Model ( s : "abc" )). roundTrips ( to : [ "s" : "abc" ])
2019-06-19 10:00:17 -04:00
}
func testOptional () {
struct Model : Codable , Equatable {
let x : Int
let opt : Int ?
}
2019-10-01 10:04:52 -07:00
assertThat ( Model ( x : 42 , opt : nil )). roundTrips ( to : [ "x" : 42 ])
assertThat ( Model ( x : 42 , opt : 7 )). roundTrips ( to : [ "x" : 42 , "opt" : 7 ])
assertThat ([ "x" : 42 , "opt" : 5 ]). decodes ( to : Model ( x : 42 , opt : 5 ))
assertThat ([ "x" : 42 , "opt" : true ]). failsDecoding ( to : Model . self )
assertThat ([ "x" : 42 , "opt" : "abc" ]). failsDecoding ( to : Model . self )
assertThat ([ "x" : 45.55 , "opt" : 5 ]). failsDecoding ( to : Model . self )
assertThat ([ "opt" : 5 ]). failsDecoding ( to : Model . self )
2019-06-19 10:00:17 -04:00
// TODO: - handle encoding keys with nil values
// See https://stackoverflow.com/questions/47266862/encode-nil-value-as-null-with-jsonencoder
// and https://bugs.swift.org/browse/SR-9232
// XCTAssertTrue(encodedDict.keys.contains("x"))
}
func testEnum () {
enum MyEnum : Codable , Equatable {
case num ( number : Int )
case text ( String )
case timestamp ( Timestamp )
private enum CodingKeys : String , CodingKey {
case num
case text
case timestamp
}
private enum DecodingError : Error {
case decoding ( String )
}
init ( from decoder : Decoder ) throws {
let values = try decoder . container ( keyedBy : CodingKeys . self )
if let value = try ? values . decode ( Int . self , forKey : . num ) {
self = . num ( number : value )
return
}
if let value = try ? values . decode ( String . self , forKey : . text ) {
self = . text ( value )
return
}
if let value = try ? values . decode ( Timestamp . self , forKey : . timestamp ) {
self = . timestamp ( value )
return
}
throw DecodingError . decoding ( "Decoding error: \( dump ( values )) " )
}
func encode ( to encoder : Encoder ) throws {
var container = encoder . container ( keyedBy : CodingKeys . self )
switch self {
case let . num ( number ):
try container . encode ( number , forKey : . num )
case let . text ( value ):
try container . encode ( value , forKey : . text )
case let . timestamp ( stamp ):
try container . encode ( stamp , forKey : . timestamp )
}
}
}
struct Model : Codable , Equatable {
let x : Int
let e : MyEnum
}
2019-10-01 10:04:52 -07:00
assertThat ( Model ( x : 42 , e : MyEnum . num ( number : 4 )))
. roundTrips ( to : [ "x" : 42 , "e" : [ "num" : 4 ]])
assertThat ( Model ( x : 43 , e : MyEnum . text ( "abc" )))
. roundTrips ( to : [ "x" : 43 , "e" : [ "text" : "abc" ]])
2019-06-19 10:00:17 -04:00
let timestamp = Timestamp ( date : Date ())
2019-10-01 10:04:52 -07:00
assertThat ( Model ( x : 43 , e : MyEnum . timestamp ( timestamp )))
. roundTrips ( to : [ "x" : 43 , "e" : [ "timestamp" : timestamp ]])
2019-06-19 10:00:17 -04:00
}
func testGeoPoint () {
struct Model : Codable , Equatable {
let p : GeoPoint
}
let geopoint = GeoPoint ( latitude : 1 , longitude : - 2 )
2019-10-01 10:04:52 -07:00
assertThat ( Model ( p : geopoint )). roundTrips ( to : [ "p" : geopoint ])
2019-06-19 10:00:17 -04:00
}
func testDate () {
struct Model : Codable , Equatable {
let date : Date
}
let date = Date ( timeIntervalSinceReferenceDate : 0 )
2019-10-01 10:04:52 -07:00
assertThat ( Model ( date : date )). roundTrips ( to : [ "date" : date ])
2019-06-19 10:00:17 -04:00
}
2019-08-14 17:00:52 -04:00
func testTimestampCanDecodeAsDate () {
struct EncodingModel : Codable , Equatable {
let date : Timestamp
}
struct DecodingModel : Codable , Equatable {
let date : Date
}
2019-10-01 10:04:52 -07:00
2019-08-14 17:00:52 -04:00
let date = Date ( timeIntervalSinceReferenceDate : 0 )
let timestamp = Timestamp ( date : date )
2019-10-01 10:04:52 -07:00
assertThat ( EncodingModel ( date : timestamp ))
. encodes ( to : [ "date" : timestamp ])
. decodes ( to : DecodingModel ( date : date ))
2019-08-14 17:00:52 -04:00
}
2019-06-24 16:18:30 -04:00
func testDocumentReference () {
struct Model : Codable , Equatable {
let doc : DocumentReference
}
let d = FSTTestDocRef ( "abc/xyz" )
2019-10-01 10:04:52 -07:00
assertThat ( Model ( doc : d )). roundTrips ( to : [ "doc" : d ])
2019-06-24 16:18:30 -04:00
}
func testEncodingDocumentReferenceThrowsWithJSONEncoder () {
2019-10-01 10:04:52 -07:00
assertThat ( FSTTestDocRef ( "abc/xyz" )). failsEncodingWithJSONEncoder ()
2019-06-24 16:18:30 -04:00
}
func testEncodingDocumentReferenceNotEmbeddedThrows () {
2019-10-01 10:04:52 -07:00
assertThat ( FSTTestDocRef ( "abc/xyz" )). failsEncodingAtTopLevel ()
2019-06-24 16:18:30 -04:00
}
2019-06-19 10:00:17 -04:00
func testTimestamp () {
struct Model : Codable , Equatable {
let timestamp : Timestamp
}
let t = Timestamp ( date : Date ())
2019-10-01 10:04:52 -07:00
assertThat ( Model ( timestamp : t )). roundTrips ( to : [ "timestamp" : t ])
2019-06-19 10:00:17 -04:00
}
func testBadValue () {
struct Model : Codable , Equatable {
let x : Int
}
2019-10-01 10:04:52 -07:00
assertThat ([ "x" : "abc" ]). failsDecoding ( to : Model . self ) // Wrong type
2019-06-19 10:00:17 -04:00
}
func testValueTooBig () {
struct Model : Codable , Equatable {
let x : CChar
}
2019-10-01 10:04:52 -07:00
assertThat ( Model ( x : 42 )). roundTrips ( to : [ "x" : 42 ])
assertThat ([ "x" : 12345 ]). failsDecoding ( to : Model . self ) // Overflow
2019-06-19 10:00:17 -04:00
}
// Inspired by https://github.com/firebase/firebase-android-sdk/blob/master/firebase-firestore/src/test/java/com/google/firebase/firestore/util/MapperTest.java
func testBeans () {
struct Model : Codable , Equatable {
let s : String
let d : Double
let f : Float
let l : CLongLong
let i : Int
let b : Bool
let sh : CShort
let byte : CChar
let uchar : CUnsignedChar
let ai : [ Int ]
let si : [ String ]
let caseSensitive : String
let casESensitive : String
let casESensitivE : String
}
let model = Model (
s : "abc" ,
d : 123 ,
f : - 4 ,
l : 1_234_567_890_123 ,
i : - 4444 ,
b : false ,
sh : 123 ,
byte : 45 ,
uchar : 44 ,
ai : [ 1 , 2 , 3 , 4 ],
si : [ "abc" , "def" ],
caseSensitive : "aaa" ,
casESensitive : "bbb" ,
casESensitivE : "ccc"
)
let dict = [
"s" : "abc" ,
"d" : 123 ,
"f" : - 4 ,
"l" : 1_234_567_890_123 ,
"i" : - 4444 ,
"b" : false ,
"sh" : 123 ,
"byte" : 45 ,
"uchar" : 44 ,
"ai" : [ 1 , 2 , 3 , 4 ],
"si" : [ "abc" , "def" ],
"caseSensitive" : "aaa" ,
"casESensitive" : "bbb" ,
"casESensitivE" : "ccc" ,
] as [ String : Any ]
2019-10-01 10:04:52 -07:00
assertThat ( model ). roundTrips ( to : dict )
2019-06-19 10:00:17 -04:00
}
2019-10-01 10:04:52 -07:00
func testCodingKeysCanCustomizeEncodingAndDecoding () throws {
2019-06-19 10:00:17 -04:00
struct Model : Codable , Equatable {
var s : String
2019-10-01 10:04:52 -07:00
var ms : String = "filler"
2019-06-19 10:00:17 -04:00
var d : Double
2019-10-01 10:04:52 -07:00
var md : Double = 42.42
2019-06-19 10:00:17 -04:00
// Use CodingKeys to only encode part of the struct.
enum CodingKeys : String , CodingKey {
case s
case d
}
}
2019-10-01 10:04:52 -07:00
assertThat ( Model ( s : "abc" , ms : "dummy" , d : 123.3 , md : 0 ))
. encodes ( to : [ "s" : "abc" , "d" : 123.3 ])
. decodes ( to : Model ( s : "abc" , ms : "filler" , d : 123.3 , md : 42.42 ))
2019-06-19 10:00:17 -04:00
}
func testNestedObjects () {
struct SecondLevelNestedModel : Codable , Equatable {
var age : Int8
var weight : Double
}
struct NestedModel : Codable , Equatable {
var group : String
var groupList : [ SecondLevelNestedModel ]
var groupMap : [ String : SecondLevelNestedModel ]
var point : GeoPoint
}
struct Model : Codable , Equatable {
var id : Int64
var group : NestedModel
}
2019-10-01 10:04:52 -07:00
let model = Model (
id : 123 ,
group : NestedModel (
group : "g1" ,
groupList : [
SecondLevelNestedModel ( age : 20 , weight : 80.1 ),
SecondLevelNestedModel ( age : 25 , weight : 85.1 ),
2019-06-19 10:00:17 -04:00
],
2019-10-01 10:04:52 -07:00
groupMap : [
"name1" : SecondLevelNestedModel ( age : 30 , weight : 64.2 ),
"name2" : SecondLevelNestedModel ( age : 35 , weight : 79.2 ),
],
point : GeoPoint ( latitude : 12.0 , longitude : 9.1 )
)
)
let dict = [
"group" : [
"group" : "g1" ,
"point" : GeoPoint ( latitude : 12.0 , longitude : 9.1 ),
"groupList" : [
[
"age" : 20 ,
"weight" : 80.1 ,
],
[
"age" : 25 ,
"weight" : 85.1 ,
],
],
"groupMap" : [
"name1" : [
"age" : 30 ,
"weight" : 64.2 ,
],
"name2" : [
"age" : 35 ,
"weight" : 79.2 ,
],
2019-06-19 10:00:17 -04:00
],
],
2019-10-01 10:04:52 -07:00
"id" : 123 ,
] as [ String : Any ]
2019-06-19 10:00:17 -04:00
2019-10-01 10:04:52 -07:00
assertThat ( model ). roundTrips ( to : dict )
2019-06-19 10:00:17 -04:00
}
func testCollapsingNestedObjects () {
// The model is flat but the document has a nested Map.
struct Model : Codable , Equatable {
var id : Int64
var name : String
init ( id : Int64 , name : String ) {
self . id = id
self . name = name
}
private enum CodingKeys : String , CodingKey {
case id
case nested
}
private enum NestedCodingKeys : String , CodingKey {
case name
}
init ( from decoder : Decoder ) throws {
let container = try decoder . container ( keyedBy : CodingKeys . self )
try id = container . decode ( Int64 . self , forKey : . id )
let nestedContainer = try container . nestedContainer ( keyedBy : NestedCodingKeys . self , forKey : . nested )
try name = nestedContainer . decode ( String . self , forKey : . name )
}
func encode ( to encoder : Encoder ) throws {
var container = encoder . container ( keyedBy : CodingKeys . self )
try container . encode ( id , forKey : . id )
var nestedContainer = container . nestedContainer ( keyedBy : NestedCodingKeys . self , forKey : . nested )
try nestedContainer . encode ( name , forKey : . name )
}
}
2019-10-01 10:04:52 -07:00
assertThat ( Model ( id : 12345 , name : "ModelName" ))
. roundTrips ( to : [
"id" : 12345 ,
"nested" : [ "name" : "ModelName" ],
])
2019-06-19 10:00:17 -04:00
}
class SuperModel : Codable , Equatable {
var superPower : Double ? = 100.0
var superName : String ? = "superName"
init ( power : Double , name : String ) {
superPower = power
superName = name
}
static func == ( lhs : SuperModel , rhs : SuperModel ) -> Bool {
return ( lhs . superName == rhs . superName ) && ( lhs . superPower == rhs . superPower )
}
private enum CodingKeys : String , CodingKey {
case superPower
case superName
}
required init ( from decoder : Decoder ) throws {
let container = try decoder . container ( keyedBy : CodingKeys . self )
superPower = try container . decode ( Double . self , forKey : . superPower )
superName = try container . decode ( String . self , forKey : . superName )
}
func encode ( to encoder : Encoder ) throws {
var container = encoder . container ( keyedBy : CodingKeys . self )
try container . encode ( superPower , forKey : . superPower )
try container . encode ( superName , forKey : . superName )
}
}
class SubModel : SuperModel {
var timestamp : Timestamp ? = Timestamp ( seconds : 848_483_737 , nanoseconds : 23423 )
init ( power : Double , name : String , seconds : Int64 , nano : Int32 ) {
super . init ( power : power , name : name )
timestamp = Timestamp ( seconds : seconds , nanoseconds : nano )
}
static func == ( lhs : SubModel , rhs : SubModel ) -> Bool {
return (( lhs as SuperModel ) == ( rhs as SuperModel )) && ( lhs . timestamp == rhs . timestamp )
}
private enum CodingKeys : String , CodingKey {
case timestamp
}
required init ( from decoder : Decoder ) throws {
let container = try decoder . container ( keyedBy : CodingKeys . self )
timestamp = try container . decode ( Timestamp . self , forKey : . timestamp )
try super . init ( from : container . superDecoder ())
}
override func encode ( to encoder : Encoder ) throws {
var container = encoder . container ( keyedBy : CodingKeys . self )
try container . encode ( timestamp , forKey : . timestamp )
try super . encode ( to : container . superEncoder ())
}
}
func testClassHierarchy () {
2019-10-01 10:04:52 -07:00
assertThat ( SubModel ( power : 100 , name : "name" , seconds : 123_456_789 , nano : 654_321 ))
. roundTrips ( to : [
"super" : [ "superPower" : 100 , "superName" : "name" ],
"timestamp" : Timestamp ( seconds : 123_456_789 , nanoseconds : 654_321 ),
])
2019-06-19 10:00:17 -04:00
}
func testEncodingEncodableArrayNotSupported () {
struct Model : Codable , Equatable {
var name : String
}
2019-10-01 10:04:52 -07:00
assertThat ([ Model ( name : "1" )]). failsToEncode ()
2019-06-19 10:00:17 -04:00
}
2019-06-24 16:18:30 -04:00
2019-10-01 10:04:52 -07:00
func testFieldValuePassthrough () throws {
2019-06-24 16:18:30 -04:00
struct Model : Encodable , Equatable {
var fieldValue : FieldValue
}
2019-10-01 10:04:52 -07:00
assertThat ( Model ( fieldValue : FieldValue . delete ()))
. encodes ( to : [ "fieldValue" : FieldValue . delete ()])
2019-06-24 16:18:30 -04:00
}
func testEncodingFieldValueNotEmbeddedThrows () {
let ts = FieldValue . serverTimestamp ()
2019-10-01 10:04:52 -07:00
assertThat ( ts ). failsEncodingAtTopLevel ()
2019-06-24 16:18:30 -04:00
}
2019-10-08 10:11:28 -07:00
#if swift ( >= 5.1 )
func testServerTimestamp () throws {
struct Model : Codable , Equatable {
@ ServerTimestamp var timestamp : Timestamp ? = nil
}
// Encoding a pending server timestamp
assertThat ( Model ())
. encodes ( to : [ "timestamp" : FieldValue . serverTimestamp ()])
// Encoding a resolved server timestamp yields a timestamp; decoding
// yields it back.
let timestamp = Timestamp ( seconds : 123_456_789 , nanoseconds : 4321 )
assertThat ( Model ( timestamp : timestamp ))
. roundTrips ( to : [ "timestamp" : timestamp ])
// Decoding a NSNull() leads to nil.
assertThat ([ "timestamp" : NSNull ()])
. decodes ( to : Model ( timestamp : nil ))
}
func testServerTimestampUserType () throws {
struct Model : Codable , Equatable {
@ ServerTimestamp var timestamp : String ? = nil
}
// Encoding a pending server timestamp
assertThat ( Model ())
. encodes ( to : [ "timestamp" : FieldValue . serverTimestamp ()])
// Encoding a resolved server timestamp yields a timestamp; decoding
// yields it back.
2019-10-15 12:59:36 -07:00
let timestamp = Timestamp ( seconds : 1_570_484_031 , nanoseconds : 122_999_906 )
assertThat ( Model ( timestamp : "2019-10-07T21:33:51.123Z" ))
. roundTrips ( to : [ "timestamp" : timestamp ])
assertThat ( Model ( timestamp : "Invalid date" ))
. failsToEncode ()
2019-10-08 10:11:28 -07:00
}
#endif // swift(>=5.1)
@ available ( swift , deprecated : 5.1 )
func testSwift4ServerTimestamp () throws {
2019-10-01 10:04:52 -07:00
struct Model : Codable , Equatable {
2019-10-08 10:11:28 -07:00
var timestamp : Swift4ServerTimestamp
2019-06-24 16:18:30 -04:00
}
2019-10-01 10:04:52 -07:00
// Encoding a pending server timestamp
assertThat ( Model ( timestamp : . pending ))
. encodes ( to : [ "timestamp" : FieldValue . serverTimestamp ()])
2019-06-24 16:18:30 -04:00
2019-10-01 10:04:52 -07:00
// Encoding a resolved server timestamp yields a timestamp; decoding yields
// it back.
let timestamp = Timestamp ( seconds : 123_456_789 , nanoseconds : 4321 )
assertThat ( Model ( timestamp : . resolved ( timestamp )))
. roundTrips ( to : [ "timestamp" : timestamp ])
2019-06-24 16:18:30 -04:00
2019-10-01 10:04:52 -07:00
// Decoding a NSNull() leads to nil.
assertThat ([ "timestamp" : NSNull ()])
. decodes ( to : Model ( timestamp : . pending ))
2019-06-24 16:18:30 -04:00
}
2019-06-28 15:07:52 -04:00
2019-10-07 09:10:47 -07:00
#if swift ( >= 5.1 )
func testExplicitNull () throws {
struct Model : Codable , Equatable {
@ ExplicitNull var name : String ?
}
assertThat ( Model ( name : nil ))
. roundTrips ( to : [ "name" : NSNull ()])
assertThat ( Model ( name : "good name" ))
. roundTrips ( to : [ "name" : "good name" ])
2019-10-02 16:47:49 -07:00
}
2019-10-07 09:10:47 -07:00
#endif // swift(>=5.1)
2019-10-02 16:47:49 -07:00
@ available ( swift , deprecated : 5.1 )
func testSwift4ExplicitNull () throws {
struct Model : Codable , Equatable {
var name : Swift4ExplicitNull < String >
2019-06-28 15:07:52 -04:00
}
2019-10-01 10:04:52 -07:00
assertThat ( Model ( name : . none ))
. roundTrips ( to : [ "name" : NSNull ()])
2019-06-28 15:07:52 -04:00
2019-10-01 10:04:52 -07:00
assertThat ( Model ( name : . some ( "good name" )))
. roundTrips ( to : [ "name" : "good name" ])
2019-06-28 15:07:52 -04:00
}
2019-09-06 09:26:59 -04:00
2019-10-15 12:59:36 -07:00
#if swift ( >= 5.1 )
func testAutomaticallyPopulatesDocumentIDOnDocumentReference () throws {
struct Model : Codable , Equatable {
var name : String
@ DocumentID var docId : DocumentReference ?
}
assertThat ([ "name" : "abc" ], in : "abc/123" )
. decodes ( to : Model ( name : "abc" , docId : FSTTestDocRef ( "abc/123" )))
2019-09-06 09:26:59 -04:00
}
2019-10-15 12:59:36 -07:00
func testAutomaticallyPopulatesDocumentIDOnString () throws {
struct Model : Codable , Equatable {
var name : String
@ DocumentID var docId : String ?
}
assertThat ([ "name" : "abc" ], in : "abc/123" )
. decodes ( to : Model ( name : "abc" , docId : "123" ))
2019-09-06 09:26:59 -04:00
}
2019-10-15 12:59:36 -07:00
func testDocumentIDIgnoredInEncoding () throws {
struct Model : Codable , Equatable {
var name : String
@ DocumentID var docId : DocumentReference ?
}
assertThat ( Model ( name : "abc" , docId : FSTTestDocRef ( "abc/123" )))
. encodes ( to : [ "name" : "abc" ])
}
func testDocumentIDWithJsonEncoderThrows () {
assertThat ( DocumentID ( wrappedValue : FSTTestDocRef ( "abc/xyz" )))
. failsEncodingWithJSONEncoder ()
}
func testDecodingDocumentIDWithConfictingFieldsThrows () throws {
struct Model : Codable , Equatable {
var name : String
@ DocumentID var docId : DocumentReference ?
}
do {
_ = try Firestore . Decoder (). decode (
Model . self ,
from : [ "name" : "abc" , "docId" : "Causing conflict" ],
in : FSTTestDocRef ( "abc/123" )
)
XCTFail ( "Failed to throw" )
} catch let FirestoreDecodingError . fieldNameConfict ( msg ) {
XCTAssertEqual ( msg , "Field name [ \" docId \" ] was found from document \" abc/123 \" , "
+ "cannot assign the document reference to this field." )
return
} catch {
XCTFail ( "Unrecognized error: \( error ) " )
}
}
#endif // swift(>=5.1)
2019-06-19 10:00:17 -04:00
}
2019-10-01 10:04:52 -07:00
private func assertThat ( _ dictionary : [ String : Any ], in document : String ? = nil , file : StaticString = #file , line : UInt = #line ) -> DictionarySubject {
return DictionarySubject ( dictionary , in : document , file : file , line : line )
}
private func assertThat < X : Equatable & Codable >( _ model : X , file : StaticString = #file , line : UInt = #line ) -> CodableSubject < X > {
return CodableSubject ( model , file : file , line : line )
}
private func assertThat < X : Equatable & Encodable >( _ model : X , file : StaticString = #file , line : UInt = #line ) -> EncodableSubject < X > {
return EncodableSubject ( model , file : file , line : line )
}
private class EncodableSubject < X : Equatable & Encodable > {
var subject : X
var file : StaticString
var line : UInt
init ( _ subject : X , file : StaticString , line : UInt ) {
self . subject = subject
self . file = file
self . line = line
}
@ discardableResult
func encodes ( to expected : [ String : Any ]) -> DictionarySubject {
let encoded = assertEncodes ( to : expected )
return DictionarySubject ( encoded , file : file , line : line )
}
func failsToEncode () {
do {
_ = try Firestore . Encoder (). encode ( subject )
} catch {
return
}
XCTFail ( "Failed to throw" )
}
func failsEncodingWithJSONEncoder () {
do {
_ = try JSONEncoder (). encode ( subject )
XCTFail ( "Failed to throw" , file : file , line : line )
} catch FirestoreEncodingError . encodingIsNotSupported {
return
} catch {
XCTFail ( "Unrecognized error: \( error ) " , file : file , line : line )
}
}
func failsEncodingAtTopLevel () {
do {
_ = try Firestore . Encoder (). encode ( subject )
XCTFail ( "Failed to throw" , file : file , line : line )
} catch EncodingError . invalidValue ( _ , _ ) {
return
} catch {
XCTFail ( "Unrecognized error: \( error ) " , file : file , line : line )
}
}
private func assertEncodes ( to expected : [ String : Any ]) -> [ String : Any ] {
do {
let enc = try Firestore . Encoder (). encode ( subject )
XCTAssertEqual ( enc as NSDictionary , expected as NSDictionary , file : file , line : line )
return enc
} catch {
XCTFail ( "Failed to encode \( X . self ) : error: \( error ) " )
return [ "" : - 1 ]
}
}
}
private class CodableSubject < X : Equatable & Codable >: EncodableSubject < X > {
func roundTrips ( to expected : [ String : Any ]) {
let reverseSubject = encodes ( to : expected )
reverseSubject . decodes ( to : subject )
}
}
private class DictionarySubject {
var subject : [ String : Any ]
var document : DocumentReference ?
var file : StaticString
var line : UInt
init ( _ subject : [ String : Any ], in documentName : String ? = nil , file : StaticString , line : UInt ) {
self . subject = subject
if let documentName = documentName {
document = FSTTestDocRef ( documentName )
}
self . file = file
self . line = line
}
func decodes < X : Equatable & Codable >( to expected : X ) -> Void {
do {
let decoded = try Firestore . Decoder (). decode ( X . self , from : subject , in : document )
XCTAssertEqual ( decoded , expected )
} catch {
XCTFail ( "Failed to decode \( X . self ) : \( error ) " , file : file , line : line )
}
}
func failsDecoding < X : Equatable & Codable >( to _ : X . Type ) -> Void {
XCTAssertThrowsError ( try Firestore . Decoder (). decode ( X . self , from : subject ), file : file , line : line )
}
}
2019-10-08 10:11:28 -07:00
#if swift ( >= 5.1 )
2019-10-15 12:59:36 -07:00
enum DateError : Error {
case invalidDate ( String )
}
2019-10-08 10:11:28 -07:00
// Extends Strings to allow them to be wrapped with @ServerTimestamp. Resolved
2019-10-15 12:59:36 -07:00
// server timestamps will be stored in an ISO 8601 date format.
2019-10-08 10:11:28 -07:00
//
// This example exists outside the main implementation to show that users can
2019-10-15 12:59:36 -07:00
// extend @ServerTimestamp with arbitrary types.
2019-10-08 10:11:28 -07:00
extension String : ServerTimestampWrappable {
2019-10-15 12:59:36 -07:00
static let formatter : DateFormatter = {
2019-10-08 10:11:28 -07:00
let formatter = DateFormatter ()
formatter . calendar = Calendar ( identifier : . iso8601 )
formatter . locale = Locale ( identifier : "en_US_POSIX" )
formatter . timeZone = TimeZone ( secondsFromGMT : 0 )
formatter . dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSXXXXX"
return formatter
}()
2019-10-15 12:59:36 -07:00
public static func wrap ( _ timestamp : Timestamp ) throws -> Self {
return formatter . string ( from : timestamp . dateValue ())
2019-10-08 10:11:28 -07:00
}
2019-10-15 12:59:36 -07:00
public static func unwrap ( _ value : Self ) throws -> Timestamp {
let date = formatter . date ( from : value )
if let date = date {
return Timestamp ( date : date )
} else {
throw DateError . invalidDate ( value )
}
2019-10-08 10:11:28 -07:00
}
}
#endif // swift(>=5.1)