aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--.gitignore8
-rw-r--r--.travis.yml103
-rw-r--r--CMakeLists.txt23
-rw-r--r--CODE_OF_CONDUCT.md93
-rw-r--r--Carthage.md85
-rw-r--r--Example/Auth/ApiTests/AuthCredentialsTemplate.h4
-rw-r--r--Example/Auth/ApiTests/FirebaseAuthApiTests.m141
-rw-r--r--Example/Auth/App/GoogleService-Info.plist2
-rw-r--r--Example/Auth/App/tvOS/AppDelegate.h21
-rw-r--r--Example/Auth/App/tvOS/AppDelegate.m63
-rw-r--r--Example/Auth/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json11
-rw-r--r--Example/Auth/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json6
-rw-r--r--Example/Auth/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json17
-rw-r--r--Example/Auth/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json11
-rw-r--r--Example/Auth/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json6
-rw-r--r--Example/Auth/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json11
-rw-r--r--Example/Auth/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json6
-rw-r--r--Example/Auth/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json16
-rw-r--r--Example/Auth/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json6
-rw-r--r--Example/Auth/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json17
-rw-r--r--Example/Auth/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json16
-rw-r--r--Example/Auth/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json6
-rw-r--r--Example/Auth/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json16
-rw-r--r--Example/Auth/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json6
-rw-r--r--Example/Auth/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json32
-rw-r--r--Example/Auth/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json16
-rw-r--r--Example/Auth/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json16
-rw-r--r--Example/Auth/App/tvOS/Assets.xcassets/Contents.json6
-rw-r--r--Example/Auth/App/tvOS/Assets.xcassets/LaunchImage.launchimage/Contents.json22
-rw-r--r--Example/Auth/App/tvOS/Info.plist32
-rw-r--r--Example/Auth/App/tvOS/Main.storyboard28
-rw-r--r--Example/Auth/App/tvOS/ViewController.h19
-rw-r--r--Example/Auth/App/tvOS/ViewController.m33
-rw-r--r--Example/Auth/App/tvOS/main.m22
-rw-r--r--Example/Auth/Sample/MainViewController.m299
-rw-r--r--Example/Auth/Tests/FIRAuthAPNSTokenManagerTests.m2
-rw-r--r--Example/Auth/Tests/FIRAuthDispatcherTests.m2
-rw-r--r--Example/Auth/Tests/FIRAuthTests.m470
-rw-r--r--Example/Auth/Tests/FIRAuthURLPresenterTests.m2
-rw-r--r--Example/Auth/Tests/FIREmailLinkRequestTests.m137
-rw-r--r--Example/Auth/Tests/FIREmailLinkSignInResponseTests.m195
-rw-r--r--Example/Auth/Tests/FIRGetOOBConfirmationCodeRequestTests.m43
-rw-r--r--Example/Auth/Tests/FIRPhoneAuthProviderTests.m4
-rw-r--r--Example/Auth/Tests/FIRUserTests.m198
-rw-r--r--Example/Auth/Tests/FIRVerifyPasswordRequestTest.m16
-rw-r--r--Example/Core/App/GoogleService-Info.plist2
-rw-r--r--Example/Core/App/tvOS/AppDelegate.h21
-rw-r--r--Example/Core/App/tvOS/AppDelegate.m62
-rw-r--r--Example/Core/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json11
-rw-r--r--Example/Core/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json6
-rw-r--r--Example/Core/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json17
-rw-r--r--Example/Core/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json11
-rw-r--r--Example/Core/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json6
-rw-r--r--Example/Core/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json11
-rw-r--r--Example/Core/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json6
-rw-r--r--Example/Core/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json16
-rw-r--r--Example/Core/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json6
-rw-r--r--Example/Core/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json17
-rw-r--r--Example/Core/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json16
-rw-r--r--Example/Core/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json6
-rw-r--r--Example/Core/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json16
-rw-r--r--Example/Core/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json6
-rw-r--r--Example/Core/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json32
-rw-r--r--Example/Core/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json16
-rw-r--r--Example/Core/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json16
-rw-r--r--Example/Core/App/tvOS/Assets.xcassets/Contents.json6
-rw-r--r--Example/Core/App/tvOS/Assets.xcassets/LaunchImage.launchimage/Contents.json22
-rw-r--r--Example/Core/App/tvOS/Info.plist32
-rw-r--r--Example/Core/App/tvOS/Main.storyboard28
-rw-r--r--Example/Core/App/tvOS/ViewController.h19
-rw-r--r--Example/Core/App/tvOS/ViewController.m33
-rw-r--r--Example/Core/App/tvOS/main.m22
-rw-r--r--Example/Core/Tests/FIRAppAssociationRegistrationUnitTests.m2
-rw-r--r--Example/Core/Tests/FIRAppTest.m58
-rw-r--r--Example/Core/Tests/FIRLoggerTest.m21
-rw-r--r--Example/Core/Tests/FIROptionsTest.m117
-rw-r--r--Example/Core/Tests/FIRTestCase.m2
-rw-r--r--Example/Database/App/GoogleService-Info.plist2
-rw-r--r--Example/Database/App/tvOS/AppDelegate.h21
-rw-r--r--Example/Database/App/tvOS/AppDelegate.m59
-rw-r--r--Example/Database/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json11
-rw-r--r--Example/Database/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json6
-rw-r--r--Example/Database/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json17
-rw-r--r--Example/Database/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json11
-rw-r--r--Example/Database/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json6
-rw-r--r--Example/Database/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json11
-rw-r--r--Example/Database/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json6
-rw-r--r--Example/Database/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json16
-rw-r--r--Example/Database/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json6
-rw-r--r--Example/Database/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json17
-rw-r--r--Example/Database/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json16
-rw-r--r--Example/Database/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json6
-rw-r--r--Example/Database/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json16
-rw-r--r--Example/Database/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json6
-rw-r--r--Example/Database/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json32
-rw-r--r--Example/Database/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json16
-rw-r--r--Example/Database/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json16
-rw-r--r--Example/Database/App/tvOS/Assets.xcassets/Contents.json6
-rw-r--r--Example/Database/App/tvOS/Assets.xcassets/LaunchImage.launchimage/Contents.json22
-rw-r--r--Example/Database/App/tvOS/Info.plist32
-rw-r--r--Example/Database/App/tvOS/Main.storyboard28
-rw-r--r--Example/Database/App/tvOS/ViewController.h19
-rw-r--r--Example/Database/App/tvOS/ViewController.m33
-rw-r--r--Example/Database/App/tvOS/main.m22
-rw-r--r--Example/Database/Tests/Integration/FData.m6
-rw-r--r--Example/Database/Tests/Integration/FRealtime.m1
-rw-r--r--Example/Database/Tests/Unit/FSyncPointTests.m6
-rw-r--r--Example/Firebase.xcodeproj/project.pbxproj3730
-rw-r--r--Example/Firebase.xcodeproj/xcshareddata/xcschemes/AllUnitTests_tvOS.xcscheme131
-rw-r--r--Example/Firebase.xcodeproj/xcshareddata/xcschemes/Auth_Example_tvOS.xcscheme103
-rw-r--r--Example/Firebase.xcodeproj/xcshareddata/xcschemes/Auth_Tests_tvOS.xcscheme68
-rw-r--r--Example/Firebase.xcodeproj/xcshareddata/xcschemes/Core_Example_tvOS.xcscheme103
-rw-r--r--Example/Firebase.xcodeproj/xcshareddata/xcschemes/Core_Tests_tvOS.xcscheme58
-rw-r--r--Example/Firebase.xcodeproj/xcshareddata/xcschemes/Database_Example_tvOS.xcscheme103
-rw-r--r--Example/Firebase.xcodeproj/xcshareddata/xcschemes/Database_Tests_tvOS.xcscheme58
-rw-r--r--Example/Firebase.xcodeproj/xcshareddata/xcschemes/Messaging_Sample_iOS.xcscheme91
-rw-r--r--Example/Firebase.xcodeproj/xcshareddata/xcschemes/Storage_Example_tvOS.xcscheme103
-rw-r--r--Example/Firebase.xcodeproj/xcshareddata/xcschemes/Storage_Tests_tvOS.xcscheme58
-rw-r--r--Example/Messaging/App/iOS/Base.lproj/LaunchScreen.storyboard12
-rw-r--r--Example/Messaging/App/iOS/Base.lproj/Main.storyboard47
-rw-r--r--Example/Messaging/App/iOS/FIRAppDelegate.h23
-rw-r--r--Example/Messaging/App/iOS/FIRAppDelegate.m58
-rw-r--r--Example/Messaging/App/iOS/FIRViewController.h21
-rw-r--r--Example/Messaging/App/iOS/FIRViewController.m33
-rw-r--r--Example/Messaging/App/iOS/main.m22
-rw-r--r--Example/Messaging/Sample/GoogleService-Info.plist (renamed from Example/Messaging/App/GoogleService-Info.plist)2
-rw-r--r--Example/Messaging/Sample/iOS/AppDelegate.swift (renamed from Example/Messaging/App/iOS/AppDelegate.swift)0
-rw-r--r--Example/Messaging/Sample/iOS/Base.lproj/LaunchScreen.storyboard27
-rw-r--r--Example/Messaging/Sample/iOS/Base.lproj/Main.storyboard48
-rw-r--r--Example/Messaging/Sample/iOS/Data+MessagingExtensions.swift (renamed from Example/Messaging/App/iOS/Data+MessagingExtensions.swift)0
-rw-r--r--Example/Messaging/Sample/iOS/Environment.swift (renamed from Example/Messaging/App/iOS/Environment.swift)0
-rw-r--r--Example/Messaging/Sample/iOS/Messaging-Info.plist (renamed from Example/Messaging/App/iOS/Messaging-Info.plist)2
-rw-r--r--Example/Messaging/Sample/iOS/MessagingViewController.swift (renamed from Example/Messaging/App/iOS/MessagingViewController.swift)0
-rw-r--r--Example/Messaging/Sample/iOS/Messaging_Example.entitlements (renamed from Example/Messaging/App/iOS/Messaging_Example.entitlements)0
-rw-r--r--Example/Messaging/Sample/iOS/NotificationsController.swift (renamed from Example/Messaging/App/iOS/NotificationsController.swift)0
-rw-r--r--Example/Messaging/Tests/FIRMessagingClientTest.m2
-rw-r--r--Example/Messaging/Tests/FIRMessagingCodedInputStreamTest.m2
-rw-r--r--Example/Messaging/Tests/FIRMessagingConnectionTest.m2
-rw-r--r--Example/Messaging/Tests/FIRMessagingDataMessageManagerTest.m2
-rw-r--r--Example/Messaging/Tests/FIRMessagingLinkHandlingTest.m2
-rw-r--r--Example/Messaging/Tests/FIRMessagingPendingTopicsListTest.m11
-rw-r--r--Example/Messaging/Tests/FIRMessagingPubSubTest.m2
-rw-r--r--Example/Messaging/Tests/FIRMessagingRegistrarTest.m45
-rw-r--r--Example/Messaging/Tests/FIRMessagingRemoteNotificationsProxyTest.m4
-rw-r--r--Example/Messaging/Tests/FIRMessagingSecureSocketTest.m2
-rw-r--r--Example/Messaging/Tests/FIRMessagingServiceTest.m58
-rw-r--r--Example/Messaging/Tests/FIRMessagingTest.m38
-rw-r--r--Example/Messaging/Tests/Info.plist2
-rw-r--r--Example/Podfile62
-rw-r--r--Example/Shared/FIRSampleAppUtilities.m1
-rw-r--r--Example/Storage/App/GoogleService-Info.plist2
-rw-r--r--Example/Storage/App/iOS/FIRAppDelegate.m2
-rw-r--r--Example/Storage/App/tvOS/AppDelegate.h21
-rw-r--r--Example/Storage/App/tvOS/AppDelegate.m63
-rw-r--r--Example/Storage/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json11
-rw-r--r--Example/Storage/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json6
-rw-r--r--Example/Storage/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json17
-rw-r--r--Example/Storage/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json11
-rw-r--r--Example/Storage/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json6
-rw-r--r--Example/Storage/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json11
-rw-r--r--Example/Storage/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json6
-rw-r--r--Example/Storage/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json16
-rw-r--r--Example/Storage/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json6
-rw-r--r--Example/Storage/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json17
-rw-r--r--Example/Storage/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json16
-rw-r--r--Example/Storage/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json6
-rw-r--r--Example/Storage/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json16
-rw-r--r--Example/Storage/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json6
-rw-r--r--Example/Storage/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json32
-rw-r--r--Example/Storage/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json16
-rw-r--r--Example/Storage/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json16
-rw-r--r--Example/Storage/App/tvOS/Assets.xcassets/Contents.json6
-rw-r--r--Example/Storage/App/tvOS/Assets.xcassets/LaunchImage.launchimage/Contents.json22
-rw-r--r--Example/Storage/App/tvOS/Info.plist32
-rw-r--r--Example/Storage/App/tvOS/Main.storyboard28
-rw-r--r--Example/Storage/App/tvOS/ViewController.h19
-rw-r--r--Example/Storage/App/tvOS/ViewController.m33
-rw-r--r--Example/Storage/App/tvOS/main.m22
-rw-r--r--Example/Storage/Tests/Integration/FIRStorageIntegrationTests.m34
-rw-r--r--Example/Storage/Tests/Unit/FIRStorageMetadataTests.m60
-rw-r--r--Example/Storage/Tests/Unit/FIRStorageTests.m2
-rw-r--r--Example/tvOSSample/Podfile14
-rw-r--r--Example/tvOSSample/tvOSSample.xcodeproj/project.pbxproj424
-rw-r--r--Example/tvOSSample/tvOSSample/AppDelegate.swift27
-rw-r--r--Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json11
-rw-r--r--Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json6
-rw-r--r--Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json17
-rw-r--r--Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json11
-rw-r--r--Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json6
-rw-r--r--Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json11
-rw-r--r--Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json6
-rw-r--r--Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json16
-rw-r--r--Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json6
-rw-r--r--Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json17
-rw-r--r--Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json16
-rw-r--r--Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json6
-rw-r--r--Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json16
-rw-r--r--Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json6
-rw-r--r--Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json32
-rw-r--r--Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json16
-rw-r--r--Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json16
-rw-r--r--Example/tvOSSample/tvOSSample/Assets.xcassets/Contents.json6
-rw-r--r--Example/tvOSSample/tvOSSample/Assets.xcassets/LaunchImage.launchimage/Contents.json22
-rw-r--r--Example/tvOSSample/tvOSSample/AuthLoginViewController.swift29
-rw-r--r--Example/tvOSSample/tvOSSample/AuthViewController.swift86
-rw-r--r--Example/tvOSSample/tvOSSample/Base.lproj/Main.storyboard355
-rw-r--r--Example/tvOSSample/tvOSSample/DatabaseViewController.swift83
-rw-r--r--Example/tvOSSample/tvOSSample/EmailLoginViewController.swift94
-rw-r--r--Example/tvOSSample/tvOSSample/Info.plist32
-rw-r--r--Example/tvOSSample/tvOSSample/StorageViewController.swift148
-rw-r--r--Firebase/Auth/CHANGELOG.md37
-rw-r--r--Firebase/Auth/FirebaseAuth.podspec1
-rw-r--r--Firebase/Auth/Source/AuthProviders/EmailPassword/FIREmailAuthProvider.m4
-rw-r--r--Firebase/Auth/Source/AuthProviders/EmailPassword/FIREmailPasswordAuthCredential.h13
-rw-r--r--Firebase/Auth/Source/AuthProviders/EmailPassword/FIREmailPasswordAuthCredential.m9
-rw-r--r--Firebase/Auth/Source/AuthProviders/Phone/FIRPhoneAuthProvider.m30
-rw-r--r--Firebase/Auth/Source/FIRAuth.m357
-rw-r--r--Firebase/Auth/Source/FIRAuthAPNSTokenManager.m10
-rw-r--r--Firebase/Auth/Source/FIRAuthErrorUtils.h7
-rw-r--r--Firebase/Auth/Source/FIRAuthErrorUtils.m19
-rw-r--r--Firebase/Auth/Source/FIRAuthInternalErrors.h6
-rw-r--r--Firebase/Auth/Source/FIRAuthNotificationManager.m8
-rw-r--r--Firebase/Auth/Source/FIRAuthProvider.m25
-rw-r--r--Firebase/Auth/Source/FIRAuthSerialTaskQueue.m6
-rw-r--r--Firebase/Auth/Source/FIRAuthTokenResult.m110
-rw-r--r--Firebase/Auth/Source/FIRAuthTokenResult_Internal.h37
-rw-r--r--Firebase/Auth/Source/FIRAuthURLPresenter.m28
-rw-r--r--Firebase/Auth/Source/FIRSecureTokenService.m13
-rw-r--r--Firebase/Auth/Source/FIRUser.m188
-rw-r--r--Firebase/Auth/Source/FIRUser_Internal.h6
-rw-r--r--Firebase/Auth/Source/Public/FIRAuth.h94
-rw-r--r--Firebase/Auth/Source/Public/FIRAuthErrors.h5
-rw-r--r--Firebase/Auth/Source/Public/FIRAuthTokenResult.h66
-rw-r--r--Firebase/Auth/Source/Public/FIREmailAuthProvider.h22
-rw-r--r--Firebase/Auth/Source/Public/FIRFacebookAuthProvider.h5
-rw-r--r--Firebase/Auth/Source/Public/FIRGitHubAuthProvider.h6
-rw-r--r--Firebase/Auth/Source/Public/FIRGoogleAuthProvider.h5
-rw-r--r--Firebase/Auth/Source/Public/FIRPhoneAuthProvider.h6
-rw-r--r--Firebase/Auth/Source/Public/FIRTwitterAuthProvider.h6
-rw-r--r--Firebase/Auth/Source/Public/FIRUser.h52
-rw-r--r--Firebase/Auth/Source/Public/FirebaseAuth.h1
-rw-r--r--Firebase/Auth/Source/RPCs/FIRAuthBackend.h32
-rw-r--r--Firebase/Auth/Source/RPCs/FIRAuthBackend.m19
-rw-r--r--Firebase/Auth/Source/RPCs/FIRCreateAuthURIResponse.h5
-rw-r--r--Firebase/Auth/Source/RPCs/FIRCreateAuthURIResponse.m3
-rw-r--r--Firebase/Auth/Source/RPCs/FIRDeleteAccountResponse.m2
-rw-r--r--Firebase/Auth/Source/RPCs/FIREmailLinkSignInRequest.h66
-rw-r--r--Firebase/Auth/Source/RPCs/FIREmailLinkSignInRequest.m70
-rw-r--r--Firebase/Auth/Source/RPCs/FIREmailLinkSignInResponse.h54
-rw-r--r--Firebase/Auth/Source/RPCs/FIREmailLinkSignInResponse.m32
-rw-r--r--Firebase/Auth/Source/RPCs/FIRGetOOBConfirmationCodeRequest.h22
-rw-r--r--Firebase/Auth/Source/RPCs/FIRGetOOBConfirmationCodeRequest.m24
-rw-r--r--Firebase/Auth/Source/RPCs/FIRGetOOBConfirmationCodeResponse.m2
-rw-r--r--Firebase/Auth/Source/RPCs/FIRResetPasswordResponse.m2
-rw-r--r--Firebase/Auth/Source/RPCs/FIRSetAccountInfoResponse.m2
-rw-r--r--Firebase/Auth/Source/RPCs/FIRSignUpNewUserResponse.m2
-rw-r--r--Firebase/Auth/Source/RPCs/FIRVerifyAssertionResponse.m2
-rw-r--r--Firebase/Auth/Source/RPCs/FIRVerifyCustomTokenResponse.m2
-rw-r--r--Firebase/Auth/Source/RPCs/FIRVerifyPasswordResponse.m2
-rw-r--r--Firebase/Core/CHANGELOG.md35
-rw-r--r--Firebase/Core/FIRApp.m68
-rw-r--r--Firebase/Core/FIRConfiguration.m6
-rw-r--r--Firebase/Core/FIRErrors.m4
-rw-r--r--Firebase/Core/FIRLogger.m17
-rw-r--r--Firebase/Core/FIRMutableDictionary.m14
-rw-r--r--Firebase/Core/FIRNetworkConstants.m2
-rw-r--r--Firebase/Core/FIRNetworkURLSession.m15
-rw-r--r--Firebase/Core/FIROptions.m92
-rw-r--r--Firebase/Core/FIRReachabilityChecker.m4
-rw-r--r--Firebase/Core/FIRVersion.m36
-rw-r--r--Firebase/Core/Private/FIRAppInternal.h17
-rw-r--r--Firebase/Core/Private/FIRErrors.h10
-rw-r--r--Firebase/Core/Private/FIRLogger.h1
-rw-r--r--Firebase/Core/Private/FIRMutableDictionary.h2
-rw-r--r--Firebase/Core/Private/FIRNetwork.h2
-rw-r--r--Firebase/Core/Private/FIRNetworkConstants.h2
-rw-r--r--Firebase/Core/Private/FIRNetworkLoggerProtocol.h2
-rw-r--r--Firebase/Core/Private/FIRNetworkURLSession.h2
-rw-r--r--Firebase/Core/Private/FIRReachabilityChecker.h4
-rw-r--r--Firebase/Core/Private/FIRVersion.h (renamed from Firestore/Source/Auth/FSTEmptyCredentialsProvider.h)13
-rw-r--r--Firebase/Core/Public/FIRApp.h16
-rw-r--r--Firebase/Core/Public/FIRConfiguration.h28
-rw-r--r--Firebase/Core/Public/FIROptions.h25
-rw-r--r--Firebase/Core/third_party/FIRAppEnvironmentUtil.h14
-rw-r--r--Firebase/Core/third_party/FIRAppEnvironmentUtil.m47
-rw-r--r--Firebase/Database/CHANGELOG.md10
-rw-r--r--Firebase/Database/Core/FPersistentConnection.m18
-rw-r--r--Firebase/Database/Core/FRepo.m8
-rw-r--r--Firebase/Database/Persistence/FLevelDBStorageEngine.m4
-rw-r--r--Firebase/Database/Realtime/FWebSocketConnection.m4
-rw-r--r--Firebase/Database/module.modulemap13
-rw-r--r--Firebase/Database/third_party/SocketRocket/FSRWebSocket.m38
-rw-r--r--Firebase/Messaging/CHANGELOG.md91
-rw-r--r--Firebase/Messaging/FIRMMessageCode.h3
-rw-r--r--Firebase/Messaging/FIRMessaging.m168
-rw-r--r--Firebase/Messaging/FIRMessagingClient.h2
-rw-r--r--Firebase/Messaging/FIRMessagingClient.m6
-rw-r--r--Firebase/Messaging/FIRMessagingContextManagerService.m5
-rw-r--r--Firebase/Messaging/FIRMessagingInstanceIDProxy.h56
-rw-r--r--Firebase/Messaging/FIRMessagingInstanceIDProxy.m123
-rw-r--r--Firebase/Messaging/FIRMessagingPendingTopicsList.h1
-rw-r--r--Firebase/Messaging/FIRMessagingPendingTopicsList.m86
-rw-r--r--Firebase/Messaging/FIRMessagingPubSub.h25
-rw-r--r--Firebase/Messaging/FIRMessagingPubSub.m38
-rw-r--r--Firebase/Messaging/FIRMessagingRegistrar.h2
-rw-r--r--Firebase/Messaging/FIRMessagingRegistrar.m2
-rw-r--r--Firebase/Messaging/FIRMessagingRemoteNotificationsProxy.m19
-rw-r--r--Firebase/Messaging/FIRMessagingRmq2PersistentStore.m5
-rw-r--r--Firebase/Messaging/FIRMessagingTopicOperation.h1
-rw-r--r--Firebase/Messaging/FIRMessagingTopicOperation.m26
-rw-r--r--Firebase/Messaging/FIRMessagingTopicsCommon.h23
-rw-r--r--Firebase/Messaging/FIRMessaging_Private.h5
-rw-r--r--Firebase/Messaging/NSError+FIRMessaging.h1
-rw-r--r--Firebase/Messaging/Protos/GtalkCore.pbobjc.h56
-rw-r--r--Firebase/Messaging/Protos/GtalkCore.pbobjc.m122
-rw-r--r--Firebase/Messaging/Public/FIRMessaging.h151
-rw-r--r--Firebase/Storage/CHANGELOG.md16
-rw-r--r--Firebase/Storage/FIRStorageDeleteTask.m2
-rw-r--r--Firebase/Storage/FIRStorageDownloadTask.m6
-rw-r--r--Firebase/Storage/FIRStorageErrors.m21
-rw-r--r--Firebase/Storage/FIRStorageGetDownloadURLTask.m117
-rw-r--r--Firebase/Storage/FIRStorageGetMetadataTask.m38
-rw-r--r--Firebase/Storage/FIRStorageMetadata.m47
-rw-r--r--Firebase/Storage/FIRStorageObservableTask.m3
-rw-r--r--Firebase/Storage/FIRStorageReference.m16
-rw-r--r--Firebase/Storage/FIRStorageUpdateMetadataTask.m38
-rw-r--r--Firebase/Storage/FIRStorageUploadTask.m17
-rw-r--r--Firebase/Storage/FIRStorageUtils.m2
-rw-r--r--Firebase/Storage/Private/FIRStorageConstants_Private.h1
-rw-r--r--Firebase/Storage/Private/FIRStorageErrors.h18
-rw-r--r--Firebase/Storage/Private/FIRStorageGetDownloadURLTask.h (renamed from Firestore/core/src/firebase/firestore/base/port.h)27
-rw-r--r--Firebase/Storage/Private/FIRStorageGetDownloadURLTask_Private.h31
-rw-r--r--Firebase/Storage/Public/FIRStorage.h3
-rw-r--r--Firebase/Storage/Public/FIRStorageMetadata.h12
-rw-r--r--FirebaseAuth.podspec12
-rw-r--r--FirebaseCore.podspec16
-rw-r--r--FirebaseDatabase.podspec5
-rw-r--r--FirebaseFirestore.podspec52
-rw-r--r--FirebaseFirestoreSwift.podspec37
-rw-r--r--FirebaseFunctions.podspec33
-rw-r--r--FirebaseMessaging.podspec4
-rw-r--r--FirebaseStorage.podspec5
-rw-r--r--Firestore/CHANGELOG.md77
-rw-r--r--Firestore/CMakeLists.txt98
-rw-r--r--Firestore/Example/Firestore.xcodeproj/project.pbxproj1046
-rw-r--r--Firestore/Example/Firestore.xcodeproj/xcshareddata/xcschemes/AllTests.xcscheme24
-rw-r--r--Firestore/Example/Firestore.xcodeproj/xcshareddata/xcschemes/Firestore_Tests.xcscheme56
-rw-r--r--Firestore/Example/Podfile7
-rw-r--r--Firestore/Example/SwiftBuildTest/main.swift471
-rw-r--r--Firestore/Example/Tests/API/FIRCollectionReferenceTests.mm43
-rw-r--r--Firestore/Example/Tests/API/FIRDocumentReferenceTests.mm43
-rw-r--r--Firestore/Example/Tests/API/FIRDocumentSnapshotTests.mm59
-rw-r--r--Firestore/Example/Tests/API/FIRFieldPathTests.mm49
-rw-r--r--Firestore/Example/Tests/API/FIRFieldValueTests.mm46
-rw-r--r--Firestore/Example/Tests/API/FIRFirestoreTests.mm65
-rw-r--r--Firestore/Example/Tests/API/FIRGeoPointTests.mm (renamed from Firestore/Example/Tests/API/FIRGeoPointTests.m)23
-rw-r--r--Firestore/Example/Tests/API/FIRQuerySnapshotTests.mm56
-rw-r--r--Firestore/Example/Tests/API/FIRQueryTests.mm84
-rw-r--r--Firestore/Example/Tests/API/FIRSnapshotMetadataTests.mm52
-rw-r--r--Firestore/Example/Tests/API/FIRTimestampTest.m102
-rw-r--r--Firestore/Example/Tests/API/FSTAPIHelpers.h76
-rw-r--r--Firestore/Example/Tests/API/FSTAPIHelpers.mm124
-rw-r--r--Firestore/Example/Tests/Core/FSTDatabaseInfoTests.m59
-rw-r--r--Firestore/Example/Tests/Core/FSTEventManagerTests.mm (renamed from Firestore/Example/Tests/Core/FSTEventManagerTests.m)20
-rw-r--r--Firestore/Example/Tests/Core/FSTQueryListenerTests.mm (renamed from Firestore/Example/Tests/Core/FSTQueryListenerTests.m)103
-rw-r--r--Firestore/Example/Tests/Core/FSTQueryTests.m577
-rw-r--r--Firestore/Example/Tests/Core/FSTQueryTests.mm578
-rw-r--r--Firestore/Example/Tests/Core/FSTSyncEngine+Testing.h7
-rw-r--r--Firestore/Example/Tests/Core/FSTTargetIDGeneratorTests.m94
-rw-r--r--Firestore/Example/Tests/Core/FSTTimestampTests.m88
-rw-r--r--Firestore/Example/Tests/Core/FSTViewSnapshotTest.mm (renamed from Firestore/Example/Tests/Core/FSTViewSnapshotTest.m)25
-rw-r--r--Firestore/Example/Tests/Core/FSTViewTests.mm (renamed from Firestore/Example/Tests/Core/FSTViewTests.m)175
-rw-r--r--Firestore/Example/Tests/GoogleTest/FSTGoogleTestTests.mm3
-rw-r--r--Firestore/Example/Tests/Integration/API/FIRCursorTests.mm (renamed from Firestore/Example/Tests/Integration/API/FIRCursorTests.m)80
-rw-r--r--Firestore/Example/Tests/Integration/API/FIRDatabaseTests.mm (renamed from Firestore/Example/Tests/Integration/API/FIRDatabaseTests.m)208
-rw-r--r--Firestore/Example/Tests/Integration/API/FIRFieldsTests.mm (renamed from Firestore/Example/Tests/Integration/API/FIRFieldsTests.m)86
-rw-r--r--Firestore/Example/Tests/Integration/API/FIRListenerRegistrationTests.mm (renamed from Firestore/Example/Tests/Integration/API/FIRListenerRegistrationTests.m)33
-rw-r--r--Firestore/Example/Tests/Integration/API/FIRQueryTests.mm (renamed from Firestore/Example/Tests/Integration/API/FIRQueryTests.m)110
-rw-r--r--Firestore/Example/Tests/Integration/API/FIRServerTimestampTests.m183
-rw-r--r--Firestore/Example/Tests/Integration/API/FIRServerTimestampTests.mm318
-rw-r--r--Firestore/Example/Tests/Integration/API/FIRTypeTests.mm (renamed from Firestore/Example/Tests/Integration/API/FIRTypeTests.m)22
-rw-r--r--Firestore/Example/Tests/Integration/API/FIRValidationTests.mm (renamed from Firestore/Example/Tests/Integration/API/FIRValidationTests.m)12
-rw-r--r--Firestore/Example/Tests/Integration/API/FIRWriteBatchTests.mm (renamed from Firestore/Example/Tests/Integration/API/FIRWriteBatchTests.m)42
-rw-r--r--Firestore/Example/Tests/Integration/FSTDatastoreTests.mm (renamed from Firestore/Example/Tests/Integration/FSTDatastoreTests.m)49
-rw-r--r--Firestore/Example/Tests/Integration/FSTSmokeTests.mm (renamed from Firestore/Example/Tests/Integration/FSTSmokeTests.m)8
-rw-r--r--Firestore/Example/Tests/Integration/FSTStreamTests.mm (renamed from Firestore/Example/Tests/Integration/FSTStreamTests.m)76
-rw-r--r--Firestore/Example/Tests/Integration/FSTTransactionTests.mm (renamed from Firestore/Example/Tests/Integration/FSTTransactionTests.m)11
-rw-r--r--Firestore/Example/Tests/Local/FSTEagerGarbageCollectorTests.mm (renamed from Firestore/Example/Tests/Local/FSTEagerGarbageCollectorTests.m)36
-rw-r--r--Firestore/Example/Tests/Local/FSTLevelDBKeyTests.mm13
-rw-r--r--Firestore/Example/Tests/Local/FSTLevelDBLocalStoreTests.mm (renamed from Firestore/Example/Tests/Local/FSTLevelDBLocalStoreTests.m)6
-rw-r--r--Firestore/Example/Tests/Local/FSTLevelDBMigrationsTests.mm117
-rw-r--r--Firestore/Example/Tests/Local/FSTLevelDBMutationQueueTests.mm19
-rw-r--r--Firestore/Example/Tests/Local/FSTLevelDBQueryCacheTests.mm (renamed from Firestore/Example/Tests/Local/FSTLevelDBQueryCacheTests.m)1
-rw-r--r--Firestore/Example/Tests/Local/FSTLevelDBRemoteDocumentCacheTests.mm13
-rw-r--r--Firestore/Example/Tests/Local/FSTLevelDBTransactionTests.mm309
-rw-r--r--Firestore/Example/Tests/Local/FSTLocalSerializerTests.mm (renamed from Firestore/Example/Tests/Local/FSTLocalSerializerTests.m)48
-rw-r--r--Firestore/Example/Tests/Local/FSTLocalStoreTests.h4
-rw-r--r--Firestore/Example/Tests/Local/FSTLocalStoreTests.mm (renamed from Firestore/Example/Tests/Local/FSTLocalStoreTests.m)322
-rw-r--r--Firestore/Example/Tests/Local/FSTMemoryLocalStoreTests.mm (renamed from Firestore/Example/Tests/Local/FSTMemoryLocalStoreTests.m)5
-rw-r--r--Firestore/Example/Tests/Local/FSTMemoryMutationQueueTests.mm (renamed from Firestore/Example/Tests/Local/FSTMemoryMutationQueueTests.m)8
-rw-r--r--Firestore/Example/Tests/Local/FSTMemoryQueryCacheTests.mm (renamed from Firestore/Example/Tests/Local/FSTMemoryQueryCacheTests.m)1
-rw-r--r--Firestore/Example/Tests/Local/FSTMemoryRemoteDocumentCacheTests.mm (renamed from Firestore/Example/Tests/Local/FSTMemoryRemoteDocumentCacheTests.m)1
-rw-r--r--Firestore/Example/Tests/Local/FSTMutationQueueTests.m511
-rw-r--r--Firestore/Example/Tests/Local/FSTMutationQueueTests.mm538
-rw-r--r--Firestore/Example/Tests/Local/FSTPersistenceTestHelpers.h6
-rw-r--r--Firestore/Example/Tests/Local/FSTPersistenceTestHelpers.mm (renamed from Firestore/Example/Tests/Local/FSTPersistenceTestHelpers.m)20
-rw-r--r--Firestore/Example/Tests/Local/FSTQueryCacheTests.m375
-rw-r--r--Firestore/Example/Tests/Local/FSTQueryCacheTests.mm445
-rw-r--r--Firestore/Example/Tests/Local/FSTReferenceSetTests.mm (renamed from Firestore/Example/Tests/Local/FSTReferenceSetTests.m)9
-rw-r--r--Firestore/Example/Tests/Local/FSTRemoteDocumentCacheTests.m151
-rw-r--r--Firestore/Example/Tests/Local/FSTRemoteDocumentCacheTests.mm157
-rw-r--r--Firestore/Example/Tests/Local/FSTRemoteDocumentChangeBufferTests.m113
-rw-r--r--Firestore/Example/Tests/Local/FSTWriteGroupTests.mm121
-rw-r--r--Firestore/Example/Tests/Local/StringViewTests.mm3
-rw-r--r--Firestore/Example/Tests/Model/FSTDatabaseIDTests.m45
-rw-r--r--Firestore/Example/Tests/Model/FSTDocumentKeyTests.mm (renamed from Firestore/Example/Tests/Model/FSTDocumentKeyTests.m)32
-rw-r--r--Firestore/Example/Tests/Model/FSTDocumentSetTests.mm (renamed from Firestore/Example/Tests/Model/FSTDocumentSetTests.m)20
-rw-r--r--Firestore/Example/Tests/Model/FSTDocumentTests.m101
-rw-r--r--Firestore/Example/Tests/Model/FSTDocumentTests.mm98
-rw-r--r--Firestore/Example/Tests/Model/FSTFieldValueTests.mm (renamed from Firestore/Example/Tests/Model/FSTFieldValueTests.m)115
-rw-r--r--Firestore/Example/Tests/Model/FSTMutationTests.m216
-rw-r--r--Firestore/Example/Tests/Model/FSTMutationTests.mm292
-rw-r--r--Firestore/Example/Tests/Model/FSTPathTests.m196
-rw-r--r--Firestore/Example/Tests/Remote/FSTDatastoreTests.mm (renamed from Firestore/Example/Tests/Remote/FSTDatastoreTests.m)2
-rw-r--r--Firestore/Example/Tests/Remote/FSTRemoteEventTests.mm (renamed from Firestore/Example/Tests/Remote/FSTRemoteEventTests.m)92
-rw-r--r--Firestore/Example/Tests/Remote/FSTSerializerBetaTests.mm (renamed from Firestore/Example/Tests/Remote/FSTSerializerBetaTests.m)130
-rw-r--r--Firestore/Example/Tests/Remote/FSTWatchChange+Testing.mm (renamed from Firestore/Example/Tests/Remote/FSTWatchChange+Testing.m)0
-rw-r--r--Firestore/Example/Tests/Remote/FSTWatchChangeTests.mm (renamed from Firestore/Example/Tests/Remote/FSTWatchChangeTests.m)2
-rw-r--r--Firestore/Example/Tests/SpecTests/FSTLevelDBSpecTests.mm (renamed from Firestore/Example/Tests/SpecTests/FSTLevelDBSpecTests.m)0
-rw-r--r--Firestore/Example/Tests/SpecTests/FSTMemorySpecTests.mm (renamed from Firestore/Example/Tests/SpecTests/FSTMemorySpecTests.m)0
-rw-r--r--Firestore/Example/Tests/SpecTests/FSTMockDatastore.h2
-rw-r--r--Firestore/Example/Tests/SpecTests/FSTMockDatastore.mm (renamed from Firestore/Example/Tests/SpecTests/FSTMockDatastore.m)62
-rw-r--r--Firestore/Example/Tests/SpecTests/FSTSpecTests.mm (renamed from Firestore/Example/Tests/SpecTests/FSTSpecTests.m)102
-rw-r--r--Firestore/Example/Tests/SpecTests/FSTSyncEngineTestDriver.h39
-rw-r--r--Firestore/Example/Tests/SpecTests/FSTSyncEngineTestDriver.mm (renamed from Firestore/Example/Tests/SpecTests/FSTSyncEngineTestDriver.m)170
-rw-r--r--Firestore/Example/Tests/SpecTests/json/existence_filter_spec_test.json3
-rw-r--r--Firestore/Example/Tests/SpecTests/json/listen_spec_test.json14
-rw-r--r--Firestore/Example/Tests/SpecTests/json/offline_spec_test.json811
-rw-r--r--Firestore/Example/Tests/SpecTests/json/remote_store_spec_test.json74
-rw-r--r--Firestore/Example/Tests/SpecTests/json/resume_token_spec_test.json3
-rw-r--r--Firestore/Example/Tests/SpecTests/json/write_spec_test.json192
-rw-r--r--Firestore/Example/Tests/Util/FSTAssertTests.mm (renamed from Firestore/Example/Tests/Util/FSTAssertTests.m)0
-rw-r--r--Firestore/Example/Tests/Util/FSTComparisonTests.m143
-rw-r--r--Firestore/Example/Tests/Util/FSTDispatchQueueTests.mm266
-rw-r--r--Firestore/Example/Tests/Util/FSTEventAccumulator.h5
-rw-r--r--Firestore/Example/Tests/Util/FSTEventAccumulator.mm (renamed from Firestore/Example/Tests/Util/FSTEventAccumulator.m)5
-rw-r--r--Firestore/Example/Tests/Util/FSTHelpers.h92
-rw-r--r--Firestore/Example/Tests/Util/FSTHelpers.mm (renamed from Firestore/Example/Tests/Util/FSTHelpers.m)165
-rw-r--r--Firestore/Example/Tests/Util/FSTIntegrationTestCase.h10
-rw-r--r--Firestore/Example/Tests/Util/FSTIntegrationTestCase.mm97
-rw-r--r--Firestore/Example/Tests/Util/FSTTestDispatchQueue.m61
-rw-r--r--Firestore/Example/Tests/Util/XCTestCase+Await.h8
-rw-r--r--Firestore/Example/Tests/Util/XCTestCase+Await.mm (renamed from Firestore/Example/Tests/Util/XCTestCase+Await.m)12
-rw-r--r--Firestore/Port/absl/absl_attributes.h644
-rw-r--r--Firestore/Port/absl/absl_config.h306
-rw-r--r--Firestore/Port/absl/absl_endian.h342
-rw-r--r--Firestore/Port/absl/absl_integral_types.h148
-rw-r--r--Firestore/Port/absl/absl_port.h535
-rw-r--r--Firestore/Port/string_util.h66
-rw-r--r--Firestore/Protos/CMakeLists.txt52
-rw-r--r--Firestore/Protos/FrameworkMaker.xcodeproj/project.pbxproj4
-rw-r--r--Firestore/Protos/README.md18
-rwxr-xr-xFirestore/Protos/build-protos.sh16
-rw-r--r--Firestore/Protos/nanopb/firestore/local/maybe_document.pb.c66
-rw-r--r--Firestore/Protos/nanopb/firestore/local/maybe_document.pb.h88
-rw-r--r--Firestore/Protos/nanopb/firestore/local/mutation.pb.c67
-rw-r--r--Firestore/Protos/nanopb/firestore/local/mutation.pb.h87
-rw-r--r--Firestore/Protos/nanopb/firestore/local/target.pb.c72
-rw-r--r--Firestore/Protos/nanopb/firestore/local/target.pb.h100
-rw-r--r--Firestore/Protos/nanopb/google/api/annotations.pb.c31
-rw-r--r--Firestore/Protos/nanopb/google/api/annotations.pb.h44
-rw-r--r--Firestore/Protos/nanopb/google/api/http.pb.c79
-rw-r--r--Firestore/Protos/nanopb/google/api/http.pb.h107
-rw-r--r--Firestore/Protos/nanopb/google/firestore/v1beta1/common.pb.c81
-rw-r--r--Firestore/Protos/nanopb/google/firestore/v1beta1/common.pb.h124
-rw-r--r--Firestore/Protos/nanopb/google/firestore/v1beta1/document.pb.c105
-rw-r--r--Firestore/Protos/nanopb/google/firestore/v1beta1/document.pb.h155
-rw-r--r--Firestore/Protos/nanopb/google/firestore/v1beta1/firestore.pb.c259
-rw-r--r--Firestore/Protos/nanopb/google/firestore/v1beta1/firestore.pb.h508
-rw-r--r--Firestore/Protos/nanopb/google/firestore/v1beta1/query.pb.c124
-rw-r--r--Firestore/Protos/nanopb/google/firestore/v1beta1/query.pb.h241
-rw-r--r--Firestore/Protos/nanopb/google/firestore/v1beta1/write.pb.c111
-rw-r--r--Firestore/Protos/nanopb/google/firestore/v1beta1/write.pb.h189
-rw-r--r--Firestore/Protos/nanopb/google/protobuf/any.pb.c36
-rw-r--r--Firestore/Protos/nanopb/google/protobuf/any.pb.h69
-rw-r--r--Firestore/Protos/nanopb/google/protobuf/empty.pb.c34
-rw-r--r--Firestore/Protos/nanopb/google/protobuf/empty.pb.h66
-rw-r--r--Firestore/Protos/nanopb/google/protobuf/struct.pb.c87
-rw-r--r--Firestore/Protos/nanopb/google/protobuf/struct.pb.h117
-rw-r--r--Firestore/Protos/nanopb/google/protobuf/timestamp.pb.c36
-rw-r--r--Firestore/Protos/nanopb/google/protobuf/timestamp.pb.h69
-rw-r--r--Firestore/Protos/nanopb/google/protobuf/wrappers.pb.c81
-rw-r--r--Firestore/Protos/nanopb/google/protobuf/wrappers.pb.h147
-rw-r--r--Firestore/Protos/nanopb/google/rpc/status.pb.c37
-rw-r--r--Firestore/Protos/nanopb/google/rpc/status.pb.h73
-rw-r--r--Firestore/Protos/nanopb/google/type/latlng.pb.c42
-rw-r--r--Firestore/Protos/nanopb/google/type/latlng.pb.h69
-rw-r--r--Firestore/Protos/objc/firestore/local/Target.pbobjc.h4
-rw-r--r--Firestore/Protos/objc/firestore/local/Target.pbobjc.m11
-rw-r--r--Firestore/Protos/objc/google/api/HTTP.pbobjc.h75
-rw-r--r--Firestore/Protos/objc/google/api/HTTP.pbobjc.m10
-rw-r--r--Firestore/Protos/objc/google/firestore/v1beta1/Document.pbobjc.h4
-rw-r--r--Firestore/Protos/objc/google/firestore/v1beta1/Firestore.pbobjc.h5
-rw-r--r--Firestore/Protos/objc/google/firestore/v1beta1/Firestore.pbrpc.h58
-rw-r--r--Firestore/Protos/objc/google/firestore/v1beta1/Firestore.pbrpc.m19
-rw-r--r--Firestore/Protos/objc/google/firestore/v1beta1/Query.pbobjc.h3
-rw-r--r--Firestore/Protos/objc/google/firestore/v1beta1/Query.pbobjc.m4
-rw-r--r--Firestore/Protos/objc/google/firestore/v1beta1/Write.pbobjc.h57
-rw-r--r--Firestore/Protos/objc/google/firestore/v1beta1/Write.pbobjc.m33
-rw-r--r--Firestore/Protos/protos/firestore/local/target.proto3
-rw-r--r--Firestore/Protos/protos/google/api/http.options1
-rw-r--r--Firestore/Protos/protos/google/api/http.proto72
-rw-r--r--Firestore/Protos/protos/google/firestore/v1beta1/common.proto3
-rw-r--r--Firestore/Protos/protos/google/firestore/v1beta1/document.options1
-rw-r--r--Firestore/Protos/protos/google/firestore/v1beta1/document.proto7
-rw-r--r--Firestore/Protos/protos/google/firestore/v1beta1/firestore.options6
-rw-r--r--Firestore/Protos/protos/google/firestore/v1beta1/firestore.proto76
-rw-r--r--Firestore/Protos/protos/google/firestore/v1beta1/query.proto6
-rw-r--r--Firestore/Protos/protos/google/firestore/v1beta1/write.options1
-rw-r--r--Firestore/Protos/protos/google/firestore/v1beta1/write.proto45
-rw-r--r--Firestore/Protos/protos/google/protobuf/any.proto154
-rw-r--r--Firestore/Protos/protos/google/protobuf/empty.proto52
-rw-r--r--Firestore/Protos/protos/google/protobuf/struct.options1
-rw-r--r--Firestore/Protos/protos/google/protobuf/struct.proto96
-rw-r--r--Firestore/Protos/protos/google/protobuf/timestamp.proto135
-rw-r--r--Firestore/Protos/protos/google/protobuf/wrappers.proto118
-rw-r--r--Firestore/Source/API/FIRCollectionReference+Internal.h7
-rw-r--r--Firestore/Source/API/FIRCollectionReference.mm60
-rw-r--r--Firestore/Source/API/FIRDocumentChange+Internal.h1
-rw-r--r--Firestore/Source/API/FIRDocumentChange.mm (renamed from Firestore/Source/API/FIRDocumentChange.m)22
-rw-r--r--Firestore/Source/API/FIRDocumentReference+Internal.h14
-rw-r--r--Firestore/Source/API/FIRDocumentReference.mm (renamed from Firestore/Source/API/FIRDocumentReference.m)177
-rw-r--r--Firestore/Source/API/FIRDocumentSnapshot+Internal.h5
-rw-r--r--Firestore/Source/API/FIRDocumentSnapshot.m175
-rw-r--r--Firestore/Source/API/FIRDocumentSnapshot.mm273
-rw-r--r--Firestore/Source/API/FIRFieldPath+Internal.h8
-rw-r--r--Firestore/Source/API/FIRFieldPath.mm (renamed from Firestore/Source/API/FIRFieldPath.m)45
-rw-r--r--Firestore/Source/API/FIRFieldValue+Internal.h20
-rw-r--r--Firestore/Source/API/FIRFieldValue.mm (renamed from Firestore/Source/API/FIRFieldValue.m)46
-rw-r--r--Firestore/Source/API/FIRFirestore+Internal.h18
-rw-r--r--Firestore/Source/API/FIRFirestore.m284
-rw-r--r--Firestore/Source/API/FIRFirestore.mm394
-rw-r--r--Firestore/Source/API/FIRFirestoreSettings.mm (renamed from Firestore/Source/API/FIRFirestoreSettings.m)8
-rw-r--r--Firestore/Source/API/FIRFirestoreVersion.mm (renamed from Firestore/Source/API/FIRFirestoreVersion.m)4
-rw-r--r--Firestore/Source/API/FIRGeoPoint.mm (renamed from Firestore/Source/API/FIRGeoPoint.m)17
-rw-r--r--Firestore/Source/API/FIRListenerRegistration.mm (renamed from Firestore/Source/API/FIRListenerRegistration.m)0
-rw-r--r--Firestore/Source/API/FIRQuery.mm (renamed from Firestore/Source/API/FIRQuery.m)162
-rw-r--r--Firestore/Source/API/FIRQuerySnapshot.mm (renamed from Firestore/Source/API/FIRQuerySnapshot.m)40
-rw-r--r--Firestore/Source/API/FIRSetOptions.m65
-rw-r--r--Firestore/Source/API/FIRSnapshotMetadata.mm (renamed from Firestore/Source/API/FIRSnapshotMetadata.m)21
-rw-r--r--Firestore/Source/API/FIRSnapshotOptions+Internal.h (renamed from Firestore/Example/Tests/Util/FSTTestDispatchQueue.h)25
-rw-r--r--Firestore/Source/API/FIRSnapshotOptions.mm72
-rw-r--r--Firestore/Source/API/FIRTimestamp+Internal.h35
-rw-r--r--Firestore/Source/API/FIRTimestamp.m (renamed from Firestore/Source/Core/FSTTimestamp.m)114
-rw-r--r--Firestore/Source/API/FIRTransaction.mm (renamed from Firestore/Source/API/FIRTransaction.m)11
-rw-r--r--Firestore/Source/API/FIRWriteBatch.mm (renamed from Firestore/Source/API/FIRWriteBatch.m)30
-rw-r--r--Firestore/Source/API/FSTUserDataConverter.h66
-rw-r--r--Firestore/Source/API/FSTUserDataConverter.mm (renamed from Firestore/Source/API/FSTUserDataConverter.m)486
-rw-r--r--Firestore/Source/Auth/FSTCredentialsProvider.h113
-rw-r--r--Firestore/Source/Auth/FSTCredentialsProvider.m164
-rw-r--r--Firestore/Source/Auth/FSTEmptyCredentialsProvider.m47
-rw-r--r--Firestore/Source/Auth/FSTUser.h43
-rw-r--r--Firestore/Source/Auth/FSTUser.m68
-rw-r--r--Firestore/Source/Core/FSTDatabaseInfo.h55
-rw-r--r--Firestore/Source/Core/FSTDatabaseInfo.m70
-rw-r--r--Firestore/Source/Core/FSTEventManager.h2
-rw-r--r--Firestore/Source/Core/FSTEventManager.mm (renamed from Firestore/Source/Core/FSTEventManager.m)16
-rw-r--r--Firestore/Source/Core/FSTFirestoreClient.h17
-rw-r--r--Firestore/Source/Core/FSTFirestoreClient.mm (renamed from Firestore/Source/Core/FSTFirestoreClient.m)120
-rw-r--r--Firestore/Source/Core/FSTListenSequence.h37
-rw-r--r--Firestore/Source/Core/FSTListenSequence.mm50
-rw-r--r--Firestore/Source/Core/FSTQuery.h42
-rw-r--r--Firestore/Source/Core/FSTQuery.mm (renamed from Firestore/Source/Core/FSTQuery.m)233
-rw-r--r--Firestore/Source/Core/FSTSnapshotVersion.h6
-rw-r--r--Firestore/Source/Core/FSTSnapshotVersion.mm (renamed from Firestore/Source/Core/FSTSnapshotVersion.m)8
-rw-r--r--Firestore/Source/Core/FSTSyncEngine.h11
-rw-r--r--Firestore/Source/Core/FSTSyncEngine.mm (renamed from Firestore/Source/Core/FSTSyncEngine.m)154
-rw-r--r--Firestore/Source/Core/FSTTargetIDGenerator.h55
-rw-r--r--Firestore/Source/Core/FSTTargetIDGenerator.m105
-rw-r--r--Firestore/Source/Core/FSTTransaction.h18
-rw-r--r--Firestore/Source/Core/FSTTransaction.mm (renamed from Firestore/Source/Core/FSTTransaction.m)125
-rw-r--r--Firestore/Source/Core/FSTTypes.h30
-rw-r--r--Firestore/Source/Core/FSTView.h16
-rw-r--r--Firestore/Source/Core/FSTView.mm (renamed from Firestore/Source/Core/FSTView.m)83
-rw-r--r--Firestore/Source/Core/FSTViewSnapshot.mm (renamed from Firestore/Source/Core/FSTViewSnapshot.m)7
-rw-r--r--Firestore/Source/Local/FSTDocumentReference.h9
-rw-r--r--Firestore/Source/Local/FSTDocumentReference.mm (renamed from Firestore/Source/Local/FSTDocumentReference.m)30
-rw-r--r--Firestore/Source/Local/FSTEagerGarbageCollector.h2
-rw-r--r--Firestore/Source/Local/FSTEagerGarbageCollector.mm (renamed from Firestore/Source/Local/FSTEagerGarbageCollector.m)29
-rw-r--r--Firestore/Source/Local/FSTGarbageCollector.h11
-rw-r--r--Firestore/Source/Local/FSTLevelDB.h25
-rw-r--r--Firestore/Source/Local/FSTLevelDB.mm96
-rw-r--r--Firestore/Source/Local/FSTLevelDBKey.h35
-rw-r--r--Firestore/Source/Local/FSTLevelDBKey.mm86
-rw-r--r--Firestore/Source/Local/FSTLevelDBMigrations.h43
-rw-r--r--Firestore/Source/Local/FSTLevelDBMigrations.mm125
-rw-r--r--Firestore/Source/Local/FSTLevelDBMutationQueue.h18
-rw-r--r--Firestore/Source/Local/FSTLevelDBMutationQueue.mm205
-rw-r--r--Firestore/Source/Local/FSTLevelDBQueryCache.h28
-rw-r--r--Firestore/Source/Local/FSTLevelDBQueryCache.mm257
-rw-r--r--Firestore/Source/Local/FSTLevelDBRemoteDocumentCache.h13
-rw-r--r--Firestore/Source/Local/FSTLevelDBRemoteDocumentCache.mm81
-rw-r--r--Firestore/Source/Local/FSTLocalDocumentsView.h5
-rw-r--r--Firestore/Source/Local/FSTLocalDocumentsView.mm (renamed from Firestore/Source/Local/FSTLocalDocumentsView.m)21
-rw-r--r--Firestore/Source/Local/FSTLocalSerializer.mm (renamed from Firestore/Source/Local/FSTLocalSerializer.m)15
-rw-r--r--Firestore/Source/Local/FSTLocalStore.h14
-rw-r--r--Firestore/Source/Local/FSTLocalStore.m546
-rw-r--r--Firestore/Source/Local/FSTLocalStore.mm533
-rw-r--r--Firestore/Source/Local/FSTLocalViewChanges.h1
-rw-r--r--Firestore/Source/Local/FSTLocalViewChanges.mm (renamed from Firestore/Source/Local/FSTLocalViewChanges.m)0
-rw-r--r--Firestore/Source/Local/FSTLocalWriteResult.mm (renamed from Firestore/Source/Local/FSTLocalWriteResult.m)0
-rw-r--r--Firestore/Source/Local/FSTMemoryMutationQueue.mm (renamed from Firestore/Source/Local/FSTMemoryMutationQueue.m)67
-rw-r--r--Firestore/Source/Local/FSTMemoryPersistence.mm (renamed from Firestore/Source/Local/FSTMemoryPersistence.m)39
-rw-r--r--Firestore/Source/Local/FSTMemoryQueryCache.mm (renamed from Firestore/Source/Local/FSTMemoryQueryCache.m)55
-rw-r--r--Firestore/Source/Local/FSTMemoryRemoteDocumentCache.mm (renamed from Firestore/Source/Local/FSTMemoryRemoteDocumentCache.m)21
-rw-r--r--Firestore/Source/Local/FSTMutationQueue.h26
-rw-r--r--Firestore/Source/Local/FSTNoOpGarbageCollector.h2
-rw-r--r--Firestore/Source/Local/FSTNoOpGarbageCollector.mm (renamed from Firestore/Source/Local/FSTNoOpGarbageCollector.m)12
-rw-r--r--Firestore/Source/Local/FSTPersistence.h95
-rw-r--r--Firestore/Source/Local/FSTQueryCache.h46
-rw-r--r--Firestore/Source/Local/FSTQueryData.h4
-rw-r--r--Firestore/Source/Local/FSTQueryData.mm (renamed from Firestore/Source/Local/FSTQueryData.m)5
-rw-r--r--Firestore/Source/Local/FSTReferenceSet.h6
-rw-r--r--Firestore/Source/Local/FSTReferenceSet.mm (renamed from Firestore/Source/Local/FSTReferenceSet.m)29
-rw-r--r--Firestore/Source/Local/FSTRemoteDocumentCache.h14
-rw-r--r--Firestore/Source/Local/FSTRemoteDocumentChangeBuffer.h66
-rw-r--r--Firestore/Source/Local/FSTRemoteDocumentChangeBuffer.m88
-rw-r--r--Firestore/Source/Local/FSTWriteGroup.h97
-rw-r--r--Firestore/Source/Local/FSTWriteGroup.mm147
-rw-r--r--Firestore/Source/Local/FSTWriteGroupTracker.h45
-rw-r--r--Firestore/Source/Local/FSTWriteGroupTracker.m52
-rw-r--r--Firestore/Source/Local/StringView.h31
-rw-r--r--Firestore/Source/Model/FSTDatabaseID.h48
-rw-r--r--Firestore/Source/Model/FSTDatabaseID.m90
-rw-r--r--Firestore/Source/Model/FSTDocument.h14
-rw-r--r--Firestore/Source/Model/FSTDocument.mm (renamed from Firestore/Source/Model/FSTDocument.m)48
-rw-r--r--Firestore/Source/Model/FSTDocumentDictionary.mm (renamed from Firestore/Source/Model/FSTDocumentDictionary.m)0
-rw-r--r--Firestore/Source/Model/FSTDocumentKey.h16
-rw-r--r--Firestore/Source/Model/FSTDocumentKey.mm (renamed from Firestore/Source/Model/FSTDocumentKey.m)53
-rw-r--r--Firestore/Source/Model/FSTDocumentKeySet.mm (renamed from Firestore/Source/Model/FSTDocumentKeySet.m)0
-rw-r--r--Firestore/Source/Model/FSTDocumentSet.h21
-rw-r--r--Firestore/Source/Model/FSTDocumentSet.mm (renamed from Firestore/Source/Model/FSTDocumentSet.m)30
-rw-r--r--Firestore/Source/Model/FSTDocumentVersionDictionary.mm (renamed from Firestore/Source/Model/FSTDocumentVersionDictionary.m)0
-rw-r--r--Firestore/Source/Model/FSTFieldValue.h128
-rw-r--r--Firestore/Source/Model/FSTFieldValue.mm (renamed from Firestore/Source/Model/FSTFieldValue.m)239
-rw-r--r--Firestore/Source/Model/FSTMutation.h175
-rw-r--r--Firestore/Source/Model/FSTMutation.m575
-rw-r--r--Firestore/Source/Model/FSTMutation.mm462
-rw-r--r--Firestore/Source/Model/FSTMutationBatch.h12
-rw-r--r--Firestore/Source/Model/FSTMutationBatch.mm (renamed from Firestore/Source/Model/FSTMutationBatch.m)19
-rw-r--r--Firestore/Source/Model/FSTPath.h141
-rw-r--r--Firestore/Source/Model/FSTPath.m356
-rw-r--r--Firestore/Source/Public/FIRDocumentChange.h4
-rw-r--r--Firestore/Source/Public/FIRDocumentReference.h55
-rw-r--r--Firestore/Source/Public/FIRDocumentSnapshot.h143
-rw-r--r--Firestore/Source/Public/FIRFieldValue.h23
-rw-r--r--Firestore/Source/Public/FIRFirestore.h22
-rw-r--r--Firestore/Source/Public/FIRFirestoreSettings.h17
-rw-r--r--Firestore/Source/Public/FIRQuery.h13
-rw-r--r--Firestore/Source/Public/FIRQuerySnapshot.h4
-rw-r--r--Firestore/Source/Public/FIRSetOptions.h46
-rw-r--r--Firestore/Source/Public/FIRSnapshotMetadata.h10
-rw-r--r--Firestore/Source/Public/FIRTimestamp.h (renamed from Firestore/Source/Core/FSTTimestamp.h)50
-rw-r--r--Firestore/Source/Public/FIRTransaction.h11
-rw-r--r--Firestore/Source/Public/FIRWriteBatch.h18
-rw-r--r--Firestore/Source/Public/FirebaseFirestore.h2
-rw-r--r--Firestore/Source/Remote/FSTBufferedWriter.mm (renamed from Firestore/Source/Remote/FSTBufferedWriter.m)0
-rw-r--r--Firestore/Source/Remote/FSTDatastore.h32
-rw-r--r--Firestore/Source/Remote/FSTDatastore.mm (renamed from Firestore/Source/Remote/FSTDatastore.m)115
-rw-r--r--Firestore/Source/Remote/FSTExistenceFilter.mm (renamed from Firestore/Source/Remote/FSTExistenceFilter.m)0
-rw-r--r--Firestore/Source/Remote/FSTExponentialBackoff.h23
-rw-r--r--Firestore/Source/Remote/FSTExponentialBackoff.mm30
-rw-r--r--Firestore/Source/Remote/FSTOnlineStateTracker.h72
-rw-r--r--Firestore/Source/Remote/FSTOnlineStateTracker.mm155
-rw-r--r--Firestore/Source/Remote/FSTRemoteEvent.h10
-rw-r--r--Firestore/Source/Remote/FSTRemoteEvent.mm (renamed from Firestore/Source/Remote/FSTRemoteEvent.m)72
-rw-r--r--Firestore/Source/Remote/FSTRemoteStore.h20
-rw-r--r--Firestore/Source/Remote/FSTRemoteStore.mm (renamed from Firestore/Source/Remote/FSTRemoteStore.m)189
-rw-r--r--Firestore/Source/Remote/FSTSerializerBeta.h21
-rw-r--r--Firestore/Source/Remote/FSTSerializerBeta.mm (renamed from Firestore/Source/Remote/FSTSerializerBeta.m)281
-rw-r--r--Firestore/Source/Remote/FSTStream.h48
-rw-r--r--Firestore/Source/Remote/FSTStream.mm (renamed from Firestore/Source/Remote/FSTStream.m)195
-rw-r--r--Firestore/Source/Remote/FSTWatchChange.h11
-rw-r--r--Firestore/Source/Remote/FSTWatchChange.mm (renamed from Firestore/Source/Remote/FSTWatchChange.m)21
-rw-r--r--Firestore/Source/Util/FSTAssert.h2
-rw-r--r--Firestore/Source/Util/FSTAsyncQueryListener.mm (renamed from Firestore/Source/Util/FSTAsyncQueryListener.m)11
-rw-r--r--Firestore/Source/Util/FSTComparison.h66
-rw-r--r--Firestore/Source/Util/FSTComparison.m175
-rw-r--r--Firestore/Source/Util/FSTDispatchQueue.h80
-rw-r--r--Firestore/Source/Util/FSTDispatchQueue.m80
-rw-r--r--Firestore/Source/Util/FSTDispatchQueue.mm314
-rw-r--r--Firestore/Source/Util/FSTLogger.h8
-rw-r--r--Firestore/Source/Util/FSTLogger.mm (renamed from Firestore/Source/Util/FSTLogger.m)0
-rw-r--r--Firestore/Source/Util/FSTUsageValidation.h10
-rw-r--r--Firestore/Source/Util/FSTUsageValidation.mm (renamed from Firestore/Source/Util/FSTUsageValidation.m)2
-rw-r--r--Firestore/Swift/Source/Codable/CodableGeoPoint.swift62
-rw-r--r--Firestore/Swift/Tests/Codable/CodableGeoPointTests.swift49
-rw-r--r--Firestore/Swift/Tests/Info.plist22
-rw-r--r--Firestore/core/.clang-tidy34
-rw-r--r--Firestore/core/CMakeLists.txt18
-rw-r--r--Firestore/core/include/firebase/firestore/CMakeLists.txt25
-rw-r--r--Firestore/core/include/firebase/firestore/document_reference.h375
-rw-r--r--Firestore/core/include/firebase/firestore/event_listener.h53
-rw-r--r--Firestore/core/include/firebase/firestore/firestore.h160
-rw-r--r--Firestore/core/include/firebase/firestore/firestore_errors.h115
-rw-r--r--Firestore/core/include/firebase/firestore/geo_point.h89
-rw-r--r--Firestore/core/include/firebase/firestore/timestamp.h221
-rw-r--r--Firestore/core/src/firebase/firestore/CMakeLists.txt30
-rw-r--r--Firestore/core/src/firebase/firestore/auth/CMakeLists.txt52
-rw-r--r--Firestore/core/src/firebase/firestore/auth/credentials_provider.cc31
-rw-r--r--Firestore/core/src/firebase/firestore/auth/credentials_provider.h78
-rw-r--r--Firestore/core/src/firebase/firestore/auth/empty_credentials_provider.cc44
-rw-r--r--Firestore/core/src/firebase/firestore/auth/empty_credentials_provider.h37
-rw-r--r--Firestore/core/src/firebase/firestore/auth/firebase_credentials_provider_apple.h113
-rw-r--r--Firestore/core/src/firebase/firestore/auth/firebase_credentials_provider_apple.mm149
-rw-r--r--Firestore/core/src/firebase/firestore/auth/token.cc35
-rw-r--r--Firestore/core/src/firebase/firestore/auth/token.h79
-rw-r--r--Firestore/core/src/firebase/firestore/auth/user.cc39
-rw-r--r--Firestore/core/src/firebase/firestore/auth/user.h103
-rw-r--r--Firestore/core/src/firebase/firestore/core/CMakeLists.txt25
-rw-r--r--Firestore/core/src/firebase/firestore/core/database_info.cc36
-rw-r--r--Firestore/core/src/firebase/firestore/core/database_info.h79
-rw-r--r--Firestore/core/src/firebase/firestore/core/target_id_generator.cc64
-rw-r--r--Firestore/core/src/firebase/firestore/core/target_id_generator.h83
-rw-r--r--Firestore/core/src/firebase/firestore/geo_point.cc52
-rw-r--r--Firestore/core/src/firebase/firestore/immutable/CMakeLists.txt27
-rw-r--r--Firestore/core/src/firebase/firestore/immutable/array_sorted_map.h286
-rw-r--r--Firestore/core/src/firebase/firestore/immutable/llrb_node.h140
-rw-r--r--Firestore/core/src/firebase/firestore/immutable/map_entry.h62
-rw-r--r--Firestore/core/src/firebase/firestore/immutable/sorted_map.h213
-rw-r--r--Firestore/core/src/firebase/firestore/immutable/sorted_map_base.cc30
-rw-r--r--Firestore/core/src/firebase/firestore/immutable/sorted_map_base.h64
-rw-r--r--Firestore/core/src/firebase/firestore/immutable/tree_sorted_map.h120
-rw-r--r--Firestore/core/src/firebase/firestore/local/CMakeLists.txt25
-rw-r--r--Firestore/core/src/firebase/firestore/local/leveldb_key.cc817
-rw-r--r--Firestore/core/src/firebase/firestore/local/leveldb_key.h485
-rw-r--r--Firestore/core/src/firebase/firestore/local/leveldb_transaction.cc249
-rw-r--r--Firestore/core/src/firebase/firestore/local/leveldb_transaction.h208
-rw-r--r--Firestore/core/src/firebase/firestore/local/leveldb_util.h43
-rw-r--r--Firestore/core/src/firebase/firestore/model/CMakeLists.txt47
-rw-r--r--Firestore/core/src/firebase/firestore/model/base_path.h194
-rw-r--r--Firestore/core/src/firebase/firestore/model/database_id.cc36
-rw-r--r--Firestore/core/src/firebase/firestore/model/database_id.h109
-rw-r--r--Firestore/core/src/firebase/firestore/model/document.cc50
-rw-r--r--Firestore/core/src/firebase/firestore/model/document.h72
-rw-r--r--Firestore/core/src/firebase/firestore/model/document_key.cc54
-rw-r--r--Firestore/core/src/firebase/firestore/model/document_key.h123
-rw-r--r--Firestore/core/src/firebase/firestore/model/field_mask.h95
-rw-r--r--Firestore/core/src/firebase/firestore/model/field_path.cc173
-rw-r--r--Firestore/core/src/firebase/firestore/model/field_path.h94
-rw-r--r--Firestore/core/src/firebase/firestore/model/field_transform.h70
-rw-r--r--Firestore/core/src/firebase/firestore/model/field_value.cc427
-rw-r--r--Firestore/core/src/firebase/firestore/model/field_value.h238
-rw-r--r--Firestore/core/src/firebase/firestore/model/maybe_document.cc38
-rw-r--r--Firestore/core/src/firebase/firestore/model/maybe_document.h100
-rw-r--r--Firestore/core/src/firebase/firestore/model/no_document.cc32
-rw-r--r--Firestore/core/src/firebase/firestore/model/no_document.h36
-rw-r--r--Firestore/core/src/firebase/firestore/model/precondition.cc67
-rw-r--r--Firestore/core/src/firebase/firestore/model/precondition.h160
-rw-r--r--Firestore/core/src/firebase/firestore/model/resource_path.cc58
-rw-r--r--Firestore/core/src/firebase/firestore/model/resource_path.h79
-rw-r--r--Firestore/core/src/firebase/firestore/model/snapshot_version.cc34
-rw-r--r--Firestore/core/src/firebase/firestore/model/snapshot_version.h97
-rw-r--r--Firestore/core/src/firebase/firestore/model/transform_operations.h164
-rw-r--r--Firestore/core/src/firebase/firestore/model/types.h42
-rw-r--r--Firestore/core/src/firebase/firestore/remote/CMakeLists.txt27
-rw-r--r--Firestore/core/src/firebase/firestore/remote/datastore.cc31
-rw-r--r--Firestore/core/src/firebase/firestore/remote/datastore.h40
-rw-r--r--Firestore/core/src/firebase/firestore/remote/serializer.cc668
-rw-r--r--Firestore/core/src/firebase/firestore/remote/serializer.h110
-rw-r--r--Firestore/core/src/firebase/firestore/timestamp.cc117
-rw-r--r--Firestore/core/src/firebase/firestore/util/CMakeLists.txt134
-rw-r--r--Firestore/core/src/firebase/firestore/util/assert_apple.mm49
-rw-r--r--Firestore/core/src/firebase/firestore/util/assert_stdio.cc53
-rw-r--r--Firestore/core/src/firebase/firestore/util/bits.cc (renamed from Firestore/Port/bits.cc)14
-rw-r--r--Firestore/core/src/firebase/firestore/util/bits.h (renamed from Firestore/Port/bits.h)40
-rw-r--r--Firestore/core/src/firebase/firestore/util/comparator_holder.h62
-rw-r--r--Firestore/core/src/firebase/firestore/util/comparison.cc113
-rw-r--r--Firestore/core/src/firebase/firestore/util/comparison.h181
-rw-r--r--Firestore/core/src/firebase/firestore/util/config.h.in35
-rw-r--r--Firestore/core/src/firebase/firestore/util/error_apple.h57
-rw-r--r--Firestore/core/src/firebase/firestore/util/firebase_assert.h119
-rw-r--r--Firestore/core/src/firebase/firestore/util/iterator_adaptors.h812
-rw-r--r--Firestore/core/src/firebase/firestore/util/log.h63
-rw-r--r--Firestore/core/src/firebase/firestore/util/log_apple.mm123
-rw-r--r--Firestore/core/src/firebase/firestore/util/log_stdio.cc97
-rw-r--r--Firestore/core/src/firebase/firestore/util/ordered_code.cc (renamed from Firestore/Port/ordered_code.cc)248
-rw-r--r--Firestore/core/src/firebase/firestore/util/ordered_code.h (renamed from Firestore/Port/ordered_code.h)75
-rw-r--r--Firestore/core/src/firebase/firestore/util/secure_random.h18
-rw-r--r--Firestore/core/src/firebase/firestore/util/secure_random_arc4random.cc4
-rw-r--r--Firestore/core/src/firebase/firestore/util/secure_random_openssl.cc46
-rw-r--r--Firestore/core/src/firebase/firestore/util/status.cc130
-rw-r--r--Firestore/core/src/firebase/firestore/util/status.h141
-rw-r--r--Firestore/core/src/firebase/firestore/util/statusor.cc41
-rw-r--r--Firestore/core/src/firebase/firestore/util/statusor.h322
-rw-r--r--Firestore/core/src/firebase/firestore/util/statusor_internals.h258
-rw-r--r--Firestore/core/src/firebase/firestore/util/string_apple.h74
-rw-r--r--Firestore/core/src/firebase/firestore/util/string_printf.cc102
-rw-r--r--Firestore/core/src/firebase/firestore/util/string_printf.h46
-rw-r--r--Firestore/core/src/firebase/firestore/util/string_util.cc (renamed from Firestore/Port/string_util.cc)18
-rw-r--r--Firestore/core/src/firebase/firestore/util/string_util.h77
-rw-r--r--Firestore/core/test/firebase/firestore/CMakeLists.txt22
-rw-r--r--Firestore/core/test/firebase/firestore/auth/CMakeLists.txt35
-rw-r--r--Firestore/core/test/firebase/firestore/auth/credentials_provider_test.cc50
-rw-r--r--Firestore/core/test/firebase/firestore/auth/empty_credentials_provider_test.cc51
-rw-r--r--Firestore/core/test/firebase/firestore/auth/firebase_credentials_provider_test.mm94
-rw-r--r--Firestore/core/test/firebase/firestore/auth/token_test.cc39
-rw-r--r--Firestore/core/test/firebase/firestore/auth/user_test.cc59
-rw-r--r--Firestore/core/test/firebase/firestore/core/CMakeLists.txt22
-rw-r--r--Firestore/core/test/firebase/firestore/core/database_info_test.cc48
-rw-r--r--Firestore/core/test/firebase/firestore/core/target_id_generator_test.cc80
-rw-r--r--Firestore/core/test/firebase/firestore/geo_point_test.cc41
-rw-r--r--Firestore/core/test/firebase/firestore/immutable/CMakeLists.txt25
-rw-r--r--Firestore/core/test/firebase/firestore/immutable/array_sorted_map_test.cc204
-rw-r--r--Firestore/core/test/firebase/firestore/immutable/sorted_map_test.cc77
-rw-r--r--Firestore/core/test/firebase/firestore/immutable/testing.h162
-rw-r--r--Firestore/core/test/firebase/firestore/immutable/tree_sorted_map_test.cc40
-rw-r--r--Firestore/core/test/firebase/firestore/local/CMakeLists.txt22
-rw-r--r--Firestore/core/test/firebase/firestore/local/leveldb_key_test.cc364
-rw-r--r--Firestore/core/test/firebase/firestore/model/CMakeLists.txt33
-rw-r--r--Firestore/core/test/firebase/firestore/model/database_id_test.cc47
-rw-r--r--Firestore/core/test/firebase/firestore/model/document_key_test.cc153
-rw-r--r--Firestore/core/test/firebase/firestore/model/document_test.cc76
-rw-r--r--Firestore/core/test/firebase/firestore/model/field_mask_test.cc56
-rw-r--r--Firestore/core/test/firebase/firestore/model/field_path_test.cc277
-rw-r--r--Firestore/core/test/firebase/firestore/model/field_transform_test.cc39
-rw-r--r--Firestore/core/test/firebase/firestore/model/field_value_test.cc497
-rw-r--r--Firestore/core/test/firebase/firestore/model/maybe_document_test.cc66
-rw-r--r--Firestore/core/test/firebase/firestore/model/no_document_test.cc51
-rw-r--r--Firestore/core/test/firebase/firestore/model/precondition_test.cc77
-rw-r--r--Firestore/core/test/firebase/firestore/model/resource_path_test.cc105
-rw-r--r--Firestore/core/test/firebase/firestore/model/snapshot_version_test.cc56
-rw-r--r--Firestore/core/test/firebase/firestore/model/transform_operations_test.cc54
-rw-r--r--Firestore/core/test/firebase/firestore/remote/CMakeLists.txt22
-rw-r--r--Firestore/core/test/firebase/firestore/remote/datastore_test.cc30
-rw-r--r--Firestore/core/test/firebase/firestore/remote/serializer_test.cc282
-rw-r--r--Firestore/core/test/firebase/firestore/testutil/CMakeLists.txt38
-rw-r--r--Firestore/core/test/firebase/firestore/testutil/app_testing.h43
-rw-r--r--Firestore/core/test/firebase/firestore/testutil/app_testing.mm48
-rw-r--r--Firestore/core/test/firebase/firestore/testutil/testutil.cc28
-rw-r--r--Firestore/core/test/firebase/firestore/testutil/testutil.h80
-rw-r--r--Firestore/core/test/firebase/firestore/timestamp_test.cc299
-rw-r--r--Firestore/core/test/firebase/firestore/util/CMakeLists.txt71
-rw-r--r--Firestore/core/test/firebase/firestore/util/assert_test.cc63
-rw-r--r--Firestore/core/test/firebase/firestore/util/autoid_test.cc6
-rw-r--r--Firestore/core/test/firebase/firestore/util/bits_test.cc (renamed from Firestore/Port/bits_test.cc)72
-rw-r--r--Firestore/core/test/firebase/firestore/util/comparison_test.cc210
-rw-r--r--Firestore/core/test/firebase/firestore/util/iterator_adaptors_test.cc1277
-rw-r--r--Firestore/core/test/firebase/firestore/util/log_test.cc61
-rw-r--r--Firestore/core/test/firebase/firestore/util/ordered_code_test.cc (renamed from Firestore/Port/ordered_code_test.cc)264
-rw-r--r--Firestore/core/test/firebase/firestore/util/secure_random_test.cc25
-rw-r--r--Firestore/core/test/firebase/firestore/util/status_test.cc107
-rw-r--r--Firestore/core/test/firebase/firestore/util/status_test_util.h35
-rw-r--r--Firestore/core/test/firebase/firestore/util/statusor_test.cc436
-rw-r--r--Firestore/core/test/firebase/firestore/util/string_printf_test.cc78
-rw-r--r--Firestore/core/test/firebase/firestore/util/string_util_test.cc (renamed from Firestore/Port/string_util_test.cc)22
-rwxr-xr-xFirestore/test.sh37
-rw-r--r--Firestore/third_party/Immutable/FSTArraySortedDictionary.m13
-rw-r--r--Firestore/third_party/Immutable/FSTImmutableSortedDictionary.h8
-rw-r--r--Firestore/third_party/Immutable/FSTImmutableSortedDictionary.m4
-rw-r--r--Firestore/third_party/Immutable/FSTImmutableSortedSet.h2
-rw-r--r--Firestore/third_party/Immutable/FSTImmutableSortedSet.m4
-rw-r--r--Firestore/third_party/Immutable/FSTTreeSortedDictionary.m30
-rw-r--r--Firestore/third_party/Immutable/Tests/FSTArraySortedDictionaryTests.m22
-rw-r--r--Firestore/third_party/Immutable/Tests/FSTTreeSortedDictionaryTests.m19
-rw-r--r--Firestore/third_party/abseil-cpp/CMakeLists.txt1
-rw-r--r--Firestore/third_party/abseil-cpp/absl/CMakeLists.txt7
-rw-r--r--Firestore/third_party/abseil-cpp/absl/base/CMakeLists.txt256
-rw-r--r--Firestore/third_party/abseil-cpp/absl/base/config_test.cc17
-rw-r--r--Firestore/third_party/abseil-cpp/absl/base/macros.h2
-rw-r--r--Firestore/third_party/abseil-cpp/absl/meta/CMakeLists.txt3
-rw-r--r--Firestore/third_party/abseil-cpp/absl/numeric/int128_test.cc1
-rw-r--r--Firestore/third_party/abseil-cpp/absl/strings/CMakeLists.txt1
-rw-r--r--Functions/Backend/index.js95
-rw-r--r--Functions/Backend/package.json12
-rwxr-xr-xFunctions/Backend/start.sh55
-rw-r--r--Functions/CHANGELOG.md2
-rw-r--r--Functions/Example/FirebaseFunctions.xcodeproj/project.pbxproj848
-rw-r--r--Functions/Example/FirebaseFunctions.xcodeproj/xcshareddata/xcschemes/FirebaseFunctions-Example.xcscheme113
-rw-r--r--Functions/Example/FirebaseFunctions.xcodeproj/xcshareddata/xcschemes/FirebaseFunctions_IntegrationTests.xcscheme56
-rw-r--r--Functions/Example/FirebaseFunctions.xcodeproj/xcshareddata/xcschemes/FirebaseFunctions_Tests.xcscheme84
-rw-r--r--Functions/Example/FirebaseFunctions/Base.lproj/LaunchScreen.storyboard27
-rw-r--r--Functions/Example/FirebaseFunctions/Base.lproj/Main.storyboard27
-rw-r--r--Functions/Example/FirebaseFunctions/FIRAppDelegate.h21
-rw-r--r--Functions/Example/FirebaseFunctions/FIRAppDelegate.m55
-rw-r--r--Functions/Example/FirebaseFunctions/FIRViewController.h19
-rw-r--r--Functions/Example/FirebaseFunctions/FIRViewController.m33
-rw-r--r--Functions/Example/FirebaseFunctions/FirebaseFunctions-Info.plist54
-rw-r--r--Functions/Example/FirebaseFunctions/Images.xcassets/AppIcon.appiconset/Contents.json93
-rw-r--r--Functions/Example/FirebaseFunctions/en.lproj/InfoPlist.strings2
-rw-r--r--Functions/Example/FirebaseFunctions/main.m22
-rw-r--r--Functions/Example/IntegrationTests/FIRIntegrationTests.m190
-rw-r--r--Functions/Example/IntegrationTests/IntegrationTests-Info.plist22
-rw-r--r--Functions/Example/Podfile16
-rw-r--r--Functions/Example/TestUtils/FUNFakeApp.h39
-rw-r--r--Functions/Example/TestUtils/FUNFakeApp.m73
-rw-r--r--Functions/Example/TestUtils/FUNFakeInstanceID.h34
-rw-r--r--Functions/Example/TestUtils/FUNFakeInstanceID.m (renamed from Firestore/Source/API/FIRSetOptions+Internal.h)16
-rw-r--r--Functions/Example/Tests/FIRFunctionsTests.m50
-rw-r--r--Functions/Example/Tests/FUNSerializerTests.m240
-rw-r--r--Functions/Example/Tests/Tests-Info.plist22
-rw-r--r--Functions/Example/Tests/en.lproj/InfoPlist.strings2
-rw-r--r--Functions/FirebaseFunctions/FIRFunctions+Internal.h50
-rw-r--r--Functions/FirebaseFunctions/FIRFunctions.m247
-rw-r--r--Functions/FirebaseFunctions/FIRHTTPSCallable+Internal.h46
-rw-r--r--Functions/FirebaseFunctions/FIRHTTPSCallable.m71
-rw-r--r--Functions/FirebaseFunctions/FUNContext.h44
-rw-r--r--Functions/FirebaseFunctions/FUNContext.m87
-rw-r--r--Functions/FirebaseFunctions/FUNError.h34
-rw-r--r--Functions/FirebaseFunctions/FUNError.m189
-rw-r--r--Functions/FirebaseFunctions/FUNInstanceIDProxy.h29
-rw-r--r--Functions/FirebaseFunctions/FUNInstanceIDProxy.m57
-rw-r--r--Functions/FirebaseFunctions/FUNSerializer.h33
-rw-r--r--Functions/FirebaseFunctions/FUNSerializer.m231
-rw-r--r--Functions/FirebaseFunctions/FUNUsageValidation.h38
-rw-r--r--Functions/FirebaseFunctions/FUNUsageValidation.m28
-rw-r--r--Functions/FirebaseFunctions/Public/FIRError.h91
-rw-r--r--Functions/FirebaseFunctions/Public/FIRFunctions.h66
-rw-r--r--Functions/FirebaseFunctions/Public/FIRHTTPSCallable.h94
-rw-r--r--Functions/README.md21
-rw-r--r--Gemfile.lock30
-rw-r--r--README.md120
-rw-r--r--cmake/CompilerSetup.cmake99
-rw-r--r--cmake/ExternalProjectFlags.cmake71
-rw-r--r--cmake/FindFirebaseCore.cmake56
-rw-r--r--cmake/FindGRPC.cmake142
-rw-r--r--cmake/FindLevelDB.cmake55
-rw-r--r--cmake/FindNanopb.cmake48
-rw-r--r--cmake/external/FirebaseCore.cmake23
-rw-r--r--cmake/external/firestore.cmake28
-rw-r--r--cmake/external/googletest.cmake21
-rw-r--r--cmake/external/grpc.cmake90
-rw-r--r--cmake/external/leveldb.cmake74
-rw-r--r--cmake/external/nanopb.cmake64
-rw-r--r--cmake/external/protobuf.cmake31
-rw-r--r--cmake/utils.cmake80
-rw-r--r--cmake/xcodebuild.cmake89
-rw-r--r--patch/FirebaseAnalytics.h16
-rwxr-xr-xscripts/build.sh184
-rwxr-xr-xscripts/check_copyright.sh31
-rwxr-xr-xscripts/check_no_module_imports.sh41
-rwxr-xr-xscripts/check_whitespace.sh33
-rw-r--r--scripts/cpplint.py6273
-rwxr-xr-xscripts/if_changed.sh74
-rwxr-xr-xscripts/lint.sh87
-rwxr-xr-xscripts/style.sh109
-rwxr-xr-xtest.sh47
947 files changed, 59993 insertions, 17402 deletions
diff --git a/.gitignore b/.gitignore
index 93e3974..7645c89 100644
--- a/.gitignore
+++ b/.gitignore
@@ -33,6 +33,9 @@ DerivedData
*.hmap
*.ipa
+# IntelliJ
+.idea
+
# Vim
*.swo
*.swp
@@ -55,5 +58,10 @@ Pods/
Podfile.lock
*.xcworkspace
+# Firestore's build configuration, as generated by CocoaPods
+Firestore/core/src/firebase/firestore/util/config.h
+
# CMake
.downloads
+Debug
+Release
diff --git a/.travis.yml b/.travis.yml
index 351bb85..d68fafc 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,29 +1,92 @@
-osx_image: xcode9.1
+os: osx
+osx_image: xcode9.2
language: objective-c
cache:
- bundler
- cocoapods
rvm: 2.3.1
-before_install:
-# Add next line back with updated DeviceUDID for xcode9.1 if stability issues with simulator
-# - open -a "simulator" --args -CurrentDeviceUDID ABBD7191-486B-462F-80B4-AE08C5820DA1
- - bundle install
- - gem install xcpretty
- - bundle exec pod install --project-directory=Example --repo-update
- - bundle exec pod install --project-directory=Firestore/Example --no-repo-update
-
-script:
- - "! git grep -I ' $'" # Fail on trailing whitespace in non-binary files
- - ./test.sh
- - bundle exec pod lib lint FirebaseCore.podspec
-
-# TODO - Uncomment subsequent lines once FirebaseCore source repo is in public Specs repo
-# - bundle exec pod lib lint FirebaseAuth.podspec
-# - bundle exec pod lib lint FirebaseDatabase.podspec
-# - bundle exec pod lib lint FirebaseMessaging.podspec --allow-warnings #pending #390 fix
-# - bundle exec pod lib lint FirebaseStorage.podspec
-# - bundle exec pod lib lint Firestore/Firestore.podspec
+
+jobs:
+ include:
+ - stage: checks
+ # This only needs to be run once, so restrict it to an arbitrary combination
+ before_install:
+ - brew install clang-format
+ - brew install swiftformat
+ script:
+ - ./scripts/check_whitespace.sh
+ - ./scripts/check_copyright.sh
+ - ./scripts/check_no_module_imports.sh
+ - ./scripts/style.sh test-only $TRAVIS_COMMIT_RANGE
+ # Google C++ style compliance
+ - ./scripts/lint.sh $TRAVIS_COMMIT_RANGE
+
+ - stage: test
+ env:
+ - PROJECT=Firebase PLATFORM=iOS
+ before_install:
+ # Add next line back with updated DeviceUDID for xcode9.1 if stability issues with simulator
+ # - open -a "simulator" --args -CurrentDeviceUDID ABBD7191-486B-462F-80B4-AE08C5820DA1
+ - bundle install
+ - gem install xcpretty
+ - ./scripts/if_changed.sh bundle exec pod install --project-directory=Example --repo-update
+ - ./scripts/if_changed.sh bundle exec pod install --project-directory=Functions/Example
+ script:
+ - ./scripts/if_changed.sh ./scripts/build.sh $PROJECT $PLATFORM
+
+ # TODO fix os_log deprecation warning in FIRLogger to remove --allow-warnings
+ - ./scripts/if_changed.sh bundle exec pod lib lint FirebaseCore.podspec --allow-warnings
+
+ # TODO - Uncomment subsequent lines once FirebaseCore source repo is in public Specs repo
+ # - bundle exec pod lib lint FirebaseAuth.podspec
+ # - bundle exec pod lib lint FirebaseDatabase.podspec
+ # - bundle exec pod lib lint FirebaseMessaging.podspec
+ # - bundle exec pod lib lint FirebaseStorage.podspec
+ # - bundle exec pod lib lint FirebaseFirestore.podspec
+
+ - stage: test
+ env:
+ - PROJECT=Firestore PLATFORM=iOS METHOD=xcodebuild
+ before_install:
+ - bundle install
+ - gem install xcpretty
+ - ./scripts/if_changed.sh bundle exec pod install --project-directory=Firestore/Example --repo-update
+ script:
+ - ./scripts/if_changed.sh ./scripts/build.sh $PROJECT $PLATFORM $METHOD
+
+ - stage: test
+ env:
+ - PROJECT=Firestore PLATFORM=macOS METHOD=cmake
+ before_install:
+ - bundle install
+ - gem install xcpretty
+ - brew install cmake
+ - brew install go # Somehow the build for Abseil requires this.
+ - ./scripts/if_changed.sh bundle exec pod install --project-directory=Example --repo-update
+ - ./scripts/if_changed.sh bundle exec pod install --project-directory=Firestore/Example --no-repo-update
+ script:
+ - ./scripts/if_changed.sh ./scripts/build.sh $PROJECT $PLATFORM $METHOD
+
+ - stage: test
+ env:
+ - PROJECT=Firebase PLATFORM=macOS
+ before_install:
+ - bundle install
+ - gem install xcpretty
+ - ./scripts/if_changed.sh bundle exec pod install --project-directory=Example --repo-update
+ script:
+ - ./scripts/if_changed.sh ./scripts/build.sh $PROJECT $PLATFORM
+
+ - stage: test
+ env:
+ - PROJECT=Firebase PLATFORM=tvOS
+ before_install:
+ - bundle install
+ - gem install xcpretty
+ - ./scripts/if_changed.sh bundle exec pod install --project-directory=Example --repo-update
+ script:
+ - ./scripts/if_changed.sh ./scripts/build.sh $PROJECT $PLATFORM
branches:
only:
diff --git a/CMakeLists.txt b/CMakeLists.txt
index f9e7527..15b68b5 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -17,14 +17,29 @@
cmake_minimum_required(VERSION 2.8.11)
project(firebase C CXX)
+# If no build type is specified, make it a debug build
+if(NOT CMAKE_BUILD_TYPE)
+ set(CMAKE_BUILD_TYPE Debug)
+endif()
+
+if(APPLE)
+ # When building on the apple platform certain Objective-C++ classes bridge
+ # back into other Firebase Cocoapods. This requires shelling out to xcodebuild
+ # to verify the built frameworks are up-to-date. You can disable this to speed
+ # up the build.
+ option(BUILD_PODS, "Always build dependent cocoapods." ON)
+endif(APPLE)
+
list(INSERT CMAKE_MODULE_PATH 0 ${PROJECT_SOURCE_DIR}/cmake)
-# External Projects install into this prefix and download into the source tree
-# to avoid re-downloading repeatedly during development.
-set(FIREBASE_INSTALL_DIR "${PROJECT_BINARY_DIR}/usr")
-set(FIREBASE_DOWNLOAD_DIR "${PROJECT_SOURCE_DIR}/.downloads")
+set(FIREBASE_INSTALL_DIR ${PROJECT_BINARY_DIR})
enable_testing()
+include(external/FirebaseCore)
include(external/googletest)
+include(external/leveldb)
+include(external/grpc)
+include(external/protobuf)
+include(external/nanopb)
include(external/firestore)
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..5903dd7
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,93 @@
+# Firebase Code of Conduct
+
+## Our Pledge
+
+In the interest of fostering an open and welcoming environment, we as
+contributors and maintainers pledge to making participation in our project and
+our community a harassment-free experience for everyone, regardless of age, body
+size, disability, ethnicity, gender identity and expression, level of
+experience, education, socio-economic status, nationality, personal appearance,
+race, religion, or sexual identity and orientation.
+
+## Our Standards
+
+Examples of behavior that contributes to creating a positive environment
+include:
+
+* Using welcoming and inclusive language
+* Being respectful of differing viewpoints and experiences
+* Gracefully accepting constructive criticism
+* Focusing on what is best for the community
+* Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery and unwelcome sexual attention or
+ advances
+* Trolling, insulting/derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or electronic
+ address, without explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+ professional setting
+
+## Our Responsibilities
+
+Project maintainers are responsible for clarifying the standards of acceptable
+behavior and are expected to take appropriate and fair corrective action in
+response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or reject
+comments, commits, code, wiki edits, issues, and other contributions that are
+not aligned to this Code of Conduct, or to ban temporarily or permanently any
+contributor for other behaviors that they deem inappropriate, threatening,
+offensive, or harmful.
+
+## Scope
+
+This Code of Conduct applies both within project spaces and in public spaces
+when an individual is representing the project or its community. Examples of
+representing a project or community include using an official project e-mail
+address, posting via an official social media account, or acting as an appointed
+representative at an online or offline event. Representation of a project may be
+further defined and clarified by project maintainers.
+
+This Code of Conduct also applies outside the project spaces when the Project
+Steward has a reasonable belief that an individual's behavior may have a
+negative impact on the project or its community.
+
+## Conflict Resolution
+
+We do not believe that all conflict is bad; healthy debate and disagreement
+often yield positive results. However, it is never okay to be disrespectful or
+to engage in behavior that violates the project’s code of conduct.
+
+If you see someone violating the code of conduct, you are encouraged to address
+the behavior directly with those involved. Many issues can be resolved quickly
+and easily, and this gives people more control over the outcome of their
+dispute. If you are unable to resolve the matter for any reason, or if the
+behavior is threatening or harassing, report it. We are dedicated to providing
+an environment where participants feel welcome and safe.
+
+Reports should be directed to Paul Beusterien(paulbeusterien@google.com), the
+Project Steward for the Firebase iOS SDK. It is the Project Steward’s duty to
+receive and address reported violations of the code of conduct. They will then
+work with a committee consisting of representatives from the Open Source
+Programs Office and the Google Open Source Strategy team. If for any reason you
+are uncomfortable reaching out the Project Steward, please email
+opensource@google.com.
+
+We will investigate every complaint, but you may not receive a direct response.
+We will use our discretion in determining when and how to follow up on reported
+incidents, which may range from not taking action to permanent expulsion from
+the project and project-sponsored spaces. We will notify the accused of the
+report and provide them an opportunity to discuss it before any action is taken.
+The identity of the reporter will be omitted from the details of the report
+supplied to the accused. In potentially harmful situations, such as ongoing
+harassment or threats to anyone's safety, we may take action without notice.
+
+## Attribution
+
+This Code of Conduct is adapted from the Contributor Covenant, version 1.4,
+available at
+https://www.contributor-covenant.org/version/1/4/code-of-conduct.html \ No newline at end of file
diff --git a/Carthage.md b/Carthage.md
new file mode 100644
index 0000000..8f9c331
--- /dev/null
+++ b/Carthage.md
@@ -0,0 +1,85 @@
+# Firebase Carthage
+
+## Context
+
+This page introduces and provides instructions for an **experimental** Firebase
+[Carthage](https://github.com/Carthage/Carthage) implementation. Based on
+feedback and usage, the Firebase team may decide to make the Carthage
+distribution official.
+
+Please [let us know](https://github.com/firebase/firebase-ios-sdk/issues) if you
+have suggestions about how best to distribute Carthage binaries that include
+resource bundles.
+
+## Carthage Installation
+
+[Homebrew](http://brew.sh/) is one way to install Carthage.
+
+```bash
+$ brew update
+$ brew install carthage
+```
+
+See the
+[Carthage page](https://github.com/Carthage/Carthage#installing-carthage) for
+more details and additional installation methods.
+
+## Carthage Usage
+
+- Create a Cartfile with a **subset** of the following components - choosing the
+Firebase components that you want to include in your app. Note that
+**FirebaseAnalytics** must always be included.
+```
+binary "https://dl.google.com/dl/firebase/ios/carthage/FirebaseABTestingBinary.json"
+binary "https://dl.google.com/dl/firebase/ios/carthage/FirebaseAdMobBinary.json"
+binary "https://dl.google.com/dl/firebase/ios/carthage/FirebaseAnalyticsBinary.json"
+binary "https://dl.google.com/dl/firebase/ios/carthage/FirebaseAuthBinary.json"
+binary "https://dl.google.com/dl/firebase/ios/carthage/FirebaseCrashBinary.json"
+binary "https://dl.google.com/dl/firebase/ios/carthage/FirebaseDatabaseBinary.json"
+binary "https://dl.google.com/dl/firebase/ios/carthage/FirebaseDynamicLinksBinary.json"
+binary "https://dl.google.com/dl/firebase/ios/carthage/FirebaseFirestoreBinary.json"
+binary "https://dl.google.com/dl/firebase/ios/carthage/FirebaseFunctionsBinary.json"
+binary "https://dl.google.com/dl/firebase/ios/carthage/FirebaseInvitesBinary.json"
+binary "https://dl.google.com/dl/firebase/ios/carthage/FirebaseMessagingBinary.json"
+binary "https://dl.google.com/dl/firebase/ios/carthage/FirebasePerformanceBinary.json"
+binary "https://dl.google.com/dl/firebase/ios/carthage/FirebaseRemoteConfigBinary.json"
+binary "https://dl.google.com/dl/firebase/ios/carthage/FirebaseStorageBinary.json"
+```
+- Run `carthage update`
+- Use Finder to open `Carthage/Build/iOS`.
+- Copy the contents into the top level of your Xcode project and make sure
+ they're added to the right build target(s).
+- Add the -ObjC flag to "Other Linker Flags".
+- Make sure that the build target(s) includes your project's `GoogleService-Info.plist`.
+- [Delete Firebase.framework from the Link Binary With Libraries Build Phase](https://github.com/firebase/firebase-ios-sdk/issues/911#issuecomment-372455235).
+- If you're including a Firebase component that has resources, copy its bundles
+ into the Xcode project and make sure they're added to the
+ `Copy Bundle Resources` Build Phase :
+ - For Firestore:
+ - ./Carthage/Build/iOS/gRPC.framework/gRPCCertificates.bundle
+ - For Invites:
+ - ./Carthage/Build/iOS/FirebaseInvites.framework/GoogleSignIn.bundle
+ - ./Carthage/Build/iOS/FirebaseInvites.framework/GPPACLPickerResources.bundle
+ - ./Carthage/Build/iOS/FirebaseInvites.framework/GINInviteResources.bundle
+
+## Versioning
+
+Unlike the CocoaPods distribution, the Carthage distribution is like the
+Firebase zip release in that all the Firebase components share the same version.
+Mixing and matching components with different versions may cause linker errors.
+
+## Static Frameworks
+
+Note that the Firebase frameworks in the distribution include static libraries.
+While it is fine to link these into apps, it will generally not work to depend
+on them from wrapper dynamic frameworks.
+
+## Acknowledgements
+
+Thanks to the Firebase community for encouraging this implementation including
+those who have made this the most updated
+[firebase-ios-sdk](https://github.com/firebase/firebase-ios-sdk)
+[issue](https://github.com/firebase/firebase-ios-sdk/issues/9).
+
+Thanks also to those who have already done Firebase Carthage implementations,
+such as https://github.com/soheilbm/Firebase.
diff --git a/Example/Auth/ApiTests/AuthCredentialsTemplate.h b/Example/Auth/ApiTests/AuthCredentialsTemplate.h
index a8bf379..09eb62a 100644
--- a/Example/Auth/ApiTests/AuthCredentialsTemplate.h
+++ b/Example/Auth/ApiTests/AuthCredentialsTemplate.h
@@ -50,6 +50,9 @@ The name of the test user for Facebook Login.
$KCUSTOM_AUTH_TOKEN_URL
A URL to return a Custom Auth token.
+$KCUSTOM_AUTH_TOKEN_EXPIRED_URL
+A URL to return an expired Custom Auth token.
+
$KCUSTOM_AUTH_USER_ID
The ID of the test user in the Custom Auth token.
*/
@@ -61,4 +64,5 @@ The ID of the test user in the Custom Auth token.
#define KFACEBOOK_APP_ACCESS_TOKEN $KFACEBOOK_APP_ACCESS_TOKEN
#define KFACEBOOK_USER_NAME $KFACEBOOK_USER_NAME
#define KCUSTOM_AUTH_TOKEN_URL $KCUSTOM_AUTH_TOKEN_URL
+#define KCUSTOM_AUTH_TOKEN_EXPIRED_URL $KCUSTOM_AUTH_TOKEN_EXPIRED_URL
#define KCUSTOM_AUTH_USER_ID $KCUSTOM_AUTH_USER_ID
diff --git a/Example/Auth/ApiTests/FirebaseAuthApiTests.m b/Example/Auth/ApiTests/FirebaseAuthApiTests.m
index 454d9dd..741814c 100644
--- a/Example/Auth/ApiTests/FirebaseAuthApiTests.m
+++ b/Example/Auth/ApiTests/FirebaseAuthApiTests.m
@@ -36,6 +36,10 @@ static NSString *const kCustomAuthTestingAccountUserID = KCUSTOM_AUTH_USER_ID;
/** The url for obtaining a valid custom token string used to test Custom Auth. */
static NSString *const kCustomTokenUrl = KCUSTOM_AUTH_TOKEN_URL;
+/** The url for obtaining an expired but valid custom token string used to test Custom Auth failure.
+ */
+static NSString *const kExpiredCustomTokenUrl = KCUSTOM_AUTH_TOKEN_EXPIRED_URL;
+
/** Facebook app access token that will be used for Facebook Graph API, which is different from
* account access token.
*/
@@ -59,6 +63,12 @@ static NSString *const kTestingEmailToCreateUser = @"abc@xyz.com";
/** The testing email address for testSignInExistingUserWithEmailAndPassword. */
static NSString *const kExistingTestingEmailToSignIn = @"456@abc.com";
+/** The testing email address for testUpdatingUsersEmail. */
+static NSString *const kNewTestingEmail = @"updatedEmail@abc.com";
+
+/** The testing password for testSignInExistingUserWithModifiedEmailAndPassword. */
+static NSString *const kNewTestingPasswordToSignIn = @"password_new";
+
/** Error message for invalid custom token sign in. */
NSString *kInvalidTokenErrorMessage =
@"The custom token format is incorrect. Please check the documentation.";
@@ -71,7 +81,7 @@ NSString *kGoogleCliendId = KGOOGLE_CLIENT_ID;
*/
NSString *kGoogleTestAccountRefreshToken = KGOOGLE_TEST_ACCOUNT_REFRESH_TOKEN;
-static NSTimeInterval const kExpectationsTimeout = 30;
+static NSTimeInterval const kExpectationsTimeout = 10;
#ifdef NO_NETWORK
#define SKIP_IF_ON_MOBILE_HARNESS \
@@ -141,6 +151,38 @@ static NSTimeInterval const kExpectationsTimeout = 30;
[self deleteCurrentFirebaseUser];
}
+- (void)testUpdatingUsersEmail {
+ SKIP_IF_ON_MOBILE_HARNESS
+ FIRAuth *auth = [FIRAuth auth];
+ if (!auth) {
+ XCTFail(@"Could not obtain auth object.");
+ }
+
+ __block NSError *apiError;
+ XCTestExpectation *expectation =
+ [self expectationWithDescription:@"Created account with email and password."];
+ [auth createUserWithEmail:kTestingEmailToCreateUser
+ password:@"password"
+ completion:^(FIRUser *user, NSError *error) {
+ apiError = error;
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationsTimeout handler:nil];
+ expectation = [self expectationWithDescription:@"Created account with email and password."];
+ XCTAssertEqualObjects(auth.currentUser.email, kTestingEmailToCreateUser);
+ XCTAssertNil(apiError);
+ [auth.currentUser updateEmail:kNewTestingEmail
+ completion:^(NSError *_Nullable error) {
+ apiError = error;
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationsTimeout handler:nil];
+ XCTAssertNil(apiError);
+ XCTAssertEqualObjects(auth.currentUser.email, kNewTestingEmail);
+ // Clean up the created Firebase user for future runs.
+ [self deleteCurrentFirebaseUser];
+}
+
- (void)testLinkAnonymousAccountToFacebookAccount {
FIRAuth *auth = [FIRAuth auth];
if (!auth) {
@@ -253,6 +295,92 @@ static NSTimeInterval const kExpectationsTimeout = 30;
XCTAssertEqualObjects(auth.currentUser.uid, kCustomAuthTestingAccountUserID);
}
+- (void)testSignInWithValidCustomAuthExpiredToken {
+ FIRAuth *auth = [FIRAuth auth];
+ if (!auth) {
+ XCTFail(@"Could not obtain auth object.");
+ }
+
+ NSError *error;
+ NSString *customToken =
+ [NSString stringWithContentsOfURL:[NSURL URLWithString:kExpiredCustomTokenUrl]
+ encoding:NSUTF8StringEncoding
+ error:&error];
+ if (!customToken) {
+ XCTFail(@"There was an error retrieving the custom token: %@", error);
+ }
+ XCTestExpectation *expectation =
+ [self expectationWithDescription:@"CustomAuthToken sign-in finished."];
+
+ __block NSError *apiError;
+ [auth signInWithCustomToken:customToken
+ completion:^(FIRUser *_Nullable user, NSError *_Nullable error) {
+ if (error) {
+ apiError = error;
+ }
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationsTimeout
+ handler:^(NSError *error) {
+ if (error != nil) {
+ XCTFail(@"Failed to wait for expectations "
+ @"in CustomAuthToken sign in. Error: %@",
+ error.localizedDescription);
+ }
+ }];
+
+ XCTAssertNil(auth.currentUser);
+ XCTAssertEqual(apiError.code, FIRAuthErrorCodeInvalidCustomToken);
+}
+
+- (void)testInMemoryUserAfterSignOut {
+ FIRAuth *auth = [FIRAuth auth];
+ if (!auth) {
+ XCTFail(@"Could not obtain auth object.");
+ }
+ NSError *error;
+ NSString *customToken = [NSString stringWithContentsOfURL:[NSURL URLWithString:kCustomTokenUrl]
+ encoding:NSUTF8StringEncoding
+ error:&error];
+ if (!customToken) {
+ XCTFail(@"There was an error retrieving the custom token: %@", error);
+ }
+ XCTestExpectation *expectation =
+ [self expectationWithDescription:@"CustomAuthToken sign-in finished."];
+ __block NSError *rpcError;
+ [auth signInWithCustomToken:customToken
+ completion:^(FIRUser *_Nullable user, NSError *_Nullable error) {
+ if (error) {
+ rpcError = error;
+ }
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationsTimeout
+ handler:^(NSError *error) {
+ if (error != nil) {
+ XCTFail(@"Failed to wait for expectations "
+ @"in CustomAuthToken sign in. Error: %@",
+ error.localizedDescription);
+ }
+ }];
+ XCTAssertEqualObjects(auth.currentUser.uid, kCustomAuthTestingAccountUserID);
+ XCTAssertNil(rpcError);
+ FIRUser *inMemoryUser = auth.currentUser;
+ XCTestExpectation *expectation1 = [self expectationWithDescription:@"Profile data change."];
+ [auth signOut:NULL];
+ rpcError = nil;
+ NSString *newEmailAddress = [self fakeRandomEmail];
+ XCTAssertNotEqualObjects(newEmailAddress, inMemoryUser.email);
+ [inMemoryUser updateEmail:newEmailAddress completion:^(NSError *_Nullable error) {
+ rpcError = error;
+ [expectation1 fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationsTimeout handler:nil];
+ XCTAssertEqualObjects(inMemoryUser.email, newEmailAddress);
+ XCTAssertNil(rpcError);
+ XCTAssertNil(auth.currentUser);
+}
+
- (void)testSignInWithInvalidCustomAuthToken {
FIRAuth *auth = [FIRAuth auth];
if (!auth) {
@@ -354,6 +482,17 @@ static NSTimeInterval const kExpectationsTimeout = 30;
#pragma mark - Helpers
+/** Generate fake random email address */
+- (NSString *)fakeRandomEmail {
+ NSMutableString *fakeEmail = [[NSMutableString alloc] init];
+ for (int i=0; i<10; i++) {
+ [fakeEmail appendString:
+ [NSString stringWithFormat:@"%c", 'a' + arc4random_uniform('z' - 'a' + 1)]];
+ }
+ [fakeEmail appendString:@"@gmail.com"];
+ return fakeEmail;
+}
+
/** Sign out current account. */
- (void)signOut {
NSError *signOutError;
diff --git a/Example/Auth/App/GoogleService-Info.plist b/Example/Auth/App/GoogleService-Info.plist
index 89afffe..3f7547f 100644
--- a/Example/Auth/App/GoogleService-Info.plist
+++ b/Example/Auth/App/GoogleService-Info.plist
@@ -10,8 +10,6 @@
<string>correct_client_id</string>
<key>REVERSED_CLIENT_ID</key>
<string>correct_reversed_client_id</string>
- <key>ANDROID_CLIENT_ID</key>
- <string>correct_android_client_id</string>
<key>GOOGLE_APP_ID</key>
<string>1:123:ios:123abc</string>
<key>GCM_SENDER_ID</key>
diff --git a/Example/Auth/App/tvOS/AppDelegate.h b/Example/Auth/App/tvOS/AppDelegate.h
new file mode 100644
index 0000000..013891c
--- /dev/null
+++ b/Example/Auth/App/tvOS/AppDelegate.h
@@ -0,0 +1,21 @@
+// Copyright 2017 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 <UIKit/UIKit.h>
+
+@interface AppDelegate : UIResponder <UIApplicationDelegate>
+
+@property(strong, nonatomic) UIWindow *window;
+
+@end
diff --git a/Example/Auth/App/tvOS/AppDelegate.m b/Example/Auth/App/tvOS/AppDelegate.m
new file mode 100644
index 0000000..3935032
--- /dev/null
+++ b/Example/Auth/App/tvOS/AppDelegate.m
@@ -0,0 +1,63 @@
+// Copyright 2017 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 FirebaseCore;
+@import FirebaseAuth;
+
+#import "AppDelegate.h"
+
+@interface AppDelegate ()
+
+@end
+
+@implementation AppDelegate
+
+- (BOOL)application:(UIApplication *)application
+ didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
+ // Override point for customization after application launch.
+ [FIRApp configure];
+ return YES;
+}
+
+- (void)applicationWillResignActive:(UIApplication *)application {
+ // Sent when the application is about to move from active to inactive state. This can occur for
+ // certain types of temporary interruptions (such as an incoming phone call or SMS message) or
+ // when the user quits the application and it begins the transition to the background state. Use
+ // this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates.
+ // Games should use this method to pause the game.
+}
+
+- (void)applicationDidEnterBackground:(UIApplication *)application {
+ // Use this method to release shared resources, save user data, invalidate timers, and store
+ // enough application state information to restore your application to its current state in case
+ // it is terminated later. If your application supports background execution, this method is
+ // called instead of applicationWillTerminate: when the user quits.
+}
+
+- (void)applicationWillEnterForeground:(UIApplication *)application {
+ // Called as part of the transition from the background to the active state; here you can undo
+ // many of the changes made on entering the background.
+}
+
+- (void)applicationDidBecomeActive:(UIApplication *)application {
+ // Restart any tasks that were paused (or not yet started) while the application was inactive. If
+ // the application was previously in the background, optionally refresh the user interface.
+}
+
+- (void)applicationWillTerminate:(UIApplication *)application {
+ // Called when the application is about to terminate. Save data if appropriate. See also
+ // applicationDidEnterBackground:.
+}
+
+@end
diff --git a/Example/Auth/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json b/Example/Auth/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json
new file mode 100644
index 0000000..7f06667
--- /dev/null
+++ b/Example/Auth/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json
@@ -0,0 +1,11 @@
+{
+ "images" : [
+ {
+ "idiom" : "tv"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
diff --git a/Example/Auth/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json b/Example/Auth/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json
new file mode 100644
index 0000000..da4a164
--- /dev/null
+++ b/Example/Auth/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/Auth/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json b/Example/Auth/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json
new file mode 100644
index 0000000..d29f024
--- /dev/null
+++ b/Example/Auth/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json
@@ -0,0 +1,17 @@
+{
+ "layers" : [
+ {
+ "filename" : "Front.imagestacklayer"
+ },
+ {
+ "filename" : "Middle.imagestacklayer"
+ },
+ {
+ "filename" : "Back.imagestacklayer"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/Auth/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json b/Example/Auth/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json
new file mode 100644
index 0000000..7f06667
--- /dev/null
+++ b/Example/Auth/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json
@@ -0,0 +1,11 @@
+{
+ "images" : [
+ {
+ "idiom" : "tv"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
diff --git a/Example/Auth/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json b/Example/Auth/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json
new file mode 100644
index 0000000..da4a164
--- /dev/null
+++ b/Example/Auth/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/Auth/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json b/Example/Auth/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json
new file mode 100644
index 0000000..7f06667
--- /dev/null
+++ b/Example/Auth/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json
@@ -0,0 +1,11 @@
+{
+ "images" : [
+ {
+ "idiom" : "tv"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
diff --git a/Example/Auth/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json b/Example/Auth/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json
new file mode 100644
index 0000000..da4a164
--- /dev/null
+++ b/Example/Auth/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/Auth/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json b/Example/Auth/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json
new file mode 100644
index 0000000..16a370d
--- /dev/null
+++ b/Example/Auth/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images" : [
+ {
+ "idiom" : "tv",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "tv",
+ "scale" : "2x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/Auth/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json b/Example/Auth/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json
new file mode 100644
index 0000000..da4a164
--- /dev/null
+++ b/Example/Auth/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/Auth/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json b/Example/Auth/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json
new file mode 100644
index 0000000..d29f024
--- /dev/null
+++ b/Example/Auth/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json
@@ -0,0 +1,17 @@
+{
+ "layers" : [
+ {
+ "filename" : "Front.imagestacklayer"
+ },
+ {
+ "filename" : "Middle.imagestacklayer"
+ },
+ {
+ "filename" : "Back.imagestacklayer"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/Auth/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json b/Example/Auth/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json
new file mode 100644
index 0000000..16a370d
--- /dev/null
+++ b/Example/Auth/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images" : [
+ {
+ "idiom" : "tv",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "tv",
+ "scale" : "2x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/Auth/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json b/Example/Auth/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json
new file mode 100644
index 0000000..da4a164
--- /dev/null
+++ b/Example/Auth/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/Auth/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json b/Example/Auth/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json
new file mode 100644
index 0000000..16a370d
--- /dev/null
+++ b/Example/Auth/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images" : [
+ {
+ "idiom" : "tv",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "tv",
+ "scale" : "2x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/Auth/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json b/Example/Auth/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json
new file mode 100644
index 0000000..da4a164
--- /dev/null
+++ b/Example/Auth/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/Auth/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json b/Example/Auth/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json
new file mode 100644
index 0000000..b03ded1
--- /dev/null
+++ b/Example/Auth/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json
@@ -0,0 +1,32 @@
+{
+ "assets" : [
+ {
+ "size" : "1280x768",
+ "idiom" : "tv",
+ "filename" : "App Icon - App Store.imagestack",
+ "role" : "primary-app-icon"
+ },
+ {
+ "size" : "400x240",
+ "idiom" : "tv",
+ "filename" : "App Icon.imagestack",
+ "role" : "primary-app-icon"
+ },
+ {
+ "size" : "2320x720",
+ "idiom" : "tv",
+ "filename" : "Top Shelf Image Wide.imageset",
+ "role" : "top-shelf-image-wide"
+ },
+ {
+ "size" : "1920x720",
+ "idiom" : "tv",
+ "filename" : "Top Shelf Image.imageset",
+ "role" : "top-shelf-image"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
diff --git a/Example/Auth/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json b/Example/Auth/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json
new file mode 100644
index 0000000..16a370d
--- /dev/null
+++ b/Example/Auth/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images" : [
+ {
+ "idiom" : "tv",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "tv",
+ "scale" : "2x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/Auth/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json b/Example/Auth/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json
new file mode 100644
index 0000000..16a370d
--- /dev/null
+++ b/Example/Auth/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images" : [
+ {
+ "idiom" : "tv",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "tv",
+ "scale" : "2x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/Auth/App/tvOS/Assets.xcassets/Contents.json b/Example/Auth/App/tvOS/Assets.xcassets/Contents.json
new file mode 100644
index 0000000..da4a164
--- /dev/null
+++ b/Example/Auth/App/tvOS/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/Auth/App/tvOS/Assets.xcassets/LaunchImage.launchimage/Contents.json b/Example/Auth/App/tvOS/Assets.xcassets/LaunchImage.launchimage/Contents.json
new file mode 100644
index 0000000..d746a60
--- /dev/null
+++ b/Example/Auth/App/tvOS/Assets.xcassets/LaunchImage.launchimage/Contents.json
@@ -0,0 +1,22 @@
+{
+ "images" : [
+ {
+ "orientation" : "landscape",
+ "idiom" : "tv",
+ "extent" : "full-screen",
+ "minimum-system-version" : "11.0",
+ "scale" : "2x"
+ },
+ {
+ "orientation" : "landscape",
+ "idiom" : "tv",
+ "extent" : "full-screen",
+ "minimum-system-version" : "9.0",
+ "scale" : "1x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/Auth/App/tvOS/Info.plist b/Example/Auth/App/tvOS/Info.plist
new file mode 100644
index 0000000..02942a3
--- /dev/null
+++ b/Example/Auth/App/tvOS/Info.plist
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>$(DEVELOPMENT_LANGUAGE)</string>
+ <key>CFBundleExecutable</key>
+ <string>$(EXECUTABLE_NAME)</string>
+ <key>CFBundleIdentifier</key>
+ <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>$(PRODUCT_NAME)</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>1.0</string>
+ <key>CFBundleVersion</key>
+ <string>1</string>
+ <key>LSRequiresIPhoneOS</key>
+ <true/>
+ <key>UIMainStoryboardFile</key>
+ <string>Main</string>
+ <key>UIRequiredDeviceCapabilities</key>
+ <array>
+ <string>arm64</string>
+ </array>
+ <key>UIUserInterfaceStyle</key>
+ <string>Automatic</string>
+</dict>
+</plist>
diff --git a/Example/Auth/App/tvOS/Main.storyboard b/Example/Auth/App/tvOS/Main.storyboard
new file mode 100644
index 0000000..72d5e22
--- /dev/null
+++ b/Example/Auth/App/tvOS/Main.storyboard
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder.AppleTV.Storyboard" version="3.0" toolsVersion="13122.16" systemVersion="17A278a" targetRuntime="AppleTV" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
+ <dependencies>
+ <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
+ <capability name="Safe area layout guides" minToolsVersion="9.0"/>
+ <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+ </dependencies>
+ <scenes>
+ <!--View Controller-->
+ <scene sceneID="tne-QT-ifu">
+ <objects>
+ <viewController id="BYZ-38-t0r" customClass="ViewController" customModuleProvider="" sceneMemberID="viewController">
+ <layoutGuides>
+ <viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
+ <viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
+ </layoutGuides>
+ <view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
+ <rect key="frame" x="0.0" y="0.0" width="1920" height="1080"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+ <color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
+ <viewLayoutGuide key="safeArea" id="wu6-TO-1qx"/>
+ </view>
+ </viewController>
+ <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
+ </objects>
+ </scene>
+ </scenes>
+</document>
diff --git a/Example/Auth/App/tvOS/ViewController.h b/Example/Auth/App/tvOS/ViewController.h
new file mode 100644
index 0000000..b6115b8
--- /dev/null
+++ b/Example/Auth/App/tvOS/ViewController.h
@@ -0,0 +1,19 @@
+// Copyright 2017 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 <UIKit/UIKit.h>
+
+@interface ViewController : UIViewController
+
+@end
diff --git a/Example/Auth/App/tvOS/ViewController.m b/Example/Auth/App/tvOS/ViewController.m
new file mode 100644
index 0000000..6d4676b
--- /dev/null
+++ b/Example/Auth/App/tvOS/ViewController.m
@@ -0,0 +1,33 @@
+// Copyright 2017 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 "ViewController.h"
+
+@interface ViewController ()
+
+@end
+
+@implementation ViewController
+
+- (void)viewDidLoad {
+ [super viewDidLoad];
+ // Do any additional setup after loading the view, typically from a nib.
+}
+
+- (void)didReceiveMemoryWarning {
+ [super didReceiveMemoryWarning];
+ // Dispose of any resources that can be recreated.
+}
+
+@end
diff --git a/Example/Auth/App/tvOS/main.m b/Example/Auth/App/tvOS/main.m
new file mode 100644
index 0000000..d9e6654
--- /dev/null
+++ b/Example/Auth/App/tvOS/main.m
@@ -0,0 +1,22 @@
+// Copyright 2017 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 <UIKit/UIKit.h>
+#import "AppDelegate.h"
+
+int main(int argc, char* argv[]) {
+ @autoreleasepool {
+ return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
+ }
+}
diff --git a/Example/Auth/Sample/MainViewController.m b/Example/Auth/Sample/MainViewController.m
index 5326463..d4915cd 100644
--- a/Example/Auth/Sample/MainViewController.m
+++ b/Example/Auth/Sample/MainViewController.m
@@ -28,6 +28,7 @@
#import "FIROAuthProvider.h"
#import "FIRPhoneAuthCredential.h"
#import "FIRPhoneAuthProvider.h"
+#import "FIRAuthTokenResult.h"
#import "FirebaseAuth.h"
#import "CustomTokenDataEntryViewController.h"
#import "FacebookAuthProvider.h"
@@ -38,17 +39,33 @@
#import "UserInfoViewController.h"
#import "UserTableViewCell.h"
+NS_ASSUME_NONNULL_BEGIN
-/*! @typedef textInputCompletionBlock
+/** @typedef textInputCompletionBlock
@brief The type of callback used to report text input prompt results.
*/
typedef void (^textInputCompletionBlock)(NSString *_Nullable userInput);
+/** @typedef testAutomationCallback
+ @brief The type of callback used when automatically testing an API.
+ */
+typedef void (^testAutomationCallback)(NSError *_Nullable error);
+
/** @var kTokenGetButtonText
@brief The text of the "Get Token" button.
*/
static NSString *const kTokenGetButtonText = @"Get Token";
+/** @var kGetTokenResultButtonText
+ @brief The text of the "Get Token Result" button.
+ */
+static NSString *const kGetTokenResultButtonText = @"Get Token Result";
+
+/** @var kGetTokenResultForceButtonText
+ @brief The text of the "Force Token Result" button.
+ */
+static NSString *const kGetTokenResultForceButtonText = @"Force Token Result";
+
/** @var kTokenRefreshButtonText
@brief The text of the "Refresh Token" button.
*/
@@ -85,6 +102,21 @@ static NSString *const kSetPhotoURLText = @"Set Photo url";
*/
static NSString *const kSignInGoogleButtonText = @"Sign in with Google";
+/** @var kSignInWithEmailLink
+ @brief The text of the "Sign in with Email Link" button.
+ */
+static NSString *const kSignInWithEmailLink = @"Sign in with Email Link";
+
+/** @var kVerifyEmailLinkAccount
+ @brief The text of the "Verify Email-link Account" button.
+ */
+static NSString *const kVerifyEmailLinkAccount = @"Verify Email-link Account";
+
+/** @var kSendEmailSignInLink
+ @brief The text of the "Send Email SignIn link" button
+*/
+static NSString *const kSendEmailSignInLink = @"Send Email Sign in Link";
+
/** @var kSignInAndRetrieveGoogleButtonText
@brief The text of the "Sign in with Google and retrieve data" button.
*/
@@ -273,6 +305,11 @@ static NSString *const kUnlinkFromEmailPassword = @"Unlink from Email/Password";
*/
static NSString *const kGetProvidersForEmail = @"Get Provider IDs for Email";
+/** @var kGetAllSignInMethodsForEmail
+ @brief The text of the "Get sign-in methods for Email" button.
+ */
+static NSString *const kGetAllSignInMethodsForEmail = @"Get Sign-in methods for Email";
+
/** @var kActionCodeTypeDescription
@brief The description of the "Action Type" entry.
*/
@@ -392,6 +429,11 @@ static NSString *const kRemoveIDTokenListenerTitle = @"Remove Last ID Token Chan
*/
static NSString *const kSectionTitleApp = @"APP";
+/** @var kSwitchToInMemoryUserTitle
+ @brief The text of the "Switch to in memory user" button.
+ */
+static NSString *const kSwitchToInMemoryUserTitle = @"Switch to in memory user";
+
/** @var kCreateUserTitle
@brief The text of the "Create User" button.
*/
@@ -708,6 +750,9 @@ typedef enum {
}],
]],
[StaticContentTableViewSection sectionWithTitle:kSectionTitleSignIn cells:@[
+ [StaticContentTableViewCell cellWithTitle:kSwitchToInMemoryUserTitle
+ value:nil
+ action:^{ [weakSelf updateToSavedUser]; }],
[StaticContentTableViewCell cellWithTitle:kCreateUserTitle
value:nil
action:^{ [weakSelf createUser]; }
@@ -716,6 +761,12 @@ typedef enum {
action:^{ [weakSelf createUserAuthDataResult]; }],
[StaticContentTableViewCell cellWithTitle:kSignInGoogleButtonText
action:^{ [weakSelf signInGoogle]; }],
+ [StaticContentTableViewCell cellWithTitle:kSignInWithEmailLink
+ action:^{ [weakSelf signInWithEmailLink]; }],
+ [StaticContentTableViewCell cellWithTitle:kVerifyEmailLinkAccount
+ action:^{ [weakSelf verifyEmailLinkAccount]; }],
+ [StaticContentTableViewCell cellWithTitle:kSendEmailSignInLink
+ action:^{ [weakSelf sendEmailSignInLink]; }],
[StaticContentTableViewCell cellWithTitle:kSignInGoogleAndRetrieveDataButtonText
action:^{ [weakSelf signInGoogleAndRetrieveData]; }],
[StaticContentTableViewCell cellWithTitle:kSignInFacebookButtonText
@@ -748,6 +799,8 @@ typedef enum {
action:^{ [weakSelf reloadUser]; }],
[StaticContentTableViewCell cellWithTitle:kGetProvidersForEmail
action:^{ [weakSelf getProvidersForEmail]; }],
+ [StaticContentTableViewCell cellWithTitle:kGetAllSignInMethodsForEmail
+ action:^{ [weakSelf getAllSignInMethodsForEmail]; }],
[StaticContentTableViewCell cellWithTitle:kUpdateEmailText
action:^{ [weakSelf updateEmail]; }],
[StaticContentTableViewCell cellWithTitle:kUpdatePasswordText
@@ -792,7 +845,11 @@ typedef enum {
[StaticContentTableViewCell cellWithTitle:kTokenGetButtonText
action:^{ [weakSelf getUserTokenWithForce:NO]; }],
[StaticContentTableViewCell cellWithTitle:kTokenRefreshButtonText
- action:^{ [weakSelf getUserTokenWithForce:YES]; }]
+ action:^{ [weakSelf getUserTokenWithForce:YES]; }],
+ [StaticContentTableViewCell cellWithTitle:kGetTokenResultButtonText
+ action:^{ [weakSelf getUserTokenResultWithForce:NO]; }],
+ [StaticContentTableViewCell cellWithTitle:kGetTokenResultForceButtonText
+ action:^{ [weakSelf getUserTokenResultWithForce:YES]; }],
]],
[StaticContentTableViewSection sectionWithTitle:kSectionTitleLinkUnlinkAccounts cells:@[
[StaticContentTableViewCell cellWithTitle:kLinkWithGoogleText
@@ -1651,7 +1708,7 @@ static NSDictionary<NSString *, NSString *> *parseURL(NSString *urlString) {
}
/** @fn signInEmailPassword
- @brief Invoked when "sign in with Email/Password" row is pressed.
+ @brief Invoked when "Sign in with Email/Password" row is pressed.
*/
- (void)signInEmailPassword {
[self showTextInputPromptWithMessage:@"Email Address:"
@@ -1718,6 +1775,98 @@ static NSDictionary<NSString *, NSString *> *parseURL(NSString *urlString) {
}];
}
+/** @fn signInWithEmailLink
+ @brief Invoked when "Sign in with email link" row is pressed.
+ */
+- (void)signInWithEmailLink {
+ [self showTextInputPromptWithMessage:@"Email Address:"
+ keyboardType:UIKeyboardTypeEmailAddress
+ completionBlock:^(BOOL userPressedOK, NSString *_Nullable email) {
+ if (!userPressedOK || !email.length) {
+ return;
+ }
+ [self showTextInputPromptWithMessage:@"Email Sign-In Link:"
+ completionBlock:^(BOOL userPressedOK, NSString *_Nullable link) {
+ if (!userPressedOK) {
+ return;
+ }
+ if ([[FIRAuth auth] isSignInWithEmailLink:link]) {
+ [self showSpinner:^{
+ [[AppManager auth] signInWithEmail:email
+ link:link
+ completion:^(FIRAuthDataResult *_Nullable authResult,
+ NSError *_Nullable error) {
+ [self hideSpinner:^{
+ if (error) {
+ [self logFailure:@"sign-in with Email/Sign-In failed" error:error];
+ } else {
+ [self logSuccess:@"sign-in with Email/Sign-In link succeeded."];
+ [self log:[NSString stringWithFormat:@"UID: %@",authResult.user.uid]];
+ }
+ [self showTypicalUIForUserUpdateResultsWithTitle:@"Sign-In Error" error:error];
+ }];
+ }];
+ }];
+ } else {
+ [self log:@"The sign-in link is invalid"];
+ }
+ }];
+ }];
+}
+
+/** @fn verifyEmailLinkAccount
+ @brief Invoked to verify that the current user is an email-link user.
+ */
+- (void)verifyEmailLinkAccount {
+ if (![FIRAuth auth].currentUser.email) {
+ [self showMessagePrompt:@"There is no signed-in user available."];
+ return;
+ }
+ [[FIRAuth auth] fetchSignInMethodsForEmail:[FIRAuth auth].currentUser.email
+ completion:^(NSArray<NSString *> *_Nullable signInMethods,
+ NSError *_Nullable error) {
+ if (error) {
+ [self showMessagePrompt:@"There was an error fetching sign-in methods."];
+ return;
+ }
+ if (![signInMethods containsObject:FIREmailLinkAuthSignInMethod]) {
+ [self showMessagePrompt:@"Error: The current user is NOT an email-link user."];
+ return;
+ }
+ [self showMessagePrompt:@"The current user is an email-link user."];
+ }];
+}
+
+/** @fn sendEmailSignInLink
+ @brief Invoked when "Send email sign-in link" row is pressed.
+ */
+- (void)sendEmailSignInLink {
+ [self showTextInputPromptWithMessage:@"Email:"
+ completionBlock:^(BOOL userPressedOK, NSString *_Nullable userInput) {
+ if (!userPressedOK) {
+ return;
+ }
+ [self showSpinner:^{
+ void (^requestEmailSignInLink)(void (^)(NSError *)) = ^(void (^completion)(NSError *)) {
+ [[AppManager auth] sendSignInLinkToEmail:userInput
+ actionCodeSettings:[self actionCodeSettings]
+ completion:completion];
+ };
+ requestEmailSignInLink(^(NSError *_Nullable error) {
+ [self hideSpinner:^{
+ if (error) {
+ [self logFailure:@"Email Link request failed" error:error];
+ [self showMessagePrompt:error.localizedDescription];
+ return;
+ }
+ [self logSuccess:@"Email Link request succeeded."];
+ [self showMessagePrompt:@"Sent"];
+ }];
+ });
+ }];
+ }];
+}
+
/** @fn signUpNewEmail
@brief Invoked if sign-in is attempted with new email/password.
@remarks Should only be called if @c FIRAuthErrorCodeInvalidEmail is encountered on attepmt to
@@ -1975,13 +2124,46 @@ static NSDictionary<NSString *, NSString *> *parseURL(NSString *urlString) {
}
/** @fn getUserTokenWithForce:
- @brief Gets the token from @c FIRUser , optionally a refreshed one.
+ @brief Gets the token from @c FIRUser, optionally a refreshed one.
@param force Whether the refresh is forced or not.
*/
- (void)getUserTokenWithForce:(BOOL)force {
[[self user] getIDTokenForcingRefresh:force completion:[self tokenCallback]];
}
+/** @fn getUserTokenResultWithForce:
+ @brief Gets the token result object from @c FIRUser, optionally a refreshed one.
+ @param force Whether the refresh is forced or not.
+ */
+- (void)getUserTokenResultWithForce:(BOOL)force {
+
+ [[self user] getIDTokenResultForcingRefresh:force
+ completion:^(FIRAuthTokenResult *_Nullable tokenResult,
+ NSError *_Nullable error) {
+ if (error) {
+ [self showMessagePromptWithTitle:kTokenRefreshErrorAlertTitle
+ message:error.localizedDescription
+ showCancelButton:NO
+ completion:nil];
+ [self logFailure:@"refresh token failed" error:error];
+ return;
+ }
+ [self logSuccess:@"refresh token succeeded."];
+ NSMutableString *message =
+ [[NSMutableString alloc] initWithString:
+ [NSString stringWithFormat:@"Token : %@\n", tokenResult.token]];
+ [message appendString:[NSString stringWithFormat:@"Auth Date : %@\n", tokenResult.authDate]];
+ [message appendString:
+ [NSString stringWithFormat:@"EXP Date : %@\n", tokenResult.expirationDate]];
+ [message appendString:
+ [NSString stringWithFormat:@"Issued Date : %@\n", tokenResult.issuedAtDate]];
+ [self showMessagePromptWithTitle:kTokenRefreshedAlertTitle
+ message:message
+ showCancelButton:NO
+ completion:nil];
+ }];
+}
+
/** @fn getAppTokenWithForce:
@brief Gets the token from @c FIRApp , optionally a refreshed one.
@param force Whether the refresh is forced or not.
@@ -2187,18 +2369,20 @@ static NSDictionary<NSString *, NSString *> *parseURL(NSString *urlString) {
@completion A completion block to be executed after the provider is unlinked.
*/
- (void)unlinkFromProvider:(NSString *)provider
- completion:(void(^)(NSError *_Nullable))completion {
+ completion:(nullable testAutomationCallback)completion {
[[self user] unlinkFromProvider:provider
completion:^(FIRUser *_Nullable user,
NSError *_Nullable error) {
if (error) {
[self logFailure:@"unlink auth provider failed" error:error];
- completion(error);
- } else {
- [self logSuccess:@"unlink auth provider succeeded."];
if (completion) {
- completion(nil);
+ completion(error);
}
+ return;
+ }
+ [self logSuccess:@"unlink auth provider succeeded."];
+ if (completion) {
+ completion(nil);
}
[self showTypicalUIForUserUpdateResultsWithTitle:kUnlinkTitle error:error];
}];
@@ -2237,6 +2421,39 @@ static NSDictionary<NSString *, NSString *> *parseURL(NSString *urlString) {
}];
}
+/** @fn getAllSignInMethodsForEmail
+ @brief Prompts user for an email address, calls @c FIRAuth.getAllSignInMethodsForEmail:callback:
+ and displays the result.
+ */
+- (void)getAllSignInMethodsForEmail {
+ [self showTextInputPromptWithMessage:@"Email:"
+ completionBlock:^(BOOL userPressedOK, NSString *_Nullable userInput) {
+ if (!userPressedOK || !userInput.length) {
+ return;
+ }
+
+ [self showSpinner:^{
+ [[AppManager auth] fetchSignInMethodsForEmail:userInput
+ completion:^(NSArray<NSString *> *_Nullable signInMethods,
+ NSError *_Nullable error) {
+ if (error) {
+ [self logFailure:@"get sign-in methods for email failed" error:error];
+ } else {
+ [self logSuccess:@"get sign-in methods for email succeeded."];
+ }
+ [self hideSpinner:^{
+ if (error) {
+ [self showMessagePrompt:error.localizedDescription];
+ return;
+ }
+ [self showMessagePrompt:[signInMethods componentsJoinedByString:@", "]];
+ }];
+ }];
+ }];
+ }];
+}
+
+
/** @fn actionCodeRequestTypeString
@brief Returns a string description for the type of the next action code request.
*/
@@ -2478,6 +2695,8 @@ static NSDictionary<NSString *, NSString *> *parseURL(NSString *urlString) {
return @"Recover Email";
case FIRActionCodeOperationPasswordReset:
return @"Password Reset";
+ case FIRActionCodeOperationEmailLink:
+ return @"Email Sign-In Link";
case FIRActionCodeOperationUnknown:
return @"Unknown action";
}
@@ -2533,6 +2752,30 @@ static NSDictionary<NSString *, NSString *> *parseURL(NSString *urlString) {
}];
}
+/** @fn updateToSavedUser
+ @brief updates the current user to the saved user.
+ */
+- (void)updateToSavedUser {
+ if(![AppManager auth].currentUser) {
+ NSLog(@"You must be signed in to perform this action");
+ return;
+ }
+
+ if (!_userInMemory) {
+ [self showMessagePrompt:[NSString stringWithFormat:@"You need an in-memory user to perform this"
+ "action, use the M+ button to save a user to memory.", nil]];
+ return;
+ }
+
+ [[AppManager auth] updateCurrentUser:_userInMemory completion:^(NSError *_Nullable error) {
+ if (error) {
+ [self showMessagePrompt:
+ [NSString stringWithFormat:@"An error Occurred: %@", error.localizedDescription]];
+ return;
+ }
+ }];
+}
+
/** @fn createUser
@brief Creates a new user.
*/
@@ -2642,7 +2885,7 @@ static NSDictionary<NSString *, NSString *> *parseURL(NSString *urlString) {
@completion A completion block to be executed after successful phone number sign in.
*/
- (void)signInWithPhoneNumber:(NSString *_Nullable)phoneNumber
- completion:(void(^)(NSError *_Nullable))completion {
+ completion:(nullable testAutomationCallback)completion {
[self showSpinner:^{
[[AppManager phoneAuthProvider] verifyPhoneNumber:phoneNumber
UIDelegate:nil
@@ -2707,15 +2950,23 @@ static NSDictionary<NSString *, NSString *> *parseURL(NSString *urlString) {
FIRAuthCredential *credential =
[[AppManager phoneAuthProvider] credentialWithVerificationID:verificationID
verificationCode:verificationCode];
- [[AppManager auth] signInWithCredential:credential
- completion:^(FIRUser *_Nullable user,
- NSError *_Nullable error) {
+ [[AppManager auth] signInAndRetrieveDataWithCredential:credential
+ completion:^(FIRAuthDataResult *_Nullable result,
+ NSError *_Nullable error) {
[self hideSpinner:^{
if (error) {
[self logFailure:@"failed to verify phone number" error:error];
[self showMessagePrompt:error.localizedDescription];
return;
}
+ if (_isNewUserToggleOn) {
+ NSString *newUserString = result.additionalUserInfo.isNewUser ?
+ @"New user" : @"Existing user";
+ [self showMessagePromptWithTitle:@"New or Existing"
+ message:newUserString
+ showCancelButton:NO
+ completion:nil];
+ }
}];
}];
}];
@@ -2727,7 +2978,7 @@ static NSDictionary<NSString *, NSString *> *parseURL(NSString *urlString) {
@completion A completion block to be executed after phone number is updated.
*/
- (void)updatePhoneNumber:(NSString *_Nullable)phoneNumber
- completion:(void(^)(NSError *_Nullable))completion{
+ completion:(nullable testAutomationCallback)completion {
[self showSpinner:^{
[[AppManager phoneAuthProvider] verifyPhoneNumber:phoneNumber
UIDelegate:nil
@@ -2736,7 +2987,9 @@ static NSDictionary<NSString *, NSString *> *parseURL(NSString *urlString) {
if (error) {
[self logFailure:@"failed to send verification code" error:error];
[self showMessagePrompt:error.localizedDescription];
- completion(error);
+ if (completion) {
+ completion(error);
+ }
return;
}
[self logSuccess:@"Code sent"];
@@ -2757,7 +3010,9 @@ static NSDictionary<NSString *, NSString *> *parseURL(NSString *urlString) {
if (error) {
[self logFailure:@"update phone number failed" error:error];
[self showMessagePrompt:error.localizedDescription];
- completion(error);
+ if (completion) {
+ completion(error);
+ }
} else {
[self logSuccess:@"update phone number succeeded."];
if (completion) {
@@ -2794,7 +3049,7 @@ static NSDictionary<NSString *, NSString *> *parseURL(NSString *urlString) {
@completion A completion block to be executed after linking phone number.
*/
- (void)linkPhoneNumber:(NSString *_Nullable)phoneNumber
- completion:(void(^)(NSError *_Nullable))completion{
+ completion:(nullable testAutomationCallback)completion {
[self showSpinner:^{
[[AppManager phoneAuthProvider] verifyPhoneNumber:phoneNumber
UIDelegate:nil
@@ -2804,7 +3059,9 @@ static NSDictionary<NSString *, NSString *> *parseURL(NSString *urlString) {
if (error) {
[self logFailure:@"failed to send verification code" error:error];
[self showMessagePrompt:error.localizedDescription];
- completion(error);
+ if (completion) {
+ completion(error);
+ }
return;
}
[self logSuccess:@"Code sent"];
@@ -2845,7 +3102,9 @@ static NSDictionary<NSString *, NSString *> *parseURL(NSString *urlString) {
if (error) {
[self logFailure:@"failed to verify phone number" error:error];
[self showMessagePrompt:error.localizedDescription];
- completion(error);
+ if (completion) {
+ completion(error);
+ }
return;
}
}];
@@ -3147,3 +3406,5 @@ static NSDictionary<NSString *, NSString *> *parseURL(NSString *urlString) {
}
@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Example/Auth/Tests/FIRAuthAPNSTokenManagerTests.m b/Example/Auth/Tests/FIRAuthAPNSTokenManagerTests.m
index 68e8bcf..79ffb7a 100644
--- a/Example/Auth/Tests/FIRAuthAPNSTokenManagerTests.m
+++ b/Example/Auth/Tests/FIRAuthAPNSTokenManagerTests.m
@@ -31,7 +31,7 @@ static const NSTimeInterval kRegistrationTimeout = .5;
@brief The test expectation timeout.
@remarks This must be considerably greater than @c kVerificationTimeout .
*/
-static const NSTimeInterval kExpectationTimeout = 1;
+static const NSTimeInterval kExpectationTimeout = 2;
/** @class FIRAuthLegacyUIApplication
@brief A fake legacy (< iOS 7) UIApplication class.
diff --git a/Example/Auth/Tests/FIRAuthDispatcherTests.m b/Example/Auth/Tests/FIRAuthDispatcherTests.m
index fc8cab1..193be4c 100644
--- a/Example/Auth/Tests/FIRAuthDispatcherTests.m
+++ b/Example/Auth/Tests/FIRAuthDispatcherTests.m
@@ -32,7 +32,7 @@ NSTimeInterval kTestDelay = 0.1;
/** @var kExpectationTimeout
@brief The maximum time waiting for expectations to fulfill.
*/
-static const NSTimeInterval kExpectationTimeout = 1;
+static const NSTimeInterval kExpectationTimeout = 2;
id<OS_dispatch_queue> testWorkQueue;
diff --git a/Example/Auth/Tests/FIRAuthTests.m b/Example/Auth/Tests/FIRAuthTests.m
index 80a9ae9..47a430b 100644
--- a/Example/Auth/Tests/FIRAuthTests.m
+++ b/Example/Auth/Tests/FIRAuthTests.m
@@ -20,9 +20,7 @@
#import <FirebaseCore/FIRAppInternal.h>
-#import <FirebaseAuth/FIREmailAuthProvider.h>
-#import <FirebaseAuth/FIRGoogleAuthProvider.h>
-#import <FirebaseAuth/FIRAdditionalUserInfo.h>
+#import <FirebaseAuth/FirebaseAuth.h>
#import "FIRAuth_Internal.h"
#import "FIRAuthOperationType.h"
@@ -33,6 +31,8 @@
#import "FIRAuthBackend.h"
#import "FIRCreateAuthURIRequest.h"
#import "FIRCreateAuthURIResponse.h"
+#import "FIREmailLinkSignInRequest.h"
+#import "FIREmailLinkSignInResponse.h"
#import "FIRGetAccountInfoRequest.h"
#import "FIRGetAccountInfoResponse.h"
#import "FIRGetOOBConfirmationCodeRequest.h"
@@ -56,6 +56,7 @@
#import "FIRApp+FIRAuthUnitTests.h"
#import "OCMStubRecorder+FIRAuthUnitTests.h"
#import <OCMock/OCMock.h>
+#import "FIRActionCodeSettings.h"
#if TARGET_OS_IOS
#import "FIRPhoneAuthCredential.h"
@@ -167,10 +168,51 @@ static NSString *const kVerificationCode = @"12345678";
*/
static NSString *const kVerificationID = @"55432";
+/** @var kContinueURL
+ @brief Fake string value of continue url.
+ */
+static NSString *const kContinueURL = @"continueURL";
+
+/** @var kCanHandleCodeInAppKey
+ @brief The key for the request parameter indicating whether the action code can be handled in
+ the app or not.
+ */
+static NSString *const kCanHandleCodeInAppKey = @"canHandleCodeInApp";
+
+/** @var kFIREmailLinkAuthSignInMethod
+ @brief Fake email link sign-in method for testing.
+ */
+static NSString *const kFIREmailLinkAuthSignInMethod = @"emailLink";
+
+/** @var kFIRFacebookAuthSignInMethod
+ @brief Fake Facebook sign-in method for testing.
+ */
+static NSString *const kFIRFacebookAuthSignInMethod = @"facebook.com";
+
+/** @var kBadSignInEmailLink
+ @brief Bad sign-in link to test email link sign-in
+ */
+static NSString *const kBadSignInEmailLink = @"http://www.facebook.com";
+
+/** @var kFakeEmailSignInDeeplink
+ @brief Fake email sign-in link
+ */
+static NSString *const kFakeEmailSignInDeeplink =
+ @"https://example.domain.com/?apiKey=testAPIKey&oobCode=testoobcode&mode=signIn";
+
+/** @var kFakeEmailSignInlink
+ @brief Fake email sign-in link
+ */
+static NSString *const kFakeEmailSignInlink = @"https://test.app.goo.gl/?link=https://test.firebase"
+ "app.com/__/auth/action?apiKey%3DtestAPIKey%26mode%3DsignIn%26oobCode%3Dtestoobcode%26continueU"
+ "rl%3Dhttps://test.apps.com&ibi=com.test.com&ifl=https://test.firebaseapp.com/__/auth/action?ap"
+ "iKey%3DtestAPIKey%26mode%3DsignIn%26oobCode%3Dtestoobcode%26continueUrl%3Dhttps://test.apps.co"
+ "m";
+
/** @var kExpectationTimeout
@brief The maximum time waiting for expectations to fulfill.
*/
-static const NSTimeInterval kExpectationTimeout = 1;
+static const NSTimeInterval kExpectationTimeout = 2;
/** @var kWaitInterval
@brief The time waiting for background tasks to finish before continue when necessary.
@@ -360,6 +402,39 @@ static const NSTimeInterval kWaitInterval = .5;
OCMVerifyAll(_mockBackend);
}
+/** @fn testFetchSignInMethodsForEmailSuccess
+ @brief Tests the flow of a successful @c fetchSignInMethodsForEmail:completion: call.
+ */
+- (void)testFetchSignInMethodsForEmailSuccess {
+ NSArray<NSString *> *allSignInMethods =
+ @[ kFIREmailLinkAuthSignInMethod, kFIRFacebookAuthSignInMethod ];
+ OCMExpect([_mockBackend createAuthURI:[OCMArg any]
+ callback:[OCMArg any]])
+ .andCallBlock2(^(FIRCreateAuthURIRequest *_Nullable request,
+ FIRCreateAuthURIResponseCallback callback) {
+ XCTAssertEqualObjects(request.identifier, kEmail);
+ XCTAssertNotNil(request.endpoint);
+ XCTAssertEqualObjects(request.APIKey, kAPIKey);
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ id mockCreateAuthURIResponse = OCMClassMock([FIRCreateAuthURIResponse class]);
+ OCMStub([mockCreateAuthURIResponse signinMethods]).andReturn(allSignInMethods);
+ callback(mockCreateAuthURIResponse, nil);
+ });
+ });
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] fetchSignInMethodsForEmail:kEmail
+ completion:^(NSArray<NSString *> *_Nullable signInMethods,
+ NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertEqualObjects(signInMethods, allSignInMethods);
+ XCTAssertTrue([allSignInMethods isKindOfClass:[NSArray class]]);
+ XCTAssertNil(error);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+}
+
/** @fn testFetchProvidersForEmailSuccessDeprecatedProviderID
@brief Tests the flow of a successful @c fetchProvidersForEmail:completion: call using the
deprecated FIREmailPasswordAuthProviderID.
@@ -416,6 +491,25 @@ static const NSTimeInterval kWaitInterval = .5;
OCMVerifyAll(_mockBackend);
}
+/** @fn testFetchSignInMethodsForEmailFailure
+ @brief Tests the flow of a failed @c fetchSignInMethodsForEmail:completion: call.
+ */
+- (void)testFetchSignInMethodsForEmailFailure {
+ OCMExpect([_mockBackend createAuthURI:[OCMArg any] callback:[OCMArg any]])
+ .andDispatchError2([FIRAuthErrorUtils tooManyRequestsErrorWithMessage:nil]);
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] fetchSignInMethodsForEmail:kEmail
+ completion:^(NSArray<NSString *> *_Nullable signInMethods,
+ NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertNil(signInMethods);
+ XCTAssertEqual(error.code, FIRAuthErrorCodeTooManyRequests);
+ XCTAssertNotNil(error.userInfo[NSLocalizedDescriptionKey]);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+}
#if TARGET_OS_IOS
/** @fn testPhoneAuthSuccess
@brief Tests the flow of a successful @c signInWithCredential:completion for phone auth.
@@ -430,6 +524,8 @@ static const NSTimeInterval kWaitInterval = .5;
dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
id mockVerifyPhoneResponse = OCMClassMock([FIRVerifyPhoneNumberResponse class]);
[self stubTokensWithMockResponse:mockVerifyPhoneResponse];
+ // Stub isNewUser flag in the response.
+ OCMStub([mockVerifyPhoneResponse isNewUser]).andReturn(YES);
callback(mockVerifyPhoneResponse, nil);
});
});
@@ -440,10 +536,12 @@ static const NSTimeInterval kWaitInterval = .5;
[[FIRPhoneAuthProvider provider] credentialWithVerificationID:kVerificationID
verificationCode:kVerificationCode];
- [[FIRAuth auth] signInWithCredential:credential completion:^(FIRUser *_Nullable user,
- NSError *_Nullable error) {
+ [[FIRAuth auth] signInAndRetrieveDataWithCredential:credential
+ completion:^(FIRAuthDataResult *_Nullable authDataResult,
+ NSError *_Nullable error) {
XCTAssertTrue([NSThread isMainThread]);
- [self assertUser:user];
+ [self assertUser:authDataResult.user];
+ XCTAssertTrue(authDataResult.additionalUserInfo.isNewUser);
XCTAssertNil(error);
[expectation fulfill];
}];
@@ -497,6 +595,101 @@ static const NSTimeInterval kWaitInterval = .5;
}
#endif
+/** @fn testSignInWithEmailLinkSuccess
+ @brief Tests the flow of a successful @c signInWithEmail:link:completion: call.
+ */
+- (void)testSignInWithEmailLinkSuccess {
+ NSString *fakeCode = @"testoobcode";
+ OCMExpect([_mockBackend emailLinkSignin:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIREmailLinkSignInRequest *_Nullable request,
+ FIREmailLinkSigninResponseCallback callback) {
+ XCTAssertEqualObjects(request.email, kEmail);
+ XCTAssertEqualObjects(request.oobCode, fakeCode);
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ id mockEmailLinkSignInResponse = OCMClassMock([FIREmailLinkSignInResponse class]);
+ [self stubTokensWithMockResponse:mockEmailLinkSignInResponse];
+ callback(mockEmailLinkSignInResponse, nil);
+ OCMStub([mockEmailLinkSignInResponse refreshToken]).andReturn(kRefreshToken);
+ });
+ });
+ [self expectGetAccountInfo];
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] signOut:NULL];
+ [[FIRAuth auth] signInWithEmail:kEmail
+ link:kFakeEmailSignInlink
+ completion:^(FIRAuthDataResult *_Nullable authResult,
+ NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertNotNil(authResult.user);
+ XCTAssertEqualObjects(authResult.user.refreshToken, kRefreshToken);
+ XCTAssertFalse(authResult.user.anonymous);
+ XCTAssertEqualObjects(authResult.user.email, kEmail);
+ XCTAssertNil(error);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ [self assertUser:[FIRAuth auth].currentUser];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testSignInWithEmailLinkSuccessDeeplink
+ @brief Tests the flow of a successful @c signInWithEmail:link:completion: call using a deep
+ link.
+ */
+- (void)testSignInWithEmailLinkSuccessDeeplink {
+ NSString *fakeCode = @"testoobcode";
+ OCMExpect([_mockBackend emailLinkSignin:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIREmailLinkSignInRequest *_Nullable request,
+ FIREmailLinkSigninResponseCallback callback) {
+ XCTAssertEqualObjects(request.email, kEmail);
+ XCTAssertEqualObjects(request.oobCode, fakeCode);
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ id mockEmailLinkSignInResponse = OCMClassMock([FIREmailLinkSignInResponse class]);
+ [self stubTokensWithMockResponse:mockEmailLinkSignInResponse];
+ callback(mockEmailLinkSignInResponse, nil);
+ OCMStub([mockEmailLinkSignInResponse refreshToken]).andReturn(kRefreshToken);
+ });
+ });
+ [self expectGetAccountInfo];
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] signOut:NULL];
+ [[FIRAuth auth] signInWithEmail:kEmail
+ link:kFakeEmailSignInDeeplink
+ completion:^(FIRAuthDataResult *_Nullable authResult,
+ NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertNotNil(authResult.user);
+ XCTAssertEqualObjects(authResult.user.refreshToken, kRefreshToken);
+ XCTAssertFalse(authResult.user.anonymous);
+ XCTAssertEqualObjects(authResult.user.email, kEmail);
+ XCTAssertNil(error);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ [self assertUser:[FIRAuth auth].currentUser];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testSignInWithEmailLinkFailure
+ @brief Tests the flow of a failed @c signInWithEmail:link:completion: call.
+ */
+- (void)testSignInWithEmailLinkFailure {
+ OCMExpect([_mockBackend emailLinkSignin:[OCMArg any] callback:[OCMArg any]])
+ ._andDispatchError2([FIRAuthErrorUtils invalidActionCodeErrorWithMessage:nil]);
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] signOut:NULL];
+ [[FIRAuth auth] signInWithEmail:kEmail
+ link:kFakeEmailSignInlink
+ completion:^(FIRAuthDataResult *_Nullable authResult,
+ NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertEqual(error.code, FIRAuthErrorCodeInvalidActionCode);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+}
+
/** @fn testSignInWithEmailPasswordSuccess
@brief Tests the flow of a successful @c signInWithEmail:password:completion: call.
*/
@@ -517,8 +710,10 @@ static const NSTimeInterval kWaitInterval = .5;
[self expectGetAccountInfo];
XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
[[FIRAuth auth] signOut:NULL];
- [[FIRAuth auth] signInWithEmail:kEmail password:kFakePassword completion:^(FIRUser *_Nullable user,
- NSError *_Nullable error) {
+ [[FIRAuth auth] signInWithEmail:kEmail
+ password:kFakePassword
+ completion:^(FIRUser *_Nullable user,
+ NSError *_Nullable error) {
XCTAssertTrue([NSThread isMainThread]);
[self assertUser:user];
XCTAssertNil(error);
@@ -537,8 +732,10 @@ static const NSTimeInterval kWaitInterval = .5;
.andDispatchError2([FIRAuthErrorUtils wrongPasswordErrorWithMessage:nil]);
XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
[[FIRAuth auth] signOut:NULL];
- [[FIRAuth auth] signInWithEmail:kEmail password:kFakePassword completion:^(FIRUser *_Nullable user,
- NSError *_Nullable error) {
+ [[FIRAuth auth] signInWithEmail:kEmail
+ password:kFakePassword
+ completion:^(FIRUser *_Nullable user,
+ NSError *_Nullable error) {
XCTAssertTrue([NSThread isMainThread]);
XCTAssertNil(user);
XCTAssertEqual(error.code, FIRAuthErrorCodeWrongPassword);
@@ -825,6 +1022,68 @@ static const NSTimeInterval kWaitInterval = .5;
OCMVerifyAll(_mockBackend);
}
+/** @fn testSignInWithEmailLinkCredentialSuccess
+ @brief Tests the flow of a successfully @c signInWithCredential:completion: call with an
+ email sign-in link credential using FIREmailAuthProvider.
+ */
+- (void)testSignInWithEmailLinkCredentialSuccess {
+ NSString *fakeCode = @"testoobcode";
+ OCMExpect([_mockBackend emailLinkSignin:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIREmailLinkSignInRequest *_Nullable request,
+ FIREmailLinkSigninResponseCallback callback) {
+ XCTAssertEqualObjects(request.email, kEmail);
+ XCTAssertEqualObjects(request.oobCode, fakeCode);
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ id mockEmailLinkSigninResponse = OCMClassMock([FIREmailLinkSignInResponse class]);
+ [self stubTokensWithMockResponse:mockEmailLinkSigninResponse];
+ callback(mockEmailLinkSigninResponse, nil);
+ });
+ });
+ [self expectGetAccountInfo];
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] signOut:NULL];
+ FIRAuthCredential *emailCredential =
+ [FIREmailAuthProvider credentialWithEmail:kEmail link:kFakeEmailSignInlink];
+ [[FIRAuth auth] signInAndRetrieveDataWithCredential:emailCredential
+ completion:^(FIRAuthDataResult *_Nullable authResult,
+ NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertNotNil(authResult.user);
+ XCTAssertEqualObjects(authResult.user.refreshToken, kRefreshToken);
+ XCTAssertFalse(authResult.user.anonymous);
+ XCTAssertEqualObjects(authResult.user.email, kEmail);
+ XCTAssertNil(error);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ [self assertUser:[FIRAuth auth].currentUser];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testSignInWithEmailLinkCredentialFailure
+ @brief Tests the flow of a failed @c signInWithCredential:completion: call with an
+ email-email sign-in link credential using FIREmailAuthProvider.
+ */
+- (void)testSignInWithEmailLinkCredentialFailure {
+ OCMExpect([_mockBackend emailLinkSignin:[OCMArg any] callback:[OCMArg any]])
+ .andDispatchError2([FIRAuthErrorUtils userDisabledErrorWithMessage:nil]);
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] signOut:NULL];
+ FIRAuthCredential *emailCredential =
+ [FIREmailAuthProvider credentialWithEmail:kEmail link:kFakeEmailSignInlink];
+ [[FIRAuth auth] signInWithCredential:emailCredential completion:^(FIRUser *_Nullable user,
+ NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertNil(user);
+ XCTAssertEqual(error.code, FIRAuthErrorCodeUserDisabled);
+ XCTAssertNotNil(error.userInfo[NSLocalizedDescriptionKey]);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ XCTAssertNil([FIRAuth auth].currentUser);
+ OCMVerifyAll(_mockBackend);
+}
+
/** @fn testSignInWithEmailCredentialSuccess
@brief Tests the flow of a successfully @c signInWithCredential:completion: call with an
email-password credential.
@@ -1500,6 +1759,170 @@ static const NSTimeInterval kWaitInterval = .5;
OCMVerifyAll(_mockBackend);
}
+/** @fn testSendSignInLinkToEmailSuccess
+ @brief Tests the flow of a successful @c sendSignInLinkToEmail:actionCodeSettings:completion:
+ call.
+ */
+- (void)testSendSignInLinkToEmailSuccess {
+ OCMExpect([_mockBackend getOOBConfirmationCode:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRGetOOBConfirmationCodeRequest *_Nullable request,
+ FIRGetOOBConfirmationCodeResponseCallback callback) {
+ XCTAssertEqualObjects(request.APIKey, kAPIKey);
+ XCTAssertEqualObjects(request.email, kEmail);
+ XCTAssertEqualObjects(request.continueURL, kContinueURL);
+ XCTAssertTrue(request.handleCodeInApp);
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ callback([[FIRGetOOBConfirmationCodeResponse alloc] init], nil);
+ });
+ });
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] sendSignInLinkToEmail:kEmail
+ actionCodeSettings:[self fakeActionCodeSettings]
+ completion:^(NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertNil(error);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testSendSignInLinkToEmailFailure
+ @brief Tests the flow of a failed @c sendSignInLinkToEmail:actionCodeSettings:completion:
+ call.
+ */
+- (void)testSendSignInLinkToEmailFailure {
+ OCMExpect([_mockBackend getOOBConfirmationCode:[OCMArg any] callback:[OCMArg any]])
+ .andDispatchError2([FIRAuthErrorUtils appNotAuthorizedError]);
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] sendSignInLinkToEmail:kEmail
+ actionCodeSettings:[self fakeActionCodeSettings]
+ completion:^(NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertEqual(error.code, FIRAuthErrorCodeAppNotAuthorized);
+ XCTAssertNotNil(error.userInfo[NSLocalizedDescriptionKey]);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn fakeActionCodeSettings
+ @brief Constructs and returns a fake instance of @c FIRActionCodeSettings for testing.
+ @return An instance of @c FIRActionCodeSettings for testing.
+ */
+- (FIRActionCodeSettings *)fakeActionCodeSettings {
+ FIRActionCodeSettings *actionCodeSettings = [[FIRActionCodeSettings alloc]init];
+ actionCodeSettings.URL = [NSURL URLWithString:kContinueURL];
+ actionCodeSettings.handleCodeInApp = YES;
+ return actionCodeSettings;
+}
+
+/** @fn testUpdateCurrentUserFailure
+ @brief Tests the flow of a failed @c updateCurrentUser:completion:
+ call.
+ */
+- (void)testUpdateCurrentUserFailure {
+ NSString *kTestAccessToken = @"fakeAccessToken";
+ NSString *kTestAPIKey = @"fakeAPIKey";
+ [self waitForSignInWithAccessToken:kTestAccessToken
+ APIKey:kTestAPIKey
+ completion:nil];
+ NSString *kTestAPIKey2 = @"fakeAPIKey2";
+ FIRUser *user2 = [FIRAuth auth].currentUser;
+ user2.requestConfiguration = [[FIRAuthRequestConfiguration alloc]initWithAPIKey:kTestAPIKey2];
+ OCMExpect([_mockBackend getAccountInfo:[OCMArg any] callback:[OCMArg any]])
+ .andDispatchError2([FIRAuthErrorUtils invalidAPIKeyError]);
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] updateCurrentUser:user2 completion:^(NSError *_Nullable error) {
+ XCTAssertEqual(error.code, FIRAuthErrorCodeInvalidAPIKey);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testUpdateCurrentUserFailureNetworkError
+ @brief Tests the flow of a failed @c updateCurrentUser:completion:
+ call with a network error.
+ */
+- (void)testUpdateCurrentUserFailureNetworkError {
+ NSString *kTestAPIKey = @"fakeAPIKey";
+ NSString *kTestAccessToken = @"fakeAccessToken";
+ [self waitForSignInWithAccessToken:kTestAccessToken
+ APIKey:kTestAPIKey
+ completion:nil];
+ NSString *kTestAPIKey2 = @"fakeAPIKey2";
+ FIRUser *user2 = [FIRAuth auth].currentUser;
+ user2.requestConfiguration = [[FIRAuthRequestConfiguration alloc]initWithAPIKey:kTestAPIKey2];
+ OCMExpect([_mockBackend getAccountInfo:[OCMArg any] callback:[OCMArg any]])
+ .andDispatchError2([FIRAuthErrorUtils networkErrorWithUnderlyingError:[NSError new]]);
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] updateCurrentUser:user2 completion:^(NSError *_Nullable error) {
+ XCTAssertEqual(error.code, FIRAuthErrorCodeNetworkError);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testUpdateCurrentUserFailureNUllUser
+ @brief Tests the flow of a failed @c updateCurrentUser:completion:
+ call with FIRAuthErrorCodeNullUser.
+ */
+- (void)testUpdateCurrentUserFailureNUllUser {
+ NSString *kTestAccessToken = @"fakeAccessToken";
+ NSString *kTestAPIKey = @"fakeAPIKey";
+ [self waitForSignInWithAccessToken:kTestAccessToken
+ APIKey:kTestAPIKey
+ completion:nil];
+ FIRUser *fakeNilUser = nil;
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] updateCurrentUser:fakeNilUser completion:^(NSError *_Nullable error) {
+ XCTAssertEqual(error.code, FIRAuthErrorCodeNullUser);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testUpdateCurrentUserSuccess
+ @brief Tests the flow of a successful @c updateCurrentUser:completion:
+ call with a network error.
+ */
+- (void)testUpdateCurrentUserSuccess {
+ // Sign in with the first user.
+ [self waitForSignInWithAccessToken:kAccessToken
+ APIKey:kAPIKey
+ completion:nil];
+
+ FIRUser *user1 = [FIRAuth auth].currentUser;
+ NSString *kTestAPIKey = @"fakeAPIKey";
+ user1.requestConfiguration = [[FIRAuthRequestConfiguration alloc]initWithAPIKey:kTestAPIKey];
+ [[FIRAuth auth] signOut:nil];
+
+ NSString *kTestAccessToken2 = @"fakeAccessToken2";
+ [self waitForSignInWithAccessToken:kTestAccessToken2
+ APIKey:kAPIKey
+ completion:nil];
+ FIRUser *user2 = [FIRAuth auth].currentUser;
+
+ [self expectGetAccountInfoWithAccessToken:kAccessToken];
+
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ // Current user should now be user2.
+ XCTAssertEqualObjects([FIRAuth auth].currentUser, user2);
+ [[FIRAuth auth] updateCurrentUser:user1 completion:^(NSError *_Nullable error) {
+ XCTAssertNil(error);
+ // Current user should now be user1.
+ XCTAssertEqualObjects([FIRAuth auth].currentUser, user1);
+ XCTAssertNotEqualObjects([FIRAuth auth].currentUser, user2);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+}
+
/** @fn testSignOut
@brief Tests the @c signOut: method.
*/
@@ -1511,6 +1934,16 @@ static const NSTimeInterval kWaitInterval = .5;
XCTAssertNil([FIRAuth auth].currentUser);
}
+/** @fn testIsSignInWithEmailLink
+ @brief Tests the @c isSignInWithEmailLink: method.
+*/
+- (void)testIsSignInWithEmailLink {
+ XCTAssertTrue([[FIRAuth auth] isSignInWithEmailLink:kFakeEmailSignInlink]);
+ XCTAssertTrue([[FIRAuth auth] isSignInWithEmailLink:kFakeEmailSignInDeeplink]);
+ XCTAssertFalse([[FIRAuth auth] isSignInWithEmailLink:kBadSignInEmailLink]);
+ XCTAssertFalse([[FIRAuth auth] isSignInWithEmailLink:@""]);
+}
+
/** @fn testAuthStateChanges
@brief Tests @c addAuthStateDidChangeListener: and @c removeAuthStateDidChangeListener: methods.
*/
@@ -1602,7 +2035,7 @@ static const NSTimeInterval kWaitInterval = .5;
// Listener should fire for signing in again as the same user with another access token.
expectation = [self expectationWithDescription:@"sign-in again"];
shouldHaveUser = YES;
- [self waitForSignInWithAccessToken:kNewAccessToken];
+ [self waitForSignInWithAccessToken:kNewAccessToken APIKey:nil completion:nil];
// Listener should fire for signing out.
expectation = [self expectationWithDescription:@"sign-out"];
@@ -1974,15 +2407,19 @@ static const NSTimeInterval kWaitInterval = .5;
@remarks This method also waits for all other pending @c XCTestExpectation instances.
*/
- (void)waitForSignIn {
- [self waitForSignInWithAccessToken:kAccessToken];
+ [self waitForSignInWithAccessToken:kAccessToken APIKey:nil completion:nil];
}
/** @fn waitForSignInWithAccessToken:
@brief Signs in a user to prepare for tests.
@param accessToken The access token for the user to have.
+ @param APIKey Optionally, The API key associated with the user.
+ @param completion Optionally, The completion invoked at the end of the flow.
@remarks This method also waits for all other pending @c XCTestExpectation instances.
*/
-- (void)waitForSignInWithAccessToken:(NSString *)accessToken {
+- (void)waitForSignInWithAccessToken:(NSString *)accessToken
+ APIKey:(nullable NSString *)APIKey
+ completion:(nullable FIRAuthResultCallback)completion {
OCMExpect([_mockBackend verifyPassword:[OCMArg any] callback:[OCMArg any]])
.andCallBlock2(^(FIRVerifyPasswordRequest *_Nullable request,
FIRVerifyPasswordResponseCallback callback) {
@@ -1999,7 +2436,12 @@ static const NSTimeInterval kWaitInterval = .5;
XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
[[FIRAuth auth] signInWithEmail:kEmail password:kFakePassword completion:^(FIRUser *_Nullable user,
NSError *_Nullable error) {
+
+ user.requestConfiguration = [[FIRAuthRequestConfiguration alloc] initWithAPIKey:APIKey];
[expectation fulfill];
+ if (completion) {
+ completion(user, error);
+ }
}];
[self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
OCMVerifyAll(_mockBackend);
diff --git a/Example/Auth/Tests/FIRAuthURLPresenterTests.m b/Example/Auth/Tests/FIRAuthURLPresenterTests.m
index fcc64e9..a4e6c53 100644
--- a/Example/Auth/Tests/FIRAuthURLPresenterTests.m
+++ b/Example/Auth/Tests/FIRAuthURLPresenterTests.m
@@ -26,7 +26,7 @@
/** @var kExpectationTimeout
@brief The maximum time waiting for expectations to fulfill.
*/
-static NSTimeInterval kExpectationTimeout = 1;
+static NSTimeInterval kExpectationTimeout = 2;
@interface FIRAuthDefaultUIDelegate : NSObject <FIRAuthUIDelegate>
/** @fn defaultUIDelegate
diff --git a/Example/Auth/Tests/FIREmailLinkRequestTests.m b/Example/Auth/Tests/FIREmailLinkRequestTests.m
new file mode 100644
index 0000000..90d7c18
--- /dev/null
+++ b/Example/Auth/Tests/FIREmailLinkRequestTests.m
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2017 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 <XCTest/XCTest.h>
+
+#import "FIRAuthErrors.h"
+#import "FIRAuthBackend.h"
+#import "FIREmailLinkSignInRequest.h"
+#import "FIREmailLinkSignInResponse.h"
+#import "FIRFakeBackendRPCIssuer.h"
+
+/** @var kTestAPIKey
+ @brief Fake API key used for testing.
+ */
+static NSString *const kTestAPIKey = @"APIKey";
+
+/** @var kTestEmail
+ @brief The key for the "email" value in the request.
+ */
+static NSString *const kTestEmail = @"TestEmail@email.com";
+
+/** @var kTestOOBCode
+ @brief The test value for the "oobCode" in the request.
+ */
+static NSString *const kTestOOBCode = @"TestOOBCode";
+
+/** @var kTestIDToken
+ @brief The test value for "idToken" in the request.
+ */
+static NSString *const kTestIDToken = @"testIDToken";
+
+/** @var kEmailKey
+ @brief The key for the "identifier" value in the request.
+ */
+static NSString *const kEmailKey = @"email";
+
+/** @var kEmailLinkKey
+ @brief The key for the "oobCode" value in the request.
+ */
+static NSString *const kOOBCodeKey = @"oobCode";
+
+/** @var kIDTokenKey
+ @brief The key for the "IDToken" value in the request.
+ */
+static NSString *const kIDTokenKey = @"idToken";
+
+/** @var kExpectedAPIURL
+ @brief The value of the expected URL (including the backend endpoint) in the request.
+ */
+static NSString *const kExpectedAPIURL =
+ @"https://www.googleapis.com/identitytoolkit/v3/relyingparty/emailLinkSignin?key=APIKey";
+
+/** @class FIREmailLinkRequestTests
+ @brief Tests for @c FIREmailLinkRequests.
+ */
+@interface FIREmailLinkRequestTests : XCTestCase
+@end
+
+@implementation FIREmailLinkRequestTests {
+ /** @var _RPCIssuer
+ @brief This backend RPC issuer is used to fake network responses for each test in the suite.
+ In the @c setUp method we initialize this and set @c FIRAuthBackend's RPC issuer to it.
+ */
+ FIRFakeBackendRPCIssuer *_RPCIssuer;
+
+ /** @var _requestConfiguration
+ @brief This is the request configuration used for testing.
+ */
+ FIRAuthRequestConfiguration *_requestConfiguration;
+}
+
+- (void)setUp {
+ [super setUp];
+ FIRFakeBackendRPCIssuer *RPCIssuer = [[FIRFakeBackendRPCIssuer alloc] init];
+ [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:RPCIssuer];
+ _RPCIssuer = RPCIssuer;
+ _requestConfiguration = [[FIRAuthRequestConfiguration alloc] initWithAPIKey:kTestAPIKey];
+}
+
+- (void)tearDown {
+ _RPCIssuer = nil;
+ _requestConfiguration = nil;
+ [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:nil];
+ [super tearDown];
+}
+
+/** @fn testEmailLinkRequestCreation
+ @brief Tests the email link sign-in request with mandatory parameters.
+ */
+- (void)testEmailLinkRequest {
+ FIREmailLinkSignInRequest *request =
+ [[FIREmailLinkSignInRequest alloc] initWithEmail:kTestEmail
+ oobCode:kTestOOBCode
+ requestConfiguration:_requestConfiguration];
+ [FIRAuthBackend emailLinkSignin:request callback:^(FIREmailLinkSignInResponse *_Nullable response,
+ NSError *_Nullable error) {
+ }];
+ XCTAssertEqualObjects(_RPCIssuer.requestURL.absoluteString, kExpectedAPIURL);
+ XCTAssertNotNil(_RPCIssuer.decodedRequest);
+ XCTAssertEqualObjects(_RPCIssuer.decodedRequest[kEmailKey], kTestEmail);
+ XCTAssertEqualObjects(_RPCIssuer.decodedRequest[kOOBCodeKey], kTestOOBCode);
+ XCTAssertNil(_RPCIssuer.decodedRequest[kIDTokenKey]);
+}
+
+/** @fn testEmailLinkRequestCreationOptional
+ @brief Tests the email link sign-in request with mandatory parameters and optional ID token.
+ */
+- (void)testEmailLinkRequestCreationOptional {
+ FIREmailLinkSignInRequest *request =
+ [[FIREmailLinkSignInRequest alloc] initWithEmail:kTestEmail
+ oobCode:kTestOOBCode
+ requestConfiguration:_requestConfiguration];
+ request.IDToken = kTestIDToken;
+ [FIRAuthBackend emailLinkSignin:request callback:^(FIREmailLinkSignInResponse *_Nullable response,
+ NSError *_Nullable error) {
+ }];
+ XCTAssertEqualObjects(_RPCIssuer.requestURL.absoluteString, kExpectedAPIURL);
+ XCTAssertNotNil(_RPCIssuer.decodedRequest);
+ XCTAssertEqualObjects(_RPCIssuer.decodedRequest[kEmailKey], kTestEmail);
+ XCTAssertEqualObjects(_RPCIssuer.decodedRequest[kOOBCodeKey], kTestOOBCode);
+ XCTAssertEqualObjects(_RPCIssuer.decodedRequest[kIDTokenKey], kTestIDToken);
+}
+
+@end
diff --git a/Example/Auth/Tests/FIREmailLinkSignInResponseTests.m b/Example/Auth/Tests/FIREmailLinkSignInResponseTests.m
new file mode 100644
index 0000000..cc2c544
--- /dev/null
+++ b/Example/Auth/Tests/FIREmailLinkSignInResponseTests.m
@@ -0,0 +1,195 @@
+/*
+ * Copyright 2017 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 <XCTest/XCTest.h>
+
+#import "FIRAuthErrors.h"
+#import "FIRAuthErrorUtils.h"
+#import "FIRAuthBackend.h"
+#import "FIREmailLinkSignInRequest.h"
+#import "FIREmailLinkSignInResponse.h"
+#import "FIRFakeBackendRPCIssuer.h"
+
+/** @var kTestAPIKey
+ @brief Fake API key used for testing.
+ */
+static NSString *const kTestAPIKey = @"APIKey";
+
+/** @var kTestEmail
+ @brief The key for the "email" value in the request.
+ */
+static NSString *const kTestEmail = @"TestEmail@email.com";
+
+/** @var kTestOOBCode
+ @brief The test value for the "oobCode" in the request.
+ */
+static NSString *const kTestOOBCode = @"TestOOBCode";
+
+/** @var kTestIDToken
+ @brief The test value for "idToken" in the request.
+ */
+static NSString *const kTestIDToken = @"testIDToken";
+
+/** @var kEmailKey
+ @brief The key for the "identifier" value in the request.
+ */
+static NSString *const kEmailKey = @"email";
+
+/** @var kEmailLinkKey
+ @brief The key for the "emailLink" value in the request.
+ */
+static NSString *const kOOBCodeKey = @"oobCode";
+
+/** @var kIDTokenKey
+ @brief The key for the "IDToken" value in the request.
+ */
+static NSString *const kIDTokenKey = @"idToken";
+
+/** @var kTestIDTokenResponse
+ @brief A fake ID Token in the server response.
+ */
+static NSString *const kTestIDTokenResponse = @"fakeToken";
+
+/** @var kTestEmailResponse
+ @brief A fake email in the server response.
+ */
+static NSString *const kTestEmailResponse = @"fake email";
+
+/** @var kTestRefreshToken
+ @brief A fake refresh token in the server response.
+ */
+static NSString *const kTestRefreshToken = @"testRefreshToken";
+
+/** @var kInvalidEmailErrorMessage
+ @brief The error returned by the server if the email is invalid.
+ */
+static NSString *const kInvalidEmailErrorMessage = @"INVALID_EMAIL";
+
+/** @var kTestTokenExpirationTimeInterval
+ @brief The fake time interval that it takes a token to expire.
+ */
+static const NSTimeInterval kTestTokenExpirationTimeInterval = 55 * 60;
+
+/** @var kMaxDifferenceBetweenDates
+ @brief The maximum difference between time two dates (in seconds), after which they will be
+ considered different.
+ */
+static const NSTimeInterval kMaxDifferenceBetweenDates = 0.0001;
+
+/** @var kFakeIsNewUSerFlag
+ @brief The fake fake isNewUser flag in the response.
+ */
+static const BOOL kFakeIsNewUSerFlag = YES;
+
+/** @class FIREmailLinkRequestTests
+ @brief Tests for @c FIREmailLinkRequests.
+ */
+@interface FIREmailLinkSignInResponseTests : XCTestCase
+@end
+
+@implementation FIREmailLinkSignInResponseTests {
+ /** @var _RPCIssuer
+ @brief This backend RPC issuer is used to fake network responses for each test in the suite.
+ In the @c setUp method we initialize this and set @c FIRAuthBackend's RPC issuer to it.
+ */
+ FIRFakeBackendRPCIssuer *_RPCIssuer;
+
+ /** @var _requestConfiguration
+ @brief This is the request configuration used for testing.
+ */
+ FIRAuthRequestConfiguration *_requestConfiguration;
+}
+
+- (void)setUp {
+ [super setUp];
+ FIRFakeBackendRPCIssuer *RPCIssuer = [[FIRFakeBackendRPCIssuer alloc] init];
+ [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:RPCIssuer];
+ _RPCIssuer = RPCIssuer;
+ _requestConfiguration = [[FIRAuthRequestConfiguration alloc] initWithAPIKey:kTestAPIKey];
+}
+
+/** @fn testFailedEmailLinkSignInResponse
+ @brief Tests a failed email link sign-in response.
+ */
+- (void)testFailedEmailLinkSignInResponse {
+ FIREmailLinkSignInRequest *request =
+ [[FIREmailLinkSignInRequest alloc] initWithEmail:kTestEmail
+ oobCode:kTestOOBCode
+ requestConfiguration:_requestConfiguration];
+
+ __block BOOL callbackInvoked = NO;
+ __block FIREmailLinkSignInResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend emailLinkSignin:request
+ callback:^(FIREmailLinkSignInResponse *_Nullable response,
+ NSError *_Nullable error) {
+ callbackInvoked = YES;
+ RPCResponse = response;
+ RPCError = error;
+ }];
+
+ [_RPCIssuer respondWithServerErrorMessage:kInvalidEmailErrorMessage];
+
+ XCTAssert(callbackInvoked);
+ XCTAssertNil(RPCResponse);
+ XCTAssertEqual(RPCError.code, FIRAuthErrorCodeInvalidEmail);
+}
+
+/** @fn testSuccessfulEmailLinkSignInResponse
+ @brief Tests a succesful email link sign-in response.
+ */
+- (void)testSuccessfulEmailLinkSignInResponse {
+ FIREmailLinkSignInRequest *request =
+ [[FIREmailLinkSignInRequest alloc] initWithEmail:kTestEmail
+ oobCode:kTestOOBCode
+ requestConfiguration:_requestConfiguration];
+
+ __block BOOL callbackInvoked = NO;
+ __block FIREmailLinkSignInResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend emailLinkSignin:request
+ callback:^(FIREmailLinkSignInResponse *_Nullable response,
+ NSError *_Nullable error) {
+ callbackInvoked = YES;
+ RPCResponse = response;
+ RPCError = error;
+ }];
+
+ [_RPCIssuer respondWithJSON:@{
+ @"idToken" : kTestIDTokenResponse,
+ @"email" : kTestEmailResponse,
+ @"isNewUser" : kFakeIsNewUSerFlag ? @YES : @NO,
+ @"expiresIn" : [NSString stringWithFormat:@"%f",kTestTokenExpirationTimeInterval],
+ @"refreshToken" : kTestRefreshToken,
+ }];
+
+ XCTAssert(callbackInvoked);
+ XCTAssertNil(RPCError);
+ XCTAssertNotNil(RPCResponse);
+ XCTAssertEqualObjects(RPCResponse.IDToken, kTestIDTokenResponse);
+ XCTAssertEqualObjects(RPCResponse.email, kTestEmailResponse);
+ XCTAssertEqualObjects(RPCResponse.refreshToken, kTestRefreshToken);
+ XCTAssertTrue(RPCResponse.isNewUser);
+ NSTimeInterval expirationTimeInterval =
+ [RPCResponse.approximateExpirationDate timeIntervalSinceNow];
+ NSTimeInterval testTimeInterval =
+ [[NSDate dateWithTimeIntervalSinceNow:kTestTokenExpirationTimeInterval] timeIntervalSinceNow];
+ NSTimeInterval timeIntervalDifference =
+ fabs(expirationTimeInterval - testTimeInterval);
+ XCTAssert(timeIntervalDifference < kMaxDifferenceBetweenDates);
+}
+
+@end
diff --git a/Example/Auth/Tests/FIRGetOOBConfirmationCodeRequestTests.m b/Example/Auth/Tests/FIRGetOOBConfirmationCodeRequestTests.m
index 965af8a..b11c759 100644
--- a/Example/Auth/Tests/FIRGetOOBConfirmationCodeRequestTests.m
+++ b/Example/Auth/Tests/FIRGetOOBConfirmationCodeRequestTests.m
@@ -49,6 +49,11 @@ static NSString *const kPasswordResetRequestTypeValue = @"PASSWORD_RESET";
*/
static NSString *const kVerifyEmailRequestTypeValue = @"VERIFY_EMAIL";
+/** @var kEmailLinkSignInTypeValue
+ @brief The value for the "EMAIL_SIGNIN" request type.
+ */
+static NSString *const kEmailLinkSignInTypeValue = @"EMAIL_SIGNIN";
+
/** @var kEmailKey
@brief The name of the "email" property in the request.
*/
@@ -124,6 +129,7 @@ static NSString *const kCanHandleCodeInAppKey = @"canHandleCodeInApp";
/** @class FIRGetOOBConfirmationCodeRequestTests
@brief Tests for @c FIRGetOOBConfirmationCodeRequest.
*/
+
@interface FIRGetOOBConfirmationCodeRequestTests : XCTestCase
@end
@implementation FIRGetOOBConfirmationCodeRequestTests {
@@ -190,6 +196,43 @@ static NSString *const kCanHandleCodeInAppKey = @"canHandleCodeInApp";
[NSNumber numberWithBool:YES]);
}
+/** @fn testSignInWithEmailLinkRequest
+ @brief Tests the encoding of a email sign-in link request.
+ */
+- (void)testSignInWithEmailLinkRequest {
+ FIRGetOOBConfirmationCodeRequest *request =
+ [FIRGetOOBConfirmationCodeRequest signInWithEmailLinkRequest:kTestEmail
+ actionCodeSettings:[self fakeActionCodeSettings]
+ requestConfiguration:_requestConfiguration];
+
+ __block BOOL callbackInvoked;
+ __block FIRGetOOBConfirmationCodeResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend getOOBConfirmationCode:request
+ callback:^(FIRGetOOBConfirmationCodeResponse *_Nullable response,
+ NSError *_Nullable error) {
+ callbackInvoked = YES;
+ RPCResponse = response;
+ RPCError = error;
+ }];
+
+ XCTAssertEqualObjects(_RPCIssuer.requestURL.absoluteString, kExpectedAPIURL);
+ XCTAssertNotNil(_RPCIssuer.decodedRequest);
+ XCTAssert([_RPCIssuer.decodedRequest isKindOfClass:[NSDictionary class]]);
+ XCTAssertEqualObjects(_RPCIssuer.decodedRequest[kEmailKey], kTestEmail);
+ XCTAssertEqualObjects(_RPCIssuer.decodedRequest[kRequestTypeKey], kEmailLinkSignInTypeValue);
+ XCTAssertEqualObjects(_RPCIssuer.decodedRequest[kContinueURLKey], kContinueURL);
+ XCTAssertEqualObjects(_RPCIssuer.decodedRequest[kIosBundleIDKey], kIosBundleID);
+ XCTAssertEqualObjects(_RPCIssuer.decodedRequest[kAndroidPackageNameKey], kAndroidPackageName);
+ XCTAssertEqualObjects(_RPCIssuer.decodedRequest[kAndroidMinimumVersionKey],
+ kAndroidMinimumVersion);
+ XCTAssertEqualObjects(_RPCIssuer.decodedRequest[kAndroidInstallAppKey],
+ [NSNumber numberWithBool:YES]);
+ XCTAssertEqualObjects(_RPCIssuer.decodedRequest[kCanHandleCodeInAppKey],
+ [NSNumber numberWithBool:YES]);
+}
+
+
/** @fn testEmailVerificationRequest
@brief Tests the encoding of an email verification request.
*/
diff --git a/Example/Auth/Tests/FIRPhoneAuthProviderTests.m b/Example/Auth/Tests/FIRPhoneAuthProviderTests.m
index c5f4ec7..2b58270 100644
--- a/Example/Auth/Tests/FIRPhoneAuthProviderTests.m
+++ b/Example/Auth/Tests/FIRPhoneAuthProviderTests.m
@@ -44,7 +44,7 @@
#import "OCMStubRecorder+FIRAuthUnitTests.h"
#import "Phone/FIRPhoneAuthCredential_Internal.h"
-@import SafariServices;
+#import <SafariServices/SafariServices.h>
NS_ASSUME_NONNULL_BEGIN
@@ -180,7 +180,7 @@ static const NSTimeInterval kTestTimeout = 5;
/** @var kExpectationTimeout
@brief The maximum time waiting for expectations to fulfill.
*/
-static const NSTimeInterval kExpectationTimeout = 1;
+static const NSTimeInterval kExpectationTimeout = 2;
/** @class FIRPhoneAuthProviderTests
@brief Tests for @c FIRPhoneAuthProvider
diff --git a/Example/Auth/Tests/FIRUserTests.m b/Example/Auth/Tests/FIRUserTests.m
index c044e83..96a8082 100644
--- a/Example/Auth/Tests/FIRUserTests.m
+++ b/Example/Auth/Tests/FIRUserTests.m
@@ -18,17 +18,17 @@
#import <XCTest/XCTest.h>
-#import <FirebaseAuth/FIRUser.h>
-#import <FirebaseAuth/FIREmailAuthProvider.h>
-#import <FirebaseAuth/FIRFacebookAuthProvider.h>
-#import <FirebaseAuth/FIRGoogleAuthProvider.h>
-#import <FirebaseAuth/FIRAdditionalUserInfo.h>
+#import <FirebaseAuth/FirebaseAuth.h>
#import "FIRAuth_Internal.h"
#import "FIRAuthErrorUtils.h"
#import "FIRAuthBackend.h"
#import "FIRAuthGlobalWorkQueue.h"
#import "FIRAuthOperationType.h"
+#import "FIRAuthTokenResult.h"
+#import "FIRSecureTokenService.h"
+#import "FIRSecureTokenRequest.h"
+#import "FIRSecureTokenResponse.h"
#import "FIRGetAccountInfoRequest.h"
#import "FIRGetAccountInfoResponse.h"
#import "FIRSetAccountInfoRequest.h"
@@ -61,7 +61,58 @@ static NSString *const kAPIKey = @"FAKE_API_KEY";
/** @var kAccessToken
@brief The fake access token.
*/
-static NSString *const kAccessToken = @"ACCESS_TOKEN";
+static NSString *const kAccessToken = @"eyJhbGciOimnuzI1NiIsImtpZCI6ImY1YjE4Mjc2YTQ4NjYxZDBhODBiYzh"
+ "jM2U5NDM0OTc0ZDFmMWRiNTEifQ.eyJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5jb20vZmItc2EtdXBncm"
+ "FkZWQiLCJhdWQiOiJ0ZXN0X2F1ZCIsImF1dGhfdGltZSI6MTUyMjM2MDU0OSwidXNlcl9pZCI6InRlc3RfdXNlcl9pZCIs"
+ "InN1YiI6InRlc3Rfc3ViIiwiaWF0IjoxNTIyMzYwNTU3LCJleHAiOjE1MjIzNjQxNTcsImVtYWlsIjoiYXVuaXRlc3R1c2"
+ "VyQGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiZmlyZWJhc2UiOnsiaWRlbnRpdGllcyI6eyJlbWFpbCI6"
+ "WyJhdW5pdGVzdHVzZXJAZ21haWwuY29tIl19LCJzaWduX2luX3Byb3ZpZGVyIjoicGFzc3dvcmQifX0=.WFQqSrpVnxx7m"
+ "UrdKZA517Sp4ZBt-l2xQzGKNMVE90JB3vuNa-NyWZC-aTYMvND3-4aS3qRnN2kvk9KJAaF3eI_BKkcbZuq8O7iDVpOvqKC"
+ "3QcW0PnwqSPChL3XqoDF322FcBEgemwwgaEVZMuo7GhJvHw-XtBt1KRXOoGHcr3P6RsvoulUouKQmqt6TP27eZtrgH7jjN"
+ "hHm7gjX_WaRmgTOvYsuDbBBGdE15yIVZ3acI4cFUgwMRhaW-dDV7jTOqZGYJlTsI5oRMehphoVnYnEedJga28r4mqVkPbW"
+ "lddL4dVVm85FYmQcRc0b2CLMnSevBDlwu754ZUZmRgnuvDA";
+
+/** @var kAccessTokenLength415
+ @brief The fake access token with 415 characters in the claims potion of the token.
+ */
+static NSString *const kAccessTokenLength415 = @"eyJhbGciOimnuzI1NiIsImtpZCI6ImY1YjE4Mjc2YTQ4NjYxZD"
+ "BhODBiYzhjM2U5NDM0OTc0ZDFmMWRiNTEifQ.eyJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5jb20vdGVzd"
+ "CIsImF1ZCI6InRlc3RfYXVkIiwiYXV0aF90aW1lIjoxNTIyMzYwNTQ5LCJ1c2VyX2lkIjoidGVzdF91c2VyX2lkIiwic3V"
+ "iIjoidGVzdF9zdWIiLCJpYXQiOjE1MjIzNjA1NTcsImV4cCI6MTUyMjM2NDE1NywiZW1haWwiOiJhdW5pdGVzdHVzZXJAZ"
+ "21haWwuY29tIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJmaXJlYmFzZSI6eyJpZGVudGl0aWVzIjp7ImVtYWlsIjpbImF"
+ "1bml0ZXN0dXNlckBnbWFpbC5jb20iXX0sInNpZ25faW5fcHJvdmlkZXIiOiJwYXNzd29yZCJ9fQ=.WFQqSrpVnxx7m"
+ "UrdKZA517Sp4ZBt-l2xQzGKNMVE90JB3vuNa-NyWZC-aTYMvND3-4aS3qRnN2kvk9KJAaF3eI_BKkcbZuq8O7iDVpOvqKC"
+ "3QcW0PnwqSPChL3XqoDF322FcBEgemwwgaEVZMuo7GhJvHw-XtBt1KRXOoGHcr3P6RsvoulUouKQmqt6TP27eZtrgH7jjN"
+ "hHm7gjX_WaRmgTOvYsuDbBBGdE15yIVZ3acI4cFUgwMRhaW-dDV7jTOqZGYJlTsI5oRMehphoVnYnEedJga28r4mqVkPbW"
+ "lddL4dVVm85FYmQcRc0b2CLMnSevBDlwu754ZUZmRgnuvDA";
+
+/** @var kAccessTokenLength416
+ @brief The fake access token with 416 characters in the claims potion of the token.
+ */
+static NSString *const kAccessTokenLength416 = @"eyJhbGciOimnuzI1NiIsImtpZCI6ImY1YjE4Mjc2YTQ4NjYxZD"
+ "BhODBiYzhjM2U5NDM0OTc0ZDFmMWRiNTEifQ.eyJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5jb20vdGVzd"
+ "DIiLCJhdWQiOiJ0ZXN0X2F1ZCIsImF1dGhfdGltZSI6MTUyMjM2MDU0OSwidXNlcl9pZCI6InRlc3RfdXNlcl9pZCIsInN"
+ "1YiI6InRlc3Rfc3ViIiwiaWF0IjoxNTIyMzYwNTU3LCJleHAiOjE1MjIzNjQxNTcsImVtYWlsIjoiYXVuaXRlc3R1c2VyQ"
+ "GdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiZmlyZWJhc2UiOnsiaWRlbnRpdGllcyI6eyJlbWFpbCI6WyJ"
+ "hdW5pdGVzdHVzZXJAZ21haWwuY29tIl19LCJzaWduX2luX3Byb3ZpZGVyIjoicGFzc3dvcmQifX0=.WFQqSrpVnxx7m"
+ "UrdKZA517Sp4ZBt-l2xQzGKNMVE90JB3vuNa-NyWZC-aTYMvND3-4aS3qRnN2kvk9KJAaF3eI_BKkcbZuq8O7iDVpOvqKC"
+ "3QcW0PnwqSPChL3XqoDF322FcBEgemwwgaEVZMuo7GhJvHw-XtBt1KRXOoGHcr3P6RsvoulUouKQmqt6TP27eZtrgH7jjN"
+ "hHm7gjX_WaRmgTOvYsuDbBBGdE15yIVZ3acI4cFUgwMRhaW-dDV7jTOqZGYJlTsI5oRMehphoVnYnEedJga28r4mqVkPbW"
+ "lddL4dVVm85FYmQcRc0b2CLMnSevBDlwu754ZUZmRgnuvDA";
+
+/** @var kAccessTokenLength4523
+ @brief The fake access token with 523 characters in the claims potion of the token.
+ */
+static NSString *const kAccessTokenLength523 = @"eyJhbGciOimnuzI1NiIsImtpZCI6ImY1YjE4Mjc2YTQ4NjYxZD"
+ "BhODBiYzhjM2U5NDM0OTc0ZDFmMWRiNTEifQ.eyJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5jb20vdGVzd"
+ "DQiLCJhdWQiOiJ0ZXN0X2F1ZCIsImF1dGhfdGltZSI6MTUyMjM2MDU0OSwidXNlcl9pZCI6InRlc3RfdXNlcl9pZF81NDM"
+ "yIiwic3ViIjoidGVzdF9zdWIiLCJpYXQiOjE1MjIzNjA1NTcsImV4cCI6MTUyMjM2NDE1OSwiZW1haWwiOiJhdW5pdGVzd"
+ "HVzZXI0QGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJmaXJlYmFzZSI6eyJpZGVudGl0aWVzIjp7ImVtYWl"
+ "sIjpbImF1bml0ZXN0dXNlckBnbWFpbC5jb20iXX0sInNpZ25faW5fcHJvdmlkZXIiOiJwYXNzd29yZCJ9fQ=.WFQqSrpVn"
+ "xx7mUrdKZA517Sp4ZBt-l2xQzGKNMVE90JB3vuNa-NyWZC-aTYMvND3-4aS3qRnN2kvk9KJAaF3eI_BKkcbZuq8O7iDVpO"
+ "vqKC3QcW0PnwqSPChL3XqoDF322FcBEgemwwgaEVZMuo7GhJvHw-XtBt1KRXOoGHcr3P6RsvoulUouKQmqt6TP27eZtrgH"
+ "7jjNhHm7gjX_WaRmgTOvYsuDbBBGdE15yIVZ3acI4cFUgwMRhaW-dDV7jTOqZGYJlTsI5oRMehphoVnYnEedJga28r4mqV"
+ "kPbWlddL4dVVm85FYmQcRc0b2CLMnSevBDlwu754ZUZmRgnuvDA";
/** @var kNewAccessToken
@brief A new value for the fake access token.
@@ -231,7 +282,19 @@ static NSTimeInterval const kLastSignInDateTimeIntervalInSeconds = 1505858583;
/** @var kExpectationTimeout
@brief The maximum time waiting for expectations to fulfill.
*/
-static const NSTimeInterval kExpectationTimeout = 1;
+static const NSTimeInterval kExpectationTimeout = 2;
+
+/** @extention FIRSecureTokenService
+ @brief Extends the FIRSecureTokenService class to expose one private method for testing only.
+ */
+@interface FIRSecureTokenService ()
+
+/** @fn hasValidAccessToken
+ @brief private method exposed so it can be mocked to prevent the fake expiration date from
+ affecting the the unit tests.
+ */
+- (BOOL)hasValidAccessToken;
+@end
/** @class FIRUserTests
@brief Tests for @c FIRUser .
@@ -899,6 +962,84 @@ static const NSTimeInterval kExpectationTimeout = 1;
OCMVerifyAll(_mockBackend);
}
+/** @fn testGetIDTokenResultSuccess
+ @brief Tests the flow of a successful @c getIDTokenResultWithCompletion: call.
+ */
+- (void)testGetIDTokenResultSuccess {
+ id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]);
+ OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID);
+ OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail);
+ OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kGoogleDisplayName);
+ OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash);
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ id mockSecureTokenService = OCMClassMock([FIRSecureTokenService class]);
+ OCMStub([mockSecureTokenService hasValidAccessToken]).andReturn(YES);
+ [self signInWithEmailPasswordWithMockUserInfoResponse:mockGetAccountInfoResponseUser
+ completion:^(FIRUser *user) {
+ [user getIDTokenResultWithCompletion:^(FIRAuthTokenResult *_Nullable tokenResult,
+ NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertNil(error);
+ XCTAssertEqualObjects(tokenResult.token, kAccessToken);
+ XCTAssertTrue(tokenResult.issuedAtDate &&
+ [tokenResult.issuedAtDate isKindOfClass:[NSDate class]]);
+ XCTAssertTrue(tokenResult.authDate && [tokenResult.authDate isKindOfClass:[NSDate class]]);
+ XCTAssertTrue(tokenResult.expirationDate &&
+ [tokenResult.expirationDate isKindOfClass:[NSDate class]]);
+ XCTAssertTrue(tokenResult.claims && [tokenResult.claims isKindOfClass:[NSDictionary class]]);
+ [expectation fulfill];
+ }];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testGetIDTokenResultForcingRefreshFailure
+ @brief Tests the flow successful @c getIDTokenResultForcingRefresh:completion: calls.
+ */
+- (void)testGetIDTokenResultForcingRefreshSuccess {
+ [self getIDTokenResultForcingRefreshSuccessWithIDToken:kAccessToken];
+ [self getIDTokenResultForcingRefreshSuccessWithIDToken:kAccessTokenLength415];
+ [self getIDTokenResultForcingRefreshSuccessWithIDToken:kAccessTokenLength416];
+ [self getIDTokenResultForcingRefreshSuccessWithIDToken:kAccessTokenLength523];
+}
+
+
+/** @fn testGetIDTokenResultForcingRefreshFailure
+ @brief Tests the flow of a failed @c getIDTokenResultForcingRefresh:completion: call.
+ */
+- (void)testGetIDTokenResultForcingRefreshFailure {
+ id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]);
+ OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID);
+ OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail);
+ OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kGoogleDisplayName);
+ OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash);
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [self signInWithEmailPasswordWithMockUserInfoResponse:mockGetAccountInfoResponseUser
+ completion:^(FIRUser *user) {
+ OCMExpect([_mockBackend secureToken:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRSecureTokenRequest *_Nullable request,
+ FIRSecureTokenResponseCallback callback) {
+ XCTAssertEqualObjects(request.APIKey, kAPIKey);
+
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+
+ callback(nil, [FIRAuthErrorUtils networkErrorWithUnderlyingError:[NSError new]]);
+ });
+ });
+ [user getIDTokenResultForcingRefresh:YES
+ completion:^(FIRAuthTokenResult *_Nullable tokenResult,
+ NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertNil(tokenResult);
+ XCTAssertEqual(error.code, FIRAuthErrorCodeNetworkError);
+ [expectation fulfill];
+ }];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+}
+
/** @fn testReloadFailure
@brief Tests the flow of a failed @c reloadWithCompletion: call.
*/
@@ -1963,6 +2104,49 @@ static const NSTimeInterval kExpectationTimeout = 1;
#pragma mark - Helpers
+/** @fn getIDTokenResultForcingRefreshSuccess
+ @brief Helper for testing the flow of a successful @c
+ getIDTokenResultForcingRefresh:completion: call.
+ */
+- (void)getIDTokenResultForcingRefreshSuccessWithIDToken:(NSString *)idToken {
+ id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]);
+ OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID);
+ OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail);
+ OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kGoogleDisplayName);
+ OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash);
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [self signInWithEmailPasswordWithMockUserInfoResponse:mockGetAccountInfoResponseUser
+ completion:^(FIRUser *user) {
+ OCMExpect([_mockBackend secureToken:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRSecureTokenRequest *_Nullable request,
+ FIRSecureTokenResponseCallback callback) {
+ XCTAssertEqualObjects(request.APIKey, kAPIKey);
+
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ id mockSecureTokenResponse = OCMClassMock([FIRSecureTokenResponse class]);
+ OCMStub([mockSecureTokenResponse accessToken]).andReturn(idToken);
+ callback(mockSecureTokenResponse, nil);
+ });
+ });
+ [user getIDTokenResultForcingRefresh:YES
+ completion:^(FIRAuthTokenResult *_Nullable tokenResult,
+ NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertNil(error);
+ XCTAssertEqualObjects(tokenResult.token, idToken);
+ XCTAssertTrue(tokenResult.issuedAtDate &&
+ [tokenResult.issuedAtDate isKindOfClass:[NSDate class]]);
+ XCTAssertTrue(tokenResult.authDate && [tokenResult.authDate isKindOfClass:[NSDate class]]);
+ XCTAssertTrue(tokenResult.expirationDate &&
+ [tokenResult.expirationDate isKindOfClass:[NSDate class]]);
+ XCTAssertTrue(tokenResult.claims && [tokenResult.claims isKindOfClass:[NSDictionary class]]);
+ [expectation fulfill];
+ }];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+}
+
/** @fn signInWithEmailPasswordWithMockGetAccountInfoResponse:completion:
@brief Signs in with an email and password account with mocked backend end calls.
@param mockUserInfoResponse A mocked FIRGetAccountInfoResponseUser object.
diff --git a/Example/Auth/Tests/FIRVerifyPasswordRequestTest.m b/Example/Auth/Tests/FIRVerifyPasswordRequestTest.m
index 54ba7b0..2359c46 100644
--- a/Example/Auth/Tests/FIRVerifyPasswordRequestTest.m
+++ b/Example/Auth/Tests/FIRVerifyPasswordRequestTest.m
@@ -18,9 +18,9 @@
#import "FIRAuthErrors.h"
#import "FIRAuthBackend.h"
+#import "FIRFakeBackendRPCIssuer.h"
#import "FIRVerifyPasswordRequest.h"
#import "FIRVerifyPasswordResponse.h"
-#import "FIRFakeBackendRPCIssuer.h"
/** @var kTestAPIKey
@brief Fake API key used for testing.
@@ -125,9 +125,10 @@ static NSString *const kExpectedAPIURL =
@brief Tests the verify password request.
*/
- (void)testVerifyPasswordRequest {
- FIRVerifyPasswordRequest * request = [[FIRVerifyPasswordRequest alloc] initWithEmail:kTestEmail
- password:kTestPassword
- requestConfiguration:_requestConfiguration];
+ FIRVerifyPasswordRequest * request =
+ [[FIRVerifyPasswordRequest alloc] initWithEmail:kTestEmail
+ password:kTestPassword
+ requestConfiguration:_requestConfiguration];
request.returnSecureToken = NO;
[FIRAuthBackend verifyPassword:request
callback:^(FIRVerifyPasswordResponse *_Nullable response,
@@ -147,9 +148,10 @@ static NSString *const kExpectedAPIURL =
@brief Tests the verify password request with optional fields.
*/
- (void)testVerifyPasswordRequestOptionalFields {
- FIRVerifyPasswordRequest * request = [[FIRVerifyPasswordRequest alloc] initWithEmail:kTestEmail
- password:kTestPassword
- requestConfiguration:_requestConfiguration];
+ FIRVerifyPasswordRequest * request =
+ [[FIRVerifyPasswordRequest alloc] initWithEmail:kTestEmail
+ password:kTestPassword
+ requestConfiguration:_requestConfiguration];
request.pendingIDToken = kTestPendingToken;
request.captchaChallenge = kTestCaptchaChallenge;
request.captchaResponse = kTestCaptchaResponse;
diff --git a/Example/Core/App/GoogleService-Info.plist b/Example/Core/App/GoogleService-Info.plist
index 89afffe..3f7547f 100644
--- a/Example/Core/App/GoogleService-Info.plist
+++ b/Example/Core/App/GoogleService-Info.plist
@@ -10,8 +10,6 @@
<string>correct_client_id</string>
<key>REVERSED_CLIENT_ID</key>
<string>correct_reversed_client_id</string>
- <key>ANDROID_CLIENT_ID</key>
- <string>correct_android_client_id</string>
<key>GOOGLE_APP_ID</key>
<string>1:123:ios:123abc</string>
<key>GCM_SENDER_ID</key>
diff --git a/Example/Core/App/tvOS/AppDelegate.h b/Example/Core/App/tvOS/AppDelegate.h
new file mode 100644
index 0000000..013891c
--- /dev/null
+++ b/Example/Core/App/tvOS/AppDelegate.h
@@ -0,0 +1,21 @@
+// Copyright 2017 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 <UIKit/UIKit.h>
+
+@interface AppDelegate : UIResponder <UIApplicationDelegate>
+
+@property(strong, nonatomic) UIWindow *window;
+
+@end
diff --git a/Example/Core/App/tvOS/AppDelegate.m b/Example/Core/App/tvOS/AppDelegate.m
new file mode 100644
index 0000000..9717ad4
--- /dev/null
+++ b/Example/Core/App/tvOS/AppDelegate.m
@@ -0,0 +1,62 @@
+// Copyright 2017 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 FirebaseCore;
+
+#import "AppDelegate.h"
+
+@interface AppDelegate ()
+
+@end
+
+@implementation AppDelegate
+
+- (BOOL)application:(UIApplication *)application
+ didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
+ // Override point for customization after application launch.
+ [FIRApp configure];
+ return YES;
+}
+
+- (void)applicationWillResignActive:(UIApplication *)application {
+ // Sent when the application is about to move from active to inactive state. This can occur for
+ // certain types of temporary interruptions (such as an incoming phone call or SMS message) or
+ // when the user quits the application and it begins the transition to the background state. Use
+ // this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates.
+ // Games should use this method to pause the game.
+}
+
+- (void)applicationDidEnterBackground:(UIApplication *)application {
+ // Use this method to release shared resources, save user data, invalidate timers, and store
+ // enough application state information to restore your application to its current state in case
+ // it is terminated later. If your application supports background execution, this method is
+ // called instead of applicationWillTerminate: when the user quits.
+}
+
+- (void)applicationWillEnterForeground:(UIApplication *)application {
+ // Called as part of the transition from the background to the active state; here you can undo
+ // many of the changes made on entering the background.
+}
+
+- (void)applicationDidBecomeActive:(UIApplication *)application {
+ // Restart any tasks that were paused (or not yet started) while the application was inactive. If
+ // the application was previously in the background, optionally refresh the user interface.
+}
+
+- (void)applicationWillTerminate:(UIApplication *)application {
+ // Called when the application is about to terminate. Save data if appropriate. See also
+ // applicationDidEnterBackground:.
+}
+
+@end
diff --git a/Example/Core/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json b/Example/Core/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json
new file mode 100644
index 0000000..7f06667
--- /dev/null
+++ b/Example/Core/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json
@@ -0,0 +1,11 @@
+{
+ "images" : [
+ {
+ "idiom" : "tv"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
diff --git a/Example/Core/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json b/Example/Core/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json
new file mode 100644
index 0000000..da4a164
--- /dev/null
+++ b/Example/Core/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/Core/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json b/Example/Core/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json
new file mode 100644
index 0000000..d29f024
--- /dev/null
+++ b/Example/Core/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json
@@ -0,0 +1,17 @@
+{
+ "layers" : [
+ {
+ "filename" : "Front.imagestacklayer"
+ },
+ {
+ "filename" : "Middle.imagestacklayer"
+ },
+ {
+ "filename" : "Back.imagestacklayer"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/Core/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json b/Example/Core/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json
new file mode 100644
index 0000000..7f06667
--- /dev/null
+++ b/Example/Core/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json
@@ -0,0 +1,11 @@
+{
+ "images" : [
+ {
+ "idiom" : "tv"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
diff --git a/Example/Core/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json b/Example/Core/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json
new file mode 100644
index 0000000..da4a164
--- /dev/null
+++ b/Example/Core/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/Core/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json b/Example/Core/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json
new file mode 100644
index 0000000..7f06667
--- /dev/null
+++ b/Example/Core/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json
@@ -0,0 +1,11 @@
+{
+ "images" : [
+ {
+ "idiom" : "tv"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
diff --git a/Example/Core/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json b/Example/Core/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json
new file mode 100644
index 0000000..da4a164
--- /dev/null
+++ b/Example/Core/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/Core/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json b/Example/Core/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json
new file mode 100644
index 0000000..16a370d
--- /dev/null
+++ b/Example/Core/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images" : [
+ {
+ "idiom" : "tv",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "tv",
+ "scale" : "2x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/Core/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json b/Example/Core/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json
new file mode 100644
index 0000000..da4a164
--- /dev/null
+++ b/Example/Core/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/Core/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json b/Example/Core/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json
new file mode 100644
index 0000000..d29f024
--- /dev/null
+++ b/Example/Core/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json
@@ -0,0 +1,17 @@
+{
+ "layers" : [
+ {
+ "filename" : "Front.imagestacklayer"
+ },
+ {
+ "filename" : "Middle.imagestacklayer"
+ },
+ {
+ "filename" : "Back.imagestacklayer"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/Core/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json b/Example/Core/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json
new file mode 100644
index 0000000..16a370d
--- /dev/null
+++ b/Example/Core/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images" : [
+ {
+ "idiom" : "tv",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "tv",
+ "scale" : "2x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/Core/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json b/Example/Core/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json
new file mode 100644
index 0000000..da4a164
--- /dev/null
+++ b/Example/Core/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/Core/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json b/Example/Core/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json
new file mode 100644
index 0000000..16a370d
--- /dev/null
+++ b/Example/Core/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images" : [
+ {
+ "idiom" : "tv",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "tv",
+ "scale" : "2x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/Core/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json b/Example/Core/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json
new file mode 100644
index 0000000..da4a164
--- /dev/null
+++ b/Example/Core/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/Core/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json b/Example/Core/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json
new file mode 100644
index 0000000..b03ded1
--- /dev/null
+++ b/Example/Core/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json
@@ -0,0 +1,32 @@
+{
+ "assets" : [
+ {
+ "size" : "1280x768",
+ "idiom" : "tv",
+ "filename" : "App Icon - App Store.imagestack",
+ "role" : "primary-app-icon"
+ },
+ {
+ "size" : "400x240",
+ "idiom" : "tv",
+ "filename" : "App Icon.imagestack",
+ "role" : "primary-app-icon"
+ },
+ {
+ "size" : "2320x720",
+ "idiom" : "tv",
+ "filename" : "Top Shelf Image Wide.imageset",
+ "role" : "top-shelf-image-wide"
+ },
+ {
+ "size" : "1920x720",
+ "idiom" : "tv",
+ "filename" : "Top Shelf Image.imageset",
+ "role" : "top-shelf-image"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
diff --git a/Example/Core/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json b/Example/Core/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json
new file mode 100644
index 0000000..16a370d
--- /dev/null
+++ b/Example/Core/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images" : [
+ {
+ "idiom" : "tv",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "tv",
+ "scale" : "2x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/Core/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json b/Example/Core/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json
new file mode 100644
index 0000000..16a370d
--- /dev/null
+++ b/Example/Core/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images" : [
+ {
+ "idiom" : "tv",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "tv",
+ "scale" : "2x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/Core/App/tvOS/Assets.xcassets/Contents.json b/Example/Core/App/tvOS/Assets.xcassets/Contents.json
new file mode 100644
index 0000000..da4a164
--- /dev/null
+++ b/Example/Core/App/tvOS/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/Core/App/tvOS/Assets.xcassets/LaunchImage.launchimage/Contents.json b/Example/Core/App/tvOS/Assets.xcassets/LaunchImage.launchimage/Contents.json
new file mode 100644
index 0000000..d746a60
--- /dev/null
+++ b/Example/Core/App/tvOS/Assets.xcassets/LaunchImage.launchimage/Contents.json
@@ -0,0 +1,22 @@
+{
+ "images" : [
+ {
+ "orientation" : "landscape",
+ "idiom" : "tv",
+ "extent" : "full-screen",
+ "minimum-system-version" : "11.0",
+ "scale" : "2x"
+ },
+ {
+ "orientation" : "landscape",
+ "idiom" : "tv",
+ "extent" : "full-screen",
+ "minimum-system-version" : "9.0",
+ "scale" : "1x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/Core/App/tvOS/Info.plist b/Example/Core/App/tvOS/Info.plist
new file mode 100644
index 0000000..02942a3
--- /dev/null
+++ b/Example/Core/App/tvOS/Info.plist
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>$(DEVELOPMENT_LANGUAGE)</string>
+ <key>CFBundleExecutable</key>
+ <string>$(EXECUTABLE_NAME)</string>
+ <key>CFBundleIdentifier</key>
+ <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>$(PRODUCT_NAME)</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>1.0</string>
+ <key>CFBundleVersion</key>
+ <string>1</string>
+ <key>LSRequiresIPhoneOS</key>
+ <true/>
+ <key>UIMainStoryboardFile</key>
+ <string>Main</string>
+ <key>UIRequiredDeviceCapabilities</key>
+ <array>
+ <string>arm64</string>
+ </array>
+ <key>UIUserInterfaceStyle</key>
+ <string>Automatic</string>
+</dict>
+</plist>
diff --git a/Example/Core/App/tvOS/Main.storyboard b/Example/Core/App/tvOS/Main.storyboard
new file mode 100644
index 0000000..72d5e22
--- /dev/null
+++ b/Example/Core/App/tvOS/Main.storyboard
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder.AppleTV.Storyboard" version="3.0" toolsVersion="13122.16" systemVersion="17A278a" targetRuntime="AppleTV" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
+ <dependencies>
+ <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
+ <capability name="Safe area layout guides" minToolsVersion="9.0"/>
+ <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+ </dependencies>
+ <scenes>
+ <!--View Controller-->
+ <scene sceneID="tne-QT-ifu">
+ <objects>
+ <viewController id="BYZ-38-t0r" customClass="ViewController" customModuleProvider="" sceneMemberID="viewController">
+ <layoutGuides>
+ <viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
+ <viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
+ </layoutGuides>
+ <view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
+ <rect key="frame" x="0.0" y="0.0" width="1920" height="1080"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+ <color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
+ <viewLayoutGuide key="safeArea" id="wu6-TO-1qx"/>
+ </view>
+ </viewController>
+ <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
+ </objects>
+ </scene>
+ </scenes>
+</document>
diff --git a/Example/Core/App/tvOS/ViewController.h b/Example/Core/App/tvOS/ViewController.h
new file mode 100644
index 0000000..b6115b8
--- /dev/null
+++ b/Example/Core/App/tvOS/ViewController.h
@@ -0,0 +1,19 @@
+// Copyright 2017 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 <UIKit/UIKit.h>
+
+@interface ViewController : UIViewController
+
+@end
diff --git a/Example/Core/App/tvOS/ViewController.m b/Example/Core/App/tvOS/ViewController.m
new file mode 100644
index 0000000..6d4676b
--- /dev/null
+++ b/Example/Core/App/tvOS/ViewController.m
@@ -0,0 +1,33 @@
+// Copyright 2017 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 "ViewController.h"
+
+@interface ViewController ()
+
+@end
+
+@implementation ViewController
+
+- (void)viewDidLoad {
+ [super viewDidLoad];
+ // Do any additional setup after loading the view, typically from a nib.
+}
+
+- (void)didReceiveMemoryWarning {
+ [super didReceiveMemoryWarning];
+ // Dispose of any resources that can be recreated.
+}
+
+@end
diff --git a/Example/Core/App/tvOS/main.m b/Example/Core/App/tvOS/main.m
new file mode 100644
index 0000000..d9e6654
--- /dev/null
+++ b/Example/Core/App/tvOS/main.m
@@ -0,0 +1,22 @@
+// Copyright 2017 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 <UIKit/UIKit.h>
+#import "AppDelegate.h"
+
+int main(int argc, char* argv[]) {
+ @autoreleasepool {
+ return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
+ }
+}
diff --git a/Example/Core/Tests/FIRAppAssociationRegistrationUnitTests.m b/Example/Core/Tests/FIRAppAssociationRegistrationUnitTests.m
index 2bc1de7..60f0651 100644
--- a/Example/Core/Tests/FIRAppAssociationRegistrationUnitTests.m
+++ b/Example/Core/Tests/FIRAppAssociationRegistrationUnitTests.m
@@ -34,7 +34,7 @@ static NSString *kKey2 = @"key2";
/** @var gCreateNewObject
@brief A block that returns a new object everytime it is called.
*/
-static id _Nullable (^gCreateNewObject)() = ^id _Nullable() {
+static id _Nullable (^gCreateNewObject)(void) = ^id _Nullable() {
return [[NSObject alloc] init];
};
diff --git a/Example/Core/Tests/FIRAppTest.m b/Example/Core/Tests/FIRAppTest.m
index 9b3554d..6825e6a 100644
--- a/Example/Core/Tests/FIRAppTest.m
+++ b/Example/Core/Tests/FIRAppTest.m
@@ -95,8 +95,11 @@ NSString *const kFIRTestAppName2 = @"test-app-name-2";
}
- (void)testConfigureWithOptions {
- // nil options
+// nil options
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wnonnull"
XCTAssertThrows([FIRApp configureWithOptions:nil]);
+#pragma clang diagnostic pop
XCTAssertTrue([FIRApp allApps].count == 0);
NSDictionary *expectedUserInfo =
@@ -117,17 +120,10 @@ NSString *const kFIRTestAppName2 = @"test-app-name-2";
- (void)testConfigureWithCustomizedOptions {
// valid customized options
- FIROptions *options = [[FIROptions alloc] initWithGoogleAppID:kGoogleAppID
- bundleID:kBundleID
- GCMSenderID:kGCMSenderID
- APIKey:kCustomizedAPIKey
- clientID:nil
- trackingID:nil
- androidClientID:nil
- databaseURL:nil
- storageBucket:nil
- deepLinkURLScheme:nil];
-
+ FIROptions *options =
+ [[FIROptions alloc] initWithGoogleAppID:kGoogleAppID GCMSenderID:kGCMSenderID];
+ options.bundleID = kBundleID;
+ options.APIKey = kCustomizedAPIKey;
NSDictionary *expectedUserInfo =
[self expectedUserInfoWithAppName:kFIRDefaultAppName isDefaultApp:YES];
OCMExpect([self.notificationCenterMock postNotificationName:kFIRAppReadyToConfigureSDKNotification
@@ -146,8 +142,11 @@ NSString *const kFIRTestAppName2 = @"test-app-name-2";
}
- (void)testConfigureWithNameAndOptions {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wnonnull"
XCTAssertThrows([FIRApp configureWithName:nil options:[FIROptions defaultOptions]]);
XCTAssertThrows([FIRApp configureWithName:kFIRTestAppName1 options:nil]);
+#pragma clang diagnostic pop
XCTAssertThrows([FIRApp configureWithName:@"" options:[FIROptions defaultOptions]]);
XCTAssertThrows(
[FIRApp configureWithName:kFIRDefaultAppName options:[FIROptions defaultOptions]]);
@@ -186,16 +185,10 @@ NSString *const kFIRTestAppName2 = @"test-app-name-2";
self.app = [FIRApp appNamed:kFIRTestAppName1];
// Configure a different app with valid customized options
- FIROptions *customizedOptions = [[FIROptions alloc] initWithGoogleAppID:kGoogleAppID
- bundleID:kBundleID
- GCMSenderID:kGCMSenderID
- APIKey:kCustomizedAPIKey
- clientID:nil
- trackingID:nil
- androidClientID:nil
- databaseURL:nil
- storageBucket:nil
- deepLinkURLScheme:nil];
+ FIROptions *customizedOptions =
+ [[FIROptions alloc] initWithGoogleAppID:kGoogleAppID GCMSenderID:kGCMSenderID];
+ customizedOptions.bundleID = kBundleID;
+ customizedOptions.APIKey = kCustomizedAPIKey;
NSDictionary *expectedUserInfo2 =
[self expectedUserInfoWithAppName:kFIRTestAppName2 isDefaultApp:NO];
@@ -583,6 +576,27 @@ NSString *const kFIRTestAppName2 = @"test-app-name-2";
XCTAssertFalse([FIRApp isDefaultAppConfigured]);
}
+- (void)testIllegalLibraryName {
+ [FIRApp registerLibrary:@"Oops>" withVersion:@"1.0.0"];
+ XCTAssertTrue([[FIRApp firebaseUserAgent] isEqualToString:@""]);
+}
+
+- (void)testIllegalLibraryVersion {
+ [FIRApp registerLibrary:@"LegalName" withVersion:@"1.0.0+"];
+ XCTAssertTrue([[FIRApp firebaseUserAgent] isEqualToString:@""]);
+}
+
+- (void)testSingleLibrary {
+ [FIRApp registerLibrary:@"LegalName" withVersion:@"1.0.0"];
+ XCTAssertTrue([[FIRApp firebaseUserAgent] containsString:@"LegalName/1.0.0"]);
+}
+
+- (void)testMultipleLibraries {
+ [FIRApp registerLibrary:@"LegalName" withVersion:@"1.0.0"];
+ [FIRApp registerLibrary:@"LegalName2" withVersion:@"2.0.0"];
+ XCTAssertTrue([[FIRApp firebaseUserAgent] containsString:@"LegalName/1.0.0 LegalName2/2.0.0"]);
+}
+
#pragma mark - private
- (NSDictionary<NSString *, NSObject *> *)expectedUserInfoWithAppName:(NSString *)name
diff --git a/Example/Core/Tests/FIRLoggerTest.m b/Example/Core/Tests/FIRLoggerTest.m
index 7740527..31a495d 100644
--- a/Example/Core/Tests/FIRLoggerTest.m
+++ b/Example/Core/Tests/FIRLoggerTest.m
@@ -12,6 +12,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+#ifdef DEBUG
+// The tests depend upon library methods only built with #ifdef DEBUG
+
#import "FIRTestCase.h"
#import <FirebaseCore/FIRLogger.h>
@@ -28,13 +31,13 @@ extern const char *kFIRLoggerASLClientFacilityName;
extern const char *kFIRLoggerCustomASLMessageFormat;
-extern void FIRResetLogger();
+extern void FIRResetLogger(void);
-extern aslclient getFIRLoggerClient();
+extern aslclient getFIRLoggerClient(void);
-extern dispatch_queue_t getFIRClientQueue();
+extern dispatch_queue_t getFIRClientQueue(void);
-extern BOOL getFIRLoggerDebugMode();
+extern BOOL getFIRLoggerDebugMode(void);
// Define the message format again to make sure the format doesn't accidentally change.
static NSString *const kCorrectASLMessageFormat =
@@ -148,8 +151,12 @@ static NSString *const kMessageCode = @"I-COR000001";
// Lowercase should fail.
XCTAssertThrows(FIRLogError(kFIRLoggerCore, @"I-app000001", @"Message."));
- // nil or empty message code should fail.
+// nil or empty message code should fail.
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wnonnull"
XCTAssertThrows(FIRLogError(kFIRLoggerCore, nil, @"Message."));
+#pragma clang diagnostic pop
+
XCTAssertThrows(FIRLogError(kFIRLoggerCore, @"", @"Message."));
// Android message code should fail.
@@ -241,6 +248,8 @@ static NSString *const kMessageCode = @"I-COR000001";
}
- (BOOL)messageWasLogged:(NSString *)message {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
aslmsg query = asl_new(ASL_TYPE_QUERY);
asl_set_query(query, ASL_KEY_FACILITY, kFIRLoggerASLClientFacilityName, ASL_QUERY_OP_EQUAL);
aslresponse r = asl_search(getFIRLoggerClient(), query);
@@ -257,6 +266,8 @@ static NSString *const kMessageCode = @"I-COR000001";
asl_free(m);
asl_release(r);
return [allMsg containsObject:message];
+#pragma clang pop
}
@end
+#endif
diff --git a/Example/Core/Tests/FIROptionsTest.m b/Example/Core/Tests/FIROptionsTest.m
index d01eec5..20aec94 100644
--- a/Example/Core/Tests/FIROptionsTest.m
+++ b/Example/Core/Tests/FIROptionsTest.m
@@ -25,7 +25,8 @@ extern NSString *const kFIRLibraryVersionID;
@interface FIROptions (Test)
-@property(nonatomic, readonly) NSDictionary *analyticsOptionsDictionary;
+- (nullable NSDictionary *)analyticsOptionsDictionaryWithInfoDictionary:
+ (nullable NSDictionary *)infoDictionary;
@end
@@ -80,49 +81,22 @@ extern NSString *const kFIRLibraryVersionID;
}
- (void)testInitCustomizedOptions {
- FIROptions *options = [[FIROptions alloc] initWithGoogleAppID:kGoogleAppID
- bundleID:kBundleID
- GCMSenderID:kGCMSenderID
- APIKey:kAPIKey
- clientID:kClientID
- trackingID:kTrackingID
- androidClientID:kAndroidClientID
- databaseURL:kDatabaseURL
- storageBucket:kStorageBucket
- deepLinkURLScheme:kDeepLinkURLScheme];
- [self assertOptionsMatchDefaults:options andProjectID:NO];
- XCTAssertEqualObjects(options.deepLinkURLScheme, kDeepLinkURLScheme);
- XCTAssertFalse(options.usingOptionsFromDefaultPlist);
-
- FIROptions *options2 =
+ FIROptions *options =
[[FIROptions alloc] initWithGoogleAppID:kGoogleAppID GCMSenderID:kGCMSenderID];
- options2.androidClientID = kAndroidClientID;
- options2.APIKey = kAPIKey;
- options2.bundleID = kBundleID;
- options2.clientID = kClientID;
- options2.databaseURL = kDatabaseURL;
- options2.deepLinkURLScheme = kDeepLinkURLScheme;
- options2.projectID = kProjectID;
- options2.storageBucket = kStorageBucket;
- options2.trackingID = kTrackingID;
- [self assertOptionsMatchDefaults:options2 andProjectID:YES];
- XCTAssertEqualObjects(options2.deepLinkURLScheme, kDeepLinkURLScheme);
+ options.APIKey = kAPIKey;
+ options.bundleID = kBundleID;
+ options.clientID = kClientID;
+ options.databaseURL = kDatabaseURL;
+ options.deepLinkURLScheme = kDeepLinkURLScheme;
+ options.projectID = kProjectID;
+ options.storageBucket = kStorageBucket;
+ options.trackingID = kTrackingID;
+ [self assertOptionsMatchDefaults:options andProjectID:YES];
+ XCTAssertEqualObjects(options.deepLinkURLScheme, kDeepLinkURLScheme);
XCTAssertFalse(options.usingOptionsFromDefaultPlist);
-
- // nil GoogleAppID should throw an exception
- XCTAssertThrows([[FIROptions alloc] initWithGoogleAppID:nil
- bundleID:kBundleID
- GCMSenderID:kGCMSenderID
- APIKey:kCustomizedAPIKey
- clientID:nil
- trackingID:nil
- androidClientID:nil
- databaseURL:nil
- storageBucket:nil
- deepLinkURLScheme:nil]);
}
-- (void)testinitWithContentsOfFile {
+- (void)testInitWithContentsOfFile {
NSString *filePath =
[[NSBundle mainBundle] pathForResource:@"GoogleService-Info" ofType:@"plist"];
FIROptions *options = [[FIROptions alloc] initWithContentsOfFile:filePath];
@@ -130,7 +104,10 @@ extern NSString *const kFIRLibraryVersionID;
XCTAssertNil(options.deepLinkURLScheme);
XCTAssertFalse(options.usingOptionsFromDefaultPlist);
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wnonnull"
FIROptions *emptyOptions = [[FIROptions alloc] initWithContentsOfFile:nil];
+#pragma clang diagnostic pop
XCTAssertNil(emptyOptions);
FIROptions *invalidOptions = [[FIROptions alloc] initWithContentsOfFile:@"invalid.plist"];
@@ -143,7 +120,7 @@ extern NSString *const kFIRLibraryVersionID;
XCTAssertEqualObjects(options.clientID, kClientID);
XCTAssertEqualObjects(options.trackingID, kTrackingID);
XCTAssertEqualObjects(options.GCMSenderID, kGCMSenderID);
- XCTAssertEqualObjects(options.androidClientID, kAndroidClientID);
+ XCTAssertNil(options.androidClientID);
XCTAssertEqualObjects(options.libraryVersionID, kFIRLibraryVersionID);
XCTAssertEqualObjects(options.databaseURL, kDatabaseURL);
XCTAssertEqualObjects(options.storageBucket, kStorageBucket);
@@ -230,16 +207,9 @@ extern NSString *const kFIRLibraryVersionID;
XCTAssertEqualObjects(newOptions.deepLinkURLScheme, kDeepLinkURLScheme);
// customized options
- FIROptions *customizedOptions = [[FIROptions alloc] initWithGoogleAppID:kGoogleAppID
- bundleID:kBundleID
- GCMSenderID:kGCMSenderID
- APIKey:kAPIKey
- clientID:kClientID
- trackingID:kTrackingID
- androidClientID:kAndroidClientID
- databaseURL:kDatabaseURL
- storageBucket:kStorageBucket
- deepLinkURLScheme:kDeepLinkURLScheme];
+ FIROptions *customizedOptions =
+ [[FIROptions alloc] initWithGoogleAppID:kGoogleAppID GCMSenderID:kGCMSenderID];
+ customizedOptions.deepLinkURLScheme = kDeepLinkURLScheme;
FIROptions *copyCustomizedOptions = [customizedOptions copy];
[copyCustomizedOptions setDeepLinkURLScheme:kNewDeepLinkURLScheme];
XCTAssertEqualObjects(customizedOptions.deepLinkURLScheme, kDeepLinkURLScheme);
@@ -255,22 +225,20 @@ extern NSString *const kFIRLibraryVersionID;
}
- (void)testAnalyticsOptions {
- id mainBundleMock = OCMPartialMock([NSBundle mainBundle]);
-
// No keys anywhere.
NSDictionary *optionsDictionary = nil;
FIROptions *options = [[FIROptions alloc] initInternalWithOptionsDictionary:optionsDictionary];
NSDictionary *mainDictionary = nil;
- OCMExpect([mainBundleMock infoDictionary]).andReturn(mainDictionary);
NSDictionary *expectedAnalyticsOptions = @{};
- XCTAssertEqualObjects(options.analyticsOptionsDictionary, expectedAnalyticsOptions);
+ NSDictionary *analyticsOptions = [options analyticsOptionsDictionaryWithInfoDictionary:nil];
+ XCTAssertEqualObjects(analyticsOptions, expectedAnalyticsOptions);
optionsDictionary = @{};
options = [[FIROptions alloc] initInternalWithOptionsDictionary:optionsDictionary];
mainDictionary = @{};
- OCMExpect([mainBundleMock infoDictionary]).andReturn(mainDictionary);
expectedAnalyticsOptions = @{};
- XCTAssertEqualObjects(options.analyticsOptionsDictionary, expectedAnalyticsOptions);
+ analyticsOptions = [options analyticsOptionsDictionaryWithInfoDictionary:mainDictionary];
+ XCTAssertEqualObjects(analyticsOptions, expectedAnalyticsOptions);
// Main has no keys.
optionsDictionary = @{
@@ -280,9 +248,9 @@ extern NSString *const kFIRLibraryVersionID;
};
options = [[FIROptions alloc] initInternalWithOptionsDictionary:optionsDictionary];
mainDictionary = @{};
- OCMExpect([mainBundleMock infoDictionary]).andReturn(mainDictionary);
expectedAnalyticsOptions = optionsDictionary;
- XCTAssertEqualObjects(options.analyticsOptionsDictionary, expectedAnalyticsOptions);
+ analyticsOptions = [options analyticsOptionsDictionaryWithInfoDictionary:mainDictionary];
+ XCTAssertEqualObjects(analyticsOptions, expectedAnalyticsOptions);
// Main overrides all the keys.
optionsDictionary = @{
@@ -296,9 +264,9 @@ extern NSString *const kFIRLibraryVersionID;
kFIRIsAnalyticsCollectionEnabled : @NO,
kFIRIsMeasurementEnabled : @NO
};
- OCMExpect([mainBundleMock infoDictionary]).andReturn(mainDictionary);
expectedAnalyticsOptions = mainDictionary;
- XCTAssertEqualObjects(options.analyticsOptionsDictionary, expectedAnalyticsOptions);
+ analyticsOptions = [options analyticsOptionsDictionaryWithInfoDictionary:mainDictionary];
+ XCTAssertEqualObjects(analyticsOptions, expectedAnalyticsOptions);
// Keys exist only in main.
optionsDictionary = @{};
@@ -308,9 +276,9 @@ extern NSString *const kFIRLibraryVersionID;
kFIRIsAnalyticsCollectionEnabled : @YES,
kFIRIsMeasurementEnabled : @YES
};
- OCMExpect([mainBundleMock infoDictionary]).andReturn(mainDictionary);
expectedAnalyticsOptions = mainDictionary;
- XCTAssertEqualObjects(options.analyticsOptionsDictionary, expectedAnalyticsOptions);
+ analyticsOptions = [options analyticsOptionsDictionaryWithInfoDictionary:mainDictionary];
+ XCTAssertEqualObjects(analyticsOptions, expectedAnalyticsOptions);
// Main overrides single keys.
optionsDictionary = @{
@@ -320,13 +288,13 @@ extern NSString *const kFIRLibraryVersionID;
};
options = [[FIROptions alloc] initInternalWithOptionsDictionary:optionsDictionary];
mainDictionary = @{ kFIRIsAnalyticsCollectionDeactivated : @NO };
- OCMExpect([mainBundleMock infoDictionary]).andReturn(mainDictionary);
expectedAnalyticsOptions = @{
kFIRIsAnalyticsCollectionDeactivated : @NO, // override
kFIRIsAnalyticsCollectionEnabled : @YES,
kFIRIsMeasurementEnabled : @YES
};
- XCTAssertEqualObjects(options.analyticsOptionsDictionary, expectedAnalyticsOptions);
+ analyticsOptions = [options analyticsOptionsDictionaryWithInfoDictionary:mainDictionary];
+ XCTAssertEqualObjects(analyticsOptions, expectedAnalyticsOptions);
optionsDictionary = @{
kFIRIsAnalyticsCollectionDeactivated : @YES,
@@ -335,13 +303,13 @@ extern NSString *const kFIRLibraryVersionID;
};
options = [[FIROptions alloc] initInternalWithOptionsDictionary:optionsDictionary];
mainDictionary = @{ kFIRIsAnalyticsCollectionEnabled : @NO };
- OCMExpect([mainBundleMock infoDictionary]).andReturn(mainDictionary);
expectedAnalyticsOptions = @{
kFIRIsAnalyticsCollectionDeactivated : @YES,
kFIRIsAnalyticsCollectionEnabled : @NO, // override
kFIRIsMeasurementEnabled : @YES
};
- XCTAssertEqualObjects(options.analyticsOptionsDictionary, expectedAnalyticsOptions);
+ analyticsOptions = [options analyticsOptionsDictionaryWithInfoDictionary:mainDictionary];
+ XCTAssertEqualObjects(analyticsOptions, expectedAnalyticsOptions);
optionsDictionary = @{
kFIRIsAnalyticsCollectionDeactivated : @YES,
@@ -350,18 +318,18 @@ extern NSString *const kFIRLibraryVersionID;
};
options = [[FIROptions alloc] initInternalWithOptionsDictionary:optionsDictionary];
mainDictionary = @{ kFIRIsMeasurementEnabled : @NO };
- OCMExpect([mainBundleMock infoDictionary]).andReturn(mainDictionary);
expectedAnalyticsOptions = @{
kFIRIsAnalyticsCollectionDeactivated : @YES,
kFIRIsAnalyticsCollectionEnabled : @YES,
kFIRIsMeasurementEnabled : @NO // override
};
- XCTAssertEqualObjects(options.analyticsOptionsDictionary, expectedAnalyticsOptions);
+ analyticsOptions = [options analyticsOptionsDictionaryWithInfoDictionary:mainDictionary];
+ XCTAssertEqualObjects(analyticsOptions, expectedAnalyticsOptions);
}
- (void)testAnalyticsOptions_combinatorial {
// Complete combinatorial test.
- id mainBundleMock = OCMPartialMock([NSBundle mainBundle]);
+
// Possible values for the flags in the plist, where NSNull means the flag is not present.
NSArray *values = @[ [NSNull null], @NO, @YES ];
@@ -390,6 +358,7 @@ extern NSString *const kFIRLibraryVersionID;
if (![optionsMeasurementEnabled isEqual:[NSNull null]]) {
optionsDictionary[kFIRIsMeasurementEnabled] = optionsMeasurementEnabled;
}
+
FIROptions *options =
[[FIROptions alloc] initInternalWithOptionsDictionary:optionsDictionary];
if (![uniqueOptionsCombinations containsObject:optionsDictionary]) {
@@ -407,7 +376,8 @@ extern NSString *const kFIRLibraryVersionID;
if (![mainMeasurementEnabled isEqual:[NSNull null]]) {
mainDictionary[kFIRIsMeasurementEnabled] = mainMeasurementEnabled;
}
- OCMExpect([mainBundleMock infoDictionary]).andReturn(mainDictionary);
+
+ // Add mainDictionary to uniqueMainCombinations if it isn't included yet.
if (![uniqueMainCombinations containsObject:mainDictionary]) {
[uniqueMainCombinations addObject:mainDictionary];
}
@@ -419,7 +389,10 @@ extern NSString *const kFIRLibraryVersionID;
NSMutableDictionary *expectedAnalyticsOptions =
[[NSMutableDictionary alloc] initWithDictionary:optionsDictionary];
[expectedAnalyticsOptions addEntriesFromDictionary:mainDictionary];
- XCTAssertEqualObjects(options.analyticsOptionsDictionary, expectedAnalyticsOptions);
+
+ NSDictionary *analyticsOptions =
+ [options analyticsOptionsDictionaryWithInfoDictionary:mainDictionary];
+ XCTAssertEqualObjects(analyticsOptions, expectedAnalyticsOptions);
combinationCount++;
}
diff --git a/Example/Core/Tests/FIRTestCase.m b/Example/Core/Tests/FIRTestCase.m
index 2c68b8d..631075f 100644
--- a/Example/Core/Tests/FIRTestCase.m
+++ b/Example/Core/Tests/FIRTestCase.m
@@ -14,12 +14,12 @@
#import "FIRTestCase.h"
+NSString *const kAndroidClientID = @"correct_android_client_id";
NSString *const kAPIKey = @"correct_api_key";
NSString *const kCustomizedAPIKey = @"customized_api_key";
NSString *const kClientID = @"correct_client_id";
NSString *const kTrackingID = @"correct_tracking_id";
NSString *const kGCMSenderID = @"correct_gcm_sender_id";
-NSString *const kAndroidClientID = @"correct_android_client_id";
NSString *const kGoogleAppID = @"1:123:ios:123abc";
NSString *const kDatabaseURL = @"https://abc-xyz-123.firebaseio.com";
NSString *const kStorageBucket = @"project-id-123.storage.firebase.com";
diff --git a/Example/Database/App/GoogleService-Info.plist b/Example/Database/App/GoogleService-Info.plist
index 89afffe..3f7547f 100644
--- a/Example/Database/App/GoogleService-Info.plist
+++ b/Example/Database/App/GoogleService-Info.plist
@@ -10,8 +10,6 @@
<string>correct_client_id</string>
<key>REVERSED_CLIENT_ID</key>
<string>correct_reversed_client_id</string>
- <key>ANDROID_CLIENT_ID</key>
- <string>correct_android_client_id</string>
<key>GOOGLE_APP_ID</key>
<string>1:123:ios:123abc</string>
<key>GCM_SENDER_ID</key>
diff --git a/Example/Database/App/tvOS/AppDelegate.h b/Example/Database/App/tvOS/AppDelegate.h
new file mode 100644
index 0000000..013891c
--- /dev/null
+++ b/Example/Database/App/tvOS/AppDelegate.h
@@ -0,0 +1,21 @@
+// Copyright 2017 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 <UIKit/UIKit.h>
+
+@interface AppDelegate : UIResponder <UIApplicationDelegate>
+
+@property(strong, nonatomic) UIWindow *window;
+
+@end
diff --git a/Example/Database/App/tvOS/AppDelegate.m b/Example/Database/App/tvOS/AppDelegate.m
new file mode 100644
index 0000000..2e4e32f
--- /dev/null
+++ b/Example/Database/App/tvOS/AppDelegate.m
@@ -0,0 +1,59 @@
+// Copyright 2017 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 "AppDelegate.h"
+
+@interface AppDelegate ()
+
+@end
+
+@implementation AppDelegate
+
+- (BOOL)application:(UIApplication *)application
+ didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
+ // Override point for customization after application launch.
+ return YES;
+}
+
+- (void)applicationWillResignActive:(UIApplication *)application {
+ // Sent when the application is about to move from active to inactive state. This can occur for
+ // certain types of temporary interruptions (such as an incoming phone call or SMS message) or
+ // when the user quits the application and it begins the transition to the background state. Use
+ // this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates.
+ // Games should use this method to pause the game.
+}
+
+- (void)applicationDidEnterBackground:(UIApplication *)application {
+ // Use this method to release shared resources, save user data, invalidate timers, and store
+ // enough application state information to restore your application to its current state in case
+ // it is terminated later. If your application supports background execution, this method is
+ // called instead of applicationWillTerminate: when the user quits.
+}
+
+- (void)applicationWillEnterForeground:(UIApplication *)application {
+ // Called as part of the transition from the background to the active state; here you can undo
+ // many of the changes made on entering the background.
+}
+
+- (void)applicationDidBecomeActive:(UIApplication *)application {
+ // Restart any tasks that were paused (or not yet started) while the application was inactive. If
+ // the application was previously in the background, optionally refresh the user interface.
+}
+
+- (void)applicationWillTerminate:(UIApplication *)application {
+ // Called when the application is about to terminate. Save data if appropriate. See also
+ // applicationDidEnterBackground:.
+}
+
+@end
diff --git a/Example/Database/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json b/Example/Database/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json
new file mode 100644
index 0000000..7f06667
--- /dev/null
+++ b/Example/Database/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json
@@ -0,0 +1,11 @@
+{
+ "images" : [
+ {
+ "idiom" : "tv"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
diff --git a/Example/Database/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json b/Example/Database/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json
new file mode 100644
index 0000000..da4a164
--- /dev/null
+++ b/Example/Database/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/Database/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json b/Example/Database/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json
new file mode 100644
index 0000000..d29f024
--- /dev/null
+++ b/Example/Database/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json
@@ -0,0 +1,17 @@
+{
+ "layers" : [
+ {
+ "filename" : "Front.imagestacklayer"
+ },
+ {
+ "filename" : "Middle.imagestacklayer"
+ },
+ {
+ "filename" : "Back.imagestacklayer"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/Database/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json b/Example/Database/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json
new file mode 100644
index 0000000..7f06667
--- /dev/null
+++ b/Example/Database/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json
@@ -0,0 +1,11 @@
+{
+ "images" : [
+ {
+ "idiom" : "tv"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
diff --git a/Example/Database/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json b/Example/Database/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json
new file mode 100644
index 0000000..da4a164
--- /dev/null
+++ b/Example/Database/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/Database/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json b/Example/Database/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json
new file mode 100644
index 0000000..7f06667
--- /dev/null
+++ b/Example/Database/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json
@@ -0,0 +1,11 @@
+{
+ "images" : [
+ {
+ "idiom" : "tv"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
diff --git a/Example/Database/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json b/Example/Database/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json
new file mode 100644
index 0000000..da4a164
--- /dev/null
+++ b/Example/Database/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/Database/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json b/Example/Database/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json
new file mode 100644
index 0000000..16a370d
--- /dev/null
+++ b/Example/Database/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images" : [
+ {
+ "idiom" : "tv",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "tv",
+ "scale" : "2x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/Database/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json b/Example/Database/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json
new file mode 100644
index 0000000..da4a164
--- /dev/null
+++ b/Example/Database/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/Database/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json b/Example/Database/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json
new file mode 100644
index 0000000..d29f024
--- /dev/null
+++ b/Example/Database/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json
@@ -0,0 +1,17 @@
+{
+ "layers" : [
+ {
+ "filename" : "Front.imagestacklayer"
+ },
+ {
+ "filename" : "Middle.imagestacklayer"
+ },
+ {
+ "filename" : "Back.imagestacklayer"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/Database/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json b/Example/Database/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json
new file mode 100644
index 0000000..16a370d
--- /dev/null
+++ b/Example/Database/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images" : [
+ {
+ "idiom" : "tv",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "tv",
+ "scale" : "2x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/Database/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json b/Example/Database/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json
new file mode 100644
index 0000000..da4a164
--- /dev/null
+++ b/Example/Database/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/Database/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json b/Example/Database/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json
new file mode 100644
index 0000000..16a370d
--- /dev/null
+++ b/Example/Database/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images" : [
+ {
+ "idiom" : "tv",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "tv",
+ "scale" : "2x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/Database/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json b/Example/Database/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json
new file mode 100644
index 0000000..da4a164
--- /dev/null
+++ b/Example/Database/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/Database/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json b/Example/Database/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json
new file mode 100644
index 0000000..b03ded1
--- /dev/null
+++ b/Example/Database/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json
@@ -0,0 +1,32 @@
+{
+ "assets" : [
+ {
+ "size" : "1280x768",
+ "idiom" : "tv",
+ "filename" : "App Icon - App Store.imagestack",
+ "role" : "primary-app-icon"
+ },
+ {
+ "size" : "400x240",
+ "idiom" : "tv",
+ "filename" : "App Icon.imagestack",
+ "role" : "primary-app-icon"
+ },
+ {
+ "size" : "2320x720",
+ "idiom" : "tv",
+ "filename" : "Top Shelf Image Wide.imageset",
+ "role" : "top-shelf-image-wide"
+ },
+ {
+ "size" : "1920x720",
+ "idiom" : "tv",
+ "filename" : "Top Shelf Image.imageset",
+ "role" : "top-shelf-image"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
diff --git a/Example/Database/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json b/Example/Database/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json
new file mode 100644
index 0000000..16a370d
--- /dev/null
+++ b/Example/Database/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images" : [
+ {
+ "idiom" : "tv",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "tv",
+ "scale" : "2x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/Database/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json b/Example/Database/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json
new file mode 100644
index 0000000..16a370d
--- /dev/null
+++ b/Example/Database/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images" : [
+ {
+ "idiom" : "tv",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "tv",
+ "scale" : "2x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/Database/App/tvOS/Assets.xcassets/Contents.json b/Example/Database/App/tvOS/Assets.xcassets/Contents.json
new file mode 100644
index 0000000..da4a164
--- /dev/null
+++ b/Example/Database/App/tvOS/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/Database/App/tvOS/Assets.xcassets/LaunchImage.launchimage/Contents.json b/Example/Database/App/tvOS/Assets.xcassets/LaunchImage.launchimage/Contents.json
new file mode 100644
index 0000000..d746a60
--- /dev/null
+++ b/Example/Database/App/tvOS/Assets.xcassets/LaunchImage.launchimage/Contents.json
@@ -0,0 +1,22 @@
+{
+ "images" : [
+ {
+ "orientation" : "landscape",
+ "idiom" : "tv",
+ "extent" : "full-screen",
+ "minimum-system-version" : "11.0",
+ "scale" : "2x"
+ },
+ {
+ "orientation" : "landscape",
+ "idiom" : "tv",
+ "extent" : "full-screen",
+ "minimum-system-version" : "9.0",
+ "scale" : "1x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/Database/App/tvOS/Info.plist b/Example/Database/App/tvOS/Info.plist
new file mode 100644
index 0000000..02942a3
--- /dev/null
+++ b/Example/Database/App/tvOS/Info.plist
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>$(DEVELOPMENT_LANGUAGE)</string>
+ <key>CFBundleExecutable</key>
+ <string>$(EXECUTABLE_NAME)</string>
+ <key>CFBundleIdentifier</key>
+ <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>$(PRODUCT_NAME)</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>1.0</string>
+ <key>CFBundleVersion</key>
+ <string>1</string>
+ <key>LSRequiresIPhoneOS</key>
+ <true/>
+ <key>UIMainStoryboardFile</key>
+ <string>Main</string>
+ <key>UIRequiredDeviceCapabilities</key>
+ <array>
+ <string>arm64</string>
+ </array>
+ <key>UIUserInterfaceStyle</key>
+ <string>Automatic</string>
+</dict>
+</plist>
diff --git a/Example/Database/App/tvOS/Main.storyboard b/Example/Database/App/tvOS/Main.storyboard
new file mode 100644
index 0000000..72d5e22
--- /dev/null
+++ b/Example/Database/App/tvOS/Main.storyboard
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder.AppleTV.Storyboard" version="3.0" toolsVersion="13122.16" systemVersion="17A278a" targetRuntime="AppleTV" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
+ <dependencies>
+ <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
+ <capability name="Safe area layout guides" minToolsVersion="9.0"/>
+ <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+ </dependencies>
+ <scenes>
+ <!--View Controller-->
+ <scene sceneID="tne-QT-ifu">
+ <objects>
+ <viewController id="BYZ-38-t0r" customClass="ViewController" customModuleProvider="" sceneMemberID="viewController">
+ <layoutGuides>
+ <viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
+ <viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
+ </layoutGuides>
+ <view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
+ <rect key="frame" x="0.0" y="0.0" width="1920" height="1080"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+ <color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
+ <viewLayoutGuide key="safeArea" id="wu6-TO-1qx"/>
+ </view>
+ </viewController>
+ <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
+ </objects>
+ </scene>
+ </scenes>
+</document>
diff --git a/Example/Database/App/tvOS/ViewController.h b/Example/Database/App/tvOS/ViewController.h
new file mode 100644
index 0000000..b6115b8
--- /dev/null
+++ b/Example/Database/App/tvOS/ViewController.h
@@ -0,0 +1,19 @@
+// Copyright 2017 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 <UIKit/UIKit.h>
+
+@interface ViewController : UIViewController
+
+@end
diff --git a/Example/Database/App/tvOS/ViewController.m b/Example/Database/App/tvOS/ViewController.m
new file mode 100644
index 0000000..6d4676b
--- /dev/null
+++ b/Example/Database/App/tvOS/ViewController.m
@@ -0,0 +1,33 @@
+// Copyright 2017 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 "ViewController.h"
+
+@interface ViewController ()
+
+@end
+
+@implementation ViewController
+
+- (void)viewDidLoad {
+ [super viewDidLoad];
+ // Do any additional setup after loading the view, typically from a nib.
+}
+
+- (void)didReceiveMemoryWarning {
+ [super didReceiveMemoryWarning];
+ // Dispose of any resources that can be recreated.
+}
+
+@end
diff --git a/Example/Database/App/tvOS/main.m b/Example/Database/App/tvOS/main.m
new file mode 100644
index 0000000..d9e6654
--- /dev/null
+++ b/Example/Database/App/tvOS/main.m
@@ -0,0 +1,22 @@
+// Copyright 2017 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 <UIKit/UIKit.h>
+#import "AppDelegate.h"
+
+int main(int argc, char* argv[]) {
+ @autoreleasepool {
+ return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
+ }
+}
diff --git a/Example/Database/Tests/Integration/FData.m b/Example/Database/Tests/Integration/FData.m
index aef15e1..d036f77 100644
--- a/Example/Database/Tests/Integration/FData.m
+++ b/Example/Database/Tests/Integration/FData.m
@@ -487,7 +487,7 @@
[[ref child:@"100003354884401"] setValue:@"alpha"];
__block BOOL ready = NO;
- [ref observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
+ [ref observeSingleEventOfType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
id val = [snapshot value];
XCTAssertTrue([val isKindOfClass:[NSDictionary class]], @"Expected a dictionary.");
ready = YES;
@@ -678,6 +678,8 @@
[self waitUntil:^BOOL{
return setDone && calls == 1;
}];
+
+ [node removeAllObservers];
}
- (void) testHasChildrenWorksCorrectly {
@@ -878,6 +880,7 @@
[self waitUntil:^BOOL{
return calls == 1;
}];
+ [reader removeAllObservers];
}
- (void) testSetPriorityOnNonexistentNodeFails {
@@ -2208,6 +2211,7 @@
}];
WAIT_FOR(done);
+ [deleter removeAllObservers];
}
- (void) testParentDeleteShadowsChildListenersWithNonDefaultQuery {
diff --git a/Example/Database/Tests/Integration/FRealtime.m b/Example/Database/Tests/Integration/FRealtime.m
index 5c7d186..5acda07 100644
--- a/Example/Database/Tests/Integration/FRealtime.m
+++ b/Example/Database/Tests/Integration/FRealtime.m
@@ -481,6 +481,7 @@
WAIT_FOR(count == 2);
// cleanup
+ [reader removeAllObservers];
[FRepoManager disposeRepos:writerCfg];
}
diff --git a/Example/Database/Tests/Unit/FSyncPointTests.m b/Example/Database/Tests/Unit/FSyncPointTests.m
index 797a5aa..de4680f 100644
--- a/Example/Database/Tests/Unit/FSyncPointTests.m
+++ b/Example/Database/Tests/Unit/FSyncPointTests.m
@@ -86,15 +86,15 @@ typedef NSDictionary* (^fbt_nsdictionary_void)(void);
}
- (void) fireEvent:(id<FEvent>)event queue:(dispatch_queue_t)queue {
- [NSException raise:@"NotImplementedError" format:@"Method not implemneted."];
+ [NSException raise:@"NotImplementedError" format:@"Method not implemented."];
}
- (FCancelEvent *) createCancelEventFromError:(NSError *)error path:(FPath *)path {
- [NSException raise:@"NotImplementedError" format:@"Method not implemneted."];
+ [NSException raise:@"NotImplementedError" format:@"Method not implemented."];
return nil;
}
- (FIRDatabaseHandle) handle {
- [NSException raise:@"NotImplementedError" format:@"Method not implemneted."];
+ [NSException raise:@"NotImplementedError" format:@"Method not implemented."];
return 0;
}
@end
diff --git a/Example/Firebase.xcodeproj/project.pbxproj b/Example/Firebase.xcodeproj/project.pbxproj
index 489c588..9fa954c 100644
--- a/Example/Firebase.xcodeproj/project.pbxproj
+++ b/Example/Firebase.xcodeproj/project.pbxproj
@@ -49,10 +49,22 @@
name = AllUnitTests_iOS;
productName = AllTests;
};
+ DE545C7F1FBCA3F000C637AE /* AllUnitTests_tvOS */ = {
+ isa = PBXAggregateTarget;
+ buildConfigurationList = DE545C821FBCA3F000C637AE /* Build configuration list for PBXAggregateTarget "AllUnitTests_tvOS" */;
+ buildPhases = (
+ );
+ dependencies = (
+ DE545C881FBCA43200C637AE /* PBXTargetDependency */,
+ DE545C861FBCA42C00C637AE /* PBXTargetDependency */,
+ DE545C841FBCA41C00C637AE /* PBXTargetDependency */,
+ );
+ name = AllUnitTests_tvOS;
+ productName = AllUnitTests_tvOS;
+ };
/* End PBXAggregateTarget section */
/* Begin PBXBuildFile section */
- 01E863BD40D23087B77F2F03 /* Pods_Storage_Tests_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A8E6527440C118AC7C21D504 /* Pods_Storage_Tests_iOS.framework */; };
0624F3EB1EC0ED0800E5940D /* FConnectionTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 063CB46F1EBA7AEF00038A59 /* FConnectionTest.m */; };
0624F3EC1EC0ED1B00E5940D /* FData.m in Sources */ = {isa = PBXBuildFile; fileRef = 063CB4711EBA7AEF00038A59 /* FData.m */; };
0624F3ED1EC0ED2300E5940D /* FDotInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 063CB4731EBA7AEF00038A59 /* FDotInfo.m */; };
@@ -79,7 +91,6 @@
0637BA711EC0F9DD00CAEFD4 /* FTestHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = DE7B8D891E8EF202009EB6DF /* FTestHelpers.m */; };
0637BA721EC0F9E000CAEFD4 /* FTupleEventTypeString.m in Sources */ = {isa = PBXBuildFile; fileRef = DE7B8D8B1E8EF203009EB6DF /* FTupleEventTypeString.m */; };
0637BA731EC0F9E400CAEFD4 /* SenTest+FWaiter.m in Sources */ = {isa = PBXBuildFile; fileRef = DE7B8D8D1E8EF203009EB6DF /* SenTest+FWaiter.m */; };
- 063825B4D58274CB24B25FF1 /* Pods_Storage_Example_macOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8F27F07800D6FD739BD094D3 /* Pods_Storage_Example_macOS.framework */; };
063CB4A71EBA7B0B00038A59 /* FCompoundWriteTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 063CB46E1EBA7AEF00038A59 /* FCompoundWriteTest.m */; };
063CB4BE1EBA7B3100038A59 /* FIRDataSnapshotTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 063CB47B1EBA7AEF00038A59 /* FIRDataSnapshotTests.m */; };
063CB4BF1EBA7B3100038A59 /* FIRFakeApp.m in Sources */ = {isa = PBXBuildFile; fileRef = 063CB47E1EBA7AEF00038A59 /* FIRFakeApp.m */; };
@@ -105,45 +116,20 @@
0672F2F31EBBA7D900818E87 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 0672F2F11EBBA7D900818E87 /* GoogleService-Info.plist */; };
069428831EC3B38C00F7BC69 /* 1mb.dat in Resources */ = {isa = PBXBuildFile; fileRef = 069428801EC3B35A00F7BC69 /* 1mb.dat */; };
06C24A061EC39BCB005208CA /* FIRStorageIntegrationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 06121ECA1EC39A0B0008D70E /* FIRStorageIntegrationTests.m */; };
- 0C1D425E4DA4FBFD5A08B985 /* Pods_Auth_Sample.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D837E35351C0B844EA64B959 /* Pods_Auth_Sample.framework */; };
- 1A509E710C83B6D0A6CD286D /* Pods_Core_Example_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 68657030253DE5895E124F45 /* Pods_Core_Example_iOS.framework */; };
- 29136BCA0AD59481B07CFBB0 /* Pods_Core_Tests_macOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 86062A14985F49A3B99614A7 /* Pods_Core_Tests_macOS.framework */; };
- 2C8B39CFF9898AE4B29AD114 /* Pods_Auth_Tests_macOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1015568683E2DFCC2719C754 /* Pods_Auth_Tests_macOS.framework */; };
- 2ECC6F80E47D2646FA82B940 /* Pods_Messaging_Tests_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6CCB3CEB7BF2E4994FBDB2E7 /* Pods_Messaging_Tests_iOS.framework */; };
- 431EBDD6071EF1AE6F6DBE5F /* Pods_Core_Example_macOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84C51650773603D9F827CB3F /* Pods_Core_Example_macOS.framework */; };
- 5207C8D12B9830DADB85FE67 /* Pods_Auth_Tests_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0ABC4448E5917769098A4EEF /* Pods_Auth_Tests_iOS.framework */; };
- 529BBEFBB6D7A3653B6B3874 /* Pods_Storage_Tests_macOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 910D7A5DE7D5AF153328D243 /* Pods_Storage_Tests_macOS.framework */; };
- 53C9FD19788281F65D537548 /* Pods_Analytics_Tests_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 29606A0F0B0782779D4E73A1 /* Pods_Analytics_Tests_iOS.framework */; };
- 6232ED3272E9C78C2A0E127F /* Pods_Storage_IntegrationTests_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 979DF124E3D1146A81188F78 /* Pods_Storage_IntegrationTests_iOS.framework */; };
- 67EA2F675D33B39CEB0D41B1 /* Pods_Auth_Example_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4373FD322206E44A7CADC2A8 /* Pods_Auth_Example_iOS.framework */; };
- 6ADAC4BEBCE37253D2D7A50F /* Pods_Database_Tests_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4852BF989C85671F5D7EBD2A /* Pods_Database_Tests_iOS.framework */; };
- 6D6FA69218AB107C266E1B70 /* Pods_Auth_Example_macOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F7A35264721D3C8D9F96F91C /* Pods_Auth_Example_macOS.framework */; };
- 7CA435AB1A753CC9EEDFA648 /* Pods_Database_Tests_macOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DE9C3D75207E5D1DC70BB364 /* Pods_Database_Tests_macOS.framework */; };
7E9485421F578AC4005A3939 /* FIRAuthURLPresenterTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 7E94853F1F578A9D005A3939 /* FIRAuthURLPresenterTests.m */; };
+ 7EE21F7A1FE89193009B1370 /* FIREmailLinkRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 7EE21F791FE89193009B1370 /* FIREmailLinkRequestTests.m */; };
+ 7EE21F7C1FE8919E009B1370 /* FIREmailLinkSignInResponseTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 7EE21F7B1FE8919D009B1370 /* FIREmailLinkSignInResponseTests.m */; };
7EFA2E041F71C93300DD354F /* FIRUserMetadataTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 7EFA2E031F71C93300DD354F /* FIRUserMetadataTests.m */; };
- 825BE4C9299DAB7EFEB19B65 /* Pods_Database_Example_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 66544F771D53687DD73F8BE8 /* Pods_Database_Example_iOS.framework */; };
- 8313C96352C6C77D481B5937 /* Pods_Messaging_Example_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B0C1478ED7269FF2107F5B6F /* Pods_Messaging_Example_iOS.framework */; };
- 8FC590E7FF7C561991DC9DD6 /* Pods_Auth_ApiTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 93491C87A390AB737849677E /* Pods_Auth_ApiTests.framework */; };
- 91BECF25F64620DEDF99A106 /* Pods_Storage_IntegrationTests_macOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6A28B39B3D707677EF59C110 /* Pods_Storage_IntegrationTests_macOS.framework */; };
- 930570CF84207AEB98440760 /* Pods_Auth_SwiftSample.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B0C52364C5FCE5D3FBAEAF83 /* Pods_Auth_SwiftSample.framework */; };
- 9CD1CAC2BC2C39B755F7BF88 /* Pods_Storage_Example_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3742438A60FCFAAF24CDE751 /* Pods_Storage_Example_iOS.framework */; };
- A553FD534066F62EE74F2D51 /* Pods_Database_Example_macOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7CF25C495716B3441849720B /* Pods_Database_Example_macOS.framework */; };
- ABA730C3E77B260C564C288A /* Pods_Core_Tests_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B9B568851BEE22B7FCB61FB /* Pods_Core_Tests_iOS.framework */; };
+ 923F824C206C4D8000034974 /* SafariServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 923F824B206C4D8000034974 /* SafariServices.framework */; };
+ 923F824E206C4D8B00034974 /* SafariServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 923F824D206C4D8B00034974 /* SafariServices.framework */; };
+ 923F824F206C4DA500034974 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DE45C6641E7DA8CB009E6ACD /* XCTest.framework */; };
+ 923F8251206C4DC600034974 /* UserNotifications.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 923F8250206C4DC500034974 /* UserNotifications.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
+ 923F8252206C4DD500034974 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F591195388D20070C39A /* UIKit.framework */; };
AFAF36F51EC28C25004BDEE5 /* Shared.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AFAF36F41EC28C25004BDEE5 /* Shared.xcassets */; };
AFAF36F61EC28C25004BDEE5 /* Shared.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AFAF36F41EC28C25004BDEE5 /* Shared.xcassets */; };
AFAF36F71EC28C25004BDEE5 /* Shared.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AFAF36F41EC28C25004BDEE5 /* Shared.xcassets */; };
AFAF36F81EC28C25004BDEE5 /* Shared.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AFAF36F41EC28C25004BDEE5 /* Shared.xcassets */; };
AFAF36F91EC28C25004BDEE5 /* Shared.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AFAF36F41EC28C25004BDEE5 /* Shared.xcassets */; };
- AFC8BA9D1EBD230E00B8EEAE /* NotificationsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFC8BA9C1EBD230E00B8EEAE /* NotificationsController.swift */; };
- AFC8BA9F1EBD51A700B8EEAE /* Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFC8BA9E1EBD51A700B8EEAE /* Environment.swift */; };
- AFC8BAA71EC257D800B8EEAE /* FIRSampleAppUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = AFC8BAA31EC257D800B8EEAE /* FIRSampleAppUtilities.m */; };
- AFD5630E1EB1402300EA2233 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFD562FF1EB13DF200EA2233 /* AppDelegate.swift */; };
- AFD5630F1EB1402300EA2233 /* MessagingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFD563011EB13DF200EA2233 /* MessagingViewController.swift */; };
- AFD563151EB29EDE00EA2233 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = AFD563131EB1466100EA2233 /* GoogleService-Info.plist */; };
- AFD563171EBBEF7B00EA2233 /* Data+MessagingExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFD563161EBBEF7B00EA2233 /* Data+MessagingExtensions.swift */; };
- CD4DB22A28941C5FEE70686A /* Pods_Auth_EarlGreyTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BDE126A5F0AFCA9956A4C5D9 /* Pods_Auth_EarlGreyTests.framework */; };
- D018534D1EDACED4003A645C /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D01853491EDACED4003A645C /* LaunchScreen.storyboard */; };
- D018534E1EDACED4003A645C /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D018534B1EDACED4003A645C /* Main.storyboard */; };
D01853721EDAD084003A645C /* Shared.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AFAF36F41EC28C25004BDEE5 /* Shared.xcassets */; };
D01853831EDAD113003A645C /* FIRAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = D018537E1EDAD0E6003A645C /* FIRAppDelegate.m */; };
D01853841EDAD113003A645C /* FIRViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = D01853801EDAD0E6003A645C /* FIRViewController.m */; };
@@ -199,6 +185,8 @@
D064E6B51ED9B31C001956DF /* FIRTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = DEE14D7C1E844677006FA992 /* FIRTestCase.m */; };
D067EF831ED9BDE00095C27F /* Shared.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AFAF36F41EC28C25004BDEE5 /* Shared.xcassets */; };
D067EF841ED9BDFF0095C27F /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = DEE14D711E844677006FA992 /* GoogleService-Info.plist */; };
+ D09005371EDB331C00154410 /* OCMock-iOS/OCMock.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = D09005301EDB32D600154410 /* OCMock-iOS/OCMock.framework */; settings = {ATTRIBUTES = (); }; };
+ D090053D1EDB334D00154410 /* OCMock-iOS/OCMock.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = D09005301EDB32D600154410 /* OCMock-iOS/OCMock.framework */; settings = {ATTRIBUTES = (); }; };
D0EDB2C51EDA04F800B6C31B /* Shared.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AFAF36F41EC28C25004BDEE5 /* Shared.xcassets */; };
D0EDB2D71EDA057800B6C31B /* FIRAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = D0EDB2D21EDA056A00B6C31B /* FIRAppDelegate.m */; };
D0EDB2D81EDA057800B6C31B /* FIRViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = D0EDB2D41EDA056A00B6C31B /* FIRViewController.m */; };
@@ -288,11 +276,22 @@
D9B0D41F1F578F6E00A567C2 /* FIRGetProjectConfigRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D92C82C61F578DF000D5EAFF /* FIRGetProjectConfigRequestTests.m */; };
D9B0D4201F578F7200A567C2 /* FIRGetProjectConfigResponseTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D92C82C51F578DF000D5EAFF /* FIRGetProjectConfigResponseTests.m */; };
D9B0D4211F578F7300A567C2 /* FIRGetProjectConfigResponseTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D92C82C51F578DF000D5EAFF /* FIRGetProjectConfigResponseTests.m */; };
- DA464BCB6574F7FE299CB48D /* Pods_Database_IntegrationTests_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DFC35EAB61A983056BE25D28 /* Pods_Database_IntegrationTests_iOS.framework */; };
DE0E5BBB1EA7D92E00FAA825 /* FIRVerifyClientRequestTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DE0E5BB91EA7D92E00FAA825 /* FIRVerifyClientRequestTest.m */; };
DE0E5BBC1EA7D92E00FAA825 /* FIRVerifyClientResponseTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE0E5BBA1EA7D92E00FAA825 /* FIRVerifyClientResponseTests.m */; };
DE0E5BBD1EA7D93100FAA825 /* FIRAuthAppCredentialTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE0E5BB51EA7D91C00FAA825 /* FIRAuthAppCredentialTests.m */; };
DE0E5BBE1EA7D93500FAA825 /* FIRAuthAppDelegateProxyTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE0E5BB61EA7D91C00FAA825 /* FIRAuthAppDelegateProxyTests.m */; };
+ DE1EC2921FBA5EB5007D18D8 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = DE1EC28B1FBA5EB5007D18D8 /* AppDelegate.m */; };
+ DE1EC2931FBA5EB5007D18D8 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DE1EC28C1FBA5EB5007D18D8 /* Assets.xcassets */; };
+ DE1EC2951FBA5EB5007D18D8 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = DE1EC28E1FBA5EB5007D18D8 /* main.m */; };
+ DE1EC2961FBA5EB5007D18D8 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DE1EC28F1FBA5EB5007D18D8 /* Main.storyboard */; };
+ DE1EC2971FBA5EB5007D18D8 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DE1EC2911FBA5EB5007D18D8 /* ViewController.m */; };
+ DE1FAEB11FBCF60D00897AAA /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = DE53894D1FBB635400199FC2 /* AppDelegate.m */; };
+ DE1FAEB21FBCF60D00897AAA /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = DE5389501FBB635400199FC2 /* main.m */; };
+ DE1FAEB31FBCF60D00897AAA /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DE5389531FBB635400199FC2 /* ViewController.m */; };
+ DE1FAEB41FBCF60D00897AAA /* Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = DE53894F1FBB635400199FC2 /* Info.plist */; };
+ DE1FAEB51FBCF60D00897AAA /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DE5389511FBB635400199FC2 /* Main.storyboard */; };
+ DE1FAEB61FBCF60D00897AAA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DE53894E1FBB635400199FC2 /* Assets.xcassets */; };
+ DE1FAEB71FBCF6CA00897AAA /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = DE9314F61E86C6FF0083EDBF /* GoogleService-Info.plist */; };
DE26D20E1F70333E004AE1D3 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = DE26D1DA1F70333E004AE1D3 /* Localizable.strings */; };
DE26D2131F70333E004AE1D3 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DE26D1E41F70333E004AE1D3 /* Images.xcassets */; };
DE26D2441F7039BC004AE1D3 /* ApplicationDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = DE26D1D01F70333E004AE1D3 /* ApplicationDelegate.m */; };
@@ -324,6 +323,22 @@
DE26D2931F705F4D004AE1D3 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE26D2071F70333E004AE1D3 /* ViewController.swift */; };
DE26D2941F705F51004AE1D3 /* AuthCredentials.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE26D1FE1F70333E004AE1D3 /* AuthCredentials.swift */; };
DE26D2951F705F53004AE1D3 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE26D1FD1F70333E004AE1D3 /* AppDelegate.swift */; };
+ DE47C0E2207AC87D00B1AEDF /* FIRSampleAppUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = AFC8BAA31EC257D800B8EEAE /* FIRSampleAppUtilities.m */; };
+ DE47C0E7207AC87D00B1AEDF /* Shared.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AFAF36F41EC28C25004BDEE5 /* Shared.xcassets */; };
+ DE47C114207AC94A00B1AEDF /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = DE47C107207AC94A00B1AEDF /* GoogleService-Info.plist */; };
+ DE47C115207AC94A00B1AEDF /* Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE47C109207AC94A00B1AEDF /* Environment.swift */; };
+ DE47C116207AC94A00B1AEDF /* MessagingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE47C10A207AC94A00B1AEDF /* MessagingViewController.swift */; };
+ DE47C117207AC94A00B1AEDF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DE47C10C207AC94A00B1AEDF /* LaunchScreen.storyboard */; };
+ DE47C118207AC94A00B1AEDF /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DE47C10E207AC94A00B1AEDF /* Main.storyboard */; };
+ DE47C119207AC94A00B1AEDF /* Messaging-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = DE47C110207AC94A00B1AEDF /* Messaging-Info.plist */; };
+ DE47C11A207AC94A00B1AEDF /* Data+MessagingExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE47C111207AC94A00B1AEDF /* Data+MessagingExtensions.swift */; };
+ DE47C11B207AC94A00B1AEDF /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE47C112207AC94A00B1AEDF /* AppDelegate.swift */; };
+ DE47C11C207AC94A00B1AEDF /* NotificationsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE47C113207AC94A00B1AEDF /* NotificationsController.swift */; };
+ DE47C13F207ACAA900B1AEDF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DE47C136207ACAA900B1AEDF /* LaunchScreen.storyboard */; };
+ DE47C140207ACAA900B1AEDF /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DE47C138207ACAA900B1AEDF /* Main.storyboard */; };
+ DE47C142207ACAA900B1AEDF /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = DE47C13B207ACAA900B1AEDF /* main.m */; };
+ DE47C143207ACAA900B1AEDF /* FIRAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = DE47C13C207ACAA900B1AEDF /* FIRAppDelegate.m */; };
+ DE47C144207ACAA900B1AEDF /* FIRViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DE47C13D207ACAA900B1AEDF /* FIRViewController.m */; };
DE750DBD1EB3DD5B00A75E47 /* FIRAuthAPNSTokenTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE750DB61EB3DD4000A75E47 /* FIRAuthAPNSTokenTests.m */; };
DE750DBE1EB3DD6800A75E47 /* FIRAuthAPNSTokenManagerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE750DB51EB3DD4000A75E47 /* FIRAuthAPNSTokenManagerTests.m */; };
DE750DBF1EB3DD6C00A75E47 /* FIRAuthAppCredentialManagerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE750DB71EB3DD4000A75E47 /* FIRAuthAppCredentialManagerTests.m */; };
@@ -346,6 +361,40 @@
DE7B8DCC1E8EF23A009EB6DF /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = DE7B8D371E8EF202009EB6DF /* main.m */; };
DE7B8DD01E8EF246009EB6DF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DE7B8D2C1E8EF202009EB6DF /* LaunchScreen.storyboard */; };
DE7B8DD11E8EF24F009EB6DF /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DE7B8D2E1E8EF202009EB6DF /* Main.storyboard */; };
+ DE9037291FBA5F2400E239D3 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 0672F2F11EBBA7D900818E87 /* GoogleService-Info.plist */; };
+ DE90372A1FBA5F8F00E239D3 /* FArraySortedDictionaryTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 063CB4471EBA7AE200038A59 /* FArraySortedDictionaryTest.m */; };
+ DE90372B1FBA5F8F00E239D3 /* FCompoundHashTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 063CB4481EBA7AE200038A59 /* FCompoundHashTest.m */; };
+ DE90372C1FBA5F8F00E239D3 /* FCompoundWriteTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 063CB46E1EBA7AEF00038A59 /* FCompoundWriteTest.m */; };
+ DE90372D1FBA5F8F00E239D3 /* FIRDataSnapshotTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 063CB47B1EBA7AEF00038A59 /* FIRDataSnapshotTests.m */; };
+ DE90372E1FBA5F8F00E239D3 /* FIRMutableDataTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 063CB44A1EBA7AE200038A59 /* FIRMutableDataTests.m */; };
+ DE90372F1FBA5F8F00E239D3 /* FLevelDBStorageEngineTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 063CB44B1EBA7AE200038A59 /* FLevelDBStorageEngineTests.m */; };
+ DE9037301FBA5F8F00E239D3 /* FNodeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 063CB44C1EBA7AE200038A59 /* FNodeTests.m */; };
+ DE9037311FBA5F8F00E239D3 /* FPathTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 063CB44E1EBA7AE200038A59 /* FPathTests.m */; };
+ DE9037321FBA5F8F00E239D3 /* FPersistenceManagerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 063CB44F1EBA7AE200038A59 /* FPersistenceManagerTest.m */; };
+ DE9037331FBA5F8F00E239D3 /* FPruneForestTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 063CB4501EBA7AE200038A59 /* FPruneForestTest.m */; };
+ DE9037341FBA5F8F00E239D3 /* FPruningTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 063CB4511EBA7AE200038A59 /* FPruningTest.m */; };
+ DE9037351FBA5F8F00E239D3 /* FQueryParamsTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 063CB4521EBA7AE200038A59 /* FQueryParamsTest.m */; };
+ DE9037361FBA5F8F00E239D3 /* FRangeMergeTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 063CB4531EBA7AE200038A59 /* FRangeMergeTest.m */; };
+ DE9037371FBA5F8F00E239D3 /* FRepoInfoTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 063CB4541EBA7AE200038A59 /* FRepoInfoTest.m */; };
+ DE9037381FBA5F8F00E239D3 /* FSparseSnapshotTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 063CB4561EBA7AE200038A59 /* FSparseSnapshotTests.m */; };
+ DE9037391FBA5F8F00E239D3 /* FSyncPointTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 063CB4581EBA7AE200038A59 /* FSyncPointTests.m */; };
+ DE90373A1FBA5F8F00E239D3 /* FTrackedQueryManagerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 063CB45B1EBA7AE200038A59 /* FTrackedQueryManagerTest.m */; };
+ DE90373B1FBA5F8F00E239D3 /* FTreeSortedDictionaryTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 063CB4901EBA7AEF00038A59 /* FTreeSortedDictionaryTests.m */; };
+ DE90373C1FBA5F8F00E239D3 /* FUtilitiesTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 063CB45C1EBA7AE200038A59 /* FUtilitiesTest.m */; };
+ DE90373F1FBA675D00E239D3 /* FDevice.m in Sources */ = {isa = PBXBuildFile; fileRef = DE7B8D791E8EF202009EB6DF /* FDevice.m */; };
+ DE9037401FBA675D00E239D3 /* FEventTester.m in Sources */ = {isa = PBXBuildFile; fileRef = DE7B8D7B1E8EF202009EB6DF /* FEventTester.m */; };
+ DE9037411FBA675D00E239D3 /* FIRFakeApp.m in Sources */ = {isa = PBXBuildFile; fileRef = 063CB47E1EBA7AEF00038A59 /* FIRFakeApp.m */; };
+ DE9037421FBA675D00E239D3 /* FIRTestAuthTokenProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = DE7B8D7D1E8EF202009EB6DF /* FIRTestAuthTokenProvider.m */; };
+ DE9037431FBA675D00E239D3 /* FMockStorageEngine.m in Sources */ = {isa = PBXBuildFile; fileRef = DE7B8D7F1E8EF202009EB6DF /* FMockStorageEngine.m */; };
+ DE9037441FBA675D00E239D3 /* FTestAuthTokenGenerator.m in Sources */ = {isa = PBXBuildFile; fileRef = DE7B8D811E8EF202009EB6DF /* FTestAuthTokenGenerator.m */; };
+ DE9037451FBA675D00E239D3 /* FTestBase.m in Sources */ = {isa = PBXBuildFile; fileRef = 063CB45A1EBA7AE200038A59 /* FTestBase.m */; };
+ DE9037461FBA675D00E239D3 /* FTestCachePolicy.m in Sources */ = {isa = PBXBuildFile; fileRef = DE7B8D831E8EF202009EB6DF /* FTestCachePolicy.m */; };
+ DE9037471FBA675D00E239D3 /* FTestClock.m in Sources */ = {isa = PBXBuildFile; fileRef = DE7B8D851E8EF202009EB6DF /* FTestClock.m */; };
+ DE9037481FBA675D00E239D3 /* FTestExpectations.m in Sources */ = {isa = PBXBuildFile; fileRef = DE7B8D871E8EF202009EB6DF /* FTestExpectations.m */; };
+ DE9037491FBA675D00E239D3 /* FTestHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = DE7B8D891E8EF202009EB6DF /* FTestHelpers.m */; };
+ DE90374A1FBA675D00E239D3 /* FTupleEventTypeString.m in Sources */ = {isa = PBXBuildFile; fileRef = DE7B8D8B1E8EF203009EB6DF /* FTupleEventTypeString.m */; };
+ DE90374B1FBA675D00E239D3 /* SenTest+FWaiter.m in Sources */ = {isa = PBXBuildFile; fileRef = DE7B8D8D1E8EF203009EB6DF /* SenTest+FWaiter.m */; };
+ DE90374D1FBA70E400E239D3 /* syncPointSpec.json in Resources */ = {isa = PBXBuildFile; fileRef = DE7B8D8E1E8EF203009EB6DF /* syncPointSpec.json */; };
DE9315261E86C6FF0083EDBF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DE9314ED1E86C6FF0083EDBF /* LaunchScreen.storyboard */; };
DE9315271E86C6FF0083EDBF /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DE9314EF1E86C6FF0083EDBF /* Main.storyboard */; };
DE9315291E86C6FF0083EDBF /* FIRAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = DE9314F31E86C6FF0083EDBF /* FIRAppDelegate.m */; };
@@ -409,6 +458,37 @@
DE9316031E8738E60083EDBF /* FIRMessagingSyncMessageManagerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DE9315D41E8738B70083EDBF /* FIRMessagingSyncMessageManagerTest.m */; };
DE9316041E8738E60083EDBF /* FIRMessagingTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DE9315D51E8738B70083EDBF /* FIRMessagingTest.m */; };
DE9316051E8738E60083EDBF /* FIRMessagingTestNotificationUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = DE9315D71E8738B70083EDBF /* FIRMessagingTestNotificationUtilities.m */; };
+ DEA7795D207ACC8000245121 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = DE47C107207AC94A00B1AEDF /* GoogleService-Info.plist */; };
+ DEAAD3C31FBA1CD90053BF48 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = DEAAD3BD1FBA1CD80053BF48 /* main.m */; };
+ DEAAD3CE1FBA1EFA0053BF48 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DEAAD3C51FBA1EF90053BF48 /* Assets.xcassets */; };
+ DEAAD3CF1FBA1EFA0053BF48 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = DEAAD3C81FBA1EFA0053BF48 /* AppDelegate.m */; };
+ DEAAD3D01FBA1EFA0053BF48 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DEAAD3C91FBA1EFA0053BF48 /* ViewController.m */; };
+ DEAAD3D41FBA20480053BF48 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DEAAD3D21FBA1F850053BF48 /* Main.storyboard */; };
+ DEAAD3D51FBA34250053BF48 /* FIRAppAssociationRegistrationUnitTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DEE14D751E844677006FA992 /* FIRAppAssociationRegistrationUnitTests.m */; };
+ DEAAD3D61FBA34250053BF48 /* FIRAppTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DEE14D761E844677006FA992 /* FIRAppTest.m */; };
+ DEAAD3D71FBA34250053BF48 /* FIRBundleUtilTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DEE14D771E844677006FA992 /* FIRBundleUtilTest.m */; };
+ DEAAD3D81FBA34250053BF48 /* FIRConfigurationTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DEE14D781E844677006FA992 /* FIRConfigurationTest.m */; };
+ DEAAD3D91FBA34250053BF48 /* FIRLoggerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DEE14D791E844677006FA992 /* FIRLoggerTest.m */; };
+ DEAAD3DA1FBA34250053BF48 /* FIROptionsTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DEE14D7A1E844677006FA992 /* FIROptionsTest.m */; };
+ DEAAD3DB1FBA34250053BF48 /* FIRTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = DEE14D7C1E844677006FA992 /* FIRTestCase.m */; };
+ DEAAD3DC1FBA36210053BF48 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = DEE14D711E844677006FA992 /* GoogleService-Info.plist */; };
+ DEAAD41A1FBA470B0053BF48 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = DEAAD4131FBA470A0053BF48 /* AppDelegate.m */; };
+ DEAAD41B1FBA470B0053BF48 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DEAAD4141FBA470A0053BF48 /* Assets.xcassets */; };
+ DEAAD41D1FBA470B0053BF48 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = DEAAD4161FBA470A0053BF48 /* main.m */; };
+ DEAAD41E1FBA470B0053BF48 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DEAAD4171FBA470A0053BF48 /* Main.storyboard */; };
+ DEAAD41F1FBA470B0053BF48 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DEAAD4191FBA470A0053BF48 /* ViewController.m */; };
+ DEAAD4201FBA47110053BF48 /* 1mb.dat in Resources */ = {isa = PBXBuildFile; fileRef = 069428801EC3B35A00F7BC69 /* 1mb.dat */; };
+ DEAAD4211FBA49D10053BF48 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = DEB61EC11E7C5DBB00C04B96 /* GoogleService-Info.plist */; };
+ DEAAD4221FBA49ED0053BF48 /* FIRStorageDeleteTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DEB139C11E734D9D00AC236D /* FIRStorageDeleteTests.m */; };
+ DEAAD4231FBA49ED0053BF48 /* FIRStorageGetMetadataTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DEB139C21E734D9D00AC236D /* FIRStorageGetMetadataTests.m */; };
+ DEAAD4241FBA49ED0053BF48 /* FIRStorageMetadataTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DEB139C31E734D9D00AC236D /* FIRStorageMetadataTests.m */; };
+ DEAAD4251FBA49ED0053BF48 /* FIRStoragePathTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DEB139C41E734D9D00AC236D /* FIRStoragePathTests.m */; };
+ DEAAD4261FBA49ED0053BF48 /* FIRStorageReferenceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DEB139C51E734D9D00AC236D /* FIRStorageReferenceTests.m */; };
+ DEAAD4271FBA49ED0053BF48 /* FIRStorageTestHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = DEB139C71E734D9D00AC236D /* FIRStorageTestHelpers.m */; };
+ DEAAD4281FBA49ED0053BF48 /* FIRStorageTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DEB139C81E734D9D00AC236D /* FIRStorageTests.m */; };
+ DEAAD4291FBA49ED0053BF48 /* FIRStorageTokenAuthorizerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DEB139C91E734D9D00AC236D /* FIRStorageTokenAuthorizerTests.m */; };
+ DEAAD42A1FBA49ED0053BF48 /* FIRStorageUpdateMetadataTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DEB139CA1E734D9D00AC236D /* FIRStorageUpdateMetadataTests.m */; };
+ DEAAD42B1FBA49ED0053BF48 /* FIRStorageUtilsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DEB139CB1E734D9D00AC236D /* FIRStorageUtilsTests.m */; };
DEB139F41E73506A00AC236D /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58F195388D20070C39A /* CoreGraphics.framework */; };
DEB139F51E73506A00AC236D /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F591195388D20070C39A /* UIKit.framework */; };
DEB139F61E73506A00AC236D /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58D195388D20070C39A /* Foundation.framework */; };
@@ -446,7 +526,48 @@
DEE14D941E84468D006FA992 /* FIRTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = DEE14D7C1E844677006FA992 /* FIRTestCase.m */; };
DEF288411F9AB6E100D480CF /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DEF288401F9AB6E100D480CF /* Default-568h@2x.png */; };
DEF288421F9AB6E100D480CF /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DEF288401F9AB6E100D480CF /* Default-568h@2x.png */; };
- FF33B94B3A34331129E4E4D5 /* Pods_Database_IntegrationTests_macOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 81C1ABDD7BB039B4BF06A29E /* Pods_Database_IntegrationTests_macOS.framework */; };
+ DEF6C30D1FBCE72F005D0740 /* FIRAuthDispatcherTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE9314FF1E86C6FF0083EDBF /* FIRAuthDispatcherTests.m */; };
+ DEF6C30F1FBCE775005D0740 /* FIRAdditionalUserInfoTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE9314FA1E86C6FF0083EDBF /* FIRAdditionalUserInfoTests.m */; };
+ DEF6C3101FBCE775005D0740 /* FIRApp+FIRAuthUnitTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE9314FC1E86C6FF0083EDBF /* FIRApp+FIRAuthUnitTests.m */; };
+ DEF6C3121FBCE775005D0740 /* FIRAuthAPNSTokenTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE750DB61EB3DD4000A75E47 /* FIRAuthAPNSTokenTests.m */; };
+ DEF6C3131FBCE775005D0740 /* FIRAuthAppCredentialManagerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE750DB71EB3DD4000A75E47 /* FIRAuthAppCredentialManagerTests.m */; };
+ DEF6C3141FBCE775005D0740 /* FIRAuthAppCredentialTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE0E5BB51EA7D91C00FAA825 /* FIRAuthAppCredentialTests.m */; };
+ DEF6C3161FBCE775005D0740 /* FIRAuthBackendCreateAuthURITests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE9314FD1E86C6FF0083EDBF /* FIRAuthBackendCreateAuthURITests.m */; };
+ DEF6C3171FBCE775005D0740 /* FIRAuthBackendRPCImplementationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE9314FE1E86C6FF0083EDBF /* FIRAuthBackendRPCImplementationTests.m */; };
+ DEF6C3181FBCE775005D0740 /* FIRAuthGlobalWorkQueueTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE9315001E86C6FF0083EDBF /* FIRAuthGlobalWorkQueueTests.m */; };
+ DEF6C3191FBCE775005D0740 /* FIRAuthKeychainTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE9315011E86C6FF0083EDBF /* FIRAuthKeychainTests.m */; };
+ DEF6C31A1FBCE775005D0740 /* FIRAuthNotificationManagerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE750DB81EB3DD4000A75E47 /* FIRAuthNotificationManagerTests.m */; };
+ DEF6C31B1FBCE775005D0740 /* FIRAuthSerialTaskQueueTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE9315021E86C6FF0083EDBF /* FIRAuthSerialTaskQueueTests.m */; };
+ DEF6C31C1FBCE775005D0740 /* FIRAuthTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE9315031E86C6FF0083EDBF /* FIRAuthTests.m */; };
+ DEF6C31E1FBCE775005D0740 /* FIRAuthUserDefaultsStorageTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE9315041E86C6FF0083EDBF /* FIRAuthUserDefaultsStorageTests.m */; };
+ DEF6C31F1FBCE775005D0740 /* FIRCreateAuthURIRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE9315051E86C6FF0083EDBF /* FIRCreateAuthURIRequestTests.m */; };
+ DEF6C3201FBCE775005D0740 /* FIRCreateAuthURIResponseTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE9315061E86C6FF0083EDBF /* FIRCreateAuthURIResponseTests.m */; };
+ DEF6C3211FBCE775005D0740 /* FIRDeleteAccountRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE9315071E86C6FF0083EDBF /* FIRDeleteAccountRequestTests.m */; };
+ DEF6C3221FBCE775005D0740 /* FIRDeleteAccountResponseTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE9315081E86C6FF0083EDBF /* FIRDeleteAccountResponseTests.m */; };
+ DEF6C3231FBCE775005D0740 /* FIRFakeBackendRPCIssuer.m in Sources */ = {isa = PBXBuildFile; fileRef = DE93150A1E86C6FF0083EDBF /* FIRFakeBackendRPCIssuer.m */; };
+ DEF6C3241FBCE775005D0740 /* FIRGetAccountInfoRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE93150B1E86C6FF0083EDBF /* FIRGetAccountInfoRequestTests.m */; };
+ DEF6C3251FBCE775005D0740 /* FIRGetAccountInfoResponseTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE93150C1E86C6FF0083EDBF /* FIRGetAccountInfoResponseTests.m */; };
+ DEF6C3261FBCE775005D0740 /* FIRGetOOBConfirmationCodeRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE93150D1E86C6FF0083EDBF /* FIRGetOOBConfirmationCodeRequestTests.m */; };
+ DEF6C3271FBCE775005D0740 /* FIRGetOOBConfirmationCodeResponseTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE93150E1E86C6FF0083EDBF /* FIRGetOOBConfirmationCodeResponseTests.m */; };
+ DEF6C3281FBCE775005D0740 /* FIRGetProjectConfigRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D92C82C61F578DF000D5EAFF /* FIRGetProjectConfigRequestTests.m */; };
+ DEF6C3291FBCE775005D0740 /* FIRGetProjectConfigResponseTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D92C82C51F578DF000D5EAFF /* FIRGetProjectConfigResponseTests.m */; };
+ DEF6C32A1FBCE775005D0740 /* FIRGitHubAuthProviderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE93150F1E86C6FF0083EDBF /* FIRGitHubAuthProviderTests.m */; };
+ DEF6C32C1FBCE775005D0740 /* FIRResetPasswordRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE9315111E86C6FF0083EDBF /* FIRResetPasswordRequestTests.m */; };
+ DEF6C32D1FBCE775005D0740 /* FIRResetPasswordResponseTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE9315121E86C6FF0083EDBF /* FIRResetPasswordResponseTests.m */; };
+ DEF6C3301FBCE775005D0740 /* FIRSetAccountInfoRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE9315151E86C6FF0083EDBF /* FIRSetAccountInfoRequestTests.m */; };
+ DEF6C3311FBCE775005D0740 /* FIRSetAccountInfoResponseTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE9315161E86C6FF0083EDBF /* FIRSetAccountInfoResponseTests.m */; };
+ DEF6C3321FBCE775005D0740 /* FIRSignUpNewUserRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE9315171E86C6FF0083EDBF /* FIRSignUpNewUserRequestTests.m */; };
+ DEF6C3331FBCE775005D0740 /* FIRSignUpNewUserResponseTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE9315181E86C6FF0083EDBF /* FIRSignUpNewUserResponseTests.m */; };
+ DEF6C3341FBCE775005D0740 /* FIRTwitterAuthProviderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE9315191E86C6FF0083EDBF /* FIRTwitterAuthProviderTests.m */; };
+ DEF6C3351FBCE775005D0740 /* FIRUserMetadataTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 7EFA2E031F71C93300DD354F /* FIRUserMetadataTests.m */; };
+ DEF6C3361FBCE775005D0740 /* FIRUserTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE93151A1E86C6FF0083EDBF /* FIRUserTests.m */; };
+ DEF6C3371FBCE775005D0740 /* FIRVerifyAssertionRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE93151B1E86C6FF0083EDBF /* FIRVerifyAssertionRequestTests.m */; };
+ DEF6C3381FBCE775005D0740 /* FIRVerifyAssertionResponseTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE93151C1E86C6FF0083EDBF /* FIRVerifyAssertionResponseTests.m */; };
+ DEF6C33B1FBCE775005D0740 /* FIRVerifyCustomTokenRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE93151D1E86C6FF0083EDBF /* FIRVerifyCustomTokenRequestTests.m */; };
+ DEF6C33C1FBCE775005D0740 /* FIRVerifyCustomTokenResponseTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE93151E1E86C6FF0083EDBF /* FIRVerifyCustomTokenResponseTests.m */; };
+ DEF6C33D1FBCE775005D0740 /* FIRVerifyPasswordRequestTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DE93151F1E86C6FF0083EDBF /* FIRVerifyPasswordRequestTest.m */; };
+ DEF6C33E1FBCE775005D0740 /* FIRVerifyPasswordResponseTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE9315201E86C6FF0083EDBF /* FIRVerifyPasswordResponseTests.m */; };
+ DEF6C3411FBCE775005D0740 /* OCMStubRecorder+FIRAuthUnitTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE9315241E86C6FF0083EDBF /* OCMStubRecorder+FIRAuthUnitTests.m */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -541,6 +662,20 @@
remoteGlobalIDString = D0FE8A1E1ED9C804003F6722;
remoteInfo = Database_Example_macOS;
};
+ DE1E3B301FEB1E7600EAEBB0 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 6003F582195388D10070C39A /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = DE1FAE8F1FBCF5E100897AAA;
+ remoteInfo = Auth_Example_tvOS;
+ };
+ DE1EC2841FBA5E63007D18D8 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 6003F582195388D10070C39A /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = DE1CD5961FBA55AF00FC031E;
+ remoteInfo = Database_Example_tvOS;
+ };
DE26D2621F7049F1004AE1D3 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 6003F582195388D10070C39A /* Project object */;
@@ -583,6 +718,27 @@
remoteGlobalIDString = DEB13A0A1E73507E00AC236D;
remoteInfo = Storage_Tests_iOS;
};
+ DE545C831FBCA41C00C637AE /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 6003F582195388D10070C39A /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = DEAAD3941FBA11270053BF48;
+ remoteInfo = Core_Tests_tvOS;
+ };
+ DE545C851FBCA42C00C637AE /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 6003F582195388D10070C39A /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = DEAAD3F41FBA46AB0053BF48;
+ remoteInfo = Storage_Tests_tvOS;
+ };
+ DE545C871FBCA43200C637AE /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 6003F582195388D10070C39A /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = DE1EC27E1FBA5E63007D18D8;
+ remoteInfo = Database_Tests_tvOS;
+ };
DE6F01B91E957157004AEE01 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 6003F582195388D10070C39A /* Project object */;
@@ -611,6 +767,20 @@
remoteGlobalIDString = DE9314DD1E86C6BE0083EDBF;
remoteInfo = Auth_Tests_iOS;
};
+ DEAAD3961FBA11280053BF48 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 6003F582195388D10070C39A /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = DEAAD3801FBA11270053BF48;
+ remoteInfo = Core_Example_tvOS;
+ };
+ DEAAD3F61FBA46AB0053BF48 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 6003F582195388D10070C39A /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = DEAAD3E01FBA46AA0053BF48;
+ remoteInfo = Storage_Example_tvOS;
+ };
DEB13A251E73512500AC236D /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 6003F582195388D10070C39A /* Project object */;
@@ -649,57 +819,13 @@
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
- D090052F1EDB32B700154410 /* CopyFiles */ = {
- isa = PBXCopyFilesBuildPhase;
- buildActionMask = 2147483647;
- dstPath = "";
- dstSubfolderSpec = 16;
- files = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
- D09005321EDB32EA00154410 /* CopyFiles */ = {
- isa = PBXCopyFilesBuildPhase;
- buildActionMask = 2147483647;
- dstPath = "";
- dstSubfolderSpec = 16;
- files = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
- D09005341EDB330800154410 /* CopyFiles */ = {
- isa = PBXCopyFilesBuildPhase;
- buildActionMask = 2147483647;
- dstPath = "";
- dstSubfolderSpec = 16;
- files = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
D09005361EDB331700154410 /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 16;
files = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
- D09005381EDB333700154410 /* CopyFiles */ = {
- isa = PBXCopyFilesBuildPhase;
- buildActionMask = 2147483647;
- dstPath = "";
- dstSubfolderSpec = 16;
- files = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
- D090053A1EDB334000154410 /* CopyFiles */ = {
- isa = PBXCopyFilesBuildPhase;
- buildActionMask = 2147483647;
- dstPath = "";
- dstSubfolderSpec = 16;
- files = (
+ D09005371EDB331C00154410 /* OCMock-iOS/OCMock.framework in CopyFiles */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -709,14 +835,13 @@
dstPath = "";
dstSubfolderSpec = 16;
files = (
+ D090053D1EDB334D00154410 /* OCMock-iOS/OCMock.framework in CopyFiles */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
- 000DAC7D0D180A9FBB395BB6 /* Pods-Database_Tests_iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Database_Tests_iOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Database_Tests_iOS/Pods-Database_Tests_iOS.debug.xcconfig"; sourceTree = "<group>"; };
- 00BD45B2141C68C3F9809A4D /* Pods-Database_IntegrationTests_macOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Database_IntegrationTests_macOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-Database_IntegrationTests_macOS/Pods-Database_IntegrationTests_macOS.release.xcconfig"; sourceTree = "<group>"; };
06121EBC1EC399C50008D70E /* Storage_IntegrationTests_iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Storage_IntegrationTests_iOS.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
06121ECA1EC39A0B0008D70E /* FIRStorageIntegrationTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRStorageIntegrationTests.m; sourceTree = "<group>"; };
0624F3E11EC0ECFA00E5940D /* Database_IntegrationTests_iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Database_IntegrationTests_iOS.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -776,93 +901,22 @@
069428801EC3B35A00F7BC69 /* 1mb.dat */ = {isa = PBXFileReference; lastKnownFileType = file; path = 1mb.dat; sourceTree = "<group>"; };
0697B1201EC13D8A00542174 /* Base64.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Base64.h; sourceTree = "<group>"; };
0697B1211EC13D8A00542174 /* Base64.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Base64.m; sourceTree = "<group>"; };
- 09F55B0265DCD315B2DD3C2E /* Pods-Auth_ApiTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Auth_ApiTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Auth_ApiTests/Pods-Auth_ApiTests.debug.xcconfig"; sourceTree = "<group>"; };
- 0ABC4448E5917769098A4EEF /* Pods_Auth_Tests_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Auth_Tests_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; };
- 0C69403B9730C701BF2E0446 /* Pods-Auth_Example_iOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Auth_Example_iOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-Auth_Example_iOS/Pods-Auth_Example_iOS.release.xcconfig"; sourceTree = "<group>"; };
- 0CA98384DDFFEECB1D473552 /* Pods-Auth_SwiftSample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Auth_SwiftSample.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Auth_SwiftSample/Pods-Auth_SwiftSample.debug.xcconfig"; sourceTree = "<group>"; };
- 0D66D613C54F5BFF80D9AB63 /* Pods-Core_Tests_iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Core_Tests_iOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Core_Tests_iOS/Pods-Core_Tests_iOS.debug.xcconfig"; sourceTree = "<group>"; };
- 0DB176DCABEFDF6C19B302B0 /* Pods-Auth_EarlGreyTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Auth_EarlGreyTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-Auth_EarlGreyTests/Pods-Auth_EarlGreyTests.release.xcconfig"; sourceTree = "<group>"; };
- 1015568683E2DFCC2719C754 /* Pods_Auth_Tests_macOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Auth_Tests_macOS.framework; sourceTree = BUILT_PRODUCTS_DIR; };
- 1068E64D36A3C656184168DE /* Pods-Database_Example_macOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Database_Example_macOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Database_Example_macOS/Pods-Database_Example_macOS.debug.xcconfig"; sourceTree = "<group>"; };
- 1735157165B298F2A1EC36E3 /* Pods-Auth_Tests_iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Auth_Tests_iOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Auth_Tests_iOS/Pods-Auth_Tests_iOS.debug.xcconfig"; sourceTree = "<group>"; };
- 1CCC00FFFC534F0E9B41CF29 /* Pods-Database_IntegrationTests_macOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Database_IntegrationTests_macOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Database_IntegrationTests_macOS/Pods-Database_IntegrationTests_macOS.debug.xcconfig"; sourceTree = "<group>"; };
- 1E6C076D38C1763E00A3DACA /* Pods-Core_Example_iOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Core_Example_iOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-Core_Example_iOS/Pods-Core_Example_iOS.release.xcconfig"; sourceTree = "<group>"; };
- 20928A4E610E48E3EA4D9F4A /* Pods-Database_IntegrationTests_iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Database_IntegrationTests_iOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Database_IntegrationTests_iOS/Pods-Database_IntegrationTests_iOS.debug.xcconfig"; sourceTree = "<group>"; };
- 24B879B03BD82C7DE771CA61 /* Pods-Storage_Example_macOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Storage_Example_macOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Storage_Example_macOS/Pods-Storage_Example_macOS.debug.xcconfig"; sourceTree = "<group>"; };
- 287D8FC7F3129B28D8A29FBE /* Pods-Core_Tests_macOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Core_Tests_macOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-Core_Tests_macOS/Pods-Core_Tests_macOS.release.xcconfig"; sourceTree = "<group>"; };
- 28B01131418E340D322829AC /* Pods-Auth_SwiftSample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Auth_SwiftSample.release.xcconfig"; path = "Pods/Target Support Files/Pods-Auth_SwiftSample/Pods-Auth_SwiftSample.release.xcconfig"; sourceTree = "<group>"; };
- 29606A0F0B0782779D4E73A1 /* Pods_Analytics_Tests_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Analytics_Tests_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; };
- 2B1B85CD0C7778447F3BFCD5 /* Pods-Database_Tests_iOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Database_Tests_iOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-Database_Tests_iOS/Pods-Database_Tests_iOS.release.xcconfig"; sourceTree = "<group>"; };
- 2B3C652966760042D996247E /* Pods-Core_Tests_macOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Core_Tests_macOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Core_Tests_macOS/Pods-Core_Tests_macOS.debug.xcconfig"; sourceTree = "<group>"; };
- 3742438A60FCFAAF24CDE751 /* Pods_Storage_Example_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Storage_Example_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; };
- 3A304052F4122D3468145F6C /* Pods-Messaging_Tests_iOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Messaging_Tests_iOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-Messaging_Tests_iOS/Pods-Messaging_Tests_iOS.release.xcconfig"; sourceTree = "<group>"; };
- 3B9B568851BEE22B7FCB61FB /* Pods_Core_Tests_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Core_Tests_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; };
- 3E26CB853AB2CAF1960A0F71 /* Pods-Storage_Example_iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Storage_Example_iOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Storage_Example_iOS/Pods-Storage_Example_iOS.debug.xcconfig"; sourceTree = "<group>"; };
- 4373FD322206E44A7CADC2A8 /* Pods_Auth_Example_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Auth_Example_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; };
- 456CD9478A63272C4397975E /* Pods-Analytics_Tests_iOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Analytics_Tests_iOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-Analytics_Tests_iOS/Pods-Analytics_Tests_iOS.release.xcconfig"; sourceTree = "<group>"; };
- 46052D607615BD81295B65C6 /* Pods-Messaging_Tests_iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Messaging_Tests_iOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Messaging_Tests_iOS/Pods-Messaging_Tests_iOS.debug.xcconfig"; sourceTree = "<group>"; };
- 466C3694B6C68F69BA4DA448 /* Pods-Storage_Tests_iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Storage_Tests_iOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Storage_Tests_iOS/Pods-Storage_Tests_iOS.debug.xcconfig"; sourceTree = "<group>"; };
- 48317719F315960780114559 /* Pods-Auth_Sample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Auth_Sample.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Auth_Sample/Pods-Auth_Sample.debug.xcconfig"; sourceTree = "<group>"; };
- 4852BF989C85671F5D7EBD2A /* Pods_Database_Tests_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Database_Tests_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; };
- 4B490EFB675400675CA98196 /* Pods-Storage_Tests_macOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Storage_Tests_macOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Storage_Tests_macOS/Pods-Storage_Tests_macOS.debug.xcconfig"; sourceTree = "<group>"; };
- 4BF8EA84DF6AF0AB6E9BB6A0 /* Pods-Auth_Example_macOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Auth_Example_macOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-Auth_Example_macOS/Pods-Auth_Example_macOS.release.xcconfig"; sourceTree = "<group>"; };
- 4CC7C8B9E821151509BB3B64 /* Pods-Database_Example_macOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Database_Example_macOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-Database_Example_macOS/Pods-Database_Example_macOS.release.xcconfig"; sourceTree = "<group>"; };
- 4D61AACC06F8E078EF051E4C /* Pods-Messaging_Example_iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Messaging_Example_iOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Messaging_Example_iOS/Pods-Messaging_Example_iOS.debug.xcconfig"; sourceTree = "<group>"; };
- 4ECAA105379B7E664C7FF223 /* Pods-Storage_IntegrationTests_iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Storage_IntegrationTests_iOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Storage_IntegrationTests_iOS/Pods-Storage_IntegrationTests_iOS.debug.xcconfig"; sourceTree = "<group>"; };
- 5CA5A85B5A80F118F3247910 /* Pods-Storage_IntegrationTests_iOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Storage_IntegrationTests_iOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-Storage_IntegrationTests_iOS/Pods-Storage_IntegrationTests_iOS.release.xcconfig"; sourceTree = "<group>"; };
- 5DA6361D6B54362D073F3BA5 /* Pods-Database_Example_iOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Database_Example_iOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-Database_Example_iOS/Pods-Database_Example_iOS.release.xcconfig"; sourceTree = "<group>"; };
6003F58D195388D20070C39A /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
6003F58F195388D20070C39A /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };
6003F591195388D20070C39A /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
- 6029CD8D7E65D491083D5944 /* Pods-Auth_Example_iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Auth_Example_iOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Auth_Example_iOS/Pods-Auth_Example_iOS.debug.xcconfig"; sourceTree = "<group>"; };
- 6098677E3698C58151DC2E85 /* Pods-Messaging_Example_iOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Messaging_Example_iOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-Messaging_Example_iOS/Pods-Messaging_Example_iOS.release.xcconfig"; sourceTree = "<group>"; };
- 66544F771D53687DD73F8BE8 /* Pods_Database_Example_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Database_Example_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; };
- 68657030253DE5895E124F45 /* Pods_Core_Example_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Core_Example_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; };
- 6A28B39B3D707677EF59C110 /* Pods_Storage_IntegrationTests_macOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Storage_IntegrationTests_macOS.framework; sourceTree = BUILT_PRODUCTS_DIR; };
- 6CCB3CEB7BF2E4994FBDB2E7 /* Pods_Messaging_Tests_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Messaging_Tests_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; };
- 6FD4B6DC35E3304CBECFEC61 /* Pods-Core_Example_macOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Core_Example_macOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Core_Example_macOS/Pods-Core_Example_macOS.debug.xcconfig"; sourceTree = "<group>"; };
- 77FA2AB612D17244983008F7 /* Pods-Analytics_Tests_iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Analytics_Tests_iOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Analytics_Tests_iOS/Pods-Analytics_Tests_iOS.debug.xcconfig"; sourceTree = "<group>"; };
- 7879DCC8860E7CED0311D4E8 /* Pods-Database_Tests_macOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Database_Tests_macOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-Database_Tests_macOS/Pods-Database_Tests_macOS.release.xcconfig"; sourceTree = "<group>"; };
- 7CF25C495716B3441849720B /* Pods_Database_Example_macOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Database_Example_macOS.framework; sourceTree = BUILT_PRODUCTS_DIR; };
7E94853F1F578A9D005A3939 /* FIRAuthURLPresenterTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRAuthURLPresenterTests.m; sourceTree = "<group>"; };
+ 7EE21F791FE89193009B1370 /* FIREmailLinkRequestTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIREmailLinkRequestTests.m; sourceTree = "<group>"; };
+ 7EE21F7B1FE8919D009B1370 /* FIREmailLinkSignInResponseTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIREmailLinkSignInResponseTests.m; sourceTree = "<group>"; };
7EFA2E031F71C93300DD354F /* FIRUserMetadataTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRUserMetadataTests.m; sourceTree = "<group>"; };
- 81C1ABDD7BB039B4BF06A29E /* Pods_Database_IntegrationTests_macOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Database_IntegrationTests_macOS.framework; sourceTree = BUILT_PRODUCTS_DIR; };
8496034D8156555C5FCF8F14 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = "<group>"; };
- 84C51650773603D9F827CB3F /* Pods_Core_Example_macOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Core_Example_macOS.framework; sourceTree = BUILT_PRODUCTS_DIR; };
- 8602A8FB9AF04A0C9A8FE380 /* Pods-Auth_Sample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Auth_Sample.release.xcconfig"; path = "Pods/Target Support Files/Pods-Auth_Sample/Pods-Auth_Sample.release.xcconfig"; sourceTree = "<group>"; };
- 86062A14985F49A3B99614A7 /* Pods_Core_Tests_macOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Core_Tests_macOS.framework; sourceTree = BUILT_PRODUCTS_DIR; };
- 86B8E0400070C72C0FE0C2F8 /* Pods-Database_Tests_macOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Database_Tests_macOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Database_Tests_macOS/Pods-Database_Tests_macOS.debug.xcconfig"; sourceTree = "<group>"; };
- 870F50EE08ED74C38B5CAF79 /* Pods-Core_Example_iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Core_Example_iOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Core_Example_iOS/Pods-Core_Example_iOS.debug.xcconfig"; sourceTree = "<group>"; };
- 8F27F07800D6FD739BD094D3 /* Pods_Storage_Example_macOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Storage_Example_macOS.framework; sourceTree = BUILT_PRODUCTS_DIR; };
- 910D7A5DE7D5AF153328D243 /* Pods_Storage_Tests_macOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Storage_Tests_macOS.framework; sourceTree = BUILT_PRODUCTS_DIR; };
- 93491C87A390AB737849677E /* Pods_Auth_ApiTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Auth_ApiTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
- 94C0FA103316CB56F37E20EA /* Pods-Auth_EarlGreyTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Auth_EarlGreyTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Auth_EarlGreyTests/Pods-Auth_EarlGreyTests.debug.xcconfig"; sourceTree = "<group>"; };
- 97790B1C788991008685954F /* Pods-Storage_Example_iOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Storage_Example_iOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-Storage_Example_iOS/Pods-Storage_Example_iOS.release.xcconfig"; sourceTree = "<group>"; };
- 979DF124E3D1146A81188F78 /* Pods_Storage_IntegrationTests_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Storage_IntegrationTests_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; };
- A8E6527440C118AC7C21D504 /* Pods_Storage_Tests_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Storage_Tests_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ 923F824B206C4D8000034974 /* SafariServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SafariServices.framework; path = System/Library/Frameworks/SafariServices.framework; sourceTree = SDKROOT; };
+ 923F824D206C4D8B00034974 /* SafariServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SafariServices.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.13.sdk/System/Library/Frameworks/SafariServices.framework; sourceTree = DEVELOPER_DIR; };
+ 923F8250206C4DC500034974 /* UserNotifications.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UserNotifications.framework; path = System/Library/Frameworks/UserNotifications.framework; sourceTree = SDKROOT; };
AFAF36F41EC28C25004BDEE5 /* Shared.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Shared.xcassets; path = Shared/Shared.xcassets; sourceTree = "<group>"; };
- AFC8BA9C1EBD230E00B8EEAE /* NotificationsController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationsController.swift; sourceTree = "<group>"; };
- AFC8BA9E1EBD51A700B8EEAE /* Environment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Environment.swift; sourceTree = "<group>"; };
AFC8BAA11EC257D700B8EEAE /* Messaging_Example-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Messaging_Example-Bridging-Header.h"; sourceTree = "<group>"; };
AFC8BAA21EC257D800B8EEAE /* FIRSampleAppUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = FIRSampleAppUtilities.h; path = Shared/FIRSampleAppUtilities.h; sourceTree = "<group>"; };
AFC8BAA31EC257D800B8EEAE /* FIRSampleAppUtilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FIRSampleAppUtilities.m; path = Shared/FIRSampleAppUtilities.m; sourceTree = "<group>"; };
AFD562E51EB13C6D00EA2233 /* Messaging_Example_iOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Messaging_Example_iOS.app; sourceTree = BUILT_PRODUCTS_DIR; };
- AFD562FF1EB13DF200EA2233 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
- AFD563001EB13DF200EA2233 /* Messaging-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Messaging-Info.plist"; sourceTree = "<group>"; };
- AFD563011EB13DF200EA2233 /* MessagingViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessagingViewController.swift; sourceTree = "<group>"; };
- AFD563131EB1466100EA2233 /* GoogleService-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "App/GoogleService-Info.plist"; sourceTree = "<group>"; };
- AFD563141EB29B8C00EA2233 /* Messaging_Example.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Messaging_Example.entitlements; sourceTree = "<group>"; };
- AFD563161EBBEF7B00EA2233 /* Data+MessagingExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Data+MessagingExtensions.swift"; sourceTree = "<group>"; };
- B0895BC929D50B20A69CEEEF /* Pods-Storage_IntegrationTests_macOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Storage_IntegrationTests_macOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-Storage_IntegrationTests_macOS/Pods-Storage_IntegrationTests_macOS.release.xcconfig"; sourceTree = "<group>"; };
- B0C1478ED7269FF2107F5B6F /* Pods_Messaging_Example_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Messaging_Example_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; };
- B0C52364C5FCE5D3FBAEAF83 /* Pods_Auth_SwiftSample.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Auth_SwiftSample.framework; sourceTree = BUILT_PRODUCTS_DIR; };
- B1EFE04FF3C9650984C5E3C3 /* Pods-Storage_Tests_iOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Storage_Tests_iOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-Storage_Tests_iOS/Pods-Storage_Tests_iOS.release.xcconfig"; sourceTree = "<group>"; };
- B4F2CCE27C567E675C27953C /* Pods-Auth_Example_macOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Auth_Example_macOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Auth_Example_macOS/Pods-Auth_Example_macOS.debug.xcconfig"; sourceTree = "<group>"; };
- BA1AAFF4508A97F7B32533FC /* Pods-Auth_Tests_macOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Auth_Tests_macOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Auth_Tests_macOS/Pods-Auth_Tests_macOS.debug.xcconfig"; sourceTree = "<group>"; };
- BDE126A5F0AFCA9956A4C5D9 /* Pods_Auth_EarlGreyTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Auth_EarlGreyTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
- C1520E81B1BFD24ED1882137 /* Pods-Storage_IntegrationTests_macOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Storage_IntegrationTests_macOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Storage_IntegrationTests_macOS/Pods-Storage_IntegrationTests_macOS.debug.xcconfig"; sourceTree = "<group>"; };
- D018534A1EDACED4003A645C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
- D018534C1EDACED4003A645C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
D01853791EDAD084003A645C /* Auth_Example_macOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Auth_Example_macOS.app; sourceTree = BUILT_PRODUCTS_DIR; };
D018537C1EDAD0E6003A645C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
D018537D1EDAD0E6003A645C /* FIRAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FIRAppDelegate.h; sourceTree = "<group>"; };
@@ -881,6 +935,7 @@
D064E6A41ED9B1BF001956DF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
D064E6A61ED9B1BF001956DF /* Core-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Core-Info.plist"; sourceTree = "<group>"; };
D064E6BF1ED9B31C001956DF /* Core_Tests_macOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Core_Tests_macOS.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+ D09005301EDB32D600154410 /* OCMock-iOS/OCMock.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = "OCMock-iOS/OCMock.framework"; sourceTree = BUILT_PRODUCTS_DIR; };
D0EDB2CD1EDA04F800B6C31B /* Storage_Example_macOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Storage_Example_macOS.app; sourceTree = BUILT_PRODUCTS_DIR; };
D0EDB2D01EDA056A00B6C31B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
D0EDB2D11EDA056A00B6C31B /* FIRAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FIRAppDelegate.h; sourceTree = "<group>"; };
@@ -901,15 +956,23 @@
D0FE8A2F1ED9C804003F6722 /* Database_Example_macOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Database_Example_macOS.app; sourceTree = BUILT_PRODUCTS_DIR; };
D0FE8A621ED9C870003F6722 /* Database_Tests_macOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Database_Tests_macOS.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
D0FE8A8C1ED9C87B003F6722 /* Database_IntegrationTests_macOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Database_IntegrationTests_macOS.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
- D6A450A39BCA3DB4138333D8 /* Pods-Auth_Tests_iOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Auth_Tests_iOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-Auth_Tests_iOS/Pods-Auth_Tests_iOS.release.xcconfig"; sourceTree = "<group>"; };
- D837E35351C0B844EA64B959 /* Pods_Auth_Sample.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Auth_Sample.framework; sourceTree = BUILT_PRODUCTS_DIR; };
D92C82C51F578DF000D5EAFF /* FIRGetProjectConfigResponseTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRGetProjectConfigResponseTests.m; sourceTree = "<group>"; };
D92C82C61F578DF000D5EAFF /* FIRGetProjectConfigRequestTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRGetProjectConfigRequestTests.m; sourceTree = "<group>"; };
- DDF4A6C7CFF20DCCF96071EC /* Pods-Core_Tests_iOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Core_Tests_iOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-Core_Tests_iOS/Pods-Core_Tests_iOS.release.xcconfig"; sourceTree = "<group>"; };
DE0E5BB51EA7D91C00FAA825 /* FIRAuthAppCredentialTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRAuthAppCredentialTests.m; sourceTree = "<group>"; };
DE0E5BB61EA7D91C00FAA825 /* FIRAuthAppDelegateProxyTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRAuthAppDelegateProxyTests.m; sourceTree = "<group>"; };
DE0E5BB91EA7D92E00FAA825 /* FIRVerifyClientRequestTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRVerifyClientRequestTest.m; sourceTree = "<group>"; };
DE0E5BBA1EA7D92E00FAA825 /* FIRVerifyClientResponseTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRVerifyClientResponseTests.m; sourceTree = "<group>"; };
+ DE1CD5971FBA55AF00FC031E /* Database_Example_tvOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Database_Example_tvOS.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ DE1EC27F1FBA5E63007D18D8 /* Database_Tests_tvOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Database_Tests_tvOS.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+ DE1EC28A1FBA5EB5007D18D8 /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
+ DE1EC28B1FBA5EB5007D18D8 /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
+ DE1EC28C1FBA5EB5007D18D8 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
+ DE1EC28D1FBA5EB5007D18D8 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
+ DE1EC28E1FBA5EB5007D18D8 /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
+ DE1EC28F1FBA5EB5007D18D8 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = "<group>"; };
+ DE1EC2901FBA5EB5007D18D8 /* ViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = "<group>"; };
+ DE1EC2911FBA5EB5007D18D8 /* ViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = "<group>"; };
+ DE1FAE901FBCF5E100897AAA /* Auth_Example_tvOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Auth_Example_tvOS.app; sourceTree = BUILT_PRODUCTS_DIR; };
DE26D1C71F70330A004AE1D3 /* AuthCredentials.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AuthCredentials.h; sourceTree = "<group>"; };
DE26D1C81F70330A004AE1D3 /* AuthCredentialsTemplate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AuthCredentialsTemplate.h; sourceTree = "<group>"; };
DE26D1C91F70330A004AE1D3 /* FirebaseAuthApiTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FirebaseAuthApiTests.m; sourceTree = "<group>"; };
@@ -974,6 +1037,33 @@
DE26D26D1F705C35004AE1D3 /* Auth_EarlGreyTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Auth_EarlGreyTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
DE26D27D1F705EC7004AE1D3 /* SwiftSample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwiftSample.app; sourceTree = BUILT_PRODUCTS_DIR; };
DE45C6641E7DA8CB009E6ACD /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Platforms/iPhoneOS.platform/Developer/Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; };
+ DE47C0ED207AC87D00B1AEDF /* Messaging_Sample_iOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Messaging_Sample_iOS.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ DE47C107207AC94A00B1AEDF /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = "<group>"; };
+ DE47C109207AC94A00B1AEDF /* Environment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Environment.swift; sourceTree = "<group>"; };
+ DE47C10A207AC94A00B1AEDF /* MessagingViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessagingViewController.swift; sourceTree = "<group>"; };
+ DE47C10B207AC94A00B1AEDF /* Messaging_Example.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Messaging_Example.entitlements; sourceTree = "<group>"; };
+ DE47C10D207AC94A00B1AEDF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
+ DE47C10F207AC94A00B1AEDF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
+ DE47C110207AC94A00B1AEDF /* Messaging-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Messaging-Info.plist"; sourceTree = "<group>"; };
+ DE47C111207AC94A00B1AEDF /* Data+MessagingExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Data+MessagingExtensions.swift"; sourceTree = "<group>"; };
+ DE47C112207AC94A00B1AEDF /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
+ DE47C113207AC94A00B1AEDF /* NotificationsController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationsController.swift; sourceTree = "<group>"; };
+ DE47C134207ACAA900B1AEDF /* FIRAppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FIRAppDelegate.h; sourceTree = "<group>"; };
+ DE47C135207ACAA900B1AEDF /* FIRViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FIRViewController.h; sourceTree = "<group>"; };
+ DE47C137207ACAA900B1AEDF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
+ DE47C139207ACAA900B1AEDF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
+ DE47C13B207ACAA900B1AEDF /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
+ DE47C13C207ACAA900B1AEDF /* FIRAppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRAppDelegate.m; sourceTree = "<group>"; };
+ DE47C13D207ACAA900B1AEDF /* FIRViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRViewController.m; sourceTree = "<group>"; };
+ DE53893E1FBB62E100199FC2 /* Auth_Tests_tvOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Auth_Tests_tvOS.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+ DE53894C1FBB635400199FC2 /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
+ DE53894D1FBB635400199FC2 /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
+ DE53894E1FBB635400199FC2 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
+ DE53894F1FBB635400199FC2 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
+ DE5389501FBB635400199FC2 /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
+ DE5389511FBB635400199FC2 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = "<group>"; };
+ DE5389521FBB635400199FC2 /* ViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = "<group>"; };
+ DE5389531FBB635400199FC2 /* ViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = "<group>"; };
DE750DB51EB3DD4000A75E47 /* FIRAuthAPNSTokenManagerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRAuthAPNSTokenManagerTests.m; sourceTree = "<group>"; };
DE750DB61EB3DD4000A75E47 /* FIRAuthAPNSTokenTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRAuthAPNSTokenTests.m; sourceTree = "<group>"; };
DE750DB71EB3DD4000A75E47 /* FIRAuthAppCredentialManagerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRAuthAppCredentialManagerTests.m; sourceTree = "<group>"; };
@@ -1089,7 +1179,26 @@
DE9315D61E8738B70083EDBF /* FIRMessagingTestNotificationUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FIRMessagingTestNotificationUtilities.h; sourceTree = "<group>"; };
DE9315D71E8738B70083EDBF /* FIRMessagingTestNotificationUtilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRMessagingTestNotificationUtilities.m; sourceTree = "<group>"; };
DE9315D81E8738B70083EDBF /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
- DE9C3D75207E5D1DC70BB364 /* Pods_Database_Tests_macOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Database_Tests_macOS.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ DEAAD3811FBA11270053BF48 /* Core_Example_tvOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Core_Example_tvOS.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ DEAAD3951FBA11270053BF48 /* Core_Tests_tvOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Core_Tests_tvOS.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+ DEAAD3BD1FBA1CD80053BF48 /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
+ DEAAD3C41FBA1EF90053BF48 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
+ DEAAD3C51FBA1EF90053BF48 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
+ DEAAD3C61FBA1EF90053BF48 /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
+ DEAAD3C71FBA1EF90053BF48 /* ViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = "<group>"; };
+ DEAAD3C81FBA1EFA0053BF48 /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
+ DEAAD3C91FBA1EFA0053BF48 /* ViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = "<group>"; };
+ DEAAD3D21FBA1F850053BF48 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = "<group>"; };
+ DEAAD3E11FBA46AA0053BF48 /* Storage_Example_tvOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Storage_Example_tvOS.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ DEAAD3F51FBA46AB0053BF48 /* Storage_Tests_tvOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Storage_Tests_tvOS.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+ DEAAD4121FBA470A0053BF48 /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
+ DEAAD4131FBA470A0053BF48 /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
+ DEAAD4141FBA470A0053BF48 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
+ DEAAD4151FBA470A0053BF48 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
+ DEAAD4161FBA470A0053BF48 /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
+ DEAAD4171FBA470A0053BF48 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = "<group>"; };
+ DEAAD4181FBA470A0053BF48 /* ViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = "<group>"; };
+ DEAAD4191FBA470A0053BF48 /* ViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = "<group>"; };
DEB139C11E734D9D00AC236D /* FIRStorageDeleteTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRStorageDeleteTests.m; sourceTree = "<group>"; };
DEB139C21E734D9D00AC236D /* FIRStorageGetMetadataTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRStorageGetMetadataTests.m; sourceTree = "<group>"; };
DEB139C31E734D9D00AC236D /* FIRStorageMetadataTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRStorageMetadataTests.m; sourceTree = "<group>"; };
@@ -1140,16 +1249,7 @@
DEE14D7C1E844677006FA992 /* FIRTestCase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRTestCase.m; sourceTree = "<group>"; };
DEE14D7D1E844677006FA992 /* Tests-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Tests-Info.plist"; sourceTree = "<group>"; };
DEF288401F9AB6E100D480CF /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-568h@2x.png"; sourceTree = "<group>"; };
- DFC35EAB61A983056BE25D28 /* Pods_Database_IntegrationTests_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Database_IntegrationTests_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; };
- E0EF5EDDB1FD839F03FC02AA /* Pods-Storage_Tests_macOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Storage_Tests_macOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-Storage_Tests_macOS/Pods-Storage_Tests_macOS.release.xcconfig"; sourceTree = "<group>"; };
E2C2834C90DBAB56D568189F /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = "<group>"; };
- E5978C421A9123C9D34CBA43 /* Pods-Database_Example_iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Database_Example_iOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Database_Example_iOS/Pods-Database_Example_iOS.debug.xcconfig"; sourceTree = "<group>"; };
- E8A8A21551A3D8557757AD0D /* Pods-Auth_ApiTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Auth_ApiTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-Auth_ApiTests/Pods-Auth_ApiTests.release.xcconfig"; sourceTree = "<group>"; };
- EDA33867CB04D0AADD09321A /* Pods-Database_IntegrationTests_iOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Database_IntegrationTests_iOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-Database_IntegrationTests_iOS/Pods-Database_IntegrationTests_iOS.release.xcconfig"; sourceTree = "<group>"; };
- F7649E1B594D8101939746EA /* Pods-Core_Example_macOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Core_Example_macOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-Core_Example_macOS/Pods-Core_Example_macOS.release.xcconfig"; sourceTree = "<group>"; };
- F7A35264721D3C8D9F96F91C /* Pods_Auth_Example_macOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Auth_Example_macOS.framework; sourceTree = BUILT_PRODUCTS_DIR; };
- FD3AEF097DFCF2ADAC345D2A /* Pods-Storage_Example_macOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Storage_Example_macOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-Storage_Example_macOS/Pods-Storage_Example_macOS.release.xcconfig"; sourceTree = "<group>"; };
- FDBC4B909E617B02D7E741F6 /* Pods-Auth_Tests_macOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Auth_Tests_macOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-Auth_Tests_macOS/Pods-Auth_Tests_macOS.release.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -1157,7 +1257,6 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- 6232ED3272E9C78C2A0E127F /* Pods_Storage_IntegrationTests_iOS.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1165,7 +1264,6 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- DA464BCB6574F7FE299CB48D /* Pods_Database_IntegrationTests_iOS.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1173,7 +1271,6 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- 8313C96352C6C77D481B5937 /* Pods_Messaging_Example_iOS.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1181,7 +1278,6 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- 6D6FA69218AB107C266E1B70 /* Pods_Auth_Example_macOS.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1189,7 +1285,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- 2C8B39CFF9898AE4B29AD114 /* Pods_Auth_Tests_macOS.framework in Frameworks */,
+ 923F824E206C4D8B00034974 /* SafariServices.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1197,7 +1293,6 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- 431EBDD6071EF1AE6F6DBE5F /* Pods_Core_Example_macOS.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1205,7 +1300,6 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- 29136BCA0AD59481B07CFBB0 /* Pods_Core_Tests_macOS.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1213,7 +1307,6 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- 063825B4D58274CB24B25FF1 /* Pods_Storage_Example_macOS.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1221,7 +1314,6 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- 529BBEFBB6D7A3653B6B3874 /* Pods_Storage_Tests_macOS.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1229,7 +1321,6 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- 91BECF25F64620DEDF99A106 /* Pods_Storage_IntegrationTests_macOS.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1237,7 +1328,6 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- A553FD534066F62EE74F2D51 /* Pods_Database_Example_macOS.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1245,7 +1335,6 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- 7CA435AB1A753CC9EEDFA648 /* Pods_Database_Tests_macOS.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1253,7 +1342,27 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- FF33B94B3A34331129E4E4D5 /* Pods_Database_IntegrationTests_macOS.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ DE1CD5941FBA55AF00FC031E /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ DE1EC27C1FBA5E63007D18D8 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ DE1FAE8D1FBCF5E100897AAA /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1261,7 +1370,6 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- 0C1D425E4DA4FBFD5A08B985 /* Pods_Auth_Sample.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1269,7 +1377,6 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- 8FC590E7FF7C561991DC9DD6 /* Pods_Auth_ApiTests.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1277,7 +1384,6 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- CD4DB22A28941C5FEE70686A /* Pods_Auth_EarlGreyTests.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1285,7 +1391,20 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- 930570CF84207AEB98440760 /* Pods_Auth_SwiftSample.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ DE47C0E4207AC87D00B1AEDF /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ DE53893B1FBB62E100199FC2 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1293,7 +1412,6 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- 825BE4C9299DAB7EFEB19B65 /* Pods_Database_Example_iOS.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1301,7 +1419,6 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- 6ADAC4BEBCE37253D2D7A50F /* Pods_Database_Tests_iOS.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1309,7 +1426,6 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- 67EA2F675D33B39CEB0D41B1 /* Pods_Auth_Example_iOS.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1317,7 +1433,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- 5207C8D12B9830DADB85FE67 /* Pods_Auth_Tests_iOS.framework in Frameworks */,
+ 923F824C206C4D8000034974 /* SafariServices.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1325,7 +1441,37 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- 2ECC6F80E47D2646FA82B940 /* Pods_Messaging_Tests_iOS.framework in Frameworks */,
+ 923F8252206C4DD500034974 /* UIKit.framework in Frameworks */,
+ 923F8251206C4DC600034974 /* UserNotifications.framework in Frameworks */,
+ 923F824F206C4DA500034974 /* XCTest.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ DEAAD37E1FBA11270053BF48 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ DEAAD3921FBA11270053BF48 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ DEAAD3DE1FBA46AA0053BF48 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ DEAAD3F21FBA46AB0053BF48 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1336,7 +1482,6 @@
DEB139F41E73506A00AC236D /* CoreGraphics.framework in Frameworks */,
DEB139F51E73506A00AC236D /* UIKit.framework in Frameworks */,
DEB139F61E73506A00AC236D /* Foundation.framework in Frameworks */,
- 9CD1CAC2BC2C39B755F7BF88 /* Pods_Storage_Example_iOS.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1344,7 +1489,6 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- 01E863BD40D23087B77F2F03 /* Pods_Storage_Tests_iOS.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1352,7 +1496,6 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- 53C9FD19788281F65D537548 /* Pods_Analytics_Tests_iOS.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1360,7 +1503,6 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- 1A509E710C83B6D0A6CD286D /* Pods_Core_Example_iOS.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1368,7 +1510,6 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- ABA730C3E77B260C564C288A /* Pods_Core_Tests_iOS.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1439,67 +1580,6 @@
path = third_party;
sourceTree = "<group>";
};
- 2A06FEEED3D48AAD710413A7 /* Pods */ = {
- isa = PBXGroup;
- children = (
- 09F55B0265DCD315B2DD3C2E /* Pods-Auth_ApiTests.debug.xcconfig */,
- E8A8A21551A3D8557757AD0D /* Pods-Auth_ApiTests.release.xcconfig */,
- 94C0FA103316CB56F37E20EA /* Pods-Auth_EarlGreyTests.debug.xcconfig */,
- 0DB176DCABEFDF6C19B302B0 /* Pods-Auth_EarlGreyTests.release.xcconfig */,
- 6029CD8D7E65D491083D5944 /* Pods-Auth_Example_iOS.debug.xcconfig */,
- 0C69403B9730C701BF2E0446 /* Pods-Auth_Example_iOS.release.xcconfig */,
- B4F2CCE27C567E675C27953C /* Pods-Auth_Example_macOS.debug.xcconfig */,
- 4BF8EA84DF6AF0AB6E9BB6A0 /* Pods-Auth_Example_macOS.release.xcconfig */,
- 48317719F315960780114559 /* Pods-Auth_Sample.debug.xcconfig */,
- 8602A8FB9AF04A0C9A8FE380 /* Pods-Auth_Sample.release.xcconfig */,
- 0CA98384DDFFEECB1D473552 /* Pods-Auth_SwiftSample.debug.xcconfig */,
- 28B01131418E340D322829AC /* Pods-Auth_SwiftSample.release.xcconfig */,
- 1735157165B298F2A1EC36E3 /* Pods-Auth_Tests_iOS.debug.xcconfig */,
- D6A450A39BCA3DB4138333D8 /* Pods-Auth_Tests_iOS.release.xcconfig */,
- BA1AAFF4508A97F7B32533FC /* Pods-Auth_Tests_macOS.debug.xcconfig */,
- FDBC4B909E617B02D7E741F6 /* Pods-Auth_Tests_macOS.release.xcconfig */,
- 870F50EE08ED74C38B5CAF79 /* Pods-Core_Example_iOS.debug.xcconfig */,
- 1E6C076D38C1763E00A3DACA /* Pods-Core_Example_iOS.release.xcconfig */,
- 6FD4B6DC35E3304CBECFEC61 /* Pods-Core_Example_macOS.debug.xcconfig */,
- F7649E1B594D8101939746EA /* Pods-Core_Example_macOS.release.xcconfig */,
- 0D66D613C54F5BFF80D9AB63 /* Pods-Core_Tests_iOS.debug.xcconfig */,
- DDF4A6C7CFF20DCCF96071EC /* Pods-Core_Tests_iOS.release.xcconfig */,
- 2B3C652966760042D996247E /* Pods-Core_Tests_macOS.debug.xcconfig */,
- 287D8FC7F3129B28D8A29FBE /* Pods-Core_Tests_macOS.release.xcconfig */,
- E5978C421A9123C9D34CBA43 /* Pods-Database_Example_iOS.debug.xcconfig */,
- 5DA6361D6B54362D073F3BA5 /* Pods-Database_Example_iOS.release.xcconfig */,
- 1068E64D36A3C656184168DE /* Pods-Database_Example_macOS.debug.xcconfig */,
- 4CC7C8B9E821151509BB3B64 /* Pods-Database_Example_macOS.release.xcconfig */,
- 20928A4E610E48E3EA4D9F4A /* Pods-Database_IntegrationTests_iOS.debug.xcconfig */,
- EDA33867CB04D0AADD09321A /* Pods-Database_IntegrationTests_iOS.release.xcconfig */,
- 1CCC00FFFC534F0E9B41CF29 /* Pods-Database_IntegrationTests_macOS.debug.xcconfig */,
- 00BD45B2141C68C3F9809A4D /* Pods-Database_IntegrationTests_macOS.release.xcconfig */,
- 000DAC7D0D180A9FBB395BB6 /* Pods-Database_Tests_iOS.debug.xcconfig */,
- 2B1B85CD0C7778447F3BFCD5 /* Pods-Database_Tests_iOS.release.xcconfig */,
- 86B8E0400070C72C0FE0C2F8 /* Pods-Database_Tests_macOS.debug.xcconfig */,
- 7879DCC8860E7CED0311D4E8 /* Pods-Database_Tests_macOS.release.xcconfig */,
- 4D61AACC06F8E078EF051E4C /* Pods-Messaging_Example_iOS.debug.xcconfig */,
- 6098677E3698C58151DC2E85 /* Pods-Messaging_Example_iOS.release.xcconfig */,
- 46052D607615BD81295B65C6 /* Pods-Messaging_Tests_iOS.debug.xcconfig */,
- 3A304052F4122D3468145F6C /* Pods-Messaging_Tests_iOS.release.xcconfig */,
- 3E26CB853AB2CAF1960A0F71 /* Pods-Storage_Example_iOS.debug.xcconfig */,
- 97790B1C788991008685954F /* Pods-Storage_Example_iOS.release.xcconfig */,
- 24B879B03BD82C7DE771CA61 /* Pods-Storage_Example_macOS.debug.xcconfig */,
- FD3AEF097DFCF2ADAC345D2A /* Pods-Storage_Example_macOS.release.xcconfig */,
- 4ECAA105379B7E664C7FF223 /* Pods-Storage_IntegrationTests_iOS.debug.xcconfig */,
- 5CA5A85B5A80F118F3247910 /* Pods-Storage_IntegrationTests_iOS.release.xcconfig */,
- C1520E81B1BFD24ED1882137 /* Pods-Storage_IntegrationTests_macOS.debug.xcconfig */,
- B0895BC929D50B20A69CEEEF /* Pods-Storage_IntegrationTests_macOS.release.xcconfig */,
- 466C3694B6C68F69BA4DA448 /* Pods-Storage_Tests_iOS.debug.xcconfig */,
- B1EFE04FF3C9650984C5E3C3 /* Pods-Storage_Tests_iOS.release.xcconfig */,
- 4B490EFB675400675CA98196 /* Pods-Storage_Tests_macOS.debug.xcconfig */,
- E0EF5EDDB1FD839F03FC02AA /* Pods-Storage_Tests_macOS.release.xcconfig */,
- 77FA2AB612D17244983008F7 /* Pods-Analytics_Tests_iOS.debug.xcconfig */,
- 456CD9478A63272C4397975E /* Pods-Analytics_Tests_iOS.release.xcconfig */,
- );
- name = Pods;
- sourceTree = "<group>";
- };
6003F581195388D10070C39A = {
isa = PBXGroup;
children = (
@@ -1513,7 +1593,6 @@
DEDFEFED1FD1B8C100F7D466 /* Analytics_Tests_iOS */,
6003F58C195388D20070C39A /* Frameworks */,
6003F58B195388D20070C39A /* Products */,
- 2A06FEEED3D48AAD710413A7 /* Pods */,
);
sourceTree = "<group>";
};
@@ -1546,7 +1625,16 @@
DE26D25D1F7049F1004AE1D3 /* Auth_ApiTests.xctest */,
DE26D26D1F705C35004AE1D3 /* Auth_EarlGreyTests.xctest */,
DE26D27D1F705EC7004AE1D3 /* SwiftSample.app */,
+ DEAAD3811FBA11270053BF48 /* Core_Example_tvOS.app */,
+ DEAAD3951FBA11270053BF48 /* Core_Tests_tvOS.xctest */,
+ DEAAD3E11FBA46AA0053BF48 /* Storage_Example_tvOS.app */,
+ DEAAD3F51FBA46AB0053BF48 /* Storage_Tests_tvOS.xctest */,
+ DE1CD5971FBA55AF00FC031E /* Database_Example_tvOS.app */,
+ DE1EC27F1FBA5E63007D18D8 /* Database_Tests_tvOS.xctest */,
+ DE53893E1FBB62E100199FC2 /* Auth_Tests_tvOS.xctest */,
+ DE1FAE901FBCF5E100897AAA /* Auth_Example_tvOS.app */,
DEDFEFEC1FD1B8C100F7D466 /* Analytics_Tests_iOS.xctest */,
+ DE47C0ED207AC87D00B1AEDF /* Messaging_Sample_iOS.app */,
);
name = Products;
sourceTree = "<group>";
@@ -1554,38 +1642,15 @@
6003F58C195388D20070C39A /* Frameworks */ = {
isa = PBXGroup;
children = (
+ 923F8250206C4DC500034974 /* UserNotifications.framework */,
+ 923F824B206C4D8000034974 /* SafariServices.framework */,
+ 923F824D206C4D8B00034974 /* SafariServices.framework */,
+ D09005301EDB32D600154410 /* OCMock-iOS/OCMock.framework */,
DE45C6641E7DA8CB009E6ACD /* XCTest.framework */,
DEB61E781E7C542600C04B96 /* libsqlite3.tbd */,
6003F58D195388D20070C39A /* Foundation.framework */,
6003F58F195388D20070C39A /* CoreGraphics.framework */,
6003F591195388D20070C39A /* UIKit.framework */,
- 93491C87A390AB737849677E /* Pods_Auth_ApiTests.framework */,
- BDE126A5F0AFCA9956A4C5D9 /* Pods_Auth_EarlGreyTests.framework */,
- 4373FD322206E44A7CADC2A8 /* Pods_Auth_Example_iOS.framework */,
- F7A35264721D3C8D9F96F91C /* Pods_Auth_Example_macOS.framework */,
- D837E35351C0B844EA64B959 /* Pods_Auth_Sample.framework */,
- B0C52364C5FCE5D3FBAEAF83 /* Pods_Auth_SwiftSample.framework */,
- 0ABC4448E5917769098A4EEF /* Pods_Auth_Tests_iOS.framework */,
- 1015568683E2DFCC2719C754 /* Pods_Auth_Tests_macOS.framework */,
- 68657030253DE5895E124F45 /* Pods_Core_Example_iOS.framework */,
- 84C51650773603D9F827CB3F /* Pods_Core_Example_macOS.framework */,
- 3B9B568851BEE22B7FCB61FB /* Pods_Core_Tests_iOS.framework */,
- 86062A14985F49A3B99614A7 /* Pods_Core_Tests_macOS.framework */,
- 66544F771D53687DD73F8BE8 /* Pods_Database_Example_iOS.framework */,
- 7CF25C495716B3441849720B /* Pods_Database_Example_macOS.framework */,
- DFC35EAB61A983056BE25D28 /* Pods_Database_IntegrationTests_iOS.framework */,
- 81C1ABDD7BB039B4BF06A29E /* Pods_Database_IntegrationTests_macOS.framework */,
- 4852BF989C85671F5D7EBD2A /* Pods_Database_Tests_iOS.framework */,
- DE9C3D75207E5D1DC70BB364 /* Pods_Database_Tests_macOS.framework */,
- B0C1478ED7269FF2107F5B6F /* Pods_Messaging_Example_iOS.framework */,
- 6CCB3CEB7BF2E4994FBDB2E7 /* Pods_Messaging_Tests_iOS.framework */,
- 3742438A60FCFAAF24CDE751 /* Pods_Storage_Example_iOS.framework */,
- 8F27F07800D6FD739BD094D3 /* Pods_Storage_Example_macOS.framework */,
- 979DF124E3D1146A81188F78 /* Pods_Storage_IntegrationTests_iOS.framework */,
- 6A28B39B3D707677EF59C110 /* Pods_Storage_IntegrationTests_macOS.framework */,
- A8E6527440C118AC7C21D504 /* Pods_Storage_Tests_iOS.framework */,
- 910D7A5DE7D5AF153328D243 /* Pods_Storage_Tests_macOS.framework */,
- 29606A0F0B0782779D4E73A1 /* Pods_Analytics_Tests_iOS.framework */,
);
name = Frameworks;
sourceTree = "<group>";
@@ -1609,15 +1674,6 @@
name = Shared;
sourceTree = "<group>";
};
- AFD562F71EB13CC700EA2233 /* App */ = {
- isa = PBXGroup;
- children = (
- AFD563131EB1466100EA2233 /* GoogleService-Info.plist */,
- D01853481EDACE1A003A645C /* iOS */,
- );
- name = App;
- sourceTree = "<group>";
- };
D01853461EDACC10003A645C /* iOS */ = {
isa = PBXGroup;
children = (
@@ -1647,23 +1703,6 @@
path = macOS;
sourceTree = "<group>";
};
- D01853481EDACE1A003A645C /* iOS */ = {
- isa = PBXGroup;
- children = (
- AFD563001EB13DF200EA2233 /* Messaging-Info.plist */,
- AFD563141EB29B8C00EA2233 /* Messaging_Example.entitlements */,
- AFD562FF1EB13DF200EA2233 /* AppDelegate.swift */,
- AFD563161EBBEF7B00EA2233 /* Data+MessagingExtensions.swift */,
- AFC8BA9E1EBD51A700B8EEAE /* Environment.swift */,
- AFD563011EB13DF200EA2233 /* MessagingViewController.swift */,
- AFC8BA9C1EBD230E00B8EEAE /* NotificationsController.swift */,
- D01853491EDACED4003A645C /* LaunchScreen.storyboard */,
- D018534B1EDACED4003A645C /* Main.storyboard */,
- );
- name = iOS;
- path = App/iOS;
- sourceTree = "<group>";
- };
D0D857F61ED9ADA8002342D2 /* iOS */ = {
isa = PBXGroup;
children = (
@@ -1751,6 +1790,21 @@
name = macOS;
sourceTree = "<group>";
};
+ DE1EC2891FBA5EB5007D18D8 /* tvOS */ = {
+ isa = PBXGroup;
+ children = (
+ DE1EC28A1FBA5EB5007D18D8 /* AppDelegate.h */,
+ DE1EC28B1FBA5EB5007D18D8 /* AppDelegate.m */,
+ DE1EC28C1FBA5EB5007D18D8 /* Assets.xcassets */,
+ DE1EC28D1FBA5EB5007D18D8 /* Info.plist */,
+ DE1EC28E1FBA5EB5007D18D8 /* main.m */,
+ DE1EC28F1FBA5EB5007D18D8 /* Main.storyboard */,
+ DE1EC2901FBA5EB5007D18D8 /* ViewController.h */,
+ DE1EC2911FBA5EB5007D18D8 /* ViewController.m */,
+ );
+ path = tvOS;
+ sourceTree = "<group>";
+ };
DE26D1C61F70330A004AE1D3 /* ApiTests */ = {
isa = PBXGroup;
children = (
@@ -1835,6 +1889,68 @@
path = SwiftSample;
sourceTree = "<group>";
};
+ DE47C106207AC94A00B1AEDF /* Sample */ = {
+ isa = PBXGroup;
+ children = (
+ DE47C107207AC94A00B1AEDF /* GoogleService-Info.plist */,
+ DE47C108207AC94A00B1AEDF /* iOS */,
+ );
+ path = Sample;
+ sourceTree = "<group>";
+ };
+ DE47C108207AC94A00B1AEDF /* iOS */ = {
+ isa = PBXGroup;
+ children = (
+ DE47C109207AC94A00B1AEDF /* Environment.swift */,
+ DE47C10A207AC94A00B1AEDF /* MessagingViewController.swift */,
+ DE47C10B207AC94A00B1AEDF /* Messaging_Example.entitlements */,
+ DE47C10C207AC94A00B1AEDF /* LaunchScreen.storyboard */,
+ DE47C10E207AC94A00B1AEDF /* Main.storyboard */,
+ DE47C110207AC94A00B1AEDF /* Messaging-Info.plist */,
+ DE47C111207AC94A00B1AEDF /* Data+MessagingExtensions.swift */,
+ DE47C112207AC94A00B1AEDF /* AppDelegate.swift */,
+ DE47C113207AC94A00B1AEDF /* NotificationsController.swift */,
+ );
+ path = iOS;
+ sourceTree = "<group>";
+ };
+ DE47C131207ACAA900B1AEDF /* App */ = {
+ isa = PBXGroup;
+ children = (
+ DE47C133207ACAA900B1AEDF /* iOS */,
+ );
+ path = App;
+ sourceTree = "<group>";
+ };
+ DE47C133207ACAA900B1AEDF /* iOS */ = {
+ isa = PBXGroup;
+ children = (
+ DE47C134207ACAA900B1AEDF /* FIRAppDelegate.h */,
+ DE47C135207ACAA900B1AEDF /* FIRViewController.h */,
+ DE47C136207ACAA900B1AEDF /* LaunchScreen.storyboard */,
+ DE47C138207ACAA900B1AEDF /* Main.storyboard */,
+ DE47C13B207ACAA900B1AEDF /* main.m */,
+ DE47C13C207ACAA900B1AEDF /* FIRAppDelegate.m */,
+ DE47C13D207ACAA900B1AEDF /* FIRViewController.m */,
+ );
+ path = iOS;
+ sourceTree = "<group>";
+ };
+ DE53894B1FBB635400199FC2 /* tvOS */ = {
+ isa = PBXGroup;
+ children = (
+ DE53894C1FBB635400199FC2 /* AppDelegate.h */,
+ DE5389521FBB635400199FC2 /* ViewController.h */,
+ DE53894D1FBB635400199FC2 /* AppDelegate.m */,
+ DE5389501FBB635400199FC2 /* main.m */,
+ DE5389531FBB635400199FC2 /* ViewController.m */,
+ DE53894F1FBB635400199FC2 /* Info.plist */,
+ DE5389511FBB635400199FC2 /* Main.storyboard */,
+ DE53894E1FBB635400199FC2 /* Assets.xcassets */,
+ );
+ path = tvOS;
+ sourceTree = "<group>";
+ };
DE7B8D2A1E8EF202009EB6DF /* Database */ = {
isa = PBXGroup;
children = (
@@ -1848,6 +1964,7 @@
isa = PBXGroup;
children = (
0672F2F11EBBA7D900818E87 /* GoogleService-Info.plist */,
+ DE1EC2891FBA5EB5007D18D8 /* tvOS */,
D0FE8A141ED9C5B2003F6722 /* iOS */,
D0FE8A151ED9C5B8003F6722 /* macOS */,
);
@@ -1871,16 +1988,18 @@
DE7B8D391E8EF202009EB6DF /* Unit */ = {
isa = PBXGroup;
children = (
+ 063CB47A1EBA7AEF00038A59 /* FIRDataSnapshotTests.h */,
+ 063CB4491EBA7AE200038A59 /* FIRMutableDataTests.h */,
+ 063CB44D1EBA7AE200038A59 /* FPathTests.h */,
+ 063CB4551EBA7AE200038A59 /* FSparseSnapshotTests.h */,
+ 063CB4571EBA7AE200038A59 /* FSyncPointTests.h */,
063CB4471EBA7AE200038A59 /* FArraySortedDictionaryTest.m */,
063CB4481EBA7AE200038A59 /* FCompoundHashTest.m */,
063CB46E1EBA7AEF00038A59 /* FCompoundWriteTest.m */,
- 063CB47A1EBA7AEF00038A59 /* FIRDataSnapshotTests.h */,
063CB47B1EBA7AEF00038A59 /* FIRDataSnapshotTests.m */,
- 063CB4491EBA7AE200038A59 /* FIRMutableDataTests.h */,
063CB44A1EBA7AE200038A59 /* FIRMutableDataTests.m */,
063CB44B1EBA7AE200038A59 /* FLevelDBStorageEngineTests.m */,
063CB44C1EBA7AE200038A59 /* FNodeTests.m */,
- 063CB44D1EBA7AE200038A59 /* FPathTests.h */,
063CB44E1EBA7AE200038A59 /* FPathTests.m */,
063CB44F1EBA7AE200038A59 /* FPersistenceManagerTest.m */,
063CB4501EBA7AE200038A59 /* FPruneForestTest.m */,
@@ -1888,9 +2007,7 @@
063CB4521EBA7AE200038A59 /* FQueryParamsTest.m */,
063CB4531EBA7AE200038A59 /* FRangeMergeTest.m */,
063CB4541EBA7AE200038A59 /* FRepoInfoTest.m */,
- 063CB4551EBA7AE200038A59 /* FSparseSnapshotTests.h */,
063CB4561EBA7AE200038A59 /* FSparseSnapshotTests.m */,
- 063CB4571EBA7AE200038A59 /* FSyncPointTests.h */,
063CB4581EBA7AE200038A59 /* FSyncPointTests.m */,
063CB45B1EBA7AE200038A59 /* FTrackedQueryManagerTest.m */,
063CB4901EBA7AEF00038A59 /* FTreeSortedDictionaryTests.m */,
@@ -1903,31 +2020,31 @@
isa = PBXGroup;
children = (
DE7B8D781E8EF202009EB6DF /* FDevice.h */,
- DE7B8D791E8EF202009EB6DF /* FDevice.m */,
DE7B8D7A1E8EF202009EB6DF /* FEventTester.h */,
- DE7B8D7B1E8EF202009EB6DF /* FEventTester.m */,
063CB47D1EBA7AEF00038A59 /* FIRFakeApp.h */,
- 063CB47E1EBA7AEF00038A59 /* FIRFakeApp.m */,
DE7B8D7C1E8EF202009EB6DF /* FIRTestAuthTokenProvider.h */,
- DE7B8D7D1E8EF202009EB6DF /* FIRTestAuthTokenProvider.m */,
DE7B8D7E1E8EF202009EB6DF /* FMockStorageEngine.h */,
- DE7B8D7F1E8EF202009EB6DF /* FMockStorageEngine.m */,
DE7B8D801E8EF202009EB6DF /* FTestAuthTokenGenerator.h */,
- DE7B8D811E8EF202009EB6DF /* FTestAuthTokenGenerator.m */,
063CB4591EBA7AE200038A59 /* FTestBase.h */,
- 063CB45A1EBA7AE200038A59 /* FTestBase.m */,
DE7B8D821E8EF202009EB6DF /* FTestCachePolicy.h */,
- DE7B8D831E8EF202009EB6DF /* FTestCachePolicy.m */,
DE7B8D841E8EF202009EB6DF /* FTestClock.h */,
- DE7B8D851E8EF202009EB6DF /* FTestClock.m */,
063CB48D1EBA7AEF00038A59 /* FTestContants.h */,
DE7B8D861E8EF202009EB6DF /* FTestExpectations.h */,
- DE7B8D871E8EF202009EB6DF /* FTestExpectations.m */,
DE7B8D881E8EF202009EB6DF /* FTestHelpers.h */,
- DE7B8D891E8EF202009EB6DF /* FTestHelpers.m */,
DE7B8D8A1E8EF203009EB6DF /* FTupleEventTypeString.h */,
- DE7B8D8B1E8EF203009EB6DF /* FTupleEventTypeString.m */,
DE7B8D8C1E8EF203009EB6DF /* SenTest+FWaiter.h */,
+ DE7B8D791E8EF202009EB6DF /* FDevice.m */,
+ DE7B8D7B1E8EF202009EB6DF /* FEventTester.m */,
+ 063CB47E1EBA7AEF00038A59 /* FIRFakeApp.m */,
+ DE7B8D7D1E8EF202009EB6DF /* FIRTestAuthTokenProvider.m */,
+ DE7B8D7F1E8EF202009EB6DF /* FMockStorageEngine.m */,
+ DE7B8D811E8EF202009EB6DF /* FTestAuthTokenGenerator.m */,
+ 063CB45A1EBA7AE200038A59 /* FTestBase.m */,
+ DE7B8D831E8EF202009EB6DF /* FTestCachePolicy.m */,
+ DE7B8D851E8EF202009EB6DF /* FTestClock.m */,
+ DE7B8D871E8EF202009EB6DF /* FTestExpectations.m */,
+ DE7B8D891E8EF202009EB6DF /* FTestHelpers.m */,
+ DE7B8D8B1E8EF203009EB6DF /* FTupleEventTypeString.m */,
DE7B8D8D1E8EF203009EB6DF /* SenTest+FWaiter.m */,
);
path = Helpers;
@@ -1949,6 +2066,7 @@
DE9314EC1E86C6FF0083EDBF /* App */ = {
isa = PBXGroup;
children = (
+ DE53894B1FBB635400199FC2 /* tvOS */,
DE9314F61E86C6FF0083EDBF /* GoogleService-Info.plist */,
D01853461EDACC10003A645C /* iOS */,
D01853471EDACC10003A645C /* macOS */,
@@ -1959,28 +2077,27 @@
DE9314F91E86C6FF0083EDBF /* Tests */ = {
isa = PBXGroup;
children = (
- 7E94853F1F578A9D005A3939 /* FIRAuthURLPresenterTests.m */,
- DE750DB51EB3DD4000A75E47 /* FIRAuthAPNSTokenManagerTests.m */,
- DE750DB61EB3DD4000A75E47 /* FIRAuthAPNSTokenTests.m */,
- DE750DB71EB3DD4000A75E47 /* FIRAuthAppCredentialManagerTests.m */,
- DE750DB81EB3DD4000A75E47 /* FIRAuthNotificationManagerTests.m */,
- DE0E5BB91EA7D92E00FAA825 /* FIRVerifyClientRequestTest.m */,
- DE0E5BBA1EA7D92E00FAA825 /* FIRVerifyClientResponseTests.m */,
- DE0E5BB51EA7D91C00FAA825 /* FIRAuthAppCredentialTests.m */,
- DE0E5BB61EA7D91C00FAA825 /* FIRAuthAppDelegateProxyTests.m */,
- DECE03991E9ECFF500164CA4 /* FIRPhoneAuthProviderTests.m */,
DE9314FB1E86C6FF0083EDBF /* FIRApp+FIRAuthUnitTests.h */,
DE9315091E86C6FF0083EDBF /* FIRFakeBackendRPCIssuer.h */,
DE9315231E86C6FF0083EDBF /* OCMStubRecorder+FIRAuthUnitTests.h */,
DE9314FA1E86C6FF0083EDBF /* FIRAdditionalUserInfoTests.m */,
DE9314FC1E86C6FF0083EDBF /* FIRApp+FIRAuthUnitTests.m */,
+ DE750DB51EB3DD4000A75E47 /* FIRAuthAPNSTokenManagerTests.m */,
+ DE750DB61EB3DD4000A75E47 /* FIRAuthAPNSTokenTests.m */,
+ DE750DB71EB3DD4000A75E47 /* FIRAuthAppCredentialManagerTests.m */,
+ DE0E5BB51EA7D91C00FAA825 /* FIRAuthAppCredentialTests.m */,
+ DE0E5BB61EA7D91C00FAA825 /* FIRAuthAppDelegateProxyTests.m */,
DE9314FD1E86C6FF0083EDBF /* FIRAuthBackendCreateAuthURITests.m */,
DE9314FE1E86C6FF0083EDBF /* FIRAuthBackendRPCImplementationTests.m */,
DE9314FF1E86C6FF0083EDBF /* FIRAuthDispatcherTests.m */,
DE9315001E86C6FF0083EDBF /* FIRAuthGlobalWorkQueueTests.m */,
DE9315011E86C6FF0083EDBF /* FIRAuthKeychainTests.m */,
+ DE750DB81EB3DD4000A75E47 /* FIRAuthNotificationManagerTests.m */,
+ 7EE21F791FE89193009B1370 /* FIREmailLinkRequestTests.m */,
DE9315021E86C6FF0083EDBF /* FIRAuthSerialTaskQueueTests.m */,
DE9315031E86C6FF0083EDBF /* FIRAuthTests.m */,
+ 7E94853F1F578A9D005A3939 /* FIRAuthURLPresenterTests.m */,
+ 7EE21F7B1FE8919D009B1370 /* FIREmailLinkSignInResponseTests.m */,
DE9315041E86C6FF0083EDBF /* FIRAuthUserDefaultsStorageTests.m */,
DE9315051E86C6FF0083EDBF /* FIRCreateAuthURIRequestTests.m */,
DE9315061E86C6FF0083EDBF /* FIRCreateAuthURIResponseTests.m */,
@@ -1994,6 +2111,7 @@
D92C82C61F578DF000D5EAFF /* FIRGetProjectConfigRequestTests.m */,
D92C82C51F578DF000D5EAFF /* FIRGetProjectConfigResponseTests.m */,
DE93150F1E86C6FF0083EDBF /* FIRGitHubAuthProviderTests.m */,
+ DECE03991E9ECFF500164CA4 /* FIRPhoneAuthProviderTests.m */,
DE9315111E86C6FF0083EDBF /* FIRResetPasswordRequestTests.m */,
DE9315121E86C6FF0083EDBF /* FIRResetPasswordResponseTests.m */,
DE9315131E86C6FF0083EDBF /* FIRSendVerificationCodeRequestTests.m */,
@@ -2003,9 +2121,12 @@
DE9315171E86C6FF0083EDBF /* FIRSignUpNewUserRequestTests.m */,
DE9315181E86C6FF0083EDBF /* FIRSignUpNewUserResponseTests.m */,
DE9315191E86C6FF0083EDBF /* FIRTwitterAuthProviderTests.m */,
+ 7EFA2E031F71C93300DD354F /* FIRUserMetadataTests.m */,
DE93151A1E86C6FF0083EDBF /* FIRUserTests.m */,
DE93151B1E86C6FF0083EDBF /* FIRVerifyAssertionRequestTests.m */,
DE93151C1E86C6FF0083EDBF /* FIRVerifyAssertionResponseTests.m */,
+ DE0E5BB91EA7D92E00FAA825 /* FIRVerifyClientRequestTest.m */,
+ DE0E5BBA1EA7D92E00FAA825 /* FIRVerifyClientResponseTests.m */,
DE93151D1E86C6FF0083EDBF /* FIRVerifyCustomTokenRequestTests.m */,
DE93151E1E86C6FF0083EDBF /* FIRVerifyCustomTokenResponseTests.m */,
DE93151F1E86C6FF0083EDBF /* FIRVerifyPasswordRequestTest.m */,
@@ -2014,7 +2135,6 @@
DE9315221E86C6FF0083EDBF /* FIRVerifyPhoneNumberResponseTests.m */,
DE9315241E86C6FF0083EDBF /* OCMStubRecorder+FIRAuthUnitTests.m */,
DE9315251E86C6FF0083EDBF /* Tests-Info.plist */,
- 7EFA2E031F71C93300DD354F /* FIRUserMetadataTests.m */,
);
path = Tests;
sourceTree = "<group>";
@@ -2022,8 +2142,9 @@
DE9315B41E8738B70083EDBF /* Messaging */ = {
isa = PBXGroup;
children = (
+ DE47C131207ACAA900B1AEDF /* App */,
+ DE47C106207AC94A00B1AEDF /* Sample */,
AFC8BAA11EC257D700B8EEAE /* Messaging_Example-Bridging-Header.h */,
- AFD562F71EB13CC700EA2233 /* App */,
DE9315C21E8738B70083EDBF /* Tests */,
);
path = Messaging;
@@ -2058,6 +2179,36 @@
path = Tests;
sourceTree = "<group>";
};
+ DEAAD3B31FBA1CD80053BF48 /* tvOS */ = {
+ isa = PBXGroup;
+ children = (
+ DEAAD3D21FBA1F850053BF48 /* Main.storyboard */,
+ DEAAD3C61FBA1EF90053BF48 /* AppDelegate.h */,
+ DEAAD3C81FBA1EFA0053BF48 /* AppDelegate.m */,
+ DEAAD3C51FBA1EF90053BF48 /* Assets.xcassets */,
+ DEAAD3C41FBA1EF90053BF48 /* Info.plist */,
+ DEAAD3C71FBA1EF90053BF48 /* ViewController.h */,
+ DEAAD3C91FBA1EFA0053BF48 /* ViewController.m */,
+ DEAAD3BD1FBA1CD80053BF48 /* main.m */,
+ );
+ path = tvOS;
+ sourceTree = "<group>";
+ };
+ DEAAD4111FBA470A0053BF48 /* tvOS */ = {
+ isa = PBXGroup;
+ children = (
+ DEAAD4121FBA470A0053BF48 /* AppDelegate.h */,
+ DEAAD4131FBA470A0053BF48 /* AppDelegate.m */,
+ DEAAD4141FBA470A0053BF48 /* Assets.xcassets */,
+ DEAAD4151FBA470A0053BF48 /* Info.plist */,
+ DEAAD4161FBA470A0053BF48 /* main.m */,
+ DEAAD4171FBA470A0053BF48 /* Main.storyboard */,
+ DEAAD4181FBA470A0053BF48 /* ViewController.h */,
+ DEAAD4191FBA470A0053BF48 /* ViewController.m */,
+ );
+ path = tvOS;
+ sourceTree = "<group>";
+ };
DEB139B31E734D9D00AC236D /* Storage */ = {
isa = PBXGroup;
children = (
@@ -2080,6 +2231,7 @@
DEB61EB81E7C5DBB00C04B96 /* App */ = {
isa = PBXGroup;
children = (
+ DEAAD4111FBA470A0053BF48 /* tvOS */,
069428801EC3B35A00F7BC69 /* 1mb.dat */,
DEB61EC11E7C5DBB00C04B96 /* GoogleService-Info.plist */,
D0EDB2BA1EDA041900B6C31B /* iOS */,
@@ -2110,6 +2262,7 @@
DEE14D671E844677006FA992 /* App */ = {
isa = PBXGroup;
children = (
+ DEAAD3B31FBA1CD80053BF48 /* tvOS */,
DEE14D711E844677006FA992 /* GoogleService-Info.plist */,
D0D857F71ED9ADAE002342D2 /* macOS */,
D0D857F61ED9ADA8002342D2 /* iOS */,
@@ -2140,13 +2293,10 @@
isa = PBXNativeTarget;
buildConfigurationList = 06121EC51EC399C50008D70E /* Build configuration list for PBXNativeTarget "Storage_IntegrationTests_iOS" */;
buildPhases = (
- C6DD8EA209B18D8651337E5A /* [CP] Check Pods Manifest.lock */,
06121EB81EC399C50008D70E /* Sources */,
06121EB91EC399C50008D70E /* Frameworks */,
06121EBA1EC399C50008D70E /* Resources */,
D090053C1EDB334800154410 /* CopyFiles */,
- D57035FA9DBD9B5C6F7E44BB /* [CP] Embed Pods Frameworks */,
- E4CD99103647D7D03D05576E /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@@ -2162,13 +2312,10 @@
isa = PBXNativeTarget;
buildConfigurationList = 0624F3E81EC0ECFA00E5940D /* Build configuration list for PBXNativeTarget "Database_IntegrationTests_iOS" */;
buildPhases = (
- 964A6DD602ABD81ABDE945E1 /* [CP] Check Pods Manifest.lock */,
0624F3DD1EC0ECFA00E5940D /* Sources */,
0624F3DE1EC0ECFA00E5940D /* Frameworks */,
0624F3DF1EC0ECFA00E5940D /* Resources */,
D09005361EDB331700154410 /* CopyFiles */,
- 2429BCE77F52A3CA917EBAD3 /* [CP] Embed Pods Frameworks */,
- BC5C8E9DA5ECC9095376EEFC /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@@ -2184,12 +2331,9 @@
isa = PBXNativeTarget;
buildConfigurationList = AFD562F41EB13C6D00EA2233 /* Build configuration list for PBXNativeTarget "Messaging_Example_iOS" */;
buildPhases = (
- 4F7410B194A68B6D9F9D1C23 /* [CP] Check Pods Manifest.lock */,
AFD562E11EB13C6D00EA2233 /* Sources */,
AFD562E21EB13C6D00EA2233 /* Frameworks */,
AFD562E31EB13C6D00EA2233 /* Resources */,
- E14B732B18AC2D43E964F013 /* [CP] Embed Pods Frameworks */,
- CB265192F117EE5B7B6A90F3 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@@ -2204,12 +2348,9 @@
isa = PBXNativeTarget;
buildConfigurationList = D01853761EDAD084003A645C /* Build configuration list for PBXNativeTarget "Auth_Example_macOS" */;
buildPhases = (
- BD09C44595A6ECBB8FA2350D /* [CP] Check Pods Manifest.lock */,
D01853691EDAD084003A645C /* Sources */,
D018536D1EDAD084003A645C /* Frameworks */,
D018536F1EDAD084003A645C /* Resources */,
- 61ED313363F4BDC7657BD2EE /* [CP] Embed Pods Frameworks */,
- 547E7E1564F7C15525BE1E84 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@@ -2224,12 +2365,9 @@
isa = PBXNativeTarget;
buildConfigurationList = D01853C31EDAD364003A645C /* Build configuration list for PBXNativeTarget "Auth_Tests_macOS" */;
buildPhases = (
- 38CF5DFC135922889EC15A84 /* [CP] Check Pods Manifest.lock */,
D018538C1EDAD364003A645C /* Sources */,
D01853BD1EDAD364003A645C /* Frameworks */,
D01853C01EDAD364003A645C /* Resources */,
- 2144CF65BA13A617A6510316 /* [CP] Embed Pods Frameworks */,
- 68EBBBF8D47F1A4548F5F8A1 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@@ -2245,12 +2383,9 @@
isa = PBXNativeTarget;
buildConfigurationList = D064E6A71ED9B1BF001956DF /* Build configuration list for PBXNativeTarget "Core_Example_macOS" */;
buildPhases = (
- 1BC0F30C28D9EDC8E02C0412 /* [CP] Check Pods Manifest.lock */,
D064E6921ED9B1BF001956DF /* Sources */,
D064E6931ED9B1BF001956DF /* Frameworks */,
D064E6941ED9B1BF001956DF /* Resources */,
- 062EEAC29DE0575BF611178E /* [CP] Embed Pods Frameworks */,
- 952D8E3BC2A768AF99B029D4 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@@ -2265,12 +2400,9 @@
isa = PBXNativeTarget;
buildConfigurationList = D064E6BC1ED9B31C001956DF /* Build configuration list for PBXNativeTarget "Core_Tests_macOS" */;
buildPhases = (
- 24BCD9BFDB0623B2BA73098C /* [CP] Check Pods Manifest.lock */,
D064E6AE1ED9B31C001956DF /* Sources */,
D064E6B61ED9B31C001956DF /* Frameworks */,
D064E6B91ED9B31C001956DF /* Resources */,
- 7ADF89772D7C70DB74EF0384 /* [CP] Embed Pods Frameworks */,
- FFAC5DC18B783B814EF2DAB5 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@@ -2286,12 +2418,9 @@
isa = PBXNativeTarget;
buildConfigurationList = D0EDB2CA1EDA04F800B6C31B /* Build configuration list for PBXNativeTarget "Storage_Example_macOS" */;
buildPhases = (
- 00DF54E18B7F0C37010CF5C6 /* [CP] Check Pods Manifest.lock */,
D0EDB2BE1EDA04F800B6C31B /* Sources */,
D0EDB2C21EDA04F800B6C31B /* Frameworks */,
D0EDB2C41EDA04F800B6C31B /* Resources */,
- E1EF4640668E42876CD0680B /* [CP] Embed Pods Frameworks */,
- DE3BC872B0631D9222625218 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@@ -2306,12 +2435,9 @@
isa = PBXNativeTarget;
buildConfigurationList = D0EDB2F31EDA06CB00B6C31B /* Build configuration list for PBXNativeTarget "Storage_Tests_macOS" */;
buildPhases = (
- 9F9A46A840E3517985F97BF7 /* [CP] Check Pods Manifest.lock */,
D0EDB2E21EDA06CB00B6C31B /* Sources */,
D0EDB2ED1EDA06CB00B6C31B /* Frameworks */,
D0EDB2F01EDA06CB00B6C31B /* Resources */,
- 13C0A425999DC1AFAAFA85A2 /* [CP] Embed Pods Frameworks */,
- DC8467211F158C333D6E1851 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@@ -2327,12 +2453,9 @@
isa = PBXNativeTarget;
buildConfigurationList = D0EDB3041EDA06D500B6C31B /* Build configuration list for PBXNativeTarget "Storage_IntegrationTests_macOS" */;
buildPhases = (
- 80A283F697210020814F3349 /* [CP] Check Pods Manifest.lock */,
D0EDB2FC1EDA06D500B6C31B /* Sources */,
D0EDB2FE1EDA06D500B6C31B /* Frameworks */,
D0EDB3011EDA06D500B6C31B /* Resources */,
- 7EF78DCE613E776ECD9373C2 /* [CP] Embed Pods Frameworks */,
- 8263B27A42441EB177C87183 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@@ -2348,12 +2471,9 @@
isa = PBXNativeTarget;
buildConfigurationList = D0FE8A2C1ED9C804003F6722 /* Build configuration list for PBXNativeTarget "Database_Example_macOS" */;
buildPhases = (
- 08057DD8B44996A54EB74007 /* [CP] Check Pods Manifest.lock */,
D0FE8A201ED9C804003F6722 /* Sources */,
D0FE8A241ED9C804003F6722 /* Frameworks */,
D0FE8A261ED9C804003F6722 /* Resources */,
- EBD8971C0B3B5BCDA5F5EA9F /* [CP] Embed Pods Frameworks */,
- 89FC9C3CF79117EF07001272 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@@ -2368,12 +2488,9 @@
isa = PBXNativeTarget;
buildConfigurationList = D0FE8A5F1ED9C86F003F6722 /* Build configuration list for PBXNativeTarget "Database_Tests_macOS" */;
buildPhases = (
- 83EC34B122E68BACA199C8BD /* [CP] Check Pods Manifest.lock */,
D0FE8A351ED9C86F003F6722 /* Sources */,
D0FE8A561ED9C86F003F6722 /* Frameworks */,
D0FE8A591ED9C86F003F6722 /* Resources */,
- B5D70414394DD066BE115ED8 /* [CP] Embed Pods Frameworks */,
- 8AC1F8EDC83D64A46940EA1F /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@@ -2389,12 +2506,9 @@
isa = PBXNativeTarget;
buildConfigurationList = D0FE8A891ED9C87B003F6722 /* Build configuration list for PBXNativeTarget "Database_IntegrationTests_macOS" */;
buildPhases = (
- 1D62AD16C337F524407C8EBF /* [CP] Check Pods Manifest.lock */,
D0FE8A681ED9C87B003F6722 /* Sources */,
D0FE8A831ED9C87B003F6722 /* Frameworks */,
D0FE8A861ED9C87B003F6722 /* Resources */,
- 3261343A30C97057EDCB6749 /* [CP] Embed Pods Frameworks */,
- 2D6499C61F0A16EE67985F35 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@@ -2406,16 +2520,65 @@
productReference = D0FE8A8C1ED9C87B003F6722 /* Database_IntegrationTests_macOS.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
+ DE1CD5961FBA55AF00FC031E /* Database_Example_tvOS */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = DE1CD5B61FBA55B000FC031E /* Build configuration list for PBXNativeTarget "Database_Example_tvOS" */;
+ buildPhases = (
+ DE1CD5931FBA55AF00FC031E /* Sources */,
+ DE1CD5941FBA55AF00FC031E /* Frameworks */,
+ DE1CD5951FBA55AF00FC031E /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = Database_Example_tvOS;
+ productName = Database_Example_tvOS;
+ productReference = DE1CD5971FBA55AF00FC031E /* Database_Example_tvOS.app */;
+ productType = "com.apple.product-type.application";
+ };
+ DE1EC27E1FBA5E63007D18D8 /* Database_Tests_tvOS */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = DE1EC2861FBA5E63007D18D8 /* Build configuration list for PBXNativeTarget "Database_Tests_tvOS" */;
+ buildPhases = (
+ DE1EC27B1FBA5E63007D18D8 /* Sources */,
+ DE1EC27C1FBA5E63007D18D8 /* Frameworks */,
+ DE1EC27D1FBA5E63007D18D8 /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ DE1EC2851FBA5E63007D18D8 /* PBXTargetDependency */,
+ );
+ name = Database_Tests_tvOS;
+ productName = Database_Tests_tvOS;
+ productReference = DE1EC27F1FBA5E63007D18D8 /* Database_Tests_tvOS.xctest */;
+ productType = "com.apple.product-type.bundle.unit-test";
+ };
+ DE1FAE8F1FBCF5E100897AAA /* Auth_Example_tvOS */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = DE1FAEAB1FBCF5E200897AAA /* Build configuration list for PBXNativeTarget "Auth_Example_tvOS" */;
+ buildPhases = (
+ DE1FAE8C1FBCF5E100897AAA /* Sources */,
+ DE1FAE8D1FBCF5E100897AAA /* Frameworks */,
+ DE1FAE8E1FBCF5E100897AAA /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = Auth_Example_tvOS;
+ productName = Auth_Example_tvOS;
+ productReference = DE1FAE901FBCF5E100897AAA /* Auth_Example_tvOS.app */;
+ productType = "com.apple.product-type.application";
+ };
DE26D22D1F70398A004AE1D3 /* Auth_Sample */ = {
isa = PBXNativeTarget;
buildConfigurationList = DE26D2411F70398A004AE1D3 /* Build configuration list for PBXNativeTarget "Auth_Sample" */;
buildPhases = (
- 2F058EA64448194D0606AF43 /* [CP] Check Pods Manifest.lock */,
DE26D22A1F70398A004AE1D3 /* Sources */,
DE26D22B1F70398A004AE1D3 /* Frameworks */,
DE26D22C1F70398A004AE1D3 /* Resources */,
- C4CB228F9FBF834637FDD550 /* [CP] Embed Pods Frameworks */,
- 7C60623F83982B8C98AD8A03 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@@ -2430,12 +2593,9 @@
isa = PBXNativeTarget;
buildConfigurationList = DE26D2641F7049F1004AE1D3 /* Build configuration list for PBXNativeTarget "Auth_ApiTests" */;
buildPhases = (
- 94DE13FC01F4BCC0A3AA92B8 /* [CP] Check Pods Manifest.lock */,
DE26D2591F7049F1004AE1D3 /* Sources */,
DE26D25A1F7049F1004AE1D3 /* Frameworks */,
DE26D25B1F7049F1004AE1D3 /* Resources */,
- 3EC098CF0777B0916DAB8AE7 /* [CP] Embed Pods Frameworks */,
- 418D23667D638E23FC6765B0 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@@ -2451,12 +2611,9 @@
isa = PBXNativeTarget;
buildConfigurationList = DE26D2741F705C35004AE1D3 /* Build configuration list for PBXNativeTarget "Auth_EarlGreyTests" */;
buildPhases = (
- B6C7305ECEA2DA69869E3199 /* [CP] Check Pods Manifest.lock */,
DE26D2691F705C35004AE1D3 /* Sources */,
DE26D26A1F705C35004AE1D3 /* Frameworks */,
DE26D26B1F705C35004AE1D3 /* Resources */,
- F5B5EF87DFA0131455579138 /* [CP] Embed Pods Frameworks */,
- EB62267583A8460DAF576DF9 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@@ -2472,12 +2629,9 @@
isa = PBXNativeTarget;
buildConfigurationList = DE26D28C1F705EC7004AE1D3 /* Build configuration list for PBXNativeTarget "Auth_SwiftSample" */;
buildPhases = (
- 2A3CED73D6CF971623B65B11 /* [CP] Check Pods Manifest.lock */,
DE26D2791F705EC7004AE1D3 /* Sources */,
DE26D27A1F705EC7004AE1D3 /* Frameworks */,
DE26D27B1F705EC7004AE1D3 /* Resources */,
- A397E143BFA2B54BBA78DB27 /* [CP] Embed Pods Frameworks */,
- DC71E8C018300A993E541683 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@@ -2488,16 +2642,48 @@
productReference = DE26D27D1F705EC7004AE1D3 /* SwiftSample.app */;
productType = "com.apple.product-type.application";
};
+ DE47C0DC207AC87D00B1AEDF /* Messaging_Sample_iOS */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = DE47C0EA207AC87D00B1AEDF /* Build configuration list for PBXNativeTarget "Messaging_Sample_iOS" */;
+ buildPhases = (
+ DE47C0DD207AC87D00B1AEDF /* Sources */,
+ DE47C0E4207AC87D00B1AEDF /* Frameworks */,
+ DE47C0E5207AC87D00B1AEDF /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = Messaging_Sample_iOS;
+ productName = Messaging_Example_iOS;
+ productReference = DE47C0ED207AC87D00B1AEDF /* Messaging_Sample_iOS.app */;
+ productType = "com.apple.product-type.application";
+ };
+ DE53893D1FBB62E100199FC2 /* Auth_Tests_tvOS */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = DE5389491FBB62E100199FC2 /* Build configuration list for PBXNativeTarget "Auth_Tests_tvOS" */;
+ buildPhases = (
+ DE53893A1FBB62E100199FC2 /* Sources */,
+ DE53893B1FBB62E100199FC2 /* Frameworks */,
+ DE53893C1FBB62E100199FC2 /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ DE1E3B311FEB1E7600EAEBB0 /* PBXTargetDependency */,
+ );
+ name = Auth_Tests_tvOS;
+ productName = Auth_Example_tvOSTests;
+ productReference = DE53893E1FBB62E100199FC2 /* Auth_Tests_tvOS.xctest */;
+ productType = "com.apple.product-type.bundle.unit-test";
+ };
DE7B8D041E8EF077009EB6DF /* Database_Example_iOS */ = {
isa = PBXNativeTarget;
buildConfigurationList = DE7B8D281E8EF078009EB6DF /* Build configuration list for PBXNativeTarget "Database_Example_iOS" */;
buildPhases = (
- 4B926E79AD9E0016DDA7FBF8 /* [CP] Check Pods Manifest.lock */,
DE7B8D011E8EF077009EB6DF /* Sources */,
DE7B8D021E8EF077009EB6DF /* Frameworks */,
DE7B8D031E8EF077009EB6DF /* Resources */,
- 7F7E65CEFB1C184D69448404 /* [CP] Embed Pods Frameworks */,
- B5724E5E5CB63BD5B738C4F6 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@@ -2512,13 +2698,9 @@
isa = PBXNativeTarget;
buildConfigurationList = DE7B8D291E8EF078009EB6DF /* Build configuration list for PBXNativeTarget "Database_Tests_iOS" */;
buildPhases = (
- FF06F317B2790040C6157248 /* [CP] Check Pods Manifest.lock */,
DE7B8D191E8EF078009EB6DF /* Sources */,
DE7B8D1A1E8EF078009EB6DF /* Frameworks */,
DE7B8D1B1E8EF078009EB6DF /* Resources */,
- D09005341EDB330800154410 /* CopyFiles */,
- 2F2E08FC97C14E7EDDB8D804 /* [CP] Embed Pods Frameworks */,
- 91ED54292706C89DEAF738F0 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@@ -2534,12 +2716,9 @@
isa = PBXNativeTarget;
buildConfigurationList = DE9314E91E86C6BE0083EDBF /* Build configuration list for PBXNativeTarget "Auth_Example_iOS" */;
buildPhases = (
- 2BE22FBF4B4647B6AF2A58D2 /* [CP] Check Pods Manifest.lock */,
DE9314C21E86C6BD0083EDBF /* Sources */,
DE9314C31E86C6BD0083EDBF /* Frameworks */,
DE9314C41E86C6BD0083EDBF /* Resources */,
- C0BB541B4E2E4188148B74BF /* [CP] Embed Pods Frameworks */,
- 3F2AF12006B4163F0BCBD5E2 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@@ -2554,13 +2733,9 @@
isa = PBXNativeTarget;
buildConfigurationList = DE9314EA1E86C6BE0083EDBF /* Build configuration list for PBXNativeTarget "Auth_Tests_iOS" */;
buildPhases = (
- C981CB657374F18444683DDE /* [CP] Check Pods Manifest.lock */,
DE9314DA1E86C6BE0083EDBF /* Sources */,
DE9314DB1E86C6BE0083EDBF /* Frameworks */,
DE9314DC1E86C6BE0083EDBF /* Resources */,
- D090052F1EDB32B700154410 /* CopyFiles */,
- 8489BA940D280D954CF784DF /* [CP] Embed Pods Frameworks */,
- 194262A69E6C961D8D1888EB /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@@ -2576,13 +2751,9 @@
isa = PBXNativeTarget;
buildConfigurationList = DE9315B31E8738460083EDBF /* Build configuration list for PBXNativeTarget "Messaging_Tests_iOS" */;
buildPhases = (
- 310F0C6DEC04131DD5D31B3C /* [CP] Check Pods Manifest.lock */,
DE9315A31E8738460083EDBF /* Sources */,
DE9315A41E8738460083EDBF /* Frameworks */,
DE9315A51E8738460083EDBF /* Resources */,
- D09005381EDB333700154410 /* CopyFiles */,
- D736CA94F00AB417403CEC0D /* [CP] Embed Pods Frameworks */,
- 55298685299C7889EDDFF818 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@@ -2594,16 +2765,83 @@
productReference = DE9315A71E8738460083EDBF /* Messaging_Tests_iOS.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
+ DEAAD3801FBA11270053BF48 /* Core_Example_tvOS */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = DEAAD3A01FBA11280053BF48 /* Build configuration list for PBXNativeTarget "Core_Example_tvOS" */;
+ buildPhases = (
+ DEAAD37D1FBA11270053BF48 /* Sources */,
+ DEAAD37E1FBA11270053BF48 /* Frameworks */,
+ DEAAD37F1FBA11270053BF48 /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = Core_Example_tvOS;
+ productName = Core_Example_tvOS;
+ productReference = DEAAD3811FBA11270053BF48 /* Core_Example_tvOS.app */;
+ productType = "com.apple.product-type.application";
+ };
+ DEAAD3941FBA11270053BF48 /* Core_Tests_tvOS */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = DEAAD3A11FBA11280053BF48 /* Build configuration list for PBXNativeTarget "Core_Tests_tvOS" */;
+ buildPhases = (
+ DEAAD3911FBA11270053BF48 /* Sources */,
+ DEAAD3921FBA11270053BF48 /* Frameworks */,
+ DEAAD3931FBA11270053BF48 /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ DEAAD3971FBA11280053BF48 /* PBXTargetDependency */,
+ );
+ name = Core_Tests_tvOS;
+ productName = Core_Example_tvOSTests;
+ productReference = DEAAD3951FBA11270053BF48 /* Core_Tests_tvOS.xctest */;
+ productType = "com.apple.product-type.bundle.unit-test";
+ };
+ DEAAD3E01FBA46AA0053BF48 /* Storage_Example_tvOS */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = DEAAD4001FBA46AB0053BF48 /* Build configuration list for PBXNativeTarget "Storage_Example_tvOS" */;
+ buildPhases = (
+ DEAAD3DD1FBA46AA0053BF48 /* Sources */,
+ DEAAD3DE1FBA46AA0053BF48 /* Frameworks */,
+ DEAAD3DF1FBA46AA0053BF48 /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = Storage_Example_tvOS;
+ productName = Storage_Example_tvOS;
+ productReference = DEAAD3E11FBA46AA0053BF48 /* Storage_Example_tvOS.app */;
+ productType = "com.apple.product-type.application";
+ };
+ DEAAD3F41FBA46AB0053BF48 /* Storage_Tests_tvOS */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = DEAAD4011FBA46AB0053BF48 /* Build configuration list for PBXNativeTarget "Storage_Tests_tvOS" */;
+ buildPhases = (
+ DEAAD3F11FBA46AB0053BF48 /* Sources */,
+ DEAAD3F21FBA46AB0053BF48 /* Frameworks */,
+ DEAAD3F31FBA46AB0053BF48 /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ DEAAD3F71FBA46AB0053BF48 /* PBXTargetDependency */,
+ );
+ name = Storage_Tests_tvOS;
+ productName = Storage_Example_tvOSTests;
+ productReference = DEAAD3F51FBA46AB0053BF48 /* Storage_Tests_tvOS.xctest */;
+ productType = "com.apple.product-type.bundle.unit-test";
+ };
DEB139E01E73506A00AC236D /* Storage_Example_iOS */ = {
isa = PBXNativeTarget;
buildConfigurationList = DEB13A051E73506A00AC236D /* Build configuration list for PBXNativeTarget "Storage_Example_iOS" */;
buildPhases = (
- 0045465B164896D3C570A79A /* [CP] Check Pods Manifest.lock */,
DEB139E21E73506A00AC236D /* Sources */,
DEB139F31E73506A00AC236D /* Frameworks */,
DEB139F91E73506A00AC236D /* Resources */,
- 9DDF848892C1B2DCE343D139 /* [CP] Embed Pods Frameworks */,
- 776FB063FB216F38E91EC8A1 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@@ -2618,13 +2856,9 @@
isa = PBXNativeTarget;
buildConfigurationList = DEB13A201E73507E00AC236D /* Build configuration list for PBXNativeTarget "Storage_Tests_iOS" */;
buildPhases = (
- C0894681AFF0FF07CE891310 /* [CP] Check Pods Manifest.lock */,
DEB13A0E1E73507E00AC236D /* Sources */,
DEB13A161E73507E00AC236D /* Frameworks */,
DEB13A1D1E73507E00AC236D /* Resources */,
- D090053A1EDB334000154410 /* CopyFiles */,
- 5355975898496CD7FF7DD106 /* [CP] Embed Pods Frameworks */,
- B57C4BF2A720187D5DF4C848 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@@ -2640,12 +2874,9 @@
isa = PBXNativeTarget;
buildConfigurationList = DEDFEFF51FD1B8C100F7D466 /* Build configuration list for PBXNativeTarget "Analytics_Tests_iOS" */;
buildPhases = (
- E3A92F1E7F7F65ACF2BCEC78 /* [CP] Check Pods Manifest.lock */,
DEDFEFE81FD1B8C100F7D466 /* Sources */,
DEDFEFE91FD1B8C100F7D466 /* Frameworks */,
DEDFEFEA1FD1B8C100F7D466 /* Resources */,
- DC31836CBCE7DE03EC932511 /* [CP] Embed Pods Frameworks */,
- D293C865CA6C9CFE510F2030 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@@ -2661,12 +2892,9 @@
isa = PBXNativeTarget;
buildConfigurationList = DEE14D641E84464D006FA992 /* Build configuration list for PBXNativeTarget "Core_Example_iOS" */;
buildPhases = (
- 6F3682326BA63694ECB240A3 /* [CP] Check Pods Manifest.lock */,
DEE14D3D1E84464D006FA992 /* Sources */,
DEE14D3E1E84464D006FA992 /* Frameworks */,
DEE14D3F1E84464D006FA992 /* Resources */,
- 4AA54CBB8303130FC18C1A27 /* [CP] Embed Pods Frameworks */,
- BD7302A2861F068F5540CCA6 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@@ -2681,13 +2909,9 @@
isa = PBXNativeTarget;
buildConfigurationList = DEE14D651E84464D006FA992 /* Build configuration list for PBXNativeTarget "Core_Tests_iOS" */;
buildPhases = (
- E4708A6EB45D6F7D30070DCF /* [CP] Check Pods Manifest.lock */,
DEE14D551E84464D006FA992 /* Sources */,
DEE14D561E84464D006FA992 /* Frameworks */,
DEE14D571E84464D006FA992 /* Resources */,
- D09005321EDB32EA00154410 /* CopyFiles */,
- DBE97573DB393D723CD8CDA2 /* [CP] Embed Pods Frameworks */,
- FFBE661125B41DE1A5B115C3 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@@ -2773,6 +2997,22 @@
D0FE8A641ED9C87B003F6722 = {
TestTargetID = D0FE8A1E1ED9C804003F6722;
};
+ DE1CD5961FBA55AF00FC031E = {
+ CreatedOnToolsVersion = 9.1;
+ DevelopmentTeam = EQHXZ8M8AV;
+ ProvisioningStyle = Automatic;
+ };
+ DE1EC27E1FBA5E63007D18D8 = {
+ CreatedOnToolsVersion = 9.1;
+ DevelopmentTeam = EQHXZ8M8AV;
+ ProvisioningStyle = Automatic;
+ TestTargetID = DE1CD5961FBA55AF00FC031E;
+ };
+ DE1FAE8F1FBCF5E100897AAA = {
+ CreatedOnToolsVersion = 9.1;
+ DevelopmentTeam = EQHXZ8M8AV;
+ ProvisioningStyle = Automatic;
+ };
DE26D22D1F70398A004AE1D3 = {
CreatedOnToolsVersion = 9.0;
DevelopmentTeam = EQHXZ8M8AV;
@@ -2810,6 +3050,20 @@
DevelopmentTeam = EQHXZ8M8AV;
ProvisioningStyle = Automatic;
};
+ DE47C0DC207AC87D00B1AEDF = {
+ DevelopmentTeam = EQHXZ8M8AV;
+ };
+ DE53893D1FBB62E100199FC2 = {
+ CreatedOnToolsVersion = 9.1;
+ DevelopmentTeam = EQHXZ8M8AV;
+ ProvisioningStyle = Automatic;
+ TestTargetID = DE5389291FBB62E100199FC2;
+ };
+ DE545C7F1FBCA3F000C637AE = {
+ CreatedOnToolsVersion = 9.1;
+ DevelopmentTeam = EQHXZ8M8AV;
+ ProvisioningStyle = Automatic;
+ };
DE7B8D041E8EF077009EB6DF = {
CreatedOnToolsVersion = 8.3;
ProvisioningStyle = Automatic;
@@ -2834,14 +3088,34 @@
ProvisioningStyle = Automatic;
TestTargetID = AFD562E41EB13C6D00EA2233;
};
+ DEAAD3801FBA11270053BF48 = {
+ CreatedOnToolsVersion = 9.1;
+ DevelopmentTeam = EQHXZ8M8AV;
+ ProvisioningStyle = Automatic;
+ };
+ DEAAD3941FBA11270053BF48 = {
+ CreatedOnToolsVersion = 9.1;
+ DevelopmentTeam = EQHXZ8M8AV;
+ ProvisioningStyle = Automatic;
+ TestTargetID = DEAAD3801FBA11270053BF48;
+ };
+ DEAAD3E01FBA46AA0053BF48 = {
+ CreatedOnToolsVersion = 9.1;
+ DevelopmentTeam = EQHXZ8M8AV;
+ ProvisioningStyle = Automatic;
+ };
+ DEAAD3F41FBA46AB0053BF48 = {
+ CreatedOnToolsVersion = 9.1;
+ DevelopmentTeam = EQHXZ8M8AV;
+ ProvisioningStyle = Automatic;
+ TestTargetID = DEAAD3E01FBA46AA0053BF48;
+ };
DEB13A0A1E73507E00AC236D = {
TestTargetID = DEB139E01E73506A00AC236D;
};
DEDFEFEB1FD1B8C100F7D466 = {
- CreatedOnToolsVersion = 9.1;
DevelopmentTeam = EQHXZ8M8AV;
ProvisioningStyle = Automatic;
- TestTargetID = DEE14D401E84464D006FA992;
};
DEE14D401E84464D006FA992 = {
CreatedOnToolsVersion = 8.2.1;
@@ -2875,22 +3149,30 @@
DE9314DD1E86C6BE0083EDBF /* Auth_Tests_iOS */,
D01853671EDAD084003A645C /* Auth_Example_macOS */,
D01853881EDAD364003A645C /* Auth_Tests_macOS */,
+ DE1FAE8F1FBCF5E100897AAA /* Auth_Example_tvOS */,
+ DE53893D1FBB62E100199FC2 /* Auth_Tests_tvOS */,
DE26D22D1F70398A004AE1D3 /* Auth_Sample */,
DE26D27C1F705EC7004AE1D3 /* Auth_SwiftSample */,
DE26D25C1F7049F1004AE1D3 /* Auth_ApiTests */,
DE26D26C1F705C35004AE1D3 /* Auth_EarlGreyTests */,
+ DE26D2971F70668F004AE1D3 /* Auth_AllTests */,
DEE14D401E84464D006FA992 /* Core_Example_iOS */,
DEE14D581E84464D006FA992 /* Core_Tests_iOS */,
D064E6951ED9B1BF001956DF /* Core_Example_macOS */,
D064E6AA1ED9B31C001956DF /* Core_Tests_macOS */,
+ DEAAD3801FBA11270053BF48 /* Core_Example_tvOS */,
+ DEAAD3941FBA11270053BF48 /* Core_Tests_tvOS */,
DE7B8D041E8EF077009EB6DF /* Database_Example_iOS */,
DE7B8D1C1E8EF078009EB6DF /* Database_Tests_iOS */,
0624F3E01EC0ECFA00E5940D /* Database_IntegrationTests_iOS */,
D0FE8A1E1ED9C804003F6722 /* Database_Example_macOS */,
D0FE8A311ED9C86F003F6722 /* Database_Tests_macOS */,
D0FE8A641ED9C87B003F6722 /* Database_IntegrationTests_macOS */,
+ DE1CD5961FBA55AF00FC031E /* Database_Example_tvOS */,
+ DE1EC27E1FBA5E63007D18D8 /* Database_Tests_tvOS */,
AFD562E41EB13C6D00EA2233 /* Messaging_Example_iOS */,
DE9315A61E8738460083EDBF /* Messaging_Tests_iOS */,
+ DE47C0DC207AC87D00B1AEDF /* Messaging_Sample_iOS */,
DEB139E01E73506A00AC236D /* Storage_Example_iOS */,
DEB13A0A1E73507E00AC236D /* Storage_Tests_iOS */,
06121EBB1EC399C50008D70E /* Storage_IntegrationTests_iOS */,
@@ -2898,9 +3180,11 @@
D0EDB2DE1EDA06CB00B6C31B /* Storage_Tests_macOS */,
D0EDB2F81EDA06D500B6C31B /* Storage_IntegrationTests_macOS */,
DEDFEFEB1FD1B8C100F7D466 /* Analytics_Tests_iOS */,
+ DEAAD3E01FBA46AA0053BF48 /* Storage_Example_tvOS */,
+ DEAAD3F41FBA46AB0053BF48 /* Storage_Tests_tvOS */,
DE3373891E73773400881891 /* AllUnitTests_iOS */,
D0FE8A041ED9C32C003F6722 /* AllUnitTests_macOS */,
- DE26D2971F70668F004AE1D3 /* Auth_AllTests */,
+ DE545C7F1FBCA3F000C637AE /* AllUnitTests_tvOS */,
);
};
/* End PBXProject section */
@@ -2924,10 +3208,10 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
- D018534D1EDACED4003A645C /* LaunchScreen.storyboard in Resources */,
+ DE47C13F207ACAA900B1AEDF /* LaunchScreen.storyboard in Resources */,
+ DEA7795D207ACC8000245121 /* GoogleService-Info.plist in Resources */,
AFAF36F81EC28C25004BDEE5 /* Shared.xcassets in Resources */,
- AFD563151EB29EDE00EA2233 /* GoogleService-Info.plist in Resources */,
- D018534E1EDACED4003A645C /* Main.storyboard in Resources */,
+ DE47C140207ACAA900B1AEDF /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -3017,6 +3301,35 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ DE1CD5951FBA55AF00FC031E /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ DE1EC2961FBA5EB5007D18D8 /* Main.storyboard in Resources */,
+ DE9037291FBA5F2400E239D3 /* GoogleService-Info.plist in Resources */,
+ DE1EC2931FBA5EB5007D18D8 /* Assets.xcassets in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ DE1EC27D1FBA5E63007D18D8 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ DE90374D1FBA70E400E239D3 /* syncPointSpec.json in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ DE1FAE8E1FBCF5E100897AAA /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ DE1FAEB41FBCF60D00897AAA /* Info.plist in Resources */,
+ DE1FAEB71FBCF6CA00897AAA /* GoogleService-Info.plist in Resources */,
+ DE1FAEB51FBCF60D00897AAA /* Main.storyboard in Resources */,
+ DE1FAEB61FBCF60D00897AAA /* Assets.xcassets in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
DE26D22C1F70398A004AE1D3 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
@@ -3057,6 +3370,25 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ DE47C0E5207AC87D00B1AEDF /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ DE47C118207AC94A00B1AEDF /* Main.storyboard in Resources */,
+ DE47C0E7207AC87D00B1AEDF /* Shared.xcassets in Resources */,
+ DE47C114207AC94A00B1AEDF /* GoogleService-Info.plist in Resources */,
+ DE47C117207AC94A00B1AEDF /* LaunchScreen.storyboard in Resources */,
+ DE47C119207AC94A00B1AEDF /* Messaging-Info.plist in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ DE53893C1FBB62E100199FC2 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
DE7B8D031E8EF077009EB6DF /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
@@ -3105,1478 +3437,86 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
- DEB139F91E73506A00AC236D /* Resources */ = {
+ DEAAD37F1FBA11270053BF48 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
- 069428831EC3B38C00F7BC69 /* 1mb.dat in Resources */,
- DEB61EC51E7C5DBB00C04B96 /* LaunchScreen.storyboard in Resources */,
- AFAF36F91EC28C25004BDEE5 /* Shared.xcassets in Resources */,
- DEB61EC61E7C5DBB00C04B96 /* Main.storyboard in Resources */,
- DEB61EC91E7C5DBB00C04B96 /* GoogleService-Info.plist in Resources */,
+ DEAAD3CE1FBA1EFA0053BF48 /* Assets.xcassets in Resources */,
+ DEAAD3DC1FBA36210053BF48 /* GoogleService-Info.plist in Resources */,
+ DEAAD3D41FBA20480053BF48 /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
- DEB13A1D1E73507E00AC236D /* Resources */ = {
+ DEAAD3931FBA11270053BF48 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
- DEDFEFEA1FD1B8C100F7D466 /* Resources */ = {
+ DEAAD3DF1FBA46AA0053BF48 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ DEAAD4201FBA47110053BF48 /* 1mb.dat in Resources */,
+ DEAAD4211FBA49D10053BF48 /* GoogleService-Info.plist in Resources */,
+ DEAAD41E1FBA470B0053BF48 /* Main.storyboard in Resources */,
+ DEAAD41B1FBA470B0053BF48 /* Assets.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
- DEE14D3F1E84464D006FA992 /* Resources */ = {
+ DEAAD3F31FBA46AB0053BF48 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
- AFAF36F61EC28C25004BDEE5 /* Shared.xcassets in Resources */,
- DEE14D831E844677006FA992 /* GoogleService-Info.plist in Resources */,
- DEE14D7E1E844677006FA992 /* LaunchScreen.storyboard in Resources */,
- DEE14D7F1E844677006FA992 /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
- DEE14D571E84464D006FA992 /* Resources */ = {
+ DEB139F91E73506A00AC236D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ 069428831EC3B38C00F7BC69 /* 1mb.dat in Resources */,
+ DEB61EC51E7C5DBB00C04B96 /* LaunchScreen.storyboard in Resources */,
+ AFAF36F91EC28C25004BDEE5 /* Shared.xcassets in Resources */,
+ DEB61EC61E7C5DBB00C04B96 /* Main.storyboard in Resources */,
+ DEB61EC91E7C5DBB00C04B96 /* GoogleService-Info.plist in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
-/* End PBXResourcesBuildPhase section */
-
-/* Begin PBXShellScriptBuildPhase section */
- 0045465B164896D3C570A79A /* [CP] Check Pods Manifest.lock */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
- "${PODS_ROOT}/Manifest.lock",
- );
- name = "[CP] Check Pods Manifest.lock";
- outputPaths = (
- "$(DERIVED_FILE_DIR)/Pods-Storage_Example_iOS-checkManifestLockResult.txt",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
- showEnvVarsInLog = 0;
- };
- 00DF54E18B7F0C37010CF5C6 /* [CP] Check Pods Manifest.lock */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
- "${PODS_ROOT}/Manifest.lock",
- );
- name = "[CP] Check Pods Manifest.lock";
- outputPaths = (
- "$(DERIVED_FILE_DIR)/Pods-Storage_Example_macOS-checkManifestLockResult.txt",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
- showEnvVarsInLog = 0;
- };
- 062EEAC29DE0575BF611178E /* [CP] Embed Pods Frameworks */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- "${SRCROOT}/Pods/Target Support Files/Pods-Core_Example_macOS/Pods-Core_Example_macOS-frameworks.sh",
- "${BUILT_PRODUCTS_DIR}/GoogleToolboxForMac-Defines-NSData+zlib-macOS/GoogleToolboxForMac.framework",
- );
- name = "[CP] Embed Pods Frameworks";
- outputPaths = (
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleToolboxForMac.framework",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Core_Example_macOS/Pods-Core_Example_macOS-frameworks.sh\"\n";
- showEnvVarsInLog = 0;
- };
- 08057DD8B44996A54EB74007 /* [CP] Check Pods Manifest.lock */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
- "${PODS_ROOT}/Manifest.lock",
- );
- name = "[CP] Check Pods Manifest.lock";
- outputPaths = (
- "$(DERIVED_FILE_DIR)/Pods-Database_Example_macOS-checkManifestLockResult.txt",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
- showEnvVarsInLog = 0;
- };
- 13C0A425999DC1AFAAFA85A2 /* [CP] Embed Pods Frameworks */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- "${SRCROOT}/Pods/Target Support Files/Pods-Storage_Tests_macOS/Pods-Storage_Tests_macOS-frameworks.sh",
- "${BUILT_PRODUCTS_DIR}/OCMock-macOS/OCMock.framework",
- );
- name = "[CP] Embed Pods Frameworks";
- outputPaths = (
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OCMock.framework",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Storage_Tests_macOS/Pods-Storage_Tests_macOS-frameworks.sh\"\n";
- showEnvVarsInLog = 0;
- };
- 194262A69E6C961D8D1888EB /* [CP] Copy Pods Resources */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- );
- name = "[CP] Copy Pods Resources";
- outputPaths = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Auth_Tests_iOS/Pods-Auth_Tests_iOS-resources.sh\"\n";
- showEnvVarsInLog = 0;
- };
- 1BC0F30C28D9EDC8E02C0412 /* [CP] Check Pods Manifest.lock */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
- "${PODS_ROOT}/Manifest.lock",
- );
- name = "[CP] Check Pods Manifest.lock";
- outputPaths = (
- "$(DERIVED_FILE_DIR)/Pods-Core_Example_macOS-checkManifestLockResult.txt",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
- showEnvVarsInLog = 0;
- };
- 1D62AD16C337F524407C8EBF /* [CP] Check Pods Manifest.lock */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
- "${PODS_ROOT}/Manifest.lock",
- );
- name = "[CP] Check Pods Manifest.lock";
- outputPaths = (
- "$(DERIVED_FILE_DIR)/Pods-Database_IntegrationTests_macOS-checkManifestLockResult.txt",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
- showEnvVarsInLog = 0;
- };
- 2144CF65BA13A617A6510316 /* [CP] Embed Pods Frameworks */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- "${SRCROOT}/Pods/Target Support Files/Pods-Auth_Tests_macOS/Pods-Auth_Tests_macOS-frameworks.sh",
- "${BUILT_PRODUCTS_DIR}/OCMock-macOS/OCMock.framework",
- );
- name = "[CP] Embed Pods Frameworks";
- outputPaths = (
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OCMock.framework",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Auth_Tests_macOS/Pods-Auth_Tests_macOS-frameworks.sh\"\n";
- showEnvVarsInLog = 0;
- };
- 2429BCE77F52A3CA917EBAD3 /* [CP] Embed Pods Frameworks */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- "${SRCROOT}/Pods/Target Support Files/Pods-Database_IntegrationTests_iOS/Pods-Database_IntegrationTests_iOS-frameworks.sh",
- "${BUILT_PRODUCTS_DIR}/OCMock-iOS/OCMock.framework",
- );
- name = "[CP] Embed Pods Frameworks";
- outputPaths = (
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OCMock.framework",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Database_IntegrationTests_iOS/Pods-Database_IntegrationTests_iOS-frameworks.sh\"\n";
- showEnvVarsInLog = 0;
- };
- 24BCD9BFDB0623B2BA73098C /* [CP] Check Pods Manifest.lock */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
- "${PODS_ROOT}/Manifest.lock",
- );
- name = "[CP] Check Pods Manifest.lock";
- outputPaths = (
- "$(DERIVED_FILE_DIR)/Pods-Core_Tests_macOS-checkManifestLockResult.txt",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
- showEnvVarsInLog = 0;
- };
- 2A3CED73D6CF971623B65B11 /* [CP] Check Pods Manifest.lock */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
- "${PODS_ROOT}/Manifest.lock",
- );
- name = "[CP] Check Pods Manifest.lock";
- outputPaths = (
- "$(DERIVED_FILE_DIR)/Pods-Auth_SwiftSample-checkManifestLockResult.txt",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
- showEnvVarsInLog = 0;
- };
- 2BE22FBF4B4647B6AF2A58D2 /* [CP] Check Pods Manifest.lock */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
- "${PODS_ROOT}/Manifest.lock",
- );
- name = "[CP] Check Pods Manifest.lock";
- outputPaths = (
- "$(DERIVED_FILE_DIR)/Pods-Auth_Example_iOS-checkManifestLockResult.txt",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
- showEnvVarsInLog = 0;
- };
- 2D6499C61F0A16EE67985F35 /* [CP] Copy Pods Resources */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- );
- name = "[CP] Copy Pods Resources";
- outputPaths = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Database_IntegrationTests_macOS/Pods-Database_IntegrationTests_macOS-resources.sh\"\n";
- showEnvVarsInLog = 0;
- };
- 2F058EA64448194D0606AF43 /* [CP] Check Pods Manifest.lock */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
- "${PODS_ROOT}/Manifest.lock",
- );
- name = "[CP] Check Pods Manifest.lock";
- outputPaths = (
- "$(DERIVED_FILE_DIR)/Pods-Auth_Sample-checkManifestLockResult.txt",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
- showEnvVarsInLog = 0;
- };
- 2F2E08FC97C14E7EDDB8D804 /* [CP] Embed Pods Frameworks */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- "${SRCROOT}/Pods/Target Support Files/Pods-Database_Tests_iOS/Pods-Database_Tests_iOS-frameworks.sh",
- "${BUILT_PRODUCTS_DIR}/OCMock-iOS/OCMock.framework",
- );
- name = "[CP] Embed Pods Frameworks";
- outputPaths = (
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OCMock.framework",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Database_Tests_iOS/Pods-Database_Tests_iOS-frameworks.sh\"\n";
- showEnvVarsInLog = 0;
- };
- 310F0C6DEC04131DD5D31B3C /* [CP] Check Pods Manifest.lock */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
- "${PODS_ROOT}/Manifest.lock",
- );
- name = "[CP] Check Pods Manifest.lock";
- outputPaths = (
- "$(DERIVED_FILE_DIR)/Pods-Messaging_Tests_iOS-checkManifestLockResult.txt",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
- showEnvVarsInLog = 0;
- };
- 3261343A30C97057EDCB6749 /* [CP] Embed Pods Frameworks */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- "${SRCROOT}/Pods/Target Support Files/Pods-Database_IntegrationTests_macOS/Pods-Database_IntegrationTests_macOS-frameworks.sh",
- "${BUILT_PRODUCTS_DIR}/OCMock-macOS/OCMock.framework",
- );
- name = "[CP] Embed Pods Frameworks";
- outputPaths = (
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OCMock.framework",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Database_IntegrationTests_macOS/Pods-Database_IntegrationTests_macOS-frameworks.sh\"\n";
- showEnvVarsInLog = 0;
- };
- 38CF5DFC135922889EC15A84 /* [CP] Check Pods Manifest.lock */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
- "${PODS_ROOT}/Manifest.lock",
- );
- name = "[CP] Check Pods Manifest.lock";
- outputPaths = (
- "$(DERIVED_FILE_DIR)/Pods-Auth_Tests_macOS-checkManifestLockResult.txt",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
- showEnvVarsInLog = 0;
- };
- 3EC098CF0777B0916DAB8AE7 /* [CP] Embed Pods Frameworks */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- );
- name = "[CP] Embed Pods Frameworks";
- outputPaths = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Auth_ApiTests/Pods-Auth_ApiTests-frameworks.sh\"\n";
- showEnvVarsInLog = 0;
- };
- 3F2AF12006B4163F0BCBD5E2 /* [CP] Copy Pods Resources */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- );
- name = "[CP] Copy Pods Resources";
- outputPaths = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Auth_Example_iOS/Pods-Auth_Example_iOS-resources.sh\"\n";
- showEnvVarsInLog = 0;
- };
- 418D23667D638E23FC6765B0 /* [CP] Copy Pods Resources */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- );
- name = "[CP] Copy Pods Resources";
- outputPaths = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Auth_ApiTests/Pods-Auth_ApiTests-resources.sh\"\n";
- showEnvVarsInLog = 0;
- };
- 4AA54CBB8303130FC18C1A27 /* [CP] Embed Pods Frameworks */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- "${SRCROOT}/Pods/Target Support Files/Pods-Core_Example_iOS/Pods-Core_Example_iOS-frameworks.sh",
- "${BUILT_PRODUCTS_DIR}/GoogleToolboxForMac-Defines-NSData+zlib-iOS/GoogleToolboxForMac.framework",
- "${BUILT_PRODUCTS_DIR}/nanopb/nanopb.framework",
- );
- name = "[CP] Embed Pods Frameworks";
- outputPaths = (
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleToolboxForMac.framework",
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/nanopb.framework",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Core_Example_iOS/Pods-Core_Example_iOS-frameworks.sh\"\n";
- showEnvVarsInLog = 0;
- };
- 4B926E79AD9E0016DDA7FBF8 /* [CP] Check Pods Manifest.lock */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
- "${PODS_ROOT}/Manifest.lock",
- );
- name = "[CP] Check Pods Manifest.lock";
- outputPaths = (
- "$(DERIVED_FILE_DIR)/Pods-Database_Example_iOS-checkManifestLockResult.txt",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
- showEnvVarsInLog = 0;
- };
- 4F7410B194A68B6D9F9D1C23 /* [CP] Check Pods Manifest.lock */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
- "${PODS_ROOT}/Manifest.lock",
- );
- name = "[CP] Check Pods Manifest.lock";
- outputPaths = (
- "$(DERIVED_FILE_DIR)/Pods-Messaging_Example_iOS-checkManifestLockResult.txt",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
- showEnvVarsInLog = 0;
- };
- 5355975898496CD7FF7DD106 /* [CP] Embed Pods Frameworks */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- "${SRCROOT}/Pods/Target Support Files/Pods-Storage_Tests_iOS/Pods-Storage_Tests_iOS-frameworks.sh",
- "${BUILT_PRODUCTS_DIR}/OCMock-iOS/OCMock.framework",
- );
- name = "[CP] Embed Pods Frameworks";
- outputPaths = (
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OCMock.framework",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Storage_Tests_iOS/Pods-Storage_Tests_iOS-frameworks.sh\"\n";
- showEnvVarsInLog = 0;
- };
- 547E7E1564F7C15525BE1E84 /* [CP] Copy Pods Resources */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- );
- name = "[CP] Copy Pods Resources";
- outputPaths = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Auth_Example_macOS/Pods-Auth_Example_macOS-resources.sh\"\n";
- showEnvVarsInLog = 0;
- };
- 55298685299C7889EDDFF818 /* [CP] Copy Pods Resources */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- );
- name = "[CP] Copy Pods Resources";
- outputPaths = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Messaging_Tests_iOS/Pods-Messaging_Tests_iOS-resources.sh\"\n";
- showEnvVarsInLog = 0;
- };
- 61ED313363F4BDC7657BD2EE /* [CP] Embed Pods Frameworks */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- "${SRCROOT}/Pods/Target Support Files/Pods-Auth_Example_macOS/Pods-Auth_Example_macOS-frameworks.sh",
- "${BUILT_PRODUCTS_DIR}/GoogleToolboxForMac-465fce74/GoogleToolboxForMac.framework",
- "${BUILT_PRODUCTS_DIR}/GTMSessionFetcher-Core-macOS/GTMSessionFetcher.framework",
- );
- name = "[CP] Embed Pods Frameworks";
- outputPaths = (
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleToolboxForMac.framework",
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GTMSessionFetcher.framework",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Auth_Example_macOS/Pods-Auth_Example_macOS-frameworks.sh\"\n";
- showEnvVarsInLog = 0;
- };
- 68EBBBF8D47F1A4548F5F8A1 /* [CP] Copy Pods Resources */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- );
- name = "[CP] Copy Pods Resources";
- outputPaths = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Auth_Tests_macOS/Pods-Auth_Tests_macOS-resources.sh\"\n";
- showEnvVarsInLog = 0;
- };
- 6F3682326BA63694ECB240A3 /* [CP] Check Pods Manifest.lock */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
- "${PODS_ROOT}/Manifest.lock",
- );
- name = "[CP] Check Pods Manifest.lock";
- outputPaths = (
- "$(DERIVED_FILE_DIR)/Pods-Core_Example_iOS-checkManifestLockResult.txt",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
- showEnvVarsInLog = 0;
- };
- 776FB063FB216F38E91EC8A1 /* [CP] Copy Pods Resources */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- );
- name = "[CP] Copy Pods Resources";
- outputPaths = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Storage_Example_iOS/Pods-Storage_Example_iOS-resources.sh\"\n";
- showEnvVarsInLog = 0;
- };
- 7ADF89772D7C70DB74EF0384 /* [CP] Embed Pods Frameworks */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- "${SRCROOT}/Pods/Target Support Files/Pods-Core_Tests_macOS/Pods-Core_Tests_macOS-frameworks.sh",
- "${BUILT_PRODUCTS_DIR}/OCMock-macOS/OCMock.framework",
- );
- name = "[CP] Embed Pods Frameworks";
- outputPaths = (
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OCMock.framework",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Core_Tests_macOS/Pods-Core_Tests_macOS-frameworks.sh\"\n";
- showEnvVarsInLog = 0;
- };
- 7C60623F83982B8C98AD8A03 /* [CP] Copy Pods Resources */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- "${SRCROOT}/Pods/Target Support Files/Pods-Auth_Sample/Pods-Auth_Sample-resources.sh",
- "${PODS_ROOT}/GoogleSignIn/Resources/GoogleSignIn.bundle",
- );
- name = "[CP] Copy Pods Resources";
- outputPaths = (
- "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleSignIn.bundle",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Auth_Sample/Pods-Auth_Sample-resources.sh\"\n";
- showEnvVarsInLog = 0;
- };
- 7EF78DCE613E776ECD9373C2 /* [CP] Embed Pods Frameworks */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- "${SRCROOT}/Pods/Target Support Files/Pods-Storage_IntegrationTests_macOS/Pods-Storage_IntegrationTests_macOS-frameworks.sh",
- "${BUILT_PRODUCTS_DIR}/OCMock-macOS/OCMock.framework",
- );
- name = "[CP] Embed Pods Frameworks";
- outputPaths = (
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OCMock.framework",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Storage_IntegrationTests_macOS/Pods-Storage_IntegrationTests_macOS-frameworks.sh\"\n";
- showEnvVarsInLog = 0;
- };
- 7F7E65CEFB1C184D69448404 /* [CP] Embed Pods Frameworks */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- "${SRCROOT}/Pods/Target Support Files/Pods-Database_Example_iOS/Pods-Database_Example_iOS-frameworks.sh",
- "${BUILT_PRODUCTS_DIR}/GoogleToolboxForMac-Defines-NSData+zlib-iOS/GoogleToolboxForMac.framework",
- "${BUILT_PRODUCTS_DIR}/nanopb/nanopb.framework",
- "${BUILT_PRODUCTS_DIR}/leveldb-library-iOS/leveldb.framework",
- );
- name = "[CP] Embed Pods Frameworks";
- outputPaths = (
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleToolboxForMac.framework",
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/nanopb.framework",
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/leveldb.framework",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Database_Example_iOS/Pods-Database_Example_iOS-frameworks.sh\"\n";
- showEnvVarsInLog = 0;
- };
- 80A283F697210020814F3349 /* [CP] Check Pods Manifest.lock */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
- "${PODS_ROOT}/Manifest.lock",
- );
- name = "[CP] Check Pods Manifest.lock";
- outputPaths = (
- "$(DERIVED_FILE_DIR)/Pods-Storage_IntegrationTests_macOS-checkManifestLockResult.txt",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
- showEnvVarsInLog = 0;
- };
- 8263B27A42441EB177C87183 /* [CP] Copy Pods Resources */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- );
- name = "[CP] Copy Pods Resources";
- outputPaths = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Storage_IntegrationTests_macOS/Pods-Storage_IntegrationTests_macOS-resources.sh\"\n";
- showEnvVarsInLog = 0;
- };
- 83EC34B122E68BACA199C8BD /* [CP] Check Pods Manifest.lock */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
- "${PODS_ROOT}/Manifest.lock",
- );
- name = "[CP] Check Pods Manifest.lock";
- outputPaths = (
- "$(DERIVED_FILE_DIR)/Pods-Database_Tests_macOS-checkManifestLockResult.txt",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
- showEnvVarsInLog = 0;
- };
- 8489BA940D280D954CF784DF /* [CP] Embed Pods Frameworks */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- "${SRCROOT}/Pods/Target Support Files/Pods-Auth_Tests_iOS/Pods-Auth_Tests_iOS-frameworks.sh",
- "${BUILT_PRODUCTS_DIR}/OCMock-iOS/OCMock.framework",
- );
- name = "[CP] Embed Pods Frameworks";
- outputPaths = (
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OCMock.framework",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Auth_Tests_iOS/Pods-Auth_Tests_iOS-frameworks.sh\"\n";
- showEnvVarsInLog = 0;
- };
- 89FC9C3CF79117EF07001272 /* [CP] Copy Pods Resources */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- );
- name = "[CP] Copy Pods Resources";
- outputPaths = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Database_Example_macOS/Pods-Database_Example_macOS-resources.sh\"\n";
- showEnvVarsInLog = 0;
- };
- 8AC1F8EDC83D64A46940EA1F /* [CP] Copy Pods Resources */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- );
- name = "[CP] Copy Pods Resources";
- outputPaths = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Database_Tests_macOS/Pods-Database_Tests_macOS-resources.sh\"\n";
- showEnvVarsInLog = 0;
- };
- 91ED54292706C89DEAF738F0 /* [CP] Copy Pods Resources */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- );
- name = "[CP] Copy Pods Resources";
- outputPaths = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Database_Tests_iOS/Pods-Database_Tests_iOS-resources.sh\"\n";
- showEnvVarsInLog = 0;
- };
- 94DE13FC01F4BCC0A3AA92B8 /* [CP] Check Pods Manifest.lock */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
- "${PODS_ROOT}/Manifest.lock",
- );
- name = "[CP] Check Pods Manifest.lock";
- outputPaths = (
- "$(DERIVED_FILE_DIR)/Pods-Auth_ApiTests-checkManifestLockResult.txt",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
- showEnvVarsInLog = 0;
- };
- 952D8E3BC2A768AF99B029D4 /* [CP] Copy Pods Resources */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- );
- name = "[CP] Copy Pods Resources";
- outputPaths = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Core_Example_macOS/Pods-Core_Example_macOS-resources.sh\"\n";
- showEnvVarsInLog = 0;
- };
- 964A6DD602ABD81ABDE945E1 /* [CP] Check Pods Manifest.lock */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
- "${PODS_ROOT}/Manifest.lock",
- );
- name = "[CP] Check Pods Manifest.lock";
- outputPaths = (
- "$(DERIVED_FILE_DIR)/Pods-Database_IntegrationTests_iOS-checkManifestLockResult.txt",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
- showEnvVarsInLog = 0;
- };
- 9DDF848892C1B2DCE343D139 /* [CP] Embed Pods Frameworks */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- "${SRCROOT}/Pods/Target Support Files/Pods-Storage_Example_iOS/Pods-Storage_Example_iOS-frameworks.sh",
- "${BUILT_PRODUCTS_DIR}/GoogleToolboxForMac-Defines-NSData+zlib-iOS/GoogleToolboxForMac.framework",
- "${BUILT_PRODUCTS_DIR}/nanopb/nanopb.framework",
- "${BUILT_PRODUCTS_DIR}/GTMSessionFetcher-Core-iOS/GTMSessionFetcher.framework",
- );
- name = "[CP] Embed Pods Frameworks";
- outputPaths = (
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleToolboxForMac.framework",
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/nanopb.framework",
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GTMSessionFetcher.framework",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Storage_Example_iOS/Pods-Storage_Example_iOS-frameworks.sh\"\n";
- showEnvVarsInLog = 0;
- };
- 9F9A46A840E3517985F97BF7 /* [CP] Check Pods Manifest.lock */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
- "${PODS_ROOT}/Manifest.lock",
- );
- name = "[CP] Check Pods Manifest.lock";
- outputPaths = (
- "$(DERIVED_FILE_DIR)/Pods-Storage_Tests_macOS-checkManifestLockResult.txt",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
- showEnvVarsInLog = 0;
- };
- A397E143BFA2B54BBA78DB27 /* [CP] Embed Pods Frameworks */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- "${SRCROOT}/Pods/Target Support Files/Pods-Auth_SwiftSample/Pods-Auth_SwiftSample-frameworks.sh",
- "${BUILT_PRODUCTS_DIR}/GoogleToolboxForMac-be8a5251/GoogleToolboxForMac.framework",
- "${BUILT_PRODUCTS_DIR}/nanopb/nanopb.framework",
- "${BUILT_PRODUCTS_DIR}/GTMSessionFetcher.default-Core/GTMSessionFetcher.framework",
- "${BUILT_PRODUCTS_DIR}/GTMOAuth2/GTMOAuth2.framework",
- );
- name = "[CP] Embed Pods Frameworks";
- outputPaths = (
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleToolboxForMac.framework",
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/nanopb.framework",
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GTMSessionFetcher.framework",
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GTMOAuth2.framework",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Auth_SwiftSample/Pods-Auth_SwiftSample-frameworks.sh\"\n";
- showEnvVarsInLog = 0;
- };
- B5724E5E5CB63BD5B738C4F6 /* [CP] Copy Pods Resources */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- );
- name = "[CP] Copy Pods Resources";
- outputPaths = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Database_Example_iOS/Pods-Database_Example_iOS-resources.sh\"\n";
- showEnvVarsInLog = 0;
- };
- B57C4BF2A720187D5DF4C848 /* [CP] Copy Pods Resources */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- );
- name = "[CP] Copy Pods Resources";
- outputPaths = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Storage_Tests_iOS/Pods-Storage_Tests_iOS-resources.sh\"\n";
- showEnvVarsInLog = 0;
- };
- B5D70414394DD066BE115ED8 /* [CP] Embed Pods Frameworks */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- "${SRCROOT}/Pods/Target Support Files/Pods-Database_Tests_macOS/Pods-Database_Tests_macOS-frameworks.sh",
- "${BUILT_PRODUCTS_DIR}/OCMock-macOS/OCMock.framework",
- );
- name = "[CP] Embed Pods Frameworks";
- outputPaths = (
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OCMock.framework",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Database_Tests_macOS/Pods-Database_Tests_macOS-frameworks.sh\"\n";
- showEnvVarsInLog = 0;
- };
- B6C7305ECEA2DA69869E3199 /* [CP] Check Pods Manifest.lock */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
- "${PODS_ROOT}/Manifest.lock",
- );
- name = "[CP] Check Pods Manifest.lock";
- outputPaths = (
- "$(DERIVED_FILE_DIR)/Pods-Auth_EarlGreyTests-checkManifestLockResult.txt",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
- showEnvVarsInLog = 0;
- };
- BC5C8E9DA5ECC9095376EEFC /* [CP] Copy Pods Resources */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- );
- name = "[CP] Copy Pods Resources";
- outputPaths = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Database_IntegrationTests_iOS/Pods-Database_IntegrationTests_iOS-resources.sh\"\n";
- showEnvVarsInLog = 0;
- };
- BD09C44595A6ECBB8FA2350D /* [CP] Check Pods Manifest.lock */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
- "${PODS_ROOT}/Manifest.lock",
- );
- name = "[CP] Check Pods Manifest.lock";
- outputPaths = (
- "$(DERIVED_FILE_DIR)/Pods-Auth_Example_macOS-checkManifestLockResult.txt",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
- showEnvVarsInLog = 0;
- };
- BD7302A2861F068F5540CCA6 /* [CP] Copy Pods Resources */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- );
- name = "[CP] Copy Pods Resources";
- outputPaths = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Core_Example_iOS/Pods-Core_Example_iOS-resources.sh\"\n";
- showEnvVarsInLog = 0;
- };
- C0894681AFF0FF07CE891310 /* [CP] Check Pods Manifest.lock */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
- "${PODS_ROOT}/Manifest.lock",
- );
- name = "[CP] Check Pods Manifest.lock";
- outputPaths = (
- "$(DERIVED_FILE_DIR)/Pods-Storage_Tests_iOS-checkManifestLockResult.txt",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
- showEnvVarsInLog = 0;
- };
- C0BB541B4E2E4188148B74BF /* [CP] Embed Pods Frameworks */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- "${SRCROOT}/Pods/Target Support Files/Pods-Auth_Example_iOS/Pods-Auth_Example_iOS-frameworks.sh",
- "${BUILT_PRODUCTS_DIR}/GoogleToolboxForMac-be8a5251/GoogleToolboxForMac.framework",
- "${BUILT_PRODUCTS_DIR}/nanopb/nanopb.framework",
- "${BUILT_PRODUCTS_DIR}/GTMSessionFetcher-Core-iOS/GTMSessionFetcher.framework",
- );
- name = "[CP] Embed Pods Frameworks";
- outputPaths = (
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleToolboxForMac.framework",
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/nanopb.framework",
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GTMSessionFetcher.framework",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Auth_Example_iOS/Pods-Auth_Example_iOS-frameworks.sh\"\n";
- showEnvVarsInLog = 0;
- };
- C4CB228F9FBF834637FDD550 /* [CP] Embed Pods Frameworks */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- "${SRCROOT}/Pods/Target Support Files/Pods-Auth_Sample/Pods-Auth_Sample-frameworks.sh",
- "${BUILT_PRODUCTS_DIR}/GoogleToolboxForMac-be8a5251/GoogleToolboxForMac.framework",
- "${BUILT_PRODUCTS_DIR}/nanopb/nanopb.framework",
- "${BUILT_PRODUCTS_DIR}/GTMSessionFetcher.default-Core/GTMSessionFetcher.framework",
- "${BUILT_PRODUCTS_DIR}/Bolts/Bolts.framework",
- "${BUILT_PRODUCTS_DIR}/FBSDKCoreKit/FBSDKCoreKit.framework",
- "${BUILT_PRODUCTS_DIR}/FBSDKLoginKit/FBSDKLoginKit.framework",
- "${BUILT_PRODUCTS_DIR}/GTMOAuth2/GTMOAuth2.framework",
- );
- name = "[CP] Embed Pods Frameworks";
- outputPaths = (
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleToolboxForMac.framework",
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/nanopb.framework",
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GTMSessionFetcher.framework",
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Bolts.framework",
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FBSDKCoreKit.framework",
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FBSDKLoginKit.framework",
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GTMOAuth2.framework",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Auth_Sample/Pods-Auth_Sample-frameworks.sh\"\n";
- showEnvVarsInLog = 0;
- };
- C6DD8EA209B18D8651337E5A /* [CP] Check Pods Manifest.lock */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
- "${PODS_ROOT}/Manifest.lock",
- );
- name = "[CP] Check Pods Manifest.lock";
- outputPaths = (
- "$(DERIVED_FILE_DIR)/Pods-Storage_IntegrationTests_iOS-checkManifestLockResult.txt",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
- showEnvVarsInLog = 0;
- };
- C981CB657374F18444683DDE /* [CP] Check Pods Manifest.lock */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
- "${PODS_ROOT}/Manifest.lock",
- );
- name = "[CP] Check Pods Manifest.lock";
- outputPaths = (
- "$(DERIVED_FILE_DIR)/Pods-Auth_Tests_iOS-checkManifestLockResult.txt",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
- showEnvVarsInLog = 0;
- };
- CB265192F117EE5B7B6A90F3 /* [CP] Copy Pods Resources */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- );
- name = "[CP] Copy Pods Resources";
- outputPaths = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Messaging_Example_iOS/Pods-Messaging_Example_iOS-resources.sh\"\n";
- showEnvVarsInLog = 0;
- };
- D293C865CA6C9CFE510F2030 /* [CP] Copy Pods Resources */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- );
- name = "[CP] Copy Pods Resources";
- outputPaths = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Analytics_Tests_iOS/Pods-Analytics_Tests_iOS-resources.sh\"\n";
- showEnvVarsInLog = 0;
- };
- D57035FA9DBD9B5C6F7E44BB /* [CP] Embed Pods Frameworks */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- "${SRCROOT}/Pods/Target Support Files/Pods-Storage_IntegrationTests_iOS/Pods-Storage_IntegrationTests_iOS-frameworks.sh",
- "${BUILT_PRODUCTS_DIR}/OCMock-iOS/OCMock.framework",
- );
- name = "[CP] Embed Pods Frameworks";
- outputPaths = (
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OCMock.framework",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Storage_IntegrationTests_iOS/Pods-Storage_IntegrationTests_iOS-frameworks.sh\"\n";
- showEnvVarsInLog = 0;
- };
- D736CA94F00AB417403CEC0D /* [CP] Embed Pods Frameworks */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- "${SRCROOT}/Pods/Target Support Files/Pods-Messaging_Tests_iOS/Pods-Messaging_Tests_iOS-frameworks.sh",
- "${BUILT_PRODUCTS_DIR}/OCMock-iOS/OCMock.framework",
- );
- name = "[CP] Embed Pods Frameworks";
- outputPaths = (
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OCMock.framework",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Messaging_Tests_iOS/Pods-Messaging_Tests_iOS-frameworks.sh\"\n";
- showEnvVarsInLog = 0;
- };
- DBE97573DB393D723CD8CDA2 /* [CP] Embed Pods Frameworks */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- "${SRCROOT}/Pods/Target Support Files/Pods-Core_Tests_iOS/Pods-Core_Tests_iOS-frameworks.sh",
- "${BUILT_PRODUCTS_DIR}/OCMock-iOS/OCMock.framework",
- );
- name = "[CP] Embed Pods Frameworks";
- outputPaths = (
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OCMock.framework",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Core_Tests_iOS/Pods-Core_Tests_iOS-frameworks.sh\"\n";
- showEnvVarsInLog = 0;
- };
- DC31836CBCE7DE03EC932511 /* [CP] Embed Pods Frameworks */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- "${SRCROOT}/Pods/Target Support Files/Pods-Analytics_Tests_iOS/Pods-Analytics_Tests_iOS-frameworks.sh",
- "${BUILT_PRODUCTS_DIR}/GoogleToolboxForMac-Defines-NSData+zlib-iOS/GoogleToolboxForMac.framework",
- "${BUILT_PRODUCTS_DIR}/nanopb/nanopb.framework",
- );
- name = "[CP] Embed Pods Frameworks";
- outputPaths = (
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleToolboxForMac.framework",
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/nanopb.framework",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Analytics_Tests_iOS/Pods-Analytics_Tests_iOS-frameworks.sh\"\n";
- showEnvVarsInLog = 0;
- };
- DC71E8C018300A993E541683 /* [CP] Copy Pods Resources */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- "${SRCROOT}/Pods/Target Support Files/Pods-Auth_SwiftSample/Pods-Auth_SwiftSample-resources.sh",
- "${PODS_ROOT}/GoogleSignIn/Resources/GoogleSignIn.bundle",
- );
- name = "[CP] Copy Pods Resources";
- outputPaths = (
- "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleSignIn.bundle",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Auth_SwiftSample/Pods-Auth_SwiftSample-resources.sh\"\n";
- showEnvVarsInLog = 0;
- };
- DC8467211F158C333D6E1851 /* [CP] Copy Pods Resources */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- );
- name = "[CP] Copy Pods Resources";
- outputPaths = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Storage_Tests_macOS/Pods-Storage_Tests_macOS-resources.sh\"\n";
- showEnvVarsInLog = 0;
- };
- DE3BC872B0631D9222625218 /* [CP] Copy Pods Resources */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- );
- name = "[CP] Copy Pods Resources";
- outputPaths = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Storage_Example_macOS/Pods-Storage_Example_macOS-resources.sh\"\n";
- showEnvVarsInLog = 0;
- };
- E14B732B18AC2D43E964F013 /* [CP] Embed Pods Frameworks */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- "${SRCROOT}/Pods/Target Support Files/Pods-Messaging_Example_iOS/Pods-Messaging_Example_iOS-frameworks.sh",
- "${BUILT_PRODUCTS_DIR}/GoogleToolboxForMac-Defines-Logger-NSData+zlib/GoogleToolboxForMac.framework",
- "${BUILT_PRODUCTS_DIR}/nanopb/nanopb.framework",
- "${BUILT_PRODUCTS_DIR}/Protobuf/Protobuf.framework",
- );
- name = "[CP] Embed Pods Frameworks";
- outputPaths = (
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleToolboxForMac.framework",
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/nanopb.framework",
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Protobuf.framework",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Messaging_Example_iOS/Pods-Messaging_Example_iOS-frameworks.sh\"\n";
- showEnvVarsInLog = 0;
- };
- E1EF4640668E42876CD0680B /* [CP] Embed Pods Frameworks */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- "${SRCROOT}/Pods/Target Support Files/Pods-Storage_Example_macOS/Pods-Storage_Example_macOS-frameworks.sh",
- "${BUILT_PRODUCTS_DIR}/GoogleToolboxForMac-Defines-NSData+zlib-macOS/GoogleToolboxForMac.framework",
- "${BUILT_PRODUCTS_DIR}/GTMSessionFetcher-Core-macOS/GTMSessionFetcher.framework",
- );
- name = "[CP] Embed Pods Frameworks";
- outputPaths = (
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleToolboxForMac.framework",
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GTMSessionFetcher.framework",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Storage_Example_macOS/Pods-Storage_Example_macOS-frameworks.sh\"\n";
- showEnvVarsInLog = 0;
- };
- E3A92F1E7F7F65ACF2BCEC78 /* [CP] Check Pods Manifest.lock */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
- "${PODS_ROOT}/Manifest.lock",
- );
- name = "[CP] Check Pods Manifest.lock";
- outputPaths = (
- "$(DERIVED_FILE_DIR)/Pods-Analytics_Tests_iOS-checkManifestLockResult.txt",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
- showEnvVarsInLog = 0;
- };
- E4708A6EB45D6F7D30070DCF /* [CP] Check Pods Manifest.lock */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
- "${PODS_ROOT}/Manifest.lock",
- );
- name = "[CP] Check Pods Manifest.lock";
- outputPaths = (
- "$(DERIVED_FILE_DIR)/Pods-Core_Tests_iOS-checkManifestLockResult.txt",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
- showEnvVarsInLog = 0;
- };
- E4CD99103647D7D03D05576E /* [CP] Copy Pods Resources */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- );
- name = "[CP] Copy Pods Resources";
- outputPaths = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Storage_IntegrationTests_iOS/Pods-Storage_IntegrationTests_iOS-resources.sh\"\n";
- showEnvVarsInLog = 0;
- };
- EB62267583A8460DAF576DF9 /* [CP] Copy Pods Resources */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- );
- name = "[CP] Copy Pods Resources";
- outputPaths = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Auth_EarlGreyTests/Pods-Auth_EarlGreyTests-resources.sh\"\n";
- showEnvVarsInLog = 0;
- };
- EBD8971C0B3B5BCDA5F5EA9F /* [CP] Embed Pods Frameworks */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- "${SRCROOT}/Pods/Target Support Files/Pods-Database_Example_macOS/Pods-Database_Example_macOS-frameworks.sh",
- "${BUILT_PRODUCTS_DIR}/GoogleToolboxForMac-Defines-NSData+zlib-macOS/GoogleToolboxForMac.framework",
- "${BUILT_PRODUCTS_DIR}/leveldb-library-macOS/leveldb.framework",
- );
- name = "[CP] Embed Pods Frameworks";
- outputPaths = (
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleToolboxForMac.framework",
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/leveldb.framework",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Database_Example_macOS/Pods-Database_Example_macOS-frameworks.sh\"\n";
- showEnvVarsInLog = 0;
- };
- F5B5EF87DFA0131455579138 /* [CP] Embed Pods Frameworks */ = {
- isa = PBXShellScriptBuildPhase;
+ DEB13A1D1E73507E00AC236D /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
- inputPaths = (
- "${SRCROOT}/Pods/Target Support Files/Pods-Auth_EarlGreyTests/Pods-Auth_EarlGreyTests-frameworks.sh",
- "${PODS_ROOT}/EarlGrey/EarlGrey/EarlGrey.framework",
- );
- name = "[CP] Embed Pods Frameworks";
- outputPaths = (
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/EarlGrey.framework",
- );
runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Auth_EarlGreyTests/Pods-Auth_EarlGreyTests-frameworks.sh\"\n";
- showEnvVarsInLog = 0;
};
- FF06F317B2790040C6157248 /* [CP] Check Pods Manifest.lock */ = {
- isa = PBXShellScriptBuildPhase;
+ DEDFEFEA1FD1B8C100F7D466 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
- inputPaths = (
- "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
- "${PODS_ROOT}/Manifest.lock",
- );
- name = "[CP] Check Pods Manifest.lock";
- outputPaths = (
- "$(DERIVED_FILE_DIR)/Pods-Database_Tests_iOS-checkManifestLockResult.txt",
- );
runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
- showEnvVarsInLog = 0;
};
- FFAC5DC18B783B814EF2DAB5 /* [CP] Copy Pods Resources */ = {
- isa = PBXShellScriptBuildPhase;
+ DEE14D3F1E84464D006FA992 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
- );
- inputPaths = (
- );
- name = "[CP] Copy Pods Resources";
- outputPaths = (
+ AFAF36F61EC28C25004BDEE5 /* Shared.xcassets in Resources */,
+ DEE14D831E844677006FA992 /* GoogleService-Info.plist in Resources */,
+ DEE14D7E1E844677006FA992 /* LaunchScreen.storyboard in Resources */,
+ DEE14D7F1E844677006FA992 /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Core_Tests_macOS/Pods-Core_Tests_macOS-resources.sh\"\n";
- showEnvVarsInLog = 0;
};
- FFBE661125B41DE1A5B115C3 /* [CP] Copy Pods Resources */ = {
- isa = PBXShellScriptBuildPhase;
+ DEE14D571E84464D006FA992 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
- inputPaths = (
- );
- name = "[CP] Copy Pods Resources";
- outputPaths = (
- );
runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Core_Tests_iOS/Pods-Core_Tests_iOS-resources.sh\"\n";
- showEnvVarsInLog = 0;
};
-/* End PBXShellScriptBuildPhase section */
+/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
06121EB81EC399C50008D70E /* Sources */ = {
@@ -4624,12 +3564,9 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
- AFD563171EBBEF7B00EA2233 /* Data+MessagingExtensions.swift in Sources */,
- AFD5630E1EB1402300EA2233 /* AppDelegate.swift in Sources */,
- AFC8BA9D1EBD230E00B8EEAE /* NotificationsController.swift in Sources */,
- AFD5630F1EB1402300EA2233 /* MessagingViewController.swift in Sources */,
- AFC8BAA71EC257D800B8EEAE /* FIRSampleAppUtilities.m in Sources */,
- AFC8BA9F1EBD51A700B8EEAE /* Environment.swift in Sources */,
+ DE47C142207ACAA900B1AEDF /* main.m in Sources */,
+ DE47C143207ACAA900B1AEDF /* FIRAppDelegate.m in Sources */,
+ DE47C144207ACAA900B1AEDF /* FIRViewController.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -4829,6 +3766,65 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ DE1CD5931FBA55AF00FC031E /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ DE1EC2971FBA5EB5007D18D8 /* ViewController.m in Sources */,
+ DE1EC2951FBA5EB5007D18D8 /* main.m in Sources */,
+ DE1EC2921FBA5EB5007D18D8 /* AppDelegate.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ DE1EC27B1FBA5E63007D18D8 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ DE90372F1FBA5F8F00E239D3 /* FLevelDBStorageEngineTests.m in Sources */,
+ DE9037361FBA5F8F00E239D3 /* FRangeMergeTest.m in Sources */,
+ DE90373F1FBA675D00E239D3 /* FDevice.m in Sources */,
+ DE9037311FBA5F8F00E239D3 /* FPathTests.m in Sources */,
+ DE9037441FBA675D00E239D3 /* FTestAuthTokenGenerator.m in Sources */,
+ DE9037391FBA5F8F00E239D3 /* FSyncPointTests.m in Sources */,
+ DE9037371FBA5F8F00E239D3 /* FRepoInfoTest.m in Sources */,
+ DE9037451FBA675D00E239D3 /* FTestBase.m in Sources */,
+ DE90372D1FBA5F8F00E239D3 /* FIRDataSnapshotTests.m in Sources */,
+ DE90372B1FBA5F8F00E239D3 /* FCompoundHashTest.m in Sources */,
+ DE90374B1FBA675D00E239D3 /* SenTest+FWaiter.m in Sources */,
+ DE9037321FBA5F8F00E239D3 /* FPersistenceManagerTest.m in Sources */,
+ DE9037461FBA675D00E239D3 /* FTestCachePolicy.m in Sources */,
+ DE90373A1FBA5F8F00E239D3 /* FTrackedQueryManagerTest.m in Sources */,
+ DE90373C1FBA5F8F00E239D3 /* FUtilitiesTest.m in Sources */,
+ DE90372C1FBA5F8F00E239D3 /* FCompoundWriteTest.m in Sources */,
+ DE9037431FBA675D00E239D3 /* FMockStorageEngine.m in Sources */,
+ DE9037381FBA5F8F00E239D3 /* FSparseSnapshotTests.m in Sources */,
+ DE9037341FBA5F8F00E239D3 /* FPruningTest.m in Sources */,
+ DE9037471FBA675D00E239D3 /* FTestClock.m in Sources */,
+ DE90373B1FBA5F8F00E239D3 /* FTreeSortedDictionaryTests.m in Sources */,
+ DE9037491FBA675D00E239D3 /* FTestHelpers.m in Sources */,
+ DE9037401FBA675D00E239D3 /* FEventTester.m in Sources */,
+ DE9037351FBA5F8F00E239D3 /* FQueryParamsTest.m in Sources */,
+ DE90372E1FBA5F8F00E239D3 /* FIRMutableDataTests.m in Sources */,
+ DE9037301FBA5F8F00E239D3 /* FNodeTests.m in Sources */,
+ DE9037421FBA675D00E239D3 /* FIRTestAuthTokenProvider.m in Sources */,
+ DE9037411FBA675D00E239D3 /* FIRFakeApp.m in Sources */,
+ DE9037331FBA5F8F00E239D3 /* FPruneForestTest.m in Sources */,
+ DE90374A1FBA675D00E239D3 /* FTupleEventTypeString.m in Sources */,
+ DE90372A1FBA5F8F00E239D3 /* FArraySortedDictionaryTest.m in Sources */,
+ DE9037481FBA675D00E239D3 /* FTestExpectations.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ DE1FAE8C1FBCF5E100897AAA /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ DE1FAEB31FBCF60D00897AAA /* ViewController.m in Sources */,
+ DE1FAEB21FBCF60D00897AAA /* main.m in Sources */,
+ DE1FAEB11FBCF60D00897AAA /* AppDelegate.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
DE26D22A1F70398A004AE1D3 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
@@ -4876,6 +3872,68 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ DE47C0DD207AC87D00B1AEDF /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ DE47C11A207AC94A00B1AEDF /* Data+MessagingExtensions.swift in Sources */,
+ DE47C115207AC94A00B1AEDF /* Environment.swift in Sources */,
+ DE47C116207AC94A00B1AEDF /* MessagingViewController.swift in Sources */,
+ DE47C0E2207AC87D00B1AEDF /* FIRSampleAppUtilities.m in Sources */,
+ DE47C11C207AC94A00B1AEDF /* NotificationsController.swift in Sources */,
+ DE47C11B207AC94A00B1AEDF /* AppDelegate.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ DE53893A1FBB62E100199FC2 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ DEF6C3171FBCE775005D0740 /* FIRAuthBackendRPCImplementationTests.m in Sources */,
+ DEF6C30D1FBCE72F005D0740 /* FIRAuthDispatcherTests.m in Sources */,
+ DEF6C3121FBCE775005D0740 /* FIRAuthAPNSTokenTests.m in Sources */,
+ DEF6C3131FBCE775005D0740 /* FIRAuthAppCredentialManagerTests.m in Sources */,
+ DEF6C3301FBCE775005D0740 /* FIRSetAccountInfoRequestTests.m in Sources */,
+ DEF6C3341FBCE775005D0740 /* FIRTwitterAuthProviderTests.m in Sources */,
+ DEF6C3271FBCE775005D0740 /* FIRGetOOBConfirmationCodeResponseTests.m in Sources */,
+ DEF6C3241FBCE775005D0740 /* FIRGetAccountInfoRequestTests.m in Sources */,
+ DEF6C33E1FBCE775005D0740 /* FIRVerifyPasswordResponseTests.m in Sources */,
+ DEF6C3141FBCE775005D0740 /* FIRAuthAppCredentialTests.m in Sources */,
+ DEF6C31C1FBCE775005D0740 /* FIRAuthTests.m in Sources */,
+ DEF6C33D1FBCE775005D0740 /* FIRVerifyPasswordRequestTest.m in Sources */,
+ DEF6C3181FBCE775005D0740 /* FIRAuthGlobalWorkQueueTests.m in Sources */,
+ DEF6C3191FBCE775005D0740 /* FIRAuthKeychainTests.m in Sources */,
+ DEF6C32A1FBCE775005D0740 /* FIRGitHubAuthProviderTests.m in Sources */,
+ DEF6C3321FBCE775005D0740 /* FIRSignUpNewUserRequestTests.m in Sources */,
+ DEF6C30F1FBCE775005D0740 /* FIRAdditionalUserInfoTests.m in Sources */,
+ DEF6C31B1FBCE775005D0740 /* FIRAuthSerialTaskQueueTests.m in Sources */,
+ DEF6C3251FBCE775005D0740 /* FIRGetAccountInfoResponseTests.m in Sources */,
+ DEF6C3281FBCE775005D0740 /* FIRGetProjectConfigRequestTests.m in Sources */,
+ DEF6C3291FBCE775005D0740 /* FIRGetProjectConfigResponseTests.m in Sources */,
+ DEF6C32D1FBCE775005D0740 /* FIRResetPasswordResponseTests.m in Sources */,
+ DEF6C33C1FBCE775005D0740 /* FIRVerifyCustomTokenResponseTests.m in Sources */,
+ DEF6C3161FBCE775005D0740 /* FIRAuthBackendCreateAuthURITests.m in Sources */,
+ DEF6C32C1FBCE775005D0740 /* FIRResetPasswordRequestTests.m in Sources */,
+ DEF6C31F1FBCE775005D0740 /* FIRCreateAuthURIRequestTests.m in Sources */,
+ DEF6C3201FBCE775005D0740 /* FIRCreateAuthURIResponseTests.m in Sources */,
+ DEF6C31A1FBCE775005D0740 /* FIRAuthNotificationManagerTests.m in Sources */,
+ DEF6C33B1FBCE775005D0740 /* FIRVerifyCustomTokenRequestTests.m in Sources */,
+ DEF6C3221FBCE775005D0740 /* FIRDeleteAccountResponseTests.m in Sources */,
+ DEF6C3381FBCE775005D0740 /* FIRVerifyAssertionResponseTests.m in Sources */,
+ DEF6C3101FBCE775005D0740 /* FIRApp+FIRAuthUnitTests.m in Sources */,
+ DEF6C3411FBCE775005D0740 /* OCMStubRecorder+FIRAuthUnitTests.m in Sources */,
+ DEF6C3211FBCE775005D0740 /* FIRDeleteAccountRequestTests.m in Sources */,
+ DEF6C3331FBCE775005D0740 /* FIRSignUpNewUserResponseTests.m in Sources */,
+ DEF6C3371FBCE775005D0740 /* FIRVerifyAssertionRequestTests.m in Sources */,
+ DEF6C3231FBCE775005D0740 /* FIRFakeBackendRPCIssuer.m in Sources */,
+ DEF6C31E1FBCE775005D0740 /* FIRAuthUserDefaultsStorageTests.m in Sources */,
+ DEF6C3351FBCE775005D0740 /* FIRUserMetadataTests.m in Sources */,
+ DEF6C3311FBCE775005D0740 /* FIRSetAccountInfoResponseTests.m in Sources */,
+ DEF6C3361FBCE775005D0740 /* FIRUserTests.m in Sources */,
+ DEF6C3261FBCE775005D0740 /* FIRGetOOBConfirmationCodeRequestTests.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
DE7B8D011E8EF077009EB6DF /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
@@ -4947,6 +4005,7 @@
DE9315571E86C71C0083EDBF /* FIRAdditionalUserInfoTests.m in Sources */,
DE750DBF1EB3DD6C00A75E47 /* FIRAuthAppCredentialManagerTests.m in Sources */,
DE93157B1E86C71C0083EDBF /* FIRVerifyPasswordResponseTests.m in Sources */,
+ 7EE21F7A1FE89193009B1370 /* FIREmailLinkRequestTests.m in Sources */,
DE93155B1E86C71C0083EDBF /* FIRAuthDispatcherTests.m in Sources */,
DE9315791E86C71C0083EDBF /* FIRVerifyCustomTokenResponseTests.m in Sources */,
DE9315601E86C71C0083EDBF /* FIRAuthUserDefaultsStorageTests.m in Sources */,
@@ -4975,6 +4034,7 @@
DE93155A1E86C71C0083EDBF /* FIRAuthBackendRPCImplementationTests.m in Sources */,
DE93157D1E86C71C0083EDBF /* FIRVerifyPhoneNumberResponseTests.m in Sources */,
DE93157E1E86C71C0083EDBF /* OCMStubRecorder+FIRAuthUnitTests.m in Sources */,
+ 7EE21F7C1FE8919E009B1370 /* FIREmailLinkSignInResponseTests.m in Sources */,
DE9315771E86C71C0083EDBF /* FIRVerifyAssertionResponseTests.m in Sources */,
DE9315721E86C71C0083EDBF /* FIRSignUpNewUserRequestTests.m in Sources */,
DE9315671E86C71C0083EDBF /* FIRGetAccountInfoResponseTests.m in Sources */,
@@ -5019,6 +4079,57 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ DEAAD37D1FBA11270053BF48 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ DEAAD3D01FBA1EFA0053BF48 /* ViewController.m in Sources */,
+ DEAAD3CF1FBA1EFA0053BF48 /* AppDelegate.m in Sources */,
+ DEAAD3C31FBA1CD90053BF48 /* main.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ DEAAD3911FBA11270053BF48 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ DEAAD3DA1FBA34250053BF48 /* FIROptionsTest.m in Sources */,
+ DEAAD3D51FBA34250053BF48 /* FIRAppAssociationRegistrationUnitTests.m in Sources */,
+ DEAAD3D91FBA34250053BF48 /* FIRLoggerTest.m in Sources */,
+ DEAAD3D61FBA34250053BF48 /* FIRAppTest.m in Sources */,
+ DEAAD3D81FBA34250053BF48 /* FIRConfigurationTest.m in Sources */,
+ DEAAD3DB1FBA34250053BF48 /* FIRTestCase.m in Sources */,
+ DEAAD3D71FBA34250053BF48 /* FIRBundleUtilTest.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ DEAAD3DD1FBA46AA0053BF48 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ DEAAD41F1FBA470B0053BF48 /* ViewController.m in Sources */,
+ DEAAD41D1FBA470B0053BF48 /* main.m in Sources */,
+ DEAAD41A1FBA470B0053BF48 /* AppDelegate.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ DEAAD3F11FBA46AB0053BF48 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ DEAAD4251FBA49ED0053BF48 /* FIRStoragePathTests.m in Sources */,
+ DEAAD42A1FBA49ED0053BF48 /* FIRStorageUpdateMetadataTests.m in Sources */,
+ DEAAD4221FBA49ED0053BF48 /* FIRStorageDeleteTests.m in Sources */,
+ DEAAD4291FBA49ED0053BF48 /* FIRStorageTokenAuthorizerTests.m in Sources */,
+ DEAAD4231FBA49ED0053BF48 /* FIRStorageGetMetadataTests.m in Sources */,
+ DEAAD42B1FBA49ED0053BF48 /* FIRStorageUtilsTests.m in Sources */,
+ DEAAD4261FBA49ED0053BF48 /* FIRStorageReferenceTests.m in Sources */,
+ DEAAD4271FBA49ED0053BF48 /* FIRStorageTestHelpers.m in Sources */,
+ DEAAD4281FBA49ED0053BF48 /* FIRStorageTests.m in Sources */,
+ DEAAD4241FBA49ED0053BF48 /* FIRStorageMetadataTests.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
DEB139E21E73506A00AC236D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
@@ -5147,6 +4258,16 @@
target = D0FE8A1E1ED9C804003F6722 /* Database_Example_macOS */;
targetProxy = D0FE8A901ED9C9CD003F6722 /* PBXContainerItemProxy */;
};
+ DE1E3B311FEB1E7600EAEBB0 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = DE1FAE8F1FBCF5E100897AAA /* Auth_Example_tvOS */;
+ targetProxy = DE1E3B301FEB1E7600EAEBB0 /* PBXContainerItemProxy */;
+ };
+ DE1EC2851FBA5E63007D18D8 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = DE1CD5961FBA55AF00FC031E /* Database_Example_tvOS */;
+ targetProxy = DE1EC2841FBA5E63007D18D8 /* PBXContainerItemProxy */;
+ };
DE26D2631F7049F1004AE1D3 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = DE26D22D1F70398A004AE1D3 /* Auth_Sample */;
@@ -5177,6 +4298,21 @@
target = DEB13A0A1E73507E00AC236D /* Storage_Tests_iOS */;
targetProxy = DE3373971E73776F00881891 /* PBXContainerItemProxy */;
};
+ DE545C841FBCA41C00C637AE /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = DEAAD3941FBA11270053BF48 /* Core_Tests_tvOS */;
+ targetProxy = DE545C831FBCA41C00C637AE /* PBXContainerItemProxy */;
+ };
+ DE545C861FBCA42C00C637AE /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = DEAAD3F41FBA46AB0053BF48 /* Storage_Tests_tvOS */;
+ targetProxy = DE545C851FBCA42C00C637AE /* PBXContainerItemProxy */;
+ };
+ DE545C881FBCA43200C637AE /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = DE1EC27E1FBA5E63007D18D8 /* Database_Tests_tvOS */;
+ targetProxy = DE545C871FBCA43200C637AE /* PBXContainerItemProxy */;
+ };
DE6F01BA1E957157004AEE01 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = DE9315A61E8738460083EDBF /* Messaging_Tests_iOS */;
@@ -5197,6 +4333,16 @@
target = DE9314DD1E86C6BE0083EDBF /* Auth_Tests_iOS */;
targetProxy = DE9315861E86E9990083EDBF /* PBXContainerItemProxy */;
};
+ DEAAD3971FBA11280053BF48 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = DEAAD3801FBA11270053BF48 /* Core_Example_tvOS */;
+ targetProxy = DEAAD3961FBA11280053BF48 /* PBXContainerItemProxy */;
+ };
+ DEAAD3F71FBA46AB0053BF48 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = DEAAD3E01FBA46AA0053BF48 /* Storage_Example_tvOS */;
+ targetProxy = DEAAD3F61FBA46AB0053BF48 /* PBXContainerItemProxy */;
+ };
DEB13A261E73512500AC236D /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = DEB139E01E73506A00AC236D /* Storage_Example_iOS */;
@@ -5225,22 +4371,6 @@
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
- D01853491EDACED4003A645C /* LaunchScreen.storyboard */ = {
- isa = PBXVariantGroup;
- children = (
- D018534A1EDACED4003A645C /* Base */,
- );
- name = LaunchScreen.storyboard;
- sourceTree = "<group>";
- };
- D018534B1EDACED4003A645C /* Main.storyboard */ = {
- isa = PBXVariantGroup;
- children = (
- D018534C1EDACED4003A645C /* Base */,
- );
- name = Main.storyboard;
- sourceTree = "<group>";
- };
D018537B1EDAD0E6003A645C /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
@@ -5285,6 +4415,38 @@
name = Localizable.strings;
sourceTree = "<group>";
};
+ DE47C10C207AC94A00B1AEDF /* LaunchScreen.storyboard */ = {
+ isa = PBXVariantGroup;
+ children = (
+ DE47C10D207AC94A00B1AEDF /* Base */,
+ );
+ name = LaunchScreen.storyboard;
+ sourceTree = "<group>";
+ };
+ DE47C10E207AC94A00B1AEDF /* Main.storyboard */ = {
+ isa = PBXVariantGroup;
+ children = (
+ DE47C10F207AC94A00B1AEDF /* Base */,
+ );
+ name = Main.storyboard;
+ sourceTree = "<group>";
+ };
+ DE47C136207ACAA900B1AEDF /* LaunchScreen.storyboard */ = {
+ isa = PBXVariantGroup;
+ children = (
+ DE47C137207ACAA900B1AEDF /* Base */,
+ );
+ name = LaunchScreen.storyboard;
+ sourceTree = "<group>";
+ };
+ DE47C138207ACAA900B1AEDF /* Main.storyboard */ = {
+ isa = PBXVariantGroup;
+ children = (
+ DE47C139207ACAA900B1AEDF /* Base */,
+ );
+ name = Main.storyboard;
+ sourceTree = "<group>";
+ };
DE7B8D2C1E8EF202009EB6DF /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
@@ -5362,7 +4524,6 @@
/* Begin XCBuildConfiguration section */
06121EC31EC399C50008D70E /* Debug */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 4ECAA105379B7E664C7FF223 /* Pods-Storage_IntegrationTests_iOS.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CLANG_ANALYZER_NONNULL = YES;
@@ -5384,7 +4545,6 @@
};
06121EC41EC399C50008D70E /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 5CA5A85B5A80F118F3247910 /* Pods-Storage_IntegrationTests_iOS.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CLANG_ANALYZER_NONNULL = YES;
@@ -5407,7 +4567,6 @@
};
0624F3E91EC0ECFA00E5940D /* Debug */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 20928A4E610E48E3EA4D9F4A /* Pods-Database_IntegrationTests_iOS.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CLANG_ANALYZER_NONNULL = YES;
@@ -5444,7 +4603,6 @@
};
0624F3EA1EC0ECFA00E5940D /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = EDA33867CB04D0AADD09321A /* Pods-Database_IntegrationTests_iOS.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CLANG_ANALYZER_NONNULL = YES;
@@ -5565,17 +4723,16 @@
};
AFD562F51EB13C6D00EA2233 /* Debug */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 4D61AACC06F8E078EF051E4C /* Pods-Messaging_Example_iOS.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
- CODE_SIGN_ENTITLEMENTS = Messaging/App/iOS/Messaging_Example.entitlements;
+ CODE_SIGN_ENTITLEMENTS = Messaging/Sample/iOS/Messaging_Example.entitlements;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = EQHXZ8M8AV;
- INFOPLIST_FILE = "Messaging/App/iOS/Messaging-Info.plist";
+ INFOPLIST_FILE = "Messaging/Sample/iOS/Messaging-Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MTL_ENABLE_DEBUG_INFO = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.google.FirebaseMessagingSample.dev;
@@ -5590,18 +4747,17 @@
};
AFD562F61EB13C6D00EA2233 /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 6098677E3698C58151DC2E85 /* Pods-Messaging_Example_iOS.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
- CODE_SIGN_ENTITLEMENTS = Messaging/App/iOS/Messaging_Example.entitlements;
+ CODE_SIGN_ENTITLEMENTS = Messaging/Sample/iOS/Messaging_Example.entitlements;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = EQHXZ8M8AV;
- INFOPLIST_FILE = "Messaging/App/iOS/Messaging-Info.plist";
+ INFOPLIST_FILE = "Messaging/Sample/iOS/Messaging-Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MTL_ENABLE_DEBUG_INFO = NO;
PRODUCT_BUNDLE_IDENTIFIER = com.google.FirebaseMessagingSample.dev;
@@ -5615,7 +4771,6 @@
};
D01853771EDAD084003A645C /* Debug */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = B4F2CCE27C567E675C27953C /* Pods-Auth_Example_macOS.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ANALYZER_NONNULL = YES;
@@ -5636,7 +4791,6 @@
};
D01853781EDAD084003A645C /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 4BF8EA84DF6AF0AB6E9BB6A0 /* Pods-Auth_Example_macOS.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ANALYZER_NONNULL = YES;
@@ -5658,7 +4812,6 @@
};
D01853C41EDAD364003A645C /* Debug */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = BA1AAFF4508A97F7B32533FC /* Pods-Auth_Tests_macOS.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CLANG_ANALYZER_NONNULL = YES;
@@ -5683,7 +4836,6 @@
};
D01853C51EDAD364003A645C /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = FDBC4B909E617B02D7E741F6 /* Pods-Auth_Tests_macOS.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CLANG_ANALYZER_NONNULL = YES;
@@ -5709,7 +4861,6 @@
};
D064E6A81ED9B1BF001956DF /* Debug */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 6FD4B6DC35E3304CBECFEC61 /* Pods-Core_Example_macOS.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ANALYZER_NONNULL = YES;
@@ -5730,7 +4881,6 @@
};
D064E6A91ED9B1BF001956DF /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = F7649E1B594D8101939746EA /* Pods-Core_Example_macOS.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ANALYZER_NONNULL = YES;
@@ -5752,7 +4902,6 @@
};
D064E6BD1ED9B31C001956DF /* Debug */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 2B3C652966760042D996247E /* Pods-Core_Tests_macOS.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CLANG_ANALYZER_NONNULL = YES;
@@ -5772,7 +4921,6 @@
};
D064E6BE1ED9B31C001956DF /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 287D8FC7F3129B28D8A29FBE /* Pods-Core_Tests_macOS.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CLANG_ANALYZER_NONNULL = YES;
@@ -5793,7 +4941,6 @@
};
D0EDB2CB1EDA04F800B6C31B /* Debug */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 24B879B03BD82C7DE771CA61 /* Pods-Storage_Example_macOS.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ANALYZER_NONNULL = YES;
@@ -5814,7 +4961,6 @@
};
D0EDB2CC1EDA04F800B6C31B /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = FD3AEF097DFCF2ADAC345D2A /* Pods-Storage_Example_macOS.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ANALYZER_NONNULL = YES;
@@ -5836,7 +4982,6 @@
};
D0EDB2F41EDA06CB00B6C31B /* Debug */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 4B490EFB675400675CA98196 /* Pods-Storage_Tests_macOS.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
DEVELOPMENT_TEAM = "";
@@ -5861,7 +5006,6 @@
};
D0EDB2F51EDA06CB00B6C31B /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = E0EF5EDDB1FD839F03FC02AA /* Pods-Storage_Tests_macOS.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
DEVELOPMENT_TEAM = "";
@@ -5882,7 +5026,6 @@
};
D0EDB3051EDA06D500B6C31B /* Debug */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = C1520E81B1BFD24ED1882137 /* Pods-Storage_IntegrationTests_macOS.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CLANG_ANALYZER_NONNULL = YES;
@@ -5904,7 +5047,6 @@
};
D0EDB3061EDA06D500B6C31B /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = B0895BC929D50B20A69CEEEF /* Pods-Storage_IntegrationTests_macOS.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CLANG_ANALYZER_NONNULL = YES;
@@ -5945,7 +5087,6 @@
};
D0FE8A2D1ED9C804003F6722 /* Debug */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 1068E64D36A3C656184168DE /* Pods-Database_Example_macOS.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ANALYZER_NONNULL = YES;
@@ -5966,7 +5107,6 @@
};
D0FE8A2E1ED9C804003F6722 /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 4CC7C8B9E821151509BB3B64 /* Pods-Database_Example_macOS.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ANALYZER_NONNULL = YES;
@@ -5988,7 +5128,6 @@
};
D0FE8A601ED9C86F003F6722 /* Debug */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 86B8E0400070C72C0FE0C2F8 /* Pods-Database_Tests_macOS.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CLANG_ANALYZER_NONNULL = YES;
@@ -6027,7 +5166,6 @@
};
D0FE8A611ED9C86F003F6722 /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 7879DCC8860E7CED0311D4E8 /* Pods-Database_Tests_macOS.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CLANG_ANALYZER_NONNULL = YES;
@@ -6066,7 +5204,6 @@
};
D0FE8A8A1ED9C87B003F6722 /* Debug */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 1CCC00FFFC534F0E9B41CF29 /* Pods-Database_IntegrationTests_macOS.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CLANG_ANALYZER_NONNULL = YES;
@@ -6104,7 +5241,6 @@
};
D0FE8A8B1ED9C87B003F6722 /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 00BD45B2141C68C3F9809A4D /* Pods-Database_IntegrationTests_macOS.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CLANG_ANALYZER_NONNULL = YES;
@@ -6141,9 +5277,235 @@
};
name = Release;
};
+ DE1CD5B21FBA55B000FC031E /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image";
+ ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CODE_SIGN_STYLE = Automatic;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ DEVELOPMENT_TEAM = EQHXZ8M8AV;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ INFOPLIST_FILE = "$(SRCROOT)/Database/App/tvOS/Info.plist";
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ MTL_ENABLE_DEBUG_INFO = YES;
+ PRODUCT_BUNDLE_IDENTIFIER = "com.google.Database-Example-tvOS";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SDKROOT = appletvos;
+ TARGETED_DEVICE_FAMILY = 3;
+ TVOS_DEPLOYMENT_TARGET = 11.1;
+ };
+ name = Debug;
+ };
+ DE1CD5B31FBA55B000FC031E /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image";
+ ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CODE_SIGN_STYLE = Automatic;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ DEVELOPMENT_TEAM = EQHXZ8M8AV;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ INFOPLIST_FILE = "$(SRCROOT)/Database/App/tvOS/Info.plist";
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ MTL_ENABLE_DEBUG_INFO = NO;
+ PRODUCT_BUNDLE_IDENTIFIER = "com.google.Database-Example-tvOS";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SDKROOT = appletvos;
+ TARGETED_DEVICE_FAMILY = 3;
+ TVOS_DEPLOYMENT_TARGET = 11.1;
+ };
+ name = Release;
+ };
+ DE1EC2871FBA5E63007D18D8 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CODE_SIGN_STYLE = Automatic;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ DEVELOPMENT_TEAM = EQHXZ8M8AV;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ HEADER_SEARCH_PATHS = (
+ "$(inherited)",
+ "\"${PODS_ROOT}/../../Firebase/Database/Utilities/Tuples\"",
+ "\"${PODS_ROOT}/../../Firebase/Database/Core\"",
+ "\"${PODS_ROOT}/../../Firebase/Database/Realtime\"",
+ "\"${PODS_ROOT}/../../Firebase/Database/third_party/SocketRocket\"",
+ "\"${PODS_ROOT}/../../Firebase/Database/Utilities\"",
+ "\"${PODS_ROOT}/../../Firebase/Database/Libraries\"",
+ "\"${PODS_ROOT}/../../Firebase/Database/Core/Utilities\"",
+ "\"${PODS_ROOT}/../../Firebase/Database/Api/Private\"",
+ "\"${PODS_ROOT}/../../Firebase/Database/Api\"",
+ "\"${PODS_ROOT}/../../Firebase/Database/Snapshot\"",
+ "\"${PODS_ROOT}/../../Firebase/Database/Login\"",
+ "\"${PODS_ROOT}/../../Firebase/Database/Constants\"",
+ "\"${PODS_ROOT}/../../Firebase/Database\"",
+ "\"${PODS_ROOT}/../../Firebase/Database/Persistence\"",
+ "\"${PODS_ROOT}/../../Firebase/Database/third_party/FImmutableSortedDictionary/FImmutableSortedDictionary\"",
+ "\"${PODS_ROOT}/../../Firebase/Database/Core/View\"",
+ );
+ INFOPLIST_FILE = "Database/Tests/FirebaseTests-Info.plist";
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+ MTL_ENABLE_DEBUG_INFO = YES;
+ PRODUCT_BUNDLE_IDENTIFIER = "com.google.Database-Tests-tvOS";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SDKROOT = appletvos;
+ TARGETED_DEVICE_FAMILY = 3;
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Database_Example_tvOS.app/Database_Example_tvOS";
+ TVOS_DEPLOYMENT_TARGET = 11.1;
+ };
+ name = Debug;
+ };
+ DE1EC2881FBA5E63007D18D8 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CODE_SIGN_STYLE = Automatic;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ DEVELOPMENT_TEAM = EQHXZ8M8AV;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ HEADER_SEARCH_PATHS = (
+ "$(inherited)",
+ "\"${PODS_ROOT}/../../Firebase/Database/Utilities/Tuples\"",
+ "\"${PODS_ROOT}/../../Firebase/Database/Core\"",
+ "\"${PODS_ROOT}/../../Firebase/Database/Realtime\"",
+ "\"${PODS_ROOT}/../../Firebase/Database/third_party/SocketRocket\"",
+ "\"${PODS_ROOT}/../../Firebase/Database/Utilities\"",
+ "\"${PODS_ROOT}/../../Firebase/Database/Libraries\"",
+ "\"${PODS_ROOT}/../../Firebase/Database/Core/Utilities\"",
+ "\"${PODS_ROOT}/../../Firebase/Database/Api/Private\"",
+ "\"${PODS_ROOT}/../../Firebase/Database/Api\"",
+ "\"${PODS_ROOT}/../../Firebase/Database/Snapshot\"",
+ "\"${PODS_ROOT}/../../Firebase/Database/Login\"",
+ "\"${PODS_ROOT}/../../Firebase/Database/Constants\"",
+ "\"${PODS_ROOT}/../../Firebase/Database\"",
+ "\"${PODS_ROOT}/../../Firebase/Database/Persistence\"",
+ "\"${PODS_ROOT}/../../Firebase/Database/third_party/FImmutableSortedDictionary/FImmutableSortedDictionary\"",
+ "\"${PODS_ROOT}/../../Firebase/Database/Core/View\"",
+ );
+ INFOPLIST_FILE = "Database/Tests/FirebaseTests-Info.plist";
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+ MTL_ENABLE_DEBUG_INFO = NO;
+ PRODUCT_BUNDLE_IDENTIFIER = "com.google.Database-Tests-tvOS";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SDKROOT = appletvos;
+ TARGETED_DEVICE_FAMILY = 3;
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Database_Example_tvOS.app/Database_Example_tvOS";
+ TVOS_DEPLOYMENT_TARGET = 11.1;
+ };
+ name = Release;
+ };
+ DE1FAEAC1FBCF5E200897AAA /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image";
+ ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CODE_SIGN_STYLE = Automatic;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ DEVELOPMENT_TEAM = EQHXZ8M8AV;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ INFOPLIST_FILE = "$(SRCROOT)/Auth/App/iOS/Auth-Info.plist";
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ MTL_ENABLE_DEBUG_INFO = YES;
+ PRODUCT_BUNDLE_IDENTIFIER = "com.google.Auth-Example-tvOS";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SDKROOT = appletvos;
+ TARGETED_DEVICE_FAMILY = 3;
+ TVOS_DEPLOYMENT_TARGET = 11.1;
+ };
+ name = Debug;
+ };
+ DE1FAEAD1FBCF5E200897AAA /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image";
+ ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CODE_SIGN_STYLE = Automatic;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ DEVELOPMENT_TEAM = EQHXZ8M8AV;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ INFOPLIST_FILE = "$(SRCROOT)/Auth/App/iOS/Auth-Info.plist";
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ MTL_ENABLE_DEBUG_INFO = NO;
+ PRODUCT_BUNDLE_IDENTIFIER = "com.google.Auth-Example-tvOS";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SDKROOT = appletvos;
+ TARGETED_DEVICE_FAMILY = 3;
+ TVOS_DEPLOYMENT_TARGET = 11.1;
+ };
+ name = Release;
+ };
DE26D2421F70398A004AE1D3 /* Debug */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 48317719F315960780114559 /* Pods-Auth_Sample.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ANALYZER_NONNULL = YES;
@@ -6182,7 +5544,6 @@
};
DE26D2431F70398A004AE1D3 /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 8602A8FB9AF04A0C9A8FE380 /* Pods-Auth_Sample.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ANALYZER_NONNULL = YES;
@@ -6222,7 +5583,6 @@
};
DE26D2651F7049F1004AE1D3 /* Debug */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 09F55B0265DCD315B2DD3C2E /* Pods-Auth_ApiTests.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CLANG_ANALYZER_NONNULL = YES;
@@ -6254,7 +5614,6 @@
};
DE26D2661F7049F1004AE1D3 /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = E8A8A21551A3D8557757AD0D /* Pods-Auth_ApiTests.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CLANG_ANALYZER_NONNULL = YES;
@@ -6287,7 +5646,6 @@
};
DE26D2751F705C35004AE1D3 /* Debug */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 94C0FA103316CB56F37E20EA /* Pods-Auth_EarlGreyTests.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CLANG_ANALYZER_NONNULL = YES;
@@ -6319,7 +5677,6 @@
};
DE26D2761F705C35004AE1D3 /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 0DB176DCABEFDF6C19B302B0 /* Pods-Auth_EarlGreyTests.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CLANG_ANALYZER_NONNULL = YES;
@@ -6352,7 +5709,6 @@
};
DE26D28D1F705EC7004AE1D3 /* Debug */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 0CA98384DDFFEECB1D473552 /* Pods-Auth_SwiftSample.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ANALYZER_NONNULL = YES;
@@ -6386,7 +5742,6 @@
};
DE26D28E1F705EC7004AE1D3 /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 28B01131418E340D322829AC /* Pods-Auth_SwiftSample.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ANALYZER_NONNULL = YES;
@@ -6452,14 +5807,151 @@
};
name = Release;
};
+ DE47C0EB207AC87D00B1AEDF /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CODE_SIGN_ENTITLEMENTS = Messaging/Sample/iOS/Messaging_Example.entitlements;
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ DEVELOPMENT_TEAM = EQHXZ8M8AV;
+ INFOPLIST_FILE = "$(SRCROOT)/Messaging/Sample/iOS/Messaging-Info.plist";
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ MTL_ENABLE_DEBUG_INFO = YES;
+ PRODUCT_BUNDLE_IDENTIFIER = com.google.FirebaseMessagingSample.dev;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ PROVISIONING_PROFILE_SPECIFIER = "";
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
+ SWIFT_OBJC_BRIDGING_HEADER = "Messaging/Messaging_Example-Bridging-Header.h";
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ SWIFT_VERSION = 3.0;
+ };
+ name = Debug;
+ };
+ DE47C0EC207AC87D00B1AEDF /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CODE_SIGN_ENTITLEMENTS = Messaging/Sample/iOS/Messaging_Example.entitlements;
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ DEVELOPMENT_TEAM = EQHXZ8M8AV;
+ INFOPLIST_FILE = "$(SRCROOT)/Messaging/Sample/iOS/Messaging-Info.plist";
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ MTL_ENABLE_DEBUG_INFO = NO;
+ PRODUCT_BUNDLE_IDENTIFIER = com.google.FirebaseMessagingSample.dev;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ PROVISIONING_PROFILE_SPECIFIER = "";
+ SWIFT_OBJC_BRIDGING_HEADER = "Messaging/Messaging_Example-Bridging-Header.h";
+ SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
+ SWIFT_VERSION = 3.0;
+ };
+ name = Release;
+ };
+ DE5389451FBB62E100199FC2 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image";
+ ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CODE_SIGN_STYLE = Automatic;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ DEVELOPMENT_TEAM = EQHXZ8M8AV;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ HEADER_SEARCH_PATHS = (
+ "$(inherited)",
+ "\"${PODS_ROOT}/../../Firebase/Auth/Source/RPCs\"",
+ "\"${PODS_ROOT}/../../Firebase/Auth/Source\"",
+ "\"${PODS_ROOT}/../../Firebase/Auth/Source/AuthProviders\"",
+ );
+ INFOPLIST_FILE = "$(SRCROOT)/Auth/App/iOS/Auth-Info.plist";
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ MTL_ENABLE_DEBUG_INFO = YES;
+ OTHER_LDFLAGS = "$(inherited)";
+ PRODUCT_BUNDLE_IDENTIFIER = "com.google.Auth-Example-tvOS";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SDKROOT = appletvos;
+ TARGETED_DEVICE_FAMILY = 3;
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Auth_Example_tvOS.app/Auth_Example_tvOS";
+ TVOS_DEPLOYMENT_TARGET = 11.1;
+ };
+ name = Debug;
+ };
+ DE5389461FBB62E100199FC2 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image";
+ ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CODE_SIGN_STYLE = Automatic;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ DEVELOPMENT_TEAM = EQHXZ8M8AV;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ HEADER_SEARCH_PATHS = (
+ "$(inherited)",
+ "\"${PODS_ROOT}/../../Firebase/Auth/Source/RPCs\"",
+ "\"${PODS_ROOT}/../../Firebase/Auth/Source\"",
+ "\"${PODS_ROOT}/../../Firebase/Auth/Source/AuthProviders\"",
+ );
+ INFOPLIST_FILE = "$(SRCROOT)/Auth/App/iOS/Auth-Info.plist";
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ MTL_ENABLE_DEBUG_INFO = NO;
+ OTHER_LDFLAGS = "$(inherited)";
+ PRODUCT_BUNDLE_IDENTIFIER = "com.google.Auth-Example-tvOS";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SDKROOT = appletvos;
+ TARGETED_DEVICE_FAMILY = 3;
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Auth_Example_tvOS.app/Auth_Example_tvOS";
+ TVOS_DEPLOYMENT_TARGET = 11.1;
+ };
+ name = Release;
+ };
+ DE545C811FBCA3F000C637AE /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CODE_SIGN_STYLE = Automatic;
+ DEVELOPMENT_TEAM = EQHXZ8M8AV;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SDKROOT = appletvos;
+ };
+ name = Release;
+ };
DE7B8D241E8EF078009EB6DF /* Debug */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = E5978C421A9123C9D34CBA43 /* Pods-Database_Example_iOS.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = "";
INFOPLIST_FILE = "$(SRCROOT)/Database/App/iOS/Database-Info.plist";
@@ -6472,12 +5964,12 @@
};
DE7B8D251E8EF078009EB6DF /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 5DA6361D6B54362D073F3BA5 /* Pods-Database_Example_iOS.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = "";
@@ -6491,7 +5983,6 @@
};
DE7B8D261E8EF078009EB6DF /* Debug */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 000DAC7D0D180A9FBB395BB6 /* Pods-Database_Tests_iOS.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CLANG_ANALYZER_NONNULL = YES;
@@ -6517,6 +6008,7 @@
"\"${PODS_ROOT}/../../Firebase/Database/Persistence\"",
"\"${PODS_ROOT}/../../Firebase/Database/third_party/FImmutableSortedDictionary/FImmutableSortedDictionary\"",
"\"${PODS_ROOT}/../../Firebase/Database/Core/View\"",
+ "\"${PODS_ROOT}/Headers/Private\"",
);
INFOPLIST_FILE = "Database/Tests/FirebaseTests-Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 10.2;
@@ -6531,7 +6023,6 @@
};
DE7B8D271E8EF078009EB6DF /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 2B1B85CD0C7778447F3BFCD5 /* Pods-Database_Tests_iOS.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CLANG_ANALYZER_NONNULL = YES;
@@ -6558,6 +6049,7 @@
"\"${PODS_ROOT}/../../Firebase/Database/Persistence\"",
"\"${PODS_ROOT}/../../Firebase/Database/third_party/FImmutableSortedDictionary/FImmutableSortedDictionary\"",
"\"${PODS_ROOT}/../../Firebase/Database/Core/View\"",
+ "\"${PODS_ROOT}/Headers/Private\"",
);
INFOPLIST_FILE = "Database/Tests/FirebaseTests-Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 10.2;
@@ -6572,7 +6064,6 @@
};
DE9314E51E86C6BE0083EDBF /* Debug */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 6029CD8D7E65D491083D5944 /* Pods-Auth_Example_iOS.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ANALYZER_NONNULL = YES;
@@ -6594,7 +6085,6 @@
};
DE9314E61E86C6BE0083EDBF /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 0C69403B9730C701BF2E0446 /* Pods-Auth_Example_iOS.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ANALYZER_NONNULL = YES;
@@ -6616,7 +6106,6 @@
};
DE9314E71E86C6BE0083EDBF /* Debug */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 1735157165B298F2A1EC36E3 /* Pods-Auth_Tests_iOS.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CLANG_ANALYZER_NONNULL = YES;
@@ -6628,6 +6117,7 @@
"\"${PODS_ROOT}/../../Firebase/Auth/Source/RPCs\"",
"\"${PODS_ROOT}/../../Firebase/Auth/Source\"",
"\"${PODS_ROOT}/../../Firebase/Auth/Source/AuthProviders\"",
+ "\"${PODS_ROOT}/Headers/Private\"",
);
INFOPLIST_FILE = "Auth/Tests/Tests-Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 10.2;
@@ -6641,7 +6131,6 @@
};
DE9314E81E86C6BE0083EDBF /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = D6A450A39BCA3DB4138333D8 /* Pods-Auth_Tests_iOS.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CLANG_ANALYZER_NONNULL = YES;
@@ -6654,6 +6143,7 @@
"\"${PODS_ROOT}/../../Firebase/Auth/Source/RPCs\"",
"\"${PODS_ROOT}/../../Firebase/Auth/Source\"",
"\"${PODS_ROOT}/../../Firebase/Auth/Source/AuthProviders\"",
+ "\"${PODS_ROOT}/Headers/Private\"",
);
INFOPLIST_FILE = "Auth/Tests/Tests-Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 10.2;
@@ -6667,9 +6157,7 @@
};
DE9315B01E8738460083EDBF /* Debug */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 46052D607615BD81295B65C6 /* Pods-Messaging_Tests_iOS.debug.xcconfig */;
buildSettings = {
- ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
BUNDLE_LOADER = "$(TEST_HOST)";
CLANG_ANALYZER_NONNULL = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
@@ -6685,6 +6173,7 @@
"\"${PODS_ROOT}/Headers/Public\"",
"\"${PODS_ROOT}/Headers/Public/FirebaseAnalytics\"",
"\"${PODS_ROOT}/../../Firebase/Messaging\"",
+ "\"${PODS_ROOT}/Headers/Private\"",
);
INFOPLIST_FILE = Messaging/Tests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
@@ -6697,9 +6186,7 @@
};
DE9315B11E8738460083EDBF /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 3A304052F4122D3468145F6C /* Pods-Messaging_Tests_iOS.release.xcconfig */;
buildSettings = {
- ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
BUNDLE_LOADER = "$(TEST_HOST)";
CLANG_ANALYZER_NONNULL = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
@@ -6716,6 +6203,7 @@
"\"${PODS_ROOT}/Headers/Public\"",
"\"${PODS_ROOT}/Headers/Public/FirebaseAnalytics\"",
"\"${PODS_ROOT}/../../Firebase/Messaging\"",
+ "\"${PODS_ROOT}/Headers/Private\"",
);
INFOPLIST_FILE = Messaging/Tests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
@@ -6726,11 +6214,271 @@
};
name = Release;
};
+ DEAAD39C1FBA11280053BF48 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image";
+ ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CODE_SIGN_STYLE = Automatic;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ DEVELOPMENT_TEAM = EQHXZ8M8AV;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ INFOPLIST_FILE = "$(SRCROOT)/Core/App/tvOS/Info.plist";
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ MTL_ENABLE_DEBUG_INFO = YES;
+ PRODUCT_BUNDLE_IDENTIFIER = "com.google.Core-Example-tvOS";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SDKROOT = appletvos;
+ TARGETED_DEVICE_FAMILY = 3;
+ TVOS_DEPLOYMENT_TARGET = 11.1;
+ };
+ name = Debug;
+ };
+ DEAAD39D1FBA11280053BF48 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image";
+ ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CODE_SIGN_STYLE = Automatic;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ DEVELOPMENT_TEAM = EQHXZ8M8AV;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ INFOPLIST_FILE = "$(SRCROOT)/Core/App/tvOS/Info.plist";
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ MTL_ENABLE_DEBUG_INFO = NO;
+ PRODUCT_BUNDLE_IDENTIFIER = "com.google.Core-Example-tvOS";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SDKROOT = appletvos;
+ TARGETED_DEVICE_FAMILY = 3;
+ TVOS_DEPLOYMENT_TARGET = 11.1;
+ };
+ name = Release;
+ };
+ DEAAD39E1FBA11280053BF48 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CODE_SIGN_STYLE = Automatic;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ DEVELOPMENT_TEAM = EQHXZ8M8AV;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ INFOPLIST_FILE = "Core/Tests/Tests-Info.plist";
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+ MTL_ENABLE_DEBUG_INFO = YES;
+ PRODUCT_BUNDLE_IDENTIFIER = "com.google.Core-Example-tvOSTests";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SDKROOT = appletvos;
+ TARGETED_DEVICE_FAMILY = 3;
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Core_Example_tvOS.app/Core_Example_tvOS";
+ TVOS_DEPLOYMENT_TARGET = 11.1;
+ };
+ name = Debug;
+ };
+ DEAAD39F1FBA11280053BF48 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CODE_SIGN_STYLE = Automatic;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ DEVELOPMENT_TEAM = EQHXZ8M8AV;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ INFOPLIST_FILE = "Core/Tests/Tests-Info.plist";
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+ MTL_ENABLE_DEBUG_INFO = NO;
+ PRODUCT_BUNDLE_IDENTIFIER = "com.google.Core-Example-tvOSTests";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SDKROOT = appletvos;
+ TARGETED_DEVICE_FAMILY = 3;
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Core_Example_tvOS.app/Core_Example_tvOS";
+ TVOS_DEPLOYMENT_TARGET = 11.1;
+ };
+ name = Release;
+ };
+ DEAAD3FC1FBA46AB0053BF48 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image";
+ ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CODE_SIGN_STYLE = Automatic;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ DEVELOPMENT_TEAM = EQHXZ8M8AV;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ INFOPLIST_FILE = "$(SRCROOT)/Storage/App/tvOS/Info.plist";
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ MTL_ENABLE_DEBUG_INFO = YES;
+ PRODUCT_BUNDLE_IDENTIFIER = "com.google.Storage-Example-tvOS";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SDKROOT = appletvos;
+ TARGETED_DEVICE_FAMILY = 3;
+ TVOS_DEPLOYMENT_TARGET = 11.1;
+ };
+ name = Debug;
+ };
+ DEAAD3FD1FBA46AB0053BF48 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image";
+ ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CODE_SIGN_STYLE = Automatic;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ DEVELOPMENT_TEAM = EQHXZ8M8AV;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ INFOPLIST_FILE = "$(SRCROOT)/Storage/App/tvOS/Info.plist";
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ MTL_ENABLE_DEBUG_INFO = NO;
+ PRODUCT_BUNDLE_IDENTIFIER = "com.google.Storage-Example-tvOS";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SDKROOT = appletvos;
+ TARGETED_DEVICE_FAMILY = 3;
+ TVOS_DEPLOYMENT_TARGET = 11.1;
+ };
+ name = Release;
+ };
+ DEAAD3FE1FBA46AB0053BF48 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CODE_SIGN_STYLE = Automatic;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ DEVELOPMENT_TEAM = EQHXZ8M8AV;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ HEADER_SEARCH_PATHS = (
+ "$(inherited)",
+ "\"${PODS_ROOT}/../../Firebase/Storage/Private\"",
+ );
+ INFOPLIST_FILE = "Storage/Tests/Tests-Info.plist";
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+ MTL_ENABLE_DEBUG_INFO = YES;
+ PRODUCT_BUNDLE_IDENTIFIER = "com.google.Storage-Example-tvOSTests";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SDKROOT = appletvos;
+ TARGETED_DEVICE_FAMILY = 3;
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Storage_Example_tvOS.app/Storage_Example_tvOS";
+ TVOS_DEPLOYMENT_TARGET = 11.1;
+ };
+ name = Debug;
+ };
+ DEAAD3FF1FBA46AB0053BF48 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CODE_SIGN_STYLE = Automatic;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ DEVELOPMENT_TEAM = EQHXZ8M8AV;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ HEADER_SEARCH_PATHS = (
+ "$(inherited)",
+ "\"${PODS_ROOT}/../../Firebase/Storage/Private\"",
+ );
+ INFOPLIST_FILE = "Storage/Tests/Tests-Info.plist";
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+ MTL_ENABLE_DEBUG_INFO = NO;
+ PRODUCT_BUNDLE_IDENTIFIER = "com.google.Storage-Example-tvOSTests";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SDKROOT = appletvos;
+ TARGETED_DEVICE_FAMILY = 3;
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Storage_Example_tvOS.app/Storage_Example_tvOS";
+ TVOS_DEPLOYMENT_TARGET = 11.1;
+ };
+ name = Release;
+ };
DEB13A061E73506A00AC236D /* Debug */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 3E26CB853AB2CAF1960A0F71 /* Pods-Storage_Example_iOS.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
DEVELOPMENT_TEAM = "";
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
GCC_PRECOMPILE_PREFIX_HEADER = YES;
@@ -6749,9 +6497,9 @@
};
DEB13A071E73506A00AC236D /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 97790B1C788991008685954F /* Pods-Storage_Example_iOS.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
DEVELOPMENT_TEAM = "";
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
GCC_PRECOMPILE_PREFIX_HEADER = YES;
@@ -6770,7 +6518,6 @@
};
DEB13A211E73507E00AC236D /* Debug */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 466C3694B6C68F69BA4DA448 /* Pods-Storage_Tests_iOS.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
DEVELOPMENT_TEAM = "";
@@ -6783,6 +6530,7 @@
HEADER_SEARCH_PATHS = (
"$(inherited)",
"\"${PODS_ROOT}/../../Firebase/Storage/Private\"",
+ "\"${PODS_ROOT}/Headers/Private\"",
);
INFOPLIST_FILE = "Storage/Tests/Tests-Info.plist";
PRODUCT_BUNDLE_IDENTIFIER = "com.google.Storage-Tests-iOS";
@@ -6794,7 +6542,6 @@
};
DEB13A221E73507E00AC236D /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = B1EFE04FF3C9650984C5E3C3 /* Pods-Storage_Tests_iOS.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
DEVELOPMENT_TEAM = "";
@@ -6803,6 +6550,7 @@
HEADER_SEARCH_PATHS = (
"$(inherited)",
"\"${PODS_ROOT}/../../Firebase/Storage/Private\"",
+ "\"${PODS_ROOT}/Headers/Private\"",
);
INFOPLIST_FILE = "Storage/Tests/Tests-Info.plist";
PRODUCT_BUNDLE_IDENTIFIER = "com.google.Storage-Tests-iOS";
@@ -6814,7 +6562,6 @@
};
DEDFEFF31FD1B8C100F7D466 /* Debug */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 77FA2AB612D17244983008F7 /* Pods-Analytics_Tests_iOS.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CLANG_ANALYZER_NONNULL = YES;
@@ -6846,7 +6593,6 @@
};
DEDFEFF41FD1B8C100F7D466 /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 456CD9478A63272C4397975E /* Pods-Analytics_Tests_iOS.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CLANG_ANALYZER_NONNULL = YES;
@@ -6879,7 +6625,6 @@
};
DEE14D601E84464D006FA992 /* Debug */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 870F50EE08ED74C38B5CAF79 /* Pods-Core_Example_iOS.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ANALYZER_NONNULL = YES;
@@ -6896,7 +6641,6 @@
};
DEE14D611E84464D006FA992 /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 1E6C076D38C1763E00A3DACA /* Pods-Core_Example_iOS.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ANALYZER_NONNULL = YES;
@@ -6914,14 +6658,16 @@
};
DEE14D621E84464D006FA992 /* Debug */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 0D66D613C54F5BFF80D9AB63 /* Pods-Core_Tests_iOS.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CLANG_ANALYZER_NONNULL = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = "";
- HEADER_SEARCH_PATHS = "$(inherited)";
+ HEADER_SEARCH_PATHS = (
+ "$(inherited)",
+ "${PODS_ROOT}/Headers/Private",
+ );
INFOPLIST_FILE = "Core/Tests/Tests-Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 10.2;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
@@ -6934,7 +6680,6 @@
};
DEE14D631E84464D006FA992 /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = DDF4A6C7CFF20DCCF96071EC /* Pods-Core_Tests_iOS.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CLANG_ANALYZER_NONNULL = YES;
@@ -6942,7 +6687,10 @@
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = "";
- HEADER_SEARCH_PATHS = "$(inherited)";
+ HEADER_SEARCH_PATHS = (
+ "$(inherited)",
+ "${PODS_ROOT}/Headers/Private",
+ );
INFOPLIST_FILE = "Core/Tests/Tests-Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 10.2;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
@@ -6953,6 +6701,16 @@
};
name = Release;
};
+ DEF6C30C1FBCE70C005D0740 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ COPY_PHASE_STRIP = NO;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ PRODUCT_NAME = AllUnitTests_tvOS;
+ };
+ name = Debug;
+ };
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
@@ -7091,6 +6849,33 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
+ DE1CD5B61FBA55B000FC031E /* Build configuration list for PBXNativeTarget "Database_Example_tvOS" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ DE1CD5B21FBA55B000FC031E /* Debug */,
+ DE1CD5B31FBA55B000FC031E /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ DE1EC2861FBA5E63007D18D8 /* Build configuration list for PBXNativeTarget "Database_Tests_tvOS" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ DE1EC2871FBA5E63007D18D8 /* Debug */,
+ DE1EC2881FBA5E63007D18D8 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ DE1FAEAB1FBCF5E200897AAA /* Build configuration list for PBXNativeTarget "Auth_Example_tvOS" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ DE1FAEAC1FBCF5E200897AAA /* Debug */,
+ DE1FAEAD1FBCF5E200897AAA /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
DE26D2411F70398A004AE1D3 /* Build configuration list for PBXNativeTarget "Auth_Sample" */ = {
isa = XCConfigurationList;
buildConfigurations = (
@@ -7145,6 +6930,33 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
+ DE47C0EA207AC87D00B1AEDF /* Build configuration list for PBXNativeTarget "Messaging_Sample_iOS" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ DE47C0EB207AC87D00B1AEDF /* Debug */,
+ DE47C0EC207AC87D00B1AEDF /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ DE5389491FBB62E100199FC2 /* Build configuration list for PBXNativeTarget "Auth_Tests_tvOS" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ DE5389451FBB62E100199FC2 /* Debug */,
+ DE5389461FBB62E100199FC2 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ DE545C821FBCA3F000C637AE /* Build configuration list for PBXAggregateTarget "AllUnitTests_tvOS" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ DE545C811FBCA3F000C637AE /* Release */,
+ DEF6C30C1FBCE70C005D0740 /* Debug */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
DE7B8D281E8EF078009EB6DF /* Build configuration list for PBXNativeTarget "Database_Example_iOS" */ = {
isa = XCConfigurationList;
buildConfigurations = (
@@ -7190,6 +7002,42 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
+ DEAAD3A01FBA11280053BF48 /* Build configuration list for PBXNativeTarget "Core_Example_tvOS" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ DEAAD39C1FBA11280053BF48 /* Debug */,
+ DEAAD39D1FBA11280053BF48 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ DEAAD3A11FBA11280053BF48 /* Build configuration list for PBXNativeTarget "Core_Tests_tvOS" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ DEAAD39E1FBA11280053BF48 /* Debug */,
+ DEAAD39F1FBA11280053BF48 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ DEAAD4001FBA46AB0053BF48 /* Build configuration list for PBXNativeTarget "Storage_Example_tvOS" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ DEAAD3FC1FBA46AB0053BF48 /* Debug */,
+ DEAAD3FD1FBA46AB0053BF48 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ DEAAD4011FBA46AB0053BF48 /* Build configuration list for PBXNativeTarget "Storage_Tests_tvOS" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ DEAAD3FE1FBA46AB0053BF48 /* Debug */,
+ DEAAD3FF1FBA46AB0053BF48 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
DEB13A051E73506A00AC236D /* Build configuration list for PBXNativeTarget "Storage_Example_iOS" */ = {
isa = XCConfigurationList;
buildConfigurations = (
diff --git a/Example/Firebase.xcodeproj/xcshareddata/xcschemes/AllUnitTests_tvOS.xcscheme b/Example/Firebase.xcodeproj/xcshareddata/xcschemes/AllUnitTests_tvOS.xcscheme
new file mode 100644
index 0000000..24d7ae9
--- /dev/null
+++ b/Example/Firebase.xcodeproj/xcshareddata/xcschemes/AllUnitTests_tvOS.xcscheme
@@ -0,0 +1,131 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+ LastUpgradeVersion = "0910"
+ version = "1.3">
+ <BuildAction
+ parallelizeBuildables = "YES"
+ buildImplicitDependencies = "YES">
+ <BuildActionEntries>
+ <BuildActionEntry
+ buildForTesting = "YES"
+ buildForRunning = "YES"
+ buildForProfiling = "YES"
+ buildForArchiving = "YES"
+ buildForAnalyzing = "YES">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DE545C7F1FBCA3F000C637AE"
+ BuildableName = "AllUnitTests_tvOS"
+ BlueprintName = "AllUnitTests_tvOS"
+ ReferencedContainer = "container:Firebase.xcodeproj">
+ </BuildableReference>
+ </BuildActionEntry>
+ </BuildActionEntries>
+ </BuildAction>
+ <TestAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ language = ""
+ shouldUseLaunchSchemeArgsEnv = "YES">
+ <Testables>
+ <TestableReference
+ skipped = "NO">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DEAAD3941FBA11270053BF48"
+ BuildableName = "Core_Tests_tvOS.xctest"
+ BlueprintName = "Core_Tests_tvOS"
+ ReferencedContainer = "container:Firebase.xcodeproj">
+ </BuildableReference>
+ </TestableReference>
+ <TestableReference
+ skipped = "NO">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DE1EC27E1FBA5E63007D18D8"
+ BuildableName = "Database_Tests_tvOS.xctest"
+ BlueprintName = "Database_Tests_tvOS"
+ ReferencedContainer = "container:Firebase.xcodeproj">
+ </BuildableReference>
+ </TestableReference>
+ <TestableReference
+ skipped = "NO">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DEAAD3F41FBA46AB0053BF48"
+ BuildableName = "Storage_Tests_tvOS.xctest"
+ BlueprintName = "Storage_Tests_tvOS"
+ ReferencedContainer = "container:Firebase.xcodeproj">
+ </BuildableReference>
+ </TestableReference>
+ <TestableReference
+ skipped = "NO">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DE53893D1FBB62E100199FC2"
+ BuildableName = "Auth_Tests_tvOS.xctest"
+ BlueprintName = "Auth_Tests_tvOS"
+ ReferencedContainer = "container:Firebase.xcodeproj">
+ </BuildableReference>
+ </TestableReference>
+ </Testables>
+ <MacroExpansion>
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DE545C7F1FBCA3F000C637AE"
+ BuildableName = "AllUnitTests_tvOS"
+ BlueprintName = "AllUnitTests_tvOS"
+ ReferencedContainer = "container:Firebase.xcodeproj">
+ </BuildableReference>
+ </MacroExpansion>
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </TestAction>
+ <LaunchAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ language = ""
+ launchStyle = "0"
+ useCustomWorkingDirectory = "NO"
+ ignoresPersistentStateOnLaunch = "NO"
+ debugDocumentVersioning = "YES"
+ debugServiceExtension = "internal"
+ allowLocationSimulation = "YES">
+ <MacroExpansion>
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DE545C7F1FBCA3F000C637AE"
+ BuildableName = "AllUnitTests_tvOS"
+ BlueprintName = "AllUnitTests_tvOS"
+ ReferencedContainer = "container:Firebase.xcodeproj">
+ </BuildableReference>
+ </MacroExpansion>
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </LaunchAction>
+ <ProfileAction
+ buildConfiguration = "Release"
+ shouldUseLaunchSchemeArgsEnv = "YES"
+ savedToolIdentifier = ""
+ useCustomWorkingDirectory = "NO"
+ debugDocumentVersioning = "YES">
+ <MacroExpansion>
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DE545C7F1FBCA3F000C637AE"
+ BuildableName = "AllUnitTests_tvOS"
+ BlueprintName = "AllUnitTests_tvOS"
+ ReferencedContainer = "container:Firebase.xcodeproj">
+ </BuildableReference>
+ </MacroExpansion>
+ </ProfileAction>
+ <AnalyzeAction
+ buildConfiguration = "Debug">
+ </AnalyzeAction>
+ <ArchiveAction
+ buildConfiguration = "Release"
+ revealArchiveInOrganizer = "YES">
+ </ArchiveAction>
+</Scheme>
diff --git a/Example/Firebase.xcodeproj/xcshareddata/xcschemes/Auth_Example_tvOS.xcscheme b/Example/Firebase.xcodeproj/xcshareddata/xcschemes/Auth_Example_tvOS.xcscheme
new file mode 100644
index 0000000..92ef985
--- /dev/null
+++ b/Example/Firebase.xcodeproj/xcshareddata/xcschemes/Auth_Example_tvOS.xcscheme
@@ -0,0 +1,103 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+ LastUpgradeVersion = "0910"
+ version = "1.3">
+ <BuildAction
+ parallelizeBuildables = "YES"
+ buildImplicitDependencies = "YES">
+ <BuildActionEntries>
+ <BuildActionEntry
+ buildForTesting = "YES"
+ buildForRunning = "YES"
+ buildForProfiling = "YES"
+ buildForArchiving = "YES"
+ buildForAnalyzing = "YES">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DE1FAE8F1FBCF5E100897AAA"
+ BuildableName = "Auth_Example_tvOS.app"
+ BlueprintName = "Auth_Example_tvOS"
+ ReferencedContainer = "container:Firebase.xcodeproj">
+ </BuildableReference>
+ </BuildActionEntry>
+ </BuildActionEntries>
+ </BuildAction>
+ <TestAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ language = ""
+ shouldUseLaunchSchemeArgsEnv = "YES">
+ <Testables>
+ <TestableReference
+ skipped = "NO">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DE53893D1FBB62E100199FC2"
+ BuildableName = "Auth_Tests_tvOS.xctest"
+ BlueprintName = "Auth_Tests_tvOS"
+ ReferencedContainer = "container:Firebase.xcodeproj">
+ </BuildableReference>
+ </TestableReference>
+ </Testables>
+ <MacroExpansion>
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DE1FAE8F1FBCF5E100897AAA"
+ BuildableName = "Auth_Example_tvOS.app"
+ BlueprintName = "Auth_Example_tvOS"
+ ReferencedContainer = "container:Firebase.xcodeproj">
+ </BuildableReference>
+ </MacroExpansion>
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </TestAction>
+ <LaunchAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ language = ""
+ launchStyle = "0"
+ useCustomWorkingDirectory = "NO"
+ ignoresPersistentStateOnLaunch = "NO"
+ debugDocumentVersioning = "YES"
+ debugServiceExtension = "internal"
+ allowLocationSimulation = "YES">
+ <BuildableProductRunnable
+ runnableDebuggingMode = "0">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DE1FAE8F1FBCF5E100897AAA"
+ BuildableName = "Auth_Example_tvOS.app"
+ BlueprintName = "Auth_Example_tvOS"
+ ReferencedContainer = "container:Firebase.xcodeproj">
+ </BuildableReference>
+ </BuildableProductRunnable>
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </LaunchAction>
+ <ProfileAction
+ buildConfiguration = "Release"
+ shouldUseLaunchSchemeArgsEnv = "YES"
+ savedToolIdentifier = ""
+ useCustomWorkingDirectory = "NO"
+ debugDocumentVersioning = "YES">
+ <BuildableProductRunnable
+ runnableDebuggingMode = "0">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DE1FAE8F1FBCF5E100897AAA"
+ BuildableName = "Auth_Example_tvOS.app"
+ BlueprintName = "Auth_Example_tvOS"
+ ReferencedContainer = "container:Firebase.xcodeproj">
+ </BuildableReference>
+ </BuildableProductRunnable>
+ </ProfileAction>
+ <AnalyzeAction
+ buildConfiguration = "Debug">
+ </AnalyzeAction>
+ <ArchiveAction
+ buildConfiguration = "Release"
+ revealArchiveInOrganizer = "YES">
+ </ArchiveAction>
+</Scheme>
diff --git a/Example/Firebase.xcodeproj/xcshareddata/xcschemes/Auth_Tests_tvOS.xcscheme b/Example/Firebase.xcodeproj/xcshareddata/xcschemes/Auth_Tests_tvOS.xcscheme
new file mode 100644
index 0000000..10f6028
--- /dev/null
+++ b/Example/Firebase.xcodeproj/xcshareddata/xcschemes/Auth_Tests_tvOS.xcscheme
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+ LastUpgradeVersion = "0910"
+ version = "1.3">
+ <BuildAction
+ parallelizeBuildables = "YES"
+ buildImplicitDependencies = "YES">
+ </BuildAction>
+ <TestAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ language = ""
+ shouldUseLaunchSchemeArgsEnv = "YES">
+ <Testables>
+ <TestableReference
+ skipped = "NO">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DE53893D1FBB62E100199FC2"
+ BuildableName = "Auth_Tests_tvOS.xctest"
+ BlueprintName = "Auth_Tests_tvOS"
+ ReferencedContainer = "container:Firebase.xcodeproj">
+ </BuildableReference>
+ </TestableReference>
+ <TestableReference
+ skipped = "NO">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DE1FAEA31FBCF5E200897AAA"
+ BuildableName = "Auth_Example_tvOSTests.xctest"
+ BlueprintName = "Auth_Example_tvOSTests"
+ ReferencedContainer = "container:Firebase.xcodeproj">
+ </BuildableReference>
+ </TestableReference>
+ </Testables>
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </TestAction>
+ <LaunchAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ language = ""
+ launchStyle = "0"
+ useCustomWorkingDirectory = "NO"
+ ignoresPersistentStateOnLaunch = "NO"
+ debugDocumentVersioning = "YES"
+ debugServiceExtension = "internal"
+ allowLocationSimulation = "YES">
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </LaunchAction>
+ <ProfileAction
+ buildConfiguration = "Release"
+ shouldUseLaunchSchemeArgsEnv = "YES"
+ savedToolIdentifier = ""
+ useCustomWorkingDirectory = "NO"
+ debugDocumentVersioning = "YES">
+ </ProfileAction>
+ <AnalyzeAction
+ buildConfiguration = "Debug">
+ </AnalyzeAction>
+ <ArchiveAction
+ buildConfiguration = "Release"
+ revealArchiveInOrganizer = "YES">
+ </ArchiveAction>
+</Scheme>
diff --git a/Example/Firebase.xcodeproj/xcshareddata/xcschemes/Core_Example_tvOS.xcscheme b/Example/Firebase.xcodeproj/xcshareddata/xcschemes/Core_Example_tvOS.xcscheme
new file mode 100644
index 0000000..a499aca
--- /dev/null
+++ b/Example/Firebase.xcodeproj/xcshareddata/xcschemes/Core_Example_tvOS.xcscheme
@@ -0,0 +1,103 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+ LastUpgradeVersion = "0910"
+ version = "1.3">
+ <BuildAction
+ parallelizeBuildables = "YES"
+ buildImplicitDependencies = "YES">
+ <BuildActionEntries>
+ <BuildActionEntry
+ buildForTesting = "YES"
+ buildForRunning = "YES"
+ buildForProfiling = "YES"
+ buildForArchiving = "YES"
+ buildForAnalyzing = "YES">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DEAAD3801FBA11270053BF48"
+ BuildableName = "Core_Example_tvOS.app"
+ BlueprintName = "Core_Example_tvOS"
+ ReferencedContainer = "container:Firebase.xcodeproj">
+ </BuildableReference>
+ </BuildActionEntry>
+ </BuildActionEntries>
+ </BuildAction>
+ <TestAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ language = ""
+ shouldUseLaunchSchemeArgsEnv = "YES">
+ <Testables>
+ <TestableReference
+ skipped = "NO">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DEAAD3941FBA11270053BF48"
+ BuildableName = "Core_Tests_tvOS.xctest"
+ BlueprintName = "Core_Tests_tvOS"
+ ReferencedContainer = "container:Firebase.xcodeproj">
+ </BuildableReference>
+ </TestableReference>
+ </Testables>
+ <MacroExpansion>
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DEAAD3801FBA11270053BF48"
+ BuildableName = "Core_Example_tvOS.app"
+ BlueprintName = "Core_Example_tvOS"
+ ReferencedContainer = "container:Firebase.xcodeproj">
+ </BuildableReference>
+ </MacroExpansion>
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </TestAction>
+ <LaunchAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ language = ""
+ launchStyle = "0"
+ useCustomWorkingDirectory = "NO"
+ ignoresPersistentStateOnLaunch = "NO"
+ debugDocumentVersioning = "YES"
+ debugServiceExtension = "internal"
+ allowLocationSimulation = "YES">
+ <BuildableProductRunnable
+ runnableDebuggingMode = "0">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DEAAD3801FBA11270053BF48"
+ BuildableName = "Core_Example_tvOS.app"
+ BlueprintName = "Core_Example_tvOS"
+ ReferencedContainer = "container:Firebase.xcodeproj">
+ </BuildableReference>
+ </BuildableProductRunnable>
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </LaunchAction>
+ <ProfileAction
+ buildConfiguration = "Release"
+ shouldUseLaunchSchemeArgsEnv = "YES"
+ savedToolIdentifier = ""
+ useCustomWorkingDirectory = "NO"
+ debugDocumentVersioning = "YES">
+ <BuildableProductRunnable
+ runnableDebuggingMode = "0">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DEAAD3801FBA11270053BF48"
+ BuildableName = "Core_Example_tvOS.app"
+ BlueprintName = "Core_Example_tvOS"
+ ReferencedContainer = "container:Firebase.xcodeproj">
+ </BuildableReference>
+ </BuildableProductRunnable>
+ </ProfileAction>
+ <AnalyzeAction
+ buildConfiguration = "Debug">
+ </AnalyzeAction>
+ <ArchiveAction
+ buildConfiguration = "Release"
+ revealArchiveInOrganizer = "YES">
+ </ArchiveAction>
+</Scheme>
diff --git a/Example/Firebase.xcodeproj/xcshareddata/xcschemes/Core_Tests_tvOS.xcscheme b/Example/Firebase.xcodeproj/xcshareddata/xcschemes/Core_Tests_tvOS.xcscheme
new file mode 100644
index 0000000..29e1d7e
--- /dev/null
+++ b/Example/Firebase.xcodeproj/xcshareddata/xcschemes/Core_Tests_tvOS.xcscheme
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+ LastUpgradeVersion = "0910"
+ version = "1.3">
+ <BuildAction
+ parallelizeBuildables = "YES"
+ buildImplicitDependencies = "YES">
+ </BuildAction>
+ <TestAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ language = ""
+ shouldUseLaunchSchemeArgsEnv = "YES">
+ <Testables>
+ <TestableReference
+ skipped = "NO">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DEAAD3941FBA11270053BF48"
+ BuildableName = "Core_Tests_tvOS.xctest"
+ BlueprintName = "Core_Tests_tvOS"
+ ReferencedContainer = "container:Firebase.xcodeproj">
+ </BuildableReference>
+ </TestableReference>
+ </Testables>
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </TestAction>
+ <LaunchAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ language = ""
+ launchStyle = "0"
+ useCustomWorkingDirectory = "NO"
+ ignoresPersistentStateOnLaunch = "NO"
+ debugDocumentVersioning = "YES"
+ debugServiceExtension = "internal"
+ allowLocationSimulation = "YES">
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </LaunchAction>
+ <ProfileAction
+ buildConfiguration = "Release"
+ shouldUseLaunchSchemeArgsEnv = "YES"
+ savedToolIdentifier = ""
+ useCustomWorkingDirectory = "NO"
+ debugDocumentVersioning = "YES">
+ </ProfileAction>
+ <AnalyzeAction
+ buildConfiguration = "Debug">
+ </AnalyzeAction>
+ <ArchiveAction
+ buildConfiguration = "Release"
+ revealArchiveInOrganizer = "YES">
+ </ArchiveAction>
+</Scheme>
diff --git a/Example/Firebase.xcodeproj/xcshareddata/xcschemes/Database_Example_tvOS.xcscheme b/Example/Firebase.xcodeproj/xcshareddata/xcschemes/Database_Example_tvOS.xcscheme
new file mode 100644
index 0000000..8f1f744
--- /dev/null
+++ b/Example/Firebase.xcodeproj/xcshareddata/xcschemes/Database_Example_tvOS.xcscheme
@@ -0,0 +1,103 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+ LastUpgradeVersion = "0910"
+ version = "1.3">
+ <BuildAction
+ parallelizeBuildables = "YES"
+ buildImplicitDependencies = "YES">
+ <BuildActionEntries>
+ <BuildActionEntry
+ buildForTesting = "YES"
+ buildForRunning = "YES"
+ buildForProfiling = "YES"
+ buildForArchiving = "YES"
+ buildForAnalyzing = "YES">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DE1CD5961FBA55AF00FC031E"
+ BuildableName = "Database_Example_tvOS.app"
+ BlueprintName = "Database_Example_tvOS"
+ ReferencedContainer = "container:Firebase.xcodeproj">
+ </BuildableReference>
+ </BuildActionEntry>
+ </BuildActionEntries>
+ </BuildAction>
+ <TestAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ language = ""
+ shouldUseLaunchSchemeArgsEnv = "YES">
+ <Testables>
+ <TestableReference
+ skipped = "NO">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DE1EC27E1FBA5E63007D18D8"
+ BuildableName = "Database_Tests_tvOS.xctest"
+ BlueprintName = "Database_Tests_tvOS"
+ ReferencedContainer = "container:Firebase.xcodeproj">
+ </BuildableReference>
+ </TestableReference>
+ </Testables>
+ <MacroExpansion>
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DE1CD5961FBA55AF00FC031E"
+ BuildableName = "Database_Example_tvOS.app"
+ BlueprintName = "Database_Example_tvOS"
+ ReferencedContainer = "container:Firebase.xcodeproj">
+ </BuildableReference>
+ </MacroExpansion>
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </TestAction>
+ <LaunchAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ language = ""
+ launchStyle = "0"
+ useCustomWorkingDirectory = "NO"
+ ignoresPersistentStateOnLaunch = "NO"
+ debugDocumentVersioning = "YES"
+ debugServiceExtension = "internal"
+ allowLocationSimulation = "YES">
+ <BuildableProductRunnable
+ runnableDebuggingMode = "0">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DE1CD5961FBA55AF00FC031E"
+ BuildableName = "Database_Example_tvOS.app"
+ BlueprintName = "Database_Example_tvOS"
+ ReferencedContainer = "container:Firebase.xcodeproj">
+ </BuildableReference>
+ </BuildableProductRunnable>
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </LaunchAction>
+ <ProfileAction
+ buildConfiguration = "Release"
+ shouldUseLaunchSchemeArgsEnv = "YES"
+ savedToolIdentifier = ""
+ useCustomWorkingDirectory = "NO"
+ debugDocumentVersioning = "YES">
+ <BuildableProductRunnable
+ runnableDebuggingMode = "0">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DE1CD5961FBA55AF00FC031E"
+ BuildableName = "Database_Example_tvOS.app"
+ BlueprintName = "Database_Example_tvOS"
+ ReferencedContainer = "container:Firebase.xcodeproj">
+ </BuildableReference>
+ </BuildableProductRunnable>
+ </ProfileAction>
+ <AnalyzeAction
+ buildConfiguration = "Debug">
+ </AnalyzeAction>
+ <ArchiveAction
+ buildConfiguration = "Release"
+ revealArchiveInOrganizer = "YES">
+ </ArchiveAction>
+</Scheme>
diff --git a/Example/Firebase.xcodeproj/xcshareddata/xcschemes/Database_Tests_tvOS.xcscheme b/Example/Firebase.xcodeproj/xcshareddata/xcschemes/Database_Tests_tvOS.xcscheme
new file mode 100644
index 0000000..1174631
--- /dev/null
+++ b/Example/Firebase.xcodeproj/xcshareddata/xcschemes/Database_Tests_tvOS.xcscheme
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+ LastUpgradeVersion = "0910"
+ version = "1.3">
+ <BuildAction
+ parallelizeBuildables = "YES"
+ buildImplicitDependencies = "YES">
+ </BuildAction>
+ <TestAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ language = ""
+ shouldUseLaunchSchemeArgsEnv = "YES">
+ <Testables>
+ <TestableReference
+ skipped = "NO">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DE1EC27E1FBA5E63007D18D8"
+ BuildableName = "Database_Tests_tvOS.xctest"
+ BlueprintName = "Database_Tests_tvOS"
+ ReferencedContainer = "container:Firebase.xcodeproj">
+ </BuildableReference>
+ </TestableReference>
+ </Testables>
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </TestAction>
+ <LaunchAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ language = ""
+ launchStyle = "0"
+ useCustomWorkingDirectory = "NO"
+ ignoresPersistentStateOnLaunch = "NO"
+ debugDocumentVersioning = "YES"
+ debugServiceExtension = "internal"
+ allowLocationSimulation = "YES">
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </LaunchAction>
+ <ProfileAction
+ buildConfiguration = "Release"
+ shouldUseLaunchSchemeArgsEnv = "YES"
+ savedToolIdentifier = ""
+ useCustomWorkingDirectory = "NO"
+ debugDocumentVersioning = "YES">
+ </ProfileAction>
+ <AnalyzeAction
+ buildConfiguration = "Debug">
+ </AnalyzeAction>
+ <ArchiveAction
+ buildConfiguration = "Release"
+ revealArchiveInOrganizer = "YES">
+ </ArchiveAction>
+</Scheme>
diff --git a/Example/Firebase.xcodeproj/xcshareddata/xcschemes/Messaging_Sample_iOS.xcscheme b/Example/Firebase.xcodeproj/xcshareddata/xcschemes/Messaging_Sample_iOS.xcscheme
new file mode 100644
index 0000000..57e31f3
--- /dev/null
+++ b/Example/Firebase.xcodeproj/xcshareddata/xcschemes/Messaging_Sample_iOS.xcscheme
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+ LastUpgradeVersion = "0930"
+ version = "1.3">
+ <BuildAction
+ parallelizeBuildables = "YES"
+ buildImplicitDependencies = "YES">
+ <BuildActionEntries>
+ <BuildActionEntry
+ buildForTesting = "YES"
+ buildForRunning = "YES"
+ buildForProfiling = "YES"
+ buildForArchiving = "YES"
+ buildForAnalyzing = "YES">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DE47C0DC207AC87D00B1AEDF"
+ BuildableName = "Messaging_Sample_iOS.app"
+ BlueprintName = "Messaging_Sample_iOS"
+ ReferencedContainer = "container:Firebase.xcodeproj">
+ </BuildableReference>
+ </BuildActionEntry>
+ </BuildActionEntries>
+ </BuildAction>
+ <TestAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ shouldUseLaunchSchemeArgsEnv = "YES">
+ <Testables>
+ </Testables>
+ <MacroExpansion>
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DE47C0DC207AC87D00B1AEDF"
+ BuildableName = "Messaging_Sample_iOS.app"
+ BlueprintName = "Messaging_Sample_iOS"
+ ReferencedContainer = "container:Firebase.xcodeproj">
+ </BuildableReference>
+ </MacroExpansion>
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </TestAction>
+ <LaunchAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ launchStyle = "0"
+ useCustomWorkingDirectory = "NO"
+ ignoresPersistentStateOnLaunch = "NO"
+ debugDocumentVersioning = "YES"
+ debugServiceExtension = "internal"
+ allowLocationSimulation = "YES">
+ <BuildableProductRunnable
+ runnableDebuggingMode = "0">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DE47C0DC207AC87D00B1AEDF"
+ BuildableName = "Messaging_Sample_iOS.app"
+ BlueprintName = "Messaging_Sample_iOS"
+ ReferencedContainer = "container:Firebase.xcodeproj">
+ </BuildableReference>
+ </BuildableProductRunnable>
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </LaunchAction>
+ <ProfileAction
+ buildConfiguration = "Release"
+ shouldUseLaunchSchemeArgsEnv = "YES"
+ savedToolIdentifier = ""
+ useCustomWorkingDirectory = "NO"
+ debugDocumentVersioning = "YES">
+ <BuildableProductRunnable
+ runnableDebuggingMode = "0">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DE47C0DC207AC87D00B1AEDF"
+ BuildableName = "Messaging_Sample_iOS.app"
+ BlueprintName = "Messaging_Sample_iOS"
+ ReferencedContainer = "container:Firebase.xcodeproj">
+ </BuildableReference>
+ </BuildableProductRunnable>
+ </ProfileAction>
+ <AnalyzeAction
+ buildConfiguration = "Debug">
+ </AnalyzeAction>
+ <ArchiveAction
+ buildConfiguration = "Release"
+ revealArchiveInOrganizer = "YES">
+ </ArchiveAction>
+</Scheme>
diff --git a/Example/Firebase.xcodeproj/xcshareddata/xcschemes/Storage_Example_tvOS.xcscheme b/Example/Firebase.xcodeproj/xcshareddata/xcschemes/Storage_Example_tvOS.xcscheme
new file mode 100644
index 0000000..7cd609f
--- /dev/null
+++ b/Example/Firebase.xcodeproj/xcshareddata/xcschemes/Storage_Example_tvOS.xcscheme
@@ -0,0 +1,103 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+ LastUpgradeVersion = "0910"
+ version = "1.3">
+ <BuildAction
+ parallelizeBuildables = "YES"
+ buildImplicitDependencies = "YES">
+ <BuildActionEntries>
+ <BuildActionEntry
+ buildForTesting = "YES"
+ buildForRunning = "YES"
+ buildForProfiling = "YES"
+ buildForArchiving = "YES"
+ buildForAnalyzing = "YES">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DEAAD3E01FBA46AA0053BF48"
+ BuildableName = "Storage_Example_tvOS.app"
+ BlueprintName = "Storage_Example_tvOS"
+ ReferencedContainer = "container:Firebase.xcodeproj">
+ </BuildableReference>
+ </BuildActionEntry>
+ </BuildActionEntries>
+ </BuildAction>
+ <TestAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ language = ""
+ shouldUseLaunchSchemeArgsEnv = "YES">
+ <Testables>
+ <TestableReference
+ skipped = "NO">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DEAAD3F41FBA46AB0053BF48"
+ BuildableName = "Storage_Tests_tvOS.xctest"
+ BlueprintName = "Storage_Tests_tvOS"
+ ReferencedContainer = "container:Firebase.xcodeproj">
+ </BuildableReference>
+ </TestableReference>
+ </Testables>
+ <MacroExpansion>
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DEAAD3E01FBA46AA0053BF48"
+ BuildableName = "Storage_Example_tvOS.app"
+ BlueprintName = "Storage_Example_tvOS"
+ ReferencedContainer = "container:Firebase.xcodeproj">
+ </BuildableReference>
+ </MacroExpansion>
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </TestAction>
+ <LaunchAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ language = ""
+ launchStyle = "0"
+ useCustomWorkingDirectory = "NO"
+ ignoresPersistentStateOnLaunch = "NO"
+ debugDocumentVersioning = "YES"
+ debugServiceExtension = "internal"
+ allowLocationSimulation = "YES">
+ <BuildableProductRunnable
+ runnableDebuggingMode = "0">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DEAAD3E01FBA46AA0053BF48"
+ BuildableName = "Storage_Example_tvOS.app"
+ BlueprintName = "Storage_Example_tvOS"
+ ReferencedContainer = "container:Firebase.xcodeproj">
+ </BuildableReference>
+ </BuildableProductRunnable>
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </LaunchAction>
+ <ProfileAction
+ buildConfiguration = "Release"
+ shouldUseLaunchSchemeArgsEnv = "YES"
+ savedToolIdentifier = ""
+ useCustomWorkingDirectory = "NO"
+ debugDocumentVersioning = "YES">
+ <BuildableProductRunnable
+ runnableDebuggingMode = "0">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DEAAD3E01FBA46AA0053BF48"
+ BuildableName = "Storage_Example_tvOS.app"
+ BlueprintName = "Storage_Example_tvOS"
+ ReferencedContainer = "container:Firebase.xcodeproj">
+ </BuildableReference>
+ </BuildableProductRunnable>
+ </ProfileAction>
+ <AnalyzeAction
+ buildConfiguration = "Debug">
+ </AnalyzeAction>
+ <ArchiveAction
+ buildConfiguration = "Release"
+ revealArchiveInOrganizer = "YES">
+ </ArchiveAction>
+</Scheme>
diff --git a/Example/Firebase.xcodeproj/xcshareddata/xcschemes/Storage_Tests_tvOS.xcscheme b/Example/Firebase.xcodeproj/xcshareddata/xcschemes/Storage_Tests_tvOS.xcscheme
new file mode 100644
index 0000000..abc482e
--- /dev/null
+++ b/Example/Firebase.xcodeproj/xcshareddata/xcschemes/Storage_Tests_tvOS.xcscheme
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+ LastUpgradeVersion = "0910"
+ version = "1.3">
+ <BuildAction
+ parallelizeBuildables = "YES"
+ buildImplicitDependencies = "YES">
+ </BuildAction>
+ <TestAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ language = ""
+ shouldUseLaunchSchemeArgsEnv = "YES">
+ <Testables>
+ <TestableReference
+ skipped = "NO">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DEAAD3F41FBA46AB0053BF48"
+ BuildableName = "Storage_Tests_tvOS.xctest"
+ BlueprintName = "Storage_Tests_tvOS"
+ ReferencedContainer = "container:Firebase.xcodeproj">
+ </BuildableReference>
+ </TestableReference>
+ </Testables>
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </TestAction>
+ <LaunchAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ language = ""
+ launchStyle = "0"
+ useCustomWorkingDirectory = "NO"
+ ignoresPersistentStateOnLaunch = "NO"
+ debugDocumentVersioning = "YES"
+ debugServiceExtension = "internal"
+ allowLocationSimulation = "YES">
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </LaunchAction>
+ <ProfileAction
+ buildConfiguration = "Release"
+ shouldUseLaunchSchemeArgsEnv = "YES"
+ savedToolIdentifier = ""
+ useCustomWorkingDirectory = "NO"
+ debugDocumentVersioning = "YES">
+ </ProfileAction>
+ <AnalyzeAction
+ buildConfiguration = "Debug">
+ </AnalyzeAction>
+ <ArchiveAction
+ buildConfiguration = "Release"
+ revealArchiveInOrganizer = "YES">
+ </ArchiveAction>
+</Scheme>
diff --git a/Example/Messaging/App/iOS/Base.lproj/LaunchScreen.storyboard b/Example/Messaging/App/iOS/Base.lproj/LaunchScreen.storyboard
index fdf3f97..66a7681 100644
--- a/Example/Messaging/App/iOS/Base.lproj/LaunchScreen.storyboard
+++ b/Example/Messaging/App/iOS/Base.lproj/LaunchScreen.storyboard
@@ -1,8 +1,8 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="11134" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="16C67" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" initialViewController="01J-lp-oVM">
<dependencies>
- <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11106"/>
- <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+ <deployment identifier="iOS"/>
+ <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
</dependencies>
<scenes>
<!--View Controller-->
@@ -14,9 +14,9 @@
<viewControllerLayoutGuide type="bottom" id="xb3-aO-Qok"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
- <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
+ <rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
- <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+ <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
diff --git a/Example/Messaging/App/iOS/Base.lproj/Main.storyboard b/Example/Messaging/App/iOS/Base.lproj/Main.storyboard
index 6df1a82..d164a23 100644
--- a/Example/Messaging/App/iOS/Base.lproj/Main.storyboard
+++ b/Example/Messaging/App/iOS/Base.lproj/Main.storyboard
@@ -1,48 +1,27 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12120" systemVersion="16E195" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="taE-sK-BOl">
- <device id="retina4_7" orientation="portrait">
- <adaptation id="fullscreen"/>
- </device>
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="7706" systemVersion="14D136" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="whP-gf-Uak">
<dependencies>
<deployment identifier="iOS"/>
- <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12088"/>
- <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+ <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="7703"/>
</dependencies>
<scenes>
- <!--Firebase Cloud Messaging-->
- <scene sceneID="tne-QT-ifu">
+ <!--View Controller-->
+ <scene sceneID="wQg-tq-qST">
<objects>
- <viewController id="BYZ-38-t0r" customClass="MessagingViewController" customModule="Messaging_Example" customModuleProvider="target" sceneMemberID="viewController">
+ <viewController id="whP-gf-Uak" customClass="FIRViewController" sceneMemberID="viewController">
<layoutGuides>
- <viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
- <viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
+ <viewControllerLayoutGuide type="top" id="uEw-UM-LJ8"/>
+ <viewControllerLayoutGuide type="bottom" id="Mvr-aV-6Um"/>
</layoutGuides>
- <view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
- <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
+ <view key="view" contentMode="scaleToFill" id="TpU-gO-2f1">
+ <rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
- <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+ <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
</view>
- <navigationItem key="navigationItem" title="Firebase Cloud Messaging" id="z1u-kE-qKb"/>
</viewController>
- <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
+ <placeholder placeholderIdentifier="IBFirstResponder" id="tc2-Qw-aMS" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
- <point key="canvasLocation" x="698" y="164"/>
- </scene>
- <!--Navigation Controller-->
- <scene sceneID="rmF-xz-rwn">
- <objects>
- <placeholder placeholderIdentifier="IBFirstResponder" id="Ju1-Bj-8eG" userLabel="First Responder" sceneMemberID="firstResponder"/>
- <navigationController id="taE-sK-BOl" sceneMemberID="viewController">
- <navigationBar key="navigationBar" contentMode="scaleToFill" id="iTL-Kg-11w">
- <rect key="frame" x="0.0" y="0.0" width="375" height="44"/>
- <autoresizingMask key="autoresizingMask"/>
- </navigationBar>
- <connections>
- <segue destination="BYZ-38-t0r" kind="relationship" relationship="rootViewController" id="04R-HZ-bi6"/>
- </connections>
- </navigationController>
- </objects>
- <point key="canvasLocation" x="-92" y="165"/>
+ <point key="canvasLocation" x="305" y="433"/>
</scene>
</scenes>
</document>
diff --git a/Example/Messaging/App/iOS/FIRAppDelegate.h b/Example/Messaging/App/iOS/FIRAppDelegate.h
new file mode 100644
index 0000000..1eb5040
--- /dev/null
+++ b/Example/Messaging/App/iOS/FIRAppDelegate.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2017 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 UIKit;
+
+@interface FIRAppDelegate : UIResponder <UIApplicationDelegate>
+
+@property(strong, nonatomic) UIWindow *window;
+
+@end
diff --git a/Example/Messaging/App/iOS/FIRAppDelegate.m b/Example/Messaging/App/iOS/FIRAppDelegate.m
new file mode 100644
index 0000000..535e529
--- /dev/null
+++ b/Example/Messaging/App/iOS/FIRAppDelegate.m
@@ -0,0 +1,58 @@
+// Copyright 2017 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 "FIRAppDelegate.h"
+
+#import <FirebaseCore/FIRApp.h>
+
+@implementation FIRAppDelegate
+
+- (BOOL)application:(UIApplication *)application
+ didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
+ [FIRApp configure];
+ return YES;
+}
+
+- (void)applicationWillResignActive:(UIApplication *)application {
+ // Sent when the application is about to move from active to inactive state. This can occur for
+ // certain types of temporary interruptions (such as an incoming phone call or SMS message) or
+ // when the user quits the application and it begins the transition to the background state.
+ // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame
+ // rates. Games should use this method to pause the game.
+}
+
+- (void)applicationDidEnterBackground:(UIApplication *)application {
+ // Use this method to release shared resources, save user data, invalidate timers, and store
+ // enough application state information to restore your application to its current state in case
+ // it is terminated later.
+ // If your application supports background execution, this method is called instead of
+ // applicationWillTerminate: when the user quits.
+}
+
+- (void)applicationWillEnterForeground:(UIApplication *)application {
+ // Called as part of the transition from the background to the inactive state; here you can undo
+ // many of the changes made on entering the background.
+}
+
+- (void)applicationDidBecomeActive:(UIApplication *)application {
+ // Restart any tasks that were paused (or not yet started) while the application was inactive. If
+ // the application was previously in the background, optionally refresh the user interface.
+}
+
+- (void)applicationWillTerminate:(UIApplication *)application {
+ // Called when the application is about to terminate. Save data if appropriate. See also
+ // applicationDidEnterBackground:.
+}
+
+@end
diff --git a/Example/Messaging/App/iOS/FIRViewController.h b/Example/Messaging/App/iOS/FIRViewController.h
new file mode 100644
index 0000000..64b4b74
--- /dev/null
+++ b/Example/Messaging/App/iOS/FIRViewController.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2017 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 UIKit;
+
+@interface FIRViewController : UIViewController
+
+@end
diff --git a/Example/Messaging/App/iOS/FIRViewController.m b/Example/Messaging/App/iOS/FIRViewController.m
new file mode 100644
index 0000000..027aabf
--- /dev/null
+++ b/Example/Messaging/App/iOS/FIRViewController.m
@@ -0,0 +1,33 @@
+// Copyright 2017 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 "FIRViewController.h"
+
+@interface FIRViewController ()
+
+@end
+
+@implementation FIRViewController
+
+- (void)viewDidLoad {
+ [super viewDidLoad];
+ // Do any additional setup after loading the view, typically from a nib.
+}
+
+- (void)didReceiveMemoryWarning {
+ [super didReceiveMemoryWarning];
+ // Dispose of any resources that can be recreated.
+}
+
+@end
diff --git a/Example/Messaging/App/iOS/main.m b/Example/Messaging/App/iOS/main.m
new file mode 100644
index 0000000..39c05a5
--- /dev/null
+++ b/Example/Messaging/App/iOS/main.m
@@ -0,0 +1,22 @@
+// Copyright 2017 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 UIKit;
+#import "FIRAppDelegate.h"
+
+int main(int argc, char* argv[]) {
+ @autoreleasepool {
+ return UIApplicationMain(argc, argv, nil, NSStringFromClass([FIRAppDelegate class]));
+ }
+}
diff --git a/Example/Messaging/App/GoogleService-Info.plist b/Example/Messaging/Sample/GoogleService-Info.plist
index 89afffe..3f7547f 100644
--- a/Example/Messaging/App/GoogleService-Info.plist
+++ b/Example/Messaging/Sample/GoogleService-Info.plist
@@ -10,8 +10,6 @@
<string>correct_client_id</string>
<key>REVERSED_CLIENT_ID</key>
<string>correct_reversed_client_id</string>
- <key>ANDROID_CLIENT_ID</key>
- <string>correct_android_client_id</string>
<key>GOOGLE_APP_ID</key>
<string>1:123:ios:123abc</string>
<key>GCM_SENDER_ID</key>
diff --git a/Example/Messaging/App/iOS/AppDelegate.swift b/Example/Messaging/Sample/iOS/AppDelegate.swift
index 89a5120..89a5120 100644
--- a/Example/Messaging/App/iOS/AppDelegate.swift
+++ b/Example/Messaging/Sample/iOS/AppDelegate.swift
diff --git a/Example/Messaging/Sample/iOS/Base.lproj/LaunchScreen.storyboard b/Example/Messaging/Sample/iOS/Base.lproj/LaunchScreen.storyboard
new file mode 100644
index 0000000..fdf3f97
--- /dev/null
+++ b/Example/Messaging/Sample/iOS/Base.lproj/LaunchScreen.storyboard
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="11134" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
+ <dependencies>
+ <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11106"/>
+ <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+ </dependencies>
+ <scenes>
+ <!--View Controller-->
+ <scene sceneID="EHf-IW-A2E">
+ <objects>
+ <viewController id="01J-lp-oVM" sceneMemberID="viewController">
+ <layoutGuides>
+ <viewControllerLayoutGuide type="top" id="Llm-lL-Icb"/>
+ <viewControllerLayoutGuide type="bottom" id="xb3-aO-Qok"/>
+ </layoutGuides>
+ <view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
+ <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+ <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+ </view>
+ </viewController>
+ <placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
+ </objects>
+ <point key="canvasLocation" x="53" y="375"/>
+ </scene>
+ </scenes>
+</document>
diff --git a/Example/Messaging/Sample/iOS/Base.lproj/Main.storyboard b/Example/Messaging/Sample/iOS/Base.lproj/Main.storyboard
new file mode 100644
index 0000000..6df1a82
--- /dev/null
+++ b/Example/Messaging/Sample/iOS/Base.lproj/Main.storyboard
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12120" systemVersion="16E195" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="taE-sK-BOl">
+ <device id="retina4_7" orientation="portrait">
+ <adaptation id="fullscreen"/>
+ </device>
+ <dependencies>
+ <deployment identifier="iOS"/>
+ <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12088"/>
+ <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+ </dependencies>
+ <scenes>
+ <!--Firebase Cloud Messaging-->
+ <scene sceneID="tne-QT-ifu">
+ <objects>
+ <viewController id="BYZ-38-t0r" customClass="MessagingViewController" customModule="Messaging_Example" customModuleProvider="target" sceneMemberID="viewController">
+ <layoutGuides>
+ <viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
+ <viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
+ </layoutGuides>
+ <view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
+ <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+ <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+ </view>
+ <navigationItem key="navigationItem" title="Firebase Cloud Messaging" id="z1u-kE-qKb"/>
+ </viewController>
+ <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
+ </objects>
+ <point key="canvasLocation" x="698" y="164"/>
+ </scene>
+ <!--Navigation Controller-->
+ <scene sceneID="rmF-xz-rwn">
+ <objects>
+ <placeholder placeholderIdentifier="IBFirstResponder" id="Ju1-Bj-8eG" userLabel="First Responder" sceneMemberID="firstResponder"/>
+ <navigationController id="taE-sK-BOl" sceneMemberID="viewController">
+ <navigationBar key="navigationBar" contentMode="scaleToFill" id="iTL-Kg-11w">
+ <rect key="frame" x="0.0" y="0.0" width="375" height="44"/>
+ <autoresizingMask key="autoresizingMask"/>
+ </navigationBar>
+ <connections>
+ <segue destination="BYZ-38-t0r" kind="relationship" relationship="rootViewController" id="04R-HZ-bi6"/>
+ </connections>
+ </navigationController>
+ </objects>
+ <point key="canvasLocation" x="-92" y="165"/>
+ </scene>
+ </scenes>
+</document>
diff --git a/Example/Messaging/App/iOS/Data+MessagingExtensions.swift b/Example/Messaging/Sample/iOS/Data+MessagingExtensions.swift
index 99ded25..99ded25 100644
--- a/Example/Messaging/App/iOS/Data+MessagingExtensions.swift
+++ b/Example/Messaging/Sample/iOS/Data+MessagingExtensions.swift
diff --git a/Example/Messaging/App/iOS/Environment.swift b/Example/Messaging/Sample/iOS/Environment.swift
index 5219c64..5219c64 100644
--- a/Example/Messaging/App/iOS/Environment.swift
+++ b/Example/Messaging/Sample/iOS/Environment.swift
diff --git a/Example/Messaging/App/iOS/Messaging-Info.plist b/Example/Messaging/Sample/iOS/Messaging-Info.plist
index e42f39d..b43105e 100644
--- a/Example/Messaging/App/iOS/Messaging-Info.plist
+++ b/Example/Messaging/Sample/iOS/Messaging-Info.plist
@@ -2,6 +2,8 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
+ <key>FirebaseMessagingAutoInitEnabled</key>
+ <false/>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>
diff --git a/Example/Messaging/App/iOS/MessagingViewController.swift b/Example/Messaging/Sample/iOS/MessagingViewController.swift
index 9bd07c1..9bd07c1 100644
--- a/Example/Messaging/App/iOS/MessagingViewController.swift
+++ b/Example/Messaging/Sample/iOS/MessagingViewController.swift
diff --git a/Example/Messaging/App/iOS/Messaging_Example.entitlements b/Example/Messaging/Sample/iOS/Messaging_Example.entitlements
index 903def2..903def2 100644
--- a/Example/Messaging/App/iOS/Messaging_Example.entitlements
+++ b/Example/Messaging/Sample/iOS/Messaging_Example.entitlements
diff --git a/Example/Messaging/App/iOS/NotificationsController.swift b/Example/Messaging/Sample/iOS/NotificationsController.swift
index 7545b44..7545b44 100644
--- a/Example/Messaging/App/iOS/NotificationsController.swift
+++ b/Example/Messaging/Sample/iOS/NotificationsController.swift
diff --git a/Example/Messaging/Tests/FIRMessagingClientTest.m b/Example/Messaging/Tests/FIRMessagingClientTest.m
index 5505b70..9c60d9c 100644
--- a/Example/Messaging/Tests/FIRMessagingClientTest.m
+++ b/Example/Messaging/Tests/FIRMessagingClientTest.m
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-@import XCTest;
+#import <XCTest/XCTest.h>
#import <OCMock/OCMock.h>
diff --git a/Example/Messaging/Tests/FIRMessagingCodedInputStreamTest.m b/Example/Messaging/Tests/FIRMessagingCodedInputStreamTest.m
index 7cc2d97..bafa714 100644
--- a/Example/Messaging/Tests/FIRMessagingCodedInputStreamTest.m
+++ b/Example/Messaging/Tests/FIRMessagingCodedInputStreamTest.m
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-@import XCTest;
+#import <XCTest/XCTest.h>
#import "FIRMessagingCodedInputStream.h"
diff --git a/Example/Messaging/Tests/FIRMessagingConnectionTest.m b/Example/Messaging/Tests/FIRMessagingConnectionTest.m
index 47b29d2..b770cf5 100644
--- a/Example/Messaging/Tests/FIRMessagingConnectionTest.m
+++ b/Example/Messaging/Tests/FIRMessagingConnectionTest.m
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-@import XCTest;
+#import <XCTest/XCTest.h>
#import <OCMock/OCMock.h>
diff --git a/Example/Messaging/Tests/FIRMessagingDataMessageManagerTest.m b/Example/Messaging/Tests/FIRMessagingDataMessageManagerTest.m
index 5771337..8aaad51 100644
--- a/Example/Messaging/Tests/FIRMessagingDataMessageManagerTest.m
+++ b/Example/Messaging/Tests/FIRMessagingDataMessageManagerTest.m
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-@import XCTest;
+#import <XCTest/XCTest.h>
#import <OCMock/OCMock.h>
diff --git a/Example/Messaging/Tests/FIRMessagingLinkHandlingTest.m b/Example/Messaging/Tests/FIRMessagingLinkHandlingTest.m
index 8f93240..4ef2525 100644
--- a/Example/Messaging/Tests/FIRMessagingLinkHandlingTest.m
+++ b/Example/Messaging/Tests/FIRMessagingLinkHandlingTest.m
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-@import XCTest;
+#import <XCTest/XCTest.h>
#import <OCMock/OCMock.h>
diff --git a/Example/Messaging/Tests/FIRMessagingPendingTopicsListTest.m b/Example/Messaging/Tests/FIRMessagingPendingTopicsListTest.m
index 2033cb4..9c30388 100644
--- a/Example/Messaging/Tests/FIRMessagingPendingTopicsListTest.m
+++ b/Example/Messaging/Tests/FIRMessagingPendingTopicsListTest.m
@@ -164,7 +164,7 @@ typedef void (^MockDelegateSubscriptionHandler)(NSString *topic,
XCTAssertEqual(pendingTopics.numberOfBatches, 1);
[batchSizeReductionExpectation fulfill];
}
- completion(FIRMessagingTopicOperationResultSucceeded, nil);
+ completion(nil);
});
};
@@ -197,7 +197,7 @@ typedef void (^MockDelegateSubscriptionHandler)(NSString *topic,
FIRMessagingTopicOperationCompletion completion) {
// Typically, our callbacks happen asynchronously, but to ensure resilience,
// call back the operation on the same thread it was called in.
- completion(FIRMessagingTopicOperationResultSucceeded, nil);
+ completion(nil);
};
self.alwaysReadyDelegate.updateHandler = ^{
@@ -238,10 +238,9 @@ typedef void (^MockDelegateSubscriptionHandler)(NSString *topic,
// Add a 0.5 second delay to the completion, to give time to add a straggler before the batch
// is completed
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)),
- dispatch_get_main_queue(),
- ^{
- completion(FIRMessagingTopicOperationResultSucceeded, nil);
- });
+ dispatch_get_main_queue(), ^{
+ completion(nil);
+ });
};
// This is a normal topic, which should start fairly soon, but take a while to complete
diff --git a/Example/Messaging/Tests/FIRMessagingPubSubTest.m b/Example/Messaging/Tests/FIRMessagingPubSubTest.m
index 2981b54..3af1402 100644
--- a/Example/Messaging/Tests/FIRMessagingPubSubTest.m
+++ b/Example/Messaging/Tests/FIRMessagingPubSubTest.m
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-@import XCTest;
+#import <XCTest/XCTest.h>
#import "FIRMessagingPubSub.h"
diff --git a/Example/Messaging/Tests/FIRMessagingRegistrarTest.m b/Example/Messaging/Tests/FIRMessagingRegistrarTest.m
index b32851c..f3e66fd 100644
--- a/Example/Messaging/Tests/FIRMessagingRegistrarTest.m
+++ b/Example/Messaging/Tests/FIRMessagingRegistrarTest.m
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-@import UIKit;
-@import XCTest;
+#import <UIKit/UIKit.h>
+#import <XCTest/XCTest.h>
#import <OCMock/OCMock.h>
@@ -70,9 +70,8 @@ static NSString *const kSubscriptionID = @"sample-subscription-id-xyz";
withToken:kFIRMessagingAppIDToken
options:nil
shouldDelete:NO
- handler:
- ^(FIRMessagingTopicOperationResult result, NSError *error) {
- }];
+ handler:^(NSError *error){
+ }];
OCMVerify([self.mockPubsubRegistrar updateSubscriptionToTopic:[OCMArg isEqual:kTopicToSubscribeTo]
withToken:[OCMArg isEqual:kFIRMessagingAppIDToken]
@@ -85,27 +84,23 @@ static NSString *const kSubscriptionID = @"sample-subscription-id-xyz";
[self stubCheckinService];
__block FIRMessagingTopicOperationCompletion pubsubCompletion;
- [[[self.mockPubsubRegistrar stub]
- andDo:^(NSInvocation *invocation) {
- pubsubCompletion(FIRMessagingTopicOperationResultSucceeded, nil);
- }]
- updateSubscriptionToTopic:kTopicToSubscribeTo
- withToken:kFIRMessagingAppIDToken
- options:nil
- shouldDelete:NO
- handler:[OCMArg checkWithBlock:^BOOL(id obj) {
- return (pubsubCompletion = obj) != nil;
- }]];
+ [[[self.mockPubsubRegistrar stub] andDo:^(NSInvocation *invocation) {
+ pubsubCompletion(nil);
+ }] updateSubscriptionToTopic:kTopicToSubscribeTo
+ withToken:kFIRMessagingAppIDToken
+ options:nil
+ shouldDelete:NO
+ handler:[OCMArg checkWithBlock:^BOOL(id obj) {
+ return (pubsubCompletion = obj) != nil;
+ }]];
[self.registrar updateSubscriptionToTopic:kTopicToSubscribeTo
withToken:kFIRMessagingAppIDToken
options:nil
shouldDelete:NO
- handler:
- ^(FIRMessagingTopicOperationResult result, NSError *error) {
- XCTAssertNil(error);
- XCTAssertEqual(result, FIRMessagingTopicOperationResultSucceeded);
- }];
+ handler:^(NSError *error) {
+ XCTAssertNil(error);
+ }];
}
- (void)testFailedUpdateSubscriptionWithNoCheckin {
@@ -116,11 +111,9 @@ static NSString *const kSubscriptionID = @"sample-subscription-id-xyz";
withToken:kFIRMessagingAppIDToken
options:nil
shouldDelete:NO
- handler:
- ^(FIRMessagingTopicOperationResult result, NSError *error) {
- XCTAssertNotNil(error);
- XCTAssertEqual(result, FIRMessagingTopicOperationResultError);
- }];
+ handler:^(NSError *error) {
+ XCTAssertNotNil(error);
+ }];
}
#pragma mark - Private Helpers
diff --git a/Example/Messaging/Tests/FIRMessagingRemoteNotificationsProxyTest.m b/Example/Messaging/Tests/FIRMessagingRemoteNotificationsProxyTest.m
index 1e1cbf3..3453f11 100644
--- a/Example/Messaging/Tests/FIRMessagingRemoteNotificationsProxyTest.m
+++ b/Example/Messaging/Tests/FIRMessagingRemoteNotificationsProxyTest.m
@@ -15,9 +15,9 @@
*/
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
-@import UserNotifications;
+#import <UserNotifications/UserNotifications.h>
#endif
-@import XCTest;
+#import <XCTest/XCTest.h>
#import <OCMock/OCMock.h>
diff --git a/Example/Messaging/Tests/FIRMessagingSecureSocketTest.m b/Example/Messaging/Tests/FIRMessagingSecureSocketTest.m
index 9f6186b..b86592c 100644
--- a/Example/Messaging/Tests/FIRMessagingSecureSocketTest.m
+++ b/Example/Messaging/Tests/FIRMessagingSecureSocketTest.m
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-@import XCTest;
+#import <XCTest/XCTest.h>
#import <OCMock/OCMock.h>
diff --git a/Example/Messaging/Tests/FIRMessagingServiceTest.m b/Example/Messaging/Tests/FIRMessagingServiceTest.m
index c2b0853..073adad 100644
--- a/Example/Messaging/Tests/FIRMessagingServiceTest.m
+++ b/Example/Messaging/Tests/FIRMessagingServiceTest.m
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-@import UIKit;
-@import XCTest;
+#import <UIKit/UIKit.h>
+#import <XCTest/XCTest.h>
#import <OCMock/OCMock.h>
@@ -71,8 +71,8 @@
[service.pubsub subscribeWithToken:token
topic:topic
options:nil
- handler:^(FIRMessagingTopicOperationResult result, NSError *error) {
- // not a nil block
+ handler:^(NSError *error){
+ // not a nil block
}];
// should call updateSubscription
@@ -112,7 +112,7 @@
[messaging.pubsub unsubscribeWithToken:token
topic:topic
options:nil
- handler:^(FIRMessagingTopicOperationResult result, NSError *error){
+ handler:^(NSError *error){
}];
@@ -128,15 +128,14 @@
* Test using PubSub without explicitly starting FIRMessagingService.
*/
- (void)testSubscribeWithoutStart {
- [[[FIRMessaging messaging] pubsub] subscribeWithToken:@"abcdef1234"
- topic:@"/topics/hello-world"
- options:nil
- handler:
- ^(FIRMessagingTopicOperationResult result, NSError *error) {
- XCTAssertNil(error);
- XCTAssertEqual(kFIRMessagingErrorCodePubSubFIRMessagingNotSetup,
- error.code);
- }];
+ [[[FIRMessaging messaging] pubsub]
+ subscribeWithToken:@"abcdef1234"
+ topic:@"/topics/hello-world"
+ options:nil
+ handler:^(NSError *error) {
+ XCTAssertNil(error);
+ XCTAssertEqual(kFIRMessagingErrorCodePubSubFIRMessagingNotSetup, error.code);
+ }];
}
// TODO(chliangGoogle) Investigate why invalid token can't throw assertion but the rest can under
@@ -147,13 +146,15 @@
XCTestExpectation *exceptionExpectation =
[self expectationWithDescription:@"Should throw exception for invalid token"];
@try {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wnonnull"
[messaging.pubsub subscribeWithToken:@"abcdef1234"
topic:nil
options:nil
- handler:
- ^(FIRMessagingTopicOperationResult result, NSError *error) {
- XCTFail(@"Should not invoke the handler");
- }];
+ handler:^(NSError *error) {
+ XCTFail(@"Should not invoke the handler");
+ }];
+#pragma clang diagnostic pop
}
@catch (NSException *exception) {
[exceptionExpectation fulfill];
@@ -171,13 +172,15 @@
XCTestExpectation *exceptionExpectation =
[self expectationWithDescription:@"Should throw exception for invalid token"];
@try {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wnonnull"
[messaging.pubsub unsubscribeWithToken:@"abcdef1234"
topic:nil
options:nil
- handler:
- ^(FIRMessagingTopicOperationResult result, NSError *error) {
- XCTFail(@"Should not invoke the handler");
- }];
+ handler:^(NSError *error) {
+ XCTFail(@"Should not invoke the handler");
+ }];
+#pragma clang diagnostic pop
}
@catch (NSException *exception) {
[exceptionExpectation fulfill];
@@ -198,7 +201,8 @@
NSString *topicNameWithPrefix = [FIRMessagingPubSub addPrefixToTopic:topicName];
messaging.pubsub = mockPubSub;
messaging.defaultFcmToken = @"fake-default-token";
- OCMExpect([messaging.pubsub subscribeToTopic:[OCMArg isEqual:topicNameWithPrefix]]);
+ OCMExpect([messaging.pubsub subscribeToTopic:[OCMArg isEqual:topicNameWithPrefix]
+ handler:[OCMArg any]]);
[messaging subscribeToTopic:topicName];
OCMVerifyAll(mockPubSub);
// Need to swap back since it's a singleton and hence will live beyond the scope of this test.
@@ -213,7 +217,7 @@
NSString *topicName = @"/topics/topicWithoutPrefix";
messaging.pubsub = mockPubSub;
messaging.defaultFcmToken = @"fake-default-token";
- OCMExpect([messaging.pubsub subscribeToTopic:[OCMArg isEqual:topicName]]);
+ OCMExpect([messaging.pubsub subscribeToTopic:[OCMArg isEqual:topicName] handler:[OCMArg any]]);
[messaging subscribeToTopic:topicName];
OCMVerifyAll(mockPubSub);
// Need to swap back since it's a singleton and hence will live beyond the scope of this test.
@@ -229,7 +233,8 @@
NSString *topicNameWithPrefix = [FIRMessagingPubSub addPrefixToTopic:topicName];
messaging.pubsub = mockPubSub;
messaging.defaultFcmToken = @"fake-default-token";
- OCMExpect([messaging.pubsub unsubscribeFromTopic:[OCMArg isEqual:topicNameWithPrefix]]);
+ OCMExpect([messaging.pubsub unsubscribeFromTopic:[OCMArg isEqual:topicNameWithPrefix]
+ handler:[OCMArg any]]);
[messaging unsubscribeFromTopic:topicName];
OCMVerifyAll(mockPubSub);
// Need to swap back since it's a singleton and hence will live beyond the scope of this test.
@@ -244,7 +249,8 @@
NSString *topicName = @"/topics/topicWithPrefix";
messaging.pubsub = mockPubSub;
messaging.defaultFcmToken = @"fake-default-token";
- OCMExpect([messaging.pubsub unsubscribeFromTopic:[OCMArg isEqual:topicName]]);
+ OCMExpect([messaging.pubsub unsubscribeFromTopic:[OCMArg isEqual:topicName]
+ handler:[OCMArg any]]);
[messaging unsubscribeFromTopic:topicName];
OCMVerifyAll(mockPubSub);
// Need to swap back since it's a singleton and hence will live beyond the scope of this test.
diff --git a/Example/Messaging/Tests/FIRMessagingTest.m b/Example/Messaging/Tests/FIRMessagingTest.m
index 09cdffc..c2a136a 100644
--- a/Example/Messaging/Tests/FIRMessagingTest.m
+++ b/Example/Messaging/Tests/FIRMessagingTest.m
@@ -14,12 +14,13 @@
* limitations under the License.
*/
-@import XCTest;
+#import <XCTest/XCTest.h>
#import <OCMock/OCMock.h>
+#import <FirebaseInstanceID/FirebaseInstanceID.h>
#import "FIRMessaging.h"
-#import "FIRMessagingInstanceIDProxy.h"
+#import "FIRMessaging_Private.h"
extern NSString *const kFIRMessagingFCMTokenFetchAPNSOption;
@@ -27,9 +28,11 @@ extern NSString *const kFIRMessagingFCMTokenFetchAPNSOption;
@property(nonatomic, readwrite, strong) NSString *defaultFcmToken;
@property(nonatomic, readwrite, strong) NSData *apnsTokenData;
-@property(nonatomic, readwrite, strong) FIRMessagingInstanceIDProxy *instanceIDProxy;
+@property(nonatomic, readwrite, strong) FIRInstanceID *instanceID;
+@property(nonatomic, readwrite, strong) NSUserDefaults *messagingUserDefaults;
-- (instancetype)initPrivately;
+- (instancetype)initWithInstanceID:(FIRInstanceID *)instanceID
+ userDefaults:(NSUserDefaults *)defaults;
// Direct Channel Methods
- (void)updateAutomaticClientConnection;
- (BOOL)shouldBeConnectedAutomatically;
@@ -40,7 +43,7 @@ extern NSString *const kFIRMessagingFCMTokenFetchAPNSOption;
@property(nonatomic, readonly, strong) FIRMessaging *messaging;
@property(nonatomic, readwrite, strong) id mockMessaging;
-@property(nonatomic, readwrite, strong) id mockInstanceIDProxy;
+@property(nonatomic, readwrite, strong) id mockInstanceID;
@end
@@ -48,18 +51,33 @@ extern NSString *const kFIRMessagingFCMTokenFetchAPNSOption;
- (void)setUp {
[super setUp];
- _messaging = [[FIRMessaging alloc] initPrivately];
+ _messaging = [[FIRMessaging alloc] initWithInstanceID:[FIRInstanceID instanceID]
+ userDefaults:[NSUserDefaults standardUserDefaults]];
_mockMessaging = OCMPartialMock(self.messaging);
- _mockInstanceIDProxy = OCMPartialMock(self.messaging.instanceIDProxy);
- self.messaging.instanceIDProxy = _mockInstanceIDProxy;
+ _mockInstanceID = OCMPartialMock(self.messaging.instanceID);
+ self.messaging.instanceID = _mockInstanceID;
+ [[NSUserDefaults standardUserDefaults]
+ removePersistentDomainForName:[NSBundle mainBundle].bundleIdentifier];
}
- (void)tearDown {
_messaging = nil;
+ [_mockMessaging stopMocking];
_mockMessaging = nil;
+ [_mockInstanceID stopMocking];
+ _mockInstanceID = nil;
[super tearDown];
}
+- (void)testAutoInitEnableFlag {
+ // Should read from Info.plist
+ XCTAssertFalse(_messaging.isAutoInitEnabled);
+
+ // Now set the flag should overwrite Info.plist value.
+ _messaging.autoInitEnabled = YES;
+ XCTAssertTrue(_messaging.isAutoInitEnabled);
+}
+
#pragma mark - Direct Channel Establishment Testing
// Should connect with valid token and application in foreground
@@ -124,7 +142,7 @@ extern NSString *const kFIRMessagingFCMTokenFetchAPNSOption;
XCTestExpectation *expectation =
[self expectationWithDescription:@"Included APNS Token data in options dict."];
// Inspect the 'options' dictionary to tell whether our expectation was fulfilled
- [[[self.mockInstanceIDProxy stub] andDo:^(NSInvocation *invocation) {
+ [[[self.mockInstanceID stub] andDo:^(NSInvocation *invocation) {
// Calling getArgument:atIndex: directly leads to an EXC_BAD_ACCESS; use OCMock's wrapper.
NSDictionary *options = [invocation getArgumentAtIndexAsObject:4];
if (options[@"apns_token"] != nil) {
@@ -141,7 +159,7 @@ extern NSString *const kFIRMessagingFCMTokenFetchAPNSOption;
XCTestExpectation *expectation =
[self expectationWithDescription:@"Included APNS Token data not included in options dict."];
// Inspect the 'options' dictionary to tell whether our expectation was fulfilled
- [[[self.mockInstanceIDProxy stub] andDo:^(NSInvocation *invocation) {
+ [[[self.mockInstanceID stub] andDo:^(NSInvocation *invocation) {
// Calling getArgument:atIndex: directly leads to an EXC_BAD_ACCESS; use OCMock's wrapper.
NSDictionary *options = [invocation getArgumentAtIndexAsObject:4];
if (options[@"apns_token"] == nil) {
diff --git a/Example/Messaging/Tests/Info.plist b/Example/Messaging/Tests/Info.plist
index ba72822..4df9372 100644
--- a/Example/Messaging/Tests/Info.plist
+++ b/Example/Messaging/Tests/Info.plist
@@ -20,5 +20,7 @@
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
+ <key>FirebaseMessagingAutoInitEnabled</key>
+ <false/>
</dict>
</plist>
diff --git a/Example/Podfile b/Example/Podfile
index 164ca72..0d46941 100644
--- a/Example/Podfile
+++ b/Example/Podfile
@@ -8,7 +8,7 @@ target 'Core_Example_iOS' do
# The next line is the forcing function for the Firebase pod. The Firebase
# version's subspecs should depend on the component versions in their
# corresponding podspec's.
- pod 'Firebase/Core', '4.7.0'
+ pod 'Firebase/Core', '4.13.0'
target 'Core_Tests_iOS' do
inherit! :search_paths
@@ -52,7 +52,6 @@ target 'Messaging_Example_iOS' do
platform :ios, '8.0'
pod 'FirebaseMessaging' , :path => '../'
- pod 'FirebaseInstanceID'
target 'Messaging_Tests_iOS' do
inherit! :search_paths
@@ -60,6 +59,11 @@ target 'Messaging_Example_iOS' do
end
end
+target 'Messaging_Sample_iOS' do
+ platform :ios, '8.0'
+ pod 'FirebaseMessaging' , :path => '../'
+end
+
target 'Storage_Example_iOS' do
platform :ios, '8.0'
@@ -157,6 +161,60 @@ target 'Storage_Example_macOS' do
end
end
+target 'Core_Example_tvOS' do
+ platform :tvos, '10.0'
+
+ target 'Core_Tests_tvOS' do
+ inherit! :search_paths
+ pod 'OCMock'
+ end
+end
+
+target 'Auth_Example_tvOS' do
+ platform :tvos, '10.0'
+
+ pod 'FirebaseAuth', :path => '../'
+
+ target 'Auth_Tests_tvOS' do
+ inherit! :search_paths
+ pod 'OCMock'
+ end
+end
+
+target 'Database_Example_tvOS' do
+ platform :tvos, '10.0'
+
+ pod 'FirebaseDatabase', :path => '../'
+
+ target 'Database_Tests_tvOS' do
+ inherit! :search_paths
+ pod 'OCMock'
+ end
+
+# TODO
+# target 'Database_IntegrationTests_tvOS' do
+# inherit! :search_paths
+# pod 'OCMock'
+# end
+end
+
+target 'Storage_Example_tvOS' do
+ platform :tvos, '10.0'
+
+ pod 'FirebaseStorage', :path => '../'
+
+ target 'Storage_Tests_tvOS' do
+ inherit! :search_paths
+ pod 'OCMock'
+ end
+
+#TODO Storage_IntegrationTests_tvOS
+# target 'Storage_IntegrationTests_tvOS' do
+# inherit! :search_paths
+# pod 'OCMock'
+# end
+end
+
# This post_install workaround should be removed when FirebaseAnalytics
# removes its module includes to FirebaseCore.
diff --git a/Example/Shared/FIRSampleAppUtilities.m b/Example/Shared/FIRSampleAppUtilities.m
index 7a7ce3b..d161cb9 100644
--- a/Example/Shared/FIRSampleAppUtilities.m
+++ b/Example/Shared/FIRSampleAppUtilities.m
@@ -88,7 +88,6 @@ NSString *const kInvalidPlistAlertMessage =
handler:^(UIAlertAction *_Nonnull action) {
NSURL *githubURL = [NSURL URLWithString:kGithubRepoURLString];
[FIRSampleAppUtilities navigateToURL:githubURL fromViewController:viewController];
-
}];
[alertController addAction:viewReadmeAction];
diff --git a/Example/Storage/App/GoogleService-Info.plist b/Example/Storage/App/GoogleService-Info.plist
index 89afffe..3f7547f 100644
--- a/Example/Storage/App/GoogleService-Info.plist
+++ b/Example/Storage/App/GoogleService-Info.plist
@@ -10,8 +10,6 @@
<string>correct_client_id</string>
<key>REVERSED_CLIENT_ID</key>
<string>correct_reversed_client_id</string>
- <key>ANDROID_CLIENT_ID</key>
- <string>correct_android_client_id</string>
<key>GOOGLE_APP_ID</key>
<string>1:123:ios:123abc</string>
<key>GCM_SENDER_ID</key>
diff --git a/Example/Storage/App/iOS/FIRAppDelegate.m b/Example/Storage/App/iOS/FIRAppDelegate.m
index d8e4497..9568d06 100644
--- a/Example/Storage/App/iOS/FIRAppDelegate.m
+++ b/Example/Storage/App/iOS/FIRAppDelegate.m
@@ -12,8 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-@import FirebaseStorage;
-
#import "FIRAppDelegate.h"
@implementation FIRAppDelegate
diff --git a/Example/Storage/App/tvOS/AppDelegate.h b/Example/Storage/App/tvOS/AppDelegate.h
new file mode 100644
index 0000000..013891c
--- /dev/null
+++ b/Example/Storage/App/tvOS/AppDelegate.h
@@ -0,0 +1,21 @@
+// Copyright 2017 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 <UIKit/UIKit.h>
+
+@interface AppDelegate : UIResponder <UIApplicationDelegate>
+
+@property(strong, nonatomic) UIWindow *window;
+
+@end
diff --git a/Example/Storage/App/tvOS/AppDelegate.m b/Example/Storage/App/tvOS/AppDelegate.m
new file mode 100644
index 0000000..fb6dbcf
--- /dev/null
+++ b/Example/Storage/App/tvOS/AppDelegate.m
@@ -0,0 +1,63 @@
+// Copyright 2017 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 FirebaseCore;
+@import FirebaseStorage;
+
+#import "AppDelegate.h"
+
+@interface AppDelegate ()
+
+@end
+
+@implementation AppDelegate
+
+- (BOOL)application:(UIApplication *)application
+ didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
+ // Override point for customization after application launch.
+ [FIRApp configure];
+ return YES;
+}
+
+- (void)applicationWillResignActive:(UIApplication *)application {
+ // Sent when the application is about to move from active to inactive state. This can occur for
+ // certain types of temporary interruptions (such as an incoming phone call or SMS message) or
+ // when the user quits the application and it begins the transition to the background state. Use
+ // this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates.
+ // Games should use this method to pause the game.
+}
+
+- (void)applicationDidEnterBackground:(UIApplication *)application {
+ // Use this method to release shared resources, save user data, invalidate timers, and store
+ // enough application state information to restore your application to its current state in case
+ // it is terminated later. If your application supports background execution, this method is
+ // called instead of applicationWillTerminate: when the user quits.
+}
+
+- (void)applicationWillEnterForeground:(UIApplication *)application {
+ // Called as part of the transition from the background to the active state; here you can undo
+ // many of the changes made on entering the background.
+}
+
+- (void)applicationDidBecomeActive:(UIApplication *)application {
+ // Restart any tasks that were paused (or not yet started) while the application was inactive. If
+ // the application was previously in the background, optionally refresh the user interface.
+}
+
+- (void)applicationWillTerminate:(UIApplication *)application {
+ // Called when the application is about to terminate. Save data if appropriate. See also
+ // applicationDidEnterBackground:.
+}
+
+@end
diff --git a/Example/Storage/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json b/Example/Storage/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json
new file mode 100644
index 0000000..7f06667
--- /dev/null
+++ b/Example/Storage/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json
@@ -0,0 +1,11 @@
+{
+ "images" : [
+ {
+ "idiom" : "tv"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
diff --git a/Example/Storage/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json b/Example/Storage/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json
new file mode 100644
index 0000000..da4a164
--- /dev/null
+++ b/Example/Storage/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/Storage/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json b/Example/Storage/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json
new file mode 100644
index 0000000..d29f024
--- /dev/null
+++ b/Example/Storage/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json
@@ -0,0 +1,17 @@
+{
+ "layers" : [
+ {
+ "filename" : "Front.imagestacklayer"
+ },
+ {
+ "filename" : "Middle.imagestacklayer"
+ },
+ {
+ "filename" : "Back.imagestacklayer"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/Storage/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json b/Example/Storage/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json
new file mode 100644
index 0000000..7f06667
--- /dev/null
+++ b/Example/Storage/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json
@@ -0,0 +1,11 @@
+{
+ "images" : [
+ {
+ "idiom" : "tv"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
diff --git a/Example/Storage/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json b/Example/Storage/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json
new file mode 100644
index 0000000..da4a164
--- /dev/null
+++ b/Example/Storage/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/Storage/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json b/Example/Storage/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json
new file mode 100644
index 0000000..7f06667
--- /dev/null
+++ b/Example/Storage/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json
@@ -0,0 +1,11 @@
+{
+ "images" : [
+ {
+ "idiom" : "tv"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
diff --git a/Example/Storage/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json b/Example/Storage/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json
new file mode 100644
index 0000000..da4a164
--- /dev/null
+++ b/Example/Storage/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/Storage/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json b/Example/Storage/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json
new file mode 100644
index 0000000..16a370d
--- /dev/null
+++ b/Example/Storage/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images" : [
+ {
+ "idiom" : "tv",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "tv",
+ "scale" : "2x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/Storage/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json b/Example/Storage/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json
new file mode 100644
index 0000000..da4a164
--- /dev/null
+++ b/Example/Storage/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/Storage/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json b/Example/Storage/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json
new file mode 100644
index 0000000..d29f024
--- /dev/null
+++ b/Example/Storage/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json
@@ -0,0 +1,17 @@
+{
+ "layers" : [
+ {
+ "filename" : "Front.imagestacklayer"
+ },
+ {
+ "filename" : "Middle.imagestacklayer"
+ },
+ {
+ "filename" : "Back.imagestacklayer"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/Storage/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json b/Example/Storage/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json
new file mode 100644
index 0000000..16a370d
--- /dev/null
+++ b/Example/Storage/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images" : [
+ {
+ "idiom" : "tv",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "tv",
+ "scale" : "2x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/Storage/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json b/Example/Storage/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json
new file mode 100644
index 0000000..da4a164
--- /dev/null
+++ b/Example/Storage/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/Storage/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json b/Example/Storage/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json
new file mode 100644
index 0000000..16a370d
--- /dev/null
+++ b/Example/Storage/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images" : [
+ {
+ "idiom" : "tv",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "tv",
+ "scale" : "2x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/Storage/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json b/Example/Storage/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json
new file mode 100644
index 0000000..da4a164
--- /dev/null
+++ b/Example/Storage/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/Storage/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json b/Example/Storage/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json
new file mode 100644
index 0000000..b03ded1
--- /dev/null
+++ b/Example/Storage/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json
@@ -0,0 +1,32 @@
+{
+ "assets" : [
+ {
+ "size" : "1280x768",
+ "idiom" : "tv",
+ "filename" : "App Icon - App Store.imagestack",
+ "role" : "primary-app-icon"
+ },
+ {
+ "size" : "400x240",
+ "idiom" : "tv",
+ "filename" : "App Icon.imagestack",
+ "role" : "primary-app-icon"
+ },
+ {
+ "size" : "2320x720",
+ "idiom" : "tv",
+ "filename" : "Top Shelf Image Wide.imageset",
+ "role" : "top-shelf-image-wide"
+ },
+ {
+ "size" : "1920x720",
+ "idiom" : "tv",
+ "filename" : "Top Shelf Image.imageset",
+ "role" : "top-shelf-image"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
diff --git a/Example/Storage/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json b/Example/Storage/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json
new file mode 100644
index 0000000..16a370d
--- /dev/null
+++ b/Example/Storage/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images" : [
+ {
+ "idiom" : "tv",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "tv",
+ "scale" : "2x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/Storage/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json b/Example/Storage/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json
new file mode 100644
index 0000000..16a370d
--- /dev/null
+++ b/Example/Storage/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images" : [
+ {
+ "idiom" : "tv",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "tv",
+ "scale" : "2x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/Storage/App/tvOS/Assets.xcassets/Contents.json b/Example/Storage/App/tvOS/Assets.xcassets/Contents.json
new file mode 100644
index 0000000..da4a164
--- /dev/null
+++ b/Example/Storage/App/tvOS/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/Storage/App/tvOS/Assets.xcassets/LaunchImage.launchimage/Contents.json b/Example/Storage/App/tvOS/Assets.xcassets/LaunchImage.launchimage/Contents.json
new file mode 100644
index 0000000..d746a60
--- /dev/null
+++ b/Example/Storage/App/tvOS/Assets.xcassets/LaunchImage.launchimage/Contents.json
@@ -0,0 +1,22 @@
+{
+ "images" : [
+ {
+ "orientation" : "landscape",
+ "idiom" : "tv",
+ "extent" : "full-screen",
+ "minimum-system-version" : "11.0",
+ "scale" : "2x"
+ },
+ {
+ "orientation" : "landscape",
+ "idiom" : "tv",
+ "extent" : "full-screen",
+ "minimum-system-version" : "9.0",
+ "scale" : "1x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/Storage/App/tvOS/Info.plist b/Example/Storage/App/tvOS/Info.plist
new file mode 100644
index 0000000..02942a3
--- /dev/null
+++ b/Example/Storage/App/tvOS/Info.plist
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>$(DEVELOPMENT_LANGUAGE)</string>
+ <key>CFBundleExecutable</key>
+ <string>$(EXECUTABLE_NAME)</string>
+ <key>CFBundleIdentifier</key>
+ <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>$(PRODUCT_NAME)</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>1.0</string>
+ <key>CFBundleVersion</key>
+ <string>1</string>
+ <key>LSRequiresIPhoneOS</key>
+ <true/>
+ <key>UIMainStoryboardFile</key>
+ <string>Main</string>
+ <key>UIRequiredDeviceCapabilities</key>
+ <array>
+ <string>arm64</string>
+ </array>
+ <key>UIUserInterfaceStyle</key>
+ <string>Automatic</string>
+</dict>
+</plist>
diff --git a/Example/Storage/App/tvOS/Main.storyboard b/Example/Storage/App/tvOS/Main.storyboard
new file mode 100644
index 0000000..72d5e22
--- /dev/null
+++ b/Example/Storage/App/tvOS/Main.storyboard
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder.AppleTV.Storyboard" version="3.0" toolsVersion="13122.16" systemVersion="17A278a" targetRuntime="AppleTV" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
+ <dependencies>
+ <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
+ <capability name="Safe area layout guides" minToolsVersion="9.0"/>
+ <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+ </dependencies>
+ <scenes>
+ <!--View Controller-->
+ <scene sceneID="tne-QT-ifu">
+ <objects>
+ <viewController id="BYZ-38-t0r" customClass="ViewController" customModuleProvider="" sceneMemberID="viewController">
+ <layoutGuides>
+ <viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
+ <viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
+ </layoutGuides>
+ <view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
+ <rect key="frame" x="0.0" y="0.0" width="1920" height="1080"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+ <color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
+ <viewLayoutGuide key="safeArea" id="wu6-TO-1qx"/>
+ </view>
+ </viewController>
+ <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
+ </objects>
+ </scene>
+ </scenes>
+</document>
diff --git a/Example/Storage/App/tvOS/ViewController.h b/Example/Storage/App/tvOS/ViewController.h
new file mode 100644
index 0000000..b6115b8
--- /dev/null
+++ b/Example/Storage/App/tvOS/ViewController.h
@@ -0,0 +1,19 @@
+// Copyright 2017 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 <UIKit/UIKit.h>
+
+@interface ViewController : UIViewController
+
+@end
diff --git a/Example/Storage/App/tvOS/ViewController.m b/Example/Storage/App/tvOS/ViewController.m
new file mode 100644
index 0000000..6d4676b
--- /dev/null
+++ b/Example/Storage/App/tvOS/ViewController.m
@@ -0,0 +1,33 @@
+// Copyright 2017 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 "ViewController.h"
+
+@interface ViewController ()
+
+@end
+
+@implementation ViewController
+
+- (void)viewDidLoad {
+ [super viewDidLoad];
+ // Do any additional setup after loading the view, typically from a nib.
+}
+
+- (void)didReceiveMemoryWarning {
+ [super didReceiveMemoryWarning];
+ // Dispose of any resources that can be recreated.
+}
+
+@end
diff --git a/Example/Storage/App/tvOS/main.m b/Example/Storage/App/tvOS/main.m
new file mode 100644
index 0000000..d9e6654
--- /dev/null
+++ b/Example/Storage/App/tvOS/main.m
@@ -0,0 +1,22 @@
+// Copyright 2017 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 <UIKit/UIKit.h>
+#import "AppDelegate.h"
+
+int main(int argc, char* argv[]) {
+ @autoreleasepool {
+ return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
+ }
+}
diff --git a/Example/Storage/Tests/Integration/FIRStorageIntegrationTests.m b/Example/Storage/Tests/Integration/FIRStorageIntegrationTests.m
index 703c2c6..b20108a 100644
--- a/Example/Storage/Tests/Integration/FIRStorageIntegrationTests.m
+++ b/Example/Storage/Tests/Integration/FIRStorageIntegrationTests.m
@@ -12,10 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-#import <FirebaseStorage/FIRStorageMetadata.h>
#import <XCTest/XCTest.h>
-#import <math.h>
-
#import "FirebaseStorage.h"
#import <FirebaseCore/FIRApp.h>
@@ -33,7 +30,7 @@ NSTimeInterval kFIRStorageIntegrationTestTimeout = 30;
* A sample configuration may look like:
*
* service firebase.storage {
- * match /b/{YOUR_PROJECT_ID}.appspot.com/o {
+ * match /b/{bucket}/o {
* ...
* match /ios {
* match /public/{allPaths=**} {
@@ -381,7 +378,7 @@ NSTimeInterval kFIRStorageIntegrationTestTimeout = 30;
/// Only allow 1kB size, which is smaller than our file
[ref dataWithMaxSize:1 * 1024
completion:^(NSData *data, NSError *error) {
- XCTAssertEqual(data, nil);
+ XCTAssertNil(data);
XCTAssertEqual(error.code, FIRStorageErrorCodeDownloadSizeExceeded);
[expectation fulfill];
}];
@@ -389,6 +386,33 @@ NSTimeInterval kFIRStorageIntegrationTestTimeout = 30;
[self waitForExpectations];
}
+- (void)testUnauthenticatedSimpleGetDownloadURL {
+ XCTestExpectation *expectation =
+ [self expectationWithDescription:@"testUnauthenticatedSimpleGetDownloadURL"];
+
+ FIRStorageReference *ref = [self.storage referenceWithPath:@"ios/public/1mb"];
+
+ // Download URL format is
+ // "https://firebasestorage.googleapis.com/v0/b/{bucket}/o/{path}?alt=media&token={token}"
+ NSString *downloadURLPattern =
+ @"^https:\\/\\/firebasestorage.googleapis.com\\/v0\\/b\\/[^\\/]*\\/o\\/"
+ @"ios%2Fpublic%2F1mb\\?alt=media&token=[a-z0-9-]*$";
+
+ [ref downloadURLWithCompletion:^(NSURL *downloadURL, NSError *error) {
+ XCTAssertNil(error);
+ NSRegularExpression *testRegex =
+ [NSRegularExpression regularExpressionWithPattern:downloadURLPattern options:0 error:nil];
+ NSString *urlString = [downloadURL absoluteString];
+ XCTAssertEqual([testRegex numberOfMatchesInString:urlString
+ options:0
+ range:NSMakeRange(0, [urlString length])],
+ 1);
+ [expectation fulfill];
+ }];
+
+ [self waitForExpectations];
+}
+
- (void)testUnauthenticatedSimpleGetFile {
XCTestExpectation *expectation =
[self expectationWithDescription:@"testUnauthenticatedSimpleGetData"];
diff --git a/Example/Storage/Tests/Unit/FIRStorageMetadataTests.m b/Example/Storage/Tests/Unit/FIRStorageMetadataTests.m
index 84b5271..6a83741 100644
--- a/Example/Storage/Tests/Unit/FIRStorageMetadataTests.m
+++ b/Example/Storage/Tests/Unit/FIRStorageMetadataTests.m
@@ -15,6 +15,8 @@
#import <FirebaseStorage/FIRStorageMetadata.h>
#import <XCTest/XCTest.h>
+#import "FIRStorageGetDownloadURLTask.h"
+#import "FIRStorageGetDownloadURLTask_Private.h"
#import "FIRStorageMetadata.h"
#import "FIRStorageMetadata_Private.h"
#import "FIRStorageUtils.h"
@@ -39,7 +41,6 @@
kFIRStorageMetadataContentLanguage : @"en-us",
kFIRStorageMetadataContentType : @"application/octet-stream",
kFIRStorageMetadataCustomMetadata : @{@"foo" : @{@"bar" : @"baz"}},
- kFIRStorageMetadataDownloadTokens : @"1234567890",
kFIRStorageMetadataGeneration : @"12345",
kFIRStorageMetadataMetageneration : @"67890",
kFIRStorageMetadataName : @"path/to/object",
@@ -58,12 +59,6 @@
XCTAssertEqualObjects(metadata.contentType, metaDict[kFIRStorageMetadataContentType]);
XCTAssertEqualObjects(metadata.customMetadata, metaDict[kFIRStorageMetadataCustomMetadata]);
XCTAssertEqualObjects(metadata.md5Hash, metaDict[kFIRStorageMetadataMd5Hash]);
- NSString *URLFormat = @"https://firebasestorage.googleapis.com/v0/b/%@/o/%@?alt=media&token=%@";
- NSString *URLString = [NSString
- stringWithFormat:URLFormat, metaDict[kFIRStorageMetadataBucket],
- [FIRStorageUtils GCSEscapedString:metaDict[kFIRStorageMetadataName]],
- metaDict[kFIRStorageMetadataDownloadTokens]];
- XCTAssertEqualObjects([metadata.downloadURL description], URLString);
NSString *generation = [NSString stringWithFormat:@"%lld", metadata.generation];
XCTAssertEqualObjects(generation, metaDict[kFIRStorageMetadataGeneration]);
NSString *metageneration = [NSString stringWithFormat:@"%lld", metadata.metageneration];
@@ -86,7 +81,6 @@
kFIRStorageMetadataContentLanguage : @"en-us",
kFIRStorageMetadataContentType : @"application/octet-stream",
kFIRStorageMetadataCustomMetadata : @{@"foo" : @{@"bar" : @"baz"}},
- kFIRStorageMetadataDownloadTokens : @"1234567890",
kFIRStorageMetadataGeneration : @"12345",
kFIRStorageMetadataMetageneration : @"67890",
kFIRStorageMetadataName : @"path/to/object",
@@ -97,7 +91,7 @@
};
FIRStorageMetadata *metadata = [[FIRStorageMetadata alloc] initWithDictionary:metaDict];
NSDictionary *dictRepresentation = [metadata dictionaryRepresentation];
- XCTAssertNotEqual(dictRepresentation, nil);
+ XCTAssertNotNil(dictRepresentation);
XCTAssertEqualObjects(dictRepresentation[kFIRStorageMetadataBucket],
metaDict[kFIRStorageMetadataBucket]);
XCTAssertEqualObjects(dictRepresentation[kFIRStorageMetadataCacheControl],
@@ -130,60 +124,28 @@
metaDict[kFIRStorageMetadataMd5Hash]);
}
-- (void)testInitialzeNoDownloadTokensGetToken {
- NSDictionary *metaDict = @{
- kFIRStorageMetadataBucket : @"bucket",
- kFIRStorageMetadataName : @"path/to/object",
- };
- FIRStorageMetadata *metadata = [[FIRStorageMetadata alloc] initWithDictionary:metaDict];
- XCTAssertNotNil(metadata);
- XCTAssertEqual(metadata.downloadURL, nil);
- XCTAssertEqual(metadata.downloadURLs, nil);
-}
-
-- (void)testInitialzeMultipleDownloadTokensGetToken {
+- (void)testInitializeEmptyDownloadURL {
NSDictionary *metaDict = @{
kFIRStorageMetadataBucket : @"bucket",
- kFIRStorageMetadataDownloadTokens : @"12345,67890",
kFIRStorageMetadataName : @"path/to/object",
};
- FIRStorageMetadata *metadata = [[FIRStorageMetadata alloc] initWithDictionary:metaDict];
- XCTAssertNotNil(metadata);
- NSString *URLformat = @"https://firebasestorage.googleapis.com/v0/b/%@/o/%@?alt=media&token=%@";
- NSString *URLString0 = [NSString
- stringWithFormat:URLformat, metaDict[kFIRStorageMetadataBucket],
- [FIRStorageUtils GCSEscapedString:metaDict[kFIRStorageMetadataName]],
- @"12345"];
- NSString *URLString1 = [NSString
- stringWithFormat:URLformat, metaDict[kFIRStorageMetadataBucket],
- [FIRStorageUtils GCSEscapedString:metaDict[kFIRStorageMetadataName]],
- @"67890"];
- XCTAssertEqualObjects([metadata.downloadURL absoluteString], URLString0);
- XCTAssertEqualObjects([metadata.downloadURLs[0] absoluteString], URLString0);
- XCTAssertEqualObjects([metadata.downloadURLs[1] absoluteString], URLString1);
+ NSURL *actualURL = [FIRStorageGetDownloadURLTask downloadURLFromMetadataDictionary:metaDict];
+ XCTAssertNil(actualURL);
}
-- (void)testMultipleDownloadURLsGetToken {
+- (void)testInitializeDownloadURLFromToken {
NSDictionary *metaDict = @{
kFIRStorageMetadataBucket : @"bucket",
+ kFIRStorageMetadataDownloadTokens : @"12345,ignored",
kFIRStorageMetadataName : @"path/to/object",
};
- FIRStorageMetadata *metadata = [[FIRStorageMetadata alloc] initWithDictionary:metaDict];
NSString *URLformat = @"https://firebasestorage.googleapis.com/v0/b/%@/o/%@?alt=media&token=%@";
- NSString *URLString0 = [NSString
+ NSString *expectedURL = [NSString
stringWithFormat:URLformat, metaDict[kFIRStorageMetadataBucket],
[FIRStorageUtils GCSEscapedString:metaDict[kFIRStorageMetadataName]],
@"12345"];
- NSString *URLString1 = [NSString
- stringWithFormat:URLformat, metaDict[kFIRStorageMetadataBucket],
- [FIRStorageUtils GCSEscapedString:metaDict[kFIRStorageMetadataName]],
- @"67890"];
- NSURL *URL0 = [NSURL URLWithString:URLString0];
- NSURL *URL1 = [NSURL URLWithString:URLString1];
- NSArray *downloadURLs = @[ URL0, URL1 ];
- [metadata setValue:downloadURLs forKey:@"downloadURLs"];
- NSDictionary *newMetaDict = metadata.dictionaryRepresentation;
- XCTAssertEqualObjects(newMetaDict[kFIRStorageMetadataDownloadTokens], @"12345,67890");
+ NSURL *actualURL = [FIRStorageGetDownloadURLTask downloadURLFromMetadataDictionary:metaDict];
+ XCTAssertEqualObjects([actualURL absoluteString], expectedURL);
}
- (void)testInitialzeMetadataWithFile {
diff --git a/Example/Storage/Tests/Unit/FIRStorageTests.m b/Example/Storage/Tests/Unit/FIRStorageTests.m
index 503ac94..4086f62 100644
--- a/Example/Storage/Tests/Unit/FIRStorageTests.m
+++ b/Example/Storage/Tests/Unit/FIRStorageTests.m
@@ -79,7 +79,7 @@
}
- (void)testInitWithNilURL {
- XCTAssertThrows([FIRStorage storageForApp:self.app URL:nil]);
+ XCTAssertThrows([FIRStorage storageForApp:self.app URL:(id _Nonnull)nil]);
}
- (void)testInitWithPath {
diff --git a/Example/tvOSSample/Podfile b/Example/tvOSSample/Podfile
new file mode 100644
index 0000000..1e79a07
--- /dev/null
+++ b/Example/tvOSSample/Podfile
@@ -0,0 +1,14 @@
+# Uncomment the next line to define a global platform for your project
+# platform :ios, '9.0'
+
+target 'tvOSSample' do
+ # Comment the next line if you're not using Swift and don't want to use dynamic frameworks
+ use_frameworks!
+
+ # Pods for tvOSSample
+ pod 'FirebaseCore', :path => '../../'
+ pod 'FirebaseAuth', :path => '../../'
+ pod 'FirebaseDatabase', :path => '../../'
+ pod 'FirebaseStorage', :path => '../../'
+
+end
diff --git a/Example/tvOSSample/tvOSSample.xcodeproj/project.pbxproj b/Example/tvOSSample/tvOSSample.xcodeproj/project.pbxproj
new file mode 100644
index 0000000..60d2d25
--- /dev/null
+++ b/Example/tvOSSample/tvOSSample.xcodeproj/project.pbxproj
@@ -0,0 +1,424 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 48;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ B7F83BACE8E8330E2A5C0861 /* Pods_tvOSSample.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CCE8CD052953D89D96C9CDC /* Pods_tvOSSample.framework */; };
+ DE397DCB1FC8AD39007CBF0E /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = ED4D5FEB1FBA055300501573 /* GoogleService-Info.plist */; };
+ ED4D5FDD1FBA008200501573 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED4D5FDC1FBA008200501573 /* AppDelegate.swift */; };
+ ED4D5FE21FBA008200501573 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = ED4D5FE01FBA008200501573 /* Main.storyboard */; };
+ ED4D5FE41FBA008200501573 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = ED4D5FE31FBA008200501573 /* Assets.xcassets */; };
+ ED822C851FBA212600B00A2F /* StorageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED822C841FBA212600B00A2F /* StorageViewController.swift */; };
+ EDFBCF4C1FBB3ACC0041A9FD /* DatabaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDFBCF4B1FBB3ACC0041A9FD /* DatabaseViewController.swift */; };
+ EDFBCF511FBC88D20041A9FD /* AuthViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDFBCF501FBC88D20041A9FD /* AuthViewController.swift */; };
+ EDFBCF531FBC89B70041A9FD /* AuthLoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDFBCF521FBC89B70041A9FD /* AuthLoginViewController.swift */; };
+ EDFBCF551FBC95DD0041A9FD /* EmailLoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDFBCF541FBC95DD0041A9FD /* EmailLoginViewController.swift */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXFileReference section */
+ 3F2496EBDAD58301BC9119C5 /* Pods-tvOSSample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-tvOSSample.debug.xcconfig"; path = "Pods/Target Support Files/Pods-tvOSSample/Pods-tvOSSample.debug.xcconfig"; sourceTree = "<group>"; };
+ 5CCE8CD052953D89D96C9CDC /* Pods_tvOSSample.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_tvOSSample.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ E0A4F4B42E46BAA2DF7A366E /* Pods-tvOSSample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-tvOSSample.release.xcconfig"; path = "Pods/Target Support Files/Pods-tvOSSample/Pods-tvOSSample.release.xcconfig"; sourceTree = "<group>"; };
+ ED4D5FD91FBA008200501573 /* tvOSSample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = tvOSSample.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ ED4D5FDC1FBA008200501573 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
+ ED4D5FE11FBA008200501573 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
+ ED4D5FE31FBA008200501573 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
+ ED4D5FE51FBA008300501573 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
+ ED4D5FEB1FBA055300501573 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = "<group>"; };
+ ED822C841FBA212600B00A2F /* StorageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageViewController.swift; sourceTree = "<group>"; };
+ EDFBCF4B1FBB3ACC0041A9FD /* DatabaseViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatabaseViewController.swift; sourceTree = "<group>"; };
+ EDFBCF501FBC88D20041A9FD /* AuthViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthViewController.swift; sourceTree = "<group>"; };
+ EDFBCF521FBC89B70041A9FD /* AuthLoginViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthLoginViewController.swift; sourceTree = "<group>"; };
+ EDFBCF541FBC95DD0041A9FD /* EmailLoginViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmailLoginViewController.swift; sourceTree = "<group>"; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ ED4D5FD61FBA008200501573 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ B7F83BACE8E8330E2A5C0861 /* Pods_tvOSSample.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ A45A5C3CB0C23372780D9FA9 /* Pods */ = {
+ isa = PBXGroup;
+ children = (
+ 3F2496EBDAD58301BC9119C5 /* Pods-tvOSSample.debug.xcconfig */,
+ E0A4F4B42E46BAA2DF7A366E /* Pods-tvOSSample.release.xcconfig */,
+ );
+ name = Pods;
+ sourceTree = "<group>";
+ };
+ BA0B7BD0C775F3B6FDDCCEB5 /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ 5CCE8CD052953D89D96C9CDC /* Pods_tvOSSample.framework */,
+ );
+ name = Frameworks;
+ sourceTree = "<group>";
+ };
+ ED4D5FD01FBA008200501573 = {
+ isa = PBXGroup;
+ children = (
+ ED4D5FDB1FBA008200501573 /* tvOSSample */,
+ ED4D5FDA1FBA008200501573 /* Products */,
+ A45A5C3CB0C23372780D9FA9 /* Pods */,
+ BA0B7BD0C775F3B6FDDCCEB5 /* Frameworks */,
+ );
+ sourceTree = "<group>";
+ };
+ ED4D5FDA1FBA008200501573 /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ ED4D5FD91FBA008200501573 /* tvOSSample.app */,
+ );
+ name = Products;
+ sourceTree = "<group>";
+ };
+ ED4D5FDB1FBA008200501573 /* tvOSSample */ = {
+ isa = PBXGroup;
+ children = (
+ ED4D5FEB1FBA055300501573 /* GoogleService-Info.plist */,
+ ED4D5FDC1FBA008200501573 /* AppDelegate.swift */,
+ ED4D5FE01FBA008200501573 /* Main.storyboard */,
+ ED4D5FE31FBA008200501573 /* Assets.xcassets */,
+ ED4D5FE51FBA008300501573 /* Info.plist */,
+ ED822C841FBA212600B00A2F /* StorageViewController.swift */,
+ EDFBCF501FBC88D20041A9FD /* AuthViewController.swift */,
+ EDFBCF521FBC89B70041A9FD /* AuthLoginViewController.swift */,
+ EDFBCF541FBC95DD0041A9FD /* EmailLoginViewController.swift */,
+ EDFBCF4B1FBB3ACC0041A9FD /* DatabaseViewController.swift */,
+ );
+ path = tvOSSample;
+ sourceTree = "<group>";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+ ED4D5FD81FBA008200501573 /* tvOSSample */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = ED4D5FE81FBA008300501573 /* Build configuration list for PBXNativeTarget "tvOSSample" */;
+ buildPhases = (
+ D5025FECAC3B7FED5FF1A46A /* [CP] Check Pods Manifest.lock */,
+ ED4D5FD51FBA008200501573 /* Sources */,
+ ED4D5FD61FBA008200501573 /* Frameworks */,
+ ED4D5FD71FBA008200501573 /* Resources */,
+ 1D04A7E6AB24B35668030EB4 /* [CP] Embed Pods Frameworks */,
+ 3A8B26C16219186ABE0EDF0F /* [CP] Copy Pods Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = tvOSSample;
+ productName = tvOSSample;
+ productReference = ED4D5FD91FBA008200501573 /* tvOSSample.app */;
+ productType = "com.apple.product-type.application";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ ED4D5FD11FBA008200501573 /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ LastSwiftUpdateCheck = 0910;
+ LastUpgradeCheck = 0910;
+ ORGANIZATIONNAME = Firebase;
+ TargetAttributes = {
+ ED4D5FD81FBA008200501573 = {
+ CreatedOnToolsVersion = 9.1;
+ ProvisioningStyle = Manual;
+ };
+ };
+ };
+ buildConfigurationList = ED4D5FD41FBA008200501573 /* Build configuration list for PBXProject "tvOSSample" */;
+ compatibilityVersion = "Xcode 8.0";
+ developmentRegion = en;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ Base,
+ );
+ mainGroup = ED4D5FD01FBA008200501573;
+ productRefGroup = ED4D5FDA1FBA008200501573 /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ ED4D5FD81FBA008200501573 /* tvOSSample */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+ ED4D5FD71FBA008200501573 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ ED4D5FE41FBA008200501573 /* Assets.xcassets in Resources */,
+ ED4D5FE21FBA008200501573 /* Main.storyboard in Resources */,
+ DE397DCB1FC8AD39007CBF0E /* GoogleService-Info.plist in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXShellScriptBuildPhase section */
+ 1D04A7E6AB24B35668030EB4 /* [CP] Embed Pods Frameworks */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ "${SRCROOT}/Pods/Target Support Files/Pods-tvOSSample/Pods-tvOSSample-frameworks.sh",
+ "${BUILT_PRODUCTS_DIR}/GTMSessionFetcher/GTMSessionFetcher.framework",
+ "${BUILT_PRODUCTS_DIR}/GoogleToolboxForMac/GoogleToolboxForMac.framework",
+ "${BUILT_PRODUCTS_DIR}/leveldb-library/leveldb.framework",
+ );
+ name = "[CP] Embed Pods Frameworks";
+ outputPaths = (
+ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GTMSessionFetcher.framework",
+ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleToolboxForMac.framework",
+ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/leveldb.framework",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-tvOSSample/Pods-tvOSSample-frameworks.sh\"\n";
+ showEnvVarsInLog = 0;
+ };
+ 3A8B26C16219186ABE0EDF0F /* [CP] Copy Pods Resources */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ );
+ name = "[CP] Copy Pods Resources";
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-tvOSSample/Pods-tvOSSample-resources.sh\"\n";
+ showEnvVarsInLog = 0;
+ };
+ D5025FECAC3B7FED5FF1A46A /* [CP] Check Pods Manifest.lock */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
+ "${PODS_ROOT}/Manifest.lock",
+ );
+ name = "[CP] Check Pods Manifest.lock";
+ outputPaths = (
+ "$(DERIVED_FILE_DIR)/Pods-tvOSSample-checkManifestLockResult.txt",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
+ showEnvVarsInLog = 0;
+ };
+/* End PBXShellScriptBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ ED4D5FD51FBA008200501573 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ EDFBCF551FBC95DD0041A9FD /* EmailLoginViewController.swift in Sources */,
+ EDFBCF4C1FBB3ACC0041A9FD /* DatabaseViewController.swift in Sources */,
+ ED4D5FDD1FBA008200501573 /* AppDelegate.swift in Sources */,
+ EDFBCF511FBC88D20041A9FD /* AuthViewController.swift in Sources */,
+ EDFBCF531FBC89B70041A9FD /* AuthLoginViewController.swift in Sources */,
+ ED822C851FBA212600B00A2F /* StorageViewController.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXVariantGroup section */
+ ED4D5FE01FBA008200501573 /* Main.storyboard */ = {
+ isa = PBXVariantGroup;
+ children = (
+ ED4D5FE11FBA008200501573 /* Base */,
+ );
+ name = Main.storyboard;
+ sourceTree = "<group>";
+ };
+/* End PBXVariantGroup section */
+
+/* Begin XCBuildConfiguration section */
+ ED4D5FE61FBA008300501573 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_TESTABILITY = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ MTL_ENABLE_DEBUG_INFO = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ SDKROOT = appletvos;
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ TVOS_DEPLOYMENT_TARGET = 11.1;
+ };
+ name = Debug;
+ };
+ ED4D5FE71FBA008300501573 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ SDKROOT = appletvos;
+ SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
+ TVOS_DEPLOYMENT_TARGET = 11.1;
+ VALIDATE_PRODUCT = YES;
+ };
+ name = Release;
+ };
+ ED4D5FE91FBA008300501573 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 3F2496EBDAD58301BC9119C5 /* Pods-tvOSSample.debug.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image";
+ ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
+ CODE_SIGN_STYLE = Manual;
+ DEVELOPMENT_TEAM = "";
+ INFOPLIST_FILE = tvOSSample/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ PRODUCT_BUNDLE_IDENTIFIER = com.firebase.tvOSSample;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ PROVISIONING_PROFILE_SPECIFIER = "";
+ SWIFT_VERSION = 4.0;
+ TARGETED_DEVICE_FAMILY = 3;
+ };
+ name = Debug;
+ };
+ ED4D5FEA1FBA008300501573 /* Release */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = E0A4F4B42E46BAA2DF7A366E /* Pods-tvOSSample.release.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image";
+ ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
+ CODE_SIGN_STYLE = Manual;
+ DEVELOPMENT_TEAM = "";
+ INFOPLIST_FILE = tvOSSample/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ PRODUCT_BUNDLE_IDENTIFIER = com.firebase.tvOSSample;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ PROVISIONING_PROFILE_SPECIFIER = "";
+ SWIFT_VERSION = 4.0;
+ TARGETED_DEVICE_FAMILY = 3;
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ ED4D5FD41FBA008200501573 /* Build configuration list for PBXProject "tvOSSample" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ ED4D5FE61FBA008300501573 /* Debug */,
+ ED4D5FE71FBA008300501573 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ ED4D5FE81FBA008300501573 /* Build configuration list for PBXNativeTarget "tvOSSample" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ ED4D5FE91FBA008300501573 /* Debug */,
+ ED4D5FEA1FBA008300501573 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = ED4D5FD11FBA008200501573 /* Project object */;
+}
diff --git a/Example/tvOSSample/tvOSSample/AppDelegate.swift b/Example/tvOSSample/tvOSSample/AppDelegate.swift
new file mode 100644
index 0000000..723a3c4
--- /dev/null
+++ b/Example/tvOSSample/tvOSSample/AppDelegate.swift
@@ -0,0 +1,27 @@
+// Copyright 2017 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 UIKit
+import FirebaseCore
+
+@UIApplicationMain
+class AppDelegate: UIResponder, UIApplicationDelegate {
+ var window: UIWindow?
+
+ func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
+ // Override point for customization after application launch.
+ FirebaseApp.configure()
+ return true
+ }
+}
diff --git a/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json b/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json
new file mode 100644
index 0000000..7f06667
--- /dev/null
+++ b/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json
@@ -0,0 +1,11 @@
+{
+ "images" : [
+ {
+ "idiom" : "tv"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
diff --git a/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json b/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json
new file mode 100644
index 0000000..da4a164
--- /dev/null
+++ b/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json b/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json
new file mode 100644
index 0000000..d29f024
--- /dev/null
+++ b/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json
@@ -0,0 +1,17 @@
+{
+ "layers" : [
+ {
+ "filename" : "Front.imagestacklayer"
+ },
+ {
+ "filename" : "Middle.imagestacklayer"
+ },
+ {
+ "filename" : "Back.imagestacklayer"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json b/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json
new file mode 100644
index 0000000..7f06667
--- /dev/null
+++ b/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json
@@ -0,0 +1,11 @@
+{
+ "images" : [
+ {
+ "idiom" : "tv"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
diff --git a/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json b/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json
new file mode 100644
index 0000000..da4a164
--- /dev/null
+++ b/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json b/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json
new file mode 100644
index 0000000..7f06667
--- /dev/null
+++ b/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json
@@ -0,0 +1,11 @@
+{
+ "images" : [
+ {
+ "idiom" : "tv"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
diff --git a/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json b/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json
new file mode 100644
index 0000000..da4a164
--- /dev/null
+++ b/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json b/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json
new file mode 100644
index 0000000..16a370d
--- /dev/null
+++ b/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images" : [
+ {
+ "idiom" : "tv",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "tv",
+ "scale" : "2x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json b/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json
new file mode 100644
index 0000000..da4a164
--- /dev/null
+++ b/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json b/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json
new file mode 100644
index 0000000..d29f024
--- /dev/null
+++ b/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json
@@ -0,0 +1,17 @@
+{
+ "layers" : [
+ {
+ "filename" : "Front.imagestacklayer"
+ },
+ {
+ "filename" : "Middle.imagestacklayer"
+ },
+ {
+ "filename" : "Back.imagestacklayer"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json b/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json
new file mode 100644
index 0000000..16a370d
--- /dev/null
+++ b/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images" : [
+ {
+ "idiom" : "tv",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "tv",
+ "scale" : "2x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json b/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json
new file mode 100644
index 0000000..da4a164
--- /dev/null
+++ b/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json b/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json
new file mode 100644
index 0000000..16a370d
--- /dev/null
+++ b/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images" : [
+ {
+ "idiom" : "tv",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "tv",
+ "scale" : "2x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json b/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json
new file mode 100644
index 0000000..da4a164
--- /dev/null
+++ b/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json b/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json
new file mode 100644
index 0000000..b03ded1
--- /dev/null
+++ b/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json
@@ -0,0 +1,32 @@
+{
+ "assets" : [
+ {
+ "size" : "1280x768",
+ "idiom" : "tv",
+ "filename" : "App Icon - App Store.imagestack",
+ "role" : "primary-app-icon"
+ },
+ {
+ "size" : "400x240",
+ "idiom" : "tv",
+ "filename" : "App Icon.imagestack",
+ "role" : "primary-app-icon"
+ },
+ {
+ "size" : "2320x720",
+ "idiom" : "tv",
+ "filename" : "Top Shelf Image Wide.imageset",
+ "role" : "top-shelf-image-wide"
+ },
+ {
+ "size" : "1920x720",
+ "idiom" : "tv",
+ "filename" : "Top Shelf Image.imageset",
+ "role" : "top-shelf-image"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
diff --git a/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json b/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json
new file mode 100644
index 0000000..16a370d
--- /dev/null
+++ b/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images" : [
+ {
+ "idiom" : "tv",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "tv",
+ "scale" : "2x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json b/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json
new file mode 100644
index 0000000..16a370d
--- /dev/null
+++ b/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images" : [
+ {
+ "idiom" : "tv",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "tv",
+ "scale" : "2x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/tvOSSample/tvOSSample/Assets.xcassets/Contents.json b/Example/tvOSSample/tvOSSample/Assets.xcassets/Contents.json
new file mode 100644
index 0000000..da4a164
--- /dev/null
+++ b/Example/tvOSSample/tvOSSample/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/tvOSSample/tvOSSample/Assets.xcassets/LaunchImage.launchimage/Contents.json b/Example/tvOSSample/tvOSSample/Assets.xcassets/LaunchImage.launchimage/Contents.json
new file mode 100644
index 0000000..d746a60
--- /dev/null
+++ b/Example/tvOSSample/tvOSSample/Assets.xcassets/LaunchImage.launchimage/Contents.json
@@ -0,0 +1,22 @@
+{
+ "images" : [
+ {
+ "orientation" : "landscape",
+ "idiom" : "tv",
+ "extent" : "full-screen",
+ "minimum-system-version" : "11.0",
+ "scale" : "2x"
+ },
+ {
+ "orientation" : "landscape",
+ "idiom" : "tv",
+ "extent" : "full-screen",
+ "minimum-system-version" : "9.0",
+ "scale" : "1x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/tvOSSample/tvOSSample/AuthLoginViewController.swift b/Example/tvOSSample/tvOSSample/AuthLoginViewController.swift
new file mode 100644
index 0000000..65e0316
--- /dev/null
+++ b/Example/tvOSSample/tvOSSample/AuthLoginViewController.swift
@@ -0,0 +1,29 @@
+// Copyright 2017 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 UIKit
+import FirebaseAuth
+
+class AuthLoginViewController: UIViewController {
+ override func viewDidLoad() {
+ super.viewDidLoad()
+
+ // Do any additional setup after loading the view.
+ }
+
+ override func didReceiveMemoryWarning() {
+ super.didReceiveMemoryWarning()
+ // Dispose of any resources that can be recreated.
+ }
+}
diff --git a/Example/tvOSSample/tvOSSample/AuthViewController.swift b/Example/tvOSSample/tvOSSample/AuthViewController.swift
new file mode 100644
index 0000000..56276ed
--- /dev/null
+++ b/Example/tvOSSample/tvOSSample/AuthViewController.swift
@@ -0,0 +1,86 @@
+// Copyright 2017 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 UIKit
+import FirebaseAuth
+
+class AuthViewController: UIViewController {
+
+ // MARK: - User Interface
+
+ /// A stackview containing all of the buttons to providers (Email, OAuth, etc).
+ @IBOutlet var providers: UIStackView!
+
+ /// A stackview containing a signed in label and sign out button.
+ @IBOutlet var signedIn: UIStackView!
+
+ /// A label to display the status for the signed in user.
+ @IBOutlet var signInStatus: UILabel!
+
+ // MARK: - User Actions
+
+ @IBAction func signOutButtonHit(_ sender: UIButton) {
+ // Sign out via Auth and update the UI.
+ try? Auth.auth().signOut()
+
+ setUserSignedIn(nil)
+ }
+
+ // MARK: - View Controller Lifecycle
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+
+ // Update the UI based on the current user (if there is one).
+ setUserSignedIn(Auth.auth().currentUser)
+ }
+
+ override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
+ let destination = segue.destination
+ if let emailVC = destination as? EmailLoginViewController {
+ emailVC.delegate = self
+ }
+ }
+
+ // MARK: - Internal Helpers
+
+ private func setUserSignedIn(_ user: User?) {
+ if let user = user {
+ providers.isHidden = true
+ signedIn.isHidden = false
+
+ signInStatus.text = "User is signed in via \(user.providerID) and the UID \(user.uid)"
+ } else {
+ // User is signed out, hide the signed in state and show the providers.
+ providers.isHidden = false
+ signedIn.isHidden = true
+ }
+ }
+}
+
+// MARK: - EmailLoginDelegate conformance.
+
+extension AuthViewController: EmailLoginDelegate {
+ func emailLogin(_ controller: EmailLoginViewController, signedInAs user: User) {
+ setUserSignedIn(user)
+ dismiss(animated: true)
+ }
+
+ func emailLogin(_ controller: EmailLoginViewController, failedWithError error: Error) {
+ print("Fail..... \(error)")
+ DispatchQueue.main.async {
+ controller.presentError(with: "There was an issue logging in. Please try again.")
+ }
+ }
+}
diff --git a/Example/tvOSSample/tvOSSample/Base.lproj/Main.storyboard b/Example/tvOSSample/tvOSSample/Base.lproj/Main.storyboard
new file mode 100644
index 0000000..a2539b3
--- /dev/null
+++ b/Example/tvOSSample/tvOSSample/Base.lproj/Main.storyboard
@@ -0,0 +1,355 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder.AppleTV.Storyboard" version="3.0" toolsVersion="13529" targetRuntime="AppleTV" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="7zF-dN-6Yw">
+ <device id="appleTV" orientation="landscape">
+ <adaptation id="light"/>
+ </device>
+ <dependencies>
+ <deployment identifier="tvOS"/>
+ <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13527"/>
+ <capability name="Constraints to layout margins" minToolsVersion="6.0"/>
+ <capability name="Safe area layout guides" minToolsVersion="9.0"/>
+ <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+ </dependencies>
+ <customFonts key="customFonts">
+ <array key="HelveticaNeue.ttc">
+ <string>HelveticaNeue</string>
+ </array>
+ </customFonts>
+ <scenes>
+ <!--Storage-->
+ <scene sceneID="tne-QT-ifu">
+ <objects>
+ <viewController title="Storage" id="BYZ-38-t0r" customClass="StorageViewController" customModule="tvOSSample" customModuleProvider="target" sceneMemberID="viewController">
+ <layoutGuides>
+ <viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
+ <viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
+ </layoutGuides>
+ <view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
+ <rect key="frame" x="0.0" y="0.0" width="1920" height="1080"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+ <subviews>
+ <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="center" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="G46-SV-2zY">
+ <rect key="frame" x="90" y="60" width="1740" height="960"/>
+ <subviews>
+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="1000" text="Hello, Firebase!" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="g3G-9d-w9c">
+ <rect key="frame" x="628" y="0.0" width="484" height="91"/>
+ <fontDescription key="fontDescription" style="UICTFontTextStyleTitle1"/>
+ <nil key="textColor"/>
+ <nil key="highlightedColor"/>
+ </label>
+ <imageView userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="1Ht-Xf-hyX">
+ <rect key="frame" x="0.0" y="111" width="1740" height="677"/>
+ </imageView>
+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="1000" text="State: Pending download" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="4sv-2l-7R6">
+ <rect key="frame" x="657" y="808" width="427" height="46"/>
+ <fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/>
+ <nil key="textColor"/>
+ <nil key="highlightedColor"/>
+ </label>
+ <stackView opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="253" verticalCompressionResistancePriority="749" distribution="fillEqually" spacing="60" translatesAutoresizingMaskIntoConstraints="NO" id="Pcd-oU-DKI" userLabel="Button Stack View">
+ <rect key="frame" x="589" y="874" width="562" height="86"/>
+ <subviews>
+ <button opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="251" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="ERN-SX-QsK">
+ <rect key="frame" x="0.0" y="0.0" width="251" height="86"/>
+ <inset key="contentEdgeInsets" minX="40" minY="20" maxX="40" maxY="20"/>
+ <state key="normal" title="Download"/>
+ <connections>
+ <action selector="downloadButtonHit:" destination="BYZ-38-t0r" eventType="primaryActionTriggered" id="wUX-Vk-IME"/>
+ </connections>
+ </button>
+ <button opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="251" verticalCompressionResistancePriority="1000" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="tx1-wP-N8v">
+ <rect key="frame" x="311" y="0.0" width="251" height="86"/>
+ <color key="backgroundColor" red="1" green="0.40000000600000002" blue="0.40000000600000002" alpha="1" colorSpace="calibratedRGB"/>
+ <inset key="contentEdgeInsets" minX="40" minY="20" maxX="40" maxY="20"/>
+ <state key="normal" title="Clear"/>
+ <connections>
+ <action selector="clearButtonHit:" destination="BYZ-38-t0r" eventType="primaryActionTriggered" id="s8P-pU-T19"/>
+ </connections>
+ </button>
+ </subviews>
+ </stackView>
+ </subviews>
+ </stackView>
+ </subviews>
+ <color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
+ <constraints>
+ <constraint firstItem="G46-SV-2zY" firstAttribute="leading" secondItem="wu6-TO-1qx" secondAttribute="leading" id="HA1-L9-mG6"/>
+ <constraint firstItem="wu6-TO-1qx" firstAttribute="trailing" secondItem="G46-SV-2zY" secondAttribute="trailing" id="f82-FP-nqR"/>
+ <constraint firstItem="G46-SV-2zY" firstAttribute="top" secondItem="y3c-jy-aDJ" secondAttribute="bottom" id="gcN-NC-PDe"/>
+ <constraint firstItem="wfy-db-euE" firstAttribute="top" secondItem="G46-SV-2zY" secondAttribute="bottom" id="hb7-C3-J4p"/>
+ </constraints>
+ <viewLayoutGuide key="safeArea" id="wu6-TO-1qx"/>
+ </view>
+ <extendedEdge key="edgesForExtendedLayout" bottom="YES"/>
+ <tabBarItem key="tabBarItem" title="Storage" id="4mO-Ls-vgJ"/>
+ <connections>
+ <outlet property="clearButton" destination="tx1-wP-N8v" id="lQA-AQ-sT6"/>
+ <outlet property="downloadButton" destination="ERN-SX-QsK" id="2Kf-3u-AxO"/>
+ <outlet property="imageView" destination="1Ht-Xf-hyX" id="MeV-di-b99"/>
+ <outlet property="stateLabel" destination="4sv-2l-7R6" id="DKm-L2-aiR"/>
+ </connections>
+ </viewController>
+ <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
+ </objects>
+ <point key="canvasLocation" x="-3032" y="1936"/>
+ </scene>
+ <!--Database-->
+ <scene sceneID="i8D-25-rvO">
+ <objects>
+ <viewController id="gup-Ft-HnK" customClass="DatabaseViewController" customModule="tvOSSample" customModuleProvider="target" sceneMemberID="viewController">
+ <layoutGuides>
+ <viewControllerLayoutGuide type="top" id="i5o-My-7RI"/>
+ <viewControllerLayoutGuide type="bottom" id="R20-Wh-bn4"/>
+ </layoutGuides>
+ <view key="view" contentMode="scaleToFill" id="1lM-LN-Cey">
+ <rect key="frame" x="0.0" y="0.0" width="1920" height="1080"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+ <subviews>
+ <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="Bah-Ek-V99">
+ <rect key="frame" x="90" y="60" width="1740" height="960"/>
+ <subviews>
+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="253" verticalCompressionResistancePriority="1000" text="Magic Syncing Counter" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Pjh-c5-D2o">
+ <rect key="frame" x="0.0" y="0.0" width="1740" height="91"/>
+ <fontDescription key="fontDescription" style="UICTFontTextStyleTitle1"/>
+ <nil key="textColor"/>
+ <nil key="highlightedColor"/>
+ </label>
+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalCompressionResistancePriority="900" text="?" textAlignment="center" lineBreakMode="tailTruncation" minimumScaleFactor="0.20000000000000001" translatesAutoresizingMaskIntoConstraints="NO" id="gcg-TC-hY4">
+ <rect key="frame" x="0.0" y="111" width="1740" height="743"/>
+ <fontDescription key="fontDescription" name="HelveticaNeue" family="Helvetica Neue" pointSize="700"/>
+ <nil key="textColor"/>
+ <nil key="highlightedColor"/>
+ </label>
+ <stackView opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="253" verticalCompressionResistancePriority="749" distribution="fillEqually" spacing="60" translatesAutoresizingMaskIntoConstraints="NO" id="1hw-2E-6dg" userLabel="Button Stack View">
+ <rect key="frame" x="0.0" y="874" width="1740" height="86"/>
+ <subviews>
+ <button opaque="NO" contentMode="scaleToFill" verticalCompressionResistancePriority="1000" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Yc7-OR-WVJ">
+ <rect key="frame" x="0.0" y="0.0" width="840" height="86"/>
+ <color key="backgroundColor" red="0.25098040700000002" green="0.50196081400000003" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
+ <inset key="contentEdgeInsets" minX="40" minY="20" maxX="40" maxY="20"/>
+ <state key="normal" title="+1"/>
+ <connections>
+ <action selector="incrementButtonHit:" destination="gup-Ft-HnK" eventType="primaryActionTriggered" id="zR6-AT-sGq"/>
+ </connections>
+ </button>
+ <button opaque="NO" contentMode="scaleToFill" verticalCompressionResistancePriority="1000" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Ams-4H-2D4">
+ <rect key="frame" x="900" y="0.0" width="840" height="86"/>
+ <color key="backgroundColor" red="1" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
+ <inset key="contentEdgeInsets" minX="40" minY="20" maxX="40" maxY="20"/>
+ <state key="normal" title="-1"/>
+ <connections>
+ <action selector="decrementButton:" destination="gup-Ft-HnK" eventType="primaryActionTriggered" id="KTe-Dn-DgZ"/>
+ </connections>
+ </button>
+ </subviews>
+ </stackView>
+ </subviews>
+ </stackView>
+ </subviews>
+ <constraints>
+ <constraint firstItem="R20-Wh-bn4" firstAttribute="top" secondItem="Bah-Ek-V99" secondAttribute="bottom" id="ECo-G5-TMU"/>
+ <constraint firstItem="V8l-aL-U1D" firstAttribute="trailing" secondItem="Bah-Ek-V99" secondAttribute="trailing" id="Ikr-TA-Ejk"/>
+ <constraint firstItem="Bah-Ek-V99" firstAttribute="leading" secondItem="V8l-aL-U1D" secondAttribute="leading" id="L8R-dY-jge"/>
+ <constraint firstItem="Bah-Ek-V99" firstAttribute="top" secondItem="i5o-My-7RI" secondAttribute="bottom" id="nvx-MN-rDc"/>
+ </constraints>
+ <viewLayoutGuide key="safeArea" id="V8l-aL-U1D"/>
+ </view>
+ <extendedEdge key="edgesForExtendedLayout" bottom="YES"/>
+ <tabBarItem key="tabBarItem" title="Database" id="4ry-Ig-tAU"/>
+ <connections>
+ <outlet property="currentValue" destination="gcg-TC-hY4" id="1Cs-5w-yPF"/>
+ </connections>
+ </viewController>
+ <placeholder placeholderIdentifier="IBFirstResponder" id="FDt-0K-Tad" userLabel="First Responder" sceneMemberID="firstResponder"/>
+ </objects>
+ <point key="canvasLocation" x="-812" y="1936"/>
+ </scene>
+ <!--Firebase Demo-->
+ <scene sceneID="Cbs-Et-MXF">
+ <objects>
+ <tabBarController title="Firebase Demo" automaticallyAdjustsScrollViewInsets="NO" id="7zF-dN-6Yw" sceneMemberID="viewController">
+ <toolbarItems/>
+ <tabBar key="tabBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="yhw-Ot-Uul">
+ <rect key="frame" x="0.0" y="0.0" width="1000" height="1000"/>
+ <autoresizingMask key="autoresizingMask"/>
+ <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
+ </tabBar>
+ <connections>
+ <segue destination="BYZ-38-t0r" kind="relationship" relationship="viewControllers" id="he8-oR-peB"/>
+ <segue destination="XYg-pc-Kbc" kind="relationship" relationship="viewControllers" id="x6X-oT-xPX"/>
+ <segue destination="gup-Ft-HnK" kind="relationship" relationship="viewControllers" id="CM1-Uf-Vcz"/>
+ </connections>
+ </tabBarController>
+ <placeholder placeholderIdentifier="IBFirstResponder" id="2Mm-5J-pE9" userLabel="First Responder" sceneMemberID="firstResponder"/>
+ </objects>
+ <point key="canvasLocation" x="-814" y="-224"/>
+ </scene>
+ <!--Auth-->
+ <scene sceneID="KOh-Uh-VKO">
+ <objects>
+ <viewController id="XYg-pc-Kbc" customClass="AuthViewController" customModule="tvOSSample" customModuleProvider="target" sceneMemberID="viewController">
+ <layoutGuides>
+ <viewControllerLayoutGuide type="top" id="q8D-PQ-udq"/>
+ <viewControllerLayoutGuide type="bottom" id="w5r-DR-UCF"/>
+ </layoutGuides>
+ <view key="view" contentMode="scaleToFill" id="8lE-2P-i4i">
+ <rect key="frame" x="0.0" y="0.0" width="1920" height="1080"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+ <subviews>
+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="500" verticalCompressionResistancePriority="1000" text="Auth" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="GfO-0W-QKE">
+ <rect key="frame" x="885" y="60" width="151" height="91"/>
+ <fontDescription key="fontDescription" style="UICTFontTextStyleTitle1"/>
+ <nil key="textColor"/>
+ <nil key="highlightedColor"/>
+ </label>
+ <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="fillEqually" spacing="60" translatesAutoresizingMaskIntoConstraints="NO" id="jeZ-ht-4RO" userLabel="Outer Stack">
+ <rect key="frame" x="769" y="497" width="383" height="86"/>
+ <subviews>
+ <stackView hidden="YES" opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="center" spacing="60" translatesAutoresizingMaskIntoConstraints="NO" id="ljv-p2-x1f" userLabel="Signed In Stack">
+ <rect key="frame" x="0.0" y="-497" width="383" height="60"/>
+ <subviews>
+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Signed in: (UID)" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="j4e-N4-2IJ">
+ <rect key="frame" x="57" y="0.0" width="269" height="0.0"/>
+ <fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/>
+ <nil key="textColor"/>
+ <nil key="highlightedColor"/>
+ </label>
+ <button opaque="NO" contentMode="scaleToFill" ambiguous="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="m3A-YK-ass">
+ <rect key="frame" x="78" y="60" width="228" height="0.0"/>
+ <inset key="contentEdgeInsets" minX="40" minY="20" maxX="40" maxY="20"/>
+ <state key="normal" title="Sign Out"/>
+ <connections>
+ <action selector="signOutButtonHit:" destination="XYg-pc-Kbc" eventType="primaryActionTriggered" id="Hiq-Jx-oNo"/>
+ </connections>
+ </button>
+ </subviews>
+ </stackView>
+ <stackView opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="500" verticalCompressionResistancePriority="749" axis="vertical" distribution="fillEqually" alignment="center" spacing="40" translatesAutoresizingMaskIntoConstraints="NO" id="0bY-NY-sEX" userLabel="Other Providers Stack">
+ <rect key="frame" x="0.0" y="0.0" width="383" height="86"/>
+ <subviews>
+ <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="FRK-uH-meZ">
+ <rect key="frame" x="0.0" y="0.0" width="383" height="86"/>
+ <inset key="contentEdgeInsets" minX="40" minY="20" maxX="40" maxY="20"/>
+ <state key="normal" title="Email &amp; Password"/>
+ <connections>
+ <segue destination="64h-nN-haS" kind="show" id="OBj-mt-jsE"/>
+ </connections>
+ </button>
+ </subviews>
+ </stackView>
+ </subviews>
+ </stackView>
+ </subviews>
+ <constraints>
+ <constraint firstItem="jeZ-ht-4RO" firstAttribute="top" relation="greaterThanOrEqual" secondItem="GfO-0W-QKE" secondAttribute="bottom" id="HyJ-ip-Q1e"/>
+ <constraint firstItem="GfO-0W-QKE" firstAttribute="top" secondItem="q8D-PQ-udq" secondAttribute="bottom" id="Icd-az-gH7"/>
+ <constraint firstItem="GfO-0W-QKE" firstAttribute="centerX" secondItem="8lE-2P-i4i" secondAttribute="centerX" id="q5o-7T-CbO"/>
+ <constraint firstItem="jeZ-ht-4RO" firstAttribute="centerY" secondItem="8lE-2P-i4i" secondAttribute="centerY" id="tii-97-jog"/>
+ <constraint firstItem="jeZ-ht-4RO" firstAttribute="centerX" secondItem="8lE-2P-i4i" secondAttribute="centerX" id="u5t-zJ-nRH"/>
+ </constraints>
+ <viewLayoutGuide key="safeArea" id="Kvt-PZ-Nk9"/>
+ </view>
+ <extendedEdge key="edgesForExtendedLayout"/>
+ <tabBarItem key="tabBarItem" title="Auth" id="IQh-1s-utZ"/>
+ <connections>
+ <outlet property="providers" destination="0bY-NY-sEX" id="8oq-4v-UHT"/>
+ <outlet property="signInStatus" destination="j4e-N4-2IJ" id="7wt-c1-8N9"/>
+ <outlet property="signedIn" destination="ljv-p2-x1f" id="4IZ-Bm-XNW"/>
+ </connections>
+ </viewController>
+ <placeholder placeholderIdentifier="IBFirstResponder" id="7sk-w5-jZ7" userLabel="First Responder" sceneMemberID="firstResponder"/>
+ </objects>
+ <point key="canvasLocation" x="1350" y="1936"/>
+ </scene>
+ <!--Email Login View Controller-->
+ <scene sceneID="QMw-Qt-hDM">
+ <objects>
+ <viewController id="64h-nN-haS" customClass="EmailLoginViewController" customModule="tvOSSample" customModuleProvider="target" sceneMemberID="viewController">
+ <layoutGuides>
+ <viewControllerLayoutGuide type="top" id="4fg-KG-aTM"/>
+ <viewControllerLayoutGuide type="bottom" id="RkR-wX-Tdq"/>
+ </layoutGuides>
+ <view key="view" contentMode="scaleToFill" id="yhK-2X-Z3p">
+ <rect key="frame" x="0.0" y="0.0" width="1920" height="1080"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+ <subviews>
+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="1000" text="Email Login" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="46l-Z7-4KI">
+ <rect key="frame" x="778" y="20" width="365" height="91"/>
+ <fontDescription key="fontDescription" style="UICTFontTextStyleTitle1"/>
+ <nil key="textColor"/>
+ <nil key="highlightedColor"/>
+ </label>
+ <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="equalSpacing" alignment="center" spacing="40" translatesAutoresizingMaskIntoConstraints="NO" id="A1D-vt-Yfv">
+ <rect key="frame" x="716" y="409" width="490" height="262"/>
+ <subviews>
+ <stackView opaque="NO" contentMode="scaleToFill" verticalCompressionResistancePriority="1000" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="8Ag-kk-FiI" userLabel="Email Inputs">
+ <rect key="frame" x="0.0" y="0.0" width="490" height="136"/>
+ <subviews>
+ <stackView opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="100" axis="vertical" distribution="fillEqually" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="OMj-ve-Czq" userLabel="Email Fields Stack">
+ <rect key="frame" x="0.0" y="0.0" width="490" height="136"/>
+ <subviews>
+ <textField opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="100" horizontalCompressionResistancePriority="800" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="Email Address" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="Jwa-bJ-Tt6">
+ <rect key="frame" x="0.0" y="0.0" width="490" height="58"/>
+ <nil key="textColor"/>
+ <fontDescription key="fontDescription" style="UICTFontTextStyleTitle3"/>
+ <textInputTraits key="textInputTraits" textContentType="email"/>
+ </textField>
+ <textField opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="100" horizontalCompressionResistancePriority="800" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="Password" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="GP0-E2-Zjx">
+ <rect key="frame" x="0.0" y="78" width="490" height="58"/>
+ <nil key="textColor"/>
+ <fontDescription key="fontDescription" style="UICTFontTextStyleTitle3"/>
+ <textInputTraits key="textInputTraits" secureTextEntry="YES" textContentType="password"/>
+ </textField>
+ </subviews>
+ </stackView>
+ </subviews>
+ </stackView>
+ <stackView opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="256" verticalCompressionResistancePriority="1000" distribution="fillEqually" alignment="center" spacing="60" translatesAutoresizingMaskIntoConstraints="NO" id="6pe-KG-Tt8" userLabel="Email Buttons Stack">
+ <rect key="frame" x="0.0" y="176" width="490" height="86"/>
+ <subviews>
+ <button opaque="NO" contentMode="scaleToFill" verticalCompressionResistancePriority="1000" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="JUJ-6s-duw">
+ <rect key="frame" x="0.0" y="0.0" width="215" height="86"/>
+ <inset key="contentEdgeInsets" minX="40" minY="20" maxX="40" maxY="20"/>
+ <state key="normal" title="Log In"/>
+ <connections>
+ <action selector="logInButtonHit:" destination="64h-nN-haS" eventType="primaryActionTriggered" id="Ea2-dP-bgD"/>
+ </connections>
+ </button>
+ <button opaque="NO" contentMode="scaleToFill" verticalCompressionResistancePriority="1000" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Dt9-2d-WJM">
+ <rect key="frame" x="275" y="0.0" width="215" height="86"/>
+ <inset key="contentEdgeInsets" minX="40" minY="20" maxX="40" maxY="20"/>
+ <state key="normal" title="Sign Up"/>
+ <connections>
+ <action selector="signUpButtonHit:" destination="64h-nN-haS" eventType="primaryActionTriggered" id="KUW-Xi-zeI"/>
+ </connections>
+ </button>
+ </subviews>
+ </stackView>
+ </subviews>
+ <constraints>
+ <constraint firstItem="6pe-KG-Tt8" firstAttribute="width" secondItem="8Ag-kk-FiI" secondAttribute="width" id="si6-Rj-cKU"/>
+ </constraints>
+ </stackView>
+ </subviews>
+ <constraints>
+ <constraint firstItem="A1D-vt-Yfv" firstAttribute="centerY" secondItem="yhK-2X-Z3p" secondAttribute="centerY" id="AsP-r2-CmU"/>
+ <constraint firstItem="46l-Z7-4KI" firstAttribute="centerX" secondItem="yhK-2X-Z3p" secondAttribute="centerX" id="FSK-gW-wzc"/>
+ <constraint firstItem="A1D-vt-Yfv" firstAttribute="centerX" secondItem="46l-Z7-4KI" secondAttribute="centerX" id="SRe-fq-sjl"/>
+ <constraint firstItem="46l-Z7-4KI" firstAttribute="top" secondItem="yhK-2X-Z3p" secondAttribute="top" constant="20" id="WYg-tl-PPG"/>
+ <constraint firstAttribute="bottomMargin" relation="greaterThanOrEqual" secondItem="A1D-vt-Yfv" secondAttribute="bottom" id="b2e-xU-0xq"/>
+ <constraint firstItem="A1D-vt-Yfv" firstAttribute="top" relation="greaterThanOrEqual" secondItem="46l-Z7-4KI" secondAttribute="bottom" id="bj1-cy-X9f"/>
+ </constraints>
+ <viewLayoutGuide key="safeArea" id="mS6-Be-asr"/>
+ </view>
+ <value key="contentSizeForViewInPopover" type="size" width="840" height="385"/>
+ <connections>
+ <outlet property="emailAddress" destination="Jwa-bJ-Tt6" id="HOE-at-pka"/>
+ <outlet property="password" destination="GP0-E2-Zjx" id="X8f-Ew-moe"/>
+ </connections>
+ </viewController>
+ <placeholder placeholderIdentifier="IBFirstResponder" id="guA-tW-goc" userLabel="First Responder" sceneMemberID="firstResponder"/>
+ </objects>
+ <point key="canvasLocation" x="447" y="4052"/>
+ </scene>
+ </scenes>
+</document>
diff --git a/Example/tvOSSample/tvOSSample/DatabaseViewController.swift b/Example/tvOSSample/tvOSSample/DatabaseViewController.swift
new file mode 100644
index 0000000..2b710fa
--- /dev/null
+++ b/Example/tvOSSample/tvOSSample/DatabaseViewController.swift
@@ -0,0 +1,83 @@
+// Copyright 2017 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 UIKit
+import FirebaseDatabase
+
+/// A class to demonstrate the Firebase Realtime Database API. This will show a number read
+/// from the Database and increase or decrease it based on the buttons pressed.
+class DatabaseViewController: UIViewController {
+ private enum Counter: Int {
+ case increment = 1
+ case decrement = -1
+
+ var intValue: Int {
+ return rawValue
+ }
+ }
+
+ // MARK: - Interface
+
+ /// Label to display the current value.
+ @IBOutlet var currentValue: UILabel!
+
+ // MARK: - User Actions
+
+ /// The increment button was hit.
+ @IBAction func incrementButtonHit(_ sender: UIButton) { changeServerValue(with: .increment) }
+
+ /// the decrement button was hit.
+ @IBAction func decrementButton(_ sender: UIButton) { changeServerValue(with: .decrement) }
+
+ // MARK: - Internal Helpers
+
+ /// Update the number on the server by a particular value. Note: the number passed in should only
+ /// be one above or below the current number.
+ private func changeServerValue(with type: Counter) {
+ let ref = Database.database().reference(withPath: Constants.databasePath)
+ // Update the current value of the number.
+ ref.runTransactionBlock { (currentData) -> TransactionResult in
+ guard let value = currentData.value as? Int else {
+ return TransactionResult.abort()
+ }
+
+ currentData.value = value + type.intValue
+ return TransactionResult.success(withValue: currentData)
+ }
+ }
+
+ // MARK: - View Controller Lifecycle
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+
+ // Observe the current value, and update the UI every time it changes.
+ let ref = Database.database().reference(withPath: Constants.databasePath)
+
+ ref.observe(.value) { [weak self] snapshot in
+ guard let value = snapshot.value as? Int else {
+ print("Error grabbing value from Snapshot!")
+ return
+ }
+
+ self?.currentValue.text = "\(value)"
+ }
+ }
+
+ // MARK: - Constants
+
+ private struct Constants {
+ static let databasePath = "magicSyncingCounter"
+ }
+}
diff --git a/Example/tvOSSample/tvOSSample/EmailLoginViewController.swift b/Example/tvOSSample/tvOSSample/EmailLoginViewController.swift
new file mode 100644
index 0000000..9bea765
--- /dev/null
+++ b/Example/tvOSSample/tvOSSample/EmailLoginViewController.swift
@@ -0,0 +1,94 @@
+// Copyright 2017 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 UIKit
+import FirebaseAuth
+
+protocol EmailLoginDelegate {
+ func emailLogin(_ controller: EmailLoginViewController, signedInAs user: User)
+ func emailLogin(_ controller: EmailLoginViewController, failedWithError error: Error)
+}
+
+class EmailLoginViewController: UIViewController {
+
+ // MARK: - Public Properties
+
+ var delegate: EmailLoginDelegate?
+
+ // MARK: - User Interface
+
+ @IBOutlet private var emailAddress: UITextField!
+ @IBOutlet private var password: UITextField!
+
+ // MARK: - User Actions
+
+ @IBAction func logInButtonHit(_ sender: UIButton) {
+ guard let (email, password) = validatedInputs() else { return }
+
+ Auth.auth().signIn(withEmail: email, password: password) { [unowned self] user, error in
+ guard let user = user else {
+ print("Error signing in: \(error!)")
+ self.delegate?.emailLogin(self, failedWithError: error!)
+ return
+ }
+
+ print("Signed in as user: \(user.uid)!")
+ self.delegate?.emailLogin(self, signedInAs: user)
+ }
+ }
+
+ @IBAction func signUpButtonHit(_ sender: UIButton) {
+ guard let (email, password) = validatedInputs() else { return }
+
+ Auth.auth().createUser(withEmail: email, password: password) { [unowned self] user, error in
+ guard let user = user else {
+ print("Error signing up: \(error!)")
+ self.delegate?.emailLogin(self, failedWithError: error!)
+ return
+ }
+
+ print("Created new user: \(user.uid)!")
+ self.delegate?.emailLogin(self, signedInAs: user)
+ }
+ }
+
+ // MARK: - View Controller Lifecycle
+
+ override func viewDidLoad() {
+ }
+
+ // MARK: - Helper Methods
+
+ /// Validate the inputs for user email and password, returning the username and password if valid,
+ /// otherwise nil.
+ private func validatedInputs() -> (email: String, password: String)? {
+ guard let userEmail = emailAddress.text, userEmail.count >= 6 else {
+ presentError(with: "Email address isn't long enough.")
+ return nil
+ }
+
+ guard let userPassword = password.text, userPassword.count >= 6 else {
+ presentError(with: "Password is not long enough!")
+ return nil
+ }
+
+ return (userEmail, userPassword)
+ }
+
+ func presentError(with text: String) {
+ let alert = UIAlertController(title: "Error", message: text, preferredStyle: .alert)
+ alert.addAction(UIAlertAction(title: "Okay", style: .default))
+ present(alert, animated: true)
+ }
+}
diff --git a/Example/tvOSSample/tvOSSample/Info.plist b/Example/tvOSSample/tvOSSample/Info.plist
new file mode 100644
index 0000000..02942a3
--- /dev/null
+++ b/Example/tvOSSample/tvOSSample/Info.plist
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>$(DEVELOPMENT_LANGUAGE)</string>
+ <key>CFBundleExecutable</key>
+ <string>$(EXECUTABLE_NAME)</string>
+ <key>CFBundleIdentifier</key>
+ <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>$(PRODUCT_NAME)</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>1.0</string>
+ <key>CFBundleVersion</key>
+ <string>1</string>
+ <key>LSRequiresIPhoneOS</key>
+ <true/>
+ <key>UIMainStoryboardFile</key>
+ <string>Main</string>
+ <key>UIRequiredDeviceCapabilities</key>
+ <array>
+ <string>arm64</string>
+ </array>
+ <key>UIUserInterfaceStyle</key>
+ <string>Automatic</string>
+</dict>
+</plist>
diff --git a/Example/tvOSSample/tvOSSample/StorageViewController.swift b/Example/tvOSSample/tvOSSample/StorageViewController.swift
new file mode 100644
index 0000000..2e91d92
--- /dev/null
+++ b/Example/tvOSSample/tvOSSample/StorageViewController.swift
@@ -0,0 +1,148 @@
+// Copyright 2017 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 UIKit
+import FirebaseStorage
+
+class StorageViewController: UIViewController {
+ /// An enum describing the different states of the view controller.
+ private enum UIState: Equatable {
+ /// No image is being shown, waiting on user action.
+ case cleared
+
+ /// Currently downloading from Firebase.
+ case downloading(StorageTask)
+
+ /// The image has downloaded and should be displayed.
+ case downloaded(UIImage)
+
+ /// Show an error message and stop downloading.
+ case failed(String)
+
+ /// Equatable support for UIState.
+ static func == (lhs: StorageViewController.UIState, rhs: StorageViewController.UIState) -> Bool {
+ switch (lhs, rhs) {
+ case (.cleared, .cleared): return true
+ case (.downloading, .downloading): return true
+ case (.downloaded, .downloaded): return true
+ case (.failed, .failed): return true
+ default: return false
+ }
+ }
+ }
+
+ /// MARK: - Properties
+
+ /// The current internal state of the view controller.
+ private var state: UIState = .cleared {
+ didSet { changeState(from: oldValue, to: state) }
+ }
+
+ // MARK: Interface
+
+ /// Image view to display the downloaded image.
+ @IBOutlet var imageView: UIImageView!
+
+ /// The download button.
+ @IBOutlet var downloadButton: UIButton!
+
+ /// The clear button.
+ @IBOutlet var clearButton: UIButton!
+
+ /// A visual representation of the state.
+ @IBOutlet var stateLabel: UILabel!
+
+ // MARK: - User Actions
+
+ @IBAction func downloadButtonHit(_ sender: UIButton) {
+ guard case .cleared = state else { return }
+
+ // Start the download.
+ let storage = Storage.storage()
+ let ref = storage.reference(withPath: Constants.downloadPath)
+ // TODO: Show progress bar here using proper API.
+ let task = ref.getData(maxSize: Constants.maxSize) { [unowned self] data, error in
+ guard let data = data else {
+ self.state = .failed("Error downloading: \(error!.localizedDescription)")
+ return
+ }
+
+ // Create a UIImage from the PNG data.
+ guard let image = UIImage(data: data) else {
+ self.state = .failed("Unable to initialize image with data downloaded.")
+ return
+ }
+
+ self.state = .downloaded(image)
+ }
+
+ // The completion block above could be run before this line in some situations. If that's the
+ // case, we don't need to do anything else and can return.
+ if case .downloaded = state { return }
+
+ // Set the state to downloading!
+ state = .downloading(task)
+ }
+
+ @IBAction func clearButtonHit(_ sender: UIButton) {
+ guard case .downloaded = state else { return }
+
+ state = .cleared
+ }
+
+ // MARK: - State Management
+
+ /// Changing from old state to new state.
+ private func changeState(from oldState: UIState, to newState: UIState) {
+ if oldState == newState { return }
+
+ switch (oldState, newState) {
+ // Regular state, start downloading the image.
+ case (.cleared, .downloading(_)):
+ // TODO: Update the UI with a spinner? Progress update?
+ stateLabel.text = "State: Downloading..."
+
+ // Download complete, ensure the download button is still off and enable the clear button.
+ case let (_, .downloaded(image)):
+ imageView.image = image
+ stateLabel.text = "State: Image downloaded!"
+
+ // Clear everything and reset to the original state.
+ case (_, .cleared):
+ imageView.image = nil
+ stateLabel.text = "State: Pending download"
+
+ // An error occurred.
+ case let (_, .failed(error)):
+ stateLabel.text = "State: \(error)"
+
+ // For now, as the default, throw a fatal error because it's an unexpected state. This will
+ // allow us to catch it immediately and add the required action or fix the bug.
+ default:
+ fatalError("Programmer error! Tried to go from \(oldState) to \(newState)")
+ }
+ }
+
+ // MARK: - Constants
+
+ /// Internal constants for this class.
+ private struct Constants {
+ /// The image name to download. Can comment this out and replace it with the other below it as
+ /// part of the demo. Ensure that Storage has an image uploaded to this path for this to
+ /// function properly.
+ static let downloadPath = "YOUR_IMAGE_NAME.jpg"
+
+ static let maxSize: Int64 = 1024 * 1024 * 10 // ~10MB
+ }
+}
diff --git a/Firebase/Auth/CHANGELOG.md b/Firebase/Auth/CHANGELOG.md
index d464cd6..858d2a7 100644
--- a/Firebase/Auth/CHANGELOG.md
+++ b/Firebase/Auth/CHANGELOG.md
@@ -1,3 +1,40 @@
+# v4.6.1
+- Fixes crash which occurred when certain Firebase IDTokens were being parsed (#1076).
+
+# v4.6.0
+- Adds `getIDTokenResultWithCompletion:` and `getIDTokenResultForcingRefresh:completion:` APIs which
+ call back with an AuthTokenResult object. The Auth token result object contains the ID token JWT string and other properties associated with the token including the decoded available payload claims (#1004).
+
+- Adds the `updateCurrentUser:completion:` API which sets the currentUser on the calling Auth instance to the provided user object (#1018).
+
+- Adds client-side validation to prevent setting `handleCodeInApp` to false when performing
+ email-link authentication. If `handleCodeInApp` is set to false an invalid argument exception
+ is thrown (#931).
+
+- Adds support for passing the deep link (which is embedded in the sign-in link sent via email) to the
+ `signInWithEmail:link:completion:` and `isSignInWithEmailLink:` methods during an
+ email/link sign-in flow (#1023).
+
+# v4.5.0
+- Adds new API which provides a way to determine the sign-in methods associated with an
+ email address.
+- Adds new API which allows authentication using only an email link (Passwordless Authentication
+ with email link).
+
+# v4.4.4
+- Addresses CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF warnings that surface in newer versions of
+ Xcode and CocoaPods.
+- Improves FIRUser documentation with clear message explaining when Firebase Auth attempts to validate
+ users and what happens when an invalidated user is detected (#694) .
+
+# v4.4.3
+- Adds an explicit dependency on CoreGraphics from Firebase Auth.
+
+# v4.4.2
+- Fixes bug where the FIRAuthResult object returned following a Phone Number authentication
+ always contained a nil FIRAdditionalUserInfo object. Now the FIRAdditionalUserInfo object is
+ never nil and its newUser field is populated correctly.
+
# v4.4.0
- Adds new APIs which return an AuthDataResult object after successfully creating an
Email/Password user, signing in anonymously, signing in with Email/Password and signing
diff --git a/Firebase/Auth/FirebaseAuth.podspec b/Firebase/Auth/FirebaseAuth.podspec
index 8823238..8211bb0 100644
--- a/Firebase/Auth/FirebaseAuth.podspec
+++ b/Firebase/Auth/FirebaseAuth.podspec
@@ -45,6 +45,7 @@ Simplify your iOS development, grow your user base, and monetize more effectivel
'$(inherited) ' + 'FIRAuth_VERSION=' + s.version.to_s +
' FIRAuth_MINOR_VERSION=' + s.version.to_s.split(".")[0] + "." + s.version.to_s.split(".")[1]
}
+ s.framework = 'CoreGraphics'
s.framework = 'SafariServices'
s.framework = 'Security'
# s.dependency 'FirebaseCommunity/Core'
diff --git a/Firebase/Auth/Source/AuthProviders/EmailPassword/FIREmailAuthProvider.m b/Firebase/Auth/Source/AuthProviders/EmailPassword/FIREmailAuthProvider.m
index d27611e..7a871e2 100644
--- a/Firebase/Auth/Source/AuthProviders/EmailPassword/FIREmailAuthProvider.m
+++ b/Firebase/Auth/Source/AuthProviders/EmailPassword/FIREmailAuthProvider.m
@@ -32,4 +32,8 @@
return [[FIREmailPasswordAuthCredential alloc] initWithEmail:email password:password];
}
++ (FIRAuthCredential *)credentialWithEmail:(NSString *)email link:(NSString *)link {
+ return [[FIREmailPasswordAuthCredential alloc] initWithEmail:email link:link];
+}
+
@end
diff --git a/Firebase/Auth/Source/AuthProviders/EmailPassword/FIREmailPasswordAuthCredential.h b/Firebase/Auth/Source/AuthProviders/EmailPassword/FIREmailPasswordAuthCredential.h
index d50bf17..7625685 100644
--- a/Firebase/Auth/Source/AuthProviders/EmailPassword/FIREmailPasswordAuthCredential.h
+++ b/Firebase/Auth/Source/AuthProviders/EmailPassword/FIREmailPasswordAuthCredential.h
@@ -35,6 +35,11 @@ NS_ASSUME_NONNULL_BEGIN
*/
@property(nonatomic, readonly) NSString *password;
+/** @property link
+ @brief The email sign-in link.
+ */
+@property(nonatomic, readonly) NSString *link;
+
/** @fn initWithEmail:password:
@brief Designated initializer.
@param email The user's email address.
@@ -43,6 +48,14 @@ NS_ASSUME_NONNULL_BEGIN
- (nullable instancetype)initWithEmail:(NSString *)email password:(NSString *)password
NS_DESIGNATED_INITIALIZER;
+/** @fn initWithEmail:link:
+ @brief Designated initializer.
+ @param email The user's email address.
+ @param link The email sign-in link.
+ */
+- (nullable instancetype)initWithEmail:(NSString *)email link:(NSString *)link
+ NS_DESIGNATED_INITIALIZER;
+
@end
NS_ASSUME_NONNULL_END
diff --git a/Firebase/Auth/Source/AuthProviders/EmailPassword/FIREmailPasswordAuthCredential.m b/Firebase/Auth/Source/AuthProviders/EmailPassword/FIREmailPasswordAuthCredential.m
index 4361366..71cc330 100644
--- a/Firebase/Auth/Source/AuthProviders/EmailPassword/FIREmailPasswordAuthCredential.m
+++ b/Firebase/Auth/Source/AuthProviders/EmailPassword/FIREmailPasswordAuthCredential.m
@@ -43,6 +43,15 @@
return self;
}
+- (nullable instancetype)initWithEmail:(NSString *)email link:(NSString *)link {
+ self = [super initWithProvider:FIREmailAuthProviderID];
+ if (self) {
+ _email = [email copy];
+ _link = [link copy];
+ }
+ return self;
+}
+
- (void)prepareVerifyAssertionRequest:(FIRVerifyAssertionRequest *)request {
[FIRAuthExceptionUtils raiseMethodNotImplementedExceptionWithReason:
@"Attempt to call prepareVerifyAssertionRequest: on a FIREmailPasswordAuthCredential."];
diff --git a/Firebase/Auth/Source/AuthProviders/Phone/FIRPhoneAuthProvider.m b/Firebase/Auth/Source/AuthProviders/Phone/FIRPhoneAuthProvider.m
index 4a0120b..a44a340 100644
--- a/Firebase/Auth/Source/AuthProviders/Phone/FIRPhoneAuthProvider.m
+++ b/Firebase/Auth/Source/AuthProviders/Phone/FIRPhoneAuthProvider.m
@@ -166,11 +166,11 @@ NSString *const kReCAPTCHAURLStringFormat = @"https://%@/__/auth/handler?%@";
FIRAuthURLCallbackMatcher callbackMatcher = ^BOOL(NSURL *_Nullable callbackURL) {
return [self isVerifyAppURL:callbackURL eventID:eventID];
};
- [_auth.authURLPresenter presentURL:reCAPTCHAURL
- UIDelegate:UIDelegate
- callbackMatcher:callbackMatcher
- completion:^(NSURL *_Nullable callbackURL,
- NSError *_Nullable error) {
+ [self->_auth.authURLPresenter presentURL:reCAPTCHAURL
+ UIDelegate:UIDelegate
+ callbackMatcher:callbackMatcher
+ completion:^(NSURL *_Nullable callbackURL,
+ NSError *_Nullable error) {
if (error) {
callBackOnMainThread(nil, error);
return;
@@ -185,7 +185,8 @@ NSString *const kReCAPTCHAURLStringFormat = @"https://%@/__/auth/handler?%@";
[[FIRSendVerificationCodeRequest alloc] initWithPhoneNumber:phoneNumber
appCredential:nil
reCAPTCHAToken:reCAPTCHAToken
- requestConfiguration:_auth.requestConfiguration];
+ requestConfiguration:
+ self->_auth.requestConfiguration];
[FIRAuthBackend sendVerificationCode:request
callback:^(FIRSendVerificationCodeResponse
*_Nullable response, NSError *_Nullable error) {
@@ -361,14 +362,15 @@ NSString *const kReCAPTCHAURLStringFormat = @"https://%@/__/auth/handler?%@";
[[FIRSendVerificationCodeRequest alloc] initWithPhoneNumber:phoneNumber
appCredential:appCredential
reCAPTCHAToken:nil
- requestConfiguration:_auth.requestConfiguration];
+ requestConfiguration:
+ self->_auth.requestConfiguration];
[FIRAuthBackend sendVerificationCode:request
callback:^(FIRSendVerificationCodeResponse *_Nullable response,
NSError *_Nullable error) {
if (error) {
if (error.code == FIRAuthErrorCodeInvalidAppCredential) {
if (retryOnInvalidAppCredential) {
- [_auth.appCredentialManager clearCredential];
+ [self->_auth.appCredentialManager clearCredential];
[self verifyClientAndSendVerificationCodeToPhoneNumber:phoneNumber
retryOnInvalidAppCredential:NO
callback:callback];
@@ -404,7 +406,7 @@ NSString *const kReCAPTCHAURLStringFormat = @"https://%@/__/auth/handler?%@";
FIRVerifyClientRequest *request =
[[FIRVerifyClientRequest alloc] initWithAppToken:token.string
isSandbox:token.type == FIRAuthAPNSTokenTypeSandbox
- requestConfiguration:_auth.requestConfiguration];
+ requestConfiguration:self->_auth.requestConfiguration];
[FIRAuthBackend verifyClient:request callback:^(FIRVerifyClientResponse *_Nullable response,
NSError *_Nullable error) {
if (error) {
@@ -412,7 +414,7 @@ NSString *const kReCAPTCHAURLStringFormat = @"https://%@/__/auth/handler?%@";
return;
}
NSTimeInterval timeout = [response.suggestedTimeOutDate timeIntervalSinceNow];
- [_auth.appCredentialManager
+ [self->_auth.appCredentialManager
didStartVerificationWithReceipt:response.receipt
timeout:timeout
callback:^(FIRAuthAppCredential *credential) {
@@ -442,8 +444,8 @@ NSString *const kReCAPTCHAURLStringFormat = @"https://%@/__/auth/handler?%@";
return;
}
NSString *bundleID = [NSBundle mainBundle].bundleIdentifier;
- NSString *clienID = _auth.app.options.clientID;
- NSString *apiKey = _auth.requestConfiguration.APIKey;
+ NSString *clienID = self->_auth.app.options.clientID;
+ NSString *apiKey = self->_auth.requestConfiguration.APIKey;
NSMutableDictionary *urlArguments = [[NSMutableDictionary alloc] initWithDictionary: @{
@"apiKey" : apiKey,
@"authType" : kAuthTypeVerifyApp,
@@ -452,8 +454,8 @@ NSString *const kReCAPTCHAURLStringFormat = @"https://%@/__/auth/handler?%@";
@"v" : [FIRAuthBackend authUserAgent],
@"eventId" : eventID,
}];
- if (_auth.requestConfiguration.languageCode) {
- urlArguments[@"hl"] = _auth.requestConfiguration.languageCode;
+ if (self->_auth.requestConfiguration.languageCode) {
+ urlArguments[@"hl"] = self->_auth.requestConfiguration.languageCode;
}
NSString *argumentsString = [urlArguments gtm_httpArgumentsString];
NSString *URLString =
diff --git a/Firebase/Auth/Source/FIRAuth.m b/Firebase/Auth/Source/FIRAuth.m
index ad363e0..1930957 100644
--- a/Firebase/Auth/Source/FIRAuth.m
+++ b/Firebase/Auth/Source/FIRAuth.m
@@ -39,6 +39,8 @@
#import "FIRAuthRequestConfiguration.h"
#import "FIRCreateAuthURIRequest.h"
#import "FIRCreateAuthURIResponse.h"
+#import "FIREmailLinkSignInRequest.h"
+#import "FIREmailLinkSignInResponse.h"
#import "FIRGetOOBConfirmationCodeRequest.h"
#import "FIRGetOOBConfirmationCodeResponse.h"
#import "FIRResetPasswordRequest.h"
@@ -102,6 +104,18 @@ static NSString *const kUserKey = @"%@_firebase_user";
static NSString *const kMissingEmailInvalidParameterExceptionReason =
@"The email used to initiate password reset cannot be nil.";
+/** @var kHandleCodeInAppFalseExceptionReason
+ @brief The reason for @c invalidParameterException when the handleCodeInApp parameter is false
+ on the ActionCodeSettings object used to send the link for Email-link Authentication.
+ */
+static NSString *const kHandleCodeInAppFalseExceptionReason =
+ @"You must set handleCodeInApp in your ActionCodeSettings to true for Email-link "
+ "Authentication.";
+
+static NSString *const kInvalidEmailSignInLinkExceptionMessage =
+ @"The link provided is not valid for email/link sign-in. Please check the link by calling "
+ "isSignInWithEmailLink:link: on Auth before attempting to use it for email/link sign-in.";
+
/** @var kPasswordResetRequestType
@brief The action code type value for resetting password in the check action code response.
*/
@@ -117,6 +131,11 @@ static NSString *const kVerifyEmailRequestType = @"VERIFY_EMAIL";
*/
static NSString *const kRecoverEmailRequestType = @"RECOVER_EMAIL";
+/** @var kEmailLinkSignInRequestType
+ @brief The action code type value for an email sign-in link in the check action code response.
+*/
+static NSString *const kEmailLinkSignInRequestType = @"EMAIL_SIGNIN";
+
/** @var kMissingPasswordReason
@brief The reason why the @c FIRAuthErrorCodeWeakPassword error is thrown.
@remarks This error message will be localized in the future.
@@ -186,6 +205,9 @@ static NSMutableDictionary *gKeychainServiceNameForAppName;
if ([requestType isEqualToString:kRecoverEmailRequestType]) {
return FIRActionCodeOperationRecoverEmail;
}
+ if ([requestType isEqualToString:kEmailLinkSignInRequestType]) {
+ return FIRActionCodeOperationEmailLink;
+ }
return FIRActionCodeOperationUnknown;
}
@@ -436,7 +458,7 @@ static NSMutableDictionary *gKeychainServiceNameForAppName;
NSError *error;
if ([strongSelf getUser:&user error:&error]) {
[strongSelf updateCurrentUser:user byForce:NO savingToDisk:NO error:&error];
- _lastNotifiedUserToken = user.rawAccessToken;
+ self->_lastNotifiedUserToken = user.rawAccessToken;
} else {
FIRLogError(kFIRLoggerAuth, @"I-AUT000001",
@"Error loading saved user when starting up: %@", error);
@@ -486,7 +508,7 @@ static NSMutableDictionary *gKeychainServiceNameForAppName;
- (FIRUser *)currentUser {
__block FIRUser *result;
dispatch_sync(FIRAuthGlobalWorkQueue(), ^{
- result = _currentUser;
+ result = self->_currentUser;
});
return result;
}
@@ -497,7 +519,7 @@ static NSMutableDictionary *gKeychainServiceNameForAppName;
FIRCreateAuthURIRequest *request =
[[FIRCreateAuthURIRequest alloc] initWithIdentifier:email
continueURI:@"http://www.google.com/"
- requestConfiguration:_requestConfiguration];
+ requestConfiguration:self->_requestConfiguration];
[FIRAuthBackend createAuthURI:request callback:^(FIRCreateAuthURIResponse *_Nullable response,
NSError *_Nullable error) {
if (completion) {
@@ -509,6 +531,24 @@ static NSMutableDictionary *gKeychainServiceNameForAppName;
});
}
+- (void)fetchSignInMethodsForEmail:(nonnull NSString *)email
+ completion:(nullable FIRSignInMethodQueryCallback)completion {
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^{
+ FIRCreateAuthURIRequest *request =
+ [[FIRCreateAuthURIRequest alloc] initWithIdentifier:email
+ continueURI:@"http://www.google.com/"
+ requestConfiguration:self->_requestConfiguration];
+ [FIRAuthBackend createAuthURI:request callback:^(FIRCreateAuthURIResponse *_Nullable response,
+ NSError *_Nullable error) {
+ if (completion) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ completion(response.signinMethods, error);
+ });
+ }
+ }];
+ });
+}
+
- (void)signInWithEmail:(NSString *)email
password:(NSString *)password
completion:(FIRAuthResultCallback)completion {
@@ -524,6 +564,23 @@ static NSMutableDictionary *gKeychainServiceNameForAppName;
});
}
+- (void)signInWithEmail:(NSString *)email
+ link:(NSString *)link
+ completion:(FIRAuthDataResultCallback)completion {
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^{
+ FIRAuthDataResultCallback decoratedCallback =
+ [self signInFlowAuthDataResultCallbackByDecoratingCallback:completion];
+ FIREmailPasswordAuthCredential *credential =
+ [[FIREmailPasswordAuthCredential alloc] initWithEmail:email link:link];
+ [self internalSignInAndRetrieveDataWithCredential:credential
+ isReauthentication:NO
+ callback:^(FIRAuthDataResult *_Nullable authResult,
+ NSError *_Nullable error) {
+ decoratedCallback(authResult, error);
+ }];
+ });
+}
+
/** @fn signInWithEmail:password:callback:
@brief Signs in using an email address and password.
@param email The user's email address.
@@ -536,6 +593,7 @@ static NSMutableDictionary *gKeychainServiceNameForAppName;
- (void)signInWithEmail:(NSString *)email
password:(NSString *)password
callback:(FIRAuthResultCallback)callback {
+
FIRVerifyPasswordRequest *request =
[[FIRVerifyPasswordRequest alloc] initWithEmail:email
password:password
@@ -591,6 +649,48 @@ static NSMutableDictionary *gKeychainServiceNameForAppName;
callback:completion];
}
+/** @fn internalSignInWithEmail:link:completion:
+ @brief Signs in using an email and email sign-in link.
+ @param email The user's email address.
+ @param link The email sign-in link.
+ @param callback A block which is invoked when the sign in finishes (or is cancelled.) Invoked
+ asynchronously on the global auth work queue in the future.
+ */
+- (void)internalSignInWithEmail:(nonnull NSString *)email
+ link:(nonnull NSString *)link
+ callback:(nullable FIRAuthResultCallback)callback {
+ if (![self isSignInWithEmailLink:link]) {
+ [FIRAuthExceptionUtils raiseInvalidParameterExceptionWithReason:
+ kInvalidEmailSignInLinkExceptionMessage];
+ return;
+ }
+ NSDictionary<NSString *, NSString *> *queryItems = FIRAuthParseURL(link);
+ if (![queryItems count]) {
+ NSURLComponents *urlComponents = [NSURLComponents componentsWithString:link];
+ queryItems = FIRAuthParseURL(urlComponents.query);
+ }
+ NSString *actionCode = queryItems[@"oobCode"];
+
+ FIREmailLinkSignInRequest *request =
+ [[FIREmailLinkSignInRequest alloc] initWithEmail:email
+ oobCode:actionCode
+ requestConfiguration:_requestConfiguration];
+
+ [FIRAuthBackend emailLinkSignin:request
+ callback:^(FIREmailLinkSignInResponse *_Nullable response,
+ NSError *_Nullable error) {
+ if (error) {
+ callback(nil, error);
+ return;
+ }
+ [self completeSignInWithAccessToken:response.IDToken
+ accessTokenExpirationDate:response.approximateExpirationDate
+ refreshToken:response.refreshToken
+ anonymous:NO
+ callback:callback];
+ }];
+}
+
- (void)signInWithCredential:(FIRAuthCredential *)credential
completion:(FIRAuthResultCallback)completion {
dispatch_async(FIRAuthGlobalWorkQueue(), ^{
@@ -628,24 +728,31 @@ static NSMutableDictionary *gKeychainServiceNameForAppName;
// Special case for email/password credentials
FIREmailPasswordAuthCredential *emailPasswordCredential =
(FIREmailPasswordAuthCredential *)credential;
- [self signInWithEmail:emailPasswordCredential.email
- password:emailPasswordCredential.password
- callback:^(FIRUser *_Nullable user, NSError *_Nullable error) {
+ FIRAuthResultCallback completeEmailSignIn = ^(FIRUser *user, NSError *error) {
if (callback) {
if (error) {
callback(nil, error);
return;
}
- FIRAdditionalUserInfo *additionalUserInfo =
- [[FIRAdditionalUserInfo alloc] initWithProviderID:FIREmailAuthProviderID
- profile:nil
- username:nil
- isNewUser:NO];
+ FIRAdditionalUserInfo *additionalUserInfo =
+ [[FIRAdditionalUserInfo alloc] initWithProviderID:FIREmailAuthProviderID
+ profile:nil
+ username:nil
+ isNewUser:NO];
FIRAuthDataResult *result = [[FIRAuthDataResult alloc] initWithUser:user
additionalUserInfo:additionalUserInfo];
- callback(result, nil);
+ callback(result, error);
}
- }];
+ };
+ if (emailPasswordCredential.link) {
+ [self internalSignInWithEmail:emailPasswordCredential.email
+ link:emailPasswordCredential.link
+ callback:completeEmailSignIn];
+ } else {
+ [self signInWithEmail:emailPasswordCredential.email
+ password:emailPasswordCredential.password
+ callback:completeEmailSignIn];
+ }
return;
}
@@ -657,12 +764,29 @@ static NSMutableDictionary *gKeychainServiceNameForAppName;
isReauthentication ? FIRAuthOperationTypeReauth : FIRAuthOperationTypeSignUpOrSignIn;
[self signInWithPhoneCredential:phoneCredential
operation:operation
- callback:^(FIRUser *_Nullable user,
+ callback:^(FIRVerifyPhoneNumberResponse *_Nullable response,
NSError *_Nullable error) {
if (callback) {
- FIRAuthDataResult *result = user ?
- [[FIRAuthDataResult alloc] initWithUser:user additionalUserInfo:nil] : nil;
- callback(result, error);
+ if (error) {
+ callback(nil, error);
+ return;
+ }
+
+ [self completeSignInWithAccessToken:response.IDToken
+ accessTokenExpirationDate:response.approximateExpirationDate
+ refreshToken:response.refreshToken
+ anonymous:NO
+ callback:^(FIRUser *_Nullable user, NSError *_Nullable error) {
+ FIRAdditionalUserInfo *additionalUserInfo =
+ [[FIRAdditionalUserInfo alloc] initWithProviderID:FIRPhoneAuthProviderID
+ profile:nil
+ username:nil
+ isNewUser:response.isNewUser];
+ FIRAuthDataResult *result = user ?
+ [[FIRAuthDataResult alloc] initWithUser:user
+ additionalUserInfo:additionalUserInfo] : nil;
+ callback(result, error);
+ }];
}
}];
return;
@@ -726,14 +850,14 @@ static NSMutableDictionary *gKeychainServiceNameForAppName;
dispatch_async(FIRAuthGlobalWorkQueue(), ^{
FIRAuthDataResultCallback decoratedCallback =
[self signInFlowAuthDataResultCallbackByDecoratingCallback:completion];
- if (_currentUser.anonymous) {
+ if (self->_currentUser.anonymous) {
FIRAdditionalUserInfo *additionalUserInfo =
[[FIRAdditionalUserInfo alloc] initWithProviderID:nil
profile:nil
username:nil
isNewUser:NO];
FIRAuthDataResult *authDataResult =
- [[FIRAuthDataResult alloc] initWithUser:_currentUser
+ [[FIRAuthDataResult alloc] initWithUser:self->_currentUser
additionalUserInfo:additionalUserInfo];
decoratedCallback(authDataResult, nil);
return;
@@ -767,8 +891,8 @@ static NSMutableDictionary *gKeychainServiceNameForAppName;
dispatch_async(FIRAuthGlobalWorkQueue(), ^{
FIRAuthResultCallback decoratedCallback =
[self signInFlowAuthResultCallbackByDecoratingCallback:completion];
- if (_currentUser.anonymous) {
- decoratedCallback(_currentUser, nil);
+ if (self->_currentUser.anonymous) {
+ decoratedCallback(self->_currentUser, nil);
return;
}
[self internalSignInAnonymouslyWithCompletion:^(FIRSignUpNewUserResponse *_Nullable response,
@@ -872,7 +996,7 @@ static NSMutableDictionary *gKeychainServiceNameForAppName;
FIRResetPasswordRequest *request =
[[FIRResetPasswordRequest alloc] initWithOobCode:code
newPassword:newPassword
- requestConfiguration:_requestConfiguration];
+ requestConfiguration:self->_requestConfiguration];
[FIRAuthBackend resetPassword:request callback:^(FIRResetPasswordResponse *_Nullable response,
NSError *_Nullable error) {
if (completion) {
@@ -893,7 +1017,7 @@ static NSMutableDictionary *gKeychainServiceNameForAppName;
FIRResetPasswordRequest *request =
[[FIRResetPasswordRequest alloc] initWithOobCode:code
newPassword:nil
- requestConfiguration:_requestConfiguration];
+ requestConfiguration:self->_requestConfiguration];
[FIRAuthBackend resetPassword:request callback:^(FIRResetPasswordResponse *_Nullable response,
NSError *_Nullable error) {
if (completion) {
@@ -934,7 +1058,7 @@ static NSMutableDictionary *gKeychainServiceNameForAppName;
- (void)applyActionCode:(NSString *)code completion:(FIRApplyActionCodeCallback)completion {
dispatch_async(FIRAuthGlobalWorkQueue(), ^ {
FIRSetAccountInfoRequest *request =
- [[FIRSetAccountInfoRequest alloc] initWithRequestConfiguration:_requestConfiguration];
+ [[FIRSetAccountInfoRequest alloc] initWithRequestConfiguration:self->_requestConfiguration];
request.OOBCode = code;
[FIRAuthBackend setAccountInfo:request callback:^(FIRSetAccountInfoResponse *_Nullable response,
NSError *_Nullable error) {
@@ -982,7 +1106,8 @@ static NSMutableDictionary *gKeychainServiceNameForAppName;
FIRGetOOBConfirmationCodeRequest *request =
[FIRGetOOBConfirmationCodeRequest passwordResetRequestWithEmail:email
actionCodeSettings:actionCodeSettings
- requestConfiguration:_requestConfiguration];
+ requestConfiguration:self->_requestConfiguration
+ ];
[FIRAuthBackend getOOBConfirmationCode:request
callback:^(FIRGetOOBConfirmationCodeResponse *_Nullable response,
NSError *_Nullable error) {
@@ -995,10 +1120,86 @@ static NSMutableDictionary *gKeychainServiceNameForAppName;
});
}
+- (void)sendSignInLinkToEmail:(nonnull NSString *)email
+ actionCodeSettings:(nonnull FIRActionCodeSettings *)actionCodeSettings
+ completion:(nullable FIRSendSignInLinkToEmailCallback)completion {
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^{
+ if (!email) {
+ [FIRAuthExceptionUtils raiseInvalidParameterExceptionWithReason:
+ kMissingEmailInvalidParameterExceptionReason];
+ }
+
+ if (!actionCodeSettings.handleCodeInApp) {
+ [FIRAuthExceptionUtils raiseInvalidParameterExceptionWithReason:
+ kHandleCodeInAppFalseExceptionReason];
+ }
+ FIRGetOOBConfirmationCodeRequest *request =
+ [FIRGetOOBConfirmationCodeRequest signInWithEmailLinkRequest:email
+ actionCodeSettings:actionCodeSettings
+ requestConfiguration:self->_requestConfiguration];
+ [FIRAuthBackend getOOBConfirmationCode:request
+ callback:^(FIRGetOOBConfirmationCodeResponse *_Nullable response,
+ NSError *_Nullable error) {
+ if (completion) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ completion(error);
+ });
+ }
+ }];
+ });
+}
+
+- (void)updateCurrentUser:(FIRUser *)user completion:(nullable FIRUserUpdateCallback)completion {
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^{
+ if (!user) {
+ if (completion) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ completion([FIRAuthErrorUtils nullUserErrorWithMessage:nil]);
+ });
+ }
+ return;
+ }
+ void (^updateUserBlock)(FIRUser *user) = ^(FIRUser *user) {
+ NSError *error;
+ [self updateCurrentUser:user byForce:YES savingToDisk:YES error:(&error)];
+ if (error) {
+ if (completion) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ completion(error);
+ });
+ }
+ return;
+ } if (completion) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ completion(nil);
+ });
+ }
+ };
+ if (![user.requestConfiguration.APIKey isEqualToString:self->_requestConfiguration.APIKey]) {
+ // If the API keys are different, then we need to confirm that the user belongs to the same
+ // project before proceeding.
+ user.requestConfiguration = self->_requestConfiguration;
+ [user reloadWithCompletion:^(NSError *_Nullable error) {
+ if (error) {
+ if (completion) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ completion(error);
+ });
+ }
+ return;
+ }
+ updateUserBlock(user);
+ }];
+ } else {
+ updateUserBlock(user);
+ }
+ });
+}
+
- (BOOL)signOut:(NSError *_Nullable __autoreleasing *_Nullable)error {
__block BOOL result = YES;
dispatch_sync(FIRAuthGlobalWorkQueue(), ^{
- if (!_currentUser) {
+ if (!self->_currentUser) {
return;
}
result = [self updateCurrentUser:nil byForce:NO savingToDisk:YES error:error];
@@ -1013,6 +1214,60 @@ static NSMutableDictionary *gKeychainServiceNameForAppName;
return [self updateCurrentUser:nil byForce:YES savingToDisk:YES error:error];
}
+- (BOOL)isSignInWithEmailLink:(NSString *)link {
+ if (link.length == 0) {
+ return NO;
+ }
+ NSDictionary<NSString *, NSString *> *queryItems = FIRAuthParseURL(link);
+ if (![queryItems count]) {
+ NSURLComponents *urlComponents = [NSURLComponents componentsWithString:link];
+ if (!urlComponents.query) {
+ return NO;
+ }
+ queryItems = FIRAuthParseURL(urlComponents.query);
+ }
+
+ if (![queryItems count]) {
+ return NO;
+ }
+
+ NSString *actionCode = queryItems[@"oobCode"];
+ NSString *mode = queryItems[@"mode"];
+
+ if (actionCode && [mode isEqualToString:@"signIn"]) {
+ return YES;
+ }
+ return NO;
+}
+
+/** @fn FIRAuthParseURL:NSString
+ @brief Parses an incoming URL into all available query items.
+ @param urlString The url to be parsed.
+ @return A dictionary of available query items in the target URL.
+ */
+static NSDictionary<NSString *, NSString *> *FIRAuthParseURL(NSString *urlString) {
+ NSString *linkURL = [NSURLComponents componentsWithString:urlString].query;
+ if (!linkURL) {
+ return @{};
+ }
+ NSArray<NSString *> *URLComponents = [linkURL componentsSeparatedByString:@"&"];
+ NSMutableDictionary<NSString *, NSString *> *queryItems =
+ [[NSMutableDictionary alloc] initWithCapacity:URLComponents.count];
+ for (NSString *component in URLComponents) {
+ NSRange equalRange = [component rangeOfString:@"="];
+ if (equalRange.location != NSNotFound) {
+ NSString *queryItemKey =
+ [[component substringToIndex:equalRange.location] stringByRemovingPercentEncoding];
+ NSString *queryItemValue =
+ [[component substringFromIndex:equalRange.location + 1] stringByRemovingPercentEncoding];
+ if (queryItemKey && queryItemValue) {
+ queryItems[queryItemKey] = queryItemValue;
+ }
+ }
+ }
+ return queryItems;
+}
+
- (FIRAuthStateDidChangeListenerHandle)addAuthStateDidChangeListener:
(FIRAuthStateDidChangeListenerBlock)listener {
__block BOOL firstInvocation = YES;
@@ -1051,7 +1306,7 @@ static NSMutableDictionary *gKeychainServiceNameForAppName;
[_listenerHandles addObject:handle];
}
dispatch_async(dispatch_get_main_queue(), ^{
- listener(self, self.currentUser);
+ listener(self, self->_currentUser);
});
return handle;
}
@@ -1065,7 +1320,8 @@ static NSMutableDictionary *gKeychainServiceNameForAppName;
- (void)useAppLanguage {
dispatch_sync(FIRAuthGlobalWorkQueue(), ^{
- _requestConfiguration.languageCode = [NSBundle mainBundle].preferredLocalizations.firstObject;
+ self->_requestConfiguration.languageCode =
+ [NSBundle mainBundle].preferredLocalizations.firstObject;
});
}
@@ -1075,17 +1331,17 @@ static NSMutableDictionary *gKeychainServiceNameForAppName;
- (void)setLanguageCode:(nullable NSString *)languageCode {
dispatch_sync(FIRAuthGlobalWorkQueue(), ^{
- _requestConfiguration.languageCode = [languageCode copy];
+ self->_requestConfiguration.languageCode = [languageCode copy];
});
}
- (NSString *)additionalFrameworkMarker {
- return _requestConfiguration.additionalFrameworkMarker;
+ return self->_requestConfiguration.additionalFrameworkMarker;
}
- (void)setAdditionalFrameworkMarker:(NSString *)additionalFrameworkMarker {
dispatch_sync(FIRAuthGlobalWorkQueue(), ^{
- _requestConfiguration.additionalFrameworkMarker = [additionalFrameworkMarker copy];
+ self->_requestConfiguration.additionalFrameworkMarker = [additionalFrameworkMarker copy];
});
}
@@ -1093,7 +1349,7 @@ static NSMutableDictionary *gKeychainServiceNameForAppName;
- (NSData *)APNSToken {
__block NSData *result = nil;
dispatch_sync(FIRAuthGlobalWorkQueue(), ^{
- result = _tokenManager.token.data;
+ result = self->_tokenManager.token.data;
});
return result;
}
@@ -1104,20 +1360,20 @@ static NSMutableDictionary *gKeychainServiceNameForAppName;
- (void)setAPNSToken:(NSData *)token type:(FIRAuthAPNSTokenType)type {
dispatch_sync(FIRAuthGlobalWorkQueue(), ^{
- _tokenManager.token = [[FIRAuthAPNSToken alloc] initWithData:token type:type];
+ self->_tokenManager.token = [[FIRAuthAPNSToken alloc] initWithData:token type:type];
});
}
- (void)handleAPNSTokenError:(NSError *)error {
dispatch_sync(FIRAuthGlobalWorkQueue(), ^{
- [_tokenManager cancelWithError:error];
+ [self->_tokenManager cancelWithError:error];
});
}
- (BOOL)canHandleNotification:(NSDictionary *)userInfo {
__block BOOL result = NO;
dispatch_sync(FIRAuthGlobalWorkQueue(), ^{
- result = [_notificationManager canHandleNotification:userInfo];
+ result = [self->_notificationManager canHandleNotification:userInfo];
});
return result;
}
@@ -1125,7 +1381,7 @@ static NSMutableDictionary *gKeychainServiceNameForAppName;
- (BOOL)canHandleURL:(NSURL *)URL {
__block BOOL result = NO;
dispatch_sync(FIRAuthGlobalWorkQueue(), ^{
- result = [_authURLPresenter canHandleURL:URL];
+ result = [self->_authURLPresenter canHandleURL:URL];
});
return result;
}
@@ -1143,14 +1399,14 @@ static NSMutableDictionary *gKeychainServiceNameForAppName;
*/
- (void)signInWithPhoneCredential:(FIRPhoneAuthCredential *)credential
operation:(FIRAuthOperationType)operation
- callback:(FIRAuthResultCallback)callback {
+ callback:(FIRVerifyPhoneNumberResponseCallback)callback {
if (credential.temporaryProof.length && credential.phoneNumber.length) {
FIRVerifyPhoneNumberRequest *request =
[[FIRVerifyPhoneNumberRequest alloc] initWithTemporaryProof:credential.temporaryProof
phoneNumber:credential.phoneNumber
operation:operation
requestConfiguration:_requestConfiguration];
- [self phoneNumberSignInWithRequest:request callback:callback];
+ [FIRAuthBackend verifyPhoneNumber:request callback:callback];
return;
}
@@ -1167,32 +1423,9 @@ static NSMutableDictionary *gKeychainServiceNameForAppName;
verificationCode:credential.verificationCode
operation:operation
requestConfiguration:_requestConfiguration];
- [self phoneNumberSignInWithRequest:request callback:callback];
+ [FIRAuthBackend verifyPhoneNumber:request callback:callback];
}
-
-/** @fn phoneNumberSignInWithVerificationID:pasverificationCodesword:callback:
- @brief Signs in using a FIRVerifyPhoneNumberRequest object.
- @param request THe FIRVerifyPhoneNumberRequest request object.
- @param callback A block which is invoked when the sign in finishes (or is cancelled.) Invoked
- asynchronously on the global auth work queue in the future.
- */
-- (void)phoneNumberSignInWithRequest:(FIRVerifyPhoneNumberRequest *)request
- callback:(FIRAuthResultCallback)callback {
- [FIRAuthBackend verifyPhoneNumber:request
- callback:^(FIRVerifyPhoneNumberResponse *_Nullable response,
- NSError *_Nullable error) {
- if (error) {
- callback(nil, error);
- return;
- }
- [self completeSignInWithAccessToken:response.IDToken
- accessTokenExpirationDate:response.approximateExpirationDate
- refreshToken:response.refreshToken
- anonymous:NO
- callback:callback];
- }];
-}
#endif
/** @fn internalSignInAndRetrieveDataWithCustomToken:completion:
diff --git a/Firebase/Auth/Source/FIRAuthAPNSTokenManager.m b/Firebase/Auth/Source/FIRAuthAPNSTokenManager.m
index 7775305..2b39aef 100644
--- a/Firebase/Auth/Source/FIRAuthAPNSTokenManager.m
+++ b/Firebase/Auth/Source/FIRAuthAPNSTokenManager.m
@@ -68,12 +68,14 @@ static const NSTimeInterval kLegacyRegistrationTimeout = 30;
_pendingCallbacks =
[[NSMutableArray<FIRAuthAPNSTokenCallback> alloc] initWithObjects:callback, nil];
dispatch_async(dispatch_get_main_queue(), ^{
- if ([_application respondsToSelector:@selector(registerForRemoteNotifications)]) {
- [_application registerForRemoteNotifications];
+ if ([self->_application respondsToSelector:@selector(registerForRemoteNotifications)]) {
+ [self->_application registerForRemoteNotifications];
} else {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
- [_application registerForRemoteNotificationTypes:UIRemoteNotificationTypeAlert];
+#if TARGET_OS_IOS
+ [self->_application registerForRemoteNotificationTypes:UIRemoteNotificationTypeAlert];
+#endif // TARGET_OS_IOS
#pragma clang diagnostic pop
}
});
@@ -81,7 +83,7 @@ static const NSTimeInterval kLegacyRegistrationTimeout = 30;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_timeout * NSEC_PER_SEC)),
FIRAuthGlobalWorkQueue(), ^{
// Only cancel if the pending callbacks remain the same, i.e., not triggered yet.
- if (applicableCallbacks == _pendingCallbacks) {
+ if (applicableCallbacks == self->_pendingCallbacks) {
[self callBackWithToken:nil error:nil];
}
});
diff --git a/Firebase/Auth/Source/FIRAuthErrorUtils.h b/Firebase/Auth/Source/FIRAuthErrorUtils.h
index c3fad0d..5b8205f 100644
--- a/Firebase/Auth/Source/FIRAuthErrorUtils.h
+++ b/Firebase/Auth/Source/FIRAuthErrorUtils.h
@@ -481,6 +481,13 @@ NS_ASSUME_NONNULL_BEGIN
*/
+ (NSError *)URLResponseErrorWithCode:(NSString *)code message:(nullable NSString *)message;
+/** @fn nullUserErrorWithMessage:
+ @brief Constructs an @c NSError with the code and message provided.
+ @param message Error message from the backend, if any.
+ @return The nullable NSError instance associated with the given error message, if one is found.
+ */
++ (NSError *)nullUserErrorWithMessage:(nullable NSString *)message;
+
/** @fn keychainErrorWithFunction:status:
@brief Constructs an @c NSError with the @c FIRAuthErrorCodeKeychainError code.
@param keychainFunction The keychain function which was invoked and yielded an unexpected
diff --git a/Firebase/Auth/Source/FIRAuthErrorUtils.m b/Firebase/Auth/Source/FIRAuthErrorUtils.m
index 3748048..f4dbb94 100644
--- a/Firebase/Auth/Source/FIRAuthErrorUtils.m
+++ b/Firebase/Auth/Source/FIRAuthErrorUtils.m
@@ -139,8 +139,9 @@ static NSString *const kFIRAuthErrorMessageNoSuchProvider = @"User was not linke
/** @var kFIRAuthErrorMessageInvalidUserToken
@brief Message for @c FIRAuthErrorCodeInvalidUserToken error code.
*/
-static NSString *const kFIRAuthErrorMessageInvalidUserToken = @"The user's credential is no longer "
- "valid. The user must sign in again.";
+static NSString *const kFIRAuthErrorMessageInvalidUserToken = @"This user's credential isn't valid "
+ "for this project. This can happen if the user's token has been tampered with, or if the user "
+ "doesn’t belong to the project associated with the API key used in your request.";
/** @var kFIRAuthErrorMessageNetworkError
@brief Message for @c FIRAuthErrorCodeNetworkError error code.
@@ -400,6 +401,12 @@ static NSString *const kFIRAuthErrorMessageWebInternalError = @"An internal erro
static NSString *const kFIRAuthErrorMessageAppVerificationUserInteractionFailure = @"The app "
"verification process has failed, print and inspect the error details for more information";
+/** @var kFIRAuthErrorMessageNullUser
+ @brief Message for @c FIRAuthErrorCodeNullUser error code.
+ */
+static NSString *const kFIRAuthErrorMessageNullUser = @"A null user object was provided as the "
+ "argument for an operation which requires a non-null user object.";
+
/** @var kFIRAuthErrorMessageInternalError
@brief Message for @c FIRAuthErrorCodeInternalError error code.
*/
@@ -520,6 +527,8 @@ static NSString *FIRAuthErrorDescription(FIRAuthErrorCode code) {
return kFIRAuthErrorMessageAppVerificationUserInteractionFailure;
case FIRAuthErrorCodeWebNetworkRequestFailed:
return kFIRAuthErrorMessageWebRequestFailed;
+ case FIRAuthErrorCodeNullUser:
+ return kFIRAuthErrorMessageNullUser;
case FIRAuthErrorCodeWebInternalError:
return kFIRAuthErrorMessageWebInternalError;
}
@@ -639,6 +648,8 @@ static NSString *const FIRAuthErrorCodeString(FIRAuthErrorCode code) {
return @"ERROR_APP_VERIFICATION_FAILED";
case FIRAuthErrorCodeWebNetworkRequestFailed:
return @"ERROR_WEB_NETWORK_REQUEST_FAILED";
+ case FIRAuthErrorCodeNullUser:
+ return @"ERROR_NULL_USER";
case FIRAuthErrorCodeWebInternalError:
return @"ERROR_WEB_INTERNAL_ERROR";
}
@@ -981,6 +992,10 @@ static NSString *const FIRAuthErrorCodeString(FIRAuthErrorCode code) {
return nil;
}
++ (NSError *)nullUserErrorWithMessage:(nullable NSString *)message {
+ return [self errorWithCode:FIRAuthInternalErrorCodeNullUser message:message];
+}
+
+ (NSError *)keychainErrorWithFunction:(NSString *)keychainFunction status:(OSStatus)status {
NSString *failureReason = [NSString stringWithFormat:@"%@ (%li)", keychainFunction, (long)status];
return [self errorWithCode:FIRAuthInternalErrorCodeKeychainError userInfo:@{
diff --git a/Firebase/Auth/Source/FIRAuthInternalErrors.h b/Firebase/Auth/Source/FIRAuthInternalErrors.h
index d2905cc..fd08022 100644
--- a/Firebase/Auth/Source/FIRAuthInternalErrors.h
+++ b/Firebase/Auth/Source/FIRAuthInternalErrors.h
@@ -370,6 +370,12 @@ typedef NS_ENUM(NSInteger, FIRAuthInternalErrorCode) {
FIRAuthInternalErrorCodeAppNotVerified =
FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeAppNotVerified,
+ /** Indicates that a non-null user was expected as an argmument to the operation but a null
+ user was provided.
+ */
+ FIRAuthInternalErrorCodeNullUser =
+ FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeNullUser,
+
/** @var FIRAuthInternalErrorCodeRPCRequestEncodingError
@brief Indicates an error encoding the RPC request.
@remarks This is typically due to some sort of unexpected input value.
diff --git a/Firebase/Auth/Source/FIRAuthNotificationManager.m b/Firebase/Auth/Source/FIRAuthNotificationManager.m
index b1dd34c..624de10 100644
--- a/Firebase/Auth/Source/FIRAuthNotificationManager.m
+++ b/Firebase/Auth/Source/FIRAuthNotificationManager.m
@@ -104,14 +104,14 @@ static const NSTimeInterval kProbingTimeout = 1;
kNotificationProberKey : @"This fake notification should be forwarded to Firebase Auth."
}
};
- if ([_application.delegate respondsToSelector:
+ if ([self->_application.delegate respondsToSelector:
@selector(application:didReceiveRemoteNotification:fetchCompletionHandler:)]) {
- [_application.delegate application:_application
+ [self->_application.delegate application:self->_application
didReceiveRemoteNotification:proberNotification
fetchCompletionHandler:^(UIBackgroundFetchResult result) {}];
- } else if ([_application.delegate respondsToSelector:
+ } else if ([self->_application.delegate respondsToSelector:
@selector(application:didReceiveRemoteNotification:)]) {
- [_application.delegate application:_application
+ [self->_application.delegate application:self->_application
didReceiveRemoteNotification:proberNotification];
} else {
FIRLogWarning(kFIRLoggerAuth, @"I-AUT000015",
diff --git a/Firebase/Auth/Source/FIRAuthProvider.m b/Firebase/Auth/Source/FIRAuthProvider.m
index 6df86d7..72a00ef 100644
--- a/Firebase/Auth/Source/FIRAuthProvider.m
+++ b/Firebase/Auth/Source/FIRAuthProvider.m
@@ -16,6 +16,8 @@
#import <Foundation/Foundation.h>
+#pragma mark - Provider ID constants
+
// Declared 'extern' in FIRGoogleAuthProvider.h
NSString *const FIRGoogleAuthProviderID = @"google.com";
@@ -36,3 +38,26 @@ NSString *const FIRGitHubAuthProviderID = @"github.com";
// Declared 'extern' in FIRPhoneAuthProvider.h
NSString *const FIRPhoneAuthProviderID = @"phone";
+
+#pragma mark - sign-in methods constants
+
+// Declared 'extern' in FIRGoogleAuthProvider.h
+NSString *const FIRGoogleAuthSignInMethod = @"google.com";
+
+// Declared 'extern' in FIREmailAuthProvider.h
+NSString *const FIREmailPasswordAuthSignInMethod = @"password";
+
+// Declared 'extern' in FIREmailAuthProvider.h
+NSString *const FIREmailLinkAuthSignInMethod = @"emailLink";
+
+// Declared 'extern' in FIRTwitterAuthProvider.h
+NSString *const FIRTwitterAuthSignInMethod = @"twitter.com";
+
+// Declared 'extern' in FIRFacebookAuthProvider.h
+NSString *const FIRFacebookAuthSignInMethod = @"facebook.com";
+
+// Declared 'extern' in FIRGitHubAuthProvider.h
+NSString *const FIRGitHubAuthSignInMethod = @"github.com";
+
+// Declared 'extern' in FIRPhoneAuthProvider.h
+NSString *const FIRPhoneAuthSignInMethod = @"phone";
diff --git a/Firebase/Auth/Source/FIRAuthSerialTaskQueue.m b/Firebase/Auth/Source/FIRAuthSerialTaskQueue.m
index 3be0f54..edceeec 100644
--- a/Firebase/Auth/Source/FIRAuthSerialTaskQueue.m
+++ b/Firebase/Auth/Source/FIRAuthSerialTaskQueue.m
@@ -37,14 +37,14 @@
- (void)enqueueTask:(FIRAuthSerialTask)task {
// This dispatch queue will run tasks serially in FIFO order, as long as it's not suspended.
- dispatch_async(_dispatchQueue, ^{
+ dispatch_async(self->_dispatchQueue, ^{
// But as soon as a task is started, stop other tasks from running until the task calls it's
// completion handler, which allows the queue to resume processing of tasks. This allows the
// task to perform other asyncronous actions on other dispatch queues and "get back to us" when
// all of their sub-tasks are complete.
- dispatch_suspend(_dispatchQueue);
+ dispatch_suspend(self->_dispatchQueue);
task(^{
- dispatch_resume(_dispatchQueue);
+ dispatch_resume(self->_dispatchQueue);
});
});
}
diff --git a/Firebase/Auth/Source/FIRAuthTokenResult.m b/Firebase/Auth/Source/FIRAuthTokenResult.m
new file mode 100644
index 0000000..3a06ac6
--- /dev/null
+++ b/Firebase/Auth/Source/FIRAuthTokenResult.m
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2018 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 "FIRAuthTokenResult_Internal.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+/** @var kExpirationDateKey
+ @brief The key used to encode the expirationDate property for NSSecureCoding.
+ */
+static NSString *const kExpirationDateKey = @"expiratinDate";
+
+/** @var kTokenKey
+ @brief The key used to encode the token property for NSSecureCoding.
+ */
+static NSString *const kTokenKey = @"token";
+
+/** @var kAuthDateKey
+ @brief The key used to encode the authDate property for NSSecureCoding.
+ */
+static NSString *const kAuthDateKey = @"authDate";
+
+/** @var kIssuedDateKey
+ @brief The key used to encode the issuedDate property for NSSecureCoding.
+ */
+static NSString *const kIssuedDateKey = @"issuedDate";
+
+/** @var kSignInProviderKey
+ @brief The key used to encode the signInProvider property for NSSecureCoding.
+ */
+static NSString *const kSignInProviderKey = @"signInProvider";
+
+/** @var kClaimsKey
+ @brief The key used to encode the claims property for NSSecureCoding.
+ */
+static NSString *const kClaimsKey = @"claims";
+
+@implementation FIRAuthTokenResult
+
+- (instancetype)initWithToken:(NSString *)token
+ expirationDate:(NSDate *)expirationDate
+ authDate:(NSDate *)authDate
+ issuedAtDate:(NSDate *)issuedAtDate
+ signInProvider:(NSString *)signInProvider
+ claims:(NSDictionary *)claims {
+ self = [super init];
+ if (self) {
+ _token = token;
+ _expirationDate = expirationDate;
+ _authDate = authDate;
+ _issuedAtDate = issuedAtDate;
+ _signInProvider = signInProvider;
+ _claims = claims;
+ }
+ return self;
+}
+
+#pragma mark - NSSecureCoding
+
++ (BOOL)supportsSecureCoding {
+ return YES;
+}
+
+- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder {
+ NSString *token =
+ [aDecoder decodeObjectOfClass:[NSDate class] forKey:kTokenKey];
+ NSDate *expirationDate =
+ [aDecoder decodeObjectOfClass:[NSDate class] forKey:kExpirationDateKey];
+ NSDate *authDate =
+ [aDecoder decodeObjectOfClass:[NSDate class] forKey:kAuthDateKey];
+ NSDate *issuedAtDate =
+ [aDecoder decodeObjectOfClass:[NSDate class] forKey:kAuthDateKey];
+ NSString *signInProvider =
+ [aDecoder decodeObjectOfClass:[NSString class] forKey:kSignInProviderKey];
+ NSDictionary<NSString *, NSString *> *claims =
+ [aDecoder decodeObjectOfClass:[NSDictionary<NSString *, NSString *> class] forKey:kClaimsKey];
+
+ return [self initWithToken:token
+ expirationDate:expirationDate
+ authDate:authDate
+ issuedAtDate:issuedAtDate
+ signInProvider:signInProvider
+ claims:claims];
+}
+
+- (void)encodeWithCoder:(NSCoder *)aCoder {
+ [aCoder encodeObject:_token forKey:kTokenKey];
+ [aCoder encodeObject:_expirationDate forKey:kExpirationDateKey];
+ [aCoder encodeObject:_authDate forKey:kAuthDateKey];
+ [aCoder encodeObject:_issuedAtDate forKey:kIssuedDateKey];
+ [aCoder encodeObject:_signInProvider forKey:kSignInProviderKey];
+ [aCoder encodeObject:_claims forKey:kClaimsKey];
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firebase/Auth/Source/FIRAuthTokenResult_Internal.h b/Firebase/Auth/Source/FIRAuthTokenResult_Internal.h
new file mode 100644
index 0000000..2914f2a
--- /dev/null
+++ b/Firebase/Auth/Source/FIRAuthTokenResult_Internal.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2018 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/Foundation.h>
+
+ #import "FIRAuthTokenResult.h"
+
+ NS_ASSUME_NONNULL_BEGIN
+
+/** @extension FIRAuthAPNSTokenResult
+ @brief An internal class used to expose internal methods of FIRAuthAPNSTokenResult.
+ */
+@interface FIRAuthTokenResult () <NSSecureCoding>
+
+- (instancetype)initWithToken:(NSString *)token
+ expirationDate:(NSDate *)expirationDate
+ authDate:(NSDate *)authDate
+ issuedAtDate:(NSDate *)issuedAtDate
+ signInProvider:(NSString *)signInProvider
+ claims:(NSDictionary *)claims;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firebase/Auth/Source/FIRAuthURLPresenter.m b/Firebase/Auth/Source/FIRAuthURLPresenter.m
index 5526a85..d8e3593 100644
--- a/Firebase/Auth/Source/FIRAuthURLPresenter.m
+++ b/Firebase/Auth/Source/FIRAuthURLPresenter.m
@@ -81,17 +81,19 @@ NS_ASSUME_NONNULL_BEGIN
_callbackMatcher = callbackMatcher;
_completion = completion;
dispatch_async(dispatch_get_main_queue(), ^() {
- _UIDelegate = UIDelegate ?: [FIRAuthDefaultUIDelegate defaultUIDelegate];
+ self->_UIDelegate = UIDelegate ?: [FIRAuthDefaultUIDelegate defaultUIDelegate];
if ([SFSafariViewController class]) {
- _safariViewController = [[SFSafariViewController alloc] initWithURL:URL];
- _safariViewController.delegate = self;
- [_UIDelegate presentViewController:_safariViewController animated:YES completion:nil];
+ self->_safariViewController = [[SFSafariViewController alloc] initWithURL:URL];
+ self->_safariViewController.delegate = self;
+ [self->_UIDelegate presentViewController:self->_safariViewController
+ animated:YES
+ completion:nil];
return;
} else {
- _webViewController = [[FIRAuthWebViewController alloc] initWithURL:URL delegate:self];
+ self->_webViewController = [[FIRAuthWebViewController alloc] initWithURL:URL delegate:self];
UINavigationController *navController =
- [[UINavigationController alloc] initWithRootViewController:_webViewController];
- [_UIDelegate presentViewController:navController animated:YES completion:nil];
+ [[UINavigationController alloc] initWithRootViewController:self->_webViewController];
+ [self->_UIDelegate presentViewController:navController animated:YES completion:nil];
}
});
}
@@ -108,8 +110,8 @@ NS_ASSUME_NONNULL_BEGIN
- (void)safariViewControllerDidFinish:(SFSafariViewController *)controller {
dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
- if (controller == _safariViewController) {
- _safariViewController = nil;
+ if (controller == self->_safariViewController) {
+ self->_safariViewController = nil;
//TODO:Ensure that the SFSafariViewController is actually removed from the screen before
//invoking finishPresentationWithURL:error:
[self finishPresentationWithURL:nil
@@ -123,7 +125,7 @@ NS_ASSUME_NONNULL_BEGIN
- (BOOL)webViewController:(FIRAuthWebViewController *)webViewController canHandleURL:(NSURL *)URL {
__block BOOL result = NO;
dispatch_sync(FIRAuthGlobalWorkQueue(), ^() {
- if (webViewController == _webViewController) {
+ if (webViewController == self->_webViewController) {
result = [self canHandleURL:URL];
}
});
@@ -132,7 +134,7 @@ NS_ASSUME_NONNULL_BEGIN
- (void)webViewControllerDidCancel:(FIRAuthWebViewController *)webViewController {
dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
- if (webViewController == _webViewController) {
+ if (webViewController == self->_webViewController) {
[self finishPresentationWithURL:nil
error:[FIRAuthErrorUtils webContextCancelledErrorWithMessage:nil]];
}
@@ -142,7 +144,7 @@ NS_ASSUME_NONNULL_BEGIN
- (void)webViewController:(FIRAuthWebViewController *)webViewController
didFailWithError:(NSError *)error {
dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
- if (webViewController == _webViewController) {
+ if (webViewController == self->_webViewController) {
[self finishPresentationWithURL:nil error:error];
}
});
@@ -163,7 +165,7 @@ NS_ASSUME_NONNULL_BEGIN
FIRAuthURLPresentationCompletion completion = _completion;
_completion = nil;
void (^finishBlock)(void) = ^() {
- _isPresenting = NO;
+ self->_isPresenting = NO;
completion(URL, error);
};
SFSafariViewController *safariViewController = _safariViewController;
diff --git a/Firebase/Auth/Source/FIRSecureTokenService.m b/Firebase/Auth/Source/FIRSecureTokenService.m
index 8e37a05..69434ff 100644
--- a/Firebase/Auth/Source/FIRSecureTokenService.m
+++ b/Firebase/Auth/Source/FIRSecureTokenService.m
@@ -107,7 +107,7 @@ static const NSTimeInterval kFiveMinutes = 5 * 60;
[_taskQueue enqueueTask:^(FIRAuthSerialTaskCompletionBlock complete) {
if (!forceRefresh && [self hasValidAccessToken]) {
complete();
- callback(_accessToken, nil, NO);
+ callback(self->_accessToken, nil, NO);
} else {
[self requestAccessToken:^(NSString *_Nullable token,
NSError *_Nullable error,
@@ -184,14 +184,15 @@ static const NSTimeInterval kFiveMinutes = 5 * 60;
NSError *_Nullable error) {
BOOL tokenUpdated = NO;
NSString *newAccessToken = response.accessToken;
- if (newAccessToken.length && ![newAccessToken isEqualToString:_accessToken]) {
- _accessToken = [newAccessToken copy];
- _accessTokenExpirationDate = response.approximateExpirationDate;
+ if (newAccessToken.length && ![newAccessToken isEqualToString:self->_accessToken]) {
+ self->_accessToken = [newAccessToken copy];
+ self->_accessTokenExpirationDate = response.approximateExpirationDate;
tokenUpdated = YES;
}
NSString *newRefreshToken = response.refreshToken;
- if (newRefreshToken.length && ![newRefreshToken isEqualToString:_refreshToken]) {
- _refreshToken = [newRefreshToken copy];
+ if (newRefreshToken.length &&
+ ![newRefreshToken isEqualToString:self->_refreshToken]) {
+ self->_refreshToken = [newRefreshToken copy];
tokenUpdated = YES;
}
callback(newAccessToken, error, tokenUpdated);
diff --git a/Firebase/Auth/Source/FIRUser.m b/Firebase/Auth/Source/FIRUser.m
index 7f2316b..0a0a664 100644
--- a/Firebase/Auth/Source/FIRUser.m
+++ b/Firebase/Auth/Source/FIRUser.m
@@ -29,6 +29,7 @@
#import "FIRAuth_Internal.h"
#import "FIRAuthBackend.h"
#import "FIRAuthRequestConfiguration.h"
+#import "FIRAuthTokenResult_Internal.h"
#import "FIRDeleteAccountRequest.h"
#import "FIRDeleteAccountResponse.h"
#import "FIREmailAuthProvider.h"
@@ -250,6 +251,7 @@ static void callInMainThreadWithAuthDataResultAndError(
refreshToken:refreshToken];
FIRUser *user = [[self alloc] initWithTokenService:tokenService];
user.auth = auth;
+ user.requestConfiguration = auth.requestConfiguration;
[user internalGetTokenWithCallback:^(NSString *_Nullable accessToken, NSError *_Nullable error) {
if (error) {
callback(nil, error);
@@ -315,6 +317,8 @@ static void callInMainThreadWithAuthDataResultAndError(
[aDecoder decodeObjectOfClass:[FIRSecureTokenService class] forKey:kTokenServiceCodingKey];
FIRUserMetadata *metadata =
[aDecoder decodeObjectOfClass:[FIRUserMetadata class] forKey:kMetadataCodingKey];
+ NSString *APIKey =
+ [aDecoder decodeObjectOfClass:[FIRUserMetadata class] forKey:kAPIKeyCodingKey];
if (!userID || !tokenService) {
return nil;
}
@@ -333,6 +337,7 @@ static void callInMainThreadWithAuthDataResultAndError(
_providerData = providerData;
_phoneNumber = phoneNumber;
_metadata = metadata ?: [[FIRUserMetadata alloc] initWithCreationDate:nil lastSignInDate:nil];
+ _requestConfiguration = [[FIRAuthRequestConfiguration alloc] initWithAPIKey:APIKey];
}
return self;
}
@@ -348,8 +353,6 @@ static void callInMainThreadWithAuthDataResultAndError(
[aCoder encodeObject:_photoURL forKey:kPhotoURLCodingKey];
[aCoder encodeObject:_displayName forKey:kDisplayNameCodingKey];
[aCoder encodeObject:_metadata forKey:kMetadataCodingKey];
- // The API key is encoded even it is not used in decoding to be compatible with previous versions
- // of the library.
[aCoder encodeObject:_auth.requestConfiguration.APIKey forKey:kAPIKeyCodingKey];
[aCoder encodeObject:_tokenService forKey:kTokenServiceCodingKey];
}
@@ -383,7 +386,7 @@ static void callInMainThreadWithAuthDataResultAndError(
}
FIRGetAccountInfoRequest *getAccountInfoRequest =
[[FIRGetAccountInfoRequest alloc] initWithAccessToken:accessToken
- requestConfiguration:_auth.requestConfiguration];
+ requestConfiguration:self->_auth.requestConfiguration];
[FIRAuthBackend getAccountInfo:getAccountInfoRequest
callback:^(FIRGetAccountInfoResponse *_Nullable response,
NSError *_Nullable error) {
@@ -451,7 +454,7 @@ static void callInMainThreadWithAuthDataResultAndError(
callback(error);
return;
}
- FIRAuthRequestConfiguration *configuration = _auth.requestConfiguration;
+ FIRAuthRequestConfiguration *configuration = self->_auth.requestConfiguration;
// Mutate setAccountInfoRequest in block:
FIRSetAccountInfoRequest *setAccountInfoRequest =
[[FIRSetAccountInfoRequest alloc] initWithRequestConfiguration:configuration];
@@ -513,7 +516,7 @@ static void callInMainThreadWithAuthDataResultAndError(
callback(error);
return;
}
- _tokenService = tokenService;
+ self->_tokenService = tokenService;
if (![self updateKeychain:&error]) {
callback(error);
return;
@@ -558,11 +561,11 @@ static void callInMainThreadWithAuthDataResultAndError(
return;
}
if (email) {
- _email = email;
+ self->_email = email;
}
- if (_email && password) {
- _anonymous = NO;
- _hasEmailPasswordCredential = YES;
+ if (self->_email && password) {
+ self->_anonymous = NO;
+ self->_hasEmailPasswordCredential = YES;
if (!hadEmailPasswordCredential) {
// The list of providers need to be updated for the newly added email-password provider.
[self internalGetTokenWithCallback:^(NSString *_Nullable accessToken,
@@ -571,7 +574,7 @@ static void callInMainThreadWithAuthDataResultAndError(
callback(error);
return;
}
- FIRAuthRequestConfiguration *requestConfiguration = _auth.requestConfiguration;
+ FIRAuthRequestConfiguration *requestConfiguration = self->_auth.requestConfiguration;
FIRGetAccountInfoRequest *getAccountInfoRequest =
[[FIRGetAccountInfoRequest alloc] initWithAccessToken:accessToken
requestConfiguration:requestConfiguration];
@@ -625,7 +628,7 @@ static void callInMainThreadWithAuthDataResultAndError(
updated.
@param phoneAuthCredential The new phone number credential corresponding to the phone number
- to be added to the firebaes account, if a phone number is already linked to the account this
+ to be added to the Firebase account, if a phone number is already linked to the account this
new phone number will replace it.
@param isLinkOperation Boolean value indicating whether or not this is a link operation.
@param completion Optionally; the block invoked when the user profile change has finished.
@@ -646,7 +649,7 @@ static void callInMainThreadWithAuthDataResultAndError(
initWithVerificationID:phoneAuthCredential.verificationID
verificationCode:phoneAuthCredential.verificationCode
operation:operation
- requestConfiguration:_auth.requestConfiguration];
+ requestConfiguration:self->_auth.requestConfiguration];
request.accessToken = accessToken;
[FIRAuthBackend verifyPhoneNumber:request
callback:^(FIRVerifyPhoneNumberResponse *_Nullable response,
@@ -664,7 +667,7 @@ static void callInMainThreadWithAuthDataResultAndError(
completion(error);
return;
}
- _anonymous = NO;
+ self->_anonymous = NO;
if (![self updateKeychain:&error]) {
completion(error);
return;
@@ -737,10 +740,11 @@ static void callInMainThreadWithAuthDataResultAndError(
reauthenticateAndRetrieveDataWithCredential:(FIRAuthCredential *) credential
completion:(nullable FIRAuthDataResultCallback) completion {
dispatch_async(FIRAuthGlobalWorkQueue(), ^{
- [_auth internalSignInAndRetrieveDataWithCredential:credential
- isReauthentication:YES
- callback:^(FIRAuthDataResult *_Nullable authResult,
- NSError *_Nullable error) {
+ [self->_auth internalSignInAndRetrieveDataWithCredential:credential
+ isReauthentication:YES
+ callback:^(FIRAuthDataResult *_Nullable
+ authResult,
+ NSError *_Nullable error) {
if (error) {
// If "user not found" error returned by backend, translate to user mismatch error which is
// more accurate.
@@ -750,7 +754,7 @@ static void callInMainThreadWithAuthDataResultAndError(
callInMainThreadWithAuthDataResultAndError(completion, authResult, error);
return;
}
- if (![authResult.user.uid isEqual:[_auth getUID]]) {
+ if (![authResult.user.uid isEqual:[self->_auth getUID]]) {
callInMainThreadWithAuthDataResultAndError(completion, authResult,
[FIRAuthErrorUtils userMismatchError]);
return;
@@ -766,7 +770,7 @@ static void callInMainThreadWithAuthDataResultAndError(
- (nullable NSString *)refreshToken {
__block NSString *result;
dispatch_sync(FIRAuthGlobalWorkQueue(), ^{
- result = _tokenService.refreshToken;
+ result = self->_tokenService.refreshToken;
});
return result;
}
@@ -783,18 +787,94 @@ static void callInMainThreadWithAuthDataResultAndError(
- (void)getIDTokenForcingRefresh:(BOOL)forceRefresh
completion:(nullable FIRAuthTokenCallback)completion {
+ [self getIDTokenResultForcingRefresh:forceRefresh
+ completion:^(FIRAuthTokenResult *_Nullable tokenResult,
+ NSError *_Nullable error) {
+
+ if (completion) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ completion(tokenResult.token, error);
+ });
+ }
+ }];
+}
+
+- (void)getIDTokenResultWithCompletion:(nullable FIRAuthTokenResultCallback)completion {
+ [self getIDTokenResultForcingRefresh:NO
+ completion:^(FIRAuthTokenResult *_Nullable tokenResult,
+ NSError *_Nullable error) {
+ if (completion) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ completion(tokenResult, error);
+ });
+ }
+ }];
+}
+
+- (void)getIDTokenResultForcingRefresh:(BOOL)forceRefresh
+ completion:(nullable FIRAuthTokenResultCallback)completion {
dispatch_async(FIRAuthGlobalWorkQueue(), ^{
[self internalGetTokenForcingRefresh:forceRefresh
callback:^(NSString *_Nullable token, NSError *_Nullable error) {
+ FIRAuthTokenResult *tokenResult;
+ if (token) {
+ tokenResult = [self parseIDToken:token error:&error];
+ }
if (completion) {
dispatch_async(dispatch_get_main_queue(), ^{
- completion(token, error);
+ completion(tokenResult, error);
});
}
}];
});
}
+- (FIRAuthTokenResult *)parseIDToken:(NSString *)token error:(NSError **)error {
+ error = nil;
+ NSArray *tokenStringArray = [token componentsSeparatedByString:@"."];
+ // The token payload is always the second index of the array.
+ NSMutableString *tokenPayload = [[NSMutableString alloc] initWithString:tokenStringArray[1]];
+
+ // Pad the token payload with "=" signs if the payload's length is not a multple of 4.
+ while ((tokenPayload.length % 4) != 0) {
+ [tokenPayload appendFormat:@"="];
+ }
+ NSData *decodedTokenPayloadData =
+ [[NSData alloc] initWithBase64EncodedString:tokenPayload
+ options:NSDataBase64DecodingIgnoreUnknownCharacters];
+ if (!decodedTokenPayloadData) {
+ *error = [FIRAuthErrorUtils unexpectedResponseWithDeserializedResponse:token];
+ return nil;
+ }
+ NSDictionary *tokenPayloadDictionary =
+ [NSJSONSerialization JSONObjectWithData:decodedTokenPayloadData
+ options:NSJSONReadingMutableContainers|NSJSONReadingAllowFragments
+ error:error];
+ if (error) {
+ return nil;
+ }
+
+ if (!tokenPayloadDictionary) {
+ *error = [FIRAuthErrorUtils unexpectedResponseWithDeserializedResponse:token];
+ return nil;
+ }
+
+ NSDate *expDate =
+ [NSDate dateWithTimeIntervalSinceNow:[tokenPayloadDictionary[@"exp"] doubleValue]];
+ NSDate *authDate =
+ [NSDate dateWithTimeIntervalSinceNow:[tokenPayloadDictionary[@"auth_time"] doubleValue]];
+ NSDate *issuedDate =
+ [NSDate dateWithTimeIntervalSinceNow:[tokenPayloadDictionary[@"iat"] doubleValue]];
+ FIRAuthTokenResult *result =
+ [[FIRAuthTokenResult alloc] initWithToken:token
+ expirationDate:expDate
+ authDate:authDate
+ issuedAtDate:issuedDate
+ signInProvider:tokenPayloadDictionary[@"sign_in_provider"]
+ claims:tokenPayloadDictionary];
+ return result;
+}
+
- (void)getTokenForcingRefresh:(BOOL)forceRefresh
completion:(nullable FIRAuthTokenCallback)completion {
[self getIDTokenForcingRefresh:forceRefresh completion:completion];
@@ -842,7 +922,7 @@ static void callInMainThreadWithAuthDataResultAndError(
- (void)linkAndRetrieveDataWithCredential:(FIRAuthCredential *)credential
completion:(nullable FIRAuthDataResultCallback)completion {
dispatch_async(FIRAuthGlobalWorkQueue(), ^{
- if (_providerData[credential.provider]) {
+ if (self->_providerData[credential.provider]) {
callInMainThreadWithAuthDataResultAndError(completion,
nil,
[FIRAuthErrorUtils providerAlreadyLinkedError]);
@@ -851,7 +931,7 @@ static void callInMainThreadWithAuthDataResultAndError(
FIRAuthDataResult *result =
[[FIRAuthDataResult alloc] initWithUser:self additionalUserInfo:nil];
if ([credential isKindOfClass:[FIREmailPasswordAuthCredential class]]) {
- if (_hasEmailPasswordCredential) {
+ if (self->_hasEmailPasswordCredential) {
callInMainThreadWithAuthDataResultAndError(completion,
nil,
[FIRAuthErrorUtils providerAlreadyLinkedError]);
@@ -887,7 +967,7 @@ static void callInMainThreadWithAuthDataResultAndError(
}
#endif
- [_taskQueue enqueueTask:^(FIRAuthSerialTaskCompletionBlock _Nonnull complete) {
+ [self->_taskQueue enqueueTask:^(FIRAuthSerialTaskCompletionBlock _Nonnull complete) {
CallbackWithAuthDataResultAndError completeWithError =
^(FIRAuthDataResult *result, NSError *error) {
complete();
@@ -899,7 +979,7 @@ static void callInMainThreadWithAuthDataResultAndError(
completeWithError(nil, error);
return;
}
- FIRAuthRequestConfiguration *requestConfiguration = _auth.requestConfiguration;
+ FIRAuthRequestConfiguration *requestConfiguration = self->_auth.requestConfiguration;
FIRVerifyAssertionRequest *request =
[[FIRVerifyAssertionRequest alloc] initWithProviderID:credential.provider
requestConfiguration:requestConfiguration];
@@ -917,7 +997,7 @@ static void callInMainThreadWithAuthDataResultAndError(
FIRAuthDataResult *result =
[[FIRAuthDataResult alloc] initWithUser:self additionalUserInfo:additionalUserInfo];
// Update the new token and refresh user info again.
- _tokenService = [[FIRSecureTokenService alloc]
+ self->_tokenService = [[FIRSecureTokenService alloc]
initWithRequestConfiguration:requestConfiguration
accessToken:response.IDToken
accessTokenExpirationDate:response.approximateExpirationDate
@@ -939,7 +1019,7 @@ static void callInMainThreadWithAuthDataResultAndError(
completeWithError(nil, error);
return;
}
- _anonymous = NO;
+ self->_anonymous = NO;
[self updateWithGetAccountInfoResponse:response];
if (![self updateKeychain:&error]) {
completeWithError(nil, error);
@@ -967,19 +1047,19 @@ static void callInMainThreadWithAuthDataResultAndError(
completeAndCallbackWithError(error);
return;
}
- FIRAuthRequestConfiguration *requestConfiguration = _auth.requestConfiguration;
+ FIRAuthRequestConfiguration *requestConfiguration = self->_auth.requestConfiguration;
FIRSetAccountInfoRequest *setAccountInfoRequest =
[[FIRSetAccountInfoRequest alloc] initWithRequestConfiguration:requestConfiguration];
setAccountInfoRequest.accessToken = accessToken;
BOOL isEmailPasswordProvider = [provider isEqualToString:FIREmailAuthProviderID];
if (isEmailPasswordProvider) {
- if (!_hasEmailPasswordCredential) {
+ if (!self->_hasEmailPasswordCredential) {
completeAndCallbackWithError([FIRAuthErrorUtils noSuchProviderError]);
return;
}
setAccountInfoRequest.deleteAttributes = @[ FIRSetAccountInfoUserAttributePassword ];
} else {
- if (!_providerData[provider]) {
+ if (!self->_providerData[provider]) {
completeAndCallbackWithError([FIRAuthErrorUtils noSuchProviderError]);
return;
}
@@ -994,19 +1074,19 @@ static void callInMainThreadWithAuthDataResultAndError(
return;
}
if (isEmailPasswordProvider) {
- _hasEmailPasswordCredential = NO;
+ self->_hasEmailPasswordCredential = NO;
} else {
// We can't just use the provider info objects in FIRSetAcccountInfoResponse because they
// don't have localID and email fields. Remove the specific provider manually.
- NSMutableDictionary *mutableProviderData = [_providerData mutableCopy];
+ NSMutableDictionary *mutableProviderData = [self->_providerData mutableCopy];
[mutableProviderData removeObjectForKey:provider];
- _providerData = [mutableProviderData copy];
+ self->_providerData = [mutableProviderData copy];
#if TARGET_OS_IOS
// After successfully unlinking a phone auth provider, remove the phone number from the
// cached user info.
if ([provider isEqualToString:FIRPhoneAuthProviderID]) {
- _phoneNumber = nil;
+ self->_phoneNumber = nil;
}
#endif
}
@@ -1060,7 +1140,7 @@ static void callInMainThreadWithAuthDataResultAndError(
callInMainThreadWithError(completion, error);
return;
}
- FIRAuthRequestConfiguration *configuration = _auth.requestConfiguration;
+ FIRAuthRequestConfiguration *configuration = self->_auth.requestConfiguration;
FIRGetOOBConfirmationCodeRequest *request =
[FIRGetOOBConfirmationCodeRequest verifyEmailRequestWithAccessToken:accessToken
actionCodeSettings:actionCodeSettings
@@ -1085,15 +1165,15 @@ static void callInMainThreadWithAuthDataResultAndError(
return;
}
FIRDeleteAccountRequest *deleteUserRequest =
- [[FIRDeleteAccountRequest alloc] initWitLocalID:_userID
+ [[FIRDeleteAccountRequest alloc] initWitLocalID:self->_userID
accessToken:accessToken
- requestConfiguration:_auth.requestConfiguration];
+ requestConfiguration:self->_auth.requestConfiguration];
[FIRAuthBackend deleteAccount:deleteUserRequest callback:^(NSError *_Nullable error) {
if (error) {
callInMainThreadWithError(completion, error);
return;
}
- if (![_auth signOutByForceWithUserID:_userID error:&error]) {
+ if (![self->_auth signOutByForceWithUserID:self->_userID error:&error]) {
callInMainThreadWithError(completion, error);
return;
}
@@ -1167,14 +1247,14 @@ static void callInMainThreadWithAuthDataResultAndError(
- (void)setDisplayName:(nullable NSString *)displayName {
dispatch_sync(FIRAuthGlobalWorkQueue(), ^{
- if (_consumed) {
+ if (self->_consumed) {
[NSException raise:NSInternalInconsistencyException
format:@"%@",
@"Invalid call to setDisplayName: after commitChangesWithCallback:."];
return;
}
- _displayNameSet = YES;
- _displayName = [displayName copy];
+ self->_displayNameSet = YES;
+ self->_displayName = [displayName copy];
});
}
@@ -1184,14 +1264,14 @@ static void callInMainThreadWithAuthDataResultAndError(
- (void)setPhotoURL:(nullable NSURL *)photoURL {
dispatch_sync(FIRAuthGlobalWorkQueue(), ^{
- if (_consumed) {
+ if (self->_consumed) {
[NSException raise:NSInternalInconsistencyException
format:@"%@",
@"Invalid call to setPhotoURL: after commitChangesWithCallback:."];
return;
}
- _photoURLSet = YES;
- _photoURL = [photoURL copy];
+ self->_photoURLSet = YES;
+ self->_photoURL = [photoURL copy];
});
}
@@ -1204,24 +1284,24 @@ static void callInMainThreadWithAuthDataResultAndError(
- (void)commitChangesWithCompletion:(nullable FIRUserProfileChangeCallback)completion {
dispatch_sync(FIRAuthGlobalWorkQueue(), ^{
- if (_consumed) {
+ if (self->_consumed) {
[NSException raise:NSInternalInconsistencyException
format:@"%@",
@"commitChangesWithCallback: should only be called once."];
return;
}
- _consumed = YES;
+ self->_consumed = YES;
// Return fast if there is nothing to update:
if (![self hasUpdates]) {
callInMainThreadWithError(completion, nil);
return;
}
- NSString *displayName = [_displayName copy];
- BOOL displayNameWasSet = _displayNameSet;
- NSURL *photoURL = [_photoURL copy];
- BOOL photoURLWasSet = _photoURLSet;
- [_user executeUserUpdateWithChanges:^(FIRGetAccountInfoResponseUser *user,
- FIRSetAccountInfoRequest *request) {
+ NSString *displayName = [self->_displayName copy];
+ BOOL displayNameWasSet = self->_displayNameSet;
+ NSURL *photoURL = [self->_photoURL copy];
+ BOOL photoURLWasSet = self->_photoURLSet;
+ [self->_user executeUserUpdateWithChanges:^(FIRGetAccountInfoResponseUser *user,
+ FIRSetAccountInfoRequest *request) {
if (photoURLWasSet) {
request.photoURL = photoURL;
}
@@ -1235,12 +1315,12 @@ static void callInMainThreadWithAuthDataResultAndError(
return;
}
if (displayNameWasSet) {
- [_user setDisplayName:displayName];
+ [self->_user setDisplayName:displayName];
}
if (photoURLWasSet) {
- [_user setPhotoURL:photoURL];
+ [self->_user setPhotoURL:photoURL];
}
- if (![_user updateKeychain:&error]) {
+ if (![self->_user updateKeychain:&error]) {
callInMainThreadWithError(completion, error);
return;
}
diff --git a/Firebase/Auth/Source/FIRUser_Internal.h b/Firebase/Auth/Source/FIRUser_Internal.h
index 9a069bb..4f89c00 100644
--- a/Firebase/Auth/Source/FIRUser_Internal.h
+++ b/Firebase/Auth/Source/FIRUser_Internal.h
@@ -17,6 +17,7 @@
#import "FIRUser.h"
@class FIRAuth;
+@class FIRAuthRequestConfiguration;
NS_ASSUME_NONNULL_BEGIN
@@ -42,6 +43,11 @@ typedef void(^FIRRetrieveUserCallback)(FIRUser *_Nullable user, NSError *_Nullab
*/
@property(nonatomic, weak) FIRAuth *auth;
+/** @property auth
+ @brief A strong reference to a requestConfiguration instance associated with this user instance.
+ */
+@property(nonatomic, strong) FIRAuthRequestConfiguration *requestConfiguration;
+
/** @var accessTokenExpirationDate
@brief The expiration date of the cached access token.
*/
diff --git a/Firebase/Auth/Source/Public/FIRAuth.h b/Firebase/Auth/Source/Public/FIRAuth.h
index f18a3d0..236dd10 100644
--- a/Firebase/Auth/Source/Public/FIRAuth.h
+++ b/Firebase/Auth/Source/Public/FIRAuth.h
@@ -32,6 +32,11 @@
NS_ASSUME_NONNULL_BEGIN
+/** @typedef FIRUserUpdateCallback
+ @brief The type of block invoked when a request to update the current user is completed.
+ */
+typedef void (^FIRUserUpdateCallback)(NSError *_Nullable error) NS_SWIFT_NAME(UserUpdateCallback);
+
/** @typedef FIRAuthStateDidChangeListenerHandle
@brief The type of handle returned by `FIRAuth.addAuthStateDidChangeListener:`.
*/
@@ -114,6 +119,14 @@ typedef void (^FIRProviderQueryCallback)(NSArray<NSString *> *_Nullable provider
NSError *_Nullable error)
NS_SWIFT_NAME(ProviderQueryCallback);
+/** @typedef FIRSignInMethodQueryCallback
+ @brief The type of block invoked when a list of sign-in methods for a given email address is
+ requested.
+ */
+typedef void (^FIRSignInMethodQueryCallback)(NSArray<NSString *> *_Nullable,
+ NSError *_Nullable)
+ NS_SWIFT_NAME(SignInMethodQueryCallback);
+
/** @typedef FIRSendPasswordResetCallback
@brief The type of block invoked when sending a password reset email.
@@ -123,6 +136,12 @@ typedef void (^FIRProviderQueryCallback)(NSArray<NSString *> *_Nullable provider
typedef void (^FIRSendPasswordResetCallback)(NSError *_Nullable error)
NS_SWIFT_NAME(SendPasswordResetCallback);
+/** @typedef FIRSendSignInLinkToEmailCallback
+ @brief The type of block invoked when sending an email sign-in link email.
+ */
+typedef void (^FIRSendSignInLinkToEmailCallback)(NSError *_Nullable error)
+ NS_SWIFT_NAME(SendSignInLinkToEmailCallback);
+
/** @typedef FIRConfirmPasswordResetCallback
@brief The type of block invoked when performing a password reset.
@@ -190,6 +209,10 @@ typedef NS_ENUM(NSInteger, FIRActionCodeOperation) {
/** Action code for recover email operation. */
FIRActionCodeOperationRecoverEmail = 3,
+ /** Action code for email link operation. */
+ FIRActionCodeOperationEmailLink = 4,
+
+
} NS_SWIFT_NAME(ActionCodeOperation);
/**
@@ -279,6 +302,14 @@ NS_SWIFT_NAME(Auth)
*/
- (instancetype)init NS_UNAVAILABLE;
+/** @fn updateCurrentUser:completion:
+ @brief Sets the currentUser on the calling Auth instance to the provided user object.
+ @param user The user object to be set as the current user of the calling Auth instance.
+ @param completion Optionally; a block invoked after the user of the calling Auth instance has
+ been updated or an error was encountered.
+ */
+- (void)updateCurrentUser:(FIRUser *)user completion:(nullable FIRUserUpdateCallback)completion;
+
/** @fn fetchProvidersForEmail:completion:
@brief Fetches the list of IdPs that can be used for signing in with the provided email address.
Useful for an "identifier-first" sign-in flow.
@@ -297,6 +328,24 @@ NS_SWIFT_NAME(Auth)
- (void)fetchProvidersForEmail:(NSString *)email
completion:(nullable FIRProviderQueryCallback)completion;
+/** @fn fetchSignInMethodsForEmail:completion:
+ @brief Fetches the list of all sign-in methods previously used for the provided email address.
+
+ @param email The email address for which to obtain a list of sign-in methods.
+ @param completion Optionally; a block which is invoked when the list of sign in methods for the
+ specified email address is ready or an error was encountered. Invoked asynchronously on the
+ main thread in the future.
+
+ @remarks Possible error codes:
+
+ + `FIRAuthErrorCodeInvalidEmail` - Indicates the email address is malformed.
+
+ @remarks See @c FIRAuthErrors for a list of error codes that are common to all API methods.
+ */
+
+- (void)fetchSignInMethodsForEmail:(NSString *)email
+ completion:(nullable FIRSignInMethodQueryCallback)completion;
+
/** @fn signInWithEmail:password:completion:
@brief Signs in using an email address and password.
@@ -322,6 +371,30 @@ NS_SWIFT_NAME(Auth)
password:(NSString *)password
completion:(nullable FIRAuthResultCallback)completion;
+/** @fn signInWithEmail:link:completion:
+ @brief Signs in using an email address and email sign-in link.
+
+ @param email The user's email address.
+ @param link The email sign-in link.
+ @param completion Optionally; a block which is invoked when the sign in flow finishes, or is
+ canceled. Invoked asynchronously on the main thread in the future.
+
+ @remarks Possible error codes:
+
+ + `FIRAuthErrorCodeOperationNotAllowed` - Indicates that email and email sign-in link
+ accounts are not enabled. Enable them in the Auth section of the
+ Firebase console.
+ + `FIRAuthErrorCodeUserDisabled` - Indicates the user's account is disabled.
+ + `FIRAuthErrorCodeInvalidEmail` - Indicates the email address is invalid.
+
+
+ @remarks See `FIRAuthErrors` for a list of error codes that are common to all API methods.
+ */
+
+- (void)signInWithEmail:(NSString *)email
+ link:(NSString *)link
+ completion:(nullable FIRAuthDataResultCallback)completion;
+
/** @fn signInAndRetrieveDataWithEmail:password:completion:
@brief Signs in using an email address and password.
@@ -654,6 +727,19 @@ NS_SWIFT_NAME(Auth)
actionCodeSettings:(FIRActionCodeSettings *)actionCodeSettings
completion:(nullable FIRSendPasswordResetCallback)completion;
+/** @fn sendSignInLinkToEmail:actionCodeSettings:completion:
+ @brief Sends a sign in with email link to provided email address.
+
+ @param email The email address of the user.
+ @param actionCodeSettings An `FIRActionCodeSettings` object containing settings related to
+ handling action codes.
+ @param completion Optionally; a block which is invoked when the request finishes. Invoked
+ asynchronously on the main thread in the future.
+ */
+- (void)sendSignInLinkToEmail:(NSString *)email
+ actionCodeSettings:(FIRActionCodeSettings *)actionCodeSettings
+ completion:(nullable FIRSendSignInLinkToEmailCallback)completion;
+
/** @fn signOut:
@brief Signs out the current user.
@@ -672,6 +758,14 @@ NS_SWIFT_NAME(Auth)
*/
- (BOOL)signOut:(NSError *_Nullable *_Nullable)error;
+/** @fn isSignInWithEmailLink
+ @brief Checks if link is an email sign-in link.
+
+ @param link The email sign-in link.
+ @return @YES when the link passed matches the expected format of an email sign-in link.
+ */
+- (BOOL)isSignInWithEmailLink:(NSString *)link;
+
/** @fn addAuthStateDidChangeListener:
@brief Registers a block as an "auth state did change" listener. To be invoked when:
diff --git a/Firebase/Auth/Source/Public/FIRAuthErrors.h b/Firebase/Auth/Source/Public/FIRAuthErrors.h
index b8e5fb6..4a1a6f1 100644
--- a/Firebase/Auth/Source/Public/FIRAuthErrors.h
+++ b/Firebase/Auth/Source/Public/FIRAuthErrors.h
@@ -299,6 +299,11 @@ typedef NS_ENUM(NSInteger, FIRAuthErrorCode) {
*/
FIRAuthErrorCodeWebInternalError = 17062,
+ /** Indicates that a non-null user was expected as an argmument to the operation but a null
+ user was provided.
+ */
+ FIRAuthErrorCodeNullUser = 17067,
+
/** Indicates an error occurred while attempting to access the keychain.
*/
FIRAuthErrorCodeKeychainError = 17995,
diff --git a/Firebase/Auth/Source/Public/FIRAuthTokenResult.h b/Firebase/Auth/Source/Public/FIRAuthTokenResult.h
new file mode 100644
index 0000000..41ba008
--- /dev/null
+++ b/Firebase/Auth/Source/Public/FIRAuthTokenResult.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2018 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/Foundation.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+/** @class FIRAuthTokenResult
+ @brief A data class containing the ID token JWT string and other properties associated with the
+ token including the decoded payload claims.
+ */
+NS_SWIFT_NAME(AuthTokenResult)
+@interface FIRAuthTokenResult : NSObject
+
+/** @property token
+ @brief Stores the JWT string of the ID token.
+ */
+@property (nonatomic, readonly) NSString *token;
+
+/** @property expirationDate
+ @brief Stores the ID token's expiration date.
+ */
+@property (nonatomic, readonly) NSDate *expirationDate;
+
+/** @property authDate
+ @brief Stores the ID token's authentication date.
+ @remarks This is the date the user was signed in and NOT the date the token was refreshed.
+ */
+@property (nonatomic, readonly) NSDate *authDate;
+
+/** @property issuedAtDate
+ @brief Stores the date that the ID token was issued.
+ @remarks This is the date last refreshed and NOT the last authentication date.
+ */
+@property (nonatomic, readonly) NSDate *issuedAtDate;
+
+/** @property signInProvider
+ @brief Stores sign-in provider through which the token was obtained.
+ @remarks This does not necesssarily map to provider IDs.
+ */
+@property (nonatomic, readonly) NSString *signInProvider;
+
+/** @property claims
+ @brief Stores the entire payload of claims found on the ID token. This includes the standard
+ reserved claims as well as custom claims set by the developer via the Admin SDK.
+ */
+@property (nonatomic, readonly) NSDictionary<NSString *, NSString *> *claims;
+
+
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firebase/Auth/Source/Public/FIREmailAuthProvider.h b/Firebase/Auth/Source/Public/FIREmailAuthProvider.h
index 0108d40..b6375bd 100644
--- a/Firebase/Auth/Source/Public/FIREmailAuthProvider.h
+++ b/Firebase/Auth/Source/Public/FIREmailAuthProvider.h
@@ -26,7 +26,18 @@ NS_ASSUME_NONNULL_BEGIN
extern NSString *const FIREmailAuthProviderID NS_SWIFT_NAME(EmailAuthProviderID);
/**
- @brief please use `FIREmailAuthProviderID` instead.
+ @brief A string constant identifying the email-link sign-in method.
+ */
+extern NSString *const FIREmailLinkAuthSignInMethod NS_SWIFT_NAME(EmailLinkAuthSignInMethod);
+
+/**
+ @brief A string constant identifying the email & password sign-in method.
+ */
+extern NSString *const FIREmailPasswordAuthSignInMethod
+ NS_SWIFT_NAME(EmailPasswordAuthSignInMethod);
+
+/**
+ @brief Please use `FIREmailAuthProviderID` for Objective-C or `EmailAuthProviderID` for Swift instead.
*/
extern NSString *const FIREmailPasswordAuthProviderID __attribute__((deprecated));
@@ -51,6 +62,15 @@ typedef FIREmailAuthProvider FIREmailPasswordAuthProvider __attribute__((depreca
*/
+ (FIRAuthCredential *)credentialWithEmail:(NSString *)email password:(NSString *)password;
+/** @fn credentialWithEmail:Link:
+ @brief Creates an `FIRAuthCredential` for an email & link sign in.
+
+ @param email The user's email address.
+ @param link The email sign-in link.
+ @return A FIRAuthCredential containing the email & link credential.
+ */
++ (FIRAuthCredential *)credentialWithEmail:(NSString *)email link:(NSString *)link;
+
/** @fn init
@brief This class is not meant to be initialized.
*/
diff --git a/Firebase/Auth/Source/Public/FIRFacebookAuthProvider.h b/Firebase/Auth/Source/Public/FIRFacebookAuthProvider.h
index f08740f..75efe13 100644
--- a/Firebase/Auth/Source/Public/FIRFacebookAuthProvider.h
+++ b/Firebase/Auth/Source/Public/FIRFacebookAuthProvider.h
@@ -25,6 +25,11 @@ NS_ASSUME_NONNULL_BEGIN
*/
extern NSString *const FIRFacebookAuthProviderID NS_SWIFT_NAME(FacebookAuthProviderID);
+/**
+ @brief A string constant identifying the Facebook sign-in method.
+ */
+extern NSString *const _Nonnull FIRFacebookAuthSignInMethod NS_SWIFT_NAME(FacebookAuthSignInMethod);
+
/** @class FIRFacebookAuthProvider
@brief Utility class for constructing Facebook credentials.
*/
diff --git a/Firebase/Auth/Source/Public/FIRGitHubAuthProvider.h b/Firebase/Auth/Source/Public/FIRGitHubAuthProvider.h
index f0b5dbe..0610427 100644
--- a/Firebase/Auth/Source/Public/FIRGitHubAuthProvider.h
+++ b/Firebase/Auth/Source/Public/FIRGitHubAuthProvider.h
@@ -25,6 +25,12 @@ NS_ASSUME_NONNULL_BEGIN
*/
extern NSString *const FIRGitHubAuthProviderID NS_SWIFT_NAME(GitHubAuthProviderID);
+/**
+ @brief A string constant identifying the GitHub sign-in method.
+ */
+extern NSString *const _Nonnull FIRGitHubAuthSignInMethod NS_SWIFT_NAME(GitHubAuthSignInMethod);
+
+
/** @class FIRGitHubAuthProvider
@brief Utility class for constructing GitHub credentials.
*/
diff --git a/Firebase/Auth/Source/Public/FIRGoogleAuthProvider.h b/Firebase/Auth/Source/Public/FIRGoogleAuthProvider.h
index e80d87e..7d6fa22 100644
--- a/Firebase/Auth/Source/Public/FIRGoogleAuthProvider.h
+++ b/Firebase/Auth/Source/Public/FIRGoogleAuthProvider.h
@@ -25,6 +25,11 @@ NS_ASSUME_NONNULL_BEGIN
*/
extern NSString *const FIRGoogleAuthProviderID NS_SWIFT_NAME(GoogleAuthProviderID);
+/**
+ @brief A string constant identifying the Google sign-in method.
+ */
+extern NSString *const _Nonnull FIRGoogleAuthSignInMethod NS_SWIFT_NAME(GoogleAuthSignInMethod);
+
/** @class FIRGoogleAuthProvider
@brief Utility class for constructing Google Sign In credentials.
*/
diff --git a/Firebase/Auth/Source/Public/FIRPhoneAuthProvider.h b/Firebase/Auth/Source/Public/FIRPhoneAuthProvider.h
index 34db683..bd68e84 100644
--- a/Firebase/Auth/Source/Public/FIRPhoneAuthProvider.h
+++ b/Firebase/Auth/Source/Public/FIRPhoneAuthProvider.h
@@ -27,6 +27,12 @@ NS_ASSUME_NONNULL_BEGIN
*/
extern NSString *const FIRPhoneAuthProviderID NS_SWIFT_NAME(PhoneAuthProviderID);
+/** @var FIRPhoneAuthProviderID
+ @brief A string constant identifying the phone sign-in method.
+ */
+extern NSString *const _Nonnull FIRPhoneAuthSignInMethod NS_SWIFT_NAME(PhoneAuthSignInMethod);
+
+
/** @typedef FIRVerificationResultCallback
@brief The type of block invoked when a request to send a verification code has finished.
diff --git a/Firebase/Auth/Source/Public/FIRTwitterAuthProvider.h b/Firebase/Auth/Source/Public/FIRTwitterAuthProvider.h
index 2ef32f7..a0d1166 100644
--- a/Firebase/Auth/Source/Public/FIRTwitterAuthProvider.h
+++ b/Firebase/Auth/Source/Public/FIRTwitterAuthProvider.h
@@ -25,6 +25,12 @@ NS_ASSUME_NONNULL_BEGIN
*/
extern NSString *const FIRTwitterAuthProviderID NS_SWIFT_NAME(TwitterAuthProviderID);
+/**
+ @brief A string constant identifying the Twitter sign-in method.
+ */
+extern NSString *const _Nonnull FIRTwitterAuthSignInMethod NS_SWIFT_NAME(TwitterAuthSignInMethod);
+
+
/** @class FIRTwitterAuthProvider
@brief Utility class for constructing Twitter credentials.
*/
diff --git a/Firebase/Auth/Source/Public/FIRUser.h b/Firebase/Auth/Source/Public/FIRUser.h
index 1bba710..dd8362d 100644
--- a/Firebase/Auth/Source/Public/FIRUser.h
+++ b/Firebase/Auth/Source/Public/FIRUser.h
@@ -20,6 +20,7 @@
#import "FIRAuthDataResult.h"
#import "FIRUserInfo.h"
+@class FIRAuthTokenResult;
@class FIRPhoneAuthCredential;
@class FIRUserProfileChangeRequest;
@class FIRUserMetadata;
@@ -39,6 +40,21 @@ NS_ASSUME_NONNULL_BEGIN
typedef void (^FIRAuthTokenCallback)(NSString *_Nullable token, NSError *_Nullable error)
NS_SWIFT_NAME(AuthTokenCallback);
+/** @typedef FIRAuthTokenResultCallback
+ @brief The type of block called when a token is ready for use.
+ @see FIRUser.getIDTokenResultWithCompletion:
+ @see FIRUser.getIDTokenResultForcingRefresh:withCompletion:
+
+ @param tokenResult Optionally; an object containing the raw access token string as well as other
+ useful data pertaining to the token.
+ @param error Optionally; the error which occurred - or nil if the request was successful.
+
+ @remarks One of: `token` or `error` will always be non-nil.
+ */
+typedef void (^FIRAuthTokenResultCallback)(FIRAuthTokenResult *_Nullable tokenResult,
+ NSError *_Nullable error)
+ NS_SWIFT_NAME(AuthTokenResultCallback);
+
/** @typedef FIRUserProfileChangeCallback
@brief The type of block called when a user profile change has finished.
@@ -56,7 +72,11 @@ typedef void (^FIRSendEmailVerificationCallback)(NSError *_Nullable error)
NS_SWIFT_NAME(SendEmailVerificationCallback);
/** @class FIRUser
- @brief Represents a user.
+ @brief Represents a user. Firebase Auth does not attempt to validate users
+ when loading them from the keychain. Invalidated users (such as those
+ whose passwords have been changed on another client) are automatically
+ logged out when an auth-dependent operation is attempted or when the
+ ID token is automatically refreshed.
@remarks This class is thread-safe.
*/
NS_SWIFT_NAME(User)
@@ -158,7 +178,7 @@ NS_SWIFT_NAME(User)
updated.
@param phoneNumberCredential The new phone number credential corresponding to the phone number
- to be added to the firebaes account, if a phone number is already linked to the account this
+ to be added to the Firebase account, if a phone number is already linked to the account this
new phone number will replace it.
@param completion Optionally; the block invoked when the user profile change has finished.
Invoked asynchronously on the main thread in the future.
@@ -246,6 +266,34 @@ NS_SWIFT_NAME(User)
- (void)reauthenticateAndRetrieveDataWithCredential:(FIRAuthCredential *) credential
completion:(nullable FIRAuthDataResultCallback) completion;
+/** @fn getIDTokenResultWithCompletion:
+ @brief Retrieves the Firebase authentication token, possibly refreshing it if it has expired.
+
+ @param completion Optionally; the block invoked when the token is available. Invoked
+ asynchronously on the main thread in the future.
+
+ @remarks See `FIRAuthErrors` for a list of error codes that are common to all API methods.
+ */
+- (void)getIDTokenResultWithCompletion:(nullable FIRAuthTokenResultCallback)completion
+ NS_SWIFT_NAME(getIDTokenResult(completion:));
+
+/** @fn getIDTokenResultForcingRefresh:completion:
+ @brief Retrieves the Firebase authentication token, possibly refreshing it if it has expired.
+
+ @param forceRefresh Forces a token refresh. Useful if the token becomes invalid for some reason
+ other than an expiration.
+ @param completion Optionally; the block invoked when the token is available. Invoked
+ asynchronously on the main thread in the future.
+
+ @remarks The authentication token will be refreshed (by making a network request) if it has
+ expired, or if `forceRefresh` is YES.
+
+ @remarks See `FIRAuthErrors` for a list of error codes that are common to all API methods.
+ */
+- (void)getIDTokenResultForcingRefresh:(BOOL)forceRefresh
+ completion:(nullable FIRAuthTokenResultCallback)completion
+ NS_SWIFT_NAME(getIDTokenResult(forcingRefresh:completion:));
+
/** @fn getIDTokenWithCompletion:
@brief Retrieves the Firebase authentication token, possibly refreshing it if it has expired.
diff --git a/Firebase/Auth/Source/Public/FirebaseAuth.h b/Firebase/Auth/Source/Public/FirebaseAuth.h
index 409ac73..cd7e4a4 100644
--- a/Firebase/Auth/Source/Public/FirebaseAuth.h
+++ b/Firebase/Auth/Source/Public/FirebaseAuth.h
@@ -22,6 +22,7 @@
#import "FIRAuthCredential.h"
#import "FIRAuthDataResult.h"
#import "FIRAuthErrors.h"
+#import "FIRAuthTokenResult.h"
#import "FirebaseAuthVersion.h"
#import "FIREmailAuthProvider.h"
#import "FIRFacebookAuthProvider.h"
diff --git a/Firebase/Auth/Source/RPCs/FIRAuthBackend.h b/Firebase/Auth/Source/RPCs/FIRAuthBackend.h
index a82c3a7..5928e71 100644
--- a/Firebase/Auth/Source/RPCs/FIRAuthBackend.h
+++ b/Firebase/Auth/Source/RPCs/FIRAuthBackend.h
@@ -19,6 +19,8 @@
@class FIRAuthRequestConfiguration;
@class FIRCreateAuthURIRequest;
@class FIRCreateAuthURIResponse;
+@class FIREmailLinkSignInRequest;
+@class FIREmailLinkSignInResponse;
@class FIRGetAccountInfoRequest;
@class FIRGetAccountInfoResponse;
@class FIRGetProjectConfigRequest;
@@ -130,6 +132,16 @@ typedef void (^FIRVerifyAssertionResponseCallback)
typedef void (^FIRVerifyPasswordResponseCallback)
(FIRVerifyPasswordResponse *_Nullable response, NSError *_Nullable error);
+/** @typedef FIREmailLinkSigninResponseCallback
+ @brief The type of block used to return the result of a call to the emailLinkSignin
+ endpoint.
+ @param response The received response, if any.
+ @param error The error which occurred, if any.
+ @remarks One of response or error will be non-nil.
+ */
+typedef void (^FIREmailLinkSigninResponseCallback)
+ (FIREmailLinkSignInResponse *_Nullable response, NSError *_Nullable error);
+
/** @typedef FIRVerifyCustomTokenResponseCallback
@brief The type of block used to return the result of a call to the verifyCustomToken
endpoint.
@@ -296,6 +308,15 @@ typedef void (^FIRVerifyClientResponseCallback)
+ (void)verifyPassword:(FIRVerifyPasswordRequest *)request
callback:(FIRVerifyPasswordResponseCallback)callback;
+/** @fn emailLinkSignin:callback:
+ @brief Calls the emailLinkSignin endpoint, which is responsible for authenticating a
+ user through passwordless sign-in.
+ @param request The request parameters.
+ @param callback The callback.
+ */
++ (void)emailLinkSignin:(FIREmailLinkSignInRequest *)request
+ callback:(FIREmailLinkSigninResponseCallback)callback;
+
/** @fn secureToken:callback:
@brief Calls the token endpoint, which is responsible for performing STS token exchanges and
token refreshes.
@@ -461,6 +482,15 @@ typedef void (^FIRVerifyClientResponseCallback)
- (void)verifyPassword:(FIRVerifyPasswordRequest *)request
callback:(FIRVerifyPasswordResponseCallback)callback;
+/** @fn emailLinkSignin:callback:
+ @brief Calls the emailLinkSignin endpoint, which is responsible for authenticating a
+ user through passwordless sign-in.
+ @param request The request parameters.
+ @param callback The callback.
+ */
+- (void)emailLinkSignin:(FIREmailLinkSignInRequest *)request
+ callback:(FIREmailLinkSigninResponseCallback)callback;
+
/** @fn secureToken:callback:
@brief Calls the token endpoint, which is responsible for performing STS token exchanges and
token refreshes.
@@ -472,7 +502,7 @@ typedef void (^FIRVerifyClientResponseCallback)
/** @fn getOOBConfirmationCode:callback:
@brief Calls the getOOBConfirmationCode endpoint, which is responsible for sending email change
- request emails, and password reset emails.
+ request emails, email sign-in link emails, and password reset emails.
@param request The request parameters.
@param callback The callback.
*/
diff --git a/Firebase/Auth/Source/RPCs/FIRAuthBackend.m b/Firebase/Auth/Source/RPCs/FIRAuthBackend.m
index 6b5232b..e380e34 100644
--- a/Firebase/Auth/Source/RPCs/FIRAuthBackend.m
+++ b/Firebase/Auth/Source/RPCs/FIRAuthBackend.m
@@ -51,6 +51,8 @@
#import "FIRVerifyCustomTokenResponse.h"
#import "FIRVerifyPasswordRequest.h"
#import "FIRVerifyPasswordResponse.h"
+#import "FIREmailLinkSignInRequest.h"
+#import "FIREmailLinkSignInResponse.h"
#import "FIRVerifyPhoneNumberRequest.h"
#import "FIRVerifyPhoneNumberResponse.h"
#import <GTMSessionFetcher/GTMSessionFetcher.h>
@@ -430,6 +432,11 @@ static id<FIRAuthBackendImplementation> gBackendImplementation;
[[self implementation] verifyPassword:request callback:callback];
}
++ (void)emailLinkSignin:(FIREmailLinkSignInRequest *)request
+ callback:(FIREmailLinkSigninResponseCallback)callback {
+ [[self implementation] emailLinkSignin:request callback:callback];
+}
+
+ (void)secureToken:(FIRSecureTokenRequest *)request
callback:(FIRSecureTokenResponseCallback)callback {
[[self implementation] secureToken:request callback:callback];
@@ -623,6 +630,18 @@ static id<FIRAuthBackendImplementation> gBackendImplementation;
}];
}
+- (void)emailLinkSignin:(FIREmailLinkSignInRequest *)request
+ callback:(FIREmailLinkSigninResponseCallback)callback {
+ FIREmailLinkSignInResponse *response = [[FIREmailLinkSignInResponse alloc] init];
+ [self postWithRequest:request response:response callback:^(NSError *error) {
+ if (error) {
+ callback(nil, error);
+ } else {
+ callback(response, nil);
+ }
+ }];
+}
+
- (void)secureToken:(FIRSecureTokenRequest *)request
callback:(FIRSecureTokenResponseCallback)callback {
FIRSecureTokenResponse *response = [[FIRSecureTokenResponse alloc] init];
diff --git a/Firebase/Auth/Source/RPCs/FIRCreateAuthURIResponse.h b/Firebase/Auth/Source/RPCs/FIRCreateAuthURIResponse.h
index 9f6cbae..8e8f7b0 100644
--- a/Firebase/Auth/Source/RPCs/FIRCreateAuthURIResponse.h
+++ b/Firebase/Auth/Source/RPCs/FIRCreateAuthURIResponse.h
@@ -51,6 +51,11 @@ NS_ASSUME_NONNULL_BEGIN
*/
@property(nonatomic, copy, readonly, nullable) NSArray<NSString *> *allProviders;
+/** @property signinMethods
+ @brief A list of sign-in methods available for the passed @c identifier.
+ */
+@property(nonatomic, copy, readonly, nullable) NSArray<NSString *> *signinMethods;
+
@end
NS_ASSUME_NONNULL_END
diff --git a/Firebase/Auth/Source/RPCs/FIRCreateAuthURIResponse.m b/Firebase/Auth/Source/RPCs/FIRCreateAuthURIResponse.m
index c6b9d03..6f2937f 100644
--- a/Firebase/Auth/Source/RPCs/FIRCreateAuthURIResponse.m
+++ b/Firebase/Auth/Source/RPCs/FIRCreateAuthURIResponse.m
@@ -16,8 +16,6 @@
#import "FIRCreateAuthURIResponse.h"
-#import "FIRAuthErrorUtils.h"
-
@implementation FIRCreateAuthURIResponse
- (BOOL)setWithDictionary:(NSDictionary *)dictionary
@@ -27,6 +25,7 @@
_registered = [dictionary[@"registered"] boolValue];
_forExistingProvider = [dictionary[@"forExistingProvider"] boolValue];
_allProviders = [dictionary[@"allProviders"] copy];
+ _signinMethods = [dictionary[@"signinMethods"] copy];
return YES;
}
diff --git a/Firebase/Auth/Source/RPCs/FIRDeleteAccountResponse.m b/Firebase/Auth/Source/RPCs/FIRDeleteAccountResponse.m
index 671a41f..ae98175 100644
--- a/Firebase/Auth/Source/RPCs/FIRDeleteAccountResponse.m
+++ b/Firebase/Auth/Source/RPCs/FIRDeleteAccountResponse.m
@@ -16,8 +16,6 @@
#import "FIRDeleteAccountResponse.h"
-#import "FIRAuthErrorUtils.h"
-
@implementation FIRDeleteAccountResponse
- (BOOL)setWithDictionary:(NSDictionary *)dictionary
diff --git a/Firebase/Auth/Source/RPCs/FIREmailLinkSignInRequest.h b/Firebase/Auth/Source/RPCs/FIREmailLinkSignInRequest.h
new file mode 100644
index 0000000..e1b10d8
--- /dev/null
+++ b/Firebase/Auth/Source/RPCs/FIREmailLinkSignInRequest.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2017 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/Foundation.h>
+
+#import "FIRAuthRPCRequest.h"
+#import "FIRIdentityToolkitRequest.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+/** @class FIREmailLinkSignInRequest
+ @brief Represents the parameters for the emailLinkSignin endpoint.
+ */
+@interface FIREmailLinkSignInRequest : FIRIdentityToolkitRequest <FIRAuthRPCRequest>
+
+#pragma mark - Components of "postBody"
+
+/** @property email
+ @brief The email identifier used to complete the email link sign-in.
+ */
+@property(nonatomic, copy, readonly) NSString *email;
+
+/** @property oobCode
+ @brief The OOB code used to complete the email link sign-in flow.
+ */
+@property(nonatomic, copy, readonly) NSString *oobCode;
+
+/** @property idToken
+ @brief The ID Token code potentially used to complete the email link sign-in flow.
+ */
+@property(nonatomic, copy) NSString *IDToken;
+
+/** @fn initWithEndpoint:requestConfiguration:
+ @brief Please use initWithProviderID:requestConfifuration instead.
+ */
+- (instancetype)initWithEndpoint:(NSString *)endpoint
+ requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration NS_UNAVAILABLE;
+
+/** @fn initWithProviderID:requestConfifuration
+ @brief Designated initializer.
+ @param email The email identifier used to complete hte email link sign-in flow.
+ @param oobCode The OOB code used to complete the email link sign-in flow.
+ @param requestConfiguration An object containing configurations to be added to the request.
+
+ */
+- (instancetype)initWithEmail:(NSString *)email
+ oobCode:(NSString *)oobCode
+ requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration
+ NS_DESIGNATED_INITIALIZER;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firebase/Auth/Source/RPCs/FIREmailLinkSignInRequest.m b/Firebase/Auth/Source/RPCs/FIREmailLinkSignInRequest.m
new file mode 100644
index 0000000..9787e8e
--- /dev/null
+++ b/Firebase/Auth/Source/RPCs/FIREmailLinkSignInRequest.m
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2017 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 "FIREmailLinkSignInRequest.h"
+
+/** @var kEmailLinkSigninEndpoint
+ @brief The "EmailLinkSignin" endpoint.
+ */
+static NSString *const kEmailLinkSigninEndpoint = @"emailLinkSignin";
+
+/** @var kEmailKey
+ @brief The key for the "identifier" value in the request.
+ */
+static NSString *const kEmailKey = @"email";
+
+/** @var kEmailLinkKey
+ @brief The key for the "emailLink" value in the request.
+ */
+static NSString *const kOOBCodeKey = @"oobCode";
+
+/** @var kIDTokenKey
+ @brief The key for the "IDToken" value in the request.
+ */
+static NSString *const kIDTokenKey = @"idToken";
+
+/** @var kPostBodyKey
+ @brief The key for the "postBody" value in the request.
+ */
+static NSString *const kPostBodyKey = @"postBody";
+
+@implementation FIREmailLinkSignInRequest
+
+- (instancetype)initWithEmail:(NSString *)email
+ oobCode:(NSString *)oobCode
+ requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration {
+ self = [super initWithEndpoint:kEmailLinkSigninEndpoint
+ requestConfiguration:requestConfiguration];
+ if (self) {
+ _email = email;
+ _oobCode = oobCode;
+ }
+ return self;
+}
+
+- (nullable id)unencodedHTTPRequestBodyWithError:(NSError *_Nullable *_Nullable)error {
+ NSMutableDictionary *postBody = [@{
+ kEmailKey : _email,
+ kOOBCodeKey : _oobCode,
+ } mutableCopy];
+
+ if (_IDToken) {
+ postBody[kIDTokenKey] = _IDToken;
+ }
+ return postBody;
+}
+
+@end
diff --git a/Firebase/Auth/Source/RPCs/FIREmailLinkSignInResponse.h b/Firebase/Auth/Source/RPCs/FIREmailLinkSignInResponse.h
new file mode 100644
index 0000000..df0a127
--- /dev/null
+++ b/Firebase/Auth/Source/RPCs/FIREmailLinkSignInResponse.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2017 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/Foundation.h>
+
+#import "FIRAuthRPCResponse.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+/** @class FIRVerifyAssertionResponse
+ @brief Represents the response from the emailLinkSignin endpoint.
+ */
+@interface FIREmailLinkSignInResponse : NSObject<FIRAuthRPCResponse>
+
+/** @property IDToken
+ @brief The ID token in the email link sign-in response.
+ */
+@property(nonatomic, copy, readonly) NSString *IDToken;
+
+/** @property email
+ @brief The email returned by the IdP.
+ */
+@property(nonatomic, strong, readonly, nullable) NSString *email;
+
+/** @property refreshToken
+ @brief The refreshToken returned by the server.
+ */
+@property(nonatomic, strong, readonly, nullable) NSString *refreshToken;
+
+/** @property approximateExpirationDate
+ @brief The approximate expiration date of the access token.
+ */
+@property(nonatomic, copy, readonly, nullable) NSDate *approximateExpirationDate;
+
+/** @property isNewUser
+ @brief Flag indicating that the user signing in is a new user and not a returning user.
+ */
+@property(nonatomic, assign) BOOL isNewUser;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firebase/Auth/Source/RPCs/FIREmailLinkSignInResponse.m b/Firebase/Auth/Source/RPCs/FIREmailLinkSignInResponse.m
new file mode 100644
index 0000000..cd36d41
--- /dev/null
+++ b/Firebase/Auth/Source/RPCs/FIREmailLinkSignInResponse.m
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2017 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 "FIREmailLinkSignInResponse.h"
+
+@implementation FIREmailLinkSignInResponse
+
+- (BOOL)setWithDictionary:(NSDictionary *)dictionary
+ error:(NSError *_Nullable *_Nullable)error {
+ _email = [dictionary[@"email"] copy];
+ _IDToken = [dictionary[@"idToken"] copy];
+ _isNewUser = [dictionary[@"isNewUser"] boolValue];
+ _refreshToken = [dictionary[@"refreshToken"] copy];
+ _approximateExpirationDate = [dictionary[@"expiresIn"] isKindOfClass:[NSString class]] ?
+ [NSDate dateWithTimeIntervalSinceNow:[dictionary[@"expiresIn"] doubleValue]] : nil;
+ return YES;
+}
+
+@end
diff --git a/Firebase/Auth/Source/RPCs/FIRGetOOBConfirmationCodeRequest.h b/Firebase/Auth/Source/RPCs/FIRGetOOBConfirmationCodeRequest.h
index abd59b4..751cfe7 100644
--- a/Firebase/Auth/Source/RPCs/FIRGetOOBConfirmationCodeRequest.h
+++ b/Firebase/Auth/Source/RPCs/FIRGetOOBConfirmationCodeRequest.h
@@ -36,6 +36,11 @@ typedef NS_ENUM(NSInteger, FIRGetOOBConfirmationCodeRequestType) {
@brief Requests an email verification code.
*/
FIRGetOOBConfirmationCodeRequestTypeVerifyEmail,
+
+ /** @var FIRGetOOBConfirmationCodeRequestTypeEmailLink
+ @brief Requests an email sign-in link.
+ */
+ FIRGetOOBConfirmationCodeRequestTypeEmailLink,
};
/** @enum FIRGetOOBConfirmationCodeRequest
@@ -91,7 +96,7 @@ typedef NS_ENUM(NSInteger, FIRGetOOBConfirmationCodeRequestType) {
*/
@property(assign, nonatomic) BOOL handleCodeInApp;
-/** @fn passwordResetRequestWithEmail:APIKey:
+/** @fn passwordResetRequestWithEmail:actionCodeSettings:requestConfiguration:
@brief Creates a password reset request.
@param email The user's email address.
@param actionCodeSettings An object of FIRActionCodeSettings which specifies action code
@@ -104,7 +109,7 @@ typedef NS_ENUM(NSInteger, FIRGetOOBConfirmationCodeRequestType) {
actionCodeSettings:(nullable FIRActionCodeSettings *)actionCodeSettings
requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration;
-/** @fn verifyEmailRequestWithAccessToken:APIKey:
+/** @fn verifyEmailRequestWithAccessToken:actionCodeSettings:requestConfiguration:
@brief Creates a password reset request.
@param accessToken The user's STS Access Token.
@param actionCodeSettings An object of FIRActionCodeSettings which specifies action code
@@ -117,6 +122,19 @@ typedef NS_ENUM(NSInteger, FIRGetOOBConfirmationCodeRequestType) {
actionCodeSettings:(nullable FIRActionCodeSettings *)actionCodeSettings
requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration;
+/** @fn signInWithEmailLinkRequest:actionCodeSettings:requestConfiguration:
+ @brief Creates a sign-in with email link.
+ @param email The user's email address.
+ @param actionCodeSettings An object of FIRActionCodeSettings which specifies action code
+ settings to be applied to the email sign-in link.
+ @param requestConfiguration An object containing configurations to be added to the request.
+ @return An email sign-in link request.
+ */
++ (nullable FIRGetOOBConfirmationCodeRequest *)
+ signInWithEmailLinkRequest:(NSString *)email
+ actionCodeSettings:(nullable FIRActionCodeSettings *)actionCodeSettings
+ requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration;
+
/** @fn init
@brief Please use a factory method.
*/
diff --git a/Firebase/Auth/Source/RPCs/FIRGetOOBConfirmationCodeRequest.m b/Firebase/Auth/Source/RPCs/FIRGetOOBConfirmationCodeRequest.m
index 653eddd..438f24b 100644
--- a/Firebase/Auth/Source/RPCs/FIRGetOOBConfirmationCodeRequest.m
+++ b/Firebase/Auth/Source/RPCs/FIRGetOOBConfirmationCodeRequest.m
@@ -79,6 +79,11 @@ static NSString *const kCanHandleCodeInAppKey = @"canHandleCodeInApp";
*/
static NSString *const kPasswordResetRequestTypeValue = @"PASSWORD_RESET";
+/** @var kEmailLinkSignInTypeValue
+ @brief The value for the "EMAIL_SIGNIN" request type.
+ */
+static NSString *const kEmailLinkSignInTypeValue= @"EMAIL_SIGNIN";
+
/** @var kVerifyEmailRequestTypeValue
@brief The value for the "VERIFY_EMAIL" request type.
*/
@@ -116,6 +121,8 @@ static NSString *const kVerifyEmailRequestTypeValue = @"VERIFY_EMAIL";
return kPasswordResetRequestTypeValue;
case FIRGetOOBConfirmationCodeRequestTypeVerifyEmail:
return kVerifyEmailRequestTypeValue;
+ case FIRGetOOBConfirmationCodeRequestTypeEmailLink:
+ return kEmailLinkSignInTypeValue;
// No default case so that we get a compiler warning if a new value was added to the enum.
}
}
@@ -142,6 +149,17 @@ static NSString *const kVerifyEmailRequestTypeValue = @"VERIFY_EMAIL";
requestConfiguration:requestConfiguration];
}
++ (FIRGetOOBConfirmationCodeRequest *)
+ signInWithEmailLinkRequest:(NSString *)email
+ actionCodeSettings:(nullable FIRActionCodeSettings *)actionCodeSettings
+ requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration {
+ return [[self alloc] initWithRequestType:FIRGetOOBConfirmationCodeRequestTypeEmailLink
+ email:email
+ accessToken:nil
+ actionCodeSettings:actionCodeSettings
+ requestConfiguration:requestConfiguration];
+}
+
- (nullable instancetype)initWithRequestType:(FIRGetOOBConfirmationCodeRequestType)requestType
email:(nullable NSString *)email
accessToken:(nullable NSString *)accessToken
@@ -180,6 +198,12 @@ static NSString *const kVerifyEmailRequestTypeValue = @"VERIFY_EMAIL";
body[kIDTokenKey] = _accessToken;
}
+ // For email sign-in link requests, we only need an email address in addition to the already
+ // required fields.
+ if (_requestType == FIRGetOOBConfirmationCodeRequestTypeEmailLink) {
+ body[kEmailKey] = _email;
+ }
+
if (_continueURL) {
body[kContinueURLKey] = _continueURL;
}
diff --git a/Firebase/Auth/Source/RPCs/FIRGetOOBConfirmationCodeResponse.m b/Firebase/Auth/Source/RPCs/FIRGetOOBConfirmationCodeResponse.m
index bd028f1..0b6c416 100644
--- a/Firebase/Auth/Source/RPCs/FIRGetOOBConfirmationCodeResponse.m
+++ b/Firebase/Auth/Source/RPCs/FIRGetOOBConfirmationCodeResponse.m
@@ -16,8 +16,6 @@
#import "FIRGetOOBConfirmationCodeResponse.h"
-#import "FIRAuthErrorUtils.h"
-
NS_ASSUME_NONNULL_BEGIN
/** @var kOOBCodeKey
diff --git a/Firebase/Auth/Source/RPCs/FIRResetPasswordResponse.m b/Firebase/Auth/Source/RPCs/FIRResetPasswordResponse.m
index 9a1556b..6092cfe 100644
--- a/Firebase/Auth/Source/RPCs/FIRResetPasswordResponse.m
+++ b/Firebase/Auth/Source/RPCs/FIRResetPasswordResponse.m
@@ -16,8 +16,6 @@
#import "FIRResetPasswordResponse.h"
-#import "FIRAuthErrorUtils.h"
-
@implementation FIRResetPasswordResponse
- (BOOL)setWithDictionary:(NSDictionary *)dictionary
diff --git a/Firebase/Auth/Source/RPCs/FIRSetAccountInfoResponse.m b/Firebase/Auth/Source/RPCs/FIRSetAccountInfoResponse.m
index 6e228eb..ff9c7a6 100644
--- a/Firebase/Auth/Source/RPCs/FIRSetAccountInfoResponse.m
+++ b/Firebase/Auth/Source/RPCs/FIRSetAccountInfoResponse.m
@@ -16,8 +16,6 @@
#import "FIRSetAccountInfoResponse.h"
-#import "FIRAuthErrorUtils.h"
-
@implementation FIRSetAccountInfoResponseProviderUserInfo
- (instancetype)initWithDictionary:(NSDictionary *)dictionary {
diff --git a/Firebase/Auth/Source/RPCs/FIRSignUpNewUserResponse.m b/Firebase/Auth/Source/RPCs/FIRSignUpNewUserResponse.m
index 7e58c5d..2071e07 100644
--- a/Firebase/Auth/Source/RPCs/FIRSignUpNewUserResponse.m
+++ b/Firebase/Auth/Source/RPCs/FIRSignUpNewUserResponse.m
@@ -16,8 +16,6 @@
#import "FIRSignUpNewUserResponse.h"
-#import "FIRAuthErrorUtils.h"
-
@implementation FIRSignUpNewUserResponse
- (BOOL)setWithDictionary:(NSDictionary *)dictionary
diff --git a/Firebase/Auth/Source/RPCs/FIRVerifyAssertionResponse.m b/Firebase/Auth/Source/RPCs/FIRVerifyAssertionResponse.m
index 8c970c8..5ee39fa 100644
--- a/Firebase/Auth/Source/RPCs/FIRVerifyAssertionResponse.m
+++ b/Firebase/Auth/Source/RPCs/FIRVerifyAssertionResponse.m
@@ -16,8 +16,6 @@
#import "FIRVerifyAssertionResponse.h"
-#import "FIRAuthErrorUtils.h"
-
@implementation FIRVerifyAssertionResponse
- (BOOL)setWithDictionary:(NSDictionary *)dictionary
diff --git a/Firebase/Auth/Source/RPCs/FIRVerifyCustomTokenResponse.m b/Firebase/Auth/Source/RPCs/FIRVerifyCustomTokenResponse.m
index 8a87141..b6c3818 100644
--- a/Firebase/Auth/Source/RPCs/FIRVerifyCustomTokenResponse.m
+++ b/Firebase/Auth/Source/RPCs/FIRVerifyCustomTokenResponse.m
@@ -16,8 +16,6 @@
#import "FIRVerifyCustomTokenResponse.h"
-#import "FIRAuthErrorUtils.h"
-
@implementation FIRVerifyCustomTokenResponse
- (BOOL)setWithDictionary:(NSDictionary *)dictionary
diff --git a/Firebase/Auth/Source/RPCs/FIRVerifyPasswordResponse.m b/Firebase/Auth/Source/RPCs/FIRVerifyPasswordResponse.m
index d2c4a7c..71b4edd 100644
--- a/Firebase/Auth/Source/RPCs/FIRVerifyPasswordResponse.m
+++ b/Firebase/Auth/Source/RPCs/FIRVerifyPasswordResponse.m
@@ -16,8 +16,6 @@
#import "FIRVerifyPasswordResponse.h"
-#import "FIRAuthErrorUtils.h"
-
@implementation FIRVerifyPasswordResponse
- (BOOL)setWithDictionary:(NSDictionary *)dictionary
diff --git a/Firebase/Core/CHANGELOG.md b/Firebase/Core/CHANGELOG.md
new file mode 100644
index 0000000..f4707ea
--- /dev/null
+++ b/Firebase/Core/CHANGELOG.md
@@ -0,0 +1,35 @@
+# Unreleased
+- [changed] Removed `UIKit` import from `FIRApp.h`.
+- [changed] Removed deprecated methods.
+
+# 2018-03-06 -- v4.0.16 -- M22
+- [changed] Addresses CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF warnings that surface in newer versions of Xcode and CocoaPods.
+
+# 2018-01-18 -- v4.0.14 -- M21.1
+- [changed] Removed AppKit dependency for community macOS build.
+
+# 2017-11-30 -- v4.0.12 -- M20.2
+- [fixed] Removed `FIR_SWIFT_NAME` macro, replaced with proper `NS_SWIFT_NAME`.
+
+# 2017-11-14 -- v4.0.11 -- M20.1
+- [feature] Added `-FIRLoggerForceSTDERR` launch argument flag to force STDERR
+ output for all Firebase logging
+
+# 2017-08-25 -- v4.0.6 -- M18.1
+- [changed] Removed unused method
+
+# 2017-08-09 -- v4.0.5 -- M18.0
+- [changed] Log an error for an incorrectly configured bundle ID instead of an info
+ message.
+
+# 2017-07-12 -- v4.0.4 -- M17.4
+- [changed] Switched to using the https://cocoapods.org/pods/nanopb pod instead of
+ linking nanopb in (preventing linker conflicts).
+
+# 2017-06-06 -- v4.0.1 -- M17.1
+- [fixed] Improved diagnostic messages for Swift
+
+# 2017-05-17 -- v4.0.0 -- M17
+- [changed] Update FIROptions to have a simpler constructor and mutable properties
+- [feature] Swift naming update, FIR prefix dropped
+- [changed] Internal cleanup for open source release
diff --git a/Firebase/Core/FIRApp.m b/Firebase/Core/FIRApp.m
index 242fd79..c43db6e 100644
--- a/Firebase/Core/FIRApp.m
+++ b/Firebase/Core/FIRApp.m
@@ -84,6 +84,7 @@ static NSString *const kPlistURL = @"https://console.firebase.google.com/";
static NSMutableDictionary *sAllApps;
static FIRApp *sDefaultApp;
+static NSMutableDictionary *sLibraryVersions;
+ (void)configure {
FIROptions *options = [FIROptions defaultOptions];
@@ -132,22 +133,6 @@ static FIRApp *sDefaultApp;
[FIRApp sendNotificationsToSDKs:sDefaultApp];
sDefaultApp.alreadySentConfigureNotification = YES;
}
-
- if (![FIRAppEnvironmentUtil isFromAppStore]) {
- // Support for iOS 7 has been deprecated, but will continue to function for the time being.
- // Log a notice for developers who are still targeting iOS 7 as the minimum OS version
- // supported.
- dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
- NSDictionary<NSString *, id> *info = [[NSBundle mainBundle] infoDictionary];
-
- NSString *minVersion = info[@"MinimumOSVersion"];
- if ([minVersion hasPrefix:@"7."]) {
- FIRLogNotice(kFIRLoggerCore, @"I-COR000026", @"Support for iOS 7 is deprecated and will "
- @"stop working in the future. Please upgrade your app to target iOS 8 or "
- @"above.");
- }
- });
- }
}
}
@@ -227,6 +212,8 @@ static FIRApp *sDefaultApp;
sDefaultApp = nil;
[sAllApps removeAllObjects];
sAllApps = nil;
+ [sLibraryVersions removeAllObjects];
+ sLibraryVersions = nil;
}
- (void)deleteApp:(FIRAppVoidBoolCallback)completion {
@@ -311,13 +298,6 @@ static FIRApp *sDefaultApp;
return NO;
}
- if (NSClassFromString(@"FIRAppIndexing") != nil) {
- FIRLogDebug(kFIRLoggerCore, @"I-COR000024",
- @"Firebase App Indexing on iOS is deprecated. "
- @"You don't need to take any action at this time. Learn more about Firebase App "
- @"Indexing at https://firebase.google.com/docs/app-indexing/.");
- }
-
// Initialize the Analytics once there is a valid options under default app. Analytics should
// always initialize first by itself before the other SDKs.
if ([self.name isEqualToString:kFIRDefaultAppName]) {
@@ -367,7 +347,9 @@ static FIRApp *sDefaultApp;
NSLocalizedRecoverySuggestionErrorKey :
@"Check formatting and location of GoogleService-Info.plist."
};
- return FIRCreateError(kFirebaseCoreErrorDomain, FIRErrorCodeInvalidPlistFile, errorDict);
+ return [NSError errorWithDomain:kFirebaseCoreErrorDomain
+ code:FIRErrorCodeInvalidPlistFile
+ userInfo:errorDict];
}
+ (NSError *)errorForSubspecConfigurationFailureWithDomain:(NSString *)domain
@@ -378,7 +360,7 @@ static FIRApp *sDefaultApp;
[NSString stringWithFormat:@"Configuration failed for service %@.", service];
NSDictionary *errorDict =
@{NSLocalizedDescriptionKey : description, NSLocalizedFailureReasonErrorKey : reason};
- return FIRCreateError(domain, code, errorDict);
+ return [NSError errorWithDomain:domain code:code userInfo:errorDict];
}
+ (NSError *)errorForInvalidAppID {
@@ -388,13 +370,47 @@ static FIRApp *sDefaultApp;
@"Check formatting and location of GoogleService-Info.plist or GoogleAppID set in the "
@"customized options."
};
- return FIRCreateError(kFirebaseCoreErrorDomain, FIRErrorCodeInvalidAppID, errorDict);
+ return [NSError errorWithDomain:kFirebaseCoreErrorDomain
+ code:FIRErrorCodeInvalidAppID
+ userInfo:errorDict];
}
+ (BOOL)isDefaultAppConfigured {
return (sDefaultApp != nil);
}
++ (void)registerLibrary:(nonnull NSString *)library withVersion:(nonnull NSString *)version {
+ // Create the set of characters which aren't allowed, only if this feature is used.
+ NSMutableCharacterSet *allowedSet = [NSMutableCharacterSet alphanumericCharacterSet];
+ [allowedSet addCharactersInString:@"-_."];
+ NSCharacterSet *disallowedSet = [allowedSet invertedSet];
+ // Make sure the library name and version strings do not contain unexpected characters, and
+ // add the name/version pair to the dictionary.
+ if ([library rangeOfCharacterFromSet:disallowedSet].location == NSNotFound &&
+ [version rangeOfCharacterFromSet:disallowedSet].location == NSNotFound) {
+ if (!sLibraryVersions) {
+ sLibraryVersions = [[NSMutableDictionary alloc] init];
+ }
+ sLibraryVersions[library] = version;
+ } else {
+ FIRLogError(kFIRLoggerCore, @"I-COR000027",
+ @"The library name (%@) or version number (%@) contain illegal characters. "
+ @"Only alphanumeric, dash, underscore and period characters are allowed.",
+ library, version);
+ }
+}
+
++ (NSString *)firebaseUserAgent {
+ NSMutableArray<NSString *> *libraries =
+ [[NSMutableArray<NSString *> alloc] initWithCapacity:sLibraryVersions.count];
+ for (NSString *libraryName in sLibraryVersions) {
+ [libraries
+ addObject:[NSString stringWithFormat:@"%@/%@", libraryName, sLibraryVersions[libraryName]]];
+ }
+ [libraries sortUsingSelector:@selector(localizedCaseInsensitiveCompare:)];
+ return [libraries componentsJoinedByString:@" "];
+}
+
- (void)checkExpectedBundleID {
NSArray *bundles = [FIRBundleUtil relevantBundles];
NSString *expectedBundleID = [self expectedBundleID];
diff --git a/Firebase/Core/FIRConfiguration.m b/Firebase/Core/FIRConfiguration.m
index 02617ef..cd64862 100644
--- a/Firebase/Core/FIRConfiguration.m
+++ b/Firebase/Core/FIRConfiguration.m
@@ -35,12 +35,6 @@ extern void FIRSetLoggerLevel(FIRLoggerLevel loggerLevel);
return self;
}
-// This is deprecated, use setLoggerLevel instead.
-- (void)setLogLevel:(FIRLogLevel)logLevel {
- NSAssert(logLevel <= kFIRLogLevelMax, @"Invalid log level, %ld", (long)logLevel);
- _logLevel = logLevel;
-}
-
- (void)setLoggerLevel:(FIRLoggerLevel)loggerLevel {
NSAssert(loggerLevel <= FIRLoggerLevelMax && loggerLevel >= FIRLoggerLevelMin,
@"Invalid logger level, %ld", (long)loggerLevel);
diff --git a/Firebase/Core/FIRErrors.m b/Firebase/Core/FIRErrors.m
index 3c7e39a..6d6d52d 100644
--- a/Firebase/Core/FIRErrors.m
+++ b/Firebase/Core/FIRErrors.m
@@ -27,7 +27,3 @@ NSString *const kFirebaseDurableDeepLinkErrorDomain = @"com.firebase.durabledeep
NSString *const kFirebaseInstanceIDErrorDomain = @"com.firebase.instanceid";
NSString *const kFirebasePerfErrorDomain = @"com.firebase.perf";
NSString *const kFirebaseStorageErrorDomain = @"com.firebase.storage";
-
-NSError *FIRCreateError(NSString *domain, enum FIRErrorCode code, NSDictionary *userInfo) {
- return [NSError errorWithDomain:domain code:code userInfo:userInfo];
-}
diff --git a/Firebase/Core/FIRLogger.m b/Firebase/Core/FIRLogger.m
index ba22bde..15c0013 100644
--- a/Firebase/Core/FIRLogger.m
+++ b/Firebase/Core/FIRLogger.m
@@ -15,6 +15,7 @@
#import "Private/FIRLogger.h"
#import "FIRLoggerLevel.h"
+#import "Private/FIRVersion.h"
#import "third_party/FIRAppEnvironmentUtil.h"
#include <asl.h>
@@ -39,6 +40,7 @@ FIRLoggerService kFIRLoggerMessaging = @"[Firebase/Messaging]";
FIRLoggerService kFIRLoggerPerf = @"[Firebase/Performance]";
FIRLoggerService kFIRLoggerRemoteConfig = @"[Firebase/RemoteConfig]";
FIRLoggerService kFIRLoggerStorage = @"[Firebase/Storage]";
+FIRLoggerService kFIRLoggerSwizzler = @"[FirebaseSwizzlingUtilities]";
/// Arguments passed on launch.
NSString *const kFIRDisableDebugModeApplicationArgument = @"-FIRDebugDisabled";
@@ -164,13 +166,13 @@ void FIRSetLoggerLevel(FIRLoggerLevel loggerLevel) {
return;
}
FIRLoggerInitializeASL();
- dispatch_async(sFIRClientQueue, ^{
- // We should not raise the logger level if we are running from App Store.
- if (loggerLevel >= FIRLoggerLevelNotice && [FIRAppEnvironmentUtil isFromAppStore]) {
- return;
- }
+ // We should not raise the logger level if we are running from App Store.
+ if (loggerLevel >= FIRLoggerLevelNotice && [FIRAppEnvironmentUtil isFromAppStore]) {
+ return;
+ }
- sFIRLoggerMaximumLevel = loggerLevel;
+ sFIRLoggerMaximumLevel = loggerLevel;
+ dispatch_async(sFIRClientQueue, ^{
asl_set_filter(sFIRLoggerClient, ASL_FILTER_MASK_UPTO(loggerLevel));
});
}
@@ -229,7 +231,8 @@ void FIRLogBasic(FIRLoggerLevel level,
NSCAssert(numberOfMatches == 1, @"Incorrect message code format.");
#endif
NSString *logMsg = [[NSString alloc] initWithFormat:message arguments:args_ptr];
- logMsg = [NSString stringWithFormat:@"%@[%@] %@", service, messageCode, logMsg];
+ logMsg = [NSString
+ stringWithFormat:@"%s - %@[%@] %@", FirebaseVersionString, service, messageCode, logMsg];
dispatch_async(sFIRClientQueue, ^{
asl_log(sFIRLoggerClient, NULL, level, "%s", logMsg.UTF8String);
});
diff --git a/Firebase/Core/FIRMutableDictionary.m b/Firebase/Core/FIRMutableDictionary.m
index 1d6ef3a..31941bc 100644
--- a/Firebase/Core/FIRMutableDictionary.m
+++ b/Firebase/Core/FIRMutableDictionary.m
@@ -37,7 +37,7 @@
- (NSString *)description {
__block NSString *description;
dispatch_sync(_queue, ^{
- description = _objects.description;
+ description = self->_objects.description;
});
return description;
}
@@ -45,33 +45,33 @@
- (id)objectForKey:(id)key {
__block id object;
dispatch_sync(_queue, ^{
- object = _objects[key];
+ object = self->_objects[key];
});
return object;
}
- (void)setObject:(id)object forKey:(id<NSCopying>)key {
dispatch_async(_queue, ^{
- _objects[key] = object;
+ self->_objects[key] = object;
});
}
- (void)removeObjectForKey:(id)key {
dispatch_async(_queue, ^{
- [_objects removeObjectForKey:key];
+ [self->_objects removeObjectForKey:key];
});
}
- (void)removeAllObjects {
dispatch_async(_queue, ^{
- [_objects removeAllObjects];
+ [self->_objects removeAllObjects];
});
}
- (NSUInteger)count {
__block NSUInteger count;
dispatch_sync(_queue, ^{
- count = _objects.count;
+ count = self->_objects.count;
});
return count;
}
@@ -89,7 +89,7 @@
- (NSDictionary *)dictionary {
__block NSDictionary *dictionary;
dispatch_sync(_queue, ^{
- dictionary = [_objects copy];
+ dictionary = [self->_objects copy];
});
return dictionary;
}
diff --git a/Firebase/Core/FIRNetworkConstants.m b/Firebase/Core/FIRNetworkConstants.m
index a92e0e2..c958201 100644
--- a/Firebase/Core/FIRNetworkConstants.m
+++ b/Firebase/Core/FIRNetworkConstants.m
@@ -14,7 +14,7 @@
#import "Private/FIRNetworkConstants.h"
-@import Foundation;
+#import <Foundation/Foundation.h>
NSString *const kFIRNetworkBackgroundSessionConfigIDPrefix =
@"com.firebase.network.background-upload";
diff --git a/Firebase/Core/FIRNetworkURLSession.m b/Firebase/Core/FIRNetworkURLSession.m
index d9c6f3a..6b5ce3a 100644
--- a/Firebase/Core/FIRNetworkURLSession.m
+++ b/Firebase/Core/FIRNetworkURLSession.m
@@ -314,11 +314,11 @@
if (allow) {
completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
} else {
- [_loggerDelegate
+ [self->_loggerDelegate
firNetwork_logWithLevel:kFIRNetworkLogLevelDebug
messageCode:kFIRNetworkMessageCodeURLSession007
message:@"Cancelling authentication challenge for host. Host"
- context:_request.URL];
+ context:self->_request.URL];
completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
}
};
@@ -344,10 +344,10 @@
}
if (trustError != errSecSuccess) {
- [_loggerDelegate firNetwork_logWithLevel:kFIRNetworkLogLevelError
- messageCode:kFIRNetworkMessageCodeURLSession008
- message:@"Cannot evaluate server trust. Error, host"
- contexts:@[ @(trustError), _request.URL ]];
+ [self->_loggerDelegate firNetwork_logWithLevel:kFIRNetworkLogLevelError
+ messageCode:kFIRNetworkMessageCodeURLSession008
+ message:@"Cannot evaluate server trust. Error, host"
+ contexts:@[ @(trustError), self->_request.URL ]];
shouldAllow = NO;
} else {
// Having a trust level "unspecified" by the user is the usual result, described at
@@ -428,6 +428,7 @@
- (NSURLSessionConfiguration *)backgroundSessionConfigWithSessionID:(NSString *)sessionID {
#if (TARGET_OS_OSX && defined(MAC_OS_X_VERSION_10_10) && \
MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_10) || \
+ TARGET_OS_TV || \
(TARGET_OS_IOS && defined(__IPHONE_8_0) && __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0)
// iOS 8/10.10 builds require the new backgroundSessionConfiguration method name.
@@ -650,7 +651,7 @@
if (handler) {
dispatch_async(dispatch_get_main_queue(), ^{
- handler(response, data, _sessionID, error);
+ handler(response, data, self->_sessionID, error);
});
}
}
diff --git a/Firebase/Core/FIROptions.m b/Firebase/Core/FIROptions.m
index 841c70b..8dcd749 100644
--- a/Firebase/Core/FIROptions.m
+++ b/Firebase/Core/FIROptions.m
@@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+#import "Private/FIRAppInternal.h"
#import "Private/FIRBundleUtil.h"
#import "Private/FIRErrors.h"
#import "Private/FIRLogger.h"
@@ -42,7 +43,7 @@ NSString *const kFIRIsSignInEnabled = @"IS_SIGNIN_ENABLED";
NSString *const kFIRLibraryVersionID =
@"4" // Major version (one or more digits)
@"00" // Minor version (exactly 2 digits)
- @"12" // Build number (exactly 2 digits)
+ @"20" // Build number (exactly 2 digits)
@"000"; // Fixed "000"
// Plist file name.
NSString *const kServiceInfoFileName = @"GoogleService-Info";
@@ -62,12 +63,19 @@ NSString *const kFIRExceptionBadModification =
@property(nonatomic, readwrite) NSMutableDictionary *optionsDictionary;
/**
- * Combination of analytics options from both the main plist and the GoogleService-Info.plist.
+ * Calls `analyticsOptionsDictionaryWithInfoDictionary:` using [NSBundle mainBundle].infoDictionary.
+ * It combines analytics options from both the infoDictionary and the GoogleService-Info.plist.
* Values which are present in the main plist override values from the GoogleService-Info.plist.
*/
@property(nonatomic, readonly) NSDictionary *analyticsOptionsDictionary;
/**
+ * Combination of analytics options from both the infoDictionary and the GoogleService-Info.plist.
+ * Values which are present in the infoDictionary override values from the GoogleService-Info.plist.
+ */
+- (NSDictionary *)analyticsOptionsDictionaryWithInfoDictionary:(NSDictionary *)infoDictionary;
+
+/**
* Throw exception if editing is locked when attempting to modify an option.
*/
- (void)checkEditingLocked;
@@ -101,6 +109,30 @@ static NSDictionary *sDefaultOptionsDictionary = nil;
#pragma mark - Private class methods
++ (void)load {
+ // Report FirebaseCore version for useragent string
+ NSRange major = NSMakeRange(0, 1);
+ NSRange minor = NSMakeRange(1, 2);
+ NSRange patch = NSMakeRange(3, 2);
+ [FIRApp
+ registerLibrary:@"fire-ios"
+ withVersion:[NSString stringWithFormat:@"%@.%d.%d",
+ [kFIRLibraryVersionID substringWithRange:major],
+ [[kFIRLibraryVersionID substringWithRange:minor]
+ intValue],
+ [[kFIRLibraryVersionID substringWithRange:patch]
+ intValue]]];
+ NSDictionary<NSString *, id> *info = [[NSBundle mainBundle] infoDictionary];
+ NSString *xcodeVersion = info[@"DTXcodeBuild"];
+ NSString *sdkVersion = info[@"DTSDKBuild"];
+ if (xcodeVersion) {
+ [FIRApp registerLibrary:@"xcode" withVersion:xcodeVersion];
+ }
+ if (sdkVersion) {
+ [FIRApp registerLibrary:@"apple-sdk" withVersion:sdkVersion];
+ }
+}
+
+ (NSDictionary *)defaultOptionsDictionary {
if (sDefaultOptionsDictionary != nil) {
return sDefaultOptionsDictionary;
@@ -162,45 +194,6 @@ static NSDictionary *sDefaultOptionsDictionary = nil;
#pragma mark - Public instance methods
-- (instancetype)initWithGoogleAppID:(NSString *)googleAppID
- bundleID:(NSString *)bundleID
- GCMSenderID:(NSString *)GCMSenderID
- APIKey:(NSString *)APIKey
- clientID:(NSString *)clientID
- trackingID:(NSString *)trackingID
- androidClientID:(NSString *)androidClientID
- databaseURL:(NSString *)databaseURL
- storageBucket:(NSString *)storageBucket
- deepLinkURLScheme:(NSString *)deepLinkURLScheme {
- self = [super init];
- if (self) {
- if (!googleAppID) {
- [NSException raise:kFirebaseCoreErrorDomain format:@"Please specify a valid Google App ID."];
- } else if (!GCMSenderID) {
- [NSException raise:kFirebaseCoreErrorDomain format:@"Please specify a valid GCM Sender ID."];
- }
-
- // `bundleID` is a required property, default to the main `bundleIdentifier` if it's `nil`.
- if (!bundleID) {
- bundleID = [[NSBundle mainBundle] bundleIdentifier];
- }
-
- NSMutableDictionary *mutableOptionsDict = [NSMutableDictionary dictionary];
- [mutableOptionsDict setValue:googleAppID forKey:kFIRGoogleAppID];
- [mutableOptionsDict setValue:bundleID forKey:kFIRBundleID];
- [mutableOptionsDict setValue:GCMSenderID forKey:kFIRGCMSenderID];
- [mutableOptionsDict setValue:APIKey forKey:kFIRAPIKey];
- [mutableOptionsDict setValue:clientID forKey:kFIRClientID];
- [mutableOptionsDict setValue:trackingID forKey:kFIRTrackingID];
- [mutableOptionsDict setValue:androidClientID forKey:kFIRAndroidClientID];
- [mutableOptionsDict setValue:databaseURL forKey:kFIRDatabaseURL];
- [mutableOptionsDict setValue:storageBucket forKey:kFIRStorageBucket];
- self.optionsDictionary = mutableOptionsDict;
- self.deepLinkURLScheme = deepLinkURLScheme;
- }
- return self;
-}
-
- (instancetype)initWithContentsOfFile:(NSString *)plistPath {
self = [super init];
if (self) {
@@ -346,26 +339,29 @@ static NSDictionary *sDefaultOptionsDictionary = nil;
#pragma mark - Internal instance methods
-- (NSDictionary *)analyticsOptionsDictionary {
+- (NSDictionary *)analyticsOptionsDictionaryWithInfoDictionary:(NSDictionary *)infoDictionary {
dispatch_once(&_createAnalyticsOptionsDictionaryOnce, ^{
NSMutableDictionary *tempAnalyticsOptions = [[NSMutableDictionary alloc] init];
- NSDictionary *mainInfoDictionary = [NSBundle mainBundle].infoDictionary;
NSArray *measurementKeys = @[
kFIRIsMeasurementEnabled, kFIRIsAnalyticsCollectionEnabled,
kFIRIsAnalyticsCollectionDeactivated
];
for (NSString *key in measurementKeys) {
- id value = mainInfoDictionary[key] ?: self.optionsDictionary[key] ?: nil;
+ id value = infoDictionary[key] ?: self.optionsDictionary[key] ?: nil;
if (!value) {
continue;
}
tempAnalyticsOptions[key] = value;
}
- _analyticsOptionsDictionary = tempAnalyticsOptions;
+ self->_analyticsOptionsDictionary = tempAnalyticsOptions;
});
return _analyticsOptionsDictionary;
}
+- (NSDictionary *)analyticsOptionsDictionary {
+ return [self analyticsOptionsDictionaryWithInfoDictionary:[NSBundle mainBundle].infoDictionary];
+}
+
/**
* Whether or not Measurement was enabled. Measurement is enabled unless explicitly disabled in
* GoogleService-Info.plist. This uses the old plist flag IS_MEASUREMENT_ENABLED, which should still
@@ -376,7 +372,7 @@ static NSDictionary *sDefaultOptionsDictionary = nil;
return NO;
}
NSNumber *value = self.analyticsOptionsDictionary[kFIRIsMeasurementEnabled];
- if (!value) {
+ if (value == nil) {
return YES; // Enable Measurement by default when the key is not in the dictionary.
}
return [value boolValue];
@@ -387,7 +383,7 @@ static NSDictionary *sDefaultOptionsDictionary = nil;
return NO;
}
NSNumber *value = self.analyticsOptionsDictionary[kFIRIsAnalyticsCollectionEnabled];
- if (!value) {
+ if (value == nil) {
return self.isMeasurementEnabled; // Fall back to older plist flag.
}
return [value boolValue];
@@ -395,7 +391,7 @@ static NSDictionary *sDefaultOptionsDictionary = nil;
- (BOOL)isAnalyticsCollectionDeactivated {
NSNumber *value = self.analyticsOptionsDictionary[kFIRIsAnalyticsCollectionDeactivated];
- if (!value) {
+ if (value == nil) {
return NO; // Analytics Collection is not deactivated when the key is not in the dictionary.
}
return [value boolValue];
diff --git a/Firebase/Core/FIRReachabilityChecker.m b/Firebase/Core/FIRReachabilityChecker.m
index 733dffe..cac87ff 100644
--- a/Firebase/Core/FIRReachabilityChecker.m
+++ b/Firebase/Core/FIRReachabilityChecker.m
@@ -177,7 +177,7 @@ static NSString *const kFIRReachabilityDisconnectedStatus = @"Disconnected";
// Reachable flag is set. Check further flags.
if (!(flags & kSCNetworkReachabilityFlagsConnectionRequired)) {
// Connection required flag is not set, so we have connectivity.
-#if TARGET_OS_IOS
+#if TARGET_OS_IOS || TARGET_OS_TV
status = (flags & kSCNetworkReachabilityFlagsIsWWAN) ? kFIRReachabilityViaCellular
: kFIRReachabilityViaWifi;
#elif TARGET_OS_OSX
@@ -188,7 +188,7 @@ static NSString *const kFIRReachabilityDisconnectedStatus = @"Disconnected";
!(flags & kSCNetworkReachabilityFlagsInterventionRequired)) {
// If the connection on demand or connection on traffic flag is set, and user intervention
// is not required, we have connectivity.
-#if TARGET_OS_IOS
+#if TARGET_OS_IOS || TARGET_OS_TV
status = (flags & kSCNetworkReachabilityFlagsIsWWAN) ? kFIRReachabilityViaCellular
: kFIRReachabilityViaWifi;
#elif TARGET_OS_OSX
diff --git a/Firebase/Core/FIRVersion.m b/Firebase/Core/FIRVersion.m
new file mode 100644
index 0000000..00a6741
--- /dev/null
+++ b/Firebase/Core/FIRVersion.m
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2017 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.
+ */
+
+#ifndef Firebase_VERSION
+#error "Firebase_VERSION is not defined: add -DFirebase_VERSION=... to the build invocation"
+#endif
+
+#ifndef FIRCore_VERSION
+#error "FIRCore_VERSION is not defined: add -DFIRCore_VERSION=... to the build invocation"
+#endif
+
+// The following two macros supply the incantation so that the C
+// preprocessor does not try to parse the version as a floating
+// point number. See
+// https://www.guyrutenberg.com/2008/12/20/expanding-macros-into-string-constants-in-c/
+#define STR(x) STR_EXPAND(x)
+#define STR_EXPAND(x) #x
+
+const unsigned char *const FirebaseVersionString =
+ (const unsigned char *const)STR(Firebase_VERSION);
+
+const unsigned char *const FirebaseCoreVersionString =
+ (const unsigned char *const)STR(FIRCore_VERSION);
diff --git a/Firebase/Core/Private/FIRAppInternal.h b/Firebase/Core/Private/FIRAppInternal.h
index 447d151..b7cf5e8 100644
--- a/Firebase/Core/Private/FIRAppInternal.h
+++ b/Firebase/Core/Private/FIRAppInternal.h
@@ -135,6 +135,23 @@ typedef NSString *_Nullable (^FIRAppGetUIDImplementation)(void);
+ (BOOL)isDefaultAppConfigured;
/**
+ * Registers a given third-party library with the given version number to be reported for
+ * analyitcs.
+ *
+ * @param library Name of the library
+ * @param version Version of the library
+ */
+// clang-format off
++ (void)registerLibrary:(NSString *)library
+ withVersion:(NSString *)version NS_SWIFT_NAME(registerLibrary(_:version:));
+// clang-format on
+
+/**
+ * A concatenated string representing all the third-party libraries and version numbers.
+ */
++ (NSString *)firebaseUserAgent;
+
+/**
* Used by each SDK to send logs about SDK configuration status to Clearcut.
*/
- (void)sendLogsWithServiceName:(NSString *)serviceName
diff --git a/Firebase/Core/Private/FIRErrors.h b/Firebase/Core/Private/FIRErrors.h
index 9a03575..cf69252 100644
--- a/Firebase/Core/Private/FIRErrors.h
+++ b/Firebase/Core/Private/FIRErrors.h
@@ -31,13 +31,3 @@ extern NSString *const kFirebaseDurableDeepLinkErrorDomain;
extern NSString *const kFirebaseInstanceIDErrorDomain;
extern NSString *const kFirebasePerfErrorDomain;
extern NSString *const kFirebaseStorageErrorDomain;
-
-/**
- * Factory for a NSError in the Firebase error domain.
- *
- * @param domain Domain of Firebase error.
- * @param code Error code that NSError should have.
- * @param userInfo User info that NSError should have.
- * @return An NSError in the Firebase domain.
- */
-extern NSError *FIRCreateError(NSString *domain, FIRErrorCode code, NSDictionary *userInfo);
diff --git a/Firebase/Core/Private/FIRLogger.h b/Firebase/Core/Private/FIRLogger.h
index cab650e..358832f 100644
--- a/Firebase/Core/Private/FIRLogger.h
+++ b/Firebase/Core/Private/FIRLogger.h
@@ -40,6 +40,7 @@ extern FIRLoggerService kFIRLoggerMessaging;
extern FIRLoggerService kFIRLoggerPerf;
extern FIRLoggerService kFIRLoggerRemoteConfig;
extern FIRLoggerService kFIRLoggerStorage;
+extern FIRLoggerService kFIRLoggerSwizzler;
#ifdef __cplusplus
extern "C" {
diff --git a/Firebase/Core/Private/FIRMutableDictionary.h b/Firebase/Core/Private/FIRMutableDictionary.h
index ebe2d33..6829dbc 100644
--- a/Firebase/Core/Private/FIRMutableDictionary.h
+++ b/Firebase/Core/Private/FIRMutableDictionary.h
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-@import Foundation;
+#import <Foundation/Foundation.h>
/// A mutable dictionary that provides atomic accessor and mutators.
@interface FIRMutableDictionary : NSObject
diff --git a/Firebase/Core/Private/FIRNetwork.h b/Firebase/Core/Private/FIRNetwork.h
index aac0bca..32be35a 100644
--- a/Firebase/Core/Private/FIRNetwork.h
+++ b/Firebase/Core/Private/FIRNetwork.h
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-@import Foundation;
+#import <Foundation/Foundation.h>
#import "FIRNetworkConstants.h"
#import "FIRNetworkLoggerProtocol.h"
diff --git a/Firebase/Core/Private/FIRNetworkConstants.h b/Firebase/Core/Private/FIRNetworkConstants.h
index 5878088..d318581 100644
--- a/Firebase/Core/Private/FIRNetworkConstants.h
+++ b/Firebase/Core/Private/FIRNetworkConstants.h
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-@import Foundation;
+#import <Foundation/Foundation.h>
/// Error codes in Firebase Network error domain.
/// Note: these error codes should never change. It would make it harder to decode the errors if
diff --git a/Firebase/Core/Private/FIRNetworkLoggerProtocol.h b/Firebase/Core/Private/FIRNetworkLoggerProtocol.h
index 4a4315b..add70fc 100644
--- a/Firebase/Core/Private/FIRNetworkLoggerProtocol.h
+++ b/Firebase/Core/Private/FIRNetworkLoggerProtocol.h
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-@import Foundation;
+#import <Foundation/Foundation.h>
#import "FIRLoggerLevel.h"
#import "FIRNetworkMessageCode.h"
diff --git a/Firebase/Core/Private/FIRNetworkURLSession.h b/Firebase/Core/Private/FIRNetworkURLSession.h
index 3b60317..a51b8a9 100644
--- a/Firebase/Core/Private/FIRNetworkURLSession.h
+++ b/Firebase/Core/Private/FIRNetworkURLSession.h
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-@import Foundation;
+#import <Foundation/Foundation.h>
#import "FIRNetworkLoggerProtocol.h"
diff --git a/Firebase/Core/Private/FIRReachabilityChecker.h b/Firebase/Core/Private/FIRReachabilityChecker.h
index afbc89b..3a6a531 100644
--- a/Firebase/Core/Private/FIRReachabilityChecker.h
+++ b/Firebase/Core/Private/FIRReachabilityChecker.h
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-@import Foundation;
-@import SystemConfiguration;
+#import <Foundation/Foundation.h>
+#import <SystemConfiguration/SystemConfiguration.h>
/// Reachability Status
typedef enum {
diff --git a/Firestore/Source/Auth/FSTEmptyCredentialsProvider.h b/Firebase/Core/Private/FIRVersion.h
index f805363..f18f61f 100644
--- a/Firestore/Source/Auth/FSTEmptyCredentialsProvider.h
+++ b/Firebase/Core/Private/FIRVersion.h
@@ -16,13 +16,8 @@
#import <Foundation/Foundation.h>
-#import "Firestore/Source/Auth/FSTCredentialsProvider.h"
+/** The version of the Firebase SDK. */
+FOUNDATION_EXPORT const unsigned char *const FirebaseVersionString;
-NS_ASSUME_NONNULL_BEGIN
-
-/** `FSTEmptyCredentialsProvider` always yields an empty token. */
-@interface FSTEmptyCredentialsProvider : NSObject <FSTCredentialsProvider>
-
-@end
-
-NS_ASSUME_NONNULL_END
+/** The version of the FirebaseCore Component. */
+FOUNDATION_EXPORT const unsigned char *const FirebaseCoreVersionString;
diff --git a/Firebase/Core/Public/FIRApp.h b/Firebase/Core/Public/FIRApp.h
index e0d852b..fb18b75 100644
--- a/Firebase/Core/Public/FIRApp.h
+++ b/Firebase/Core/Public/FIRApp.h
@@ -16,11 +16,6 @@
#import <Foundation/Foundation.h>
-#if TARGET_OS_IOS
-// TODO: Remove UIKit import on next breaking change release
-#import <UIKit/UIKit.h>
-#endif
-
@class FIROptions;
NS_ASSUME_NONNULL_BEGIN
@@ -52,7 +47,8 @@ NS_SWIFT_NAME(FirebaseApp)
/**
* Configures a default Firebase app. Raises an exception if any configuration step fails. The
* default app is named "__FIRAPP_DEFAULT". This method should be called after the app is launched
- * and before using Firebase services. This method is thread safe.
+ * and before using Firebase services. This method is thread safe and contains synchronous file I/O
+ * (reading GoogleService-Info.plist from disk).
*/
+ (void)configure;
@@ -89,19 +85,11 @@ NS_SWIFT_NAME(FirebaseApp)
*/
+ (nullable FIRApp *)appNamed:(NSString *)name NS_SWIFT_NAME(app(name:));
-#if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
/**
* Returns the set of all extant FIRApp instances, or nil if there are no FIRApp instances. This
* method is thread safe.
*/
@property(class, readonly, nullable) NSDictionary<NSString *, FIRApp *> *allApps;
-#else
-/**
- * Returns the set of all extant FIRApp instances, or nil if there are no FIRApp instances. This
- * method is thread safe.
- */
-+ (nullable NSDictionary<NSString *, FIRApp *> *)allApps NS_SWIFT_NAME(allApps());
-#endif // defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
/**
* Cleans up the current FIRApp, freeing associated data and returning its name to the pool for
diff --git a/Firebase/Core/Public/FIRConfiguration.h b/Firebase/Core/Public/FIRConfiguration.h
index 05bd261..95bba5e 100644
--- a/Firebase/Core/Public/FIRConfiguration.h
+++ b/Firebase/Core/Public/FIRConfiguration.h
@@ -19,25 +19,6 @@
#import "FIRAnalyticsConfiguration.h"
#import "FIRLoggerLevel.h"
-/**
- * The log levels used by FIRConfiguration.
- */
-typedef NS_ENUM(NSInteger, FIRLogLevel) {
- /** Error */
- kFIRLogLevelError __deprecated = 0,
- /** Warning */
- kFIRLogLevelWarning __deprecated,
- /** Info */
- kFIRLogLevelInfo __deprecated,
- /** Debug */
- kFIRLogLevelDebug __deprecated,
- /** Assert */
- kFIRLogLevelAssert __deprecated,
- /** Max */
- kFIRLogLevelMax __deprecated = kFIRLogLevelAssert
-} DEPRECATED_MSG_ATTRIBUTE(
- "Use -FIRDebugEnabled and -FIRDebugDisabled or setLoggerLevel. See FIRApp.h for more details.");
-
NS_ASSUME_NONNULL_BEGIN
/**
@@ -47,21 +28,12 @@ NS_ASSUME_NONNULL_BEGIN
NS_SWIFT_NAME(FirebaseConfiguration)
@interface FIRConfiguration : NSObject
-#if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
/** Returns the shared configuration object. */
@property(class, nonatomic, readonly) FIRConfiguration *sharedInstance NS_SWIFT_NAME(shared);
-#else
-/** Returns the shared configuration object. */
-+ (FIRConfiguration *)sharedInstance NS_SWIFT_NAME(shared());
-#endif // defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
/** The configuration class for Firebase Analytics. */
@property(nonatomic, readwrite) FIRAnalyticsConfiguration *analyticsConfiguration;
-/** Global log level. Defaults to kFIRLogLevelError. */
-@property(nonatomic, readwrite, assign) FIRLogLevel logLevel DEPRECATED_MSG_ATTRIBUTE(
- "Use -FIRDebugEnabled and -FIRDebugDisabled or setLoggerLevel. See FIRApp.h for more details.");
-
/**
* Sets the logging level for internal Firebase logging. Firebase will only log messages
* that are logged at or below loggerLevel. The messages are logged both to the Xcode
diff --git a/Firebase/Core/Public/FIROptions.h b/Firebase/Core/Public/FIROptions.h
index eba0657..87a01dd 100644
--- a/Firebase/Core/Public/FIROptions.h
+++ b/Firebase/Core/Public/FIROptions.h
@@ -25,7 +25,8 @@ NS_SWIFT_NAME(FirebaseOptions)
@interface FIROptions : NSObject <NSCopying>
/**
- * Returns the default options.
+ * Returns the default options. The first time this is called it synchronously reads
+ * GoogleService-Info.plist from disk.
*/
+ (nullable FIROptions *)defaultOptions NS_SWIFT_NAME(defaultOptions());
@@ -90,26 +91,8 @@ NS_SWIFT_NAME(FirebaseOptions)
@property(nonatomic, copy, nullable) NSString *storageBucket;
/**
- * Initializes a customized instance of FIROptions with keys. googleAppID, bundleID and GCMSenderID
- * are required. Other keys may required for configuring specific services.
- */
-- (instancetype)initWithGoogleAppID:(NSString *)googleAppID
- bundleID:(NSString *)bundleID
- GCMSenderID:(NSString *)GCMSenderID
- APIKey:(NSString *)APIKey
- clientID:(NSString *)clientID
- trackingID:(NSString *)trackingID
- androidClientID:(NSString *)androidClientID
- databaseURL:(NSString *)databaseURL
- storageBucket:(NSString *)storageBucket
- deepLinkURLScheme:(NSString *)deepLinkURLScheme
- DEPRECATED_MSG_ATTRIBUTE(
- "Use `-[[FIROptions alloc] initWithGoogleAppID:GCMSenderID:]` "
- "(`FirebaseOptions(googleAppID:gcmSenderID:)` in Swift)` and property "
- "setters instead.");
-
-/**
- * Initializes a customized instance of FIROptions from the file at the given plist file path.
+ * Initializes a customized instance of FIROptions from the file at the given plist file path. This
+ * will read the file synchronously from disk.
* For example,
* NSString *filePath =
* [[NSBundle mainBundle] pathForResource:@"GoogleService-Info" ofType:@"plist"];
diff --git a/Firebase/Core/third_party/FIRAppEnvironmentUtil.h b/Firebase/Core/third_party/FIRAppEnvironmentUtil.h
index 7ae9827..09ee504 100644
--- a/Firebase/Core/third_party/FIRAppEnvironmentUtil.h
+++ b/Firebase/Core/third_party/FIRAppEnvironmentUtil.h
@@ -16,12 +16,6 @@
#import <Foundation/Foundation.h>
-#if TARGET_OS_IOS
-#import <UIKit/UIKit.h>
-#elif TARGET_OS_OSX
-#import <AppKit/AppKit.h>
-#endif
-
@interface FIRAppEnvironmentUtil : NSObject
/// Indicates whether the app is from Apple Store or not. Returns NO if the app is on simulator,
@@ -46,12 +40,4 @@
/// Indicates whether it is running inside an extension or an app.
+ (BOOL)isAppExtension;
-#if TARGET_OS_IOS
-/// Returns the [UIApplication sharedApplication] if it is running on an app, not an extension.
-+ (UIApplication *)sharedApplication;
-#elif TARGET_OS_OSX
-/// Returns the [NSApplication sharedApplication].
-+ (NSApplication *)sharedApplication;
-#endif
-
@end
diff --git a/Firebase/Core/third_party/FIRAppEnvironmentUtil.m b/Firebase/Core/third_party/FIRAppEnvironmentUtil.m
index 3a08cfa..faee38b 100644
--- a/Firebase/Core/third_party/FIRAppEnvironmentUtil.m
+++ b/Firebase/Core/third_party/FIRAppEnvironmentUtil.m
@@ -36,6 +36,12 @@ struct encryption_info_command {
@implementation FIRAppEnvironmentUtil
+/// A key for the Info.plist to enable or disable checking if the App Store is running in a sandbox.
+/// This will affect your data integrity when using Firebase Analytics, as it will disable some
+/// necessary checks.
+static NSString *const kFIRAppStoreReceiptURLCheckEnabledKey =
+ @"FirebaseAppStoreReceiptURLCheckEnabled";
+
/// The file name of the sandbox receipt. This is available on iOS >= 8.0
static NSString *const kFIRAIdentitySandboxReceiptFileName = @"sandboxReceipt";
@@ -152,13 +158,23 @@ static BOOL isAppEncrypted() {
}
+ (BOOL)isAppStoreReceiptSandbox {
+ // Since checking the App Store's receipt URL can be memory intensive, check the option in the
+ // Info.plist if developers opted out of this check.
+ id enableSandboxCheck =
+ [[NSBundle mainBundle] objectForInfoDictionaryKey:kFIRAppStoreReceiptURLCheckEnabledKey];
+ if (enableSandboxCheck &&
+ [enableSandboxCheck isKindOfClass:[NSNumber class]] &&
+ ![enableSandboxCheck boolValue]) {
+ return NO;
+ }
+
NSURL *appStoreReceiptURL = [NSBundle mainBundle].appStoreReceiptURL;
NSString *appStoreReceiptFileName = appStoreReceiptURL.lastPathComponent;
return [appStoreReceiptFileName isEqualToString:kFIRAIdentitySandboxReceiptFileName];
}
+ (BOOL)hasEmbeddedMobileProvision {
- #if TARGET_OS_IOS
+ #if TARGET_OS_IOS || TARGET_OS_TV
return [[NSBundle mainBundle] pathForResource:@"embedded" ofType:@"mobileprovision"].length > 0;
#elif TARGET_OS_OSX
return NO;
@@ -166,7 +182,7 @@ static BOOL isAppEncrypted() {
}
+ (BOOL)isSimulator {
- #if TARGET_OS_IOS
+ #if TARGET_OS_IOS || TARGET_OS_TV
NSString *platform = [FIRAppEnvironmentUtil deviceModel];
return [platform isEqual:@"x86_64"] || [platform isEqual:@"i386"];
#elif TARGET_OS_OSX
@@ -188,15 +204,11 @@ static BOOL isAppEncrypted() {
}
+ (NSString *)systemVersion {
- #if TARGET_OS_IOS
- return [UIDevice currentDevice].systemVersion;
- #elif TARGET_OS_OSX
return [NSProcessInfo processInfo].operatingSystemVersionString;
- #endif
}
+ (BOOL)isAppExtension {
- #if TARGET_OS_IOS
+ #if TARGET_OS_IOS || TARGET_OS_TV
// Documented by <a href="https://goo.gl/RRB2Up">Apple</a>
BOOL appExtension = [[[NSBundle mainBundle] bundlePath] hasSuffix:@".appex"];
return appExtension;
@@ -205,29 +217,10 @@ static BOOL isAppEncrypted() {
#endif
}
-#if TARGET_OS_IOS
-+ (UIApplication *)sharedApplication {
- if ([FIRAppEnvironmentUtil isAppExtension]) {
- return nil;
- }
- id sharedApplication = nil;
- Class uiApplicationClass = NSClassFromString(@"UIApplication");
- if (uiApplicationClass &&
- [uiApplicationClass respondsToSelector:(NSSelectorFromString(@"sharedApplication"))]) {
- sharedApplication = [uiApplicationClass sharedApplication];
- }
- return sharedApplication;
-}
-#elif TARGET_OS_OSX
-+ (NSApplication *)sharedApplication {
- return [NSApplication sharedApplication];
-}
-#endif
-
#pragma mark - Helper methods
+ (BOOL)hasSCInfoFolder {
- #if TARGET_OS_IOS
+ #if TARGET_OS_IOS || TARGET_OS_TV
NSString *bundlePath = [NSBundle mainBundle].bundlePath;
NSString *scInfoPath = [bundlePath stringByAppendingPathComponent:@"SC_Info"];
return [[NSFileManager defaultManager] fileExistsAtPath:scInfoPath];
diff --git a/Firebase/Database/CHANGELOG.md b/Firebase/Database/CHANGELOG.md
index 867def1..291ec08 100644
--- a/Firebase/Database/CHANGELOG.md
+++ b/Firebase/Database/CHANGELOG.md
@@ -1,3 +1,13 @@
+# v4.1.5
+- [fixed] Fixes loss of precision for 64 bit numbers on older 32 bit iOS devices with persistence enabled.
+- [changed] Addresses CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF warnings that surface in newer versions of Xcode and CocoaPods.
+
+# v4.1.4
+- [added] Firebase Database is now community-supported on tvOS.
+
+# v4.1.3
+- [changed] Internal cleanup in the firebase-ios-sdk repository. Functionality of the RTDB SDK is not affected.
+
# v4.1.2
- [fixed] Addresses race condition that can occur during the initialization of empty snapshots.
diff --git a/Firebase/Database/Core/FPersistentConnection.m b/Firebase/Database/Core/FPersistentConnection.m
index 6f71d8b..c32d18c 100644
--- a/Firebase/Database/Core/FPersistentConnection.m
+++ b/Firebase/Database/Core/FPersistentConnection.m
@@ -735,18 +735,22 @@ static void reachabilityCallback(SCNetworkReachabilityRef ref, SCNetworkReachabi
}
- (void) cancelSentTransactions {
- NSMutableArray* toPrune = [[NSMutableArray alloc] init];
+ NSMutableDictionary<NSNumber*, FOutstandingPut*>* cancelledOutstandingPuts = [[NSMutableDictionary alloc] init];
+
for (NSNumber* index in self.outstandingPuts) {
FOutstandingPut* put = self.outstandingPuts[index];
if (put.request[kFWPRequestHash] && put.sent) {
- // This is a sent transaction put
- put.onCompleteBlock(kFTransactionDisconnect, @"Client was disconnected while running a transaction");
- [toPrune addObject:index];
+ // This is a sent transaction put.
+ cancelledOutstandingPuts[index] = put;
}
}
- for (NSNumber* index in toPrune) {
+
+ [cancelledOutstandingPuts enumerateKeysAndObjectsUsingBlock:^(NSNumber *index, FOutstandingPut *outstandingPut, BOOL *stop) {
+ // `onCompleteBlock:` may invoke `rerunTransactionsForPath:` and enqueue new writes. We defer calling
+ // it until we have finished enumerating all existing writes.
+ outstandingPut.onCompleteBlock(kFTransactionDisconnect, @"Client was disconnected while running a transaction");
[self.outstandingPuts removeObjectForKey:index];
- }
+ }];
}
- (void) onDataPushWithAction:(NSString *)action andBody:(NSDictionary *)body {
@@ -925,7 +929,7 @@ static void reachabilityCallback(SCNetworkReachabilityRef ref, SCNetworkReachabi
- (void) sendConnectStats {
NSMutableDictionary *stats = [NSMutableDictionary dictionary];
- #if TARGET_OS_IOS
+ #if TARGET_OS_IOS || TARGET_OS_TV
if (self.config.persistenceEnabled) {
stats[@"persistence.ios.enabled"] = @1;
}
diff --git a/Firebase/Database/Core/FRepo.m b/Firebase/Database/Core/FRepo.m
index 0935b44..ae1d8e8 100644
--- a/Firebase/Database/Core/FRepo.m
+++ b/Firebase/Database/Core/FRepo.m
@@ -53,7 +53,7 @@
#import "FValueEventRegistration.h"
#import "FEmptyNode.h"
-#if TARGET_OS_IOS
+#if TARGET_OS_IOS || TARGET_OS_TV
#import <UIKit/UIKit.h>
#endif
@@ -513,11 +513,11 @@
}
- (void)onConnect:(FPersistentConnection *)fpconnection {
- [self updateInfo:kDotInfoConnected withValue:@true];
+ [self updateInfo:kDotInfoConnected withValue:@YES];
}
- (void)onDisconnect:(FPersistentConnection *)fpconnection {
- [self updateInfo:kDotInfoConnected withValue:@false];
+ [self updateInfo:kDotInfoConnected withValue:@NO];
[self runOnDisconnectEvents];
}
@@ -546,7 +546,7 @@
return;
// Targetted compilation is ONLY for testing. UIKit is weak-linked in actual release build.
- #if TARGET_OS_IOS
+ #if TARGET_OS_IOS || TARGET_OS_TV
// The idea is to wait until any outstanding sets get written to disk. Since the sets might still be in our
// dispatch queue, we wait for the dispatch queue to catch up and for persistence to catch up.
// This may be undesirable though. The dispatch queue might just be processing a bunch of incoming data or
diff --git a/Firebase/Database/Persistence/FLevelDBStorageEngine.m b/Firebase/Database/Persistence/FLevelDBStorageEngine.m
index 490fb6c..e49d6bc 100644
--- a/Firebase/Database/Persistence/FLevelDBStorageEngine.m
+++ b/Firebase/Database/Persistence/FLevelDBStorageEngine.m
@@ -211,7 +211,7 @@ static NSString* trackedQueryKeysKey(NSUInteger trackedQueryId, NSString *key) {
}
+ (NSString *) firebaseDir {
- #if TARGET_OS_IOS
+ #if TARGET_OS_IOS || TARGET_OS_TV
NSArray *dirPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDir = [dirPaths objectAtIndex:0];
return [documentsDir stringByAppendingPathComponent:@"firebase"];
@@ -687,7 +687,7 @@ static NSString* trackedQueryKeysKey(NSUInteger trackedQueryId, NSString *key) {
NSString *doubleString = [value stringValue];
return [NSNumber numberWithDouble:[doubleString doubleValue]];
} else {
- return [NSNumber numberWithLong:[value longValue]];
+ return [NSNumber numberWithLongLong:[value longLongValue]];
}
}
}
diff --git a/Firebase/Database/Realtime/FWebSocketConnection.m b/Firebase/Database/Realtime/FWebSocketConnection.m
index 0fd07e5..49d6bd8 100644
--- a/Firebase/Database/Realtime/FWebSocketConnection.m
+++ b/Firebase/Database/Realtime/FWebSocketConnection.m
@@ -25,7 +25,7 @@
#import "FStringUtilities.h"
#import "FIRDatabase_Private.h"
-#if TARGET_OS_IOS
+#if TARGET_OS_IOS || TARGET_OS_TV
#import <UIKit/UIKit.h>
#endif
@@ -85,7 +85,7 @@
BOOL hasUiDeviceClass = NO;
// Targetted compilation is ONLY for testing. UIKit is weak-linked in actual release build.
- #if TARGET_OS_IOS
+ #if TARGET_OS_IOS || TARGET_OS_TV
Class uiDeviceClass = NSClassFromString(@"UIDevice");
if (uiDeviceClass) {
systemVersion = [uiDeviceClass currentDevice].systemVersion;
diff --git a/Firebase/Database/module.modulemap b/Firebase/Database/module.modulemap
deleted file mode 100644
index 28b323e..0000000
--- a/Firebase/Database/module.modulemap
+++ /dev/null
@@ -1,13 +0,0 @@
-framework module FirebaseDatabase {
- umbrella header "FirebaseDatabase.h"
-
- export *
- module * { export * }
-
- link framework "CFNetwork"
- link framework "Security"
- link framework "SystemConfiguration"
-
- link "c++"
- link "icucore"
-}
diff --git a/Firebase/Database/third_party/SocketRocket/FSRWebSocket.m b/Firebase/Database/third_party/SocketRocket/FSRWebSocket.m
index a2c857b..9b3dad0 100644
--- a/Firebase/Database/third_party/SocketRocket/FSRWebSocket.m
+++ b/Firebase/Database/third_party/SocketRocket/FSRWebSocket.m
@@ -18,7 +18,7 @@
#import "FSRWebSocket.h"
-#if TARGET_OS_IOS
+#if TARGET_OS_IOS || TARGET_OS_TV
#define HAS_ICU
#endif
@@ -28,7 +28,7 @@
#import <unicode/utf8.h>
#endif
-#if TARGET_OS_IOS
+#if TARGET_OS_IOS || TARGET_OS_TV
#import <Endian.h>
#elif TARGET_OS_OSX
#import <CoreServices/CoreServices.h>
@@ -515,10 +515,10 @@ static __strong NSData *CRLFCRLF;
}
[self _readUntilHeaderCompleteWithCallback:^(FSRWebSocket *self, NSData *data) {
- CFHTTPMessageAppendBytes(_receivedHTTPHeaders, (const UInt8 *)data.bytes, data.length);
+ CFHTTPMessageAppendBytes(self->_receivedHTTPHeaders, (const UInt8 *)data.bytes, data.length);
- if (CFHTTPMessageIsHeaderComplete(_receivedHTTPHeaders)) {
- SRFastLog(@"Finished reading headers %@", CFBridgingRelease(CFHTTPMessageCopyAllHeaderFields(_receivedHTTPHeaders)));
+ if (CFHTTPMessageIsHeaderComplete(self->_receivedHTTPHeaders)) {
+ SRFastLog(@"Finished reading headers %@", CFBridgingRelease(CFHTTPMessageCopyAllHeaderFields(self->_receivedHTTPHeaders)));
[self _HTTPHeadersDidFinish];
} else {
[self _readHTTPHeader];
@@ -696,7 +696,7 @@ static __strong NSData *CRLFCRLF;
// Need to shunt this on the _callbackQueue first to see if they received any messages
[self _performDelegateBlock:^{
[self closeWithCode:SRStatusCodeProtocolError reason:message];
- dispatch_async(_workQueue, ^{
+ dispatch_async(self->_workQueue, ^{
[self _disconnect];
});
}];
@@ -706,7 +706,7 @@ static __strong NSData *CRLFCRLF;
{
dispatch_async(_workQueue, ^{
if (self.readyState != SR_CLOSED) {
- _failed = YES;
+ self->_failed = YES;
[self _performDelegateBlock:^{
if ([self.delegate respondsToSelector:@selector(webSocket:didFailWithError:)]) {
[self.delegate webSocket:self didFailWithError:error];
@@ -756,7 +756,7 @@ static __strong NSData *CRLFCRLF;
{
// Need to pingpong this off _callbackQueue first to make sure messages happen in order
[self _performDelegateBlock:^{
- dispatch_async(_workQueue, ^{
+ dispatch_async(self->_workQueue, ^{
[self _sendFrameWithOpcode:SROpCodePong data:pingData];
});
}];
@@ -1031,7 +1031,7 @@ static const uint8_t SRPayloadLenMask = 0x7F;
[self _closeWithProtocolError:@"Client must receive unmasked data"];
}
- size_t extra_bytes_needed = header.masked ? sizeof(_currentReadMaskKey) : 0;
+ size_t extra_bytes_needed = header.masked ? sizeof(self->_currentReadMaskKey) : 0;
if (header.payload_length == 126) {
extra_bytes_needed += sizeof(uint16_t);
@@ -1062,7 +1062,7 @@ static const uint8_t SRPayloadLenMask = 0x7F;
if (header.masked) {
- assert(mapped_size >= sizeof(_currentReadMaskOffset) + offset);
+ assert(mapped_size >= sizeof(self->_currentReadMaskOffset) + offset);
memcpy(self->_currentReadMaskKey, ((uint8_t *)mapped_buffer) + offset, sizeof(self->_currentReadMaskKey));
}
@@ -1075,12 +1075,12 @@ static const uint8_t SRPayloadLenMask = 0x7F;
- (void)_readFrameNew;
{
dispatch_async(_workQueue, ^{
- [_currentFrameData setLength:0];
+ [self->_currentFrameData setLength:0];
- _currentFrameOpcode = 0;
- _currentFrameCount = 0;
- _readOpCount = 0;
- _currentStringScanPosition = 0;
+ self->_currentFrameOpcode = 0;
+ self->_currentFrameCount = 0;
+ self->_readOpCount = 0;
+ self->_currentStringScanPosition = 0;
[self _readFrameContinue];
});
@@ -1123,7 +1123,7 @@ static const uint8_t SRPayloadLenMask = 0x7F;
if (!_failed) {
[self _performDelegateBlock:^{
if ([self.delegate respondsToSelector:@selector(webSocket:didCloseWithCode:reason:wasClean:)]) {
- [self.delegate webSocket:self didCloseWithCode:_closeCode reason:_closeReason wasClean:YES];
+ [self.delegate webSocket:self didCloseWithCode:self->_closeCode reason:self->_closeReason wasClean:YES];
}
}];
}
@@ -1184,7 +1184,7 @@ static const uint8_t SRPayloadLenMask = 0x7F;
// Cleanup selfRetain in the same GCD queue as usual
dispatch_async(_workQueue, ^{
- _selfRetain = nil;
+ self->_selfRetain = nil;
});
}
@@ -1525,8 +1525,8 @@ static const size_t SRFrameHeaderOverhead = 32;
[self _scheduleCleanup];
}
- if (!_sentClose && !_failed) {
- _sentClose = YES;
+ if (!self->_sentClose && !self->_failed) {
+ self->_sentClose = YES;
// If we get closed in this state it's probably not clean because we should be sending this when we send messages
[self _performDelegateBlock:^{
if ([self.delegate respondsToSelector:@selector(webSocket:didCloseWithCode:reason:wasClean:)]) {
diff --git a/Firebase/Messaging/CHANGELOG.md b/Firebase/Messaging/CHANGELOG.md
new file mode 100644
index 0000000..89e9870
--- /dev/null
+++ b/Firebase/Messaging/CHANGELOG.md
@@ -0,0 +1,91 @@
+# 2018-04-01 -- v2.2.0
+- Add new methods that provide completion handlers for topic subscription and unsubscription.
+
+# 2018-02-23 -- v2.1.1
+- Improve documentation on the usage of the autoInitEnabled property.
+
+# 2018-02-06 -- v2.1.0
+- Added a new property autoInitEnabled to enable and disable FCM token auto generation.
+- Fixed an issue where notification delivery would fail after changing language settings.
+
+# 2017-09-26 -- v2.0.5
+- Added swizzling of additional UNUserNotificationCenterDelegate method, for
+ more accurate Analytics logging.
+- Fixed a swizzling issue with unimplemented UNUserNotificationCenterDelegate
+ methods.
+
+# 2017-09-26 -- v2.0.4
+- Fixed an issue where the FCM token was not associating correctly with an APNs
+ device token, depending on when the APNs device token was made available.
+- Fixed an issue where FCM tokens for different Sender IDs were not associating
+ correctly with an APNs device token.
+- Fixed an issue that was preventing the FCM direct channel from being
+ established on the first start after 24 hours of being opened.
+- Clarified a log message about method swizzling being enabled.
+
+# 2017-09-13 -- v2.0.3
+- Moved to safer use of NSAsserts, instead of lower-level `__builtin_trap()`
+ method.
+- Added logging of the underlying error code for an error trying to create or
+ open an internal database file.
+
+# 2017-08-25 -- v2.0.2
+- Removed old logic which was saving the SDK version to NSUserDefaults.
+
+# 2017-08-07 -- v2.0.1
+- Fixed an issue where setting `shouldEstablishDirectChannel` in a background
+ thread was triggering the Main Thread Sanitizer in Xcode 9.
+- Removed some old logic related to logging.
+- Added some additional logging around errors while method swizzling.
+
+# 2017-05-03 -- v2.0.0
+- Introduced an improved interface for Swift 3 developers
+- Added new properties and methods to simplify FCM token management
+- Added property, APNSToken, to simplify APNs token management
+- Added new delegate method to be notified of FCM token refreshes
+- Added new property, shouldEstablishDirectChannel, to simplify connecting
+ directly to FCM
+
+# 2017-03-31 -- v1.2.3
+
+- Fixed an issue where custom UNNotificationCenterDelegates may not have been
+ swizzled (if swizzling was enabled)
+- Fixed a issue iOS 8.0 and 8.1 devices using scheduled notifications
+- Improvements to console logging
+
+# 2017-01-31 -- v1.2.2
+
+- Improved topic subscription logic for more reliable subscriptions.
+- Reduced memory footprint and CPU usage when subscribing to multiple topics.
+- Better documentation in the public headers.
+- Switched from ProtocolBuffers2 to protobuf compiler.
+
+# 2016-10-12 -- v1.2.1
+
+- Better documentation on the public headers.
+
+# 2016-09-02 -- v1.2.0
+
+- Support the UserNotifications framework introduced in iOS 10.
+- Add a new API, -applicationReceivedRemoteMessage:, to FIRMessaging. This
+ allows apps to receive data messages from FCM on devices running iOS 10 and
+ above.
+
+# 2016-07-06 -- v1.1.1
+
+- Move FIRMessaging related plists to ApplicationSupport directory.
+
+# 2016-05-04 -- v1.1.0
+
+- Change flag to disable swizzling to *FirebaseAppDelegateProxyEnabled*.
+- '[FIRMessaging appDidReceiveMessage:] returns FIRMessagingMessageInfo object.
+- Minor bug fixes.
+
+# 2016-01-25 -- v1.0.2
+
+- Accept topic names without /topics prefix.
+- Add Swift annotations to public static accessors.
+
+# 2016-01-25 -- v1.0.0
+
+- New Firebase messaging API.
diff --git a/Firebase/Messaging/FIRMMessageCode.h b/Firebase/Messaging/FIRMMessageCode.h
index 2b6ddad..c1b8ffb 100644
--- a/Firebase/Messaging/FIRMMessageCode.h
+++ b/Firebase/Messaging/FIRMMessageCode.h
@@ -37,7 +37,7 @@ typedef NS_ENUM(NSInteger, FIRMessagingMessageCode) {
kFIRMessagingMessageCodeMessaging013 = 2013, // I-FCM002013
kFIRMessagingMessageCodeMessaging014 = 2014, // I-FCM002014
kFIRMessagingMessageCodeMessaging015 = 2015, // I-FCM002015
- kFIRMessagingMessageCodeMessaging016 = 2016, // I-FCM002016
+ kFIRMessagingMessageCodeMessaging016 = 2016, // I-FCM002016 - no longer used
kFIRMessagingMessageCodeMessaging017 = 2017, // I-FCM002017
kFIRMessagingMessageCodeMessaging018 = 2018, // I-FCM002018
kFIRMessagingMessageCodeRemoteMessageDelegateMethodNotImplemented = 2019, // I-FCM002019
@@ -172,6 +172,7 @@ typedef NS_ENUM(NSInteger, FIRMessagingMessageCode) {
kFIRMessagingMessageCodeTopicOption001 = 17001, // I-FCM017001
kFIRMessagingMessageCodeTopicOption002 = 17002, // I-FCM017002
kFIRMessagingMessageCodeTopicOptionTopicEncodingFailed = 17003, // I-FCM017003
+ kFIRMessagingMessageCodeTopicOperationEmptyResponse = 17004, // I-FCM017004
// FIRMessagingUtilities.m
kFIRMessagingMessageCodeUtilities000 = 18000, // I-FCM018000
kFIRMessagingMessageCodeUtilities001 = 18001, // I-FCM018001
diff --git a/Firebase/Messaging/FIRMessaging.m b/Firebase/Messaging/FIRMessaging.m
index 782b779..8fafab8 100644
--- a/Firebase/Messaging/FIRMessaging.m
+++ b/Firebase/Messaging/FIRMessaging.m
@@ -28,7 +28,6 @@
#import "FIRMessagingContextManagerService.h"
#import "FIRMessagingDataMessageManager.h"
#import "FIRMessagingDefines.h"
-#import "FIRMessagingInstanceIDProxy.h"
#import "FIRMessagingLogger.h"
#import "FIRMessagingPubSub.h"
#import "FIRMessagingReceiver.h"
@@ -38,6 +37,7 @@
#import "FIRMessagingVersionUtilities.h"
#import <FirebaseCore/FIRReachabilityChecker.h>
+#import <FirebaseInstanceID/FirebaseInstanceID.h>
#import "NSError+FIRMessaging.h"
@@ -70,10 +70,13 @@ NSString * const FIRMessagingRegistrationTokenRefreshedNotification =
@"com.firebase.messaging.notif.fcm-token-refreshed";
#endif // defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
-// Copied from Apple's header in case it is missing in some cases (e.g. pre-Xcode 8 builds).
-#ifndef NSFoundationVersionNumber_iOS_8_x_Max
-#define NSFoundationVersionNumber_iOS_8_x_Max 1199
-#endif
+NSString *const kFIRMessagingUserDefaultsKeyAutoInitEnabled =
+ @"com.firebase.messaging.auto-init.enabled"; // Auto Init Enabled key stored in NSUserDefaults
+
+NSString *const kFIRMessagingAPNSTokenType = @"APNSTokenType"; // APNS Token type key stored in user info.
+
+static NSString *const kFIRMessagingPlistAutoInitEnabled =
+ @"FirebaseMessagingAutoInitEnabled"; // Auto Init Enabled key stored in Info.plist
@interface FIRMessagingMessageInfo ()
@@ -120,17 +123,17 @@ NSString * const FIRMessagingRegistrationTokenRefreshedNotification =
@end
-@interface FIRMessaging ()
- <FIRMessagingClientDelegate, FIRMessagingReceiverDelegate, FIRReachabilityDelegate>
+@interface FIRMessaging ()<FIRMessagingClientDelegate, FIRMessagingReceiverDelegate,
+ FIRReachabilityDelegate> {
+ BOOL _shouldEstablishDirectChannel;
+}
// FIRApp properties
@property(nonatomic, readwrite, copy) NSString *fcmSenderID;
@property(nonatomic, readwrite, strong) NSData *apnsTokenData;
@property(nonatomic, readwrite, strong) NSString *defaultFcmToken;
-// This object is used as a proxy for reflection-based calls to FIRInstanceID.
-// Due to our packaging requirements, we can't directly depend on FIRInstanceID currently.
-@property(nonatomic, readwrite, strong) FIRMessagingInstanceIDProxy *instanceIDProxy;
+@property(nonatomic, readwrite, strong) FIRInstanceID *instanceID;
@property(nonatomic, readwrite, assign) BOOL isClientSetup;
@@ -141,12 +144,16 @@ NSString * const FIRMessagingRegistrationTokenRefreshedNotification =
@property(nonatomic, readwrite, strong) FIRMessagingRmqManager *rmq2Manager;
@property(nonatomic, readwrite, strong) FIRMessagingReceiver *receiver;
@property(nonatomic, readwrite, strong) FIRMessagingSyncMessageManager *syncMessageManager;
+@property(nonatomic, readwrite, strong) NSUserDefaults *messagingUserDefaults;
/// Message ID's logged for analytics. This prevents us from logging the same message twice
/// which can happen if the user inadvertently calls `appDidReceiveMessage` along with us
/// calling it implicitly during swizzling.
@property(nonatomic, readwrite, strong) NSMutableSet *loggedMessageIDs;
+- (instancetype)initWithInstanceID:(FIRInstanceID *)instanceID
+ userDefaults:(NSUserDefaults *)defaults NS_DESIGNATED_INITIALIZER;
+
@end
@implementation FIRMessaging
@@ -161,29 +168,28 @@ NSString * const FIRMessagingRegistrationTokenRefreshedNotification =
return messaging;
}
-- (instancetype)initPrivately {
+- (instancetype)initWithInstanceID:(FIRInstanceID *)instanceID
+ userDefaults:(NSUserDefaults *)defaults {
self = [super init];
- if (self) {
+ if (self != nil) {
_loggedMessageIDs = [NSMutableSet set];
- _instanceIDProxy = [[FIRMessagingInstanceIDProxy alloc] init];
+ _instanceID = instanceID;
+ _messagingUserDefaults = defaults;
}
return self;
}
+- (instancetype)initPrivately {
+ return [self initWithInstanceID:[FIRInstanceID instanceID]
+ userDefaults:[NSUserDefaults standardUserDefaults]];
+}
+
- (void)dealloc {
[self.reachability stop];
[[NSNotificationCenter defaultCenter] removeObserver:self];
[self teardown];
}
-- (void)setRemoteMessageDelegate:(id<FIRMessagingDelegate>)delegate {
- _delegate = delegate;
-}
-
-- (id<FIRMessagingDelegate>)remoteMessageDelegate {
- return self.delegate;
-}
-
#pragma mark - Config
- (void)start {
@@ -234,11 +240,6 @@ NSString * const FIRMessagingRegistrationTokenRefreshedNotification =
name:kFIRMessagingRegistrationTokenRefreshNotification
object:nil];
[center addObserver:self
- selector:@selector(didReceiveAPNSToken:)
- name:kFIRMessagingAPNSTokenNotification
- object:nil];
-
- [center addObserver:self
selector:@selector(applicationStateChanged)
name:UIApplicationDidBecomeActiveNotification
object:nil];
@@ -404,7 +405,10 @@ NSString * const FIRMessagingRegistrationTokenRefreshedNotification =
}];
} else if ([appDelegate respondsToSelector:openURLWithOptionsSelector]) {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunguarded-availability"
[appDelegate application:application openURL:url options:@{}];
+#pragma clang diagnostic pop
// Similarly, |application:openURL:sourceApplication:annotation:| will also always be called, due
// to the default swizzling done by FIRAAppDelegateProxy in Firebase Analytics
@@ -443,16 +447,50 @@ NSString * const FIRMessagingRegistrationTokenRefreshedNotification =
return;
}
self.apnsTokenData = apnsToken;
- [self.instanceIDProxy setAPNSToken:apnsToken type:(FIRMessagingInstanceIDProxyAPNSTokenType)type];
+
+ // Notify InstanceID that APNS Token has been set.
+ NSDictionary *userInfo = @{kFIRMessagingAPNSTokenType : @(type)};
+ NSNotification *notification =
+ [NSNotification notificationWithName:kFIRMessagingAPNSTokenNotification
+ object:[apnsToken copy]
+ userInfo:userInfo];
+ [[NSNotificationQueue defaultQueue] enqueueNotification:notification postingStyle:NSPostASAP];
}
#pragma mark - FCM
+- (BOOL)isAutoInitEnabled {
+ // Check storage
+ id isAutoInitEnabledObject =
+ [_messagingUserDefaults objectForKey:kFIRMessagingUserDefaultsKeyAutoInitEnabled];
+ if (isAutoInitEnabledObject) {
+ return [isAutoInitEnabledObject boolValue];
+ }
+
+ // Check Info.plist
+ isAutoInitEnabledObject =
+ [[NSBundle mainBundle] objectForInfoDictionaryKey:kFIRMessagingPlistAutoInitEnabled];
+ if (isAutoInitEnabledObject) {
+ return [isAutoInitEnabledObject boolValue];
+ }
+ // If none of above exists, we default assume FCM auto init is enabled.
+ return YES;
+}
+
+- (void)setAutoInitEnabled:(BOOL)autoInitEnabled {
+ BOOL isFCMAutoInitEnabled = [self isAutoInitEnabled];
+ [_messagingUserDefaults setBool:autoInitEnabled
+ forKey:kFIRMessagingUserDefaultsKeyAutoInitEnabled];
+ if (!isFCMAutoInitEnabled && autoInitEnabled) {
+ self.defaultFcmToken = self.instanceID.token;
+ }
+}
+
- (NSString *)FCMToken {
NSString *token = self.defaultFcmToken;
if (!token) {
// We may not have received it from Instance ID yet (via NSNotification), so extract it directly
- token = [self.instanceIDProxy token];
+ token = self.instanceID.token;
}
return token;
}
@@ -482,10 +520,10 @@ NSString * const FIRMessagingRegistrationTokenRefreshedNotification =
@"Be sure to re-retrieve the FCM token once the APNS device token is "
@"set.", senderID);
}
- [self.instanceIDProxy tokenWithAuthorizedEntity:senderID
- scope:kFIRMessagingDefaultTokenScope
- options:options
- handler:completion];
+ [self.instanceID tokenWithAuthorizedEntity:senderID
+ scope:kFIRMessagingDefaultTokenScope
+ options:options
+ handler:completion];
}
- (void)deleteFCMTokenForSenderID:(nonnull NSString *)senderID
@@ -502,9 +540,9 @@ NSString * const FIRMessagingRegistrationTokenRefreshedNotification =
}
return;
}
- [self.instanceIDProxy deleteTokenWithAuthorizedEntity:senderID
- scope:kFIRMessagingDefaultTokenScope
- handler:completion];
+ [self.instanceID deleteTokenWithAuthorizedEntity:senderID
+ scope:kFIRMessagingDefaultTokenScope
+ handler:completion];
}
#pragma mark - FIRMessagingDelegate helper methods
@@ -513,18 +551,16 @@ NSString * const FIRMessagingRegistrationTokenRefreshedNotification =
[self validateDelegateConformsToTokenAvailabilityMethods];
}
-// Check if the delegate conforms to either |didReceiveRegistrationToken:| or
-// |didRefreshRegistrationToken:|, and display a warning to the developer if not.
+// Check if the delegate conforms to |didReceiveRegistrationToken:|
+// and display a warning to the developer if not.
// NOTE: Once |didReceiveRegistrationToken:| can be made a required method, this
// check can be removed.
- (void)validateDelegateConformsToTokenAvailabilityMethods {
if (self.delegate &&
- ![self.delegate respondsToSelector:@selector(messaging:didReceiveRegistrationToken:)] &&
- ![self.delegate respondsToSelector:@selector(messaging:didRefreshRegistrationToken:)]) {
+ ![self.delegate respondsToSelector:@selector(messaging:didReceiveRegistrationToken:)]) {
FIRMessagingLoggerWarn(kFIRMessagingMessageCodeTokenDelegateMethodsNotImplemented,
@"The object %@ does not respond to "
- @"-messaging:didReceiveRegistrationToken:, nor "
- @"-messaging:didRefreshRegistrationToken:. Please implement "
+ @"-messaging:didReceiveRegistrationToken:. Please implement "
@"-messaging:didReceiveRegistrationToken: to be provided with an FCM "
@"token.", self.delegate.description);
}
@@ -643,10 +679,15 @@ NSString * const FIRMessagingRegistrationTokenRefreshedNotification =
}
- (void)subscribeToTopic:(NSString *)topic {
+ [self subscribeToTopic:topic completion:nil];
+}
+
+- (void)subscribeToTopic:(NSString *)topic
+ completion:(nullable FIRMessagingTopicOperationCompletion)completion {
if (self.defaultFcmToken.length && topic.length) {
NSString *normalizeTopic = [[self class ] normalizeTopic:topic];
if (normalizeTopic.length) {
- [self.pubsub subscribeToTopic:normalizeTopic];
+ [self.pubsub subscribeToTopic:normalizeTopic handler:completion];
} else {
FIRMessagingLoggerError(kFIRMessagingMessageCodeMessaging009,
@"Cannot parse topic name %@. Will not subscribe.", topic);
@@ -659,10 +700,15 @@ NSString * const FIRMessagingRegistrationTokenRefreshedNotification =
}
- (void)unsubscribeFromTopic:(NSString *)topic {
+ [self unsubscribeFromTopic:topic completion:nil];
+}
+
+- (void)unsubscribeFromTopic:(NSString *)topic
+ completion:(nullable FIRMessagingTopicOperationCompletion)completion {
if (self.defaultFcmToken.length && topic.length) {
NSString *normalizeTopic = [[self class] normalizeTopic:topic];
if (normalizeTopic.length) {
- [self.pubsub unsubscribeFromTopic:normalizeTopic];
+ [self.pubsub unsubscribeFromTopic:normalizeTopic handler:completion];
} else {
FIRMessagingLoggerError(kFIRMessagingMessageCodeMessaging011,
@"Cannot parse topic name %@. Will not unsubscribe.", topic);
@@ -717,23 +763,15 @@ NSString * const FIRMessagingRegistrationTokenRefreshedNotification =
return [self currentLocale];
}
-- (void)setAPNSToken:(NSData *)apnsToken error:(NSError *)error {
- if (apnsToken) {
- self.apnsTokenData = [apnsToken copy];
- }
-}
-
#pragma mark - FIRMessagingReceiverDelegate
- (void)receiver:(FIRMessagingReceiver *)receiver
receivedRemoteMessage:(FIRMessagingRemoteMessage *)remoteMessage {
if ([self.delegate respondsToSelector:@selector(messaging:didReceiveMessage:)]) {
- [self.delegate messaging:self didReceiveMessage:remoteMessage];
- } else if ([self.delegate respondsToSelector:@selector(applicationReceivedRemoteMessage:)]) {
#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wdeprecated-declarations"
- [self.delegate applicationReceivedRemoteMessage:remoteMessage];
-#pragma clang diagnostic pop
+#pragma clang diagnostic ignored "-Wunguarded-availability"
+ [self.delegate messaging:self didReceiveMessage:remoteMessage];
+#pragma pop
} else {
// Delegate methods weren't implemented, so messages are being dropped, log a warning
FIRMessagingLoggerWarn(kFIRMessagingMessageCodeRemoteMessageDelegateMethodNotImplemented,
@@ -781,7 +819,7 @@ NSString * const FIRMessagingRegistrationTokenRefreshedNotification =
#pragma mark - Notifications
- (void)didReceiveDefaultInstanceIDToken:(NSNotification *)notification {
- if (![notification.object isKindOfClass:[NSString class]]) {
+ if (notification.object && ![notification.object isKindOfClass:[NSString class]]) {
FIRMessagingLoggerDebug(kFIRMessagingMessageCodeMessaging015,
@"Invalid default FCM token type %@",
NSStringFromClass([notification.object class]));
@@ -800,7 +838,7 @@ NSString * const FIRMessagingRegistrationTokenRefreshedNotification =
- (void)defaultInstanceIDTokenWasRefreshed:(NSNotification *)notification {
// Retrieve the Instance ID default token, and if it is non-nil, post it
- NSString *token = [self.instanceIDProxy token];
+ NSString *token = self.instanceID.token;
// Sometimes Instance ID doesn't yet have a token, so wait until the default
// token is fetched, and then notify. This ensures that this token should not
// be nil when the developer accesses it.
@@ -810,29 +848,11 @@ NSString * const FIRMessagingRegistrationTokenRefreshedNotification =
if (self.defaultFcmToken && ![self.defaultFcmToken isEqualToString:oldToken]) {
[self notifyDelegateOfFCMTokenAvailability];
}
- // Call deprecated refresh method, because it should still work (until it is removed).
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wdeprecated-declarations"
- if ([self.delegate respondsToSelector:@selector(messaging:didRefreshRegistrationToken:)]) {
- [self.delegate messaging:self didRefreshRegistrationToken:token];
- }
-#pragma clang diagnostic pop
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center postNotificationName:FIRMessagingRegistrationTokenRefreshedNotification object:nil];
}
}
-- (void)didReceiveAPNSToken:(NSNotification *)notification {
- NSData *apnsToken = notification.object;
- if (![apnsToken isKindOfClass:[NSData class]]) {
- FIRMessagingLoggerDebug(kFIRMessagingMessageCodeMessaging016, @"Invalid APNS token type %@",
- NSStringFromClass([notification.object class]));
- return;
- }
- // Set this value directly, and since this came from InstanceID, don't set it back to InstanceID
- self.apnsTokenData = [apnsToken copy];
-}
-
#pragma mark - Application Support Directory
+ (BOOL)hasApplicationSupportSubDirectory:(NSString *)subDirectoryName {
diff --git a/Firebase/Messaging/FIRMessagingClient.h b/Firebase/Messaging/FIRMessagingClient.h
index 1726428..cb76e98 100644
--- a/Firebase/Messaging/FIRMessagingClient.h
+++ b/Firebase/Messaging/FIRMessagingClient.h
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#import "FIRMessagingTopicsCommon.h"
+#import "FIRMessaging.h"
@class FIRReachabilityChecker;
@class GPBMessage;
diff --git a/Firebase/Messaging/FIRMessagingClient.m b/Firebase/Messaging/FIRMessagingClient.m
index 51a62b7..d36778d 100644
--- a/Firebase/Messaging/FIRMessagingClient.m
+++ b/Firebase/Messaging/FIRMessagingClient.m
@@ -18,6 +18,7 @@
#import <FirebaseCore/FIRReachabilityChecker.h>
+#import "FIRMessaging.h"
#import "FIRMessagingConnection.h"
#import "FIRMessagingConstants.h"
#import "FIRMessagingDataMessageManager.h"
@@ -168,8 +169,7 @@ static NSUInteger FIRMessagingServerPort() {
_FIRMessagingDevAssert(handler != nil, @"Invalid handler to FIRMessaging subscribe");
- FIRMessagingTopicOperationCompletion completion =
- ^void(FIRMessagingTopicOperationResult result, NSError * error) {
+ FIRMessagingTopicOperationCompletion completion = ^void(NSError *error) {
if (error) {
FIRMessagingLoggerError(kFIRMessagingMessageCodeClient001, @"Failed to subscribe to topic %@",
error);
@@ -182,7 +182,7 @@ static NSUInteger FIRMessagingServerPort() {
@"Successfully subscribed to topic %@", topic);
}
}
- handler(result, error);
+ handler(error);
};
[self.registrar tryToLoadValidCheckinInfo];
diff --git a/Firebase/Messaging/FIRMessagingContextManagerService.m b/Firebase/Messaging/FIRMessagingContextManagerService.m
index 1c9f653..65f64ad 100644
--- a/Firebase/Messaging/FIRMessagingContextManagerService.m
+++ b/Firebase/Messaging/FIRMessagingContextManagerService.m
@@ -143,8 +143,11 @@ typedef NS_ENUM(NSUInteger, FIRMessagingContextManagerMessageType) {
}
if ([apsDictionary[kFIRMessagingContextManagerTitleKey] length]) {
// |alertTitle| is iOS 8.2+, so check if we can set it
- if ([notification respondsToSelector:@selector(setAlertTitle:)]) {
+ if ([notification respondsToSelector:@selector(setAlertTitle:)]) {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunguarded-availability"
notification.alertTitle = apsDictionary[kFIRMessagingContextManagerTitleKey];
+#pragma pop
}
}
diff --git a/Firebase/Messaging/FIRMessagingInstanceIDProxy.h b/Firebase/Messaging/FIRMessagingInstanceIDProxy.h
deleted file mode 100644
index b7ebd4b..0000000
--- a/Firebase/Messaging/FIRMessagingInstanceIDProxy.h
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright 2017 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/Foundation.h>
-
-typedef void(^FIRMessagingInstanceIDProxyTokenHandler)(NSString * __nullable token,
- NSError * __nullable error);
-
-typedef void(^FIRMessagingInstanceIDProxyDeleteTokenHandler)(NSError * __nullable error);
-
-typedef NS_ENUM(NSInteger, FIRMessagingInstanceIDProxyAPNSTokenType) {
- /// Unknown token type.
- FIRMessagingInstanceIDProxyAPNSTokenTypeUnknown,
- /// Sandbox token type.
- FIRMessagingInstanceIDProxyAPNSTokenTypeSandbox,
- /// Production token type.
- FIRMessagingInstanceIDProxyAPNSTokenTypeProd,
-};
-
-/**
- * FIRMessaging cannot always depend on FIRInstanceID directly, due to how FIRMessaging is
- * packaged. To make it easier to make calls to FIRInstanceID, this proxy class, will provide
- * method names duplicated from FIRInstanceID, while using reflection-based called to proxy
- * the requests.
- */
-@interface FIRMessagingInstanceIDProxy : NSObject
-
-- (void)setAPNSToken:(nonnull NSData *)token type:(FIRMessagingInstanceIDProxyAPNSTokenType)type;
-
-#pragma mark - Tokens
-
-- (nullable NSString *)token;
-
-- (void)tokenWithAuthorizedEntity:(nonnull NSString *)authorizedEntity
- scope:(nonnull NSString *)scope
- options:(nullable NSDictionary *)options
- handler:(nonnull FIRMessagingInstanceIDProxyTokenHandler)handler;
-
-- (void)deleteTokenWithAuthorizedEntity:(nonnull NSString *)authorizedEntity
- scope:(nonnull NSString *)scope
- handler:
- (nonnull FIRMessagingInstanceIDProxyDeleteTokenHandler)handler;
-@end
diff --git a/Firebase/Messaging/FIRMessagingInstanceIDProxy.m b/Firebase/Messaging/FIRMessagingInstanceIDProxy.m
deleted file mode 100644
index 01b4e73..0000000
--- a/Firebase/Messaging/FIRMessagingInstanceIDProxy.m
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * Copyright 2017 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 "FIRMessagingInstanceIDProxy.h"
-
-@implementation FIRMessagingInstanceIDProxy
-
-+ (nonnull instancetype)instanceIDProxy {
- static id proxyInstanceID = nil;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- Class instanceIDClass = NSClassFromString(@"FIRInstanceID");
- if (!instanceIDClass) {
- proxyInstanceID = nil;
- return;
- }
- SEL instanceIDSelector = NSSelectorFromString(@"instanceID");
- if (![instanceIDClass respondsToSelector:instanceIDSelector]) {
- proxyInstanceID = nil;
- return;
- }
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
- proxyInstanceID = [instanceIDClass performSelector:instanceIDSelector];
-#pragma clang diagnostic pop
- });
- return (FIRMessagingInstanceIDProxy *)proxyInstanceID;
-
-}
-
-- (void)setAPNSToken:(nonnull NSData *)token
- type:(FIRMessagingInstanceIDProxyAPNSTokenType)type {
- id proxy = [[self class] instanceIDProxy];
-
- SEL setAPNSTokenSelector = NSSelectorFromString(@"setAPNSToken:type:");
- if (![proxy respondsToSelector:setAPNSTokenSelector]) {
- return;
- }
- // Since setAPNSToken takes a scalar value, use NSInvocation
- NSMethodSignature *methodSignature =
- [[proxy class] instanceMethodSignatureForSelector:setAPNSTokenSelector];
- NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
- invocation.selector = setAPNSTokenSelector;
- invocation.target = proxy;
- [invocation setArgument:&token atIndex:2];
- [invocation setArgument:&type atIndex:3];
- [invocation invoke];
-}
-
-#pragma mark - Tokens
-
-- (nullable NSString *)token {
- id proxy = [[self class] instanceIDProxy];
- SEL getTokenSelector = NSSelectorFromString(@"token");
- if (![proxy respondsToSelector:getTokenSelector]) {
- return nil;
- }
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
- return [proxy performSelector:getTokenSelector];
-#pragma clang diagnostic pop
-}
-
-
-- (void)tokenWithAuthorizedEntity:(nonnull NSString *)authorizedEntity
- scope:(nonnull NSString *)scope
- options:(nullable NSDictionary *)options
- handler:(nonnull FIRMessagingInstanceIDProxyTokenHandler)handler {
-
- id proxy = [[self class] instanceIDProxy];
- SEL getTokenSelector = NSSelectorFromString(@"tokenWithAuthorizedEntity:scope:options:handler:");
- if (![proxy respondsToSelector:getTokenSelector]) {
- return;
- }
- // Since there are >2 arguments, use NSInvocation
- NSMethodSignature *methodSignature =
- [[proxy class] instanceMethodSignatureForSelector:getTokenSelector];
- NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
- invocation.selector = getTokenSelector;
- invocation.target = proxy;
- [invocation setArgument:&authorizedEntity atIndex:2];
- [invocation setArgument:&scope atIndex:3];
- [invocation setArgument:&options atIndex:4];
- [invocation setArgument:&handler atIndex:5];
- [invocation invoke];
-}
-
-- (void)deleteTokenWithAuthorizedEntity:(nonnull NSString *)authorizedEntity
- scope:(nonnull NSString *)scope
- handler:
- (nonnull FIRMessagingInstanceIDProxyDeleteTokenHandler)handler {
-
- id proxy = [[self class] instanceIDProxy];
- SEL deleteTokenSelector = NSSelectorFromString(@"deleteTokenWithAuthorizedEntity:scope:handler:");
- if (![proxy respondsToSelector:deleteTokenSelector]) {
- return;
- }
- // Since there are >2 arguments, use NSInvocation
- NSMethodSignature *methodSignature =
- [[proxy class] instanceMethodSignatureForSelector:deleteTokenSelector];
- NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
- invocation.selector = deleteTokenSelector;
- invocation.target = proxy;
- [invocation setArgument:&authorizedEntity atIndex:2];
- [invocation setArgument:&scope atIndex:3];
- [invocation setArgument:&handler atIndex:4];
- [invocation invoke];
-}
-
-@end
diff --git a/Firebase/Messaging/FIRMessagingPendingTopicsList.h b/Firebase/Messaging/FIRMessagingPendingTopicsList.h
index c5a306a..a8108bf 100644
--- a/Firebase/Messaging/FIRMessagingPendingTopicsList.h
+++ b/Firebase/Messaging/FIRMessagingPendingTopicsList.h
@@ -16,6 +16,7 @@
#import <Foundation/Foundation.h>
+#import "FIRMessaging.h"
#import "FIRMessagingTopicsCommon.h"
NS_ASSUME_NONNULL_BEGIN
diff --git a/Firebase/Messaging/FIRMessagingPendingTopicsList.m b/Firebase/Messaging/FIRMessagingPendingTopicsList.m
index 792090e..b10b552 100644
--- a/Firebase/Messaging/FIRMessagingPendingTopicsList.m
+++ b/Firebase/Messaging/FIRMessagingPendingTopicsList.m
@@ -160,9 +160,10 @@ NSString *const kPendingTopicsTimestampEncodingKey = @"ts";
if (completion) {
NSMutableArray *handlers = lastBatch.topicHandlers[topic];
if (!handlers) {
- handlers = [NSMutableArray arrayWithCapacity:1];
+ handlers = [[NSMutableArray alloc] init];
}
[handlers addObject:completion];
+ lastBatch.topicHandlers[topic] = handlers;
}
if (!self.currentBatch) {
self.currentBatch = lastBatch;
@@ -216,46 +217,49 @@ NSString *const kPendingTopicsTimestampEncodingKey = @"ts";
[self.topicsInFlight addObject:topic];
}
FIRMessaging_WEAKIFY(self);
- [self.delegate pendingTopicsList:self
- requestedUpdateForTopic:topic
- action:self.currentBatch.action
- completion:^(FIRMessagingTopicOperationResult result, NSError * error) {
- dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
- FIRMessaging_STRONGIFY(self);
- @synchronized (self) {
- [self.topicsInFlight removeObject:topic];
-
- BOOL recoverableError = [self subscriptionErrorIsRecoverable:error];
- if (result == FIRMessagingTopicOperationResultSucceeded ||
- result == FIRMessagingTopicOperationResultCancelled ||
- !recoverableError) {
- // Notify our handlers and remove the topic from our batch
- NSMutableArray *handlers = self.currentBatch.topicHandlers[topic];
- if (handlers.count) {
- dispatch_async(dispatch_get_main_queue(), ^{
- for (FIRMessagingTopicOperationCompletion handler in handlers) {
- handler(result, error);
- }
- [handlers removeAllObjects];
- });
- }
- [self.currentBatch.topics removeObject:topic];
- [self.currentBatch.topicHandlers removeObjectForKey:topic];
- if (self.currentBatch.topics.count == 0) {
- // All topic updates successfully finished in this batch, move on to the next batch
- [self.topicBatches removeObject:self.currentBatch];
- self.currentBatch = nil;
- }
- [self.delegate pendingTopicsListDidUpdate:self];
- FIRMessaging_WEAKIFY(self)
- dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
- FIRMessaging_STRONGIFY(self)
- [self resumeOperationsIfNeeded];
- });
- }
- }
- });
- }];
+ [self.delegate
+ pendingTopicsList:self
+ requestedUpdateForTopic:topic
+ action:self.currentBatch.action
+ completion:^(NSError *error) {
+ dispatch_async(
+ dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
+ FIRMessaging_STRONGIFY(self);
+ @synchronized(self) {
+ [self.topicsInFlight removeObject:topic];
+
+ BOOL recoverableError = [self subscriptionErrorIsRecoverable:error];
+ if (!error || !recoverableError) {
+ // Notify our handlers and remove the topic from our batch
+ NSMutableArray *handlers = self.currentBatch.topicHandlers[topic];
+ if (handlers.count) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ for (FIRMessagingTopicOperationCompletion handler in handlers) {
+ handler(error);
+ }
+ [handlers removeAllObjects];
+ });
+ }
+ [self.currentBatch.topics removeObject:topic];
+ [self.currentBatch.topicHandlers removeObjectForKey:topic];
+ if (self.currentBatch.topics.count == 0) {
+ // All topic updates successfully finished in this batch, move on
+ // to the next batch
+ [self.topicBatches removeObject:self.currentBatch];
+ self.currentBatch = nil;
+ }
+ [self.delegate pendingTopicsListDidUpdate:self];
+ FIRMessaging_WEAKIFY(self);
+ dispatch_async(
+ dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),
+ ^{
+ FIRMessaging_STRONGIFY(self);
+ [self resumeOperationsIfNeeded];
+ });
+ }
+ }
+ });
+ }];
}
@end
diff --git a/Firebase/Messaging/FIRMessagingPubSub.h b/Firebase/Messaging/FIRMessagingPubSub.h
index 3a03494..1c615d1 100644
--- a/Firebase/Messaging/FIRMessagingPubSub.h
+++ b/Firebase/Messaging/FIRMessagingPubSub.h
@@ -14,7 +14,9 @@
* limitations under the License.
*/
-#import "FIRMessagingTopicsCommon.h"
+#import "FIRMessaging.h"
+
+NS_ASSUME_NONNULL_BEGIN
@class FIRMessagingClient;
@class FIRMessagingPubSubCache;
@@ -59,6 +61,7 @@
* library for a given `authorizedEntity` and "gcm" scope.
* @param topic The topic to subscribe to. Should be of the form
* `"/topics/<topic-name>"`.
+ * @param options Unused parameter, please pass nil or empty dictionary.
* @param handler The callback handler invoked when the subscribe call
* ends. In case of success, a nil error is returned. Otherwise,
* an appropriate error object is returned.
@@ -67,10 +70,9 @@
*/
- (void)subscribeWithToken:(NSString *)token
topic:(NSString *)topic
- options:(NSDictionary *)options
+ options:(nullable NSDictionary *)options
handler:(FIRMessagingTopicOperationCompletion)handler;
-
/**
* Unsubscribes an app instance from a topic, stopping it from receiving
* any further messages sent to that topic.
@@ -81,6 +83,7 @@
* @param token The token used to subscribe to this topic.
* @param topic The topic to unsubscribe from. Should be of the form
* `"/topics/<topic-name>"`.
+ * @param options Unused parameter, please pass nil or empty dictionary.
* @param handler The handler that is invoked once the unsubscribe call ends.
* In case of success, nil error is returned. Otherwise, an
* appropriate error object is returned.
@@ -89,7 +92,7 @@
*/
- (void)unsubscribeWithToken:(NSString *)token
topic:(NSString *)topic
- options:(NSDictionary *)options
+ options:(nullable NSDictionary *)options
handler:(FIRMessagingTopicOperationCompletion)handler;
/**
@@ -98,8 +101,12 @@
* as compared to the `subscribe` method above which tries once.
*
* @param topic The topic name to subscribe to. Should be of the form `"/topics/<topic-name>"`.
+ * @param handler The handler that is invoked once the unsubscribe call ends.
+ * In case of success, nil error is returned. Otherwise, an
+ * appropriate error object is returned.
*/
-- (void)subscribeToTopic:(NSString *)topic;
+- (void)subscribeToTopic:(NSString *)topic
+ handler:(nullable FIRMessagingTopicOperationCompletion)handler;
/**
* Asynchronously unsubscribe from the topic. Adds to the pending list of topic operations.
@@ -107,8 +114,12 @@
* as compared to the `unsubscribe` method above which tries once.
*
* @param topic The topic name to unsubscribe from. Should be of the form `"/topics/<topic-name>"`.
+ * @param handler The handler that is invoked once the unsubscribe call ends.
+ * In case of success, nil error is returned. Otherwise, an
+ * appropriate error object is returned.
*/
-- (void)unsubscribeFromTopic:(NSString *)topic;
+- (void)unsubscribeFromTopic:(NSString *)topic
+ handler:(nullable FIRMessagingTopicOperationCompletion)handler;
/**
* Schedule subscriptions sync.
@@ -146,3 +157,5 @@
+ (BOOL)isValidTopicWithPrefix:(NSString *)topic;
@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firebase/Messaging/FIRMessagingPubSub.m b/Firebase/Messaging/FIRMessagingPubSub.m
index c8293e0..09491b4 100644
--- a/Firebase/Messaging/FIRMessagingPubSub.m
+++ b/Firebase/Messaging/FIRMessagingPubSub.m
@@ -60,8 +60,7 @@ static NSString *const kPendingSubscriptionsListKey =
_FIRMessagingDevAssert([token length], @"FIRMessaging error no token specified");
_FIRMessagingDevAssert([topic length], @"FIRMessaging error Invalid empty topic specified");
if (!self.client) {
- handler(FIRMessagingTopicOperationResultError,
- [NSError errorWithFCMErrorCode:kFIRMessagingErrorCodePubSubFIRMessagingNotSetup]);
+ handler([NSError errorWithFCMErrorCode:kFIRMessagingErrorCodePubSubFIRMessagingNotSetup]);
return;
}
@@ -75,8 +74,7 @@ static NSString *const kPendingSubscriptionsListKey =
if (![[self class] isValidTopicWithPrefix:topic]) {
FIRMessagingLoggerError(kFIRMessagingMessageCodePubSub000,
@"Invalid FIRMessaging Pubsub topic %@", topic);
- handler(FIRMessagingTopicOperationResultError,
- [NSError errorWithFCMErrorCode:kFIRMessagingErrorCodePubSubInvalidTopic]);
+ handler([NSError errorWithFCMErrorCode:kFIRMessagingErrorCodePubSubInvalidTopic]);
return;
}
@@ -93,11 +91,9 @@ static NSString *const kPendingSubscriptionsListKey =
topic:topic
options:options
shouldDelete:NO
- handler:
- ^void(FIRMessagingTopicOperationResult result, NSError * error) {
-
- handler(result, error);
- }];
+ handler:^void(NSError *error) {
+ handler(error);
+ }];
}
- (void)unsubscribeWithToken:(NSString *)token
@@ -108,8 +104,7 @@ static NSString *const kPendingSubscriptionsListKey =
_FIRMessagingDevAssert([topic length], @"FIRMessaging error Invalid empty topic specified");
if (!self.client) {
- handler(FIRMessagingTopicOperationResultError,
- [NSError errorWithFCMErrorCode:kFIRMessagingErrorCodePubSubFIRMessagingNotSetup]);
+ handler([NSError errorWithFCMErrorCode:kFIRMessagingErrorCodePubSubFIRMessagingNotSetup]);
return;
}
@@ -122,8 +117,7 @@ static NSString *const kPendingSubscriptionsListKey =
if (![[self class] isValidTopicWithPrefix:topic]) {
FIRMessagingLoggerError(kFIRMessagingMessageCodePubSub002,
@"Invalid FIRMessaging Pubsub topic %@", topic);
- handler(FIRMessagingTopicOperationResultError,
- [NSError errorWithFCMErrorCode:kFIRMessagingErrorCodePubSubInvalidTopic]);
+ handler([NSError errorWithFCMErrorCode:kFIRMessagingErrorCodePubSubInvalidTopic]);
return;
}
if (![self verifyPubSubOptions:options]) {
@@ -139,23 +133,23 @@ static NSString *const kPendingSubscriptionsListKey =
topic:topic
options:options
shouldDelete:YES
- handler:
- ^void(FIRMessagingTopicOperationResult result, NSError * error) {
-
- handler(result, error);
- }];
+ handler:^void(NSError *error) {
+ handler(error);
+ }];
}
-- (void)subscribeToTopic:(NSString *)topic {
+- (void)subscribeToTopic:(NSString *)topic
+ handler:(nullable FIRMessagingTopicOperationCompletion)handler {
[self.pendingTopicUpdates addOperationForTopic:topic
withAction:FIRMessagingTopicActionSubscribe
- completion:nil];
+ completion:handler];
}
-- (void)unsubscribeFromTopic:(NSString *)topic {
+- (void)unsubscribeFromTopic:(NSString *)topic
+ handler:(nullable FIRMessagingTopicOperationCompletion)handler {
[self.pendingTopicUpdates addOperationForTopic:topic
withAction:FIRMessagingTopicActionUnsubscribe
- completion:nil];
+ completion:handler];
}
- (void)scheduleSync:(BOOL)immediately {
diff --git a/Firebase/Messaging/FIRMessagingRegistrar.h b/Firebase/Messaging/FIRMessagingRegistrar.h
index 5b60437..35f3ff3 100644
--- a/Firebase/Messaging/FIRMessagingRegistrar.h
+++ b/Firebase/Messaging/FIRMessagingRegistrar.h
@@ -14,8 +14,8 @@
* limitations under the License.
*/
+#import "FIRMessaging.h"
#import "FIRMessagingCheckinService.h"
-#import "FIRMessagingTopicsCommon.h"
@class FIRMessagingCheckinStore;
@class FIRMessagingPubSubRegistrar;
diff --git a/Firebase/Messaging/FIRMessagingRegistrar.m b/Firebase/Messaging/FIRMessagingRegistrar.m
index ab57b9e..bb1e9ad 100644
--- a/Firebase/Messaging/FIRMessagingRegistrar.m
+++ b/Firebase/Messaging/FIRMessagingRegistrar.m
@@ -83,7 +83,7 @@
FIRMessagingLoggerDebug(kFIRMessagingMessageCodeRegistrar000,
@"Device check in error, no auth credentials found");
NSError *error = [NSError errorWithFCMErrorCode:kFIRMessagingErrorCodeMissingDeviceID];
- handler(FIRMessagingTopicOperationResultError, error);
+ handler(error);
}
}
diff --git a/Firebase/Messaging/FIRMessagingRemoteNotificationsProxy.m b/Firebase/Messaging/FIRMessagingRemoteNotificationsProxy.m
index 0b8d92b..e9d4791 100644
--- a/Firebase/Messaging/FIRMessagingRemoteNotificationsProxy.m
+++ b/Firebase/Messaging/FIRMessagingRemoteNotificationsProxy.m
@@ -30,6 +30,7 @@ static NSString *kUserNotificationWillPresentSelectorString =
@"userNotificationCenter:willPresentNotification:withCompletionHandler:";
static NSString *kUserNotificationDidReceiveResponseSelectorString =
@"userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:";
+static NSString *kReceiveDataMessageSelectorString = @"messaging:didReceiveMessage:";
@interface FIRMessagingRemoteNotificationsProxy ()
@@ -141,9 +142,6 @@ static NSString *kUserNotificationDidReceiveResponseSelectorString =
SEL remoteNotificationWithFetchHandlerSelector =
@selector(application:didReceiveRemoteNotification:fetchCompletionHandler:);
- // For data message from MCS.
- SEL receiveDataMessageSelector = NSSelectorFromString(@"applicationReceivedRemoteMessage:");
-
// For recording when APNS tokens are registered (or fail to register)
SEL registerForAPNSFailSelector =
@selector(application:didFailToRegisterForRemoteNotificationsWithError:);
@@ -172,11 +170,13 @@ static NSString *kUserNotificationDidReceiveResponseSelectorString =
didSwizzleAppDelegate = YES;
}
+ // For data message from MCS.
+ SEL receiveDataMessageSelector = NSSelectorFromString(kReceiveDataMessageSelectorString);
if ([appDelegate respondsToSelector:receiveDataMessageSelector]) {
[self swizzleSelector:receiveDataMessageSelector
- inClass:appDelegateClass
- withImplementation:(IMP)FCM_swizzle_applicationReceivedRemoteMessage
- inProtocol:@protocol(UIApplicationDelegate)];
+ inClass:appDelegateClass
+ withImplementation:(IMP)FCM_swizzle_messagingDidReceiveMessage
+ inProtocol:@protocol(UIApplicationDelegate)];
didSwizzleAppDelegate = YES;
}
@@ -669,14 +669,15 @@ id userInfoFromNotification(id notification) {
return notificationUserInfo;
}
-void FCM_swizzle_applicationReceivedRemoteMessage(
- id self, SEL _cmd, FIRMessagingRemoteMessage *remoteMessage) {
+void FCM_swizzle_messagingDidReceiveMessage(id self, SEL _cmd, FIRMessaging *message,
+ FIRMessagingRemoteMessage *remoteMessage) {
[[FIRMessaging messaging] appDidReceiveMessage:remoteMessage.appData];
IMP original_imp =
[[FIRMessagingRemoteNotificationsProxy sharedProxy] originalImplementationForSelector:_cmd];
if (original_imp) {
- ((void (*)(id, SEL, FIRMessagingRemoteMessage *))original_imp)(self, _cmd, remoteMessage);
+ ((void (*)(id, SEL, FIRMessaging *, FIRMessagingRemoteMessage *))original_imp)(
+ self, _cmd, message, remoteMessage);
}
}
diff --git a/Firebase/Messaging/FIRMessagingRmq2PersistentStore.m b/Firebase/Messaging/FIRMessagingRmq2PersistentStore.m
index a85298c..f58bd52 100644
--- a/Firebase/Messaging/FIRMessagingRmq2PersistentStore.m
+++ b/Firebase/Messaging/FIRMessagingRmq2PersistentStore.m
@@ -104,8 +104,11 @@ typedef void(^FCMOutgoingRmqMessagesTableHandler)(int64_t rmqId, int8_t tag, NSD
// Utility to create an NSString from a sqlite3 result code
NSString * _Nonnull FIRMessagingStringFromSQLiteResult(int result) {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunguarded-availability"
const char *errorStr = sqlite3_errstr(result);
- NSString *errorString = [NSString stringWithFormat:@"%d - %s", result, errorStr];
+#pragma pop
+ NSString *errorString = [NSString stringWithFormat:@"%d - %s", result, errorStr];
return errorString;
}
diff --git a/Firebase/Messaging/FIRMessagingTopicOperation.h b/Firebase/Messaging/FIRMessagingTopicOperation.h
index e4bbde8..ea98e6d 100644
--- a/Firebase/Messaging/FIRMessagingTopicOperation.h
+++ b/Firebase/Messaging/FIRMessagingTopicOperation.h
@@ -16,6 +16,7 @@
#import <Foundation/Foundation.h>
+#import "FIRMessaging.h"
#import "FIRMessagingCheckinService.h"
#import "FIRMessagingTopicsCommon.h"
diff --git a/Firebase/Messaging/FIRMessagingTopicOperation.m b/Firebase/Messaging/FIRMessagingTopicOperation.m
index 90760eb..6703178 100644
--- a/Firebase/Messaging/FIRMessagingTopicOperation.m
+++ b/Firebase/Messaging/FIRMessagingTopicOperation.m
@@ -82,6 +82,7 @@ NSString *FIRMessagingSubscriptionsServer() {
_topic = topic;
_action = action;
_token = token;
+ _options = options;
_checkinService = checkinService;
_completion = completion;
@@ -124,7 +125,9 @@ NSString *FIRMessagingSubscriptionsServer() {
- (void)start {
if (self.isCancelled) {
- [self finishWithResult:FIRMessagingTopicOperationResultCancelled error:nil];
+ NSError *error =
+ [NSError errorWithFCMErrorCode:kFIRMessagingErrorCodePubSubOperationIsCancelled];
+ [self finishWithError:error];
return;
}
@@ -133,14 +136,14 @@ NSString *FIRMessagingSubscriptionsServer() {
[self performSubscriptionChange];
}
-- (void)finishWithResult:(FIRMessagingTopicOperationResult)result error:(NSError *)error {
+- (void)finishWithError:(NSError *)error {
// Add a check to prevent this finish from being called more than once.
if (self.isFinished) {
return;
}
self.dataTask = nil;
if (self.completion) {
- self.completion(result, error);
+ self.completion(error);
}
[self setExecuting:NO];
@@ -150,7 +153,8 @@ NSString *FIRMessagingSubscriptionsServer() {
- (void)cancel {
[super cancel];
[self.dataTask cancel];
- [self finishWithResult:FIRMessagingTopicOperationResultCancelled error:nil];
+ NSError *error = [NSError errorWithFCMErrorCode:kFIRMessagingErrorCodePubSubOperationIsCancelled];
+ [self finishWithError:error];
}
- (void)performSubscriptionChange {
@@ -217,29 +221,29 @@ NSString *FIRMessagingSubscriptionsServer() {
FIRMessagingLoggerDebug(kFIRMessagingMessageCodeTopicOption001,
@"Device registration HTTP fetch error. Error Code: %ld",
_FIRMessaging_L(error.code));
- [self finishWithResult:FIRMessagingTopicOperationResultError error:error];
+ [self finishWithError:error];
return;
}
NSString *response = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
if (response.length == 0) {
- [self finishWithResult:FIRMessagingTopicOperationResultError
- error:[NSError errorWithFCMErrorCode:kFIRMessagingErrorCodeUnknown]];
+ FIRMessagingLoggerDebug(kFIRMessagingMessageCodeTopicOperationEmptyResponse,
+ @"Invalid registration response - zero length.");
+ [self finishWithError:[NSError errorWithFCMErrorCode:kFIRMessagingErrorCodeUnknown]];
return;
}
NSArray *parts = [response componentsSeparatedByString:@"="];
_FIRMessagingDevAssert(parts.count, @"Invalid registration response");
if (![parts[0] isEqualToString:@"token"] || parts.count <= 1) {
FIRMessagingLoggerDebug(kFIRMessagingMessageCodeTopicOption002,
- @"Invalid registration request, response");
- [self finishWithResult:FIRMessagingTopicOperationResultError
- error:[NSError errorWithFCMErrorCode:kFIRMessagingErrorCodeUnknown]];
+ @"Invalid registration response %@", response);
+ [self finishWithError:[NSError errorWithFCMErrorCode:kFIRMessagingErrorCodeUnknown]];
return;
}
#if DEBUG_LOG_SUBSCRIPTION_OPERATION_DURATIONS
NSTimeInterval duration = -[start timeIntervalSinceNow];
FIRMessagingLoggerDebug(@"%@ change took %.2fs", self.topic, duration);
#endif
- [self finishWithResult:FIRMessagingTopicOperationResultSucceeded error:nil];
+ [self finishWithError:nil];
};
diff --git a/Firebase/Messaging/FIRMessagingTopicsCommon.h b/Firebase/Messaging/FIRMessagingTopicsCommon.h
index 50d8906..030b3ff 100644
--- a/Firebase/Messaging/FIRMessagingTopicsCommon.h
+++ b/Firebase/Messaging/FIRMessagingTopicsCommon.h
@@ -26,27 +26,4 @@ typedef NS_ENUM(NSInteger, FIRMessagingTopicAction) {
FIRMessagingTopicActionUnsubscribe
};
-/**
- * Represents the possible results of a topic operation.
- */
-typedef NS_ENUM(NSInteger, FIRMessagingTopicOperationResult) {
- FIRMessagingTopicOperationResultSucceeded,
- FIRMessagingTopicOperationResultError,
- FIRMessagingTopicOperationResultCancelled,
-};
-
-/**
- * Callback to invoke once the HTTP call to FIRMessaging backend for updating
- * subscription finishes.
- *
- * @param result The result of the operation. If the result is
- * FIRMessagingTopicOperationResultError, the error parameter will be
- * non-nil.
- * @param error The error which occurred while updating the subscription topic
- * on the FIRMessaging server. This will be nil in case the operation
- * was successful, or if the operation was cancelled.
- */
-typedef void(^FIRMessagingTopicOperationCompletion)
- (FIRMessagingTopicOperationResult result, NSError * _Nullable error);
-
NS_ASSUME_NONNULL_END
diff --git a/Firebase/Messaging/FIRMessaging_Private.h b/Firebase/Messaging/FIRMessaging_Private.h
index 0c35179..46daee0 100644
--- a/Firebase/Messaging/FIRMessaging_Private.h
+++ b/Firebase/Messaging/FIRMessaging_Private.h
@@ -25,6 +25,8 @@ typedef NS_ENUM(int8_t, FIRMessagingNetworkStatus) {
kFIRMessagingReachabilityReachableViaWWAN,
};
+FOUNDATION_EXPORT NSString *const kFIRMessagingUserDefaultsKeyAutoInitEnabled;
+
@interface FIRMessagingRemoteMessage ()
@property(nonatomic, strong) NSDictionary *appData;
@@ -50,7 +52,4 @@ typedef NS_ENUM(int8_t, FIRMessagingNetworkStatus) {
- (BOOL)isNetworkAvailable;
- (FIRMessagingNetworkStatus)networkType;
-// Set the APNS token for FCM.
-- (void)setAPNSToken:(NSData *)apnsToken error:(NSError *)error;
-
@end
diff --git a/Firebase/Messaging/NSError+FIRMessaging.h b/Firebase/Messaging/NSError+FIRMessaging.h
index 9b1e214..ae25b5b 100644
--- a/Firebase/Messaging/NSError+FIRMessaging.h
+++ b/Firebase/Messaging/NSError+FIRMessaging.h
@@ -56,6 +56,7 @@ typedef NS_ENUM(NSUInteger, FIRMessagingInternalErrorCode) {
kFIRMessagingErrorCodePubSubAlreadyUnsubscribed = 3002,
kFIRMessagingErrorCodePubSubInvalidTopic = 3003,
kFIRMessagingErrorCodePubSubFIRMessagingNotSetup = 3004,
+ kFIRMessagingErrorCodePubSubOperationIsCancelled = 3005,
};
@interface NSError (FIRMessaging)
diff --git a/Firebase/Messaging/Protos/GtalkCore.pbobjc.h b/Firebase/Messaging/Protos/GtalkCore.pbobjc.h
index d4c8c8c..46d2d9c 100644
--- a/Firebase/Messaging/Protos/GtalkCore.pbobjc.h
+++ b/Firebase/Messaging/Protos/GtalkCore.pbobjc.h
@@ -197,6 +197,9 @@ typedef GPB_ENUM(GtalkClientEvent_Type) {
GtalkClientEvent_Type_DiscardedEvents = 1,
GtalkClientEvent_Type_FailedConnection = 2,
GtalkClientEvent_Type_SuccessfulConnection = 3,
+ GtalkClientEvent_Type_McsReconnectRequest = 4,
+ GtalkClientEvent_Type_FailedSocketCreationMcsReconnect = 5,
+ GtalkClientEvent_Type_McsReconnectLimited = 6,
};
GPBEnumDescriptor *GtalkClientEvent_Type_EnumDescriptor(void);
@@ -207,6 +210,22 @@ GPBEnumDescriptor *GtalkClientEvent_Type_EnumDescriptor(void);
**/
BOOL GtalkClientEvent_Type_IsValidValue(int32_t value);
+#pragma mark - Enum GtalkClientEvent_McsReconnectAction
+
+typedef GPB_ENUM(GtalkClientEvent_McsReconnectAction) {
+ GtalkClientEvent_McsReconnectAction_None = 0,
+ GtalkClientEvent_McsReconnectAction_NotConnected = 1,
+ GtalkClientEvent_McsReconnectAction_TooSoon = 2,
+};
+
+GPBEnumDescriptor *GtalkClientEvent_McsReconnectAction_EnumDescriptor(void);
+
+/**
+ * Checks to see if the given value is defined by the enum or was not known at
+ * the time this source was generated.
+ **/
+BOOL GtalkClientEvent_McsReconnectAction_IsValidValue(int32_t value);
+
#pragma mark - GtalkGtalkCoreRoot
/**
@@ -247,9 +266,9 @@ typedef GPB_ENUM(GtalkHeartbeatPing_FieldNumber) {
@property(nonatomic, readwrite) BOOL hasStatus;
-@property(nonatomic, readwrite, strong, null_resettable) GtalkCellTower *cellTower;
+@property(nonatomic, readwrite, strong, null_resettable) GtalkCellTower *cellTower DEPRECATED_ATTRIBUTE;
/** Test to see if @c cellTower has been set. */
-@property(nonatomic, readwrite) BOOL hasCellTower;
+@property(nonatomic, readwrite) BOOL hasCellTower DEPRECATED_ATTRIBUTE;
@property(nonatomic, readwrite) int32_t intervalMs;
@@ -282,9 +301,9 @@ typedef GPB_ENUM(GtalkHeartbeatAck_FieldNumber) {
@property(nonatomic, readwrite) BOOL hasStatus;
-@property(nonatomic, readwrite, strong, null_resettable) GtalkCellTower *cellTower;
+@property(nonatomic, readwrite, strong, null_resettable) GtalkCellTower *cellTower DEPRECATED_ATTRIBUTE;
/** Test to see if @c cellTower has been set. */
-@property(nonatomic, readwrite) BOOL hasCellTower;
+@property(nonatomic, readwrite) BOOL hasCellTower DEPRECATED_ATTRIBUTE;
@property(nonatomic, readwrite) int32_t intervalMs;
@@ -406,7 +425,6 @@ typedef GPB_ENUM(GtalkLoginRequest_FieldNumber) {
GtalkLoginRequest_FieldNumber_DeviceId = 6,
GtalkLoginRequest_FieldNumber_LastRmqId = 7,
GtalkLoginRequest_FieldNumber_SettingArray = 8,
- GtalkLoginRequest_FieldNumber_Compress = 9,
GtalkLoginRequest_FieldNumber_ReceivedPersistentIdArray = 10,
GtalkLoginRequest_FieldNumber_IncludeStreamIds = 11,
GtalkLoginRequest_FieldNumber_HeartbeatStat = 13,
@@ -420,6 +438,8 @@ typedef GPB_ENUM(GtalkLoginRequest_FieldNumber) {
GtalkLoginRequest_FieldNumber_GcmStartTimeMs = 21,
GtalkLoginRequest_FieldNumber_ClientEventArray = 22,
GtalkLoginRequest_FieldNumber_OnFallback = 23,
+ GtalkLoginRequest_FieldNumber_NoPendingUpstream = 24,
+ GtalkLoginRequest_FieldNumber_ReconnectRequestId = 25,
};
@interface GtalkLoginRequest : GPBMessage
@@ -464,10 +484,6 @@ typedef GPB_ENUM(GtalkLoginRequest_FieldNumber) {
@property(nonatomic, readonly) NSUInteger settingArray_Count;
-@property(nonatomic, readwrite) int32_t compress;
-
-@property(nonatomic, readwrite) BOOL hasCompress;
-
@property(nonatomic, readwrite, strong, null_resettable) NSMutableArray<NSString*> *receivedPersistentIdArray;
/** The number of items in @c receivedPersistentIdArray without causing the array to be created. */
@property(nonatomic, readonly) NSUInteger receivedPersistentIdArray_Count;
@@ -507,9 +523,9 @@ typedef GPB_ENUM(GtalkLoginRequest_FieldNumber) {
@property(nonatomic, readwrite) BOOL hasTokenVersionInfo;
-@property(nonatomic, readwrite, strong, null_resettable) GtalkCellTower *cellTower;
+@property(nonatomic, readwrite, strong, null_resettable) GtalkCellTower *cellTower DEPRECATED_ATTRIBUTE;
/** Test to see if @c cellTower has been set. */
-@property(nonatomic, readwrite) BOOL hasCellTower;
+@property(nonatomic, readwrite) BOOL hasCellTower DEPRECATED_ATTRIBUTE;
@property(nonatomic, readwrite) uint64_t gcmStartTimeMs;
@@ -524,6 +540,14 @@ typedef GPB_ENUM(GtalkLoginRequest_FieldNumber) {
@property(nonatomic, readwrite) BOOL onFallback;
@property(nonatomic, readwrite) BOOL hasOnFallback;
+
+@property(nonatomic, readwrite) BOOL noPendingUpstream;
+
+@property(nonatomic, readwrite) BOOL hasNoPendingUpstream;
+
+@property(nonatomic, readwrite) int32_t reconnectRequestId;
+
+@property(nonatomic, readwrite) BOOL hasReconnectRequestId;
@end
#pragma mark - GtalkLoginResponse
@@ -1242,9 +1266,9 @@ typedef GPB_ENUM(GtalkDataMessageStanza_FieldNumber) {
@property(nonatomic, readwrite) BOOL hasFlags;
-@property(nonatomic, readwrite, strong, null_resettable) GtalkCellTower *cellTower;
+@property(nonatomic, readwrite, strong, null_resettable) GtalkCellTower *cellTower DEPRECATED_ATTRIBUTE;
/** Test to see if @c cellTower has been set. */
-@property(nonatomic, readwrite) BOOL hasCellTower;
+@property(nonatomic, readwrite) BOOL hasCellTower DEPRECATED_ATTRIBUTE;
@property(nonatomic, readwrite) int32_t priority;
@@ -1273,6 +1297,7 @@ typedef GPB_ENUM(GtalkCellTower_FieldNumber) {
GtalkCellTower_FieldNumber_KnownCongestionStatus = 2,
};
+DEPRECATED_ATTRIBUTE
@interface GtalkCellTower : GPBMessage
@@ -1297,6 +1322,7 @@ typedef GPB_ENUM(GtalkClientEvent_FieldNumber) {
GtalkClientEvent_FieldNumber_TimeConnectionEndedMs = 203,
GtalkClientEvent_FieldNumber_ErrorCode = 204,
GtalkClientEvent_FieldNumber_TimeConnectionEstablishedMs = 300,
+ GtalkClientEvent_FieldNumber_McsReconnectAction = 400,
};
@interface GtalkClientEvent : GPBMessage
@@ -1333,6 +1359,10 @@ typedef GPB_ENUM(GtalkClientEvent_FieldNumber) {
@property(nonatomic, readwrite) uint64_t timeConnectionEstablishedMs;
@property(nonatomic, readwrite) BOOL hasTimeConnectionEstablishedMs;
+
+@property(nonatomic, readwrite) GtalkClientEvent_McsReconnectAction mcsReconnectAction;
+
+@property(nonatomic, readwrite) BOOL hasMcsReconnectAction;
@end
NS_ASSUME_NONNULL_END
diff --git a/Firebase/Messaging/Protos/GtalkCore.pbobjc.m b/Firebase/Messaging/Protos/GtalkCore.pbobjc.m
index f4efe22..06c9134 100644
--- a/Firebase/Messaging/Protos/GtalkCore.pbobjc.m
+++ b/Firebase/Messaging/Protos/GtalkCore.pbobjc.m
@@ -503,7 +503,6 @@ typedef struct GtalkHeartbeatConfig__storage_ {
@dynamic hasDeviceId, deviceId;
@dynamic hasLastRmqId, lastRmqId;
@dynamic settingArray, settingArray_Count;
-@dynamic hasCompress, compress;
@dynamic receivedPersistentIdArray, receivedPersistentIdArray_Count;
@dynamic hasIncludeStreamIds, includeStreamIds;
@dynamic hasHeartbeatStat, heartbeatStat;
@@ -517,12 +516,14 @@ typedef struct GtalkHeartbeatConfig__storage_ {
@dynamic hasGcmStartTimeMs, gcmStartTimeMs;
@dynamic clientEventArray, clientEventArray_Count;
@dynamic hasOnFallback, onFallback;
+@dynamic hasNoPendingUpstream, noPendingUpstream;
+@dynamic hasReconnectRequestId, reconnectRequestId;
typedef struct GtalkLoginRequest__storage_ {
uint32_t _has_storage_[1];
- int32_t compress;
GtalkLoginRequest_AuthService authService;
int32_t networkType;
+ int32_t reconnectRequestId;
NSString *id_p;
NSString *domain;
NSString *user;
@@ -620,15 +621,6 @@ typedef struct GtalkLoginRequest__storage_ {
.dataType = GPBDataTypeMessage,
},
{
- .name = "compress",
- .dataTypeSpecific.className = NULL,
- .number = GtalkLoginRequest_FieldNumber_Compress,
- .hasIndex = 7,
- .offset = (uint32_t)offsetof(GtalkLoginRequest__storage_, compress),
- .flags = GPBFieldOptional,
- .dataType = GPBDataTypeInt32,
- },
- {
.name = "receivedPersistentIdArray",
.dataTypeSpecific.className = NULL,
.number = GtalkLoginRequest_FieldNumber_ReceivedPersistentIdArray,
@@ -641,8 +633,8 @@ typedef struct GtalkLoginRequest__storage_ {
.name = "includeStreamIds",
.dataTypeSpecific.className = NULL,
.number = GtalkLoginRequest_FieldNumber_IncludeStreamIds,
- .hasIndex = 8,
- .offset = 9, // Stored in _has_storage_ to save space.
+ .hasIndex = 7,
+ .offset = 8, // Stored in _has_storage_ to save space.
.flags = GPBFieldOptional,
.dataType = GPBDataTypeBool,
},
@@ -650,7 +642,7 @@ typedef struct GtalkLoginRequest__storage_ {
.name = "heartbeatStat",
.dataTypeSpecific.className = GPBStringifySymbol(GtalkHeartbeatStat),
.number = GtalkLoginRequest_FieldNumber_HeartbeatStat,
- .hasIndex = 10,
+ .hasIndex = 9,
.offset = (uint32_t)offsetof(GtalkLoginRequest__storage_, heartbeatStat),
.flags = GPBFieldOptional,
.dataType = GPBDataTypeMessage,
@@ -659,8 +651,8 @@ typedef struct GtalkLoginRequest__storage_ {
.name = "useRmq2",
.dataTypeSpecific.className = NULL,
.number = GtalkLoginRequest_FieldNumber_UseRmq2,
- .hasIndex = 11,
- .offset = 12, // Stored in _has_storage_ to save space.
+ .hasIndex = 10,
+ .offset = 11, // Stored in _has_storage_ to save space.
.flags = GPBFieldOptional,
.dataType = GPBDataTypeBool,
},
@@ -668,7 +660,7 @@ typedef struct GtalkLoginRequest__storage_ {
.name = "accountId",
.dataTypeSpecific.className = NULL,
.number = GtalkLoginRequest_FieldNumber_AccountId,
- .hasIndex = 13,
+ .hasIndex = 12,
.offset = (uint32_t)offsetof(GtalkLoginRequest__storage_, accountId),
.flags = GPBFieldOptional,
.dataType = GPBDataTypeInt64,
@@ -677,7 +669,7 @@ typedef struct GtalkLoginRequest__storage_ {
.name = "authService",
.dataTypeSpecific.enumDescFunc = GtalkLoginRequest_AuthService_EnumDescriptor,
.number = GtalkLoginRequest_FieldNumber_AuthService,
- .hasIndex = 14,
+ .hasIndex = 13,
.offset = (uint32_t)offsetof(GtalkLoginRequest__storage_, authService),
.flags = (GPBFieldFlags)(GPBFieldOptional | GPBFieldHasEnumDescriptor),
.dataType = GPBDataTypeEnum,
@@ -686,7 +678,7 @@ typedef struct GtalkLoginRequest__storage_ {
.name = "networkType",
.dataTypeSpecific.className = NULL,
.number = GtalkLoginRequest_FieldNumber_NetworkType,
- .hasIndex = 15,
+ .hasIndex = 14,
.offset = (uint32_t)offsetof(GtalkLoginRequest__storage_, networkType),
.flags = GPBFieldOptional,
.dataType = GPBDataTypeInt32,
@@ -695,7 +687,7 @@ typedef struct GtalkLoginRequest__storage_ {
.name = "status",
.dataTypeSpecific.className = NULL,
.number = GtalkLoginRequest_FieldNumber_Status,
- .hasIndex = 16,
+ .hasIndex = 15,
.offset = (uint32_t)offsetof(GtalkLoginRequest__storage_, status),
.flags = GPBFieldOptional,
.dataType = GPBDataTypeInt64,
@@ -704,7 +696,7 @@ typedef struct GtalkLoginRequest__storage_ {
.name = "tokenVersionInfo",
.dataTypeSpecific.className = NULL,
.number = GtalkLoginRequest_FieldNumber_TokenVersionInfo,
- .hasIndex = 17,
+ .hasIndex = 16,
.offset = (uint32_t)offsetof(GtalkLoginRequest__storage_, tokenVersionInfo),
.flags = GPBFieldOptional,
.dataType = GPBDataTypeString,
@@ -713,7 +705,7 @@ typedef struct GtalkLoginRequest__storage_ {
.name = "cellTower",
.dataTypeSpecific.className = GPBStringifySymbol(GtalkCellTower),
.number = GtalkLoginRequest_FieldNumber_CellTower,
- .hasIndex = 18,
+ .hasIndex = 17,
.offset = (uint32_t)offsetof(GtalkLoginRequest__storage_, cellTower),
.flags = GPBFieldOptional,
.dataType = GPBDataTypeMessage,
@@ -722,7 +714,7 @@ typedef struct GtalkLoginRequest__storage_ {
.name = "gcmStartTimeMs",
.dataTypeSpecific.className = NULL,
.number = GtalkLoginRequest_FieldNumber_GcmStartTimeMs,
- .hasIndex = 19,
+ .hasIndex = 18,
.offset = (uint32_t)offsetof(GtalkLoginRequest__storage_, gcmStartTimeMs),
.flags = GPBFieldOptional,
.dataType = GPBDataTypeUInt64,
@@ -740,11 +732,29 @@ typedef struct GtalkLoginRequest__storage_ {
.name = "onFallback",
.dataTypeSpecific.className = NULL,
.number = GtalkLoginRequest_FieldNumber_OnFallback,
- .hasIndex = 20,
- .offset = 21, // Stored in _has_storage_ to save space.
+ .hasIndex = 19,
+ .offset = 20, // Stored in _has_storage_ to save space.
.flags = GPBFieldOptional,
.dataType = GPBDataTypeBool,
},
+ {
+ .name = "noPendingUpstream",
+ .dataTypeSpecific.className = NULL,
+ .number = GtalkLoginRequest_FieldNumber_NoPendingUpstream,
+ .hasIndex = 21,
+ .offset = 22, // Stored in _has_storage_ to save space.
+ .flags = GPBFieldOptional,
+ .dataType = GPBDataTypeBool,
+ },
+ {
+ .name = "reconnectRequestId",
+ .dataTypeSpecific.className = NULL,
+ .number = GtalkLoginRequest_FieldNumber_ReconnectRequestId,
+ .hasIndex = 23,
+ .offset = (uint32_t)offsetof(GtalkLoginRequest__storage_, reconnectRequestId),
+ .flags = GPBFieldOptional,
+ .dataType = GPBDataTypeInt32,
+ },
};
GPBDescriptor *localDescriptor =
[GPBDescriptor allocDescriptorForClass:[GtalkLoginRequest class]
@@ -2730,6 +2740,9 @@ typedef struct GtalkTalkMetadata__storage_ {
#pragma mark - GtalkCellTower
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-implementations"
+
@implementation GtalkCellTower
@dynamic hasId_p, id_p;
@@ -2782,6 +2795,8 @@ typedef struct GtalkCellTower__storage_ {
@end
+#pragma clang diagnostic pop
+
#pragma mark - GtalkClientEvent
@implementation GtalkClientEvent
@@ -2794,6 +2809,7 @@ typedef struct GtalkCellTower__storage_ {
@dynamic hasTimeConnectionEndedMs, timeConnectionEndedMs;
@dynamic hasErrorCode, errorCode;
@dynamic hasTimeConnectionEstablishedMs, timeConnectionEstablishedMs;
+@dynamic hasMcsReconnectAction, mcsReconnectAction;
typedef struct GtalkClientEvent__storage_ {
uint32_t _has_storage_[1];
@@ -2802,6 +2818,7 @@ typedef struct GtalkClientEvent__storage_ {
int32_t networkType;
int32_t networkPort;
int32_t errorCode;
+ GtalkClientEvent_McsReconnectAction mcsReconnectAction;
uint64_t timeConnectionStartedMs;
uint64_t timeConnectionEndedMs;
uint64_t timeConnectionEstablishedMs;
@@ -2885,6 +2902,15 @@ typedef struct GtalkClientEvent__storage_ {
.flags = GPBFieldOptional,
.dataType = GPBDataTypeUInt64,
},
+ {
+ .name = "mcsReconnectAction",
+ .dataTypeSpecific.enumDescFunc = GtalkClientEvent_McsReconnectAction_EnumDescriptor,
+ .number = GtalkClientEvent_FieldNumber_McsReconnectAction,
+ .hasIndex = 8,
+ .offset = (uint32_t)offsetof(GtalkClientEvent__storage_, mcsReconnectAction),
+ .flags = (GPBFieldFlags)(GPBFieldOptional | GPBFieldHasEnumDescriptor),
+ .dataType = GPBDataTypeEnum,
+ },
};
GPBDescriptor *localDescriptor =
[GPBDescriptor allocDescriptorForClass:[GtalkClientEvent class]
@@ -2909,12 +2935,17 @@ GPBEnumDescriptor *GtalkClientEvent_Type_EnumDescriptor(void) {
if (!descriptor) {
static const char *valueNames =
"Unknown\000DiscardedEvents\000FailedConnection"
- "\000SuccessfulConnection\000";
+ "\000SuccessfulConnection\000McsReconnectReques"
+ "t\000FailedSocketCreationMcsReconnect\000McsRe"
+ "connectLimited\000";
static const int32_t values[] = {
GtalkClientEvent_Type_Unknown,
GtalkClientEvent_Type_DiscardedEvents,
GtalkClientEvent_Type_FailedConnection,
GtalkClientEvent_Type_SuccessfulConnection,
+ GtalkClientEvent_Type_McsReconnectRequest,
+ GtalkClientEvent_Type_FailedSocketCreationMcsReconnect,
+ GtalkClientEvent_Type_McsReconnectLimited,
};
GPBEnumDescriptor *worker =
[GPBEnumDescriptor allocDescriptorForName:GPBNSStringifySymbol(GtalkClientEvent_Type)
@@ -2935,6 +2966,45 @@ BOOL GtalkClientEvent_Type_IsValidValue(int32_t value__) {
case GtalkClientEvent_Type_DiscardedEvents:
case GtalkClientEvent_Type_FailedConnection:
case GtalkClientEvent_Type_SuccessfulConnection:
+ case GtalkClientEvent_Type_McsReconnectRequest:
+ case GtalkClientEvent_Type_FailedSocketCreationMcsReconnect:
+ case GtalkClientEvent_Type_McsReconnectLimited:
+ return YES;
+ default:
+ return NO;
+ }
+}
+
+#pragma mark - Enum GtalkClientEvent_McsReconnectAction
+
+GPBEnumDescriptor *GtalkClientEvent_McsReconnectAction_EnumDescriptor(void) {
+ static GPBEnumDescriptor *descriptor = NULL;
+ if (!descriptor) {
+ static const char *valueNames =
+ "None\000NotConnected\000TooSoon\000";
+ static const int32_t values[] = {
+ GtalkClientEvent_McsReconnectAction_None,
+ GtalkClientEvent_McsReconnectAction_NotConnected,
+ GtalkClientEvent_McsReconnectAction_TooSoon,
+ };
+ GPBEnumDescriptor *worker =
+ [GPBEnumDescriptor allocDescriptorForName:GPBNSStringifySymbol(GtalkClientEvent_McsReconnectAction)
+ valueNames:valueNames
+ values:values
+ count:(uint32_t)(sizeof(values) / sizeof(int32_t))
+ enumVerifier:GtalkClientEvent_McsReconnectAction_IsValidValue];
+ if (!OSAtomicCompareAndSwapPtrBarrier(nil, worker, (void * volatile *)&descriptor)) {
+ [worker release];
+ }
+ }
+ return descriptor;
+}
+
+BOOL GtalkClientEvent_McsReconnectAction_IsValidValue(int32_t value__) {
+ switch (value__) {
+ case GtalkClientEvent_McsReconnectAction_None:
+ case GtalkClientEvent_McsReconnectAction_NotConnected:
+ case GtalkClientEvent_McsReconnectAction_TooSoon:
return YES;
default:
return NO;
diff --git a/Firebase/Messaging/Public/FIRMessaging.h b/Firebase/Messaging/Public/FIRMessaging.h
index 7cd7b19..3ad15da 100644
--- a/Firebase/Messaging/Public/FIRMessaging.h
+++ b/Firebase/Messaging/Public/FIRMessaging.h
@@ -16,6 +16,8 @@
#import <Foundation/Foundation.h>
+NS_ASSUME_NONNULL_BEGIN
+
/**
* @related FIRMessaging
*
@@ -23,9 +25,9 @@
* If the call fails we return the appropriate `error code`, described by
* `FIRMessagingError`.
*
- * @param FCMToken The valid registration token returned by FCM.
- * @param error The error describing why a token request failed. The error code
- * will match a value from the FIRMessagingError enumeration.
+ * @param FCMToken The valid registration token returned by FCM.
+ * @param error The error describing why a token request failed. The error code
+ * will match a value from the FIRMessagingError enumeration.
*/
typedef void(^FIRMessagingFCMTokenFetchCompletion)(NSString * _Nullable FCMToken,
NSError * _Nullable error)
@@ -46,6 +48,16 @@ typedef void(^FIRMessagingDeleteFCMTokenCompletion)(NSError * _Nullable error)
NS_SWIFT_NAME(MessagingDeleteFCMTokenCompletion);
/**
+ * Callback to invoke once the HTTP call to FIRMessaging backend for updating
+ * subscription finishes.
+ *
+ * @param error The error which occurred while updating the subscription topic
+ * on the FIRMessaging server. This will be nil in case the operation
+ * was successful, or if the operation was cancelled.
+ */
+typedef void (^FIRMessagingTopicOperationCompletion)(NSError *_Nullable error);
+
+/**
* The completion handler invoked once the data connection with FIRMessaging is
* established. The data connection is used to send a continous stream of
* data and all the FIRMessaging data notifications arrive through this connection.
@@ -68,7 +80,7 @@ typedef void(^FIRMessagingConnectCompletion)(NSError * __nullable error)
* successfully to the server. The notification object will be the messageID
* of the successfully delivered message.
*/
-FOUNDATION_EXPORT const NSNotificationName __nonnull FIRMessagingSendSuccessNotification
+FOUNDATION_EXPORT const NSNotificationName FIRMessagingSendSuccessNotification
NS_SWIFT_NAME(MessagingSendSuccess);
/**
@@ -77,7 +89,7 @@ FOUNDATION_EXPORT const NSNotificationName __nonnull FIRMessagingSendSuccessNoti
* message. The userInfo dictionary will contain the relevant error
* information for the failure.
*/
-FOUNDATION_EXPORT const NSNotificationName __nonnull FIRMessagingSendErrorNotification
+FOUNDATION_EXPORT const NSNotificationName FIRMessagingSendErrorNotification
NS_SWIFT_NAME(MessagingSendError);
/**
@@ -88,7 +100,7 @@ FOUNDATION_EXPORT const NSNotificationName __nonnull FIRMessagingSendErrorNotifi
* It is recommended to retrieve any missing messages directly from the
* server.
*/
-FOUNDATION_EXPORT const NSNotificationName __nonnull FIRMessagingMessagesDeletedNotification
+FOUNDATION_EXPORT const NSNotificationName FIRMessagingMessagesDeletedNotification
NS_SWIFT_NAME(MessagingMessagesDeleted);
/**
@@ -96,7 +108,7 @@ FOUNDATION_EXPORT const NSNotificationName __nonnull FIRMessagingMessagesDeleted
* an FCM socket connection. You can query the connection state in this
* notification by checking the `isDirectChannelEstablished` property of FIRMessaging.
*/
-FOUNDATION_EXPORT const NSNotificationName __nonnull FIRMessagingConnectionStateChangedNotification
+FOUNDATION_EXPORT const NSNotificationName FIRMessagingConnectionStateChangedNotification
NS_SWIFT_NAME(MessagingConnectionStateChanged);
/**
@@ -104,7 +116,7 @@ FOUNDATION_EXPORT const NSNotificationName __nonnull FIRMessagingConnectionState
* FIRMessaging delegate method `messaging:didReceiveRegistrationToken:` to receive current and
* updated tokens.
*/
-FOUNDATION_EXPORT const NSNotificationName __nonnull
+FOUNDATION_EXPORT const NSNotificationName
FIRMessagingRegistrationTokenRefreshedNotification
NS_SWIFT_NAME(MessagingRegistrationTokenRefreshed);
#else
@@ -113,7 +125,7 @@ FOUNDATION_EXPORT const NSNotificationName __nonnull
* successfully to the server. The notification object will be the messageID
* of the successfully delivered message.
*/
-FOUNDATION_EXPORT NSString * __nonnull const FIRMessagingSendSuccessNotification
+FOUNDATION_EXPORT NSString *const FIRMessagingSendSuccessNotification
NS_SWIFT_NAME(MessagingSendSuccessNotification);
/**
@@ -122,7 +134,7 @@ FOUNDATION_EXPORT NSString * __nonnull const FIRMessagingSendSuccessNotification
* message. The userInfo dictionary will contain the relevant error
* information for the failure.
*/
-FOUNDATION_EXPORT NSString * __nonnull const FIRMessagingSendErrorNotification
+FOUNDATION_EXPORT NSString *const FIRMessagingSendErrorNotification
NS_SWIFT_NAME(MessagingSendErrorNotification);
/**
@@ -133,7 +145,7 @@ FOUNDATION_EXPORT NSString * __nonnull const FIRMessagingSendErrorNotification
* It is recommended to retrieve any missing messages directly from the
* server.
*/
-FOUNDATION_EXPORT NSString * __nonnull const FIRMessagingMessagesDeletedNotification
+FOUNDATION_EXPORT NSString *const FIRMessagingMessagesDeletedNotification
NS_SWIFT_NAME(MessagingMessagesDeletedNotification);
/**
@@ -141,7 +153,7 @@ FOUNDATION_EXPORT NSString * __nonnull const FIRMessagingMessagesDeletedNotifica
* an FCM socket connection. You can query the connection state in this
* notification by checking the `isDirectChannelEstablished` property of FIRMessaging.
*/
-FOUNDATION_EXPORT NSString * __nonnull const FIRMessagingConnectionStateChangedNotification
+FOUNDATION_EXPORT NSString *const FIRMessagingConnectionStateChangedNotification
NS_SWIFT_NAME(MessagingConnectionStateChangedNotification);
/**
@@ -149,7 +161,7 @@ FOUNDATION_EXPORT NSString * __nonnull const FIRMessagingConnectionStateChangedN
* FIRMessaging delegate method `messaging:didReceiveRegistrationToken:` to receive current and
* updated tokens.
*/
-FOUNDATION_EXPORT NSString * __nonnull const FIRMessagingRegistrationTokenRefreshedNotification
+FOUNDATION_EXPORT NSString *const FIRMessagingRegistrationTokenRefreshedNotification
NS_SWIFT_NAME(MessagingRegistrationTokenRefreshedNotification);
#endif // defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
@@ -222,15 +234,13 @@ NS_SWIFT_NAME(MessagingRemoteMessage)
@interface FIRMessagingRemoteMessage : NSObject
/// The downstream message received by the application.
-@property(nonatomic, readonly, strong, nonnull) NSDictionary *appData;
+@property(nonatomic, readonly, strong) NSDictionary *appData;
@end
@class FIRMessaging;
/**
- * A protocol to handle events from FCM for devices running iOS 10 or above.
+ * A protocol to handle token update or data message delivery from FCM.
*
- * To support devices running iOS 9 or below, use the local and remote notifications handlers
- * defined in UIApplicationDelegate protocol.
*/
NS_SWIFT_NAME(MessagingDelegate)
@protocol FIRMessagingDelegate <NSObject>
@@ -243,32 +253,18 @@ NS_SWIFT_NAME(MessagingDelegate)
/// * Uploading the FCM token to your application server, so targeted notifications can be sent.
///
/// * Subscribing to any topics.
-- (void)messaging:(nonnull FIRMessaging *)messaging
- didReceiveRegistrationToken:(nonnull NSString *)fcmToken
+- (void)messaging:(FIRMessaging *)messaging
+ didReceiveRegistrationToken:(NSString *)fcmToken
NS_SWIFT_NAME(messaging(_:didReceiveRegistrationToken:));
-/// This method will be called whenever FCM receives a new, default FCM token for your
-/// Firebase project's Sender ID. This method is deprecated. Please use
-/// `messaging:didReceiveRegistrationToken:`.
-- (void)messaging:(nonnull FIRMessaging *)messaging
- didRefreshRegistrationToken:(nonnull NSString *)fcmToken
- NS_SWIFT_NAME(messaging(_:didRefreshRegistrationToken:))
- __deprecated_msg("Please use messaging:didReceiveRegistrationToken:, which is called for both \
- current and refreshed tokens.");
-
/// This method is called on iOS 10 devices to handle data messages received via FCM through its
/// direct channel (not via APNS). For iOS 9 and below, the FCM data message is delivered via the
/// UIApplicationDelegate's -application:didReceiveRemoteNotification: method.
-- (void)messaging:(nonnull FIRMessaging *)messaging
- didReceiveMessage:(nonnull FIRMessagingRemoteMessage *)remoteMessage
+- (void)messaging:(FIRMessaging *)messaging
+ didReceiveMessage:(FIRMessagingRemoteMessage *)remoteMessage
NS_SWIFT_NAME(messaging(_:didReceive:))
__IOS_AVAILABLE(10.0);
-/// The callback to handle data message received via FCM for devices running iOS 10 or above.
-- (void)applicationReceivedRemoteMessage:(nonnull FIRMessagingRemoteMessage *)remoteMessage
- NS_SWIFT_NAME(application(received:))
- __deprecated_msg("Use FIRMessagingDelegate’s -messaging:didReceiveMessage:");
-
@end
/**
@@ -289,37 +285,25 @@ NS_SWIFT_NAME(Messaging)
*/
@property(nonatomic, weak, nullable) id<FIRMessagingDelegate> delegate;
-
-/**
- * Delegate to handle remote data messages received via FCM for devices running iOS 10 or above.
- */
-@property(nonatomic, weak, nullable) id<FIRMessagingDelegate> remoteMessageDelegate
- __deprecated_msg("Use 'delegate' property");
-
/**
* When set to `YES`, Firebase Messaging will automatically establish a socket-based, direct
* channel to the FCM server. Enable this only if you are sending upstream messages or
* receiving non-APNS, data-only messages in foregrounded apps.
* Default is `NO`.
*/
-@property(nonatomic) BOOL shouldEstablishDirectChannel;
-
-/**
- * Returns `YES` if the direct channel to the FCM server is active, and `NO` otherwise.
- */
-@property(nonatomic, readonly) BOOL isDirectChannelEstablished;
+@property(nonatomic, assign, getter=isDirectChannelEstablished) BOOL shouldEstablishDirectChannel;
/**
* FIRMessaging
*
* @return An instance of FIRMessaging.
*/
-+ (nonnull instancetype)messaging NS_SWIFT_NAME(messaging());
++ (instancetype)messaging NS_SWIFT_NAME(messaging());
/**
* Unavailable. Use +messaging instead.
*/
-- (nonnull instancetype)init __attribute__((unavailable("Use +messaging instead.")));
+- (instancetype)init __attribute__((unavailable("Use +messaging instead.")));
#pragma mark - APNS
@@ -349,11 +333,30 @@ NS_SWIFT_NAME(Messaging)
* FIRMessagingAPNSTokenTypeUnknown to have the type automatically
* detected based on your provisioning profile.
*/
-- (void)setAPNSToken:(nonnull NSData *)apnsToken type:(FIRMessagingAPNSTokenType)type;
+- (void)setAPNSToken:(NSData *)apnsToken type:(FIRMessagingAPNSTokenType)type;
#pragma mark - FCM Tokens
/**
+ * Is Firebase Messaging token auto generation enabled? If this flag is disabled,
+ * Firebase Messaging will not generate token automatically for message delivery.
+ *
+ * If this flag is disabled, Firebase Messaging does not generate new tokens automatically for
+ * message delivery. If this flag is enabled, FCM generates a registration token on application
+ * start when there is no existing valid token. FCM also generates a new token when an existing
+ * token is deleted.
+ *
+ * This setting is persisted, and is applied on future
+ * invocations of your application. Once explicitly set, it overrides any
+ * settings in your Info.plist.
+ *
+ * By default, FCM automatic initialization is enabled. If you need to change the
+ * default (for example, because you want to prompt the user before getting token)
+ * set FirebaseMessagingAutoInitEnabled to false in your application's Info.plist.
+ */
+@property(nonatomic, assign, getter=isAutoInitEnabled) BOOL autoInitEnabled;
+
+/**
* The FCM token is used to identify this device so that FCM can send notifications to it.
* It is associated with your APNS token when the APNS token is supplied, so that sending
* messages to the FCM token will be delivered over APNS.
@@ -385,8 +388,8 @@ NS_SWIFT_NAME(Messaging)
* @param senderID The Sender ID for a particular Firebase project.
* @param completion The completion handler to handle the token request.
*/
-- (void)retrieveFCMTokenForSenderID:(nonnull NSString *)senderID
- completion:(nonnull FIRMessagingFCMTokenFetchCompletion)completion
+- (void)retrieveFCMTokenForSenderID:(NSString *)senderID
+ completion:(FIRMessagingFCMTokenFetchCompletion)completion
NS_SWIFT_NAME(retrieveFCMToken(forSenderID:completion:));
@@ -397,8 +400,8 @@ NS_SWIFT_NAME(Messaging)
* @param senderID The senderID for a particular Firebase project.
* @param completion The completion handler to handle the token deletion.
*/
-- (void)deleteFCMTokenForSenderID:(nonnull NSString *)senderID
- completion:(nonnull FIRMessagingDeleteFCMTokenCompletion)completion
+- (void)deleteFCMTokenForSenderID:(NSString *)senderID
+ completion:(FIRMessagingDeleteFCMTokenCompletion)completion
NS_SWIFT_NAME(deleteFCMToken(forSenderID:completion:));
@@ -416,7 +419,7 @@ NS_SWIFT_NAME(Messaging)
* the same time, FIRMessaging performs exponential backoff to retry
* establishing a connection and invoke the handler when successful.
*/
-- (void)connectWithCompletion:(nonnull FIRMessagingConnectCompletion)handler
+- (void)connectWithCompletion:(FIRMessagingConnectCompletion)handler
NS_SWIFT_NAME(connect(handler:))
__deprecated_msg("Please use the shouldEstablishDirectChannel property instead.");
@@ -438,14 +441,36 @@ NS_SWIFT_NAME(Messaging)
*
* @param topic The name of the topic, for example, @"sports".
*/
-- (void)subscribeToTopic:(nonnull NSString *)topic NS_SWIFT_NAME(subscribe(toTopic:));
+- (void)subscribeToTopic:(NSString *)topic NS_SWIFT_NAME(subscribe(toTopic:));
+
+/**
+ * Asynchronously subscribe to the provided topic, retrying on failure.
+ *
+ * @param topic The topic name to subscribe to, for example, @"sports".
+ * @param completion The completion that is invoked once the subscribe call ends.
+ * In case of success, nil error is returned. Otherwise, an
+ * appropriate error object is returned.
+ */
+- (void)subscribeToTopic:(nonnull NSString *)topic
+ completion:(nullable FIRMessagingTopicOperationCompletion)completion;
/**
* Asynchronously unsubscribe from a topic.
*
* @param topic The name of the topic, for example @"sports".
*/
-- (void)unsubscribeFromTopic:(nonnull NSString *)topic NS_SWIFT_NAME(unsubscribe(fromTopic:));
+- (void)unsubscribeFromTopic:(NSString *)topic NS_SWIFT_NAME(unsubscribe(fromTopic:));
+
+/**
+ * Asynchronously unsubscribe from the provided topic, retrying on failure.
+ *
+ * @param topic The topic name to unsubscribe from, for example @"sports".
+ * @param completion The completion that is invoked once the unsubscribe call ends.
+ * In case of success, nil error is returned. Otherwise, an
+ * appropriate error object is returned.
+ */
+- (void)unsubscribeFromTopic:(nonnull NSString *)topic
+ completion:(nullable FIRMessagingTopicOperationCompletion)completion;
#pragma mark - Upstream
@@ -472,9 +497,9 @@ NS_SWIFT_NAME(Messaging)
* if the message has been dropped because of TTL; this can happen
* on the server side, and it would require extra communication.
*/
-- (void)sendMessage:(nonnull NSDictionary *)message
- to:(nonnull NSString *)receiver
- withMessageID:(nonnull NSString *)messageID
+- (void)sendMessage:(NSDictionary *)message
+ to:(NSString *)receiver
+ withMessageID:(NSString *)messageID
timeToLive:(int64_t)ttl;
#pragma mark - Analytics
@@ -490,6 +515,8 @@ NS_SWIFT_NAME(Messaging)
*
* @return Information about the downstream message.
*/
-- (nonnull FIRMessagingMessageInfo *)appDidReceiveMessage:(nonnull NSDictionary *)message;
+- (FIRMessagingMessageInfo *)appDidReceiveMessage:(NSDictionary *)message;
@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firebase/Storage/CHANGELOG.md b/Firebase/Storage/CHANGELOG.md
index 00fc3b7..135de40 100644
--- a/Firebase/Storage/CHANGELOG.md
+++ b/Firebase/Storage/CHANGELOG.md
@@ -1,3 +1,19 @@
+# v3.0.0
+- [removed] Removed `downloadURLs` property on `StorageMetadata`. Use `StorageReference.downloadURL(completion:)` to obtain a current download URL.
+- [changed] The `maxOperationRetryTime` timeout now applies to calls to `StorageReference.getMetadata(completion:)` and `StorageReference.updateMetadata(completion:)`. These calls previously used the `maxDownloadRetryTime` and `maxUploadRetryTime` timeouts.
+
+# v2.2.0
+- [changed] Deprecated `downloadURLs` property on `StorageMetadata`. Use `StorageReference.downloadURL(completion:)` to obtain a current download URL.
+
+# v2.1.3
+- [changed] Addresses CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF warnings that surface in newer versions of Xcode and CocoaPods.
+
+# v2.1.2
+- [added] Firebase Storage is now community-supported on tvOS.
+
+# v2.1.1
+- [changed] Internal cleanup in the firebase-ios-sdk repository. Functionality of the Storage SDK is not affected.
+
# v2.1.0
- [added] Added 'md5Hash' to FIRStorageMetadata.
diff --git a/Firebase/Storage/FIRStorageDeleteTask.m b/Firebase/Storage/FIRStorageDeleteTask.m
index 738d8a5..b41f06e 100644
--- a/Firebase/Storage/FIRStorageDeleteTask.m
+++ b/Firebase/Storage/FIRStorageDeleteTask.m
@@ -60,7 +60,7 @@
if (callback) {
callback(self.error);
}
- _fetcherCompletion = nil;
+ self->_fetcherCompletion = nil;
};
#pragma clang diangostic pop
diff --git a/Firebase/Storage/FIRStorageDownloadTask.m b/Firebase/Storage/FIRStorageDownloadTask.m
index c410f05..91da4b7 100644
--- a/Firebase/Storage/FIRStorageDownloadTask.m
+++ b/Firebase/Storage/FIRStorageDownloadTask.m
@@ -116,7 +116,7 @@
self.error = [FIRStorageErrors errorWithServerError:error reference:self.reference];
[self fireHandlersForStatus:FIRStorageTaskStatusFailure snapshot:self.snapshot];
[self removeAllObservers];
- _fetcherCompletion = nil;
+ self->_fetcherCompletion = nil;
return;
}
@@ -124,12 +124,12 @@
self.state = FIRStorageTaskStateSuccess;
if (data) {
- _downloadData = data;
+ self->_downloadData = data;
}
[self fireHandlersForStatus:FIRStorageTaskStatusSuccess snapshot:self.snapshot];
[self removeAllObservers];
- _fetcherCompletion = nil;
+ self->_fetcherCompletion = nil;
};
#pragma clang diagnostic pop
diff --git a/Firebase/Storage/FIRStorageErrors.m b/Firebase/Storage/FIRStorageErrors.m
index 92c0536..651bfd1 100644
--- a/Firebase/Storage/FIRStorageErrors.m
+++ b/Firebase/Storage/FIRStorageErrors.m
@@ -63,7 +63,7 @@
NSString *totalString = total ? @(total).stringValue : @"unknown";
NSString *sizeString = total ? @(size).stringValue : @"unknown";
NSString *const kSizeExceededErrorFormat =
- @"Attempeted to download object with size of %@ bytes, "
+ @"Attempted to download object with size of %@ bytes, "
@"which exceeds the maximum size of %@ bytes. "
@"Consider raising the maximum download size, or using "
@"[FIRStorageReference writeToFile:]";
@@ -104,7 +104,7 @@
break;
case FIRStorageErrorCodeUnknown:
- /* Fall through to default case for unknown errors */
+ /* Fall through to default case for unknown errors */
default:
errorMessage = @"An unknown error occurred, please check the server response.";
@@ -170,4 +170,21 @@
return clientError;
}
++ (NSError *)errorWithInvalidRequest:(NSData *)request {
+ NSString *requestString = [[NSString alloc] initWithData:request encoding:NSUTF8StringEncoding];
+ NSString *invalidDataString =
+ [NSString stringWithFormat:kFIRStorageInvalidDataFormat, requestString];
+ NSDictionary *dict;
+ if (invalidDataString.length > 0) {
+ dict = @{NSLocalizedFailureReasonErrorKey : invalidDataString};
+ }
+ return [FIRStorageErrors errorWithCode:FIRStorageErrorCodeUnknown infoDictionary:dict];
+}
+
++ (NSError *)errorWithCustomMessage:(NSString *)errorMessage {
+ return [NSError errorWithDomain:FIRStorageErrorDomain
+ code:FIRStorageErrorCodeUnknown
+ userInfo:@{NSLocalizedDescriptionKey : errorMessage}];
+}
+
@end
diff --git a/Firebase/Storage/FIRStorageGetDownloadURLTask.m b/Firebase/Storage/FIRStorageGetDownloadURLTask.m
new file mode 100644
index 0000000..02d202e
--- /dev/null
+++ b/Firebase/Storage/FIRStorageGetDownloadURLTask.m
@@ -0,0 +1,117 @@
+// Copyright 2018 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 "FIRStorageGetDownloadURLTask.h"
+
+#import "FIRStorageTask_Private.h"
+
+@implementation FIRStorageGetDownloadURLTask {
+ @private
+ FIRStorageVoidURLError _completion;
+}
+
+@synthesize fetcher = _fetcher;
+@synthesize fetcherCompletion = _fetcherCompletion;
+
+- (instancetype)initWithReference:(FIRStorageReference *)reference
+ fetcherService:(GTMSessionFetcherService *)service
+ completion:(FIRStorageVoidURLError)completion {
+ self = [super initWithReference:reference fetcherService:service];
+ if (self) {
+ _completion = [completion copy];
+ }
+ return self;
+}
+
+- (void)dealloc {
+ [_fetcher stopFetching];
+}
+
++ (NSURL *)downloadURLFromMetadataDictionary:(NSDictionary *)dictionary {
+ NSString *downloadTokens = dictionary[kFIRStorageMetadataDownloadTokens];
+
+ if (downloadTokens && downloadTokens.length > 0) {
+ NSArray<NSString *> *downloadTokenArray = [downloadTokens componentsSeparatedByString:@","];
+ NSString *bucket = dictionary[kFIRStorageMetadataBucket];
+ NSString *path = dictionary[kFIRStorageMetadataName];
+ NSString *fullPath = [NSString stringWithFormat:kFIRStorageFullPathFormat, bucket,
+ [FIRStorageUtils GCSEscapedString:path]];
+
+ NSURLComponents *components = [[NSURLComponents alloc] init];
+ components.scheme = kFIRStorageScheme;
+ components.host = kFIRStorageHost;
+ components.percentEncodedPath = fullPath;
+
+ // The backend can return an arbitrary number of download tokens, but we only expose the first
+ // token via the download URL.
+ NSURLQueryItem *altItem = [[NSURLQueryItem alloc] initWithName:@"alt" value:@"media"];
+ NSURLQueryItem *tokenItem =
+ [[NSURLQueryItem alloc] initWithName:@"token" value:downloadTokenArray[0]];
+ components.queryItems = @[ altItem, tokenItem ];
+
+ return [components URL];
+ }
+
+ return nil;
+}
+
+- (void)enqueue {
+ NSMutableURLRequest *request = [self.baseRequest mutableCopy];
+ request.HTTPMethod = @"GET";
+ request.timeoutInterval = self.reference.storage.maxOperationRetryTime;
+
+ FIRStorageVoidURLError callback = _completion;
+ _completion = nil;
+
+ GTMSessionFetcher *fetcher = [self.fetcherService fetcherWithRequest:request];
+ _fetcher = fetcher;
+ fetcher.comment = @"GetDownloadURLTask";
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Warc-retain-cycles"
+ _fetcherCompletion = ^(NSData *data, NSError *error) {
+ NSURL *downloadURL;
+ if (error) {
+ if (!self.error) {
+ self.error = [FIRStorageErrors errorWithServerError:error reference:self.reference];
+ }
+ } else {
+ NSDictionary *responseDictionary = [NSDictionary frs_dictionaryFromJSONData:data];
+ if (responseDictionary != nil) {
+ downloadURL =
+ [FIRStorageGetDownloadURLTask downloadURLFromMetadataDictionary:responseDictionary];
+ if (!downloadURL) {
+ self.error =
+ [FIRStorageErrors errorWithCustomMessage:@"Failed to retrieve a download URL."];
+ }
+ } else {
+ self.error = [FIRStorageErrors errorWithInvalidRequest:data];
+ }
+ }
+
+ if (callback) {
+ callback(downloadURL, self.error);
+ }
+
+ self->_fetcherCompletion = nil;
+ };
+#pragma clang diagnostic pop
+
+ __weak FIRStorageGetDownloadURLTask *weakSelf = self;
+ [fetcher beginFetchWithCompletionHandler:^(NSData *data, NSError *error) {
+ weakSelf.fetcherCompletion(data, error);
+ }];
+}
+
+@end
diff --git a/Firebase/Storage/FIRStorageGetMetadataTask.m b/Firebase/Storage/FIRStorageGetMetadataTask.m
index 78d8a16..752c410 100644
--- a/Firebase/Storage/FIRStorageGetMetadataTask.m
+++ b/Firebase/Storage/FIRStorageGetMetadataTask.m
@@ -46,7 +46,7 @@
- (void)enqueue {
NSMutableURLRequest *request = [self.baseRequest mutableCopy];
request.HTTPMethod = @"GET";
- request.timeoutInterval = self.reference.storage.maxDownloadRetryTime;
+ request.timeoutInterval = self.reference.storage.maxOperationRetryTime;
FIRStorageVoidMetadataError callback = _completion;
_completion = nil;
@@ -58,39 +58,25 @@
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-retain-cycles"
_fetcherCompletion = ^(NSData *data, NSError *error) {
+ FIRStorageMetadata *metadata;
if (error) {
if (!self.error) {
self.error = [FIRStorageErrors errorWithServerError:error reference:self.reference];
}
- if (callback) {
- callback(nil, self.error);
+ } else {
+ NSDictionary *responseDictionary = [NSDictionary frs_dictionaryFromJSONData:data];
+ if (responseDictionary != nil) {
+ metadata = [[FIRStorageMetadata alloc] initWithDictionary:responseDictionary];
+ [metadata setType:FIRStorageMetadataTypeFile];
+ } else {
+ self.error = [FIRStorageErrors errorWithInvalidRequest:data];
}
- _fetcherCompletion = nil;
- return;
}
- NSDictionary *responseDictionary = [NSDictionary frs_dictionaryFromJSONData:data];
- if (responseDictionary != nil) {
- FIRStorageMetadata *metadata =
- [[FIRStorageMetadata alloc] initWithDictionary:responseDictionary];
- [metadata setType:FIRStorageMetadataTypeFile];
- if (callback) {
- callback(metadata, nil);
- }
- } else {
- NSString *returnedData = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
- NSString *invalidDataString =
- [NSString stringWithFormat:kFIRStorageInvalidDataFormat, returnedData];
- NSDictionary *dict;
- if (invalidDataString.length > 0) {
- dict = @{NSLocalizedFailureReasonErrorKey : invalidDataString};
- }
- self.error = [FIRStorageErrors errorWithCode:FIRStorageErrorCodeUnknown infoDictionary:dict];
- if (callback) {
- callback(nil, self.error);
- }
+ if (callback) {
+ callback(metadata, self.error);
}
- _fetcherCompletion = nil;
+ self->_fetcherCompletion = nil;
};
#pragma clang diagnostic pop
diff --git a/Firebase/Storage/FIRStorageMetadata.m b/Firebase/Storage/FIRStorageMetadata.m
index 34ac86c..764907c 100644
--- a/Firebase/Storage/FIRStorageMetadata.m
+++ b/Firebase/Storage/FIRStorageMetadata.m
@@ -41,7 +41,6 @@
_contentType = dictionary[kFIRStorageMetadataContentType];
_customMetadata = dictionary[kFIRStorageMetadataCustomMetadata];
_size = [dictionary[kFIRStorageMetadataSize] longLongValue];
- _downloadURLs = dictionary[kFIRStorageMetadataDownloadURLs];
_generation = [dictionary[kFIRStorageMetadataGeneration] longLongValue];
_metageneration = [dictionary[kFIRStorageMetadataMetageneration] longLongValue];
_timeCreated = [self dateFromRFC3339String:dictionary[kFIRStorageMetadataTimeCreated]];
@@ -50,25 +49,6 @@
// GCS "name" is our path, our "name" is just the last path component of the path
_path = dictionary[kFIRStorageMetadataName];
_name = [_path lastPathComponent];
- NSString *downloadTokens = dictionary[kFIRStorageMetadataDownloadTokens];
- if (downloadTokens) {
- NSArray<NSString *> *downloadStringArray = [downloadTokens componentsSeparatedByString:@","];
- NSMutableArray<NSURL *> *downloadURLArray =
- [[NSMutableArray alloc] initWithCapacity:[downloadStringArray count]];
- [downloadStringArray enumerateObjectsUsingBlock:^(NSString *_Nonnull token, NSUInteger idx,
- BOOL *_Nonnull stop) {
- NSURLComponents *components = [[NSURLComponents alloc] init];
- components.scheme = kFIRStorageScheme;
- components.host = kFIRStorageHost;
- NSString *path = [FIRStorageUtils GCSEscapedString:_path];
- NSString *fullPath = [NSString stringWithFormat:kFIRStorageFullPathFormat, _bucket, path];
- components.percentEncodedPath = fullPath;
- components.query = [NSString stringWithFormat:@"alt=media&token=%@", token];
-
- [downloadURLArray insertObject:[components URL] atIndex:idx];
- }];
- _downloadURLs = downloadURLArray;
- }
}
return self;
}
@@ -146,29 +126,6 @@
metadataDictionary[kFIRStorageMetadataCustomMetadata] = _customMetadata;
}
- if (_downloadURLs) {
- NSMutableArray *downloadTokens = [[NSMutableArray alloc] init];
- [_downloadURLs
- enumerateObjectsUsingBlock:^(NSURL *_Nonnull URL, NSUInteger idx, BOOL *_Nonnull stop) {
- NSArray *queryItems = [URL.query componentsSeparatedByString:@"&"];
- [queryItems enumerateObjectsUsingBlock:^(NSString *queryString, NSUInteger idx,
- BOOL *_Nonnull stop) {
- NSString *key;
- NSString *value;
- NSScanner *scanner = [NSScanner scannerWithString:queryString];
- [scanner scanUpToString:@"=" intoString:&key];
- [scanner scanString:@"=" intoString:NULL];
- [scanner scanUpToString:@"\n" intoString:&value];
- if ([key isEqual:@"token"]) {
- [downloadTokens addObject:value];
- *stop = YES;
- }
- }];
- }];
- NSString *downloadTokenString = [downloadTokens componentsJoinedByString:@","];
- metadataDictionary[kFIRStorageMetadataDownloadTokens] = downloadTokenString;
- }
-
if (_generation) {
NSString *generationString = [NSString stringWithFormat:@"%lld", _generation];
metadataDictionary[kFIRStorageMetadataGeneration] = generationString;
@@ -206,10 +163,6 @@
return _type == FIRStorageMetadataTypeFolder;
}
-- (nullable NSURL *)downloadURL {
- return [_downloadURLs firstObject];
-}
-
#pragma mark - Private methods
+ (void)removeMatchingMetadata:(NSMutableDictionary *)metadata
diff --git a/Firebase/Storage/FIRStorageObservableTask.m b/Firebase/Storage/FIRStorageObservableTask.m
index 58455f6..af82fa2 100644
--- a/Firebase/Storage/FIRStorageObservableTask.m
+++ b/Firebase/Storage/FIRStorageObservableTask.m
@@ -102,7 +102,7 @@
break;
case FIRStorageTaskStatusUnknown:
- // Fall through to exception case if an unknown status is passed
+ // Fall through to exception case if an unknown status is passed
default:
[NSException raise:NSInternalInconsistencyException
@@ -206,7 +206,6 @@
[handlersCopy
enumerateKeysAndObjectsUsingBlock:^(
NSString *_Nonnull key, FIRStorageVoidSnapshot _Nonnull handler, BOOL *_Nonnull stop) {
-
dispatch_async(callbackQueue, ^{
handler(snapshot);
});
diff --git a/Firebase/Storage/FIRStorageReference.m b/Firebase/Storage/FIRStorageReference.m
index 7bc1934..5b70a9c 100644
--- a/Firebase/Storage/FIRStorageReference.m
+++ b/Firebase/Storage/FIRStorageReference.m
@@ -17,6 +17,7 @@
#import "FIRStorageConstants_Private.h"
#import "FIRStorageDeleteTask.h"
#import "FIRStorageDownloadTask_Private.h"
+#import "FIRStorageGetDownloadURLTask.h"
#import "FIRStorageGetMetadataTask.h"
#import "FIRStorageMetadata_Private.h"
#import "FIRStorageReference_Private.h"
@@ -319,16 +320,11 @@
}
- (void)downloadURLWithCompletion:(FIRStorageVoidURLError)completion {
- dispatch_queue_t callbackQueue = _storage.fetcherServiceForApp.callbackQueue;
- if (!callbackQueue) {
- callbackQueue = dispatch_get_main_queue();
- }
-
- return [self metadataWithCompletion:^(FIRStorageMetadata *metadata, NSError *error) {
- dispatch_async(callbackQueue, ^{
- completion(metadata.downloadURL, error);
- });
- }];
+ FIRStorageGetDownloadURLTask *task =
+ [[FIRStorageGetDownloadURLTask alloc] initWithReference:self
+ fetcherService:_storage.fetcherServiceForApp
+ completion:completion];
+ [task enqueue];
}
#pragma mark - Metadata Operations
diff --git a/Firebase/Storage/FIRStorageUpdateMetadataTask.m b/Firebase/Storage/FIRStorageUpdateMetadataTask.m
index cf1bf93..cea4e7d 100644
--- a/Firebase/Storage/FIRStorageUpdateMetadataTask.m
+++ b/Firebase/Storage/FIRStorageUpdateMetadataTask.m
@@ -48,7 +48,7 @@
NSDictionary *updateDictionary = [_updateMetadata updatedMetadata];
NSData *updateData = [NSData frs_dataFromJSONDictionary:updateDictionary];
request.HTTPMethod = @"PATCH";
- request.timeoutInterval = self.reference.storage.maxUploadRetryTime;
+ request.timeoutInterval = self.reference.storage.maxOperationRetryTime;
request.HTTPBody = updateData;
NSString *typeString = @"application/json; charset=UTF-8";
[request setValue:typeString forHTTPHeaderField:@"Content-Type"];
@@ -64,39 +64,25 @@
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-retain-cycles"
_fetcherCompletion = ^(NSData *data, NSError *error) {
+ FIRStorageMetadata *metadata;
if (error) {
if (!self.error) {
self.error = [FIRStorageErrors errorWithServerError:error reference:self.reference];
}
- if (callback) {
- callback(nil, self.error);
+ } else {
+ NSDictionary *responseDictionary = [NSDictionary frs_dictionaryFromJSONData:data];
+ if (responseDictionary) {
+ metadata = [[FIRStorageMetadata alloc] initWithDictionary:responseDictionary];
+ [metadata setType:FIRStorageMetadataTypeFile];
+ } else {
+ self.error = [FIRStorageErrors errorWithInvalidRequest:data];
}
- _fetcherCompletion = nil;
- return;
}
- NSDictionary *responseDictionary = [NSDictionary frs_dictionaryFromJSONData:data];
- if (responseDictionary) {
- FIRStorageMetadata *metadata =
- [[FIRStorageMetadata alloc] initWithDictionary:responseDictionary];
- [metadata setType:FIRStorageMetadataTypeFile];
- if (callback) {
- callback(metadata, nil);
- }
- } else {
- NSString *returnedData = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
- NSString *invalidDataString =
- [NSString stringWithFormat:kFIRStorageInvalidDataFormat, returnedData];
- NSDictionary *dict;
- if (invalidDataString.length > 0) {
- dict = @{NSLocalizedFailureReasonErrorKey : invalidDataString};
- }
- self.error = [FIRStorageErrors errorWithCode:FIRStorageErrorCodeUnknown infoDictionary:dict];
- if (callback) {
- callback(nil, self.error);
- }
+ if (callback) {
+ callback(metadata, self.error);
}
- _fetcherCompletion = nil;
+ self->_fetcherCompletion = nil;
};
#pragma clang diagnostic pop
diff --git a/Firebase/Storage/FIRStorageUploadTask.m b/Firebase/Storage/FIRStorageUploadTask.m
index 0df0bf4..2c4daa9 100644
--- a/Firebase/Storage/FIRStorageUploadTask.m
+++ b/Firebase/Storage/FIRStorageUploadTask.m
@@ -117,7 +117,7 @@
weakSelf.state = FIRStorageTaskStateProgress;
weakSelf.progress.completedUnitCount = totalBytesSent;
weakSelf.progress.totalUnitCount = totalBytesExpectedToSend;
- weakSelf.metadata = _uploadMetadata;
+ weakSelf.metadata = self->_uploadMetadata;
[weakSelf fireHandlersForStatus:FIRStorageTaskStatusProgress snapshot:weakSelf.snapshot];
weakSelf.state = FIRStorageTaskStateRunning;
}];
@@ -137,10 +137,10 @@
if (error) {
self.state = FIRStorageTaskStateFailed;
self.error = [FIRStorageErrors errorWithServerError:error reference:self.reference];
- self.metadata = _uploadMetadata;
+ self.metadata = self->_uploadMetadata;
[self fireHandlersForStatus:FIRStorageTaskStatusFailure snapshot:self.snapshot];
[self removeAllObservers];
- _fetcherCompletion = nil;
+ self->_fetcherCompletion = nil;
return;
}
@@ -154,19 +154,12 @@
[metadata setType:FIRStorageMetadataTypeFile];
self.metadata = metadata;
} else {
- NSString *returnedData = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
- NSString *invalidDataString =
- [NSString stringWithFormat:kFIRStorageInvalidDataFormat, returnedData];
- NSDictionary *dict;
- if (invalidDataString.length > 0) {
- dict = @{NSLocalizedFailureReasonErrorKey : invalidDataString};
- }
- self.error = [FIRStorageErrors errorWithCode:FIRStorageErrorCodeUnknown infoDictionary:dict];
+ self.error = [FIRStorageErrors errorWithInvalidRequest:data];
}
[self fireHandlersForStatus:FIRStorageTaskStatusSuccess snapshot:self.snapshot];
[self removeAllObservers];
- _fetcherCompletion = nil;
+ self->_fetcherCompletion = nil;
};
#pragma clang diagnostic pop
diff --git a/Firebase/Storage/FIRStorageUtils.m b/Firebase/Storage/FIRStorageUtils.m
index 216b4b6..bc517ff 100644
--- a/Firebase/Storage/FIRStorageUtils.m
+++ b/Firebase/Storage/FIRStorageUtils.m
@@ -14,7 +14,7 @@
#import <Foundation/Foundation.h>
-#if TARGET_OS_IOS
+#if TARGET_OS_IOS || TARGET_OS_TV
#import <MobileCoreServices/MobileCoreServices.h>
#elif TARGET_OS_OSX
#import <CoreServices/CoreServices.h>
diff --git a/Firebase/Storage/Private/FIRStorageConstants_Private.h b/Firebase/Storage/Private/FIRStorageConstants_Private.h
index 498c687..cf12337 100644
--- a/Firebase/Storage/Private/FIRStorageConstants_Private.h
+++ b/Firebase/Storage/Private/FIRStorageConstants_Private.h
@@ -55,7 +55,6 @@ FOUNDATION_EXPORT NSString *const kFIRStorageMetadataContentLanguage;
FOUNDATION_EXPORT NSString *const kFIRStorageMetadataContentType;
FOUNDATION_EXPORT NSString *const kFIRStorageMetadataCustomMetadata;
FOUNDATION_EXPORT NSString *const kFIRStorageMetadataSize;
-FOUNDATION_EXPORT NSString *const kFIRStorageMetadataDownloadURLs;
FOUNDATION_EXPORT NSString *const kFIRStorageMetadataGeneration;
FOUNDATION_EXPORT NSString *const kFIRStorageMetadataMetageneration;
FOUNDATION_EXPORT NSString *const kFIRStorageMetadataTimeCreated;
diff --git a/Firebase/Storage/Private/FIRStorageErrors.h b/Firebase/Storage/Private/FIRStorageErrors.h
index 7c236d9..a76a6aa 100644
--- a/Firebase/Storage/Private/FIRStorageErrors.h
+++ b/Firebase/Storage/Private/FIRStorageErrors.h
@@ -44,11 +44,27 @@ NS_ASSUME_NONNULL_BEGIN
* Creates a Firebase Storage error from a specific GCS error and FIRStorageReference.
* @param error Server error to wrap and return as a Firebase Storage error.
* @param reference FIRStorageReference which provides context about the request being made.
- * @return Returns an Firebase Storage error, or nil if no error is provided.
+ * @return Returns a Firebase Storage error, or nil if no error is provided.
*/
+ (nullable NSError *)errorWithServerError:(nullable NSError *)error
reference:(nullable FIRStorageReference *)reference;
+/**
+ * Creates a Firebase Storage error from an invalid request.
+ *
+ * @param request The NSData representation of the invalid user request.
+ * @return Returns the corresponding Firebase Storage error.
+ */
++ (NSError *)errorWithInvalidRequest:(NSData *)request;
+
+/**
+ * Creates a Firebase Storage error with a custom error message.
+ *
+ * @param errorMessage A custom error message.
+ * @return Returns the corresponding Firebase Storage error.
+ */
++ (NSError *)errorWithCustomMessage:(NSString *)errorMessage;
+
@end
NS_ASSUME_NONNULL_END
diff --git a/Firestore/core/src/firebase/firestore/base/port.h b/Firebase/Storage/Private/FIRStorageGetDownloadURLTask.h
index 37d1041..8cd9eb3 100644
--- a/Firestore/core/src/firebase/firestore/base/port.h
+++ b/Firebase/Storage/Private/FIRStorageGetDownloadURLTask.h
@@ -1,5 +1,5 @@
/*
- * Copyright 2017 Google
+ * Copyright 2018 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,20 +14,21 @@
* limitations under the License.
*/
-#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_BASE_PORT_H_
-#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_BASE_PORT_H_
+#import "FIRStorageTask.h"
-#if defined(__APPLE__)
-// On Apple platforms we support building via Cocoapods without CMake. When
-// building this way we can't test the presence of features so predefine all
-// the platform-support feature macros to their expected values.
+@class GTMSessionFetcherService;
-// All supported Apple platforms have arc4random(3).
-#define HAVE_ARC4RANDOM 1
+NS_ASSUME_NONNULL_BEGIN
-#else
+/**
+ * Task which provides the ability to get a download URL for an object in Firebase Storage.
+ */
+@interface FIRStorageGetDownloadURLTask : FIRStorageTask <FIRStorageTaskManagement>
+
+- (instancetype)initWithReference:(FIRStorageReference *)reference
+ fetcherService:(GTMSessionFetcherService *)service
+ completion:(FIRStorageVoidURLError)completion;
-#error "Unknown platform."
-#endif // defined(__APPLE__)
+@end
-#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_BASE_PORT_H_
+NS_ASSUME_NONNULL_END
diff --git a/Firebase/Storage/Private/FIRStorageGetDownloadURLTask_Private.h b/Firebase/Storage/Private/FIRStorageGetDownloadURLTask_Private.h
new file mode 100644
index 0000000..0da04a4
--- /dev/null
+++ b/Firebase/Storage/Private/FIRStorageGetDownloadURLTask_Private.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2018 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 "FIRStorageGetDownloadURLTask.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ * Task which provides the ability to get a download URL for an object in Firebase Storage.
+ */
+@interface FIRStorageGetDownloadURLTask ()
+
+/** Extracts a download URL from the StorageMetadata dictonary representation. */
++ (nullable NSURL *)downloadURLFromMetadataDictionary:(NSDictionary *)dictionary;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firebase/Storage/Public/FIRStorage.h b/Firebase/Storage/Public/FIRStorage.h
index f70b875..baa8122 100644
--- a/Firebase/Storage/Public/FIRStorage.h
+++ b/Firebase/Storage/Public/FIRStorage.h
@@ -24,7 +24,8 @@
NS_ASSUME_NONNULL_BEGIN
/** Project version string for FirebaseStorage. */
-FOUNDATION_EXPORT const unsigned char *const FIRStorageVersionString;
+FOUNDATION_EXPORT const unsigned char *const FIRStorageVersionString
+ NS_SWIFT_NAME(StorageVersionString);
/**
* FirebaseStorage is a service that supports uploading and downloading binary objects,
diff --git a/Firebase/Storage/Public/FIRStorageMetadata.h b/Firebase/Storage/Public/FIRStorageMetadata.h
index 51f3547..63b8798 100644
--- a/Firebase/Storage/Public/FIRStorageMetadata.h
+++ b/Firebase/Storage/Public/FIRStorageMetadata.h
@@ -113,11 +113,6 @@ NS_SWIFT_NAME(StorageMetadata)
@property(strong, nonatomic, readonly, nullable) FIRStorageReference *storageReference;
/**
- * An array containing all download URLs available for the object.
- */
-@property(strong, nonatomic, readonly, nullable) NSArray<NSURL *> *downloadURLs;
-
-/**
* Creates an instanece of FIRStorageMetadata from the contents of a dictionary.
* @return An instance of FIRStorageMetadata that represents the contents of a dictionary.
*/
@@ -140,13 +135,6 @@ NS_SWIFT_NAME(StorageMetadata)
*/
@property(readonly, getter=isFolder) BOOL folder;
-/**
- * Retrieves a download URL for the given object, or nil if none exist.
- * Note that if there are many valid download tokens, this will always return the first
- * valid token created.
- */
-- (nullable NSURL *)downloadURL;
-
@end
NS_ASSUME_NONNULL_END
diff --git a/FirebaseAuth.podspec b/FirebaseAuth.podspec
index 3ee4832..8c4c32d 100644
--- a/FirebaseAuth.podspec
+++ b/FirebaseAuth.podspec
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'FirebaseAuth'
- s.version = '4.4.0'
+ s.version = '4.6.1'
s.summary = 'The official iOS client for Firebase Authentication'
s.description = <<-DESC
@@ -19,8 +19,9 @@ supports email and password accounts, as well as several 3rd party authenticatio
s.social_media_url = 'https://twitter.com/Firebase'
s.ios.deployment_target = '7.0'
s.osx.deployment_target = '10.10'
+ s.tvos.deployment_target = '10.0'
- s.cocoapods_version = '>= 1.4.0.beta.2'
+ s.cocoapods_version = '>= 1.4.0'
s.static_framework = true
s.prefix_header_file = false
@@ -41,6 +42,13 @@ supports email and password accounts, as well as several 3rd party authenticatio
source + '**/FIRPhoneAuthCredential.[mh]',
source + '**/FIRPhoneAuthProvider.[mh]'
]
+ s.tvos.exclude_files = [
+ source + '**/FIRAuthURLPresenter.[mh]',
+ source + '**/FIRAuthWebView.[mh]',
+ source + '**/FIRAuthWebViewController.[mh]',
+ source + '**/FIRPhoneAuthCredential.[mh]',
+ source + '**/FIRPhoneAuthProvider.[mh]'
+ ]
s.public_header_files = source + 'Public/*.h'
s.preserve_paths = [
'Firebase/Auth/README.md',
diff --git a/FirebaseCore.podspec b/FirebaseCore.podspec
index 453518f..d25a979 100644
--- a/FirebaseCore.podspec
+++ b/FirebaseCore.podspec
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'FirebaseCore'
- s.version = '4.0.12'
+ s.version = '4.0.20'
s.summary = 'Firebase Core for iOS'
s.description = <<-DESC
@@ -18,14 +18,22 @@ Firebase Core includes FIRApp and FIROptions which provide central configuration
s.social_media_url = 'https://twitter.com/Firebase'
s.ios.deployment_target = '7.0'
s.osx.deployment_target = '10.10'
+ s.tvos.deployment_target = '10.0'
- s.cocoapods_version = '>= 1.4.0.beta.2'
+ s.cocoapods_version = '>= 1.4.0'
s.static_framework = true
s.prefix_header_file = false
s.source_files = 'Firebase/Core/**/*.[mh]'
s.public_header_files = 'Firebase/Core/Public/*.h', 'Firebase/Core/Private/*.h'
- s.private_header_files = 'Firebase/Core/Private/*.h'
- s.framework = 'SystemConfiguration'
+ s.private_header_files = 'Firebase/Core/Private/*.h', 'Firebase/Core/third_party/*.h'
+ s.frameworks = [
+ 'Foundation',
+ 'SystemConfiguration'
+ ]
s.dependency 'GoogleToolboxForMac/NSData+zlib', '~> 2.1'
+ s.pod_target_xcconfig = {
+ 'OTHER_CFLAGS' => '-fno-autolink ' +
+ '-DFIRCore_VERSION=' + s.version.to_s + ' -DFirebase_VERSION=4.13.0'
+ }
end
diff --git a/FirebaseDatabase.podspec b/FirebaseDatabase.podspec
index 338a2f6..6138536 100644
--- a/FirebaseDatabase.podspec
+++ b/FirebaseDatabase.podspec
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'FirebaseDatabase'
- s.version = '4.1.2'
+ s.version = '4.1.5'
s.summary = 'Firebase Open Source Libraries for iOS.'
s.description = <<-DESC
@@ -18,8 +18,9 @@ Simplify your iOS development, grow your user base, and monetize more effectivel
s.social_media_url = 'https://twitter.com/Firebase'
s.ios.deployment_target = '7.0'
s.osx.deployment_target = '10.10'
+ s.tvos.deployment_target = '10.0'
- s.cocoapods_version = '>= 1.4.0.beta.2'
+ s.cocoapods_version = '>= 1.4.0'
s.static_framework = true
s.prefix_header_file = false
diff --git a/FirebaseFirestore.podspec b/FirebaseFirestore.podspec
index ed7346c..fb94328 100644
--- a/FirebaseFirestore.podspec
+++ b/FirebaseFirestore.podspec
@@ -8,7 +8,7 @@
Pod::Spec.new do |s|
s.name = 'FirebaseFirestore'
- s.version = '0.9.3'
+ s.version = '0.11.0'
s.summary = 'Google Cloud Firestore for iOS'
s.description = <<-DESC
@@ -24,24 +24,32 @@ Google Cloud Firestore is a NoSQL document database built for automatic scaling,
s.ios.deployment_target = '7.0'
- s.cocoapods_version = '>= 1.4.0.beta.2'
+ s.cocoapods_version = '>= 1.4.0'
s.static_framework = true
s.prefix_header_file = false
s.source_files = [
'Firestore/Source/**/*',
'Firestore/Port/**/*',
+ 'Firestore/Protos/nanopb/**/*.[hc]',
'Firestore/Protos/objc/**/*.[hm]',
- 'Firestore/core/src/**/*.{h,cc}',
- 'Firestore/third_party/Immutable/*.[mh]'
+ 'Firestore/core/include/**/*.{h,cc,mm}',
+ 'Firestore/core/src/**/*.{h,cc,mm}',
+ 'Firestore/third_party/Immutable/*.[mh]',
]
s.requires_arc = [
'Firestore/Source/**/*',
+ 'Firestore/core/src/**/*.mm',
'Firestore/third_party/Immutable/*.[mh]'
]
s.exclude_files = [
'Firestore/Port/*test.cc',
- 'Firestore/third_party/Immutable/Tests/**'
+ 'Firestore/third_party/Immutable/Tests/**',
+
+ # Exclude alternate implementations for other platforms
+ 'Firestore/core/src/firebase/firestore/util/assert_stdio.cc',
+ 'Firestore/core/src/firebase/firestore/util/log_stdio.cc',
+ 'Firestore/core/src/firebase/firestore/util/secure_random_openssl.cc'
]
s.public_header_files = 'Firestore/Source/Public/*.h'
@@ -54,9 +62,35 @@ Google Cloud Firestore is a NoSQL document database built for automatic scaling,
s.frameworks = 'MobileCoreServices'
s.library = 'c++'
s.pod_target_xcconfig = {
- 'GCC_PREPROCESSOR_DEFINITIONS' =>
- 'GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS=1 ',
- 'HEADER_SEARCH_PATHS' => '"${PODS_TARGET_SRCROOT}"',
- 'OTHER_CFLAGS' => '-DFIRFirestore_VERSION=' + s.version.to_s
+ 'GCC_PREPROCESSOR_DEFINITIONS' => 'GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS=1 ',
+ 'HEADER_SEARCH_PATHS' =>
+ '"${PODS_TARGET_SRCROOT}" ' +
+ '"${PODS_TARGET_SRCROOT}/Firestore/third_party/abseil-cpp" ' +
+ '"${PODS_ROOT}/nanopb" ' +
+ '"${PODS_TARGET_SRCROOT}/Firestore/Protos/nanopb"',
+ 'OTHER_CFLAGS' => '-DFIRFirestore_VERSION=' + s.version.to_s + ' -DPB_FIELD_16BIT'
}
+
+ s.prepare_command = <<-CMD
+ # Generate a version of the config.h header suitable for building with
+ # CocoaPods.
+ sed '/^#cmakedefine/ d' \
+ Firestore/core/src/firebase/firestore/util/config.h.in > \
+ Firestore/core/src/firebase/firestore/util/config.h
+ CMD
+
+ s.subspec 'abseil-cpp' do |ss|
+ ss.preserve_path = [
+ 'Firestore/third_party/abseil-cpp/absl'
+ ]
+ ss.source_files = [
+ 'Firestore/third_party/abseil-cpp/**/*.cc'
+ ]
+ ss.exclude_files = [
+ 'Firestore/third_party/abseil-cpp/**/*_test.cc',
+ ]
+
+ ss.library = 'c++'
+ ss.compiler_flags = '$(inherited) ' + '-Wno-comma -Wno-range-loop-analysis'
+ end
end
diff --git a/FirebaseFirestoreSwift.podspec b/FirebaseFirestoreSwift.podspec
new file mode 100644
index 0000000..6843070
--- /dev/null
+++ b/FirebaseFirestoreSwift.podspec
@@ -0,0 +1,37 @@
+#
+# Be sure to run `pod lib lint FirebaseFirestoreSwift.podspec' to ensure this is a
+# valid spec before submitting.
+#
+
+Pod::Spec.new do |s|
+ s.name = 'FirebaseFirestoreSwift'
+ s.version = '0.1'
+ s.summary = 'Google Cloud Firestore for iOS Swift Extensions'
+
+ s.description = <<-DESC
+Google Cloud Firestore is a NoSQL document database built for automatic scaling, high performance, and ease of application development.
+ DESC
+
+ s.homepage = 'https://developers.google.com/'
+ s.license = { :type => 'Apache', :file => 'LICENSE' }
+ s.authors = 'Google, Inc.'
+
+ s.source = {
+ :git => 'https://github.com/Firebase/firebase-ios-sdk.git',
+ :tag => s.version.to_s
+ }
+
+ s.swift_version = '4.0'
+ s.ios.deployment_target = '8.0'
+
+ s.cocoapods_version = '>= 1.4.0'
+ s.static_framework = true
+ s.prefix_header_file = false
+
+ s.requires_arc = true
+ s.source_files = [
+ 'Firestore/Swift/Source/**/*.swift',
+ ]
+
+ s.dependency 'FirebaseFirestore', ">= 0.10.0"
+end
diff --git a/FirebaseFunctions.podspec b/FirebaseFunctions.podspec
new file mode 100644
index 0000000..caf3023
--- /dev/null
+++ b/FirebaseFunctions.podspec
@@ -0,0 +1,33 @@
+#
+# Be sure to run `pod lib lint FirebaseFunctions.podspec' to ensure this is a
+# valid spec before submitting.
+#
+# Any lines starting with a # are optional, but their use is encouraged
+# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html
+#
+
+Pod::Spec.new do |s|
+ s.name = 'FirebaseFunctions'
+ s.version = '1.0.0'
+ s.summary = 'Cloud Functions for Firebase iOS SDK.'
+
+ s.description = <<-DESC
+iOS SDK for Cloud Functions for Firebase.
+ DESC
+
+ s.homepage = 'https://developers.google.com/'
+ s.authors = 'Google, Inc.'
+ s.source = { :git => 'https://github.com/TBD/FirebaseFunctions.git', :tag => s.version.to_s }
+
+ s.ios.deployment_target = '8.0'
+
+ s.cocoapods_version = '>= 1.4.0'
+ s.static_framework = true
+ s.prefix_header_file = false
+
+ s.source_files = 'Functions/FirebaseFunctions/**/*'
+ s.public_header_files = 'Functions/FirebaseFunctions/Public/*.h'
+
+ s.dependency 'FirebaseCore', '~> 4.0'
+ s.dependency 'GTMSessionFetcher/Core', '~> 1.1'
+end
diff --git a/FirebaseMessaging.podspec b/FirebaseMessaging.podspec
index a26b0b9..5484013 100644
--- a/FirebaseMessaging.podspec
+++ b/FirebaseMessaging.podspec
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'FirebaseMessaging'
- s.version = '2.0.7'
+ s.version = '2.2.0'
s.summary = 'Firebase Messaging for iOS'
s.description = <<-DESC
@@ -22,7 +22,7 @@ device, and it is completely free.
s.ios.deployment_target = '7.0'
s.osx.deployment_target = '10.10'
- s.cocoapods_version = '>= 1.4.0.beta.2'
+ s.cocoapods_version = '>= 1.4.0'
s.static_framework = true
s.prefix_header_file = false
diff --git a/FirebaseStorage.podspec b/FirebaseStorage.podspec
index af81620..f3b8f2a 100644
--- a/FirebaseStorage.podspec
+++ b/FirebaseStorage.podspec
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'FirebaseStorage'
- s.version = '2.1.0'
+ s.version = '2.2.0'
s.summary = 'Firebase Storage for iOS'
s.description = <<-DESC
@@ -18,8 +18,9 @@ Firebase Storage provides robust, secure file uploads and downloads from Firebas
s.social_media_url = 'https://twitter.com/Firebase'
s.ios.deployment_target = '7.0'
s.osx.deployment_target = '10.10'
+ s.tvos.deployment_target = '10.0'
- s.cocoapods_version = '>= 1.4.0.beta.2'
+ s.cocoapods_version = '>= 1.4.0'
s.static_framework = true
s.prefix_header_file = false
diff --git a/Firestore/CHANGELOG.md b/Firestore/CHANGELOG.md
index 0c5bcdc..00e16f5 100644
--- a/Firestore/CHANGELOG.md
+++ b/Firestore/CHANGELOG.md
@@ -1,8 +1,83 @@
# Unreleased
+- [changed] Replaced the `DocumentListenOptions` object with a simple boolean.
+ Instead of calling
+ `addSnapshotListener(options: DocumentListenOptions.includeMetadataChanges(true))`
+ call `addSnapshotListener(includeMetadataChanges:true)`.
+- [changed] Replaced the `SetOptions` object with a simple boolean. Instead of
+ calling `setData(["a": "b"], options: SetOptions.merge())` call
+ `setData(["a": "b"], merge: true)`.
+
+# v0.11.0
+- [fixed] Fixed a regression in the Firebase iOS SDK release 4.11.0 that could
+ cause `getDocument()` requests made while offline to be delayed by up to 10
+ seconds (rather than returning from cache immediately).
+- [feature] Added a new `Timestamp` class to represent timestamp fields,
+ currently supporting up to microsecond precision. It can be passed to API
+ methods anywhere a system Date is currently accepted. To make
+ `DocumentSnapshot`s read timestamp fields back as `Timestamp`s instead of
+ Dates, you can set the newly added property `areTimestampsInSnapshotsEnabled`
+ in `FirestoreSettings` to `true`. Note that the current behavior
+ (`DocumentSnapshot`s returning system Dates) will be removed in a future
+ release. Using `Timestamp`s avoids rounding errors (system Date is stored as
+ a floating-point value, so the value read back from a `DocumentSnapshot`
+ might be slightly different from the value written).
+
+# v0.10.4
+- [changed] If the SDK's attempt to connect to the Cloud Firestore backend
+ neither succeeds nor fails within 10 seconds, the SDK will consider itself
+ "offline", causing getDocument() calls to resolve with cached results, rather
+ than continuing to wait.
+- [fixed] Fixed a race condition after calling `enableNetwork()` that could
+ result in a "Mutation batchIDs must be acknowledged in order" assertion crash.
+- [fixed] Fixed undefined symbols in the absl namespace (#898).
+
+# v0.10.3
+- [fixed] Fixed a regression in the 4.10.0 Firebase iOS SDK release that
+ prevented the SDK from communicating with the backend before successfully
+ authenticating via Firebase Authentication or after unauthenticating and
+ re-authenticating. Reads and writes would silently be executed locally
+ but not sent to the backend.
+
+# v0.10.2
+- [changed] When you delete a FirebaseApp, the associated Firestore instances
+ are now also deleted (#683).
+- [fixed] Fixed race conditions in streams that could be exposed by rapidly
+ toggling the network from enabled to disabled and back (#772) or encountering
+ a failure from the server (#835).
+- [fixed] Addressed warnings shown by the latest versions of Xcode and CocoaPods.
+
+# v0.10.1
+- [fixed] Fixed a regression in Firebase iOS release 4.8.1 that could in certain
+ cases result in an "OnlineState should not affect limbo documents." assertion
+ crash when the client loses its network connection.
+- [fixed] It's now possible to pass a nil completion block to WriteBatch.commit (#745).
+
+# v0.10.0
+- [changed] Removed the includeMetadataChanges property in FIRDocumentListenOptions
+ to avoid confusion with the factory method of the same name.
+- [changed] Added a commit method that takes no completion handler to FIRWriteBatch.
+- [feature] Queries can now be created from an NSPredicate.
+- [feature] Added SnapshotOptions API to control how DocumentSnapshots return unresolved
+ server timestamps.
+- [feature] Added `disableNetwork()` and `enableNetwork()` methods to
+ `Firestore` class, allowing for explicit network management.
+- [changed] For non-existing documents, DocumentSnapshot.data() now returns `nil`
+ instead of throwing an exception. A non-nullable QueryDocumentSnapshot is
+ introduced for Queries to reduce the number of nil-checks in your code.
+- [changed] Snapshot listeners (with the `includeMetadataChanges` option
+ enabled) now receive an event with `snapshot.metadata.isFromCache` set to
+ `true` if the SDK loses its connection to the backend. A new event with
+ `snapshot.metadata.isFromCache` set to false will be raised once the
+ connection is restored and the query is in sync with the backend again.
+- [fixed] Multiple offline mutations now properly reflected in retrieved
+ documents. Previously, only the last mutation would be visible. (#643)
+- [fixed] Fixed a crash in `closeWithFinaleState:` that could be triggered by
+ signing out when the app didn't have a network connection.
+
+# v0.9.4
- [changed] Firestore no longer has a direct dependency on FirebaseAuth.
- [fixed] Fixed a crash when using path names with international characters
with persistence enabled.
-
- [fixed] Addressed race condition during the teardown of idle streams (#490).
# v0.9.3
diff --git a/Firestore/CMakeLists.txt b/Firestore/CMakeLists.txt
index 6c2a32e..80308bb 100644
--- a/Firestore/CMakeLists.txt
+++ b/Firestore/CMakeLists.txt
@@ -13,20 +13,98 @@
# limitations under the License.
cmake_minimum_required(VERSION 2.8.11)
-project(firestore)
+project(firestore C CXX)
-set(FIREBASE_SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/..")
-include("${FIREBASE_SOURCE_DIR}/cmake/utils.cmake")
+option(WITH_ASAN "Build with Address Sanitizer" OFF)
+# TODO(varconst): msan
+# Memory sanitizer is more complicated:
+# - it requires all dependencies to be compiled with msan enabled (see
+# https://github.com/google/sanitizers/wiki/MemorySanitizerLibcxxHowTo);
+# - AppleClang doesn't support it.
+option(WITH_TSAN "Build with Thread Sanitizer (mutually exculsive with other sanitizers)" OFF)
+option(WITH_UBSAN "Build with Undefined Behavior sanitizer" OFF)
-find_package(GTest REQUIRED)
+macro(add_to_compile_and_link_flags flag)
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${flag}")
+ set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${flag}")
+endmacro()
-# We use C++11
-set(CMAKE_CXX_STANDARD 11)
-set(CMAKE_CXX_STANDARD_REQUIRED ON)
-set(CMAKE_CXX_EXTENSIONS OFF)
+if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
+ if(WITH_ASAN)
+ add_to_compile_and_link_flags("-fsanitize=address")
+ endif()
-# Fully qualified imports, project wide
-include_directories("${FIREBASE_SOURCE_DIR}")
+ if(WITH_TSAN)
+ if(WITH_ASAN OR WITH_UBSAN)
+ message(FATAL_ERROR "Cannot combine TSan with other santizers")
+ endif()
+ add_to_compile_and_link_flags("-fsanitize=thread")
+ endif()
+
+ if(WITH_UBSAN)
+ add_to_compile_and_link_flags("-fsanitize=undefined")
+ endif()
+
+ if (WITH_ASAN OR WITH_TSAN OR WITH_UBSAN)
+ # Recommended to "get nicer stack traces in error messages"
+ # TODO(varconst): double-check that TSan actually needs this flag (it's
+ # explicitly recommended in the docs for ASan and UBSan)
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-omit-frame-pointer")
+ endif()
+else()
+ if(WITH_ASAN OR WITH_TSAN OR WITH_UBSAN)
+ message(FATAL_ERROR "Only Clang and GCC support sanitizers")
+ endif()
+endif()
+
+set(FIREBASE_SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/..)
+
+# CMAKE_INSTALL_PREFIX should be passed in to this build so that it can find
+# outputs of the superbuild. This is handled automatically if run via the
+# superbuild (i.e. by invoking cmake on the directory above this).
+#
+# If you want to use this project directly in e.g. CLion, make sure you
+# configure this.
+set(FIREBASE_INSTALL_DIR ${CMAKE_INSTALL_PREFIX})
+
+list(INSERT CMAKE_MODULE_PATH 0 ${FIREBASE_SOURCE_DIR}/cmake)
+include(utils)
+
+# Include GoogleTest directly in the build.
+set(gtest_dir ${FIREBASE_INSTALL_DIR}/external/googletest)
+add_subdirectory(
+ ${gtest_dir}/src/googletest
+ ${gtest_dir}/src/googletest-build
+ EXCLUDE_FROM_ALL
+)
+
+# Set up aliases with the same names as available via FindGTest.
+add_library(
+ GTest::GTest ALIAS gtest
+)
+
+add_library(
+ GTest::Main ALIAS gtest_main
+)
+
+find_package(LevelDB REQUIRED)
+find_package(GRPC REQUIRED)
+find_package(Nanopb REQUIRED)
+
+if(APPLE)
+ find_package(FirebaseCore REQUIRED)
+endif()
enable_testing()
+add_subdirectory(third_party/abseil-cpp)
+
+include(CompilerSetup)
+
+# Generated sources will be relative to the binary directory.
+include_directories(${FIREBASE_INSTALL_DIR})
+
+# Fully qualified imports, project wide
+include_directories(${FIREBASE_SOURCE_DIR})
+
add_subdirectory(core)
+add_subdirectory(Protos)
diff --git a/Firestore/Example/Firestore.xcodeproj/project.pbxproj b/Firestore/Example/Firestore.xcodeproj/project.pbxproj
index 437b661..de4cf17 100644
--- a/Firestore/Example/Firestore.xcodeproj/project.pbxproj
+++ b/Firestore/Example/Firestore.xcodeproj/project.pbxproj
@@ -13,9 +13,10 @@
buildPhases = (
);
dependencies = (
- DE0761FA1F2FEE7E003233AF /* PBXTargetDependency */,
DE29E7FA1F2174DD00909613 /* PBXTargetDependency */,
+ 54C9EDFF2040E41900A969CD /* PBXTargetDependency */,
DE29E7FC1F2174DD00909613 /* PBXTargetDependency */,
+ DE0761FA1F2FEE7E003233AF /* PBXTargetDependency */,
);
name = AllTests;
productName = AllTests;
@@ -23,13 +24,90 @@
/* End PBXAggregateTarget section */
/* Begin PBXBuildFile section */
+ 132E3E53179DE287D875F3F2 /* FSTLevelDBTransactionTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 132E36BB104830BD806351AC /* FSTLevelDBTransactionTests.mm */; };
3B843E4C1F3A182900548890 /* remote_store_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 3B843E4A1F3930A400548890 /* remote_store_spec_test.json */; };
+ 5436F32420008FAD006E51E3 /* string_printf_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 5436F32320008FAD006E51E3 /* string_printf_test.cc */; };
+ 5467FB01203E5717009C9584 /* FIRFirestoreTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5467FAFF203E56F8009C9584 /* FIRFirestoreTests.mm */; };
+ 5467FB08203E6A44009C9584 /* app_testing.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5467FB07203E6A44009C9584 /* app_testing.mm */; };
54740A571FC914BA00713A1A /* secure_random_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54740A531FC913E500713A1A /* secure_random_test.cc */; };
54740A581FC914F000713A1A /* autoid_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54740A521FC913E500713A1A /* autoid_test.cc */; };
- 54764FAB1FAA0C320085E60A /* string_util_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54764FAA1FAA0C320085E60A /* string_util_test.cc */; };
54764FAF1FAA21B90085E60A /* FSTGoogleTestTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 54764FAE1FAA21B90085E60A /* FSTGoogleTestTests.mm */; };
+ 548DB927200D590300E00ABC /* assert_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 548DB926200D590300E00ABC /* assert_test.cc */; };
+ 548DB929200D59F600E00ABC /* comparison_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 548DB928200D59F600E00ABC /* comparison_test.cc */; };
5491BC721FB44593008B3588 /* FSTIntegrationTestCase.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5491BC711FB44593008B3588 /* FSTIntegrationTestCase.mm */; };
5491BC731FB44593008B3588 /* FSTIntegrationTestCase.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5491BC711FB44593008B3588 /* FSTIntegrationTestCase.mm */; };
+ 5492E03120213FFC00B64F25 /* FSTLevelDBSpecTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E02C20213FFB00B64F25 /* FSTLevelDBSpecTests.mm */; };
+ 5492E03220213FFC00B64F25 /* FSTMockDatastore.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E02D20213FFC00B64F25 /* FSTMockDatastore.mm */; };
+ 5492E03320213FFC00B64F25 /* FSTSyncEngineTestDriver.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E02E20213FFC00B64F25 /* FSTSyncEngineTestDriver.mm */; };
+ 5492E03420213FFC00B64F25 /* FSTMemorySpecTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E02F20213FFC00B64F25 /* FSTMemorySpecTests.mm */; };
+ 5492E03520213FFC00B64F25 /* FSTSpecTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E03020213FFC00B64F25 /* FSTSpecTests.mm */; };
+ 5492E03C2021401F00B64F25 /* XCTestCase+Await.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0372021401E00B64F25 /* XCTestCase+Await.mm */; };
+ 5492E03D2021401F00B64F25 /* FSTAssertTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0382021401E00B64F25 /* FSTAssertTests.mm */; };
+ 5492E03E2021401F00B64F25 /* FSTEventAccumulator.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0392021401F00B64F25 /* FSTEventAccumulator.mm */; };
+ 5492E03F2021401F00B64F25 /* FSTHelpers.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E03A2021401F00B64F25 /* FSTHelpers.mm */; };
+ 5492E041202143E700B64F25 /* FSTEventAccumulator.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0392021401F00B64F25 /* FSTEventAccumulator.mm */; };
+ 5492E0422021440500B64F25 /* FSTHelpers.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E03A2021401F00B64F25 /* FSTHelpers.mm */; };
+ 5492E0442021457E00B64F25 /* XCTestCase+Await.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0372021401E00B64F25 /* XCTestCase+Await.mm */; };
+ 5492E050202154AA00B64F25 /* FIRCollectionReferenceTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E045202154AA00B64F25 /* FIRCollectionReferenceTests.mm */; };
+ 5492E051202154AA00B64F25 /* FIRQueryTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E046202154AA00B64F25 /* FIRQueryTests.mm */; };
+ 5492E052202154AB00B64F25 /* FIRGeoPointTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E048202154AA00B64F25 /* FIRGeoPointTests.mm */; };
+ 5492E053202154AB00B64F25 /* FIRDocumentReferenceTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E049202154AA00B64F25 /* FIRDocumentReferenceTests.mm */; };
+ 5492E054202154AB00B64F25 /* FIRFieldValueTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E04A202154AA00B64F25 /* FIRFieldValueTests.mm */; };
+ 5492E055202154AB00B64F25 /* FIRDocumentSnapshotTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E04B202154AA00B64F25 /* FIRDocumentSnapshotTests.mm */; };
+ 5492E056202154AB00B64F25 /* FIRFieldPathTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E04C202154AA00B64F25 /* FIRFieldPathTests.mm */; };
+ 5492E057202154AB00B64F25 /* FIRSnapshotMetadataTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E04D202154AA00B64F25 /* FIRSnapshotMetadataTests.mm */; };
+ 5492E058202154AB00B64F25 /* FSTAPIHelpers.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E04E202154AA00B64F25 /* FSTAPIHelpers.mm */; };
+ 5492E059202154AB00B64F25 /* FIRQuerySnapshotTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E04F202154AA00B64F25 /* FIRQuerySnapshotTests.mm */; };
+ 5492E063202154B900B64F25 /* FSTViewSnapshotTest.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E05C202154B800B64F25 /* FSTViewSnapshotTest.mm */; };
+ 5492E064202154B900B64F25 /* FSTQueryListenerTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E05D202154B900B64F25 /* FSTQueryListenerTests.mm */; };
+ 5492E065202154B900B64F25 /* FSTViewTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E05E202154B900B64F25 /* FSTViewTests.mm */; };
+ 5492E067202154B900B64F25 /* FSTEventManagerTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E060202154B900B64F25 /* FSTEventManagerTests.mm */; };
+ 5492E068202154B900B64F25 /* FSTQueryTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E061202154B900B64F25 /* FSTQueryTests.mm */; };
+ 5492E072202154D600B64F25 /* FIRQueryTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E069202154D500B64F25 /* FIRQueryTests.mm */; };
+ 5492E073202154D600B64F25 /* FIRFieldsTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E06A202154D500B64F25 /* FIRFieldsTests.mm */; };
+ 5492E074202154D600B64F25 /* FIRListenerRegistrationTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E06B202154D500B64F25 /* FIRListenerRegistrationTests.mm */; };
+ 5492E075202154D600B64F25 /* FIRDatabaseTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E06C202154D500B64F25 /* FIRDatabaseTests.mm */; };
+ 5492E076202154D600B64F25 /* FIRValidationTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E06D202154D600B64F25 /* FIRValidationTests.mm */; };
+ 5492E077202154D600B64F25 /* FIRServerTimestampTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E06E202154D600B64F25 /* FIRServerTimestampTests.mm */; };
+ 5492E078202154D600B64F25 /* FIRWriteBatchTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E06F202154D600B64F25 /* FIRWriteBatchTests.mm */; };
+ 5492E079202154D600B64F25 /* FIRCursorTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E070202154D600B64F25 /* FIRCursorTests.mm */; };
+ 5492E07A202154D600B64F25 /* FIRTypeTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E071202154D600B64F25 /* FIRTypeTests.mm */; };
+ 5492E07F202154EC00B64F25 /* FSTTransactionTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E07B202154EB00B64F25 /* FSTTransactionTests.mm */; };
+ 5492E080202154EC00B64F25 /* FSTSmokeTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E07C202154EB00B64F25 /* FSTSmokeTests.mm */; };
+ 5492E081202154EC00B64F25 /* FSTStreamTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E07D202154EB00B64F25 /* FSTStreamTests.mm */; };
+ 5492E082202154EC00B64F25 /* FSTDatastoreTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E07E202154EC00B64F25 /* FSTDatastoreTests.mm */; };
+ 5492E09D2021552D00B64F25 /* FSTLocalStoreTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0832021552A00B64F25 /* FSTLocalStoreTests.mm */; };
+ 5492E09E2021552D00B64F25 /* FSTEagerGarbageCollectorTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0842021552A00B64F25 /* FSTEagerGarbageCollectorTests.mm */; };
+ 5492E09F2021552D00B64F25 /* FSTLevelDBMigrationsTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0862021552A00B64F25 /* FSTLevelDBMigrationsTests.mm */; };
+ 5492E0A02021552D00B64F25 /* FSTLevelDBMutationQueueTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0872021552A00B64F25 /* FSTLevelDBMutationQueueTests.mm */; };
+ 5492E0A12021552D00B64F25 /* FSTMemoryLocalStoreTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0882021552A00B64F25 /* FSTMemoryLocalStoreTests.mm */; };
+ 5492E0A22021552D00B64F25 /* FSTQueryCacheTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0892021552A00B64F25 /* FSTQueryCacheTests.mm */; };
+ 5492E0A32021552D00B64F25 /* FSTLocalSerializerTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E08A2021552A00B64F25 /* FSTLocalSerializerTests.mm */; };
+ 5492E0A42021552D00B64F25 /* FSTMemoryQueryCacheTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E08B2021552B00B64F25 /* FSTMemoryQueryCacheTests.mm */; };
+ 5492E0A52021552D00B64F25 /* FSTMemoryRemoteDocumentCacheTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E08C2021552B00B64F25 /* FSTMemoryRemoteDocumentCacheTests.mm */; };
+ 5492E0A62021552D00B64F25 /* FSTPersistenceTestHelpers.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E08D2021552B00B64F25 /* FSTPersistenceTestHelpers.mm */; };
+ 5492E0A72021552D00B64F25 /* FSTLevelDBKeyTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E08E2021552B00B64F25 /* FSTLevelDBKeyTests.mm */; };
+ 5492E0A82021552D00B64F25 /* FSTLevelDBLocalStoreTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E08F2021552B00B64F25 /* FSTLevelDBLocalStoreTests.mm */; };
+ 5492E0AA2021552D00B64F25 /* FSTLevelDBRemoteDocumentCacheTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0922021552B00B64F25 /* FSTLevelDBRemoteDocumentCacheTests.mm */; };
+ 5492E0AB2021552D00B64F25 /* StringViewTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0932021552B00B64F25 /* StringViewTests.mm */; };
+ 5492E0AC2021552D00B64F25 /* FSTMutationQueueTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0962021552C00B64F25 /* FSTMutationQueueTests.mm */; };
+ 5492E0AD2021552D00B64F25 /* FSTMemoryMutationQueueTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0972021552C00B64F25 /* FSTMemoryMutationQueueTests.mm */; };
+ 5492E0AE2021552D00B64F25 /* FSTLevelDBQueryCacheTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0982021552C00B64F25 /* FSTLevelDBQueryCacheTests.mm */; };
+ 5492E0AF2021552D00B64F25 /* FSTReferenceSetTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E09A2021552C00B64F25 /* FSTReferenceSetTests.mm */; };
+ 5492E0B12021552D00B64F25 /* FSTRemoteDocumentCacheTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E09C2021552D00B64F25 /* FSTRemoteDocumentCacheTests.mm */; };
+ 5492E0B92021555100B64F25 /* FSTDocumentKeyTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0B22021555000B64F25 /* FSTDocumentKeyTests.mm */; };
+ 5492E0BA2021555100B64F25 /* FSTDocumentSetTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0B32021555100B64F25 /* FSTDocumentSetTests.mm */; };
+ 5492E0BD2021555100B64F25 /* FSTDocumentTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0B62021555100B64F25 /* FSTDocumentTests.mm */; };
+ 5492E0BE2021555100B64F25 /* FSTMutationTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0B72021555100B64F25 /* FSTMutationTests.mm */; };
+ 5492E0BF2021555100B64F25 /* FSTFieldValueTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0B82021555100B64F25 /* FSTFieldValueTests.mm */; };
+ 5492E0C62021557E00B64F25 /* FSTWatchChange+Testing.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0C02021557E00B64F25 /* FSTWatchChange+Testing.mm */; };
+ 5492E0C72021557E00B64F25 /* FSTSerializerBetaTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0C12021557E00B64F25 /* FSTSerializerBetaTests.mm */; };
+ 5492E0C82021557E00B64F25 /* FSTDatastoreTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0C22021557E00B64F25 /* FSTDatastoreTests.mm */; };
+ 5492E0C92021557E00B64F25 /* FSTRemoteEventTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0C32021557E00B64F25 /* FSTRemoteEventTests.mm */; };
+ 5492E0CA2021557E00B64F25 /* FSTWatchChangeTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0C52021557E00B64F25 /* FSTWatchChangeTests.mm */; };
+ 5495EB032040E90200EBA509 /* CodableGeoPointTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5495EB022040E90200EBA509 /* CodableGeoPointTests.swift */; };
+ 54995F6F205B6E12004EFFA0 /* leveldb_key_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54995F6E205B6E12004EFFA0 /* leveldb_key_test.cc */; };
+ 54C2294F1FECABAE007D065B /* log_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54C2294E1FECABAE007D065B /* log_test.cc */; };
54DA12A61F315EE100DD57A1 /* collection_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 54DA129C1F315EE100DD57A1 /* collection_spec_test.json */; };
54DA12A71F315EE100DD57A1 /* existence_filter_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 54DA129D1F315EE100DD57A1 /* existence_filter_spec_test.json */; };
54DA12A81F315EE100DD57A1 /* limbo_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 54DA129E1F315EE100DD57A1 /* limbo_spec_test.json */; };
@@ -40,11 +118,7 @@
54DA12AD1F315EE100DD57A1 /* persistence_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 54DA12A31F315EE100DD57A1 /* persistence_spec_test.json */; };
54DA12AE1F315EE100DD57A1 /* resume_token_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 54DA12A41F315EE100DD57A1 /* resume_token_spec_test.json */; };
54DA12AF1F315EE100DD57A1 /* write_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 54DA12A51F315EE100DD57A1 /* write_spec_test.json */; };
- 54DA12B11F315F3800DD57A1 /* FIRValidationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 54DA12B01F315F3800DD57A1 /* FIRValidationTests.m */; };
- 54E928241F33953300C1953E /* FSTEventAccumulator.m in Sources */ = {isa = PBXBuildFile; fileRef = 54E9281D1F33950B00C1953E /* FSTEventAccumulator.m */; };
- 54E928251F33953400C1953E /* FSTEventAccumulator.m in Sources */ = {isa = PBXBuildFile; fileRef = 54E9281D1F33950B00C1953E /* FSTEventAccumulator.m */; };
- 54E9282C1F339CAD00C1953E /* XCTestCase+Await.m in Sources */ = {isa = PBXBuildFile; fileRef = 54E9282B1F339CAD00C1953E /* XCTestCase+Await.m */; };
- 54E9282D1F339CAD00C1953E /* XCTestCase+Await.m in Sources */ = {isa = PBXBuildFile; fileRef = 54E9282B1F339CAD00C1953E /* XCTestCase+Await.m */; };
+ 54EB764D202277B30088B8F3 /* array_sorted_map_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54EB764C202277B30088B8F3 /* array_sorted_map_test.cc */; };
6003F58E195388D20070C39A /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58D195388D20070C39A /* Foundation.framework */; };
6003F590195388D20070C39A /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58F195388D20070C39A /* CoreGraphics.framework */; };
6003F592195388D20070C39A /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F591195388D20070C39A /* UIKit.framework */; };
@@ -57,90 +131,64 @@
6003F5B1195388D20070C39A /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58D195388D20070C39A /* Foundation.framework */; };
6003F5B2195388D20070C39A /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F591195388D20070C39A /* UIKit.framework */; };
6003F5BA195388D20070C39A /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6003F5B8195388D20070C39A /* InfoPlist.strings */; };
- 61E1D8B11FCF6C5700753285 /* StringViewTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 61E1D8AF1FCF6AF500753285 /* StringViewTests.mm */; };
6ED54761B845349D43DB6B78 /* Pods_Firestore_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 75A6FE51C1A02DF38F62FAAD /* Pods_Firestore_Example.framework */; };
71719F9F1E33DC2100824A3D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 71719F9D1E33DC2100824A3D /* LaunchScreen.storyboard */; };
+ 7346E61D20325C6900FD6CEF /* FSTDispatchQueueTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 7346E61C20325C6900FD6CEF /* FSTDispatchQueueTests.mm */; };
873B8AEB1B1F5CCA007FD442 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 873B8AEA1B1F5CCA007FD442 /* Main.storyboard */; };
+ AB356EF7200EA5EB0089B766 /* field_value_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB356EF6200EA5EB0089B766 /* field_value_test.cc */; };
+ AB380CFB2019388600D97691 /* target_id_generator_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB380CF82019382300D97691 /* target_id_generator_test.cc */; };
+ AB380CFE201A2F4500D97691 /* string_util_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB380CFC201A2EE200D97691 /* string_util_test.cc */; };
+ AB380D02201BC69F00D97691 /* bits_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB380D01201BC69F00D97691 /* bits_test.cc */; };
+ AB380D04201BC6E400D97691 /* ordered_code_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB380D03201BC6E400D97691 /* ordered_code_test.cc */; };
+ AB38D93020236E21000A432D /* database_info_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB38D92E20235D22000A432D /* database_info_test.cc */; };
+ AB6B908420322E4D00CC290A /* document_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB6B908320322E4D00CC290A /* document_test.cc */; };
+ AB6B908620322E6D00CC290A /* maybe_document_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB6B908520322E6D00CC290A /* maybe_document_test.cc */; };
+ AB6B908820322E8800CC290A /* no_document_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB6B908720322E8800CC290A /* no_document_test.cc */; };
+ AB7BAB342012B519001E0872 /* geo_point_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB7BAB332012B519001E0872 /* geo_point_test.cc */; };
+ ABA495BB202B7E80008A7851 /* snapshot_version_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = ABA495B9202B7E79008A7851 /* snapshot_version_test.cc */; };
+ ABC1D7DC2023A04B00BA84F0 /* credentials_provider_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB38D9342023966E000A432D /* credentials_provider_test.cc */; };
+ ABC1D7DD2023A04F00BA84F0 /* empty_credentials_provider_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB38D93620239689000A432D /* empty_credentials_provider_test.cc */; };
+ ABC1D7DE2023A05300BA84F0 /* user_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB38D93220239654000A432D /* user_test.cc */; };
+ ABC1D7E12023A40C00BA84F0 /* token_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = ABC1D7DF2023A3EF00BA84F0 /* token_test.cc */; };
+ ABC1D7E42024AFDE00BA84F0 /* firebase_credentials_provider_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = ABC1D7E22023CDC500BA84F0 /* firebase_credentials_provider_test.mm */; };
+ ABE6637A201FA81900ED349A /* database_id_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB71064B201FA60300344F18 /* database_id_test.cc */; };
+ ABF6506C201131F8005F2C74 /* timestamp_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = ABF6506B201131F8005F2C74 /* timestamp_test.cc */; };
AFE6114F0D4DAECBA7B7C089 /* Pods_Firestore_IntegrationTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B2FA635DF5D116A67A7441CD /* Pods_Firestore_IntegrationTests.framework */; };
+ B6152AD7202A53CB000E5744 /* document_key_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6152AD5202A5385000E5744 /* document_key_test.cc */; };
+ B65D34A9203C995B0076A5E1 /* FIRTimestampTest.m in Sources */ = {isa = PBXBuildFile; fileRef = B65D34A7203C99090076A5E1 /* FIRTimestampTest.m */; };
+ B686F2AF2023DDEE0028D6BE /* field_path_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B686F2AD2023DDB20028D6BE /* field_path_test.cc */; };
+ B686F2B22025000D0028D6BE /* resource_path_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B686F2B02024FFD70028D6BE /* resource_path_test.cc */; };
C4E749275AD0FBDF9F4716A8 /* Pods_SwiftBuildTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 32AD40BF6B0E849B07FFD05E /* Pods_SwiftBuildTest.framework */; };
- D5B2532E4676014F57A7EAB9 /* FSTStreamTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D5B25C0D4AADFCA3ADB883E4 /* FSTStreamTests.m */; };
- D5B25474286C9800CE42B8C2 /* FSTTestDispatchQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = D5B25292CED31B81FDED0411 /* FSTTestDispatchQueue.m */; };
- D5B259FDEE8094E8D710C5BF /* FSTTestDispatchQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = D5B25292CED31B81FDED0411 /* FSTTestDispatchQueue.m */; };
- DE03B2C91F2149D600A30B9C /* FSTTransactionTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE51B1C61F0D48AC0013853F /* FSTTransactionTests.m */; };
DE03B2D41F2149D600A30B9C /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F5AF195388D20070C39A /* XCTest.framework */; };
DE03B2D51F2149D600A30B9C /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F591195388D20070C39A /* UIKit.framework */; };
DE03B2D61F2149D600A30B9C /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58D195388D20070C39A /* Foundation.framework */; };
DE03B2D71F2149D600A30B9C /* Pods_Firestore_Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 69F6A10DBD6187489481CD76 /* Pods_Firestore_Tests.framework */; };
DE03B2DD1F2149D600A30B9C /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6003F5B8195388D20070C39A /* InfoPlist.strings */; };
- DE03B2EC1F214BA200A30B9C /* FSTDatastoreTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE51B1C41F0D48AC0013853F /* FSTDatastoreTests.m */; };
- DE03B2ED1F214BA200A30B9C /* FSTSmokeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE51B1C51F0D48AC0013853F /* FSTSmokeTests.m */; };
- DE03B2EE1F214BAA00A30B9C /* FIRWriteBatchTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DEFE0F471F1F960A0071599A /* FIRWriteBatchTests.m */; };
- DE03B2EF1F214BAA00A30B9C /* FIRCursorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE51B1BD1F0D48AC0013853F /* FIRCursorTests.m */; };
- DE03B2F01F214BAA00A30B9C /* FIRDatabaseTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE51B1BE1F0D48AC0013853F /* FIRDatabaseTests.m */; };
- DE03B2F11F214BAA00A30B9C /* FIRFieldsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE51B1BF1F0D48AC0013853F /* FIRFieldsTests.m */; };
- DE03B2F21F214BAA00A30B9C /* FIRListenerRegistrationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE51B1C01F0D48AC0013853F /* FIRListenerRegistrationTests.m */; };
- DE03B2F31F214BAA00A30B9C /* FIRQueryTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE51B1C11F0D48AC0013853F /* FIRQueryTests.m */; };
- DE03B2F41F214BAA00A30B9C /* FIRServerTimestampTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE51B1C21F0D48AC0013853F /* FIRServerTimestampTests.m */; };
- DE03B2F51F214BAA00A30B9C /* FIRTypeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE51B1C31F0D48AC0013853F /* FIRTypeTests.m */; };
- DE03B35E1F21586C00A30B9C /* FSTHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = DE51B1891F0D48AC0013853F /* FSTHelpers.m */; };
DE03B3631F215E1A00A30B9C /* CAcert.pem in Resources */ = {isa = PBXBuildFile; fileRef = DE03B3621F215E1600A30B9C /* CAcert.pem */; };
DE0761F81F2FE68D003233AF /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE0761F61F2FE68D003233AF /* main.swift */; };
DE2EF0851F3D0B6E003D0CDC /* FSTArraySortedDictionaryTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE2EF07E1F3D0B6E003D0CDC /* FSTArraySortedDictionaryTests.m */; };
DE2EF0861F3D0B6E003D0CDC /* FSTImmutableSortedDictionary+Testing.m in Sources */ = {isa = PBXBuildFile; fileRef = DE2EF0801F3D0B6E003D0CDC /* FSTImmutableSortedDictionary+Testing.m */; };
DE2EF0871F3D0B6E003D0CDC /* FSTImmutableSortedSet+Testing.m in Sources */ = {isa = PBXBuildFile; fileRef = DE2EF0821F3D0B6E003D0CDC /* FSTImmutableSortedSet+Testing.m */; };
DE2EF0881F3D0B6E003D0CDC /* FSTTreeSortedDictionaryTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE2EF0841F3D0B6E003D0CDC /* FSTTreeSortedDictionaryTests.m */; };
- DE51B1CC1F0D48C00013853F /* FIRGeoPointTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE51B1841F0D48AC0013853F /* FIRGeoPointTests.m */; };
- DE51B1CD1F0D48CD0013853F /* FSTDatabaseInfoTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE51B1A91F0D48AC0013853F /* FSTDatabaseInfoTests.m */; };
- DE51B1CE1F0D48CD0013853F /* FSTEventManagerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE51B1AA1F0D48AC0013853F /* FSTEventManagerTests.m */; };
- DE51B1CF1F0D48CD0013853F /* FSTQueryListenerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE51B1AB1F0D48AC0013853F /* FSTQueryListenerTests.m */; };
- DE51B1D01F0D48CD0013853F /* FSTQueryTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE51B1AC1F0D48AC0013853F /* FSTQueryTests.m */; };
- DE51B1D11F0D48CD0013853F /* FSTTargetIDGeneratorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE51B1AE1F0D48AC0013853F /* FSTTargetIDGeneratorTests.m */; };
- DE51B1D21F0D48CD0013853F /* FSTTimestampTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE51B1AF1F0D48AC0013853F /* FSTTimestampTests.m */; };
- DE51B1D31F0D48CD0013853F /* FSTViewSnapshotTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DE51B1B01F0D48AC0013853F /* FSTViewSnapshotTest.m */; };
- DE51B1D41F0D48CD0013853F /* FSTViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE51B1B11F0D48AC0013853F /* FSTViewTests.m */; };
- DE51B1D91F0D490D0013853F /* FSTEagerGarbageCollectorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE51B1631F0D48AC0013853F /* FSTEagerGarbageCollectorTests.m */; };
- DE51B1DA1F0D490D0013853F /* FSTLevelDBLocalStoreTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE51B1651F0D48AC0013853F /* FSTLevelDBLocalStoreTests.m */; };
- DE51B1DB1F0D490D0013853F /* FSTLevelDBQueryCacheTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE51B1671F0D48AC0013853F /* FSTLevelDBQueryCacheTests.m */; };
- DE51B1DC1F0D490D0013853F /* FSTLocalSerializerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE51B1691F0D48AC0013853F /* FSTLocalSerializerTests.m */; };
- DE51B1DD1F0D490D0013853F /* FSTLocalStoreTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE51B16B1F0D48AC0013853F /* FSTLocalStoreTests.m */; };
- DE51B1DE1F0D490D0013853F /* FSTMemoryLocalStoreTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE51B16C1F0D48AC0013853F /* FSTMemoryLocalStoreTests.m */; };
- DE51B1DF1F0D490D0013853F /* FSTMemoryMutationQueueTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE51B16D1F0D48AC0013853F /* FSTMemoryMutationQueueTests.m */; };
- DE51B1E01F0D490D0013853F /* FSTMemoryQueryCacheTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE51B16E1F0D48AC0013853F /* FSTMemoryQueryCacheTests.m */; };
- DE51B1E11F0D490D0013853F /* FSTMemoryRemoteDocumentCacheTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE51B16F1F0D48AC0013853F /* FSTMemoryRemoteDocumentCacheTests.m */; };
- DE51B1E21F0D490D0013853F /* FSTMutationQueueTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE51B1711F0D48AC0013853F /* FSTMutationQueueTests.m */; };
- DE51B1E31F0D490D0013853F /* FSTPersistenceTestHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = DE51B1731F0D48AC0013853F /* FSTPersistenceTestHelpers.m */; };
- DE51B1E41F0D490D0013853F /* FSTQueryCacheTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE51B1751F0D48AC0013853F /* FSTQueryCacheTests.m */; };
- DE51B1E51F0D490D0013853F /* FSTReferenceSetTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE51B1761F0D48AC0013853F /* FSTReferenceSetTests.m */; };
- DE51B1E61F0D490D0013853F /* FSTRemoteDocumentCacheTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE51B1781F0D48AC0013853F /* FSTRemoteDocumentCacheTests.m */; };
- DE51B1E71F0D490D0013853F /* FSTRemoteDocumentChangeBufferTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE51B1791F0D48AC0013853F /* FSTRemoteDocumentChangeBufferTests.m */; };
- DE51B1E81F0D490D0013853F /* FSTLevelDBKeyTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = DE51B1641F0D48AC0013853F /* FSTLevelDBKeyTests.mm */; };
- DE51B1E91F0D490D0013853F /* FSTLevelDBMutationQueueTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = DE51B1661F0D48AC0013853F /* FSTLevelDBMutationQueueTests.mm */; };
- DE51B1EA1F0D490D0013853F /* FSTLevelDBRemoteDocumentCacheTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = DE51B1681F0D48AC0013853F /* FSTLevelDBRemoteDocumentCacheTests.mm */; };
- DE51B1EB1F0D490D0013853F /* FSTWriteGroupTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = DE51B17A1F0D48AC0013853F /* FSTWriteGroupTests.mm */; };
- DE51B1EC1F0D49140013853F /* FSTDatabaseIDTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE51B17C1F0D48AC0013853F /* FSTDatabaseIDTests.m */; };
- DE51B1ED1F0D49140013853F /* FSTDocumentKeyTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE51B17D1F0D48AC0013853F /* FSTDocumentKeyTests.m */; };
- DE51B1EE1F0D49140013853F /* FSTDocumentSetTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE51B17E1F0D48AC0013853F /* FSTDocumentSetTests.m */; };
- DE51B1EF1F0D49140013853F /* FSTDocumentTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE51B17F1F0D48AC0013853F /* FSTDocumentTests.m */; };
- DE51B1F01F0D49140013853F /* FSTFieldValueTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE51B1801F0D48AC0013853F /* FSTFieldValueTests.m */; };
- DE51B1F11F0D49140013853F /* FSTMutationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE51B1811F0D48AC0013853F /* FSTMutationTests.m */; };
- DE51B1F21F0D49140013853F /* FSTPathTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE51B1821F0D48AC0013853F /* FSTPathTests.m */; };
- DE51B1F31F0D491B0013853F /* FSTDatastoreTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE51B1B31F0D48AC0013853F /* FSTDatastoreTests.m */; };
- DE51B1F41F0D491B0013853F /* FSTRemoteEventTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE51B1B41F0D48AC0013853F /* FSTRemoteEventTests.m */; };
- DE51B1F61F0D491B0013853F /* FSTSerializerBetaTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE51B1B61F0D48AC0013853F /* FSTSerializerBetaTests.m */; };
- DE51B1F81F0D491F0013853F /* FSTWatchChange+Testing.m in Sources */ = {isa = PBXBuildFile; fileRef = DE51B1B91F0D48AC0013853F /* FSTWatchChange+Testing.m */; };
- DE51B1F91F0D491F0013853F /* FSTWatchChangeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE51B1BA1F0D48AC0013853F /* FSTWatchChangeTests.m */; };
- DE51B1FA1F0D492C0013853F /* FSTLevelDBSpecTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE51B1941F0D48AC0013853F /* FSTLevelDBSpecTests.m */; };
- DE51B1FB1F0D492C0013853F /* FSTMemorySpecTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE51B1951F0D48AC0013853F /* FSTMemorySpecTests.m */; };
- DE51B1FC1F0D492C0013853F /* FSTMockDatastore.m in Sources */ = {isa = PBXBuildFile; fileRef = DE51B1971F0D48AC0013853F /* FSTMockDatastore.m */; };
- DE51B1FD1F0D492C0013853F /* FSTSpecTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE51B1991F0D48AC0013853F /* FSTSpecTests.m */; };
- DE51B1FE1F0D492C0013853F /* FSTSyncEngineTestDriver.m in Sources */ = {isa = PBXBuildFile; fileRef = DE51B19B1F0D48AC0013853F /* FSTSyncEngineTestDriver.m */; };
- DE51B1FF1F0D493A0013853F /* FSTAssertTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE51B1861F0D48AC0013853F /* FSTAssertTests.m */; };
- DE51B2001F0D493A0013853F /* FSTComparisonTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE51B1871F0D48AC0013853F /* FSTComparisonTests.m */; };
- DE51B2011F0D493E0013853F /* FSTHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = DE51B1891F0D48AC0013853F /* FSTHelpers.m */; };
+ DEF43C59D90C3CF3CA34DDF4 /* Pods_Firestore_Example_Firestore_SwiftTests_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 245812330F6A31632BB4B623 /* Pods_Firestore_Example_Firestore_SwiftTests_iOS.framework */; };
F104BBD69BC3F0796E3A77C1 /* Pods_Firestore_Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 69F6A10DBD6187489481CD76 /* Pods_Firestore_Tests.framework */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
+ 54C9EDF62040E16300A969CD /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 6003F582195388D10070C39A /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 6003F589195388D20070C39A;
+ remoteInfo = Firestore_Example;
+ };
+ 54C9EDFE2040E41900A969CD /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 6003F582195388D10070C39A /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 54C9EDF02040E16300A969CD;
+ remoteInfo = Firestore_SwiftTests_iOS;
+ };
6003F5B3195388D20070C39A /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 6003F582195388D10070C39A /* Project object */;
@@ -181,15 +229,102 @@
/* Begin PBXFileReference section */
04DF37A117F88A9891379ED6 /* Pods-Firestore_Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Tests.release.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Tests/Pods-Firestore_Tests.release.xcconfig"; sourceTree = "<group>"; };
12F4357299652983A615F886 /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = "<group>"; };
+ 132E36BB104830BD806351AC /* FSTLevelDBTransactionTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTLevelDBTransactionTests.mm; sourceTree = "<group>"; };
+ 245812330F6A31632BB4B623 /* Pods_Firestore_Example_Firestore_SwiftTests_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Firestore_Example_Firestore_SwiftTests_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; };
32AD40BF6B0E849B07FFD05E /* Pods_SwiftBuildTest.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SwiftBuildTest.framework; sourceTree = BUILT_PRODUCTS_DIR; };
3B843E4A1F3930A400548890 /* remote_store_spec_test.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = remote_store_spec_test.json; sourceTree = "<group>"; };
+ 3F422FFBDA6E79396E2FB594 /* Pods-Firestore_Example-Firestore_SwiftTests_iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Example-Firestore_SwiftTests_iOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Example-Firestore_SwiftTests_iOS/Pods-Firestore_Example-Firestore_SwiftTests_iOS.debug.xcconfig"; sourceTree = "<group>"; };
42491D7DC8C8CD245CC22B93 /* Pods-SwiftBuildTest.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwiftBuildTest.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SwiftBuildTest/Pods-SwiftBuildTest.debug.xcconfig"; sourceTree = "<group>"; };
4EBC5F5ABE1FD097EFE5E224 /* Pods-Firestore_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Example.release.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Example/Pods-Firestore_Example.release.xcconfig"; sourceTree = "<group>"; };
+ 5436F32320008FAD006E51E3 /* string_printf_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = string_printf_test.cc; path = ../../core/test/firebase/firestore/util/string_printf_test.cc; sourceTree = "<group>"; };
+ 5467FAFF203E56F8009C9584 /* FIRFirestoreTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FIRFirestoreTests.mm; sourceTree = "<group>"; };
+ 5467FB06203E6A44009C9584 /* app_testing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = app_testing.h; path = ../../core/test/firebase/firestore/testutil/app_testing.h; sourceTree = "<group>"; };
+ 5467FB07203E6A44009C9584 /* app_testing.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = app_testing.mm; path = ../../core/test/firebase/firestore/testutil/app_testing.mm; sourceTree = "<group>"; };
54740A521FC913E500713A1A /* autoid_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = autoid_test.cc; path = ../../core/test/firebase/firestore/util/autoid_test.cc; sourceTree = "<group>"; };
54740A531FC913E500713A1A /* secure_random_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = secure_random_test.cc; path = ../../core/test/firebase/firestore/util/secure_random_test.cc; sourceTree = "<group>"; };
- 54764FAA1FAA0C320085E60A /* string_util_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = string_util_test.cc; path = ../../Port/string_util_test.cc; sourceTree = "<group>"; };
54764FAE1FAA21B90085E60A /* FSTGoogleTestTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = FSTGoogleTestTests.mm; path = GoogleTest/FSTGoogleTestTests.mm; sourceTree = "<group>"; };
+ 548DB926200D590300E00ABC /* assert_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = assert_test.cc; path = ../../core/test/firebase/firestore/util/assert_test.cc; sourceTree = "<group>"; };
+ 548DB928200D59F600E00ABC /* comparison_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = comparison_test.cc; path = ../../core/test/firebase/firestore/util/comparison_test.cc; sourceTree = "<group>"; };
5491BC711FB44593008B3588 /* FSTIntegrationTestCase.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTIntegrationTestCase.mm; sourceTree = "<group>"; };
+ 5492E02C20213FFB00B64F25 /* FSTLevelDBSpecTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTLevelDBSpecTests.mm; sourceTree = "<group>"; };
+ 5492E02D20213FFC00B64F25 /* FSTMockDatastore.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTMockDatastore.mm; sourceTree = "<group>"; };
+ 5492E02E20213FFC00B64F25 /* FSTSyncEngineTestDriver.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTSyncEngineTestDriver.mm; sourceTree = "<group>"; };
+ 5492E02F20213FFC00B64F25 /* FSTMemorySpecTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTMemorySpecTests.mm; sourceTree = "<group>"; };
+ 5492E03020213FFC00B64F25 /* FSTSpecTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTSpecTests.mm; sourceTree = "<group>"; };
+ 5492E0372021401E00B64F25 /* XCTestCase+Await.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "XCTestCase+Await.mm"; sourceTree = "<group>"; };
+ 5492E0382021401E00B64F25 /* FSTAssertTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTAssertTests.mm; sourceTree = "<group>"; };
+ 5492E0392021401F00B64F25 /* FSTEventAccumulator.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTEventAccumulator.mm; sourceTree = "<group>"; };
+ 5492E03A2021401F00B64F25 /* FSTHelpers.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTHelpers.mm; sourceTree = "<group>"; };
+ 5492E045202154AA00B64F25 /* FIRCollectionReferenceTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FIRCollectionReferenceTests.mm; sourceTree = "<group>"; };
+ 5492E046202154AA00B64F25 /* FIRQueryTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FIRQueryTests.mm; sourceTree = "<group>"; };
+ 5492E047202154AA00B64F25 /* FSTAPIHelpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FSTAPIHelpers.h; sourceTree = "<group>"; };
+ 5492E048202154AA00B64F25 /* FIRGeoPointTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FIRGeoPointTests.mm; sourceTree = "<group>"; };
+ 5492E049202154AA00B64F25 /* FIRDocumentReferenceTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FIRDocumentReferenceTests.mm; sourceTree = "<group>"; };
+ 5492E04A202154AA00B64F25 /* FIRFieldValueTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FIRFieldValueTests.mm; sourceTree = "<group>"; };
+ 5492E04B202154AA00B64F25 /* FIRDocumentSnapshotTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FIRDocumentSnapshotTests.mm; sourceTree = "<group>"; };
+ 5492E04C202154AA00B64F25 /* FIRFieldPathTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FIRFieldPathTests.mm; sourceTree = "<group>"; };
+ 5492E04D202154AA00B64F25 /* FIRSnapshotMetadataTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FIRSnapshotMetadataTests.mm; sourceTree = "<group>"; };
+ 5492E04E202154AA00B64F25 /* FSTAPIHelpers.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTAPIHelpers.mm; sourceTree = "<group>"; };
+ 5492E04F202154AA00B64F25 /* FIRQuerySnapshotTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FIRQuerySnapshotTests.mm; sourceTree = "<group>"; };
+ 5492E05A202154B800B64F25 /* FSTSyncEngine+Testing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "FSTSyncEngine+Testing.h"; sourceTree = "<group>"; };
+ 5492E05C202154B800B64F25 /* FSTViewSnapshotTest.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTViewSnapshotTest.mm; sourceTree = "<group>"; };
+ 5492E05D202154B900B64F25 /* FSTQueryListenerTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTQueryListenerTests.mm; sourceTree = "<group>"; };
+ 5492E05E202154B900B64F25 /* FSTViewTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTViewTests.mm; sourceTree = "<group>"; };
+ 5492E060202154B900B64F25 /* FSTEventManagerTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTEventManagerTests.mm; sourceTree = "<group>"; };
+ 5492E061202154B900B64F25 /* FSTQueryTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTQueryTests.mm; sourceTree = "<group>"; };
+ 5492E069202154D500B64F25 /* FIRQueryTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FIRQueryTests.mm; sourceTree = "<group>"; };
+ 5492E06A202154D500B64F25 /* FIRFieldsTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FIRFieldsTests.mm; sourceTree = "<group>"; };
+ 5492E06B202154D500B64F25 /* FIRListenerRegistrationTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FIRListenerRegistrationTests.mm; sourceTree = "<group>"; };
+ 5492E06C202154D500B64F25 /* FIRDatabaseTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FIRDatabaseTests.mm; sourceTree = "<group>"; };
+ 5492E06D202154D600B64F25 /* FIRValidationTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FIRValidationTests.mm; sourceTree = "<group>"; };
+ 5492E06E202154D600B64F25 /* FIRServerTimestampTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FIRServerTimestampTests.mm; sourceTree = "<group>"; };
+ 5492E06F202154D600B64F25 /* FIRWriteBatchTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FIRWriteBatchTests.mm; sourceTree = "<group>"; };
+ 5492E070202154D600B64F25 /* FIRCursorTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FIRCursorTests.mm; sourceTree = "<group>"; };
+ 5492E071202154D600B64F25 /* FIRTypeTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FIRTypeTests.mm; sourceTree = "<group>"; };
+ 5492E07B202154EB00B64F25 /* FSTTransactionTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTTransactionTests.mm; sourceTree = "<group>"; };
+ 5492E07C202154EB00B64F25 /* FSTSmokeTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTSmokeTests.mm; sourceTree = "<group>"; };
+ 5492E07D202154EB00B64F25 /* FSTStreamTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTStreamTests.mm; sourceTree = "<group>"; };
+ 5492E07E202154EC00B64F25 /* FSTDatastoreTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTDatastoreTests.mm; sourceTree = "<group>"; };
+ 5492E0832021552A00B64F25 /* FSTLocalStoreTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTLocalStoreTests.mm; sourceTree = "<group>"; };
+ 5492E0842021552A00B64F25 /* FSTEagerGarbageCollectorTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTEagerGarbageCollectorTests.mm; sourceTree = "<group>"; };
+ 5492E0852021552A00B64F25 /* FSTRemoteDocumentCacheTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FSTRemoteDocumentCacheTests.h; sourceTree = "<group>"; };
+ 5492E0862021552A00B64F25 /* FSTLevelDBMigrationsTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTLevelDBMigrationsTests.mm; sourceTree = "<group>"; };
+ 5492E0872021552A00B64F25 /* FSTLevelDBMutationQueueTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTLevelDBMutationQueueTests.mm; sourceTree = "<group>"; };
+ 5492E0882021552A00B64F25 /* FSTMemoryLocalStoreTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTMemoryLocalStoreTests.mm; sourceTree = "<group>"; };
+ 5492E0892021552A00B64F25 /* FSTQueryCacheTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTQueryCacheTests.mm; sourceTree = "<group>"; };
+ 5492E08A2021552A00B64F25 /* FSTLocalSerializerTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTLocalSerializerTests.mm; sourceTree = "<group>"; };
+ 5492E08B2021552B00B64F25 /* FSTMemoryQueryCacheTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTMemoryQueryCacheTests.mm; sourceTree = "<group>"; };
+ 5492E08C2021552B00B64F25 /* FSTMemoryRemoteDocumentCacheTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTMemoryRemoteDocumentCacheTests.mm; sourceTree = "<group>"; };
+ 5492E08D2021552B00B64F25 /* FSTPersistenceTestHelpers.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTPersistenceTestHelpers.mm; sourceTree = "<group>"; };
+ 5492E08E2021552B00B64F25 /* FSTLevelDBKeyTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTLevelDBKeyTests.mm; sourceTree = "<group>"; };
+ 5492E08F2021552B00B64F25 /* FSTLevelDBLocalStoreTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTLevelDBLocalStoreTests.mm; sourceTree = "<group>"; };
+ 5492E0912021552B00B64F25 /* FSTLocalStoreTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FSTLocalStoreTests.h; sourceTree = "<group>"; };
+ 5492E0922021552B00B64F25 /* FSTLevelDBRemoteDocumentCacheTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTLevelDBRemoteDocumentCacheTests.mm; sourceTree = "<group>"; };
+ 5492E0932021552B00B64F25 /* StringViewTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = StringViewTests.mm; sourceTree = "<group>"; };
+ 5492E0942021552C00B64F25 /* FSTMutationQueueTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FSTMutationQueueTests.h; sourceTree = "<group>"; };
+ 5492E0952021552C00B64F25 /* FSTQueryCacheTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FSTQueryCacheTests.h; sourceTree = "<group>"; };
+ 5492E0962021552C00B64F25 /* FSTMutationQueueTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTMutationQueueTests.mm; sourceTree = "<group>"; };
+ 5492E0972021552C00B64F25 /* FSTMemoryMutationQueueTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTMemoryMutationQueueTests.mm; sourceTree = "<group>"; };
+ 5492E0982021552C00B64F25 /* FSTLevelDBQueryCacheTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTLevelDBQueryCacheTests.mm; sourceTree = "<group>"; };
+ 5492E0992021552C00B64F25 /* FSTPersistenceTestHelpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FSTPersistenceTestHelpers.h; sourceTree = "<group>"; };
+ 5492E09A2021552C00B64F25 /* FSTReferenceSetTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTReferenceSetTests.mm; sourceTree = "<group>"; };
+ 5492E09C2021552D00B64F25 /* FSTRemoteDocumentCacheTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTRemoteDocumentCacheTests.mm; sourceTree = "<group>"; };
+ 5492E0B22021555000B64F25 /* FSTDocumentKeyTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTDocumentKeyTests.mm; sourceTree = "<group>"; };
+ 5492E0B32021555100B64F25 /* FSTDocumentSetTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTDocumentSetTests.mm; sourceTree = "<group>"; };
+ 5492E0B62021555100B64F25 /* FSTDocumentTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTDocumentTests.mm; sourceTree = "<group>"; };
+ 5492E0B72021555100B64F25 /* FSTMutationTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTMutationTests.mm; sourceTree = "<group>"; };
+ 5492E0B82021555100B64F25 /* FSTFieldValueTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTFieldValueTests.mm; sourceTree = "<group>"; };
+ 5492E0C02021557E00B64F25 /* FSTWatchChange+Testing.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "FSTWatchChange+Testing.mm"; sourceTree = "<group>"; };
+ 5492E0C12021557E00B64F25 /* FSTSerializerBetaTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTSerializerBetaTests.mm; sourceTree = "<group>"; };
+ 5492E0C22021557E00B64F25 /* FSTDatastoreTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTDatastoreTests.mm; sourceTree = "<group>"; };
+ 5492E0C32021557E00B64F25 /* FSTRemoteEventTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTRemoteEventTests.mm; sourceTree = "<group>"; };
+ 5492E0C42021557E00B64F25 /* FSTWatchChange+Testing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "FSTWatchChange+Testing.h"; sourceTree = "<group>"; };
+ 5492E0C52021557E00B64F25 /* FSTWatchChangeTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTWatchChangeTests.mm; sourceTree = "<group>"; };
+ 5495EB022040E90200EBA509 /* CodableGeoPointTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CodableGeoPointTests.swift; sourceTree = "<group>"; };
+ 54995F6E205B6E12004EFFA0 /* leveldb_key_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = leveldb_key_test.cc; path = ../../core/test/firebase/firestore/local/leveldb_key_test.cc; sourceTree = "<group>"; };
+ 54C2294E1FECABAE007D065B /* log_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = log_test.cc; path = ../../core/test/firebase/firestore/util/log_test.cc; sourceTree = "<group>"; };
+ 54C9EDF12040E16300A969CD /* Firestore_SwiftTests_iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Firestore_SwiftTests_iOS.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+ 54C9EDF52040E16300A969CD /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
54DA129C1F315EE100DD57A1 /* collection_spec_test.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = collection_spec_test.json; sourceTree = "<group>"; };
54DA129D1F315EE100DD57A1 /* existence_filter_spec_test.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = existence_filter_spec_test.json; sourceTree = "<group>"; };
54DA129E1F315EE100DD57A1 /* limbo_spec_test.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = limbo_spec_test.json; sourceTree = "<group>"; };
@@ -200,12 +335,10 @@
54DA12A31F315EE100DD57A1 /* persistence_spec_test.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = persistence_spec_test.json; sourceTree = "<group>"; };
54DA12A41F315EE100DD57A1 /* resume_token_spec_test.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = resume_token_spec_test.json; sourceTree = "<group>"; };
54DA12A51F315EE100DD57A1 /* write_spec_test.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = write_spec_test.json; sourceTree = "<group>"; };
- 54DA12B01F315F3800DD57A1 /* FIRValidationTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRValidationTests.m; sourceTree = "<group>"; };
54E9281C1F33950B00C1953E /* FSTEventAccumulator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FSTEventAccumulator.h; sourceTree = "<group>"; };
- 54E9281D1F33950B00C1953E /* FSTEventAccumulator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FSTEventAccumulator.m; sourceTree = "<group>"; };
54E9281E1F33950B00C1953E /* FSTIntegrationTestCase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FSTIntegrationTestCase.h; sourceTree = "<group>"; };
54E9282A1F339CAD00C1953E /* XCTestCase+Await.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "XCTestCase+Await.h"; sourceTree = "<group>"; };
- 54E9282B1F339CAD00C1953E /* XCTestCase+Await.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "XCTestCase+Await.m"; sourceTree = "<group>"; };
+ 54EB764C202277B30088B8F3 /* array_sorted_map_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = array_sorted_map_test.cc; path = ../../immutable/array_sorted_map_test.cc; sourceTree = "<group>"; };
6003F58A195388D20070C39A /* Firestore_Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Firestore_Example.app; sourceTree = BUILT_PRODUCTS_DIR; };
6003F58D195388D20070C39A /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
6003F58F195388D20070C39A /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };
@@ -222,20 +355,40 @@
6003F5AF195388D20070C39A /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; };
6003F5B7195388D20070C39A /* Tests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Tests-Info.plist"; sourceTree = "<group>"; };
6003F5B9195388D20070C39A /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
- 61E1D8AF1FCF6AF500753285 /* StringViewTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = StringViewTests.mm; sourceTree = "<group>"; };
69F6A10DBD6187489481CD76 /* Pods_Firestore_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Firestore_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
71719F9E1E33DC2100824A3D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
+ 7346E61C20325C6900FD6CEF /* FSTDispatchQueueTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTDispatchQueueTests.mm; sourceTree = "<group>"; };
75A6FE51C1A02DF38F62FAAD /* Pods_Firestore_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Firestore_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; };
873B8AEA1B1F5CCA007FD442 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = Main.storyboard; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
8E002F4AD5D9B6197C940847 /* Firestore.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = Firestore.podspec; path = ../Firestore.podspec; sourceTree = "<group>"; };
9D52E67EE96AA7E5D6F69748 /* Pods-Firestore_IntegrationTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_IntegrationTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_IntegrationTests/Pods-Firestore_IntegrationTests.debug.xcconfig"; sourceTree = "<group>"; };
9EF477AD4B2B643FD320867A /* Pods-Firestore_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Example.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Example/Pods-Firestore_Example.debug.xcconfig"; sourceTree = "<group>"; };
+ AB356EF6200EA5EB0089B766 /* field_value_test.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = field_value_test.cc; sourceTree = "<group>"; };
+ AB380CF82019382300D97691 /* target_id_generator_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = target_id_generator_test.cc; sourceTree = "<group>"; };
+ AB380CFC201A2EE200D97691 /* string_util_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = string_util_test.cc; path = ../../core/test/firebase/firestore/util/string_util_test.cc; sourceTree = "<group>"; };
+ AB380D01201BC69F00D97691 /* bits_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = bits_test.cc; path = ../../core/test/firebase/firestore/util/bits_test.cc; sourceTree = "<group>"; };
+ AB380D03201BC6E400D97691 /* ordered_code_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ordered_code_test.cc; path = ../../core/test/firebase/firestore/util/ordered_code_test.cc; sourceTree = "<group>"; };
+ AB38D92E20235D22000A432D /* database_info_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = database_info_test.cc; sourceTree = "<group>"; };
+ AB38D93220239654000A432D /* user_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = user_test.cc; sourceTree = "<group>"; };
+ AB38D9342023966E000A432D /* credentials_provider_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = credentials_provider_test.cc; sourceTree = "<group>"; };
+ AB38D93620239689000A432D /* empty_credentials_provider_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = empty_credentials_provider_test.cc; sourceTree = "<group>"; };
+ AB6B908320322E4D00CC290A /* document_test.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = document_test.cc; sourceTree = "<group>"; };
+ AB6B908520322E6D00CC290A /* maybe_document_test.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = maybe_document_test.cc; sourceTree = "<group>"; };
+ AB6B908720322E8800CC290A /* no_document_test.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = no_document_test.cc; sourceTree = "<group>"; };
+ AB71064B201FA60300344F18 /* database_id_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = database_id_test.cc; sourceTree = "<group>"; };
+ AB7BAB332012B519001E0872 /* geo_point_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = geo_point_test.cc; path = ../../core/test/firebase/firestore/geo_point_test.cc; sourceTree = "<group>"; };
+ ABA495B9202B7E79008A7851 /* snapshot_version_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = snapshot_version_test.cc; sourceTree = "<group>"; };
+ ABC1D7DF2023A3EF00BA84F0 /* token_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = token_test.cc; sourceTree = "<group>"; };
+ ABC1D7E22023CDC500BA84F0 /* firebase_credentials_provider_test.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = firebase_credentials_provider_test.mm; sourceTree = "<group>"; };
+ ABF6506B201131F8005F2C74 /* timestamp_test.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = timestamp_test.cc; path = ../../core/test/firebase/firestore/timestamp_test.cc; sourceTree = "<group>"; };
B2FA635DF5D116A67A7441CD /* Pods_Firestore_IntegrationTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Firestore_IntegrationTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ B6152AD5202A5385000E5744 /* document_key_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = document_key_test.cc; sourceTree = "<group>"; };
+ B65D34A7203C99090076A5E1 /* FIRTimestampTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRTimestampTest.m; sourceTree = "<group>"; };
+ B686F2AD2023DDB20028D6BE /* field_path_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = field_path_test.cc; sourceTree = "<group>"; };
+ B686F2B02024FFD70028D6BE /* resource_path_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = resource_path_test.cc; sourceTree = "<group>"; };
+ C1D89E5405935366C88CC3E5 /* Pods-Firestore_Example-Firestore_SwiftTests_iOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Example-Firestore_SwiftTests_iOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Example-Firestore_SwiftTests_iOS/Pods-Firestore_Example-Firestore_SwiftTests_iOS.release.xcconfig"; sourceTree = "<group>"; };
CE00BABB5A3AAB44A4C209E2 /* Pods-Firestore_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Tests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Tests/Pods-Firestore_Tests.debug.xcconfig"; sourceTree = "<group>"; };
D3CC3DC5338DCAF43A211155 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = "<group>"; };
- D5B25292CED31B81FDED0411 /* FSTTestDispatchQueue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FSTTestDispatchQueue.m; sourceTree = "<group>"; };
- D5B259DAA9149B80D6245B57 /* FSTTestDispatchQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FSTTestDispatchQueue.h; sourceTree = "<group>"; };
- D5B25C0D4AADFCA3ADB883E4 /* FSTStreamTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FSTStreamTests.m; sourceTree = "<group>"; };
DB17FEDFB80770611A935A60 /* Pods-Firestore_IntegrationTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_IntegrationTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_IntegrationTests/Pods-Firestore_IntegrationTests.release.xcconfig"; sourceTree = "<group>"; };
DE03B2E91F2149D600A30B9C /* Firestore_IntegrationTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Firestore_IntegrationTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
DE03B3621F215E1600A30B9C /* CAcert.pem */ = {isa = PBXFileReference; lastKnownFileType = text; path = CAcert.pem; sourceTree = "<group>"; };
@@ -248,81 +401,23 @@
DE2EF0821F3D0B6E003D0CDC /* FSTImmutableSortedSet+Testing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "FSTImmutableSortedSet+Testing.m"; path = "../../third_party/Immutable/Tests/FSTImmutableSortedSet+Testing.m"; sourceTree = "<group>"; };
DE2EF0831F3D0B6E003D0CDC /* FSTLLRBValueNode+Test.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "FSTLLRBValueNode+Test.h"; path = "../../third_party/Immutable/Tests/FSTLLRBValueNode+Test.h"; sourceTree = "<group>"; };
DE2EF0841F3D0B6E003D0CDC /* FSTTreeSortedDictionaryTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FSTTreeSortedDictionaryTests.m; path = ../../third_party/Immutable/Tests/FSTTreeSortedDictionaryTests.m; sourceTree = "<group>"; };
- DE51B1631F0D48AC0013853F /* FSTEagerGarbageCollectorTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FSTEagerGarbageCollectorTests.m; sourceTree = "<group>"; };
- DE51B1641F0D48AC0013853F /* FSTLevelDBKeyTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTLevelDBKeyTests.mm; sourceTree = "<group>"; };
- DE51B1651F0D48AC0013853F /* FSTLevelDBLocalStoreTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FSTLevelDBLocalStoreTests.m; sourceTree = "<group>"; };
- DE51B1661F0D48AC0013853F /* FSTLevelDBMutationQueueTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTLevelDBMutationQueueTests.mm; sourceTree = "<group>"; };
- DE51B1671F0D48AC0013853F /* FSTLevelDBQueryCacheTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FSTLevelDBQueryCacheTests.m; sourceTree = "<group>"; };
- DE51B1681F0D48AC0013853F /* FSTLevelDBRemoteDocumentCacheTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTLevelDBRemoteDocumentCacheTests.mm; sourceTree = "<group>"; };
- DE51B1691F0D48AC0013853F /* FSTLocalSerializerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FSTLocalSerializerTests.m; sourceTree = "<group>"; };
- DE51B16A1F0D48AC0013853F /* FSTLocalStoreTests.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FSTLocalStoreTests.h; sourceTree = "<group>"; };
- DE51B16B1F0D48AC0013853F /* FSTLocalStoreTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FSTLocalStoreTests.m; sourceTree = "<group>"; };
- DE51B16C1F0D48AC0013853F /* FSTMemoryLocalStoreTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FSTMemoryLocalStoreTests.m; sourceTree = "<group>"; };
- DE51B16D1F0D48AC0013853F /* FSTMemoryMutationQueueTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FSTMemoryMutationQueueTests.m; sourceTree = "<group>"; };
- DE51B16E1F0D48AC0013853F /* FSTMemoryQueryCacheTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FSTMemoryQueryCacheTests.m; sourceTree = "<group>"; };
- DE51B16F1F0D48AC0013853F /* FSTMemoryRemoteDocumentCacheTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FSTMemoryRemoteDocumentCacheTests.m; sourceTree = "<group>"; };
- DE51B1701F0D48AC0013853F /* FSTMutationQueueTests.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FSTMutationQueueTests.h; sourceTree = "<group>"; };
- DE51B1711F0D48AC0013853F /* FSTMutationQueueTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FSTMutationQueueTests.m; sourceTree = "<group>"; };
- DE51B1721F0D48AC0013853F /* FSTPersistenceTestHelpers.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FSTPersistenceTestHelpers.h; sourceTree = "<group>"; };
- DE51B1731F0D48AC0013853F /* FSTPersistenceTestHelpers.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FSTPersistenceTestHelpers.m; sourceTree = "<group>"; };
- DE51B1741F0D48AC0013853F /* FSTQueryCacheTests.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FSTQueryCacheTests.h; sourceTree = "<group>"; };
- DE51B1751F0D48AC0013853F /* FSTQueryCacheTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FSTQueryCacheTests.m; sourceTree = "<group>"; };
- DE51B1761F0D48AC0013853F /* FSTReferenceSetTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FSTReferenceSetTests.m; sourceTree = "<group>"; };
- DE51B1771F0D48AC0013853F /* FSTRemoteDocumentCacheTests.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FSTRemoteDocumentCacheTests.h; sourceTree = "<group>"; };
- DE51B1781F0D48AC0013853F /* FSTRemoteDocumentCacheTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FSTRemoteDocumentCacheTests.m; sourceTree = "<group>"; };
- DE51B1791F0D48AC0013853F /* FSTRemoteDocumentChangeBufferTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FSTRemoteDocumentChangeBufferTests.m; sourceTree = "<group>"; };
- DE51B17A1F0D48AC0013853F /* FSTWriteGroupTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTWriteGroupTests.mm; sourceTree = "<group>"; };
- DE51B17C1F0D48AC0013853F /* FSTDatabaseIDTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FSTDatabaseIDTests.m; sourceTree = "<group>"; };
- DE51B17D1F0D48AC0013853F /* FSTDocumentKeyTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FSTDocumentKeyTests.m; sourceTree = "<group>"; };
- DE51B17E1F0D48AC0013853F /* FSTDocumentSetTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FSTDocumentSetTests.m; sourceTree = "<group>"; };
- DE51B17F1F0D48AC0013853F /* FSTDocumentTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FSTDocumentTests.m; sourceTree = "<group>"; };
- DE51B1801F0D48AC0013853F /* FSTFieldValueTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FSTFieldValueTests.m; sourceTree = "<group>"; };
- DE51B1811F0D48AC0013853F /* FSTMutationTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FSTMutationTests.m; sourceTree = "<group>"; };
- DE51B1821F0D48AC0013853F /* FSTPathTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FSTPathTests.m; sourceTree = "<group>"; };
- DE51B1841F0D48AC0013853F /* FIRGeoPointTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FIRGeoPointTests.m; sourceTree = "<group>"; };
- DE51B1861F0D48AC0013853F /* FSTAssertTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FSTAssertTests.m; sourceTree = "<group>"; };
- DE51B1871F0D48AC0013853F /* FSTComparisonTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FSTComparisonTests.m; sourceTree = "<group>"; };
DE51B1881F0D48AC0013853F /* FSTHelpers.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FSTHelpers.h; sourceTree = "<group>"; };
- DE51B1891F0D48AC0013853F /* FSTHelpers.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FSTHelpers.m; sourceTree = "<group>"; };
- DE51B1941F0D48AC0013853F /* FSTLevelDBSpecTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FSTLevelDBSpecTests.m; sourceTree = "<group>"; };
- DE51B1951F0D48AC0013853F /* FSTMemorySpecTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FSTMemorySpecTests.m; sourceTree = "<group>"; };
DE51B1961F0D48AC0013853F /* FSTMockDatastore.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FSTMockDatastore.h; sourceTree = "<group>"; };
- DE51B1971F0D48AC0013853F /* FSTMockDatastore.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FSTMockDatastore.m; sourceTree = "<group>"; };
DE51B1981F0D48AC0013853F /* FSTSpecTests.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FSTSpecTests.h; sourceTree = "<group>"; };
- DE51B1991F0D48AC0013853F /* FSTSpecTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FSTSpecTests.m; sourceTree = "<group>"; };
DE51B19A1F0D48AC0013853F /* FSTSyncEngineTestDriver.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FSTSyncEngineTestDriver.h; sourceTree = "<group>"; };
- DE51B19B1F0D48AC0013853F /* FSTSyncEngineTestDriver.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FSTSyncEngineTestDriver.m; sourceTree = "<group>"; };
DE51B1A71F0D48AC0013853F /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
- DE51B1A91F0D48AC0013853F /* FSTDatabaseInfoTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FSTDatabaseInfoTests.m; sourceTree = "<group>"; };
- DE51B1AA1F0D48AC0013853F /* FSTEventManagerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FSTEventManagerTests.m; sourceTree = "<group>"; };
- DE51B1AB1F0D48AC0013853F /* FSTQueryListenerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FSTQueryListenerTests.m; sourceTree = "<group>"; };
- DE51B1AC1F0D48AC0013853F /* FSTQueryTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FSTQueryTests.m; sourceTree = "<group>"; };
- DE51B1AD1F0D48AC0013853F /* FSTSyncEngine+Testing.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "FSTSyncEngine+Testing.h"; sourceTree = "<group>"; };
- DE51B1AE1F0D48AC0013853F /* FSTTargetIDGeneratorTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FSTTargetIDGeneratorTests.m; sourceTree = "<group>"; };
- DE51B1AF1F0D48AC0013853F /* FSTTimestampTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FSTTimestampTests.m; sourceTree = "<group>"; };
- DE51B1B01F0D48AC0013853F /* FSTViewSnapshotTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FSTViewSnapshotTest.m; sourceTree = "<group>"; };
- DE51B1B11F0D48AC0013853F /* FSTViewTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FSTViewTests.m; sourceTree = "<group>"; };
- DE51B1B31F0D48AC0013853F /* FSTDatastoreTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FSTDatastoreTests.m; sourceTree = "<group>"; };
- DE51B1B41F0D48AC0013853F /* FSTRemoteEventTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FSTRemoteEventTests.m; sourceTree = "<group>"; };
- DE51B1B61F0D48AC0013853F /* FSTSerializerBetaTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FSTSerializerBetaTests.m; sourceTree = "<group>"; };
- DE51B1B81F0D48AC0013853F /* FSTWatchChange+Testing.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "FSTWatchChange+Testing.h"; sourceTree = "<group>"; };
- DE51B1B91F0D48AC0013853F /* FSTWatchChange+Testing.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "FSTWatchChange+Testing.m"; sourceTree = "<group>"; };
- DE51B1BA1F0D48AC0013853F /* FSTWatchChangeTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FSTWatchChangeTests.m; sourceTree = "<group>"; };
- DE51B1BD1F0D48AC0013853F /* FIRCursorTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FIRCursorTests.m; sourceTree = "<group>"; };
- DE51B1BE1F0D48AC0013853F /* FIRDatabaseTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FIRDatabaseTests.m; sourceTree = "<group>"; };
- DE51B1BF1F0D48AC0013853F /* FIRFieldsTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FIRFieldsTests.m; sourceTree = "<group>"; };
- DE51B1C01F0D48AC0013853F /* FIRListenerRegistrationTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FIRListenerRegistrationTests.m; sourceTree = "<group>"; };
- DE51B1C11F0D48AC0013853F /* FIRQueryTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FIRQueryTests.m; sourceTree = "<group>"; };
- DE51B1C21F0D48AC0013853F /* FIRServerTimestampTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FIRServerTimestampTests.m; sourceTree = "<group>"; };
- DE51B1C31F0D48AC0013853F /* FIRTypeTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FIRTypeTests.m; sourceTree = "<group>"; };
- DE51B1C41F0D48AC0013853F /* FSTDatastoreTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FSTDatastoreTests.m; sourceTree = "<group>"; };
- DE51B1C51F0D48AC0013853F /* FSTSmokeTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FSTSmokeTests.m; sourceTree = "<group>"; };
- DE51B1C61F0D48AC0013853F /* FSTTransactionTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FSTTransactionTests.m; sourceTree = "<group>"; };
- DEFE0F471F1F960A0071599A /* FIRWriteBatchTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRWriteBatchTests.m; sourceTree = "<group>"; };
F23325524BEAF8D24F78AC88 /* Pods-SwiftBuildTest.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwiftBuildTest.release.xcconfig"; path = "Pods/Target Support Files/Pods-SwiftBuildTest/Pods-SwiftBuildTest.release.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
+ 54C9EDEE2040E16300A969CD /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ DEF43C59D90C3CF3CA34DDF4 /* Pods_Firestore_Example_Firestore_SwiftTests_iOS.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
6003F587195388D20070C39A /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
@@ -368,11 +463,27 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
+ 5467FB05203E652F009C9584 /* testutil */ = {
+ isa = PBXGroup;
+ children = (
+ 5467FB06203E6A44009C9584 /* app_testing.h */,
+ 5467FB07203E6A44009C9584 /* app_testing.mm */,
+ );
+ name = testutil;
+ sourceTree = "<group>";
+ };
54740A561FC913EB00713A1A /* util */ = {
isa = PBXGroup;
children = (
+ 548DB926200D590300E00ABC /* assert_test.cc */,
54740A521FC913E500713A1A /* autoid_test.cc */,
+ AB380D01201BC69F00D97691 /* bits_test.cc */,
+ 548DB928200D59F600E00ABC /* comparison_test.cc */,
+ 54C2294E1FECABAE007D065B /* log_test.cc */,
+ AB380D03201BC6E400D97691 /* ordered_code_test.cc */,
54740A531FC913E500713A1A /* secure_random_test.cc */,
+ 5436F32320008FAD006E51E3 /* string_printf_test.cc */,
+ AB380CFC201A2EE200D97691 /* string_util_test.cc */,
);
name = util;
sourceTree = "<group>";
@@ -380,19 +491,53 @@
54764FAC1FAA0C390085E60A /* GoogleTests */ = {
isa = PBXGroup;
children = (
- 54764FAD1FAA0C650085E60A /* Port */,
+ AB38D9312023962A000A432D /* auth */,
+ AB380CF7201937B800D97691 /* core */,
+ 54EB764B202277970088B8F3 /* immutable */,
+ 54995F70205B6E1A004EFFA0 /* local */,
+ AB356EF5200E9D1A0089B766 /* model */,
+ 5467FB05203E652F009C9584 /* testutil */,
54740A561FC913EB00713A1A /* util */,
54764FAE1FAA21B90085E60A /* FSTGoogleTestTests.mm */,
+ AB7BAB332012B519001E0872 /* geo_point_test.cc */,
+ ABF6506B201131F8005F2C74 /* timestamp_test.cc */,
);
name = GoogleTests;
sourceTree = "<group>";
};
- 54764FAD1FAA0C650085E60A /* Port */ = {
+ 5495EB012040E90200EBA509 /* Codable */ = {
+ isa = PBXGroup;
+ children = (
+ 5495EB022040E90200EBA509 /* CodableGeoPointTests.swift */,
+ );
+ path = Codable;
+ sourceTree = "<group>";
+ };
+ 54995F70205B6E1A004EFFA0 /* local */ = {
isa = PBXGroup;
children = (
- 54764FAA1FAA0C320085E60A /* string_util_test.cc */,
+ 54995F6E205B6E12004EFFA0 /* leveldb_key_test.cc */,
);
- name = Port;
+ name = local;
+ sourceTree = "<group>";
+ };
+ 54C9EDF22040E16300A969CD /* SwiftTests */ = {
+ isa = PBXGroup;
+ children = (
+ 5495EB012040E90200EBA509 /* Codable */,
+ 54C9EDF52040E16300A969CD /* Info.plist */,
+ );
+ name = SwiftTests;
+ path = ../Swift/Tests;
+ sourceTree = "<group>";
+ };
+ 54EB764B202277970088B8F3 /* immutable */ = {
+ isa = PBXGroup;
+ children = (
+ 54EB764C202277B30088B8F3 /* array_sorted_map_test.cc */,
+ );
+ name = immutable;
+ path = ../../core/test/firebase/firestore/core/immutable;
sourceTree = "<group>";
};
6003F581195388D10070C39A = {
@@ -401,6 +546,7 @@
60FF7A9C1954A5C5007DD14C /* Podspec Metadata */,
6003F593195388D20070C39A /* Example for Firestore */,
6003F5B5195388D20070C39A /* Tests */,
+ 54C9EDF22040E16300A969CD /* SwiftTests */,
DE0761E51F2FE611003233AF /* SwiftBuildTest */,
6003F58C195388D20070C39A /* Frameworks */,
6003F58B195388D20070C39A /* Products */,
@@ -415,6 +561,7 @@
6003F5AE195388D20070C39A /* Firestore_Tests.xctest */,
DE03B2E91F2149D600A30B9C /* Firestore_IntegrationTests.xctest */,
DE0761E41F2FE611003233AF /* SwiftBuildTest.app */,
+ 54C9EDF12040E16300A969CD /* Firestore_SwiftTests_iOS.xctest */,
);
name = Products;
sourceTree = "<group>";
@@ -430,6 +577,7 @@
69F6A10DBD6187489481CD76 /* Pods_Firestore_Tests.framework */,
B2FA635DF5D116A67A7441CD /* Pods_Firestore_IntegrationTests.framework */,
32AD40BF6B0E849B07FFD05E /* Pods_SwiftBuildTest.framework */,
+ 245812330F6A31632BB4B623 /* Pods_Firestore_Example_Firestore_SwiftTests_iOS.framework */,
);
name = Frameworks;
sourceTree = "<group>";
@@ -508,10 +656,52 @@
04DF37A117F88A9891379ED6 /* Pods-Firestore_Tests.release.xcconfig */,
42491D7DC8C8CD245CC22B93 /* Pods-SwiftBuildTest.debug.xcconfig */,
F23325524BEAF8D24F78AC88 /* Pods-SwiftBuildTest.release.xcconfig */,
+ 3F422FFBDA6E79396E2FB594 /* Pods-Firestore_Example-Firestore_SwiftTests_iOS.debug.xcconfig */,
+ C1D89E5405935366C88CC3E5 /* Pods-Firestore_Example-Firestore_SwiftTests_iOS.release.xcconfig */,
);
name = Pods;
sourceTree = "<group>";
};
+ AB356EF5200E9D1A0089B766 /* model */ = {
+ isa = PBXGroup;
+ children = (
+ B6152AD5202A5385000E5744 /* document_key_test.cc */,
+ AB6B908320322E4D00CC290A /* document_test.cc */,
+ B686F2B02024FFD70028D6BE /* resource_path_test.cc */,
+ B686F2AD2023DDB20028D6BE /* field_path_test.cc */,
+ AB71064B201FA60300344F18 /* database_id_test.cc */,
+ AB356EF6200EA5EB0089B766 /* field_value_test.cc */,
+ AB6B908520322E6D00CC290A /* maybe_document_test.cc */,
+ AB6B908720322E8800CC290A /* no_document_test.cc */,
+ ABA495B9202B7E79008A7851 /* snapshot_version_test.cc */,
+ );
+ name = model;
+ path = ../../core/test/firebase/firestore/model;
+ sourceTree = "<group>";
+ };
+ AB380CF7201937B800D97691 /* core */ = {
+ isa = PBXGroup;
+ children = (
+ AB380CF82019382300D97691 /* target_id_generator_test.cc */,
+ AB38D92E20235D22000A432D /* database_info_test.cc */,
+ );
+ name = core;
+ path = ../../core/test/firebase/firestore/core;
+ sourceTree = "<group>";
+ };
+ AB38D9312023962A000A432D /* auth */ = {
+ isa = PBXGroup;
+ children = (
+ AB38D9342023966E000A432D /* credentials_provider_test.cc */,
+ AB38D93620239689000A432D /* empty_credentials_provider_test.cc */,
+ ABC1D7E22023CDC500BA84F0 /* firebase_credentials_provider_test.mm */,
+ ABC1D7DF2023A3EF00BA84F0 /* token_test.cc */,
+ AB38D93220239654000A432D /* user_test.cc */,
+ );
+ name = auth;
+ path = ../../core/test/firebase/firestore/auth;
+ sourceTree = "<group>";
+ };
DE0761E51F2FE611003233AF /* SwiftBuildTest */ = {
isa = PBXGroup;
children = (
@@ -537,31 +727,31 @@
DE51B1621F0D48AC0013853F /* Local */ = {
isa = PBXGroup;
children = (
- 61E1D8AF1FCF6AF500753285 /* StringViewTests.mm */,
- DE51B16A1F0D48AC0013853F /* FSTLocalStoreTests.h */,
- DE51B1701F0D48AC0013853F /* FSTMutationQueueTests.h */,
- DE51B1721F0D48AC0013853F /* FSTPersistenceTestHelpers.h */,
- DE51B1741F0D48AC0013853F /* FSTQueryCacheTests.h */,
- DE51B1771F0D48AC0013853F /* FSTRemoteDocumentCacheTests.h */,
- DE51B1631F0D48AC0013853F /* FSTEagerGarbageCollectorTests.m */,
- DE51B1651F0D48AC0013853F /* FSTLevelDBLocalStoreTests.m */,
- DE51B1671F0D48AC0013853F /* FSTLevelDBQueryCacheTests.m */,
- DE51B1691F0D48AC0013853F /* FSTLocalSerializerTests.m */,
- DE51B16B1F0D48AC0013853F /* FSTLocalStoreTests.m */,
- DE51B16C1F0D48AC0013853F /* FSTMemoryLocalStoreTests.m */,
- DE51B16D1F0D48AC0013853F /* FSTMemoryMutationQueueTests.m */,
- DE51B16E1F0D48AC0013853F /* FSTMemoryQueryCacheTests.m */,
- DE51B16F1F0D48AC0013853F /* FSTMemoryRemoteDocumentCacheTests.m */,
- DE51B1711F0D48AC0013853F /* FSTMutationQueueTests.m */,
- DE51B1731F0D48AC0013853F /* FSTPersistenceTestHelpers.m */,
- DE51B1751F0D48AC0013853F /* FSTQueryCacheTests.m */,
- DE51B1761F0D48AC0013853F /* FSTReferenceSetTests.m */,
- DE51B1781F0D48AC0013853F /* FSTRemoteDocumentCacheTests.m */,
- DE51B1791F0D48AC0013853F /* FSTRemoteDocumentChangeBufferTests.m */,
- DE51B1641F0D48AC0013853F /* FSTLevelDBKeyTests.mm */,
- DE51B1661F0D48AC0013853F /* FSTLevelDBMutationQueueTests.mm */,
- DE51B1681F0D48AC0013853F /* FSTLevelDBRemoteDocumentCacheTests.mm */,
- DE51B17A1F0D48AC0013853F /* FSTWriteGroupTests.mm */,
+ 5492E0842021552A00B64F25 /* FSTEagerGarbageCollectorTests.mm */,
+ 5492E08E2021552B00B64F25 /* FSTLevelDBKeyTests.mm */,
+ 5492E08F2021552B00B64F25 /* FSTLevelDBLocalStoreTests.mm */,
+ 5492E0862021552A00B64F25 /* FSTLevelDBMigrationsTests.mm */,
+ 5492E0872021552A00B64F25 /* FSTLevelDBMutationQueueTests.mm */,
+ 5492E0982021552C00B64F25 /* FSTLevelDBQueryCacheTests.mm */,
+ 5492E0922021552B00B64F25 /* FSTLevelDBRemoteDocumentCacheTests.mm */,
+ 5492E08A2021552A00B64F25 /* FSTLocalSerializerTests.mm */,
+ 5492E0912021552B00B64F25 /* FSTLocalStoreTests.h */,
+ 5492E0832021552A00B64F25 /* FSTLocalStoreTests.mm */,
+ 5492E0882021552A00B64F25 /* FSTMemoryLocalStoreTests.mm */,
+ 5492E0972021552C00B64F25 /* FSTMemoryMutationQueueTests.mm */,
+ 5492E08B2021552B00B64F25 /* FSTMemoryQueryCacheTests.mm */,
+ 5492E08C2021552B00B64F25 /* FSTMemoryRemoteDocumentCacheTests.mm */,
+ 5492E0942021552C00B64F25 /* FSTMutationQueueTests.h */,
+ 5492E0962021552C00B64F25 /* FSTMutationQueueTests.mm */,
+ 5492E0992021552C00B64F25 /* FSTPersistenceTestHelpers.h */,
+ 5492E08D2021552B00B64F25 /* FSTPersistenceTestHelpers.mm */,
+ 5492E0952021552C00B64F25 /* FSTQueryCacheTests.h */,
+ 5492E0892021552A00B64F25 /* FSTQueryCacheTests.mm */,
+ 5492E09A2021552C00B64F25 /* FSTReferenceSetTests.mm */,
+ 5492E0852021552A00B64F25 /* FSTRemoteDocumentCacheTests.h */,
+ 5492E09C2021552D00B64F25 /* FSTRemoteDocumentCacheTests.mm */,
+ 5492E0932021552B00B64F25 /* StringViewTests.mm */,
+ 132E36BB104830BD806351AC /* FSTLevelDBTransactionTests.mm */,
);
path = Local;
sourceTree = "<group>";
@@ -569,13 +759,11 @@
DE51B17B1F0D48AC0013853F /* Model */ = {
isa = PBXGroup;
children = (
- DE51B17C1F0D48AC0013853F /* FSTDatabaseIDTests.m */,
- DE51B17D1F0D48AC0013853F /* FSTDocumentKeyTests.m */,
- DE51B17E1F0D48AC0013853F /* FSTDocumentSetTests.m */,
- DE51B17F1F0D48AC0013853F /* FSTDocumentTests.m */,
- DE51B1801F0D48AC0013853F /* FSTFieldValueTests.m */,
- DE51B1811F0D48AC0013853F /* FSTMutationTests.m */,
- DE51B1821F0D48AC0013853F /* FSTPathTests.m */,
+ 5492E0B22021555000B64F25 /* FSTDocumentKeyTests.mm */,
+ 5492E0B32021555100B64F25 /* FSTDocumentSetTests.mm */,
+ 5492E0B62021555100B64F25 /* FSTDocumentTests.mm */,
+ 5492E0B82021555100B64F25 /* FSTFieldValueTests.mm */,
+ 5492E0B72021555100B64F25 /* FSTMutationTests.mm */,
);
path = Model;
sourceTree = "<group>";
@@ -583,7 +771,19 @@
DE51B1831F0D48AC0013853F /* API */ = {
isa = PBXGroup;
children = (
- DE51B1841F0D48AC0013853F /* FIRGeoPointTests.m */,
+ 5492E045202154AA00B64F25 /* FIRCollectionReferenceTests.mm */,
+ 5492E049202154AA00B64F25 /* FIRDocumentReferenceTests.mm */,
+ 5492E04B202154AA00B64F25 /* FIRDocumentSnapshotTests.mm */,
+ 5492E04C202154AA00B64F25 /* FIRFieldPathTests.mm */,
+ 5492E04A202154AA00B64F25 /* FIRFieldValueTests.mm */,
+ 5467FAFF203E56F8009C9584 /* FIRFirestoreTests.mm */,
+ 5492E048202154AA00B64F25 /* FIRGeoPointTests.mm */,
+ 5492E04F202154AA00B64F25 /* FIRQuerySnapshotTests.mm */,
+ 5492E046202154AA00B64F25 /* FIRQueryTests.mm */,
+ 5492E04D202154AA00B64F25 /* FIRSnapshotMetadataTests.mm */,
+ B65D34A7203C99090076A5E1 /* FIRTimestampTest.m */,
+ 5492E047202154AA00B64F25 /* FSTAPIHelpers.h */,
+ 5492E04E202154AA00B64F25 /* FSTAPIHelpers.mm */,
);
path = API;
sourceTree = "<group>";
@@ -591,18 +791,16 @@
DE51B1851F0D48AC0013853F /* Util */ = {
isa = PBXGroup;
children = (
+ 5492E0382021401E00B64F25 /* FSTAssertTests.mm */,
54E9281C1F33950B00C1953E /* FSTEventAccumulator.h */,
- 54E9281D1F33950B00C1953E /* FSTEventAccumulator.m */,
+ 5492E0392021401F00B64F25 /* FSTEventAccumulator.mm */,
+ DE51B1881F0D48AC0013853F /* FSTHelpers.h */,
+ 5492E03A2021401F00B64F25 /* FSTHelpers.mm */,
54E9281E1F33950B00C1953E /* FSTIntegrationTestCase.h */,
5491BC711FB44593008B3588 /* FSTIntegrationTestCase.mm */,
- DE51B1861F0D48AC0013853F /* FSTAssertTests.m */,
- DE51B1871F0D48AC0013853F /* FSTComparisonTests.m */,
- DE51B1881F0D48AC0013853F /* FSTHelpers.h */,
- DE51B1891F0D48AC0013853F /* FSTHelpers.m */,
54E9282A1F339CAD00C1953E /* XCTestCase+Await.h */,
- 54E9282B1F339CAD00C1953E /* XCTestCase+Await.m */,
- D5B259DAA9149B80D6245B57 /* FSTTestDispatchQueue.h */,
- D5B25292CED31B81FDED0411 /* FSTTestDispatchQueue.m */,
+ 5492E0372021401E00B64F25 /* XCTestCase+Await.mm */,
+ 7346E61C20325C6900FD6CEF /* FSTDispatchQueueTests.mm */,
);
path = Util;
sourceTree = "<group>";
@@ -610,14 +808,14 @@
DE51B1931F0D48AC0013853F /* SpecTests */ = {
isa = PBXGroup;
children = (
+ 5492E02C20213FFB00B64F25 /* FSTLevelDBSpecTests.mm */,
+ 5492E02F20213FFC00B64F25 /* FSTMemorySpecTests.mm */,
DE51B1961F0D48AC0013853F /* FSTMockDatastore.h */,
+ 5492E02D20213FFC00B64F25 /* FSTMockDatastore.mm */,
DE51B1981F0D48AC0013853F /* FSTSpecTests.h */,
+ 5492E03020213FFC00B64F25 /* FSTSpecTests.mm */,
DE51B19A1F0D48AC0013853F /* FSTSyncEngineTestDriver.h */,
- DE51B1941F0D48AC0013853F /* FSTLevelDBSpecTests.m */,
- DE51B1951F0D48AC0013853F /* FSTMemorySpecTests.m */,
- DE51B1971F0D48AC0013853F /* FSTMockDatastore.m */,
- DE51B1991F0D48AC0013853F /* FSTSpecTests.m */,
- DE51B19B1F0D48AC0013853F /* FSTSyncEngineTestDriver.m */,
+ 5492E02E20213FFC00B64F25 /* FSTSyncEngineTestDriver.mm */,
DE51B19C1F0D48AC0013853F /* json */,
);
path = SpecTests;
@@ -645,15 +843,12 @@
DE51B1A81F0D48AC0013853F /* Core */ = {
isa = PBXGroup;
children = (
- DE51B1AD1F0D48AC0013853F /* FSTSyncEngine+Testing.h */,
- DE51B1A91F0D48AC0013853F /* FSTDatabaseInfoTests.m */,
- DE51B1AA1F0D48AC0013853F /* FSTEventManagerTests.m */,
- DE51B1AB1F0D48AC0013853F /* FSTQueryListenerTests.m */,
- DE51B1AC1F0D48AC0013853F /* FSTQueryTests.m */,
- DE51B1AE1F0D48AC0013853F /* FSTTargetIDGeneratorTests.m */,
- DE51B1AF1F0D48AC0013853F /* FSTTimestampTests.m */,
- DE51B1B01F0D48AC0013853F /* FSTViewSnapshotTest.m */,
- DE51B1B11F0D48AC0013853F /* FSTViewTests.m */,
+ 5492E060202154B900B64F25 /* FSTEventManagerTests.mm */,
+ 5492E05D202154B900B64F25 /* FSTQueryListenerTests.mm */,
+ 5492E061202154B900B64F25 /* FSTQueryTests.mm */,
+ 5492E05A202154B800B64F25 /* FSTSyncEngine+Testing.h */,
+ 5492E05C202154B800B64F25 /* FSTViewSnapshotTest.mm */,
+ 5492E05E202154B900B64F25 /* FSTViewTests.mm */,
);
path = Core;
sourceTree = "<group>";
@@ -661,12 +856,12 @@
DE51B1B21F0D48AC0013853F /* Remote */ = {
isa = PBXGroup;
children = (
- DE51B1B31F0D48AC0013853F /* FSTDatastoreTests.m */,
- DE51B1B41F0D48AC0013853F /* FSTRemoteEventTests.m */,
- DE51B1B61F0D48AC0013853F /* FSTSerializerBetaTests.m */,
- DE51B1B81F0D48AC0013853F /* FSTWatchChange+Testing.h */,
- DE51B1B91F0D48AC0013853F /* FSTWatchChange+Testing.m */,
- DE51B1BA1F0D48AC0013853F /* FSTWatchChangeTests.m */,
+ 5492E0C22021557E00B64F25 /* FSTDatastoreTests.mm */,
+ 5492E0C32021557E00B64F25 /* FSTRemoteEventTests.mm */,
+ 5492E0C12021557E00B64F25 /* FSTSerializerBetaTests.mm */,
+ 5492E0C42021557E00B64F25 /* FSTWatchChange+Testing.h */,
+ 5492E0C02021557E00B64F25 /* FSTWatchChange+Testing.mm */,
+ 5492E0C52021557E00B64F25 /* FSTWatchChangeTests.mm */,
);
path = Remote;
sourceTree = "<group>";
@@ -674,13 +869,12 @@
DE51B1BB1F0D48AC0013853F /* Integration */ = {
isa = PBXGroup;
children = (
- DE03B3621F215E1600A30B9C /* CAcert.pem */,
DE51B1BC1F0D48AC0013853F /* API */,
- DE51B1C41F0D48AC0013853F /* FSTDatastoreTests.m */,
- DE51B1C51F0D48AC0013853F /* FSTSmokeTests.m */,
- DE51B1C61F0D48AC0013853F /* FSTTransactionTests.m */,
- DE51B1C71F0D48AC0013853F /* Util */,
- D5B25C0D4AADFCA3ADB883E4 /* FSTStreamTests.m */,
+ DE03B3621F215E1600A30B9C /* CAcert.pem */,
+ 5492E07E202154EC00B64F25 /* FSTDatastoreTests.mm */,
+ 5492E07C202154EB00B64F25 /* FSTSmokeTests.mm */,
+ 5492E07D202154EB00B64F25 /* FSTStreamTests.mm */,
+ 5492E07B202154EB00B64F25 /* FSTTransactionTests.mm */,
);
path = Integration;
sourceTree = "<group>";
@@ -688,29 +882,43 @@
DE51B1BC1F0D48AC0013853F /* API */ = {
isa = PBXGroup;
children = (
- DE51B1BD1F0D48AC0013853F /* FIRCursorTests.m */,
- DE51B1BE1F0D48AC0013853F /* FIRDatabaseTests.m */,
- DE51B1BF1F0D48AC0013853F /* FIRFieldsTests.m */,
- DE51B1C01F0D48AC0013853F /* FIRListenerRegistrationTests.m */,
- DE51B1C11F0D48AC0013853F /* FIRQueryTests.m */,
- DE51B1C21F0D48AC0013853F /* FIRServerTimestampTests.m */,
- DE51B1C31F0D48AC0013853F /* FIRTypeTests.m */,
- 54DA12B01F315F3800DD57A1 /* FIRValidationTests.m */,
- DEFE0F471F1F960A0071599A /* FIRWriteBatchTests.m */,
+ 5492E070202154D600B64F25 /* FIRCursorTests.mm */,
+ 5492E06C202154D500B64F25 /* FIRDatabaseTests.mm */,
+ 5492E06A202154D500B64F25 /* FIRFieldsTests.mm */,
+ 5492E06B202154D500B64F25 /* FIRListenerRegistrationTests.mm */,
+ 5492E069202154D500B64F25 /* FIRQueryTests.mm */,
+ 5492E06E202154D600B64F25 /* FIRServerTimestampTests.mm */,
+ 5492E071202154D600B64F25 /* FIRTypeTests.mm */,
+ 5492E06D202154D600B64F25 /* FIRValidationTests.mm */,
+ 5492E06F202154D600B64F25 /* FIRWriteBatchTests.mm */,
);
path = API;
sourceTree = "<group>";
};
- DE51B1C71F0D48AC0013853F /* Util */ = {
- isa = PBXGroup;
- children = (
- );
- path = Util;
- sourceTree = "<group>";
- };
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
+ 54C9EDF02040E16300A969CD /* Firestore_SwiftTests_iOS */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 54C9EDFA2040E16300A969CD /* Build configuration list for PBXNativeTarget "Firestore_SwiftTests_iOS" */;
+ buildPhases = (
+ 6FB7F3A6D6ADAC64E4972A29 /* [CP] Check Pods Manifest.lock */,
+ 54C9EDED2040E16300A969CD /* Sources */,
+ 54C9EDEE2040E16300A969CD /* Frameworks */,
+ 54C9EDEF2040E16300A969CD /* Resources */,
+ 9E2D564AC55ADE2D52B7E951 /* [CP] Embed Pods Frameworks */,
+ A650E34A01FE620F7B26F5FF /* [CP] Copy Pods Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ 54C9EDF72040E16300A969CD /* PBXTargetDependency */,
+ );
+ name = Firestore_SwiftTests_iOS;
+ productName = Firestore_SwiftTests_iOS;
+ productReference = 54C9EDF12040E16300A969CD /* Firestore_SwiftTests_iOS.xctest */;
+ productType = "com.apple.product-type.bundle.unit-test";
+ };
6003F589195388D20070C39A /* Firestore_Example */ = {
isa = PBXNativeTarget;
buildConfigurationList = 6003F5BF195388D20070C39A /* Build configuration list for PBXNativeTarget "Firestore_Example" */;
@@ -800,10 +1008,15 @@
isa = PBXProject;
attributes = {
CLASSPREFIX = FIR;
- LastSwiftUpdateCheck = 0830;
+ LastSwiftUpdateCheck = 0920;
LastUpgradeCheck = 0720;
ORGANIZATIONNAME = Google;
TargetAttributes = {
+ 54C9EDF02040E16300A969CD = {
+ CreatedOnToolsVersion = 9.2;
+ ProvisioningStyle = Automatic;
+ TestTargetID = 6003F589195388D20070C39A;
+ };
6003F5AD195388D20070C39A = {
DevelopmentTeam = EQHXZ8M8AV;
TestTargetID = 6003F589195388D20070C39A;
@@ -837,6 +1050,7 @@
targets = (
6003F589195388D20070C39A /* Firestore_Example */,
6003F5AD195388D20070C39A /* Firestore_Tests */,
+ 54C9EDF02040E16300A969CD /* Firestore_SwiftTests_iOS */,
DE03B2941F2149D600A30B9C /* Firestore_IntegrationTests */,
DE29E7F51F2174B000909613 /* AllTests */,
DE0761E31F2FE611003233AF /* SwiftBuildTest */,
@@ -845,6 +1059,13 @@
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
+ 54C9EDEF2040E16300A969CD /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
6003F588195388D20070C39A /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
@@ -917,8 +1138,7 @@
inputPaths = (
"${SRCROOT}/Pods/Target Support Files/Pods-SwiftBuildTest/Pods-SwiftBuildTest-frameworks.sh",
"${BUILT_PRODUCTS_DIR}/BoringSSL/openssl.framework",
- "${BUILT_PRODUCTS_DIR}/GTMSessionFetcher/GTMSessionFetcher.framework",
- "${BUILT_PRODUCTS_DIR}/GoogleToolboxForMac-f0850809/GoogleToolboxForMac.framework",
+ "${BUILT_PRODUCTS_DIR}/GoogleToolboxForMac-Defines-NSData+zlib/GoogleToolboxForMac.framework",
"${BUILT_PRODUCTS_DIR}/Protobuf/Protobuf.framework",
"${BUILT_PRODUCTS_DIR}/gRPC/GRPCClient.framework",
"${BUILT_PRODUCTS_DIR}/gRPC-Core/grpc.framework",
@@ -930,7 +1150,6 @@
name = "[CP] Embed Pods Frameworks";
outputPaths = (
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/openssl.framework",
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GTMSessionFetcher.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleToolboxForMac.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Protobuf.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GRPCClient.framework",
@@ -945,6 +1164,24 @@
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-SwiftBuildTest/Pods-SwiftBuildTest-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
+ 6FB7F3A6D6ADAC64E4972A29 /* [CP] Check Pods Manifest.lock */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
+ "${PODS_ROOT}/Manifest.lock",
+ );
+ name = "[CP] Check Pods Manifest.lock";
+ outputPaths = (
+ "$(DERIVED_FILE_DIR)/Pods-Firestore_Example-Firestore_SwiftTests_iOS-checkManifestLockResult.txt",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
+ showEnvVarsInLog = 0;
+ };
7C5123A9C345ECE100DA21BD /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
@@ -1017,6 +1254,57 @@
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
+ 9E2D564AC55ADE2D52B7E951 /* [CP] Embed Pods Frameworks */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ "${SRCROOT}/Pods/Target Support Files/Pods-Firestore_Example-Firestore_SwiftTests_iOS/Pods-Firestore_Example-Firestore_SwiftTests_iOS-frameworks.sh",
+ "${BUILT_PRODUCTS_DIR}/BoringSSL/openssl.framework",
+ "${BUILT_PRODUCTS_DIR}/GTMSessionFetcher/GTMSessionFetcher.framework",
+ "${BUILT_PRODUCTS_DIR}/GoogleToolboxForMac-f0850809/GoogleToolboxForMac.framework",
+ "${BUILT_PRODUCTS_DIR}/Protobuf/Protobuf.framework",
+ "${BUILT_PRODUCTS_DIR}/gRPC/GRPCClient.framework",
+ "${BUILT_PRODUCTS_DIR}/gRPC-Core/grpc.framework",
+ "${BUILT_PRODUCTS_DIR}/gRPC-ProtoRPC/ProtoRPC.framework",
+ "${BUILT_PRODUCTS_DIR}/gRPC-RxLibrary/RxLibrary.framework",
+ "${BUILT_PRODUCTS_DIR}/leveldb-library/leveldb.framework",
+ "${BUILT_PRODUCTS_DIR}/nanopb/nanopb.framework",
+ );
+ name = "[CP] Embed Pods Frameworks";
+ outputPaths = (
+ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/openssl.framework",
+ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GTMSessionFetcher.framework",
+ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleToolboxForMac.framework",
+ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Protobuf.framework",
+ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GRPCClient.framework",
+ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/grpc.framework",
+ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ProtoRPC.framework",
+ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxLibrary.framework",
+ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/leveldb.framework",
+ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/nanopb.framework",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Firestore_Example-Firestore_SwiftTests_iOS/Pods-Firestore_Example-Firestore_SwiftTests_iOS-frameworks.sh\"\n";
+ showEnvVarsInLog = 0;
+ };
+ A650E34A01FE620F7B26F5FF /* [CP] Copy Pods Resources */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ );
+ name = "[CP] Copy Pods Resources";
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Firestore_Example-Firestore_SwiftTests_iOS/Pods-Firestore_Example-Firestore_SwiftTests_iOS-resources.sh\"\n";
+ showEnvVarsInLog = 0;
+ };
AB3F19DA92555D3399DB07CE /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
@@ -1145,6 +1433,14 @@
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
+ 54C9EDED2040E16300A969CD /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 5495EB032040E90200EBA509 /* CodableGeoPointTests.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
6003F586195388D20070C39A /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
@@ -1159,67 +1455,99 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ 7346E61D20325C6900FD6CEF /* FSTDispatchQueueTests.mm in Sources */,
DE2EF0881F3D0B6E003D0CDC /* FSTTreeSortedDictionaryTests.m in Sources */,
- DE51B1FD1F0D492C0013853F /* FSTSpecTests.m in Sources */,
- DE51B2001F0D493A0013853F /* FSTComparisonTests.m in Sources */,
- DE51B1CC1F0D48C00013853F /* FIRGeoPointTests.m in Sources */,
- DE51B1E11F0D490D0013853F /* FSTMemoryRemoteDocumentCacheTests.m in Sources */,
- DE51B1FF1F0D493A0013853F /* FSTAssertTests.m in Sources */,
- DE51B1D31F0D48CD0013853F /* FSTViewSnapshotTest.m in Sources */,
- DE51B1F91F0D491F0013853F /* FSTWatchChangeTests.m in Sources */,
- DE51B1F81F0D491F0013853F /* FSTWatchChange+Testing.m in Sources */,
- DE51B1EB1F0D490D0013853F /* FSTWriteGroupTests.mm in Sources */,
- DE51B2011F0D493E0013853F /* FSTHelpers.m in Sources */,
- DE51B1F61F0D491B0013853F /* FSTSerializerBetaTests.m in Sources */,
- DE51B1F01F0D49140013853F /* FSTFieldValueTests.m in Sources */,
+ ABE6637A201FA81900ED349A /* database_id_test.cc in Sources */,
+ 5492E0AF2021552D00B64F25 /* FSTReferenceSetTests.mm in Sources */,
+ 5492E09E2021552D00B64F25 /* FSTEagerGarbageCollectorTests.mm in Sources */,
+ 5492E0C62021557E00B64F25 /* FSTWatchChange+Testing.mm in Sources */,
+ 5492E064202154B900B64F25 /* FSTQueryListenerTests.mm in Sources */,
+ B65D34A9203C995B0076A5E1 /* FIRTimestampTest.m in Sources */,
+ 5492E03320213FFC00B64F25 /* FSTSyncEngineTestDriver.mm in Sources */,
+ AB380CFE201A2F4500D97691 /* string_util_test.cc in Sources */,
+ 5492E0A42021552D00B64F25 /* FSTMemoryQueryCacheTests.mm in Sources */,
+ 54C2294F1FECABAE007D065B /* log_test.cc in Sources */,
+ B686F2B22025000D0028D6BE /* resource_path_test.cc in Sources */,
+ 5492E0CA2021557E00B64F25 /* FSTWatchChangeTests.mm in Sources */,
+ 5492E063202154B900B64F25 /* FSTViewSnapshotTest.mm in Sources */,
+ 5492E058202154AB00B64F25 /* FSTAPIHelpers.mm in Sources */,
+ AB380CFB2019388600D97691 /* target_id_generator_test.cc in Sources */,
+ 5492E0A82021552D00B64F25 /* FSTLevelDBLocalStoreTests.mm in Sources */,
+ ABC1D7DE2023A05300BA84F0 /* user_test.cc in Sources */,
5491BC721FB44593008B3588 /* FSTIntegrationTestCase.mm in Sources */,
+ ABC1D7DD2023A04F00BA84F0 /* empty_credentials_provider_test.cc in Sources */,
DE2EF0861F3D0B6E003D0CDC /* FSTImmutableSortedDictionary+Testing.m in Sources */,
- DE51B1DE1F0D490D0013853F /* FSTMemoryLocalStoreTests.m in Sources */,
- DE51B1EC1F0D49140013853F /* FSTDatabaseIDTests.m in Sources */,
- DE51B1ED1F0D49140013853F /* FSTDocumentKeyTests.m in Sources */,
- DE51B1D41F0D48CD0013853F /* FSTViewTests.m in Sources */,
+ B686F2AF2023DDEE0028D6BE /* field_path_test.cc in Sources */,
+ 5492E03120213FFC00B64F25 /* FSTLevelDBSpecTests.mm in Sources */,
+ 5492E0B12021552D00B64F25 /* FSTRemoteDocumentCacheTests.mm in Sources */,
+ 5492E0BA2021555100B64F25 /* FSTDocumentSetTests.mm in Sources */,
54740A581FC914F000713A1A /* autoid_test.cc in Sources */,
- DE51B1F41F0D491B0013853F /* FSTRemoteEventTests.m in Sources */,
- 54E928241F33953300C1953E /* FSTEventAccumulator.m in Sources */,
- DE51B1D11F0D48CD0013853F /* FSTTargetIDGeneratorTests.m in Sources */,
- DE51B1EF1F0D49140013853F /* FSTDocumentTests.m in Sources */,
- DE51B1DC1F0D490D0013853F /* FSTLocalSerializerTests.m in Sources */,
- DE51B1E71F0D490D0013853F /* FSTRemoteDocumentChangeBufferTests.m in Sources */,
- DE51B1E51F0D490D0013853F /* FSTReferenceSetTests.m in Sources */,
- DE51B1EA1F0D490D0013853F /* FSTLevelDBRemoteDocumentCacheTests.mm in Sources */,
- DE51B1D21F0D48CD0013853F /* FSTTimestampTests.m in Sources */,
- DE51B1EE1F0D49140013853F /* FSTDocumentSetTests.m in Sources */,
+ 548DB927200D590300E00ABC /* assert_test.cc in Sources */,
+ 5492E0A62021552D00B64F25 /* FSTPersistenceTestHelpers.mm in Sources */,
+ 5467FB01203E5717009C9584 /* FIRFirestoreTests.mm in Sources */,
+ 5492E0A12021552D00B64F25 /* FSTMemoryLocalStoreTests.mm in Sources */,
+ 5436F32420008FAD006E51E3 /* string_printf_test.cc in Sources */,
+ 5492E067202154B900B64F25 /* FSTEventManagerTests.mm in Sources */,
+ 5492E0BF2021555100B64F25 /* FSTFieldValueTests.mm in Sources */,
+ 5492E055202154AB00B64F25 /* FIRDocumentSnapshotTests.mm in Sources */,
+ 5492E03E2021401F00B64F25 /* FSTEventAccumulator.mm in Sources */,
DE2EF0851F3D0B6E003D0CDC /* FSTArraySortedDictionaryTests.m in Sources */,
- DE51B1F11F0D49140013853F /* FSTMutationTests.m in Sources */,
- DE51B1FB1F0D492C0013853F /* FSTMemorySpecTests.m in Sources */,
- DE51B1DB1F0D490D0013853F /* FSTLevelDBQueryCacheTests.m in Sources */,
- 54764FAB1FAA0C320085E60A /* string_util_test.cc in Sources */,
- 54E9282C1F339CAD00C1953E /* XCTestCase+Await.m in Sources */,
- DE51B1DF1F0D490D0013853F /* FSTMemoryMutationQueueTests.m in Sources */,
- DE51B1F31F0D491B0013853F /* FSTDatastoreTests.m in Sources */,
- DE51B1D01F0D48CD0013853F /* FSTQueryTests.m in Sources */,
+ 5492E0AA2021552D00B64F25 /* FSTLevelDBRemoteDocumentCacheTests.mm in Sources */,
+ 5492E0AC2021552D00B64F25 /* FSTMutationQueueTests.mm in Sources */,
+ 5492E056202154AB00B64F25 /* FIRFieldPathTests.mm in Sources */,
+ 5492E03220213FFC00B64F25 /* FSTMockDatastore.mm in Sources */,
+ ABC1D7E12023A40C00BA84F0 /* token_test.cc in Sources */,
+ AB356EF7200EA5EB0089B766 /* field_value_test.cc in Sources */,
+ AB7BAB342012B519001E0872 /* geo_point_test.cc in Sources */,
+ 5492E0AD2021552D00B64F25 /* FSTMemoryMutationQueueTests.mm in Sources */,
+ 5492E051202154AA00B64F25 /* FIRQueryTests.mm in Sources */,
+ 5492E054202154AB00B64F25 /* FIRFieldValueTests.mm in Sources */,
+ AB6B908620322E6D00CC290A /* maybe_document_test.cc in Sources */,
+ 5492E09F2021552D00B64F25 /* FSTLevelDBMigrationsTests.mm in Sources */,
+ 5492E053202154AB00B64F25 /* FIRDocumentReferenceTests.mm in Sources */,
+ 5492E09D2021552D00B64F25 /* FSTLocalStoreTests.mm in Sources */,
+ 5492E0A32021552D00B64F25 /* FSTLocalSerializerTests.mm in Sources */,
+ 5492E0A72021552D00B64F25 /* FSTLevelDBKeyTests.mm in Sources */,
+ 5492E0A22021552D00B64F25 /* FSTQueryCacheTests.mm in Sources */,
+ 5492E0A52021552D00B64F25 /* FSTMemoryRemoteDocumentCacheTests.mm in Sources */,
+ AB6B908820322E8800CC290A /* no_document_test.cc in Sources */,
+ 5492E0BD2021555100B64F25 /* FSTDocumentTests.mm in Sources */,
+ 5492E0B92021555100B64F25 /* FSTDocumentKeyTests.mm in Sources */,
DE2EF0871F3D0B6E003D0CDC /* FSTImmutableSortedSet+Testing.m in Sources */,
- DE51B1E01F0D490D0013853F /* FSTMemoryQueryCacheTests.m in Sources */,
- DE51B1E91F0D490D0013853F /* FSTLevelDBMutationQueueTests.mm in Sources */,
+ 5492E0C82021557E00B64F25 /* FSTDatastoreTests.mm in Sources */,
+ 54995F6F205B6E12004EFFA0 /* leveldb_key_test.cc in Sources */,
+ 5492E065202154B900B64F25 /* FSTViewTests.mm in Sources */,
+ 5492E03C2021401F00B64F25 /* XCTestCase+Await.mm in Sources */,
+ B6152AD7202A53CB000E5744 /* document_key_test.cc in Sources */,
+ 5467FB08203E6A44009C9584 /* app_testing.mm in Sources */,
54764FAF1FAA21B90085E60A /* FSTGoogleTestTests.mm in Sources */,
- DE51B1E61F0D490D0013853F /* FSTRemoteDocumentCacheTests.m in Sources */,
- 61E1D8B11FCF6C5700753285 /* StringViewTests.mm in Sources */,
- DE51B1D91F0D490D0013853F /* FSTEagerGarbageCollectorTests.m in Sources */,
- DE51B1E21F0D490D0013853F /* FSTMutationQueueTests.m in Sources */,
- DE51B1E81F0D490D0013853F /* FSTLevelDBKeyTests.mm in Sources */,
- DE51B1E31F0D490D0013853F /* FSTPersistenceTestHelpers.m in Sources */,
- DE51B1CF1F0D48CD0013853F /* FSTQueryListenerTests.m in Sources */,
- DE51B1DA1F0D490D0013853F /* FSTLevelDBLocalStoreTests.m in Sources */,
- DE51B1FA1F0D492C0013853F /* FSTLevelDBSpecTests.m in Sources */,
- DE51B1FE1F0D492C0013853F /* FSTSyncEngineTestDriver.m in Sources */,
- DE51B1FC1F0D492C0013853F /* FSTMockDatastore.m in Sources */,
- DE51B1CE1F0D48CD0013853F /* FSTEventManagerTests.m in Sources */,
- DE51B1E41F0D490D0013853F /* FSTQueryCacheTests.m in Sources */,
- DE51B1CD1F0D48CD0013853F /* FSTDatabaseInfoTests.m in Sources */,
- DE51B1F21F0D49140013853F /* FSTPathTests.m in Sources */,
+ AB380D04201BC6E400D97691 /* ordered_code_test.cc in Sources */,
+ 5492E03F2021401F00B64F25 /* FSTHelpers.mm in Sources */,
+ 5492E068202154B900B64F25 /* FSTQueryTests.mm in Sources */,
+ 5492E0AB2021552D00B64F25 /* StringViewTests.mm in Sources */,
+ 5492E0C92021557E00B64F25 /* FSTRemoteEventTests.mm in Sources */,
+ ABC1D7E42024AFDE00BA84F0 /* firebase_credentials_provider_test.mm in Sources */,
+ AB6B908420322E4D00CC290A /* document_test.cc in Sources */,
+ ABF6506C201131F8005F2C74 /* timestamp_test.cc in Sources */,
+ 5492E0AE2021552D00B64F25 /* FSTLevelDBQueryCacheTests.mm in Sources */,
+ ABC1D7DC2023A04B00BA84F0 /* credentials_provider_test.cc in Sources */,
+ 5492E059202154AB00B64F25 /* FIRQuerySnapshotTests.mm in Sources */,
+ 5492E050202154AA00B64F25 /* FIRCollectionReferenceTests.mm in Sources */,
+ ABA495BB202B7E80008A7851 /* snapshot_version_test.cc in Sources */,
+ 5492E0A02021552D00B64F25 /* FSTLevelDBMutationQueueTests.mm in Sources */,
+ 54EB764D202277B30088B8F3 /* array_sorted_map_test.cc in Sources */,
+ 5492E03420213FFC00B64F25 /* FSTMemorySpecTests.mm in Sources */,
+ AB380D02201BC69F00D97691 /* bits_test.cc in Sources */,
+ 548DB929200D59F600E00ABC /* comparison_test.cc in Sources */,
+ 5492E03D2021401F00B64F25 /* FSTAssertTests.mm in Sources */,
+ AB38D93020236E21000A432D /* database_info_test.cc in Sources */,
+ 5492E052202154AB00B64F25 /* FIRGeoPointTests.mm in Sources */,
+ 5492E0C72021557E00B64F25 /* FSTSerializerBetaTests.mm in Sources */,
+ 5492E03520213FFC00B64F25 /* FSTSpecTests.mm in Sources */,
+ 5492E057202154AB00B64F25 /* FIRSnapshotMetadataTests.mm in Sources */,
54740A571FC914BA00713A1A /* secure_random_test.cc in Sources */,
- DE51B1DD1F0D490D0013853F /* FSTLocalStoreTests.m in Sources */,
- D5B25474286C9800CE42B8C2 /* FSTTestDispatchQueue.m in Sources */,
+ 5492E0BE2021555100B64F25 /* FSTMutationTests.mm in Sources */,
+ 132E3E53179DE287D875F3F2 /* FSTLevelDBTransactionTests.mm in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1227,24 +1555,23 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
- DE03B2EE1F214BAA00A30B9C /* FIRWriteBatchTests.m in Sources */,
- DE03B2F01F214BAA00A30B9C /* FIRDatabaseTests.m in Sources */,
+ 5492E076202154D600B64F25 /* FIRValidationTests.mm in Sources */,
+ 5492E072202154D600B64F25 /* FIRQueryTests.mm in Sources */,
5491BC731FB44593008B3588 /* FSTIntegrationTestCase.mm in Sources */,
- DE03B2F41F214BAA00A30B9C /* FIRServerTimestampTests.m in Sources */,
- DE03B2F11F214BAA00A30B9C /* FIRFieldsTests.m in Sources */,
- 54E9282D1F339CAD00C1953E /* XCTestCase+Await.m in Sources */,
- DE03B2EC1F214BA200A30B9C /* FSTDatastoreTests.m in Sources */,
- 54E928251F33953400C1953E /* FSTEventAccumulator.m in Sources */,
- DE03B2ED1F214BA200A30B9C /* FSTSmokeTests.m in Sources */,
- DE03B2F31F214BAA00A30B9C /* FIRQueryTests.m in Sources */,
- DE03B35E1F21586C00A30B9C /* FSTHelpers.m in Sources */,
- DE03B2F51F214BAA00A30B9C /* FIRTypeTests.m in Sources */,
- DE03B2EF1F214BAA00A30B9C /* FIRCursorTests.m in Sources */,
- DE03B2F21F214BAA00A30B9C /* FIRListenerRegistrationTests.m in Sources */,
- DE03B2C91F2149D600A30B9C /* FSTTransactionTests.m in Sources */,
- 54DA12B11F315F3800DD57A1 /* FIRValidationTests.m in Sources */,
- D5B2532E4676014F57A7EAB9 /* FSTStreamTests.m in Sources */,
- D5B259FDEE8094E8D710C5BF /* FSTTestDispatchQueue.m in Sources */,
+ 5492E0442021457E00B64F25 /* XCTestCase+Await.mm in Sources */,
+ 5492E07A202154D600B64F25 /* FIRTypeTests.mm in Sources */,
+ 5492E0422021440500B64F25 /* FSTHelpers.mm in Sources */,
+ 5492E041202143E700B64F25 /* FSTEventAccumulator.mm in Sources */,
+ 5492E080202154EC00B64F25 /* FSTSmokeTests.mm in Sources */,
+ 5492E077202154D600B64F25 /* FIRServerTimestampTests.mm in Sources */,
+ 5492E081202154EC00B64F25 /* FSTStreamTests.mm in Sources */,
+ 5492E074202154D600B64F25 /* FIRListenerRegistrationTests.mm in Sources */,
+ 5492E082202154EC00B64F25 /* FSTDatastoreTests.mm in Sources */,
+ 5492E079202154D600B64F25 /* FIRCursorTests.mm in Sources */,
+ 5492E073202154D600B64F25 /* FIRFieldsTests.mm in Sources */,
+ 5492E07F202154EC00B64F25 /* FSTTransactionTests.mm in Sources */,
+ 5492E075202154D600B64F25 /* FIRDatabaseTests.mm in Sources */,
+ 5492E078202154D600B64F25 /* FIRWriteBatchTests.mm in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1259,6 +1586,16 @@
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
+ 54C9EDF72040E16300A969CD /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 6003F589195388D20070C39A /* Firestore_Example */;
+ targetProxy = 54C9EDF62040E16300A969CD /* PBXContainerItemProxy */;
+ };
+ 54C9EDFF2040E41900A969CD /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 54C9EDF02040E16300A969CD /* Firestore_SwiftTests_iOS */;
+ targetProxy = 54C9EDFE2040E41900A969CD /* PBXContainerItemProxy */;
+ };
6003F5B4195388D20070C39A /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 6003F589195388D20070C39A /* Firestore_Example */;
@@ -1314,6 +1651,84 @@
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
+ 54C9EDF82040E16300A969CD /* Debug */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 3F422FFBDA6E79396E2FB594 /* Pods-Firestore_Example-Firestore_SwiftTests_iOS.debug.xcconfig */;
+ buildSettings = {
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CODE_SIGN_IDENTITY = "iPhone Developer";
+ CODE_SIGN_STYLE = Automatic;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ GCC_NO_COMMON_BLOCKS = YES;
+ INFOPLIST_FILE = ../Swift/Tests/Info.plist;
+ IPHONEOS_DEPLOYMENT_TARGET = 11.2;
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+ MTL_ENABLE_DEBUG_INFO = YES;
+ PRODUCT_BUNDLE_IDENTIFIER = "com.google.Firestore-SwiftTests-iOS";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ SWIFT_VERSION = 4.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Firestore_Example.app/Firestore_Example";
+ };
+ name = Debug;
+ };
+ 54C9EDF92040E16300A969CD /* Release */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = C1D89E5405935366C88CC3E5 /* Pods-Firestore_Example-Firestore_SwiftTests_iOS.release.xcconfig */;
+ buildSettings = {
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CODE_SIGN_IDENTITY = "iPhone Developer";
+ CODE_SIGN_STYLE = Automatic;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ GCC_NO_COMMON_BLOCKS = YES;
+ INFOPLIST_FILE = ../Swift/Tests/Info.plist;
+ IPHONEOS_DEPLOYMENT_TARGET = 11.2;
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+ MTL_ENABLE_DEBUG_INFO = NO;
+ PRODUCT_BUNDLE_IDENTIFIER = "com.google.Firestore-SwiftTests-iOS";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
+ SWIFT_VERSION = 4.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Firestore_Example.app/Firestore_Example";
+ };
+ name = Release;
+ };
6003F5BD195388D20070C39A /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@@ -1450,7 +1865,9 @@
HEADER_SEARCH_PATHS = (
"$(inherited)",
"\"${PODS_ROOT}/../../..\"",
+ "\"${PODS_ROOT}/../../../Firestore/third_party/abseil-cpp\"",
"\"${PODS_ROOT}/leveldb-library/include\"",
+ "\"${PODS_ROOT}/GoogleTest/googletest/include\"",
);
INFOPLIST_FILE = "Tests/Tests-Info.plist";
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}";
@@ -1481,7 +1898,9 @@
HEADER_SEARCH_PATHS = (
"$(inherited)",
"\"${PODS_ROOT}/../../..\"",
+ "\"${PODS_ROOT}/../../../Firestore/third_party/abseil-cpp\"",
"\"${PODS_ROOT}/leveldb-library/include\"",
+ "\"${PODS_ROOT}/GoogleTest/googletest/include\"",
);
INFOPLIST_FILE = "Tests/Tests-Info.plist";
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}";
@@ -1512,6 +1931,9 @@
HEADER_SEARCH_PATHS = (
"$(inherited)",
"\"${PODS_ROOT}/../../..\"",
+ "\"${PODS_ROOT}/../../../Firestore/third_party/abseil-cpp\"",
+ "\"${PODS_ROOT}/leveldb-library/include\"",
+ "\"${PODS_ROOT}/GoogleTest/googletest/include\"",
);
INFOPLIST_FILE = "Tests/Tests-Info.plist";
OTHER_LDFLAGS = (
@@ -1550,6 +1972,9 @@
HEADER_SEARCH_PATHS = (
"$(inherited)",
"\"${PODS_ROOT}/../../..\"",
+ "\"${PODS_ROOT}/../../../Firestore/third_party/abseil-cpp\"",
+ "\"${PODS_ROOT}/leveldb-library/include\"",
+ "\"${PODS_ROOT}/GoogleTest/googletest/include\"",
);
INFOPLIST_FILE = "Tests/Tests-Info.plist";
OTHER_LDFLAGS = (
@@ -1640,6 +2065,15 @@
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
+ 54C9EDFA2040E16300A969CD /* Build configuration list for PBXNativeTarget "Firestore_SwiftTests_iOS" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 54C9EDF82040E16300A969CD /* Debug */,
+ 54C9EDF92040E16300A969CD /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
6003F585195388D10070C39A /* Build configuration list for PBXProject "Firestore" */ = {
isa = XCConfigurationList;
buildConfigurations = (
diff --git a/Firestore/Example/Firestore.xcodeproj/xcshareddata/xcschemes/AllTests.xcscheme b/Firestore/Example/Firestore.xcodeproj/xcshareddata/xcschemes/AllTests.xcscheme
index aacb70e..6cf00a9 100644
--- a/Firestore/Example/Firestore.xcodeproj/xcshareddata/xcschemes/AllTests.xcscheme
+++ b/Firestore/Example/Firestore.xcodeproj/xcshareddata/xcschemes/AllTests.xcscheme
@@ -20,6 +20,20 @@
ReferencedContainer = "container:Firestore.xcodeproj">
</BuildableReference>
</BuildActionEntry>
+ <BuildActionEntry
+ buildForTesting = "YES"
+ buildForRunning = "NO"
+ buildForProfiling = "NO"
+ buildForArchiving = "NO"
+ buildForAnalyzing = "NO">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "54C9EDF02040E16300A969CD"
+ BuildableName = "Firestore_SwiftTests_iOS.xctest"
+ BlueprintName = "Firestore_SwiftTests_iOS"
+ ReferencedContainer = "container:Firestore.xcodeproj">
+ </BuildableReference>
+ </BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
@@ -49,6 +63,16 @@
ReferencedContainer = "container:Firestore.xcodeproj">
</BuildableReference>
</TestableReference>
+ <TestableReference
+ skipped = "NO">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "54C9EDF02040E16300A969CD"
+ BuildableName = "Firestore_SwiftTests_iOS.xctest"
+ BlueprintName = "Firestore_SwiftTests_iOS"
+ ReferencedContainer = "container:Firestore.xcodeproj">
+ </BuildableReference>
+ </TestableReference>
</Testables>
<MacroExpansion>
<BuildableReference
diff --git a/Firestore/Example/Firestore.xcodeproj/xcshareddata/xcschemes/Firestore_Tests.xcscheme b/Firestore/Example/Firestore.xcodeproj/xcshareddata/xcschemes/Firestore_Tests.xcscheme
index 920e1f3..6f79458 100644
--- a/Firestore/Example/Firestore.xcodeproj/xcshareddata/xcschemes/Firestore_Tests.xcscheme
+++ b/Firestore/Example/Firestore.xcodeproj/xcshareddata/xcschemes/Firestore_Tests.xcscheme
@@ -7,8 +7,11 @@
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
+ buildForTesting = "YES"
buildForRunning = "YES"
- buildForTesting = "YES">
+ buildForProfiling = "YES"
+ buildForArchiving = "YES"
+ buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "6003F5AD195388D20070C39A"
@@ -17,6 +20,20 @@
ReferencedContainer = "container:Firestore.xcodeproj">
</BuildableReference>
</BuildActionEntry>
+ <BuildActionEntry
+ buildForTesting = "YES"
+ buildForRunning = "YES"
+ buildForProfiling = "YES"
+ buildForArchiving = "YES"
+ buildForAnalyzing = "YES">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "54C9EDF02040E16300A969CD"
+ BuildableName = "Firestore_SwiftTests_iOS.xctest"
+ BlueprintName = "Firestore_SwiftTests_iOS"
+ ReferencedContainer = "container:Firestore.xcodeproj">
+ </BuildableReference>
+ </BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
@@ -36,7 +53,26 @@
ReferencedContainer = "container:Firestore.xcodeproj">
</BuildableReference>
</TestableReference>
+ <TestableReference
+ skipped = "NO">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "54C9EDF02040E16300A969CD"
+ BuildableName = "Firestore_SwiftTests_iOS.xctest"
+ BlueprintName = "Firestore_SwiftTests_iOS"
+ ReferencedContainer = "container:Firestore.xcodeproj">
+ </BuildableReference>
+ </TestableReference>
</Testables>
+ <MacroExpansion>
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "6003F5AD195388D20070C39A"
+ BuildableName = "Firestore_Tests.xctest"
+ BlueprintName = "Firestore_Tests"
+ ReferencedContainer = "container:Firestore.xcodeproj">
+ </BuildableReference>
+ </MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
@@ -51,6 +87,15 @@
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
+ <MacroExpansion>
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "6003F5AD195388D20070C39A"
+ BuildableName = "Firestore_Tests.xctest"
+ BlueprintName = "Firestore_Tests"
+ ReferencedContainer = "container:Firestore.xcodeproj">
+ </BuildableReference>
+ </MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
@@ -60,6 +105,15 @@
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
+ <MacroExpansion>
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "6003F5AD195388D20070C39A"
+ BuildableName = "Firestore_Tests.xctest"
+ BlueprintName = "Firestore_Tests"
+ ReferencedContainer = "container:Firestore.xcodeproj">
+ </BuildableReference>
+ </MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
diff --git a/Firestore/Example/Podfile b/Firestore/Example/Podfile
index 89af74f..41df29c 100644
--- a/Firestore/Example/Podfile
+++ b/Firestore/Example/Podfile
@@ -1,7 +1,7 @@
# The next line is the forcing function for the Firebase pod. The Firebase
# version's subspecs should depend on the component versions in their
# corresponding podspec's.
-pod 'Firebase/Core', '4.7.0'
+pod 'Firebase/Core', '4.13.0'
use_frameworks!
platform :ios, '8.0'
@@ -28,6 +28,11 @@ target 'Firestore_Example' do
pod 'OCMock'
end
+
+ target 'Firestore_SwiftTests_iOS' do
+ pod 'FirebaseFirestore', :path => '../../'
+ pod 'FirebaseFirestoreSwift', :path => '../../'
+ end
end
target 'SwiftBuildTest' do
diff --git a/Firestore/Example/SwiftBuildTest/main.swift b/Firestore/Example/SwiftBuildTest/main.swift
index bea8b56..ad6c0f6 100644
--- a/Firestore/Example/SwiftBuildTest/main.swift
+++ b/Firestore/Example/SwiftBuildTest/main.swift
@@ -19,277 +19,352 @@ import Foundation
import FirebaseFirestore
func main() {
- let db = initializeDb();
+ let db = initializeDb()
- let (collectionRef, documentRef) = makeRefs(database: db);
+ let (collectionRef, documentRef) = makeRefs(database: db)
- let query = makeQuery(collection: collectionRef);
+ let query = makeQuery(collection: collectionRef)
- writeDocument(at: documentRef);
+ writeDocument(at: documentRef)
- addDocument(to: collectionRef);
+ writeDocuments(at: documentRef, database: db)
- readDocument(at: documentRef);
+ addDocument(to: collectionRef)
- readDocuments(matching: query);
+ readDocument(at: documentRef)
- listenToDocument(at: documentRef);
+ readDocuments(matching: query)
- listenToDocuments(matching: query);
+ listenToDocument(at: documentRef)
- types();
+ listenToDocuments(matching: query)
+
+ enableDisableNetwork(database: db)
+
+ types()
}
func initializeDb() -> Firestore {
+ // Initialize with ProjectID.
+ let firestore = Firestore.firestore()
- // Initialize with ProjectID.
- let firestore = Firestore.firestore()
+ // Apply settings
+ let settings = FirestoreSettings()
+ settings.host = "localhost"
+ settings.isPersistenceEnabled = true
+ settings.areTimestampsInSnapshotsEnabled = true
+ firestore.settings = settings
- // Apply settings
- let settings = FirestoreSettings()
- settings.host = "localhost"
- settings.isPersistenceEnabled = true
- firestore.settings = settings
-
- return firestore;
+ return firestore
}
func makeRefs(database db: Firestore) -> (CollectionReference, DocumentReference) {
+ var collectionRef = db.collection("my-collection")
- var collectionRef = db.collection("my-collection")
-
- var documentRef: DocumentReference;
- documentRef = collectionRef.document("my-doc")
- // or
- documentRef = db.document("my-collection/my-doc")
+ var documentRef: DocumentReference
+ documentRef = collectionRef.document("my-doc")
+ // or
+ documentRef = db.document("my-collection/my-doc")
- // deeper collection (my-collection/my-doc/some/deep/collection)
- collectionRef = documentRef.collection("some/deep/collection")
+ // deeper collection (my-collection/my-doc/some/deep/collection)
+ collectionRef = documentRef.collection("some/deep/collection")
- // parent doc (my-collection/my-doc/some/deep)
- documentRef = collectionRef.parent!
+ // parent doc (my-collection/my-doc/some/deep)
+ documentRef = collectionRef.parent!
- // print paths.
- print("Collection: \(collectionRef.path), document: \(documentRef.path)")
+ // print paths.
+ print("Collection: \(collectionRef.path), document: \(documentRef.path)")
- return (collectionRef, documentRef);
+ return (collectionRef, documentRef)
}
func makeQuery(collection collectionRef: CollectionReference) -> Query {
-
- let query = collectionRef.whereField(FieldPath(["name"]), isEqualTo: "Fred")
- .whereField("age", isGreaterThanOrEqualTo: 24)
- .whereField(FieldPath.documentID(), isEqualTo: "fred")
- .order(by: FieldPath(["age"]))
- .order(by: "name", descending: true)
- .limit(to: 10)
-
- return query;
+ let query = collectionRef.whereField(FieldPath(["name"]), isEqualTo: "Fred")
+ .whereField("age", isGreaterThanOrEqualTo: 24)
+ .whereField(FieldPath.documentID(), isEqualTo: "fred")
+ .order(by: FieldPath(["age"]))
+ .order(by: "name", descending: true)
+ .limit(to: 10)
+
+ return query
}
func writeDocument(at docRef: DocumentReference) {
+ let setData = [
+ "foo": 42,
+ "bar": [
+ "baz": "Hello world!",
+ ],
+ ] as [String: Any]
+
+ let updateData = [
+ "bar.baz": 42,
+ FieldPath(["foobar"]): 42,
+ "server_timestamp": FieldValue.serverTimestamp(),
+ "array_union": FieldValue.arrayUnion(["a", "b"]),
+ "array_remove": FieldValue.arrayRemove(["a", "b"]),
+ "field_delete": FieldValue.delete(),
+ ] as [AnyHashable: Any]
+
+ docRef.setData(setData)
+
+ // Completion callback (via trailing closure syntax).
+ docRef.setData(setData) { error in
+ if let error = error {
+ print("Uh oh! \(error)")
+ return
+ }
- let setData = [
- "foo": 42,
- "bar": [
- "baz": "Hello world!"
- ]
- ] as [String : Any];
-
- let updateData = [
- "bar.baz": 42,
- FieldPath(["foobar"]) : 42
- ] as [AnyHashable : Any];
-
- docRef.setData(setData)
-
- // Completion callback (via trailing closure syntax).
- docRef.setData(setData) { error in
- if let error = error {
- print("Uh oh! \(error)")
- return
- }
+ print("Set complete!")
+ }
- print("Set complete!")
+ // merge
+ docRef.setData(setData, merge: true)
+ docRef.setData(setData, merge: true) { error in
+ if let error = error {
+ print("Uh oh! \(error)")
+ return
}
- // SetOptions
- docRef.setData(setData, options:SetOptions.merge())
+ print("Set complete!")
+ }
- docRef.updateData(updateData)
- docRef.delete();
+ docRef.updateData(updateData)
+ docRef.delete()
- docRef.delete() { error in
- if let error = error {
- print("Uh oh! \(error)")
- return
- }
+ docRef.delete { error in
+ if let error = error {
+ print("Uh oh! \(error)")
+ return
+ }
+
+ print("Set complete!")
+ }
+}
- print("Set complete!")
+func enableDisableNetwork(database db: Firestore) {
+ // closure syntax
+ db.disableNetwork(completion: { error in
+ if let e = error {
+ print("Uh oh! \(e)")
+ return
}
+ })
+ // trailing block syntax
+ db.enableNetwork { error in
+ if let e = error {
+ print("Uh oh! \(e)")
+ return
+ }
+ }
}
-func addDocument(to collectionRef: CollectionReference) {
+func writeDocuments(at docRef: DocumentReference, database db: Firestore) {
+ var batch: WriteBatch
+
+ batch = db.batch()
+ batch.setData(["a": "b"], forDocument: docRef)
+ batch.setData(["a": "b"], forDocument: docRef, merge: true)
+ batch.setData(["c": "d"], forDocument: docRef)
+ // commit without completion callback.
+ batch.commit()
+ print("Batch write without completion complete!")
+
+ batch = db.batch()
+ batch.setData(["a": "b"], forDocument: docRef)
+ batch.setData(["c": "d"], forDocument: docRef)
+ // commit with completion callback via trailing closure syntax.
+ batch.commit { error in
+ if let error = error {
+ print("Uh oh! \(error)")
+ return
+ }
+ print("Batch write callback complete!")
+ }
+ print("Batch write with completion complete!")
+}
- collectionRef.addDocument(data: ["foo": 42]);
- //or
- collectionRef.document().setData(["foo": 42]);
+func addDocument(to collectionRef: CollectionReference) {
+ collectionRef.addDocument(data: ["foo": 42])
+ // or
+ collectionRef.document().setData(["foo": 42])
}
func readDocument(at docRef: DocumentReference) {
-
- // Trailing closure syntax.
- docRef.getDocument() { document, error in
- if let document = document {
- // NOTE that document is nullable.
- let data = document.data();
- print("Read document: \(data)")
-
- // Fields are read via subscript notation.
- if let foo = document["foo"] {
- print("Field: \(foo)")
- }
- } else {
- // TODO(mikelehen): There may be a better way to do this, but it at least demonstrates
- // the swift error domain / enum codes are renamed appropriately.
- if let errorCode = error.flatMap({
- ($0._domain == FirestoreErrorDomain) ? FirestoreErrorCode (rawValue: $0._code) : nil
- }) {
- switch errorCode {
- case .unavailable:
- print("Can't read document due to being offline!")
- case _:
- print("Failed to read.")
- }
- } else {
- print("Unknown error!")
- }
+ // Trailing closure syntax.
+ docRef.getDocument { document, error in
+ if let document = document {
+ // Note that both document and document.data() is nullable.
+ if let data = document.data() {
+ print("Read document: \(data)")
+ }
+ if let data = document.data(with: SnapshotOptions.serverTimestampBehavior(.estimate)) {
+ print("Read document: \(data)")
+ }
+ if let foo = document.get("foo") {
+ print("Field: \(foo)")
+ }
+ if let foo = document.get("foo", options: SnapshotOptions.serverTimestampBehavior(.previous)) {
+ print("Field: \(foo)")
+ }
+ // Fields can also be read via subscript notation.
+ if let foo = document["foo"] {
+ print("Field: \(foo)")
+ }
+ } else {
+ // TODO(mikelehen): There may be a better way to do this, but it at least demonstrates
+ // the swift error domain / enum codes are renamed appropriately.
+ if let errorCode = error.flatMap({
+ ($0._domain == FirestoreErrorDomain) ? FirestoreErrorCode(rawValue: $0._code) : nil
+ }) {
+ switch errorCode {
+ case .unavailable:
+ print("Can't read document due to being offline!")
+ case _:
+ print("Failed to read.")
}
-
+ } else {
+ print("Unknown error!")
+ }
}
+ }
}
func readDocuments(matching query: Query) {
- query.getDocuments() { querySnapshot, error in
- // TODO(mikelehen): Figure out how to make "for..in" syntax work
- // directly on documentSet.
- for document in querySnapshot!.documents {
- print(document.data())
- }
+ query.getDocuments { querySnapshot, error in
+ // TODO(mikelehen): Figure out how to make "for..in" syntax work
+ // directly on documentSet.
+ for document in querySnapshot!.documents {
+ print(document.data())
}
+ }
}
func listenToDocument(at docRef: DocumentReference) {
+ let listener = docRef.addSnapshotListener { document, error in
+ if let error = error {
+ print("Uh oh! Listen canceled: \(error)")
+ return
+ }
- let listener = docRef.addSnapshotListener() { document, error in
- if let error = error {
- print("Uh oh! Listen canceled: \(error)")
- return
- }
+ if let document = document {
+ // Note that document.data() is nullable.
+ if let data: [String: Any] = document.data() {
+ print("Current document: \(data)")
+ }
+ if document.metadata.isFromCache {
+ print("From Cache")
+ } else {
+ print("From Server")
+ }
+ }
+ }
- if let document = document {
- print("Current document: \(document.data())");
- if (document.metadata.isFromCache) {
- print("From Cache")
- } else {
- print("From Server")
- }
- }
+ // Unsubscribe.
+ listener.remove()
+}
+
+func listenToDocumentWithMetadataChanges(at docRef: DocumentReference) {
+ let listener = docRef.addSnapshotListener(includeMetadataChanges: true) { document, error in
+ if let document = document {
+ if document.metadata.hasPendingWrites {
+ print("Has pending writes")
+ }
}
+ }
- // Unsubscribe.
- listener.remove();
+ // Unsubscribe.
+ listener.remove()
}
func listenToDocuments(matching query: Query) {
+ let listener = query.addSnapshotListener { snap, error in
+ if let error = error {
+ print("Uh oh! Listen canceled: \(error)")
+ return
+ }
- let listener = query.addSnapshotListener() { snap, error in
- if let error = error {
- print("Uh oh! Listen canceled: \(error)")
- return
- }
-
- if let snap = snap {
- print("NEW SNAPSHOT (empty=\(snap.isEmpty) count=\(snap.count)")
+ if let snap = snap {
+ print("NEW SNAPSHOT (empty=\(snap.isEmpty) count=\(snap.count)")
- // TODO(mikelehen): Figure out how to make "for..in" syntax work
- // directly on documentSet.
- for document in snap.documents {
- print("Doc: ", document.data())
- }
- }
+ // TODO(mikelehen): Figure out how to make "for..in" syntax work
+ // directly on documentSet.
+ for document in snap.documents {
+ // Note that document.data() is not nullable.
+ let data: [String: Any] = document.data()
+ print("Doc: ", data)
+ }
}
+ }
- // Unsubscribe
- listener.remove();
+ // Unsubscribe
+ listener.remove()
}
func listenToQueryDiffs(onQuery query: Query) {
-
- let listener = query.addSnapshotListener() { snap, error in
- if let snap = snap {
- for change in snap.documentChanges {
- switch (change.type) {
- case .added:
- print("New document: \(change.document.data())")
- case .modified:
- print("Modified document: \(change.document.data())")
- case .removed:
- print("Removed document: \(change.document.data())")
- }
- }
+ let listener = query.addSnapshotListener { snap, error in
+ if let snap = snap {
+ for change in snap.documentChanges {
+ switch change.type {
+ case .added:
+ print("New document: \(change.document.data())")
+ case .modified:
+ print("Modified document: \(change.document.data())")
+ case .removed:
+ print("Removed document: \(change.document.data())")
}
+ }
}
+ }
- // Unsubscribe
- listener.remove();
+ // Unsubscribe
+ listener.remove()
}
func transactions() {
- let db = Firestore.firestore()
-
- let collectionRef = db.collection("cities")
- let accA = collectionRef.document("accountA")
- let accB = collectionRef.document("accountB")
- let amount = 20.0
-
- db.runTransaction({ (transaction, errorPointer) -> Any? in
- do {
- let balanceA = try transaction.getDocument(accA)["balance"] as! Double
- let balanceB = try transaction.getDocument(accB)["balance"] as! Double
-
- if (balanceA < amount) {
- errorPointer?.pointee = NSError(domain: "Foo", code: 123, userInfo: nil)
- return nil
- }
- transaction.updateData(["balance": balanceA - amount], forDocument:accA)
- transaction.updateData(["balance": balanceB + amount], forDocument:accB)
- } catch let error as NSError {
- print("Uh oh! \(error)")
- }
- return 0
- }) { (result, error) in
- // handle result.
+ let db = Firestore.firestore()
+
+ let collectionRef = db.collection("cities")
+ let accA = collectionRef.document("accountA")
+ let accB = collectionRef.document("accountB")
+ let amount = 20.0
+
+ db.runTransaction({ (transaction, errorPointer) -> Any? in
+ do {
+ let balanceA = try transaction.getDocument(accA)["balance"] as! Double
+ let balanceB = try transaction.getDocument(accB)["balance"] as! Double
+
+ if balanceA < amount {
+ errorPointer?.pointee = NSError(domain: "Foo", code: 123, userInfo: nil)
+ return nil
+ }
+ transaction.updateData(["balance": balanceA - amount], forDocument: accA)
+ transaction.updateData(["balance": balanceB + amount], forDocument: accB)
+ } catch let error as NSError {
+ print("Uh oh! \(error)")
}
+ return 0
+ }) { result, error in
+ // handle result.
+ }
}
func types() {
- let _: CollectionReference;
- let _: DocumentChange;
- let _: DocumentListenOptions;
- let _: DocumentReference;
- let _: DocumentSnapshot;
- let _: FieldPath;
- let _: FieldValue;
- let _: Firestore;
- let _: FirestoreSettings;
- let _: GeoPoint;
- let _: ListenerRegistration;
- let _: QueryListenOptions;
- let _: Query;
- let _: QuerySnapshot;
- let _: SetOptions;
- let _: SnapshotMetadata;
- let _: Transaction;
- let _: WriteBatch;
+ let _: CollectionReference
+ let _: DocumentChange
+ let _: DocumentReference
+ let _: DocumentSnapshot
+ let _: FieldPath
+ let _: FieldValue
+ let _: Firestore
+ let _: FirestoreSettings
+ let _: GeoPoint
+ let _: Timestamp
+ let _: ListenerRegistration
+ let _: QueryListenOptions
+ let _: Query
+ let _: QuerySnapshot
+ let _: SnapshotMetadata
+ let _: Transaction
+ let _: WriteBatch
}
diff --git a/Firestore/Example/Tests/API/FIRCollectionReferenceTests.mm b/Firestore/Example/Tests/API/FIRCollectionReferenceTests.mm
new file mode 100644
index 0000000..cb99d24
--- /dev/null
+++ b/Firestore/Example/Tests/API/FIRCollectionReferenceTests.mm
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2017 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 <FirebaseFirestore/FIRCollectionReference.h>
+
+#import <XCTest/XCTest.h>
+
+#import "Firestore/Example/Tests/API/FSTAPIHelpers.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface FIRCollectionReferenceTests : XCTestCase
+@end
+
+@implementation FIRCollectionReferenceTests
+
+- (void)testEquals {
+ FIRCollectionReference *referenceFoo = FSTTestCollectionRef("foo");
+ FIRCollectionReference *referenceFooDup = FSTTestCollectionRef("foo");
+ FIRCollectionReference *referenceBar = FSTTestCollectionRef("bar");
+ XCTAssertEqualObjects(referenceFoo, referenceFooDup);
+ XCTAssertNotEqualObjects(referenceFoo, referenceBar);
+
+ XCTAssertEqual([referenceFoo hash], [referenceFooDup hash]);
+ XCTAssertNotEqual([referenceFoo hash], [referenceBar hash]);
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firestore/Example/Tests/API/FIRDocumentReferenceTests.mm b/Firestore/Example/Tests/API/FIRDocumentReferenceTests.mm
new file mode 100644
index 0000000..4124cd0
--- /dev/null
+++ b/Firestore/Example/Tests/API/FIRDocumentReferenceTests.mm
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2017 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 <FirebaseFirestore/FIRDocumentReference.h>
+
+#import <XCTest/XCTest.h>
+
+#import "Firestore/Example/Tests/API/FSTAPIHelpers.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface FIRDocumentReferenceTests : XCTestCase
+@end
+
+@implementation FIRDocumentReferenceTests
+
+- (void)testEquals {
+ FIRDocumentReference *referenceFoo = FSTTestDocRef("rooms/foo");
+ FIRDocumentReference *referenceFooDup = FSTTestDocRef("rooms/foo");
+ FIRDocumentReference *referenceBar = FSTTestDocRef("rooms/bar");
+ XCTAssertEqualObjects(referenceFoo, referenceFooDup);
+ XCTAssertNotEqualObjects(referenceFoo, referenceBar);
+
+ XCTAssertEqual([referenceFoo hash], [referenceFooDup hash]);
+ XCTAssertNotEqual([referenceFoo hash], [referenceBar hash]);
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firestore/Example/Tests/API/FIRDocumentSnapshotTests.mm b/Firestore/Example/Tests/API/FIRDocumentSnapshotTests.mm
new file mode 100644
index 0000000..136fed6
--- /dev/null
+++ b/Firestore/Example/Tests/API/FIRDocumentSnapshotTests.mm
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2017 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 <FirebaseFirestore/FIRDocumentSnapshot.h>
+
+#import <XCTest/XCTest.h>
+
+#import "Firestore/Example/Tests/API/FSTAPIHelpers.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface FIRDocumentSnapshotTests : XCTestCase
+@end
+
+@implementation FIRDocumentSnapshotTests
+
+- (void)testEquals {
+ FIRDocumentSnapshot *base = FSTTestDocSnapshot("rooms/foo", 1, @{ @"a" : @1 }, NO, NO);
+ FIRDocumentSnapshot *baseDup = FSTTestDocSnapshot("rooms/foo", 1, @{ @"a" : @1 }, NO, NO);
+ FIRDocumentSnapshot *nilData = FSTTestDocSnapshot("rooms/foo", 1, nil, NO, NO);
+ FIRDocumentSnapshot *nilDataDup = FSTTestDocSnapshot("rooms/foo", 1, nil, NO, NO);
+ FIRDocumentSnapshot *differentPath = FSTTestDocSnapshot("rooms/bar", 1, @{ @"a" : @1 }, NO, NO);
+ FIRDocumentSnapshot *differentData = FSTTestDocSnapshot("rooms/bar", 1, @{ @"b" : @1 }, NO, NO);
+ FIRDocumentSnapshot *hasMutations = FSTTestDocSnapshot("rooms/bar", 1, @{ @"a" : @1 }, YES, NO);
+ FIRDocumentSnapshot *fromCache = FSTTestDocSnapshot("rooms/bar", 1, @{ @"a" : @1 }, NO, YES);
+ XCTAssertEqualObjects(base, baseDup);
+ XCTAssertEqualObjects(nilData, nilDataDup);
+ XCTAssertNotEqualObjects(base, nilData);
+ XCTAssertNotEqualObjects(nilData, base);
+ XCTAssertNotEqualObjects(base, differentPath);
+ XCTAssertNotEqualObjects(base, differentData);
+ XCTAssertNotEqualObjects(base, hasMutations);
+ XCTAssertNotEqualObjects(base, fromCache);
+
+ XCTAssertEqual([base hash], [baseDup hash]);
+ XCTAssertEqual([nilData hash], [nilDataDup hash]);
+ XCTAssertNotEqual([base hash], [nilData hash]);
+ XCTAssertNotEqual([base hash], [differentPath hash]);
+ XCTAssertNotEqual([base hash], [differentData hash]);
+ XCTAssertNotEqual([base hash], [hasMutations hash]);
+ XCTAssertNotEqual([base hash], [fromCache hash]);
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firestore/Example/Tests/API/FIRFieldPathTests.mm b/Firestore/Example/Tests/API/FIRFieldPathTests.mm
new file mode 100644
index 0000000..50d37c1
--- /dev/null
+++ b/Firestore/Example/Tests/API/FIRFieldPathTests.mm
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2017 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 <FirebaseFirestore/FIRFieldPath.h>
+
+#import <XCTest/XCTest.h>
+
+#import "Firestore/Source/API/FIRFieldPath+Internal.h"
+
+#import "Firestore/Example/Tests/Util/FSTHelpers.h"
+
+#include "Firestore/core/test/firebase/firestore/testutil/testutil.h"
+
+namespace testutil = firebase::firestore::testutil;
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface FIRFieldPathTests : XCTestCase
+@end
+
+@implementation FIRFieldPathTests
+
+- (void)testEquals {
+ FIRFieldPath *foo = [[FIRFieldPath alloc] initPrivate:testutil::Field("foo.ooo.oooo")];
+ FIRFieldPath *fooDup = [[FIRFieldPath alloc] initPrivate:testutil::Field("foo.ooo.oooo")];
+ FIRFieldPath *bar = [[FIRFieldPath alloc] initPrivate:testutil::Field("baa.aaa.aaar")];
+ XCTAssertEqualObjects(foo, fooDup);
+ XCTAssertNotEqualObjects(foo, bar);
+
+ XCTAssertEqual([foo hash], [fooDup hash]);
+ XCTAssertNotEqual([foo hash], [bar hash]);
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firestore/Example/Tests/API/FIRFieldValueTests.mm b/Firestore/Example/Tests/API/FIRFieldValueTests.mm
new file mode 100644
index 0000000..575dfee
--- /dev/null
+++ b/Firestore/Example/Tests/API/FIRFieldValueTests.mm
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2017 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 <FirebaseFirestore/FIRFieldValue.h>
+
+#import <XCTest/XCTest.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface FIRFieldValueTests : XCTestCase
+@end
+
+@implementation FIRFieldValueTests
+
+- (void)testEquals {
+ FIRFieldValue *deleted = [FIRFieldValue fieldValueForDelete];
+ FIRFieldValue *deleteDup = [FIRFieldValue fieldValueForDelete];
+ FIRFieldValue *serverTimestamp = [FIRFieldValue fieldValueForServerTimestamp];
+ FIRFieldValue *serverTimestampDup = [FIRFieldValue fieldValueForServerTimestamp];
+ XCTAssertEqualObjects(deleted, deleteDup);
+ XCTAssertNotEqualObjects(deleted, nil);
+ XCTAssertEqualObjects(serverTimestamp, serverTimestampDup);
+ XCTAssertNotEqualObjects(serverTimestamp, nil);
+ XCTAssertNotEqualObjects(deleted, serverTimestamp);
+
+ XCTAssertEqual([deleted hash], [deleteDup hash]);
+ XCTAssertEqual([serverTimestamp hash], [serverTimestamp hash]);
+ XCTAssertNotEqual([deleted hash], [serverTimestamp hash]);
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firestore/Example/Tests/API/FIRFirestoreTests.mm b/Firestore/Example/Tests/API/FIRFirestoreTests.mm
new file mode 100644
index 0000000..7cb49b7
--- /dev/null
+++ b/Firestore/Example/Tests/API/FIRFirestoreTests.mm
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2018 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 <FirebaseCore/FIRAppInternal.h>
+#import <FirebaseCore/FIROptionsInternal.h>
+#import <FirebaseFirestore/FIRFirestore.h>
+
+#import <XCTest/XCTest.h>
+
+#include "Firestore/core/test/firebase/firestore/testutil/app_testing.h"
+
+namespace testutil = firebase::firestore::testutil;
+
+@interface FIRFirestoreTests : XCTestCase
+@end
+
+@implementation FIRFirestoreTests
+
+- (void)testDeleteApp {
+ // Ensure the app is set appropriately.
+ FIRApp *app = testutil::AppForUnitTesting();
+ NSString *appName = app.name;
+ FIROptions *options = app.options;
+
+ FIRFirestore *firestore = [FIRFirestore firestoreForApp:app];
+ XCTAssertEqualObjects(firestore.app, app);
+
+ // Ensure that firestoreForApp returns the same instance.
+ XCTAssertEqualObjects(firestore, [FIRFirestore firestoreForApp:app]);
+
+ XCTestExpectation *defaultAppDeletedExpectation =
+ [self expectationWithDescription:
+ @"Deleting the default app should invalidate the default "
+ @"Firestore instance."];
+ [app deleteApp:^(BOOL success) {
+ // Recreate the FIRApp with the same name, fetch a new Firestore instance and make sure it's
+ // different than the other one.
+ [FIRApp configureWithName:appName options:options];
+ FIRApp *newApp = [FIRApp appNamed:appName];
+ FIRFirestore *newInstance = [FIRFirestore firestoreForApp:newApp];
+ XCTAssertNotEqualObjects(newInstance, firestore);
+
+ [defaultAppDeletedExpectation fulfill];
+ }];
+
+ [self waitForExpectationsWithTimeout:2
+ handler:^(NSError *_Nullable error) {
+ XCTAssertNil(error);
+ }];
+}
+
+@end
diff --git a/Firestore/Example/Tests/API/FIRGeoPointTests.m b/Firestore/Example/Tests/API/FIRGeoPointTests.mm
index b505de0..4de80a8 100644
--- a/Firestore/Example/Tests/API/FIRGeoPointTests.m
+++ b/Firestore/Example/Tests/API/FIRGeoPointTests.mm
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#import "FirebaseFirestore/FIRGeoPoint.h"
+#import <FirebaseFirestore/FIRGeoPoint.h>
#import <XCTest/XCTest.h>
@@ -28,16 +28,17 @@ NS_ASSUME_NONNULL_BEGIN
@implementation FIRGeoPointTests
- (void)testEquals {
- XCTAssertEqualObjects([[FIRGeoPoint alloc] initWithLatitude:0 longitude:0],
- [[FIRGeoPoint alloc] initWithLatitude:0 longitude:0]);
- XCTAssertEqualObjects([[FIRGeoPoint alloc] initWithLatitude:1.23 longitude:4.56],
- [[FIRGeoPoint alloc] initWithLatitude:1.23 longitude:4.56]);
- XCTAssertNotEqualObjects([[FIRGeoPoint alloc] initWithLatitude:0 longitude:0],
- [[FIRGeoPoint alloc] initWithLatitude:1 longitude:0]);
- XCTAssertNotEqualObjects([[FIRGeoPoint alloc] initWithLatitude:0 longitude:0],
- [[FIRGeoPoint alloc] initWithLatitude:0 longitude:1]);
- XCTAssertNotEqualObjects([[FIRGeoPoint alloc] initWithLatitude:0 longitude:0],
- [[NSObject alloc] init]);
+ FIRGeoPoint *foo = FSTTestGeoPoint(1.23, 4.56);
+ FIRGeoPoint *fooDup = FSTTestGeoPoint(1.23, 4.56);
+ FIRGeoPoint *differentLatitude = FSTTestGeoPoint(1.23, 0);
+ FIRGeoPoint *differentLongitude = FSTTestGeoPoint(0, 4.56);
+ XCTAssertEqualObjects(foo, fooDup);
+ XCTAssertNotEqualObjects(foo, differentLatitude);
+ XCTAssertNotEqualObjects(foo, differentLongitude);
+
+ XCTAssertEqual([foo hash], [fooDup hash]);
+ XCTAssertNotEqual([foo hash], [differentLatitude hash]);
+ XCTAssertNotEqual([foo hash], [differentLongitude hash]);
}
- (void)testComparison {
diff --git a/Firestore/Example/Tests/API/FIRQuerySnapshotTests.mm b/Firestore/Example/Tests/API/FIRQuerySnapshotTests.mm
new file mode 100644
index 0000000..f8c7d60
--- /dev/null
+++ b/Firestore/Example/Tests/API/FIRQuerySnapshotTests.mm
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2017 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 <FirebaseFirestore/FIRQuerySnapshot.h>
+
+#import <XCTest/XCTest.h>
+
+#import "Firestore/Example/Tests/API/FSTAPIHelpers.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface FIRQuerySnapshotTests : XCTestCase
+@end
+
+@implementation FIRQuerySnapshotTests
+
+- (void)testEquals {
+ FIRQuerySnapshot *foo = FSTTestQuerySnapshot("foo", @{}, @{ @"a" : @{@"a" : @1} }, YES, NO);
+ FIRQuerySnapshot *fooDup = FSTTestQuerySnapshot("foo", @{}, @{ @"a" : @{@"a" : @1} }, YES, NO);
+ FIRQuerySnapshot *differentPath = FSTTestQuerySnapshot("bar", @{},
+ @{ @"a" : @{@"a" : @1} }, YES, NO);
+ FIRQuerySnapshot *differentDoc = FSTTestQuerySnapshot("foo",
+ @{ @"a" : @{@"b" : @1} }, @{}, YES, NO);
+ FIRQuerySnapshot *noPendingWrites = FSTTestQuerySnapshot("foo", @{},
+ @{ @"a" : @{@"a" : @1} }, NO, NO);
+ FIRQuerySnapshot *fromCache = FSTTestQuerySnapshot("foo", @{},
+ @{ @"a" : @{@"a" : @1} }, YES, YES);
+ XCTAssertEqualObjects(foo, fooDup);
+ XCTAssertNotEqualObjects(foo, differentPath);
+ XCTAssertNotEqualObjects(foo, differentDoc);
+ XCTAssertNotEqualObjects(foo, noPendingWrites);
+ XCTAssertNotEqualObjects(foo, fromCache);
+
+ XCTAssertEqual([foo hash], [fooDup hash]);
+ XCTAssertNotEqual([foo hash], [differentPath hash]);
+ XCTAssertNotEqual([foo hash], [differentDoc hash]);
+ XCTAssertNotEqual([foo hash], [noPendingWrites hash]);
+ XCTAssertNotEqual([foo hash], [fromCache hash]);
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firestore/Example/Tests/API/FIRQueryTests.mm b/Firestore/Example/Tests/API/FIRQueryTests.mm
new file mode 100644
index 0000000..c02c92c
--- /dev/null
+++ b/Firestore/Example/Tests/API/FIRQueryTests.mm
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2017 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 <FirebaseFirestore/FIRQuery.h>
+
+#import <XCTest/XCTest.h>
+
+#import "Firestore/Source/API/FIRQuery+Internal.h"
+#import "Firestore/Source/Core/FSTQuery.h"
+
+#import "Firestore/Example/Tests/API/FSTAPIHelpers.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface FIRQueryTests : XCTestCase
+@end
+
+@implementation FIRQueryTests
+
+- (void)testEquals {
+ FIRFirestore *firestore = FSTTestFirestore();
+ FIRQuery *queryFoo = [FIRQuery referenceWithQuery:FSTTestQuery("foo") firestore:firestore];
+ FIRQuery *queryFooDup = [FIRQuery referenceWithQuery:FSTTestQuery("foo") firestore:firestore];
+ FIRQuery *queryBar = [FIRQuery referenceWithQuery:FSTTestQuery("bar") firestore:firestore];
+ XCTAssertEqualObjects(queryFoo, queryFooDup);
+ XCTAssertNotEqualObjects(queryFoo, queryBar);
+ XCTAssertEqualObjects([queryFoo queryWhereField:@"f" isEqualTo:@1],
+ [queryFoo queryWhereField:@"f" isEqualTo:@1]);
+ XCTAssertNotEqualObjects([queryFoo queryWhereField:@"f" isEqualTo:@1],
+ [queryFoo queryWhereField:@"f" isEqualTo:@2]);
+
+ XCTAssertEqual([queryFoo hash], [queryFooDup hash]);
+ XCTAssertNotEqual([queryFoo hash], [queryBar hash]);
+ XCTAssertEqual([[queryFoo queryWhereField:@"f" isEqualTo:@1] hash],
+ [[queryFoo queryWhereField:@"f" isEqualTo:@1] hash]);
+ XCTAssertNotEqual([[queryFoo queryWhereField:@"f" isEqualTo:@1] hash],
+ [[queryFoo queryWhereField:@"f" isEqualTo:@2] hash]);
+}
+
+- (void)testFilteringWithPredicate {
+ FIRFirestore *firestore = FSTTestFirestore();
+ FIRQuery *query = [FIRQuery referenceWithQuery:FSTTestQuery("foo") firestore:firestore];
+ FIRQuery *query1 = [query queryWhereField:@"f" isLessThanOrEqualTo:@1];
+ FIRQuery *query2 = [query queryFilteredUsingPredicate:[NSPredicate predicateWithFormat:@"f<=1"]];
+ FIRQuery *query3 =
+ [[query queryWhereField:@"f1" isLessThan:@2] queryWhereField:@"f2" isEqualTo:@3];
+ FIRQuery *query4 =
+ [query queryFilteredUsingPredicate:[NSPredicate predicateWithFormat:@"f1<2 && f2==3"]];
+ FIRQuery *query5 =
+ [[[[[query queryWhereField:@"f1" isLessThan:@2] queryWhereField:@"f2" isEqualTo:@3]
+ queryWhereField:@"f1"
+ isLessThanOrEqualTo:@"four"] queryWhereField:@"f1"
+ isGreaterThanOrEqualTo:@"five"] queryWhereField:@"f1"
+ isGreaterThan:@6];
+ FIRQuery *query6 = [query
+ queryFilteredUsingPredicate:
+ [NSPredicate predicateWithFormat:@"f1<2 && f2==3 && f1<='four' && f1>='five' && f1>6"]];
+ FIRQuery *query7 = [query
+ queryFilteredUsingPredicate:
+ [NSPredicate predicateWithFormat:@"2>f1 && 3==f2 && 'four'>=f1 && 'five'<=f1 && 6<f1"]];
+ XCTAssertEqualObjects(query1, query2);
+ XCTAssertNotEqualObjects(query2, query3);
+ XCTAssertEqualObjects(query3, query4);
+ XCTAssertNotEqualObjects(query4, query5);
+ XCTAssertEqualObjects(query5, query6);
+ XCTAssertEqualObjects(query6, query7);
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firestore/Example/Tests/API/FIRSnapshotMetadataTests.mm b/Firestore/Example/Tests/API/FIRSnapshotMetadataTests.mm
new file mode 100644
index 0000000..a4d321b
--- /dev/null
+++ b/Firestore/Example/Tests/API/FIRSnapshotMetadataTests.mm
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2017 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 <FirebaseFirestore/FIRSnapshotMetadata.h>
+
+#import <XCTest/XCTest.h>
+
+#import "Firestore/Source/API/FIRSnapshotMetadata+Internal.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface FIRSnapshotMetadataTests : XCTestCase
+@end
+
+@implementation FIRSnapshotMetadataTests
+
+- (void)testEquals {
+ FIRSnapshotMetadata *foo =
+ [FIRSnapshotMetadata snapshotMetadataWithPendingWrites:YES fromCache:YES];
+ FIRSnapshotMetadata *fooDup =
+ [FIRSnapshotMetadata snapshotMetadataWithPendingWrites:YES fromCache:YES];
+ FIRSnapshotMetadata *bar =
+ [FIRSnapshotMetadata snapshotMetadataWithPendingWrites:YES fromCache:NO];
+ FIRSnapshotMetadata *baz =
+ [FIRSnapshotMetadata snapshotMetadataWithPendingWrites:NO fromCache:YES];
+ XCTAssertEqualObjects(foo, fooDup);
+ XCTAssertNotEqualObjects(foo, bar);
+ XCTAssertNotEqualObjects(foo, baz);
+ XCTAssertNotEqualObjects(bar, baz);
+
+ XCTAssertEqual([foo hash], [fooDup hash]);
+ XCTAssertNotEqual([foo hash], [bar hash]);
+ XCTAssertNotEqual([foo hash], [baz hash]);
+ XCTAssertNotEqual([bar hash], [baz hash]);
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firestore/Example/Tests/API/FIRTimestampTest.m b/Firestore/Example/Tests/API/FIRTimestampTest.m
new file mode 100644
index 0000000..98ec804
--- /dev/null
+++ b/Firestore/Example/Tests/API/FIRTimestampTest.m
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2017 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 <XCTest/XCTest.h>
+
+#import "Firestore/Source/API/FIRTimestamp+Internal.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface FIRTimestampTest : XCTestCase
+@end
+
+NSDate *TestDate(int year, int month, int day, int hour, int minute, int second) {
+ NSDateComponents *comps = [[NSDateComponents alloc] init];
+ comps.year = year;
+ comps.month = month;
+ comps.day = day;
+ comps.hour = hour;
+ comps.minute = minute;
+ comps.second = second;
+ // Force time zone to UTC to avoid these values changing due to daylight saving.
+ comps.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];
+
+ return [[NSCalendar currentCalendar] dateFromComponents:comps];
+}
+
+@implementation FIRTimestampTest
+
+- (void)testFromDate {
+ // Use an NSDate such that its fractional seconds have an exact representation to avoid losing
+ // precision.
+ NSDate *input = [NSDate dateWithTimeIntervalSinceReferenceDate:1.5];
+
+ FIRTimestamp *actual = [FIRTimestamp timestampWithDate:input];
+ static const int64_t kSecondsFromEpochToReferenceDate = 978307200;
+ XCTAssertEqual(kSecondsFromEpochToReferenceDate + 1, actual.seconds);
+ XCTAssertEqual(actual.nanoseconds, 500000000);
+
+ FIRTimestamp *expected =
+ [[FIRTimestamp alloc] initWithSeconds:(kSecondsFromEpochToReferenceDate + 1)
+ nanoseconds:500000000];
+ XCTAssertEqualObjects(actual, expected);
+}
+
+- (void)testSO8601String {
+ NSDate *date = TestDate(1912, 4, 14, 23, 40, 0);
+ FIRTimestamp *timestamp =
+ [[FIRTimestamp alloc] initWithSeconds:(int64_t)date.timeIntervalSince1970
+ nanoseconds:543000000];
+ XCTAssertEqualObjects(timestamp.ISO8601String, @"1912-04-14T23:40:00.543000000Z");
+}
+
+- (void)testISO8601String_withLowMilliseconds {
+ NSDate *date = TestDate(1912, 4, 14, 23, 40, 0);
+ FIRTimestamp *timestamp =
+ [[FIRTimestamp alloc] initWithSeconds:(int64_t)date.timeIntervalSince1970
+ nanoseconds:7000000];
+ XCTAssertEqualObjects(timestamp.ISO8601String, @"1912-04-14T23:40:00.007000000Z");
+}
+
+- (void)testISO8601String_withLowNanos {
+ FIRTimestamp *timestamp = [[FIRTimestamp alloc] initWithSeconds:0 nanoseconds:1];
+ XCTAssertEqualObjects(timestamp.ISO8601String, @"1970-01-01T00:00:00.000000001Z");
+}
+
+- (void)testISO8601String_withNegativeSeconds {
+ FIRTimestamp *timestamp = [[FIRTimestamp alloc] initWithSeconds:-1 nanoseconds:999999999];
+ XCTAssertEqualObjects(timestamp.ISO8601String, @"1969-12-31T23:59:59.999999999Z");
+}
+
+- (void)testCompare {
+ NSArray<FIRTimestamp *> *timestamps = @[
+ [[FIRTimestamp alloc] initWithSeconds:12344 nanoseconds:999999999],
+ [[FIRTimestamp alloc] initWithSeconds:12345 nanoseconds:0],
+ [[FIRTimestamp alloc] initWithSeconds:12345 nanoseconds:000000001],
+ [[FIRTimestamp alloc] initWithSeconds:12345 nanoseconds:99999999],
+ [[FIRTimestamp alloc] initWithSeconds:12345 nanoseconds:100000000],
+ [[FIRTimestamp alloc] initWithSeconds:12345 nanoseconds:100000001],
+ [[FIRTimestamp alloc] initWithSeconds:12346 nanoseconds:0],
+ ];
+ for (int i = 0; i < timestamps.count - 1; ++i) {
+ XCTAssertEqual([timestamps[i] compare:timestamps[i + 1]], NSOrderedAscending);
+ XCTAssertEqual([timestamps[i + 1] compare:timestamps[i]], NSOrderedDescending);
+ }
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firestore/Example/Tests/API/FSTAPIHelpers.h b/Firestore/Example/Tests/API/FSTAPIHelpers.h
new file mode 100644
index 0000000..2325c22
--- /dev/null
+++ b/Firestore/Example/Tests/API/FSTAPIHelpers.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2017 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/Foundation.h>
+
+#import "Firestore/Example/Tests/Util/FSTHelpers.h"
+
+#include "absl/strings/string_view.h"
+
+@class FIRCollectionReference;
+@class FIRDocumentReference;
+@class FIRDocumentSnapshot;
+@class FIRFirestore;
+@class FIRQuerySnapshot;
+
+NS_ASSUME_NONNULL_BEGIN
+
+#if __cplusplus
+extern "C" {
+#endif
+
+/** A convenience method for creating dummy singleton FIRFirestore for tests. */
+FIRFirestore *FSTTestFirestore();
+
+/** A convenience method for creating a doc snapshot for tests. */
+FIRDocumentSnapshot *FSTTestDocSnapshot(const absl::string_view path,
+ FSTTestSnapshotVersion version,
+ NSDictionary<NSString *, id> *_Nullable data,
+ BOOL hasMutations,
+ BOOL fromCache);
+
+/** A convenience method for creating a collection reference from a path string. */
+FIRCollectionReference *FSTTestCollectionRef(const absl::string_view path);
+
+/** A convenience method for creating a document reference from a path string. */
+FIRDocumentReference *FSTTestDocRef(const absl::string_view path);
+
+/**
+ * A convenience method for creating a particular query snapshot for tests.
+ *
+ * @param path To be used in constructing the query.
+ * @param oldDocs Provides the prior set of documents in the QuerySnapshot. Each dictionary entry
+ * maps to a document, with the key being the document id, and the value being the document
+ * contents.
+ * @param docsToAdd Specifies data to be added into the query snapshot as of now. Each dictionary
+ * entry maps to a document, with the key being the document id, and the value being the document
+ * contents.
+ * @param hasPendingWrites Whether the query snapshot has pending writes to the server.
+ * @param fromCache Whether the query snapshot is cache result.
+ * @returns A query snapshot that consists of both sets of documents.
+ */
+FIRQuerySnapshot *FSTTestQuerySnapshot(
+ const absl::string_view path,
+ NSDictionary<NSString *, NSDictionary<NSString *, id> *> *oldDocs,
+ NSDictionary<NSString *, NSDictionary<NSString *, id> *> *docsToAdd,
+ BOOL hasPendingWrites,
+ BOOL fromCache);
+
+#if __cplusplus
+} // extern "C"
+#endif
+
+NS_ASSUME_NONNULL_END
diff --git a/Firestore/Example/Tests/API/FSTAPIHelpers.mm b/Firestore/Example/Tests/API/FSTAPIHelpers.mm
new file mode 100644
index 0000000..1ccf22e
--- /dev/null
+++ b/Firestore/Example/Tests/API/FSTAPIHelpers.mm
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2017 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 "Firestore/Example/Tests/API/FSTAPIHelpers.h"
+
+#import <FirebaseFirestore/FIRDocumentChange.h>
+#import <FirebaseFirestore/FIRDocumentReference.h>
+#import <FirebaseFirestore/FIRSnapshotMetadata.h>
+
+#import "Firestore/Source/API/FIRCollectionReference+Internal.h"
+#import "Firestore/Source/API/FIRDocumentReference+Internal.h"
+#import "Firestore/Source/API/FIRDocumentSnapshot+Internal.h"
+#import "Firestore/Source/API/FIRFirestore+Internal.h"
+#import "Firestore/Source/API/FIRQuerySnapshot+Internal.h"
+#import "Firestore/Source/API/FIRSnapshotMetadata+Internal.h"
+#import "Firestore/Source/Core/FSTQuery.h"
+#import "Firestore/Source/Core/FSTViewSnapshot.h"
+#import "Firestore/Source/Model/FSTDocument.h"
+#import "Firestore/Source/Model/FSTDocumentKey.h"
+#import "Firestore/Source/Model/FSTDocumentSet.h"
+
+#include "Firestore/core/src/firebase/firestore/util/string_apple.h"
+#include "Firestore/core/test/firebase/firestore/testutil/testutil.h"
+
+namespace testutil = firebase::firestore::testutil;
+namespace util = firebase::firestore::util;
+
+NS_ASSUME_NONNULL_BEGIN
+
+FIRFirestore *FSTTestFirestore() {
+ static FIRFirestore *sharedInstance = nil;
+ static dispatch_once_t onceToken;
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wnonnull"
+ dispatch_once(&onceToken, ^{
+ sharedInstance = [[FIRFirestore alloc] initWithProjectID:"abc"
+ database:"abc"
+ persistenceKey:@"db123"
+ credentialsProvider:nil
+ workerDispatchQueue:nil
+ firebaseApp:nil];
+ });
+#pragma clang diagnostic pop
+ return sharedInstance;
+}
+
+FIRDocumentSnapshot *FSTTestDocSnapshot(const absl::string_view path,
+ FSTTestSnapshotVersion version,
+ NSDictionary<NSString *, id> *_Nullable data,
+ BOOL hasMutations,
+ BOOL fromCache) {
+ FSTDocument *doc = data ? FSTTestDoc(path, version, data, hasMutations) : nil;
+ return [FIRDocumentSnapshot snapshotWithFirestore:FSTTestFirestore()
+ documentKey:testutil::Key(path)
+ document:doc
+ fromCache:fromCache];
+}
+
+FIRCollectionReference *FSTTestCollectionRef(const absl::string_view path) {
+ return [FIRCollectionReference referenceWithPath:testutil::Resource(path)
+ firestore:FSTTestFirestore()];
+}
+
+FIRDocumentReference *FSTTestDocRef(const absl::string_view path) {
+ return [FIRDocumentReference referenceWithPath:testutil::Resource(path)
+ firestore:FSTTestFirestore()];
+}
+
+/** A convenience method for creating a query snapshots for tests. */
+FIRQuerySnapshot *FSTTestQuerySnapshot(
+ const absl::string_view path,
+ NSDictionary<NSString *, NSDictionary<NSString *, id> *> *oldDocs,
+ NSDictionary<NSString *, NSDictionary<NSString *, id> *> *docsToAdd,
+ BOOL hasPendingWrites,
+ BOOL fromCache) {
+ FIRSnapshotMetadata *metadata =
+ [FIRSnapshotMetadata snapshotMetadataWithPendingWrites:hasPendingWrites fromCache:fromCache];
+ FSTDocumentSet *oldDocuments = FSTTestDocSet(FSTDocumentComparatorByKey, @[]);
+ for (NSString *key in oldDocs) {
+ oldDocuments = [oldDocuments
+ documentSetByAddingDocument:FSTTestDoc(util::MakeStringView([NSString
+ stringWithFormat:@"%s/%@", path.data(), key]),
+ 1, oldDocs[key], hasPendingWrites)];
+ }
+ FSTDocumentSet *newDocuments = oldDocuments;
+ NSArray<FSTDocumentViewChange *> *documentChanges = [NSArray array];
+ for (NSString *key in docsToAdd) {
+ FSTDocument *docToAdd =
+ FSTTestDoc(util::MakeStringView([NSString stringWithFormat:@"%s/%@", path.data(), key]), 1,
+ docsToAdd[key], hasPendingWrites);
+ newDocuments = [newDocuments documentSetByAddingDocument:docToAdd];
+ documentChanges = [documentChanges
+ arrayByAddingObject:[FSTDocumentViewChange
+ changeWithDocument:docToAdd
+ type:FSTDocumentViewChangeTypeAdded]];
+ }
+ FSTViewSnapshot *viewSnapshot = [[FSTViewSnapshot alloc] initWithQuery:FSTTestQuery(path)
+ documents:newDocuments
+ oldDocuments:oldDocuments
+ documentChanges:documentChanges
+ fromCache:fromCache
+ hasPendingWrites:hasPendingWrites
+ syncStateChanged:YES];
+ return [FIRQuerySnapshot snapshotWithFirestore:FSTTestFirestore()
+ originalQuery:FSTTestQuery(path)
+ snapshot:viewSnapshot
+ metadata:metadata];
+}
+
+NS_ASSUME_NONNULL_END
diff --git a/Firestore/Example/Tests/Core/FSTDatabaseInfoTests.m b/Firestore/Example/Tests/Core/FSTDatabaseInfoTests.m
deleted file mode 100644
index c7cf22a..0000000
--- a/Firestore/Example/Tests/Core/FSTDatabaseInfoTests.m
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright 2017 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 "Firestore/Source/Core/FSTDatabaseInfo.h"
-
-#import <XCTest/XCTest.h>
-
-#import "Firestore/Source/Model/FSTDatabaseID.h"
-
-NS_ASSUME_NONNULL_BEGIN
-
-@interface FSTDatabaseInfoTests : XCTestCase
-@end
-
-@implementation FSTDatabaseInfoTests
-
-- (void)testConstructor {
- FSTDatabaseID *databaseID = [FSTDatabaseID databaseIDWithProject:@"p" database:@"d"];
- FSTDatabaseInfo *databaseInfo = [FSTDatabaseInfo databaseInfoWithDatabaseID:databaseID
- persistenceKey:@"pk"
- host:@"h"
- sslEnabled:YES];
- XCTAssertEqualObjects(databaseInfo.databaseID.projectID, @"p");
- XCTAssertEqualObjects(databaseInfo.databaseID.databaseID, @"d");
- XCTAssertEqualObjects(databaseInfo.persistenceKey, @"pk");
- XCTAssertEqualObjects(databaseInfo.host, @"h");
- XCTAssertEqual(databaseInfo.sslEnabled, YES);
-}
-
-- (void)testDefaultDatabase {
- FSTDatabaseID *databaseID =
- [FSTDatabaseID databaseIDWithProject:@"p" database:kDefaultDatabaseID];
- FSTDatabaseInfo *databaseInfo = [FSTDatabaseInfo databaseInfoWithDatabaseID:databaseID
- persistenceKey:@"pk"
- host:@"h"
- sslEnabled:YES];
- XCTAssertEqualObjects(databaseInfo.databaseID.projectID, @"p");
- XCTAssertEqualObjects(databaseInfo.databaseID.databaseID, @"(default)");
- XCTAssertEqualObjects(databaseInfo.persistenceKey, @"pk");
- XCTAssertEqualObjects(databaseInfo.host, @"h");
- XCTAssertEqual(databaseInfo.sslEnabled, YES);
-}
-
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/Firestore/Example/Tests/Core/FSTEventManagerTests.m b/Firestore/Example/Tests/Core/FSTEventManagerTests.mm
index 99021ce..952d01f 100644
--- a/Firestore/Example/Tests/Core/FSTEventManagerTests.m
+++ b/Firestore/Example/Tests/Core/FSTEventManagerTests.mm
@@ -52,7 +52,7 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)testHandlesManyListenersPerQuery {
- FSTQuery *query = [FSTQuery queryWithPath:FSTTestPath(@"foo/bar")];
+ FSTQuery *query = FSTTestQuery("foo/bar");
FSTQueryListener *listener1 = [self noopListenerForQuery:query];
FSTQueryListener *listener2 = [self noopListenerForQuery:query];
@@ -73,7 +73,7 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)testHandlesUnlistenOnUnknownListenerGracefully {
- FSTQuery *query = [FSTQuery queryWithPath:FSTTestPath(@"foo/bar")];
+ FSTQuery *query = FSTTestQuery("foo/bar");
FSTQueryListener *listener = [self noopListenerForQuery:query];
FSTSyncEngine *syncEngineMock = OCMStrictClassMock([FSTSyncEngine class]);
@@ -95,8 +95,8 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)testNotifiesListenersInTheRightOrder {
- FSTQuery *query1 = [FSTQuery queryWithPath:FSTTestPath(@"foo/bar")];
- FSTQuery *query2 = [FSTQuery queryWithPath:FSTTestPath(@"bar/baz")];
+ FSTQuery *query1 = FSTTestQuery("foo/bar");
+ FSTQuery *query2 = FSTTestQuery("bar/baz");
NSMutableArray *eventOrder = [NSMutableArray array];
FSTQueryListener *listener1 = [self makeMockListenerForQuery:query1
@@ -135,17 +135,17 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)testWillForwardOnlineStateChanges {
- FSTQuery *query = [FSTQuery queryWithPath:FSTTestPath(@"foo/bar")];
+ FSTQuery *query = FSTTestQuery("foo/bar");
FSTQueryListener *fakeListener = OCMClassMock([FSTQueryListener class]);
NSMutableArray *events = [NSMutableArray array];
OCMStub([fakeListener query]).andReturn(query);
- OCMStub([fakeListener clientDidChangeOnlineState:FSTOnlineStateUnknown])
+ OCMStub([fakeListener applyChangedOnlineState:FSTOnlineStateUnknown])
.andDo(^(NSInvocation *invocation) {
[events addObject:@(FSTOnlineStateUnknown)];
});
- OCMStub([fakeListener clientDidChangeOnlineState:FSTOnlineStateHealthy])
+ OCMStub([fakeListener applyChangedOnlineState:FSTOnlineStateOnline])
.andDo(^(NSInvocation *invocation) {
- [events addObject:@(FSTOnlineStateHealthy)];
+ [events addObject:@(FSTOnlineStateOnline)];
});
FSTSyncEngine *syncEngineMock = OCMClassMock([FSTSyncEngine class]);
@@ -154,8 +154,8 @@ NS_ASSUME_NONNULL_BEGIN
[eventManager addListener:fakeListener];
XCTAssertEqualObjects(events, @[ @(FSTOnlineStateUnknown) ]);
- [eventManager watchStreamDidChangeOnlineState:FSTOnlineStateHealthy];
- XCTAssertEqualObjects(events, (@[ @(FSTOnlineStateUnknown), @(FSTOnlineStateHealthy) ]));
+ [eventManager applyChangedOnlineState:FSTOnlineStateOnline];
+ XCTAssertEqualObjects(events, (@[ @(FSTOnlineStateUnknown), @(FSTOnlineStateOnline) ]));
}
@end
diff --git a/Firestore/Example/Tests/Core/FSTQueryListenerTests.m b/Firestore/Example/Tests/Core/FSTQueryListenerTests.mm
index 1bb7a47..0454152 100644
--- a/Firestore/Example/Tests/Core/FSTQueryListenerTests.m
+++ b/Firestore/Example/Tests/Core/FSTQueryListenerTests.mm
@@ -14,10 +14,9 @@
* limitations under the License.
*/
-#import "Firestore/Source/Core/FSTEventManager.h"
-
#import <XCTest/XCTest.h>
+#import "Firestore/Source/Core/FSTEventManager.h"
#import "Firestore/Source/Core/FSTQuery.h"
#import "Firestore/Source/Core/FSTView.h"
#import "Firestore/Source/Model/FSTDocument.h"
@@ -45,11 +44,11 @@ NS_ASSUME_NONNULL_BEGIN
NSMutableArray<FSTViewSnapshot *> *accum = [NSMutableArray array];
NSMutableArray<FSTViewSnapshot *> *otherAccum = [NSMutableArray array];
- FSTQuery *query = [FSTQuery queryWithPath:FSTTestPath(@"rooms")];
- FSTDocument *doc1 = FSTTestDoc(@"rooms/Eros", 1, @{@"name" : @"Eros"}, NO);
- FSTDocument *doc2 = FSTTestDoc(@"rooms/Hades", 2, @{@"name" : @"Hades"}, NO);
+ FSTQuery *query = FSTTestQuery("rooms");
+ FSTDocument *doc1 = FSTTestDoc("rooms/Eros", 1, @{@"name" : @"Eros"}, NO);
+ FSTDocument *doc2 = FSTTestDoc("rooms/Hades", 2, @{@"name" : @"Hades"}, NO);
FSTDocument *doc2prime =
- FSTTestDoc(@"rooms/Hades", 3, @{@"name" : @"Hades", @"owner" : @"Jonny"}, NO);
+ FSTTestDoc("rooms/Hades", 3, @{@"name" : @"Hades", @"owner" : @"Jonny"}, NO);
FSTQueryListener *listener = [self listenToQuery:query accumulatingSnapshots:accum];
FSTQueryListener *otherListener = [self listenToQuery:query accumulatingSnapshots:otherAccum];
@@ -88,7 +87,7 @@ NS_ASSUME_NONNULL_BEGIN
- (void)testRaisesErrorEvent {
NSMutableArray<NSError *> *accum = [NSMutableArray array];
- FSTQuery *query = [FSTQuery queryWithPath:FSTTestPath(@"rooms/Eros")];
+ FSTQuery *query = FSTTestQuery("rooms/Eros");
FSTQueryListener *listener = [self listenToQuery:query
handler:^(FSTViewSnapshot *snapshot, NSError *error) {
@@ -104,7 +103,7 @@ NS_ASSUME_NONNULL_BEGIN
- (void)testRaisesEventForEmptyCollectionAfterSync {
NSMutableArray<FSTViewSnapshot *> *accum = [NSMutableArray array];
- FSTQuery *query = [FSTQuery queryWithPath:FSTTestPath(@"rooms")];
+ FSTQuery *query = FSTTestQuery("rooms");
FSTQueryListener *listener = [self listenToQuery:query accumulatingSnapshots:accum];
@@ -126,9 +125,9 @@ NS_ASSUME_NONNULL_BEGIN
- (void)testMutingAsyncListenerPreventsAllSubsequentEvents {
NSMutableArray<FSTViewSnapshot *> *accum = [NSMutableArray array];
- FSTQuery *query = [FSTQuery queryWithPath:FSTTestPath(@"rooms/Eros")];
- FSTDocument *doc1 = FSTTestDoc(@"rooms/Eros", 3, @{@"name" : @"Eros"}, NO);
- FSTDocument *doc2 = FSTTestDoc(@"rooms/Eros", 4, @{@"name" : @"Eros2"}, NO);
+ FSTQuery *query = FSTTestQuery("rooms/Eros");
+ FSTDocument *doc1 = FSTTestDoc("rooms/Eros", 3, @{@"name" : @"Eros"}, NO);
+ FSTDocument *doc2 = FSTTestDoc("rooms/Eros", 4, @{@"name" : @"Eros2"}, NO);
__block FSTAsyncQueryListener *listener = [[FSTAsyncQueryListener alloc]
initWithDispatchQueue:self.asyncQueue
@@ -166,9 +165,9 @@ NS_ASSUME_NONNULL_BEGIN
NSMutableArray<FSTViewSnapshot *> *filteredAccum = [NSMutableArray array];
NSMutableArray<FSTViewSnapshot *> *fullAccum = [NSMutableArray array];
- FSTQuery *query = [FSTQuery queryWithPath:FSTTestPath(@"rooms")];
- FSTDocument *doc1 = FSTTestDoc(@"rooms/Eros", 1, @{@"name" : @"Eros"}, NO);
- FSTDocument *doc2 = FSTTestDoc(@"rooms/Hades", 2, @{@"name" : @"Hades"}, NO);
+ FSTQuery *query = FSTTestQuery("rooms");
+ FSTDocument *doc1 = FSTTestDoc("rooms/Eros", 1, @{@"name" : @"Eros"}, NO);
+ FSTDocument *doc2 = FSTTestDoc("rooms/Hades", 2, @{@"name" : @"Hades"}, NO);
FSTListenOptions *options = [[FSTListenOptions alloc] initWithIncludeQueryMetadataChanges:YES
includeDocumentMetadataChanges:NO
@@ -204,11 +203,11 @@ NS_ASSUME_NONNULL_BEGIN
NSMutableArray<FSTViewSnapshot *> *filteredAccum = [NSMutableArray array];
NSMutableArray<FSTViewSnapshot *> *fullAccum = [NSMutableArray array];
- FSTQuery *query = [FSTQuery queryWithPath:FSTTestPath(@"rooms")];
- FSTDocument *doc1 = FSTTestDoc(@"rooms/Eros", 1, @{@"name" : @"Eros"}, YES);
- FSTDocument *doc2 = FSTTestDoc(@"rooms/Hades", 2, @{@"name" : @"Hades"}, NO);
- FSTDocument *doc1Prime = FSTTestDoc(@"rooms/Eros", 1, @{@"name" : @"Eros"}, NO);
- FSTDocument *doc3 = FSTTestDoc(@"rooms/Other", 3, @{@"name" : @"Other"}, NO);
+ FSTQuery *query = FSTTestQuery("rooms");
+ FSTDocument *doc1 = FSTTestDoc("rooms/Eros", 1, @{@"name" : @"Eros"}, YES);
+ FSTDocument *doc2 = FSTTestDoc("rooms/Hades", 2, @{@"name" : @"Hades"}, NO);
+ FSTDocument *doc1Prime = FSTTestDoc("rooms/Eros", 1, @{@"name" : @"Eros"}, NO);
+ FSTDocument *doc3 = FSTTestDoc("rooms/Other", 3, @{@"name" : @"Other"}, NO);
FSTListenOptions *options = [[FSTListenOptions alloc] initWithIncludeQueryMetadataChanges:NO
includeDocumentMetadataChanges:YES
@@ -253,12 +252,12 @@ NS_ASSUME_NONNULL_BEGIN
- (void)testRaisesQueryMetadataEventsOnlyWhenHasPendingWritesOnTheQueryChanges {
NSMutableArray<FSTViewSnapshot *> *fullAccum = [NSMutableArray array];
- FSTQuery *query = [FSTQuery queryWithPath:FSTTestPath(@"rooms")];
- FSTDocument *doc1 = FSTTestDoc(@"rooms/Eros", 1, @{@"name" : @"Eros"}, YES);
- FSTDocument *doc2 = FSTTestDoc(@"rooms/Hades", 2, @{@"name" : @"Hades"}, YES);
- FSTDocument *doc1Prime = FSTTestDoc(@"rooms/Eros", 1, @{@"name" : @"Eros"}, NO);
- FSTDocument *doc2Prime = FSTTestDoc(@"rooms/Hades", 2, @{@"name" : @"Hades"}, NO);
- FSTDocument *doc3 = FSTTestDoc(@"rooms/Other", 3, @{@"name" : @"Other"}, NO);
+ FSTQuery *query = FSTTestQuery("rooms");
+ FSTDocument *doc1 = FSTTestDoc("rooms/Eros", 1, @{@"name" : @"Eros"}, YES);
+ FSTDocument *doc2 = FSTTestDoc("rooms/Hades", 2, @{@"name" : @"Hades"}, YES);
+ FSTDocument *doc1Prime = FSTTestDoc("rooms/Eros", 1, @{@"name" : @"Eros"}, NO);
+ FSTDocument *doc2Prime = FSTTestDoc("rooms/Hades", 2, @{@"name" : @"Hades"}, NO);
+ FSTDocument *doc3 = FSTTestDoc("rooms/Other", 3, @{@"name" : @"Other"}, NO);
FSTListenOptions *options = [[FSTListenOptions alloc] initWithIncludeQueryMetadataChanges:YES
includeDocumentMetadataChanges:NO
@@ -290,11 +289,11 @@ NS_ASSUME_NONNULL_BEGIN
- (void)testMetadataOnlyDocumentChangesAreFilteredOutWhenIncludeDocumentMetadataChangesIsFalse {
NSMutableArray<FSTViewSnapshot *> *filteredAccum = [NSMutableArray array];
- FSTQuery *query = [FSTQuery queryWithPath:FSTTestPath(@"rooms")];
- FSTDocument *doc1 = FSTTestDoc(@"rooms/Eros", 1, @{@"name" : @"Eros"}, YES);
- FSTDocument *doc2 = FSTTestDoc(@"rooms/Hades", 2, @{@"name" : @"Hades"}, NO);
- FSTDocument *doc1Prime = FSTTestDoc(@"rooms/Eros", 1, @{@"name" : @"Eros"}, NO);
- FSTDocument *doc3 = FSTTestDoc(@"rooms/Other", 3, @{@"name" : @"Other"}, NO);
+ FSTQuery *query = FSTTestQuery("rooms");
+ FSTDocument *doc1 = FSTTestDoc("rooms/Eros", 1, @{@"name" : @"Eros"}, YES);
+ FSTDocument *doc2 = FSTTestDoc("rooms/Hades", 2, @{@"name" : @"Hades"}, NO);
+ FSTDocument *doc1Prime = FSTTestDoc("rooms/Eros", 1, @{@"name" : @"Eros"}, NO);
+ FSTDocument *doc3 = FSTTestDoc("rooms/Other", 3, @{@"name" : @"Other"}, NO);
FSTQueryListener *filteredListener =
[self listenToQuery:query accumulatingSnapshots:filteredAccum];
@@ -322,9 +321,9 @@ NS_ASSUME_NONNULL_BEGIN
- (void)testWillWaitForSyncIfOnline {
NSMutableArray<FSTViewSnapshot *> *events = [NSMutableArray array];
- FSTQuery *query = [FSTQuery queryWithPath:FSTTestPath(@"rooms")];
- FSTDocument *doc1 = FSTTestDoc(@"rooms/Eros", 1, @{@"name" : @"Eros"}, NO);
- FSTDocument *doc2 = FSTTestDoc(@"rooms/Hades", 2, @{@"name" : @"Hades"}, NO);
+ FSTQuery *query = FSTTestQuery("rooms");
+ FSTDocument *doc1 = FSTTestDoc("rooms/Eros", 1, @{@"name" : @"Eros"}, NO);
+ FSTDocument *doc2 = FSTTestDoc("rooms/Hades", 2, @{@"name" : @"Hades"}, NO);
FSTQueryListener *listener =
[self listenToQuery:query
options:[[FSTListenOptions alloc] initWithIncludeQueryMetadataChanges:NO
@@ -340,10 +339,10 @@ NS_ASSUME_NONNULL_BEGIN
[FSTTargetChange changeWithDocuments:@[ doc1, doc2 ]
currentStatusUpdate:FSTCurrentStatusUpdateMarkCurrent]);
- [listener clientDidChangeOnlineState:FSTOnlineStateHealthy]; // no event
+ [listener applyChangedOnlineState:FSTOnlineStateOnline]; // no event
[listener queryDidChangeViewSnapshot:snap1];
- [listener clientDidChangeOnlineState:FSTOnlineStateUnknown];
- [listener clientDidChangeOnlineState:FSTOnlineStateHealthy];
+ [listener applyChangedOnlineState:FSTOnlineStateUnknown];
+ [listener applyChangedOnlineState:FSTOnlineStateOnline];
[listener queryDidChangeViewSnapshot:snap2];
[listener queryDidChangeViewSnapshot:snap3];
@@ -365,9 +364,9 @@ NS_ASSUME_NONNULL_BEGIN
- (void)testWillRaiseInitialEventWhenGoingOffline {
NSMutableArray<FSTViewSnapshot *> *events = [NSMutableArray array];
- FSTQuery *query = [FSTQuery queryWithPath:FSTTestPath(@"rooms")];
- FSTDocument *doc1 = FSTTestDoc(@"rooms/Eros", 1, @{@"name" : @"Eros"}, NO);
- FSTDocument *doc2 = FSTTestDoc(@"rooms/Hades", 2, @{@"name" : @"Hades"}, NO);
+ FSTQuery *query = FSTTestQuery("rooms");
+ FSTDocument *doc1 = FSTTestDoc("rooms/Eros", 1, @{@"name" : @"Eros"}, NO);
+ FSTDocument *doc2 = FSTTestDoc("rooms/Hades", 2, @{@"name" : @"Hades"}, NO);
FSTQueryListener *listener =
[self listenToQuery:query
options:[[FSTListenOptions alloc] initWithIncludeQueryMetadataChanges:NO
@@ -379,12 +378,12 @@ NS_ASSUME_NONNULL_BEGIN
FSTViewSnapshot *snap1 = FSTTestApplyChanges(view, @[ doc1 ], nil);
FSTViewSnapshot *snap2 = FSTTestApplyChanges(view, @[ doc2 ], nil);
- [listener clientDidChangeOnlineState:FSTOnlineStateHealthy]; // no event
- [listener queryDidChangeViewSnapshot:snap1]; // no event
- [listener clientDidChangeOnlineState:FSTOnlineStateFailed]; // event
- [listener clientDidChangeOnlineState:FSTOnlineStateUnknown]; // no event
- [listener clientDidChangeOnlineState:FSTOnlineStateFailed]; // no event
- [listener queryDidChangeViewSnapshot:snap2]; // another event
+ [listener applyChangedOnlineState:FSTOnlineStateOnline]; // no event
+ [listener queryDidChangeViewSnapshot:snap1]; // no event
+ [listener applyChangedOnlineState:FSTOnlineStateOffline]; // event
+ [listener applyChangedOnlineState:FSTOnlineStateUnknown]; // no event
+ [listener applyChangedOnlineState:FSTOnlineStateOffline]; // no event
+ [listener queryDidChangeViewSnapshot:snap2]; // another event
FSTDocumentViewChange *change1 =
[FSTDocumentViewChange changeWithDocument:doc1 type:FSTDocumentViewChangeTypeAdded];
@@ -411,7 +410,7 @@ NS_ASSUME_NONNULL_BEGIN
- (void)testWillRaiseInitialEventWhenGoingOfflineAndThereAreNoDocs {
NSMutableArray<FSTViewSnapshot *> *events = [NSMutableArray array];
- FSTQuery *query = [FSTQuery queryWithPath:FSTTestPath(@"rooms")];
+ FSTQuery *query = FSTTestQuery("rooms");
FSTQueryListener *listener = [self listenToQuery:query
options:[FSTListenOptions defaultOptions]
accumulatingSnapshots:events];
@@ -419,9 +418,9 @@ NS_ASSUME_NONNULL_BEGIN
FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
FSTViewSnapshot *snap1 = FSTTestApplyChanges(view, @[], nil);
- [listener clientDidChangeOnlineState:FSTOnlineStateHealthy]; // no event
- [listener queryDidChangeViewSnapshot:snap1]; // no event
- [listener clientDidChangeOnlineState:FSTOnlineStateFailed]; // event
+ [listener applyChangedOnlineState:FSTOnlineStateOnline]; // no event
+ [listener queryDidChangeViewSnapshot:snap1]; // no event
+ [listener applyChangedOnlineState:FSTOnlineStateOffline]; // event
FSTViewSnapshot *expectedSnap = [[FSTViewSnapshot alloc]
initWithQuery:query
@@ -437,7 +436,7 @@ NS_ASSUME_NONNULL_BEGIN
- (void)testWillRaiseInitialEventWhenStartingOfflineAndThereAreNoDocs {
NSMutableArray<FSTViewSnapshot *> *events = [NSMutableArray array];
- FSTQuery *query = [FSTQuery queryWithPath:FSTTestPath(@"rooms")];
+ FSTQuery *query = FSTTestQuery("rooms");
FSTQueryListener *listener = [self listenToQuery:query
options:[FSTListenOptions defaultOptions]
accumulatingSnapshots:events];
@@ -445,8 +444,8 @@ NS_ASSUME_NONNULL_BEGIN
FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
FSTViewSnapshot *snap1 = FSTTestApplyChanges(view, @[], nil);
- [listener clientDidChangeOnlineState:FSTOnlineStateFailed]; // no event
- [listener queryDidChangeViewSnapshot:snap1]; // event
+ [listener applyChangedOnlineState:FSTOnlineStateOffline]; // no event
+ [listener queryDidChangeViewSnapshot:snap1]; // event
FSTViewSnapshot *expectedSnap = [[FSTViewSnapshot alloc]
initWithQuery:query
diff --git a/Firestore/Example/Tests/Core/FSTQueryTests.m b/Firestore/Example/Tests/Core/FSTQueryTests.m
deleted file mode 100644
index 1fd0e8b..0000000
--- a/Firestore/Example/Tests/Core/FSTQueryTests.m
+++ /dev/null
@@ -1,577 +0,0 @@
-/*
- * Copyright 2017 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 "Firestore/Source/Core/FSTQuery.h"
-
-#import <XCTest/XCTest.h>
-
-#import "Firestore/Source/API/FIRFirestore+Internal.h"
-#import "Firestore/Source/Model/FSTDatabaseID.h"
-#import "Firestore/Source/Model/FSTDocument.h"
-#import "Firestore/Source/Model/FSTDocumentKey.h"
-#import "Firestore/Source/Model/FSTPath.h"
-
-#import "Firestore/Example/Tests/Util/FSTHelpers.h"
-
-NS_ASSUME_NONNULL_BEGIN
-
-/** Convenience methods for building test queries. */
-@interface FSTQuery (Tests)
-- (FSTQuery *)queryByAddingSortBy:(NSString *)key ascending:(BOOL)ascending;
-@end
-
-@implementation FSTQuery (Tests)
-
-- (FSTQuery *)queryByAddingSortBy:(NSString *)key ascending:(BOOL)ascending {
- return [self queryByAddingSortOrder:[FSTSortOrder sortOrderWithFieldPath:FSTTestFieldPath(key)
- ascending:ascending]];
-}
-
-@end
-
-@interface FSTQueryTests : XCTestCase
-@end
-
-@implementation FSTQueryTests
-
-- (void)testConstructor {
- FSTResourcePath *path =
- [FSTResourcePath pathWithSegments:@[ @"rooms", @"Firestore", @"messages", @"0001" ]];
- FSTQuery *query = [FSTQuery queryWithPath:path];
- XCTAssertNotNil(query);
-
- XCTAssertEqual(query.sortOrders.count, 1);
- XCTAssertEqualObjects(query.sortOrders[0].field.canonicalString, kDocumentKeyPath);
- XCTAssertEqual(query.sortOrders[0].ascending, YES);
-
- XCTAssertEqual(query.explicitSortOrders.count, 0);
-}
-
-- (void)testOrderBy {
- FSTResourcePath *path =
- [FSTResourcePath pathWithSegments:@[ @"rooms", @"Firestore", @"messages" ]];
- FSTQuery *query = [FSTQuery queryWithPath:path];
- query =
- [query queryByAddingSortOrder:[FSTSortOrder sortOrderWithFieldPath:FSTTestFieldPath(@"length")
- ascending:NO]];
-
- XCTAssertEqual(query.sortOrders.count, 2);
- XCTAssertEqualObjects(query.sortOrders[0].field.canonicalString, @"length");
- XCTAssertEqual(query.sortOrders[0].ascending, NO);
- XCTAssertEqualObjects(query.sortOrders[1].field.canonicalString, kDocumentKeyPath);
- XCTAssertEqual(query.sortOrders[1].ascending, NO);
-
- XCTAssertEqual(query.explicitSortOrders.count, 1);
- XCTAssertEqualObjects(query.explicitSortOrders[0].field.canonicalString, @"length");
- XCTAssertEqual(query.explicitSortOrders[0].ascending, NO);
-}
-
-- (void)testMatchesBasedOnDocumentKey {
- FSTResourcePath *queryKey =
- [FSTResourcePath pathWithSegments:@[ @"rooms", @"eros", @"messages", @"1" ]];
- FSTDocument *doc1 = FSTTestDoc(@"rooms/eros/messages/1", 0, @{@"text" : @"msg1"}, NO);
- FSTDocument *doc2 = FSTTestDoc(@"rooms/eros/messages/2", 0, @{@"text" : @"msg2"}, NO);
- FSTDocument *doc3 = FSTTestDoc(@"rooms/other/messages/1", 0, @{@"text" : @"msg3"}, NO);
-
- // document query
- FSTQuery *query = [FSTQuery queryWithPath:queryKey];
- XCTAssertTrue([query matchesDocument:doc1]);
- XCTAssertFalse([query matchesDocument:doc2]);
- XCTAssertFalse([query matchesDocument:doc3]);
-}
-
-- (void)testMatchesCorrectlyForShallowAncestorQuery {
- FSTResourcePath *queryPath =
- [FSTResourcePath pathWithSegments:@[ @"rooms", @"eros", @"messages" ]];
- FSTDocument *doc1 = FSTTestDoc(@"rooms/eros/messages/1", 0, @{@"text" : @"msg1"}, NO);
- FSTDocument *doc1Meta = FSTTestDoc(@"rooms/eros/messages/1/meta/1", 0, @{@"meta" : @"mv"}, NO);
- FSTDocument *doc2 = FSTTestDoc(@"rooms/eros/messages/2", 0, @{@"text" : @"msg2"}, NO);
- FSTDocument *doc3 = FSTTestDoc(@"rooms/other/messages/1", 0, @{@"text" : @"msg3"}, NO);
-
- // shallow ancestor query
- FSTQuery *query = [FSTQuery queryWithPath:queryPath];
- XCTAssertTrue([query matchesDocument:doc1]);
- XCTAssertFalse([query matchesDocument:doc1Meta]);
- XCTAssertTrue([query matchesDocument:doc2]);
- XCTAssertFalse([query matchesDocument:doc3]);
-}
-
-- (void)testEmptyFieldsAreAllowedForQueries {
- FSTResourcePath *queryPath = [FSTResourcePath pathWithString:@"rooms/eros/messages"];
- FSTDocument *doc1 = FSTTestDoc(@"rooms/eros/messages/1", 0, @{@"text" : @"msg1"}, NO);
- FSTDocument *doc2 = FSTTestDoc(@"rooms/eros/messages/2", 0, @{}, NO);
-
- FSTQuery *query = [[FSTQuery queryWithPath:queryPath]
- queryByAddingFilter:FSTTestFilter(@"text", @"==", @"msg1")];
- XCTAssertTrue([query matchesDocument:doc1]);
- XCTAssertFalse([query matchesDocument:doc2]);
-}
-
-- (void)testMatchesPrimitiveValuesForFilters {
- FSTQuery *query1 = [[FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"collection" ]]]
- queryByAddingFilter:FSTTestFilter(@"sort", @">=", @(2))];
- FSTQuery *query2 = [[FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"collection" ]]]
- queryByAddingFilter:FSTTestFilter(@"sort", @"<=", @(2))];
-
- FSTDocument *doc1 = FSTTestDoc(@"collection/1", 0, @{ @"sort" : @1 }, NO);
- FSTDocument *doc2 = FSTTestDoc(@"collection/2", 0, @{ @"sort" : @2 }, NO);
- FSTDocument *doc3 = FSTTestDoc(@"collection/3", 0, @{ @"sort" : @3 }, NO);
- FSTDocument *doc4 = FSTTestDoc(@"collection/4", 0, @{ @"sort" : @NO }, NO);
- FSTDocument *doc5 = FSTTestDoc(@"collection/5", 0, @{@"sort" : @"string"}, NO);
- FSTDocument *doc6 = FSTTestDoc(@"collection/6", 0, @{}, NO);
-
- XCTAssertFalse([query1 matchesDocument:doc1]);
- XCTAssertTrue([query1 matchesDocument:doc2]);
- XCTAssertTrue([query1 matchesDocument:doc3]);
- XCTAssertFalse([query1 matchesDocument:doc4]);
- XCTAssertFalse([query1 matchesDocument:doc5]);
- XCTAssertFalse([query1 matchesDocument:doc6]);
-
- XCTAssertTrue([query2 matchesDocument:doc1]);
- XCTAssertTrue([query2 matchesDocument:doc2]);
- XCTAssertFalse([query2 matchesDocument:doc3]);
- XCTAssertFalse([query2 matchesDocument:doc4]);
- XCTAssertFalse([query2 matchesDocument:doc5]);
- XCTAssertFalse([query2 matchesDocument:doc6]);
-}
-
-- (void)testNullFilter {
- FSTQuery *query = [[FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"collection" ]]]
- queryByAddingFilter:FSTTestFilter(@"sort", @"==", [NSNull null])];
- FSTDocument *doc1 = FSTTestDoc(@"collection/1", 0, @{@"sort" : [NSNull null]}, NO);
- FSTDocument *doc2 = FSTTestDoc(@"collection/2", 0, @{ @"sort" : @2 }, NO);
- FSTDocument *doc3 = FSTTestDoc(@"collection/2", 0, @{ @"sort" : @3.1 }, NO);
- FSTDocument *doc4 = FSTTestDoc(@"collection/4", 0, @{ @"sort" : @NO }, NO);
- FSTDocument *doc5 = FSTTestDoc(@"collection/5", 0, @{@"sort" : @"string"}, NO);
-
- XCTAssertTrue([query matchesDocument:doc1]);
- XCTAssertFalse([query matchesDocument:doc2]);
- XCTAssertFalse([query matchesDocument:doc3]);
- XCTAssertFalse([query matchesDocument:doc4]);
- XCTAssertFalse([query matchesDocument:doc5]);
-}
-
-- (void)testNanFilter {
- FSTQuery *query = [[FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"collection" ]]]
- queryByAddingFilter:FSTTestFilter(@"sort", @"==", @(NAN))];
- FSTDocument *doc1 = FSTTestDoc(@"collection/1", 0, @{ @"sort" : @(NAN) }, NO);
- FSTDocument *doc2 = FSTTestDoc(@"collection/2", 0, @{ @"sort" : @2 }, NO);
- FSTDocument *doc3 = FSTTestDoc(@"collection/2", 0, @{ @"sort" : @3.1 }, NO);
- FSTDocument *doc4 = FSTTestDoc(@"collection/4", 0, @{ @"sort" : @NO }, NO);
- FSTDocument *doc5 = FSTTestDoc(@"collection/5", 0, @{@"sort" : @"string"}, NO);
-
- XCTAssertTrue([query matchesDocument:doc1]);
- XCTAssertFalse([query matchesDocument:doc2]);
- XCTAssertFalse([query matchesDocument:doc3]);
- XCTAssertFalse([query matchesDocument:doc4]);
- XCTAssertFalse([query matchesDocument:doc5]);
-}
-
-- (void)testDoesNotMatchComplexObjectsForFilters {
- FSTQuery *query1 = [[FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"collection" ]]]
- queryByAddingFilter:FSTTestFilter(@"sort", @"<=", @(2))];
- FSTQuery *query2 = [[FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"collection" ]]]
- queryByAddingFilter:FSTTestFilter(@"sort", @">=", @(2))];
-
- FSTDocument *doc1 = FSTTestDoc(@"collection/1", 0, @{ @"sort" : @2 }, NO);
- FSTDocument *doc2 = FSTTestDoc(@"collection/2", 0, @{ @"sort" : @[] }, NO);
- FSTDocument *doc3 = FSTTestDoc(@"collection/3", 0, @{ @"sort" : @[ @1 ] }, NO);
- FSTDocument *doc4 = FSTTestDoc(@"collection/4", 0, @{ @"sort" : @{@"foo" : @2} }, NO);
- FSTDocument *doc5 = FSTTestDoc(@"collection/5", 0, @{ @"sort" : @{@"foo" : @"bar"} }, NO);
- FSTDocument *doc6 = FSTTestDoc(@"collection/6", 0, @{ @"sort" : @{} }, NO); // no sort field
- FSTDocument *doc7 = FSTTestDoc(@"collection/7", 0, @{ @"sort" : @[ @3, @1 ] }, NO);
-
- XCTAssertTrue([query1 matchesDocument:doc1]);
- XCTAssertFalse([query1 matchesDocument:doc2]);
- XCTAssertFalse([query1 matchesDocument:doc3]);
- XCTAssertFalse([query1 matchesDocument:doc4]);
- XCTAssertFalse([query1 matchesDocument:doc5]);
- XCTAssertFalse([query1 matchesDocument:doc6]);
- XCTAssertFalse([query1 matchesDocument:doc7]);
-
- XCTAssertTrue([query2 matchesDocument:doc1]);
- XCTAssertFalse([query2 matchesDocument:doc2]);
- XCTAssertFalse([query2 matchesDocument:doc3]);
- XCTAssertFalse([query2 matchesDocument:doc4]);
- XCTAssertFalse([query2 matchesDocument:doc5]);
- XCTAssertFalse([query2 matchesDocument:doc6]);
- XCTAssertFalse([query2 matchesDocument:doc7]);
-}
-
-- (void)testDoesntRemoveComplexObjectsWithOrderBy {
- FSTQuery *query1 = [[FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"collection" ]]]
- queryByAddingSortOrder:[FSTSortOrder sortOrderWithFieldPath:FSTTestFieldPath(@"sort")
- ascending:YES]];
-
- FSTDocument *doc1 = FSTTestDoc(@"collection/1", 0, @{ @"sort" : @2 }, NO);
- FSTDocument *doc2 = FSTTestDoc(@"collection/2", 0, @{ @"sort" : @[] }, NO);
- FSTDocument *doc3 = FSTTestDoc(@"collection/3", 0, @{ @"sort" : @[ @1 ] }, NO);
- FSTDocument *doc4 = FSTTestDoc(@"collection/4", 0, @{ @"sort" : @{@"foo" : @2} }, NO);
- FSTDocument *doc5 = FSTTestDoc(@"collection/5", 0, @{ @"sort" : @{@"foo" : @"bar"} }, NO);
- FSTDocument *doc6 = FSTTestDoc(@"collection/6", 0, @{}, NO);
-
- XCTAssertTrue([query1 matchesDocument:doc1]);
- XCTAssertTrue([query1 matchesDocument:doc2]);
- XCTAssertTrue([query1 matchesDocument:doc3]);
- XCTAssertTrue([query1 matchesDocument:doc4]);
- XCTAssertTrue([query1 matchesDocument:doc5]);
- XCTAssertFalse([query1 matchesDocument:doc6]);
-}
-
-- (void)testFiltersBasedOnArrayValue {
- FSTQuery *baseQuery =
- [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"collection" ]]];
-
- FSTDocument *doc1 = FSTTestDoc(@"collection/doc", 0, @{ @"tags" : @[ @"foo", @1, @YES ] }, NO);
-
- NSArray<id<FSTFilter>> *matchingFilters =
- @[ FSTTestFilter(@"tags", @"==", @[ @"foo", @1, @YES ]) ];
-
- NSArray<id<FSTFilter>> *nonMatchingFilters = @[
- FSTTestFilter(@"tags", @"==", @"foo"),
- FSTTestFilter(@"tags", @"==", @[ @"foo", @1 ]),
- FSTTestFilter(@"tags", @"==", @[ @"foo", @YES, @1 ]),
- ];
-
- for (id<FSTFilter> filter in matchingFilters) {
- XCTAssertTrue([[baseQuery queryByAddingFilter:filter] matchesDocument:doc1]);
- }
-
- for (id<FSTFilter> filter in nonMatchingFilters) {
- XCTAssertFalse([[baseQuery queryByAddingFilter:filter] matchesDocument:doc1]);
- }
-}
-
-- (void)testFiltersBasedOnObjectValue {
- FSTQuery *baseQuery =
- [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"collection" ]]];
-
- FSTDocument *doc1 =
- FSTTestDoc(@"collection/doc", 0,
- @{ @"tags" : @{@"foo" : @"foo", @"a" : @0, @"b" : @YES, @"c" : @(NAN)} }, NO);
-
- NSArray<id<FSTFilter>> *matchingFilters = @[
- FSTTestFilter(@"tags", @"==",
- @{ @"foo" : @"foo",
- @"a" : @0,
- @"b" : @YES,
- @"c" : @(NAN) }),
- FSTTestFilter(@"tags", @"==",
- @{ @"b" : @YES,
- @"a" : @0,
- @"foo" : @"foo",
- @"c" : @(NAN) }),
- FSTTestFilter(@"tags.foo", @"==", @"foo")
- ];
-
- NSArray<id<FSTFilter>> *nonMatchingFilters = @[
- FSTTestFilter(@"tags", @"==", @"foo"), FSTTestFilter(@"tags", @"==", @{
- @"foo" : @"foo",
- @"a" : @0,
- @"b" : @YES,
- })
- ];
-
- for (id<FSTFilter> filter in matchingFilters) {
- XCTAssertTrue([[baseQuery queryByAddingFilter:filter] matchesDocument:doc1]);
- }
-
- for (id<FSTFilter> filter in nonMatchingFilters) {
- XCTAssertFalse([[baseQuery queryByAddingFilter:filter] matchesDocument:doc1]);
- }
-}
-
-/**
- * Checks that an ordered array of elements yields the correct pair-wise comparison result for the
- * supplied comparator.
- */
-- (void)assertCorrectComparisonsWithArray:(NSArray *)array comparator:(NSComparator)comp {
- [array enumerateObjectsUsingBlock:^(id iObj, NSUInteger i, BOOL *outerStop) {
- [array enumerateObjectsUsingBlock:^(id _Nonnull jObj, NSUInteger j, BOOL *innerStop) {
- NSComparisonResult expected = [@(i) compare:@(j)];
- NSComparisonResult actual = comp(iObj, jObj);
- XCTAssertEqual(actual, expected, @"Compared %@ to %@ at (%lu, %lu).", iObj, jObj,
- (unsigned long)i, (unsigned long)j);
- }];
- }];
-}
-
-- (void)testSortsDocumentsInTheCorrectOrder {
- FSTQuery *query = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"collection" ]]];
- query =
- [query queryByAddingSortOrder:[FSTSortOrder sortOrderWithFieldPath:FSTTestFieldPath(@"sort")
- ascending:YES]];
-
- // clang-format off
- NSArray<FSTDocument *> *docs = @[
- FSTTestDoc(@"collection/1", 0, @{@"sort": [NSNull null]}, NO),
- FSTTestDoc(@"collection/1", 0, @{@"sort": @NO}, NO),
- FSTTestDoc(@"collection/1", 0, @{@"sort": @YES}, NO),
- FSTTestDoc(@"collection/1", 0, @{@"sort": @1}, NO),
- FSTTestDoc(@"collection/2", 0, @{@"sort": @1}, NO), // by key
- FSTTestDoc(@"collection/3", 0, @{@"sort": @1}, NO), // by key
- FSTTestDoc(@"collection/1", 0, @{@"sort": @1.9}, NO),
- FSTTestDoc(@"collection/1", 0, @{@"sort": @2}, NO),
- FSTTestDoc(@"collection/1", 0, @{@"sort": @2.1}, NO),
- FSTTestDoc(@"collection/1", 0, @{@"sort": @""}, NO),
- FSTTestDoc(@"collection/1", 0, @{@"sort": @"a"}, NO),
- FSTTestDoc(@"collection/1", 0, @{@"sort": @"ab"}, NO),
- FSTTestDoc(@"collection/1", 0, @{@"sort": @"b"}, NO),
- FSTTestDoc(@"collection/1", 0, @{@"sort":
- FSTTestRef(@"project", kDefaultDatabaseID, @"collection/id1")}, NO),
- ];
- // clang-format on
-
- [self assertCorrectComparisonsWithArray:docs comparator:query.comparator];
-}
-
-- (void)testSortsDocumentsUsingMultipleFields {
- FSTQuery *query = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"collection" ]]];
- query =
- [query queryByAddingSortOrder:[FSTSortOrder sortOrderWithFieldPath:FSTTestFieldPath(@"sort1")
- ascending:YES]];
- query =
- [query queryByAddingSortOrder:[FSTSortOrder sortOrderWithFieldPath:FSTTestFieldPath(@"sort2")
- ascending:YES]];
-
- // clang-format off
- NSArray<FSTDocument *> *docs =
- @[FSTTestDoc(@"collection/1", 0, @{@"sort1": @1, @"sort2": @1}, NO),
- FSTTestDoc(@"collection/1", 0, @{@"sort1": @1, @"sort2": @2}, NO),
- FSTTestDoc(@"collection/2", 0, @{@"sort1": @1, @"sort2": @2}, NO), // by key
- FSTTestDoc(@"collection/3", 0, @{@"sort1": @1, @"sort2": @2}, NO), // by key
- FSTTestDoc(@"collection/1", 0, @{@"sort1": @1, @"sort2": @3}, NO),
- FSTTestDoc(@"collection/1", 0, @{@"sort1": @2, @"sort2": @1}, NO),
- FSTTestDoc(@"collection/1", 0, @{@"sort1": @2, @"sort2": @2}, NO),
- FSTTestDoc(@"collection/2", 0, @{@"sort1": @2, @"sort2": @2}, NO), // by key
- FSTTestDoc(@"collection/3", 0, @{@"sort1": @2, @"sort2": @2}, NO), // by key
- FSTTestDoc(@"collection/1", 0, @{@"sort1": @2, @"sort2": @3}, NO),
- ];
- // clang-format on
-
- [self assertCorrectComparisonsWithArray:docs comparator:query.comparator];
-}
-
-- (void)testSortsDocumentsWithDescendingToo {
- FSTQuery *query = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"collection" ]]];
- query =
- [query queryByAddingSortOrder:[FSTSortOrder sortOrderWithFieldPath:FSTTestFieldPath(@"sort1")
- ascending:NO]];
- query =
- [query queryByAddingSortOrder:[FSTSortOrder sortOrderWithFieldPath:FSTTestFieldPath(@"sort2")
- ascending:NO]];
-
- // clang-format off
- NSArray<FSTDocument *> *docs =
- @[FSTTestDoc(@"collection/1", 0, @{@"sort1": @2, @"sort2": @3}, NO),
- FSTTestDoc(@"collection/3", 0, @{@"sort1": @2, @"sort2": @2}, NO),
- FSTTestDoc(@"collection/2", 0, @{@"sort1": @2, @"sort2": @2}, NO), // by key
- FSTTestDoc(@"collection/1", 0, @{@"sort1": @2, @"sort2": @2}, NO), // by key
- FSTTestDoc(@"collection/1", 0, @{@"sort1": @2, @"sort2": @1}, NO),
- FSTTestDoc(@"collection/1", 0, @{@"sort1": @1, @"sort2": @3}, NO),
- FSTTestDoc(@"collection/3", 0, @{@"sort1": @1, @"sort2": @2}, NO),
- FSTTestDoc(@"collection/2", 0, @{@"sort1": @1, @"sort2": @2}, NO), // by key
- FSTTestDoc(@"collection/1", 0, @{@"sort1": @1, @"sort2": @2}, NO), // by key
- FSTTestDoc(@"collection/1", 0, @{@"sort1": @1, @"sort2": @1}, NO),
- ];
- // clang-format on
-
- [self assertCorrectComparisonsWithArray:docs comparator:query.comparator];
-}
-
-- (void)testEquality {
- FSTQuery *q11 = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo" ]]];
- q11 = [q11 queryByAddingFilter:FSTTestFilter(@"i1", @"<", @(2))];
- q11 = [q11 queryByAddingFilter:FSTTestFilter(@"i2", @"==", @(3))];
- FSTQuery *q12 = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo" ]]];
- q12 = [q12 queryByAddingFilter:FSTTestFilter(@"i2", @"==", @(3))];
- q12 = [q12 queryByAddingFilter:FSTTestFilter(@"i1", @"<", @(2))];
-
- FSTQuery *q21 = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo" ]]];
- FSTQuery *q22 = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo" ]]];
-
- FSTQuery *q31 = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo", @"bar" ]]];
- FSTQuery *q32 = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo", @"bar" ]]];
-
- FSTQuery *q41 = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo" ]]];
- q41 = [q41 queryByAddingSortBy:@"foo" ascending:YES];
- q41 = [q41 queryByAddingSortBy:@"bar" ascending:YES];
- FSTQuery *q42 = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo" ]]];
- q42 = [q42 queryByAddingSortBy:@"foo" ascending:YES];
- q42 = [q42 queryByAddingSortBy:@"bar" ascending:YES];
- FSTQuery *q43Diff = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo" ]]];
- q43Diff = [q43Diff queryByAddingSortBy:@"bar" ascending:YES];
- q43Diff = [q43Diff queryByAddingSortBy:@"foo" ascending:YES];
-
- FSTQuery *q51 = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo" ]]];
- q51 = [q51 queryByAddingSortBy:@"foo" ascending:YES];
- q51 = [q51 queryByAddingFilter:FSTTestFilter(@"foo", @">", @(2))];
- FSTQuery *q52 = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo" ]]];
- q52 = [q52 queryByAddingFilter:FSTTestFilter(@"foo", @">", @(2))];
- q52 = [q52 queryByAddingSortBy:@"foo" ascending:YES];
- FSTQuery *q53Diff = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo" ]]];
- q53Diff = [q53Diff queryByAddingFilter:FSTTestFilter(@"bar", @">", @(2))];
- q53Diff = [q53Diff queryByAddingSortBy:@"bar" ascending:YES];
-
- FSTQuery *q61 = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo" ]]];
- q61 = [q61 queryBySettingLimit:10];
-
- // XCTAssertEqualObjects(q11, q12); // TODO(klimt): not canonical yet
- XCTAssertNotEqualObjects(q11, q21);
- XCTAssertNotEqualObjects(q11, q31);
- XCTAssertNotEqualObjects(q11, q41);
- XCTAssertNotEqualObjects(q11, q51);
- XCTAssertNotEqualObjects(q11, q61);
-
- XCTAssertEqualObjects(q21, q22);
- XCTAssertNotEqualObjects(q21, q31);
- XCTAssertNotEqualObjects(q21, q41);
- XCTAssertNotEqualObjects(q21, q51);
- XCTAssertNotEqualObjects(q21, q61);
-
- XCTAssertEqualObjects(q31, q32);
- XCTAssertNotEqualObjects(q31, q41);
- XCTAssertNotEqualObjects(q31, q51);
- XCTAssertNotEqualObjects(q31, q61);
-
- XCTAssertEqualObjects(q41, q42);
- XCTAssertNotEqualObjects(q41, q43Diff);
- XCTAssertNotEqualObjects(q41, q51);
- XCTAssertNotEqualObjects(q41, q61);
-
- XCTAssertEqualObjects(q51, q52);
- XCTAssertNotEqualObjects(q51, q53Diff);
- XCTAssertNotEqualObjects(q51, q61);
-}
-
-- (void)testUniqueIds {
- FSTQuery *q11 = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo" ]]];
- q11 = [q11 queryByAddingFilter:FSTTestFilter(@"i1", @"<", @(2))];
- q11 = [q11 queryByAddingFilter:FSTTestFilter(@"i2", @"==", @(3))];
- FSTQuery *q12 = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo" ]]];
- q12 = [q12 queryByAddingFilter:FSTTestFilter(@"i2", @"==", @(3))];
- q12 = [q12 queryByAddingFilter:FSTTestFilter(@"i1", @"<", @(2))];
-
- FSTQuery *q21 = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo" ]]];
- FSTQuery *q22 = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo" ]]];
-
- FSTQuery *q31 = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo", @"bar" ]]];
- FSTQuery *q32 = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo", @"bar" ]]];
-
- FSTQuery *q41 = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo" ]]];
- q41 = [q41 queryByAddingSortBy:@"foo" ascending:YES];
- q41 = [q41 queryByAddingSortBy:@"bar" ascending:YES];
- FSTQuery *q42 = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo" ]]];
- q42 = [q42 queryByAddingSortBy:@"foo" ascending:YES];
- q42 = [q42 queryByAddingSortBy:@"bar" ascending:YES];
- FSTQuery *q43Diff = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo" ]]];
- q43Diff = [q43Diff queryByAddingSortBy:@"bar" ascending:YES];
- q43Diff = [q43Diff queryByAddingSortBy:@"foo" ascending:YES];
-
- FSTQuery *q51 = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo" ]]];
- q51 = [q51 queryByAddingSortBy:@"foo" ascending:YES];
- q51 = [q51 queryByAddingFilter:FSTTestFilter(@"foo", @">", @(2))];
- FSTQuery *q52 = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo" ]]];
- q52 = [q52 queryByAddingFilter:FSTTestFilter(@"foo", @">", @(2))];
- q52 = [q52 queryByAddingSortBy:@"foo" ascending:YES];
- FSTQuery *q53Diff = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo" ]]];
- q53Diff = [q53Diff queryByAddingFilter:FSTTestFilter(@"bar", @">", @(2))];
- q53Diff = [q53Diff queryByAddingSortBy:@"bar" ascending:YES];
-
- FSTQuery *q61 = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo" ]]];
- q61 = [q61 queryBySettingLimit:10];
-
- // XCTAssertEqual(q11.hash, q12.hash); // TODO(klimt): not canonical yet
- XCTAssertNotEqual(q11.hash, q21.hash);
- XCTAssertNotEqual(q11.hash, q31.hash);
- XCTAssertNotEqual(q11.hash, q41.hash);
- XCTAssertNotEqual(q11.hash, q51.hash);
- XCTAssertNotEqual(q11.hash, q61.hash);
-
- XCTAssertEqual(q21.hash, q22.hash);
- XCTAssertNotEqual(q21.hash, q31.hash);
- XCTAssertNotEqual(q21.hash, q41.hash);
- XCTAssertNotEqual(q21.hash, q51.hash);
- XCTAssertNotEqual(q21.hash, q61.hash);
-
- XCTAssertEqual(q31.hash, q32.hash);
- XCTAssertNotEqual(q31.hash, q41.hash);
- XCTAssertNotEqual(q31.hash, q51.hash);
- XCTAssertNotEqual(q31.hash, q61.hash);
-
- XCTAssertEqual(q41.hash, q42.hash);
- XCTAssertNotEqual(q41.hash, q43Diff.hash);
- XCTAssertNotEqual(q41.hash, q51.hash);
- XCTAssertNotEqual(q41.hash, q61.hash);
-
- XCTAssertEqual(q51.hash, q52.hash);
- XCTAssertNotEqual(q51.hash, q53Diff.hash);
- XCTAssertNotEqual(q51.hash, q61.hash);
-}
-
-- (void)testImplicitOrderBy {
- FSTQuery *baseQuery = FSTTestQuery(@"foo");
- // Default is ascending
- XCTAssertEqualObjects(baseQuery.sortOrders, @[ FSTTestOrderBy(kDocumentKeyPath, @"asc") ]);
-
- // Explicit key ordering is respected
- XCTAssertEqualObjects(
- [baseQuery queryByAddingSortOrder:FSTTestOrderBy(kDocumentKeyPath, @"asc")].sortOrders,
- @[ FSTTestOrderBy(kDocumentKeyPath, @"asc") ]);
- XCTAssertEqualObjects(
- [baseQuery queryByAddingSortOrder:FSTTestOrderBy(kDocumentKeyPath, @"desc")].sortOrders,
- @[ FSTTestOrderBy(kDocumentKeyPath, @"desc") ]);
-
- XCTAssertEqualObjects(
- [[baseQuery queryByAddingSortOrder:FSTTestOrderBy(@"foo", @"asc")]
- queryByAddingSortOrder:FSTTestOrderBy(kDocumentKeyPath, @"asc")]
- .sortOrders,
- (@[ FSTTestOrderBy(@"foo", @"asc"), FSTTestOrderBy(kDocumentKeyPath, @"asc") ]));
-
- XCTAssertEqualObjects(
- [[baseQuery queryByAddingSortOrder:FSTTestOrderBy(@"foo", @"asc")]
- queryByAddingSortOrder:FSTTestOrderBy(kDocumentKeyPath, @"desc")]
- .sortOrders,
- (@[ FSTTestOrderBy(@"foo", @"asc"), FSTTestOrderBy(kDocumentKeyPath, @"desc") ]));
-
- // Inequality filters add order bys
- XCTAssertEqualObjects(
- [baseQuery queryByAddingFilter:FSTTestFilter(@"foo", @"<", @5)].sortOrders,
- (@[ FSTTestOrderBy(@"foo", @"asc"), FSTTestOrderBy(kDocumentKeyPath, @"asc") ]));
-
- // Descending order by applies to implicit key ordering
- XCTAssertEqualObjects(
- [baseQuery queryByAddingSortOrder:FSTTestOrderBy(@"foo", @"desc")].sortOrders,
- (@[ FSTTestOrderBy(@"foo", @"desc"), FSTTestOrderBy(kDocumentKeyPath, @"desc") ]));
- XCTAssertEqualObjects([[baseQuery queryByAddingSortOrder:FSTTestOrderBy(@"foo", @"asc")]
- queryByAddingSortOrder:FSTTestOrderBy(@"bar", @"desc")]
- .sortOrders,
- (@[
- FSTTestOrderBy(@"foo", @"asc"), FSTTestOrderBy(@"bar", @"desc"),
- FSTTestOrderBy(kDocumentKeyPath, @"desc")
- ]));
- XCTAssertEqualObjects([[baseQuery queryByAddingSortOrder:FSTTestOrderBy(@"foo", @"desc")]
- queryByAddingSortOrder:FSTTestOrderBy(@"bar", @"asc")]
- .sortOrders,
- (@[
- FSTTestOrderBy(@"foo", @"desc"), FSTTestOrderBy(@"bar", @"asc"),
- FSTTestOrderBy(kDocumentKeyPath, @"asc")
- ]));
-}
-
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/Firestore/Example/Tests/Core/FSTQueryTests.mm b/Firestore/Example/Tests/Core/FSTQueryTests.mm
new file mode 100644
index 0000000..02310aa
--- /dev/null
+++ b/Firestore/Example/Tests/Core/FSTQueryTests.mm
@@ -0,0 +1,578 @@
+/*
+ * Copyright 2017 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 "Firestore/Source/Core/FSTQuery.h"
+
+#import <XCTest/XCTest.h>
+
+#import "Firestore/Source/API/FIRFirestore+Internal.h"
+#import "Firestore/Source/Model/FSTDocument.h"
+#import "Firestore/Source/Model/FSTDocumentKey.h"
+
+#import "Firestore/Example/Tests/Util/FSTHelpers.h"
+
+#include "Firestore/core/src/firebase/firestore/model/database_id.h"
+#include "Firestore/core/src/firebase/firestore/model/field_path.h"
+#include "Firestore/core/src/firebase/firestore/model/resource_path.h"
+#include "Firestore/core/src/firebase/firestore/util/string_apple.h"
+#include "Firestore/core/test/firebase/firestore/testutil/testutil.h"
+#include "absl/strings/string_view.h"
+
+namespace testutil = firebase::firestore::testutil;
+namespace util = firebase::firestore::util;
+using firebase::firestore::model::DatabaseId;
+using firebase::firestore::model::FieldPath;
+using firebase::firestore::model::ResourcePath;
+
+NS_ASSUME_NONNULL_BEGIN
+
+/** Convenience methods for building test queries. */
+@interface FSTQuery (Tests)
+- (FSTQuery *)queryByAddingSortBy:(const absl::string_view)key ascending:(BOOL)ascending;
+@end
+
+@implementation FSTQuery (Tests)
+
+- (FSTQuery *)queryByAddingSortBy:(const absl::string_view)key ascending:(BOOL)ascending {
+ return [self queryByAddingSortOrder:[FSTSortOrder sortOrderWithFieldPath:testutil::Field(key)
+ ascending:ascending]];
+}
+
+@end
+
+@interface FSTQueryTests : XCTestCase
+@end
+
+@implementation FSTQueryTests
+
+- (void)testConstructor {
+ const ResourcePath path{"rooms", "Firestore", "messages", "0001"};
+ FSTQuery *query = [FSTQuery queryWithPath:path];
+ XCTAssertNotNil(query);
+
+ XCTAssertEqual(query.sortOrders.count, 1);
+ XCTAssertEqual(query.sortOrders[0].field.CanonicalString(), FieldPath::kDocumentKeyPath);
+ XCTAssertEqual(query.sortOrders[0].ascending, YES);
+
+ XCTAssertEqual(query.explicitSortOrders.count, 0);
+}
+
+- (void)testOrderBy {
+ FSTQuery *query = FSTTestQuery("rooms/Firestore/messages");
+ query =
+ [query queryByAddingSortOrder:[FSTSortOrder sortOrderWithFieldPath:testutil::Field("length")
+ ascending:NO]];
+
+ XCTAssertEqual(query.sortOrders.count, 2);
+ XCTAssertEqual(query.sortOrders[0].field.CanonicalString(), "length");
+ XCTAssertEqual(query.sortOrders[0].ascending, NO);
+ XCTAssertEqual(query.sortOrders[1].field.CanonicalString(), FieldPath::kDocumentKeyPath);
+ XCTAssertEqual(query.sortOrders[1].ascending, NO);
+
+ XCTAssertEqual(query.explicitSortOrders.count, 1);
+ XCTAssertEqual(query.explicitSortOrders[0].field.CanonicalString(), "length");
+ XCTAssertEqual(query.explicitSortOrders[0].ascending, NO);
+}
+
+- (void)testMatchesBasedOnDocumentKey {
+ FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/1", 0, @{@"text" : @"msg1"}, NO);
+ FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/2", 0, @{@"text" : @"msg2"}, NO);
+ FSTDocument *doc3 = FSTTestDoc("rooms/other/messages/1", 0, @{@"text" : @"msg3"}, NO);
+
+ // document query
+ FSTQuery *query = FSTTestQuery("rooms/eros/messages/1");
+ XCTAssertTrue([query matchesDocument:doc1]);
+ XCTAssertFalse([query matchesDocument:doc2]);
+ XCTAssertFalse([query matchesDocument:doc3]);
+}
+
+- (void)testMatchesCorrectlyForShallowAncestorQuery {
+ FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/1", 0, @{@"text" : @"msg1"}, NO);
+ FSTDocument *doc1Meta = FSTTestDoc("rooms/eros/messages/1/meta/1", 0, @{@"meta" : @"mv"}, NO);
+ FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/2", 0, @{@"text" : @"msg2"}, NO);
+ FSTDocument *doc3 = FSTTestDoc("rooms/other/messages/1", 0, @{@"text" : @"msg3"}, NO);
+
+ // shallow ancestor query
+ FSTQuery *query = FSTTestQuery("rooms/eros/messages");
+ XCTAssertTrue([query matchesDocument:doc1]);
+ XCTAssertFalse([query matchesDocument:doc1Meta]);
+ XCTAssertTrue([query matchesDocument:doc2]);
+ XCTAssertFalse([query matchesDocument:doc3]);
+}
+
+- (void)testEmptyFieldsAreAllowedForQueries {
+ FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/1", 0, @{@"text" : @"msg1"}, NO);
+ FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/2", 0, @{}, NO);
+
+ FSTQuery *query = [FSTTestQuery("rooms/eros/messages")
+ queryByAddingFilter:FSTTestFilter("text", @"==", @"msg1")];
+ XCTAssertTrue([query matchesDocument:doc1]);
+ XCTAssertFalse([query matchesDocument:doc2]);
+}
+
+- (void)testMatchesPrimitiveValuesForFilters {
+ FSTQuery *query1 =
+ [FSTTestQuery("collection") queryByAddingFilter:FSTTestFilter("sort", @">=", @(2))];
+ FSTQuery *query2 =
+ [FSTTestQuery("collection") queryByAddingFilter:FSTTestFilter("sort", @"<=", @(2))];
+
+ FSTDocument *doc1 = FSTTestDoc("collection/1", 0, @{ @"sort" : @1 }, NO);
+ FSTDocument *doc2 = FSTTestDoc("collection/2", 0, @{ @"sort" : @2 }, NO);
+ FSTDocument *doc3 = FSTTestDoc("collection/3", 0, @{ @"sort" : @3 }, NO);
+ FSTDocument *doc4 = FSTTestDoc("collection/4", 0, @{ @"sort" : @NO }, NO);
+ FSTDocument *doc5 = FSTTestDoc("collection/5", 0, @{@"sort" : @"string"}, NO);
+ FSTDocument *doc6 = FSTTestDoc("collection/6", 0, @{}, NO);
+
+ XCTAssertFalse([query1 matchesDocument:doc1]);
+ XCTAssertTrue([query1 matchesDocument:doc2]);
+ XCTAssertTrue([query1 matchesDocument:doc3]);
+ XCTAssertFalse([query1 matchesDocument:doc4]);
+ XCTAssertFalse([query1 matchesDocument:doc5]);
+ XCTAssertFalse([query1 matchesDocument:doc6]);
+
+ XCTAssertTrue([query2 matchesDocument:doc1]);
+ XCTAssertTrue([query2 matchesDocument:doc2]);
+ XCTAssertFalse([query2 matchesDocument:doc3]);
+ XCTAssertFalse([query2 matchesDocument:doc4]);
+ XCTAssertFalse([query2 matchesDocument:doc5]);
+ XCTAssertFalse([query2 matchesDocument:doc6]);
+}
+
+- (void)testNullFilter {
+ FSTQuery *query =
+ [FSTTestQuery("collection") queryByAddingFilter:FSTTestFilter("sort", @"==", [NSNull null])];
+ FSTDocument *doc1 = FSTTestDoc("collection/1", 0, @{@"sort" : [NSNull null]}, NO);
+ FSTDocument *doc2 = FSTTestDoc("collection/2", 0, @{ @"sort" : @2 }, NO);
+ FSTDocument *doc3 = FSTTestDoc("collection/2", 0, @{ @"sort" : @3.1 }, NO);
+ FSTDocument *doc4 = FSTTestDoc("collection/4", 0, @{ @"sort" : @NO }, NO);
+ FSTDocument *doc5 = FSTTestDoc("collection/5", 0, @{@"sort" : @"string"}, NO);
+
+ XCTAssertTrue([query matchesDocument:doc1]);
+ XCTAssertFalse([query matchesDocument:doc2]);
+ XCTAssertFalse([query matchesDocument:doc3]);
+ XCTAssertFalse([query matchesDocument:doc4]);
+ XCTAssertFalse([query matchesDocument:doc5]);
+}
+
+- (void)testNanFilter {
+ FSTQuery *query =
+ [FSTTestQuery("collection") queryByAddingFilter:FSTTestFilter("sort", @"==", @(NAN))];
+ FSTDocument *doc1 = FSTTestDoc("collection/1", 0, @{ @"sort" : @(NAN) }, NO);
+ FSTDocument *doc2 = FSTTestDoc("collection/2", 0, @{ @"sort" : @2 }, NO);
+ FSTDocument *doc3 = FSTTestDoc("collection/2", 0, @{ @"sort" : @3.1 }, NO);
+ FSTDocument *doc4 = FSTTestDoc("collection/4", 0, @{ @"sort" : @NO }, NO);
+ FSTDocument *doc5 = FSTTestDoc("collection/5", 0, @{@"sort" : @"string"}, NO);
+
+ XCTAssertTrue([query matchesDocument:doc1]);
+ XCTAssertFalse([query matchesDocument:doc2]);
+ XCTAssertFalse([query matchesDocument:doc3]);
+ XCTAssertFalse([query matchesDocument:doc4]);
+ XCTAssertFalse([query matchesDocument:doc5]);
+}
+
+- (void)testDoesNotMatchComplexObjectsForFilters {
+ FSTQuery *query1 =
+ [FSTTestQuery("collection") queryByAddingFilter:FSTTestFilter("sort", @"<=", @(2))];
+ FSTQuery *query2 =
+ [FSTTestQuery("collection") queryByAddingFilter:FSTTestFilter("sort", @">=", @(2))];
+
+ FSTDocument *doc1 = FSTTestDoc("collection/1", 0, @{ @"sort" : @2 }, NO);
+ FSTDocument *doc2 = FSTTestDoc("collection/2", 0, @{ @"sort" : @[] }, NO);
+ FSTDocument *doc3 = FSTTestDoc("collection/3", 0, @{ @"sort" : @[ @1 ] }, NO);
+ FSTDocument *doc4 = FSTTestDoc("collection/4", 0, @{ @"sort" : @{@"foo" : @2} }, NO);
+ FSTDocument *doc5 = FSTTestDoc("collection/5", 0, @{ @"sort" : @{@"foo" : @"bar"} }, NO);
+ FSTDocument *doc6 = FSTTestDoc("collection/6", 0, @{ @"sort" : @{} }, NO); // no sort field
+ FSTDocument *doc7 = FSTTestDoc("collection/7", 0, @{ @"sort" : @[ @3, @1 ] }, NO);
+
+ XCTAssertTrue([query1 matchesDocument:doc1]);
+ XCTAssertFalse([query1 matchesDocument:doc2]);
+ XCTAssertFalse([query1 matchesDocument:doc3]);
+ XCTAssertFalse([query1 matchesDocument:doc4]);
+ XCTAssertFalse([query1 matchesDocument:doc5]);
+ XCTAssertFalse([query1 matchesDocument:doc6]);
+ XCTAssertFalse([query1 matchesDocument:doc7]);
+
+ XCTAssertTrue([query2 matchesDocument:doc1]);
+ XCTAssertFalse([query2 matchesDocument:doc2]);
+ XCTAssertFalse([query2 matchesDocument:doc3]);
+ XCTAssertFalse([query2 matchesDocument:doc4]);
+ XCTAssertFalse([query2 matchesDocument:doc5]);
+ XCTAssertFalse([query2 matchesDocument:doc6]);
+ XCTAssertFalse([query2 matchesDocument:doc7]);
+}
+
+- (void)testDoesntRemoveComplexObjectsWithOrderBy {
+ FSTQuery *query1 = [FSTTestQuery("collection")
+ queryByAddingSortOrder:[FSTSortOrder sortOrderWithFieldPath:testutil::Field("sort")
+ ascending:YES]];
+
+ FSTDocument *doc1 = FSTTestDoc("collection/1", 0, @{ @"sort" : @2 }, NO);
+ FSTDocument *doc2 = FSTTestDoc("collection/2", 0, @{ @"sort" : @[] }, NO);
+ FSTDocument *doc3 = FSTTestDoc("collection/3", 0, @{ @"sort" : @[ @1 ] }, NO);
+ FSTDocument *doc4 = FSTTestDoc("collection/4", 0, @{ @"sort" : @{@"foo" : @2} }, NO);
+ FSTDocument *doc5 = FSTTestDoc("collection/5", 0, @{ @"sort" : @{@"foo" : @"bar"} }, NO);
+ FSTDocument *doc6 = FSTTestDoc("collection/6", 0, @{}, NO);
+
+ XCTAssertTrue([query1 matchesDocument:doc1]);
+ XCTAssertTrue([query1 matchesDocument:doc2]);
+ XCTAssertTrue([query1 matchesDocument:doc3]);
+ XCTAssertTrue([query1 matchesDocument:doc4]);
+ XCTAssertTrue([query1 matchesDocument:doc5]);
+ XCTAssertFalse([query1 matchesDocument:doc6]);
+}
+
+- (void)testFiltersBasedOnArrayValue {
+ FSTQuery *baseQuery = FSTTestQuery("collection");
+ FSTDocument *doc1 = FSTTestDoc("collection/doc", 0, @{ @"tags" : @[ @"foo", @1, @YES ] }, NO);
+
+ NSArray<id<FSTFilter>> *matchingFilters =
+ @[ FSTTestFilter("tags", @"==", @[ @"foo", @1, @YES ]) ];
+
+ NSArray<id<FSTFilter>> *nonMatchingFilters = @[
+ FSTTestFilter("tags", @"==", @"foo"),
+ FSTTestFilter("tags", @"==", @[ @"foo", @1 ]),
+ FSTTestFilter("tags", @"==", @[ @"foo", @YES, @1 ]),
+ ];
+
+ for (id<FSTFilter> filter in matchingFilters) {
+ XCTAssertTrue([[baseQuery queryByAddingFilter:filter] matchesDocument:doc1]);
+ }
+
+ for (id<FSTFilter> filter in nonMatchingFilters) {
+ XCTAssertFalse([[baseQuery queryByAddingFilter:filter] matchesDocument:doc1]);
+ }
+}
+
+- (void)testFiltersBasedOnObjectValue {
+ FSTQuery *baseQuery = FSTTestQuery("collection");
+ FSTDocument *doc1 =
+ FSTTestDoc("collection/doc", 0,
+ @{ @"tags" : @{@"foo" : @"foo", @"a" : @0, @"b" : @YES, @"c" : @(NAN)} }, NO);
+
+ NSArray<id<FSTFilter>> *matchingFilters = @[
+ FSTTestFilter("tags", @"==",
+ @{ @"foo" : @"foo",
+ @"a" : @0,
+ @"b" : @YES,
+ @"c" : @(NAN) }),
+ FSTTestFilter("tags", @"==",
+ @{ @"b" : @YES,
+ @"a" : @0,
+ @"foo" : @"foo",
+ @"c" : @(NAN) }),
+ FSTTestFilter("tags.foo", @"==", @"foo")
+ ];
+
+ NSArray<id<FSTFilter>> *nonMatchingFilters = @[
+ FSTTestFilter("tags", @"==", @"foo"), FSTTestFilter("tags", @"==", @{
+ @"foo" : @"foo",
+ @"a" : @0,
+ @"b" : @YES,
+ })
+ ];
+
+ for (id<FSTFilter> filter in matchingFilters) {
+ XCTAssertTrue([[baseQuery queryByAddingFilter:filter] matchesDocument:doc1]);
+ }
+
+ for (id<FSTFilter> filter in nonMatchingFilters) {
+ XCTAssertFalse([[baseQuery queryByAddingFilter:filter] matchesDocument:doc1]);
+ }
+}
+
+/**
+ * Checks that an ordered array of elements yields the correct pair-wise comparison result for the
+ * supplied comparator.
+ */
+- (void)assertCorrectComparisonsWithArray:(NSArray *)array comparator:(NSComparator)comp {
+ [array enumerateObjectsUsingBlock:^(id iObj, NSUInteger i, BOOL *outerStop) {
+ [array enumerateObjectsUsingBlock:^(id _Nonnull jObj, NSUInteger j, BOOL *innerStop) {
+ NSComparisonResult expected = [@(i) compare:@(j)];
+ NSComparisonResult actual = comp(iObj, jObj);
+ XCTAssertEqual(actual, expected, @"Compared %@ to %@ at (%lu, %lu).", iObj, jObj,
+ (unsigned long)i, (unsigned long)j);
+ }];
+ }];
+}
+
+- (void)testSortsDocumentsInTheCorrectOrder {
+ FSTQuery *query = FSTTestQuery("collection");
+ query = [query queryByAddingSortOrder:[FSTSortOrder sortOrderWithFieldPath:testutil::Field("sort")
+ ascending:YES]];
+
+ // clang-format off
+ NSArray<FSTDocument *> *docs = @[
+ FSTTestDoc("collection/1", 0, @{@"sort": [NSNull null]}, NO),
+ FSTTestDoc("collection/1", 0, @{@"sort": @NO}, NO),
+ FSTTestDoc("collection/1", 0, @{@"sort": @YES}, NO),
+ FSTTestDoc("collection/1", 0, @{@"sort": @1}, NO),
+ FSTTestDoc("collection/2", 0, @{@"sort": @1}, NO), // by key
+ FSTTestDoc("collection/3", 0, @{@"sort": @1}, NO), // by key
+ FSTTestDoc("collection/1", 0, @{@"sort": @1.9}, NO),
+ FSTTestDoc("collection/1", 0, @{@"sort": @2}, NO),
+ FSTTestDoc("collection/1", 0, @{@"sort": @2.1}, NO),
+ FSTTestDoc("collection/1", 0, @{@"sort": @""}, NO),
+ FSTTestDoc("collection/1", 0, @{@"sort": @"a"}, NO),
+ FSTTestDoc("collection/1", 0, @{@"sort": @"ab"}, NO),
+ FSTTestDoc("collection/1", 0, @{@"sort": @"b"}, NO),
+ FSTTestDoc("collection/1", 0, @{@"sort":
+ FSTTestRef("project", DatabaseId::kDefault, @"collection/id1")}, NO),
+ ];
+ // clang-format on
+
+ [self assertCorrectComparisonsWithArray:docs comparator:query.comparator];
+}
+
+- (void)testSortsDocumentsUsingMultipleFields {
+ FSTQuery *query = FSTTestQuery("collection");
+ query =
+ [query queryByAddingSortOrder:[FSTSortOrder sortOrderWithFieldPath:testutil::Field("sort1")
+ ascending:YES]];
+ query =
+ [query queryByAddingSortOrder:[FSTSortOrder sortOrderWithFieldPath:testutil::Field("sort2")
+ ascending:YES]];
+
+ // clang-format off
+ NSArray<FSTDocument *> *docs =
+ @[FSTTestDoc("collection/1", 0, @{@"sort1": @1, @"sort2": @1}, NO),
+ FSTTestDoc("collection/1", 0, @{@"sort1": @1, @"sort2": @2}, NO),
+ FSTTestDoc("collection/2", 0, @{@"sort1": @1, @"sort2": @2}, NO), // by key
+ FSTTestDoc("collection/3", 0, @{@"sort1": @1, @"sort2": @2}, NO), // by key
+ FSTTestDoc("collection/1", 0, @{@"sort1": @1, @"sort2": @3}, NO),
+ FSTTestDoc("collection/1", 0, @{@"sort1": @2, @"sort2": @1}, NO),
+ FSTTestDoc("collection/1", 0, @{@"sort1": @2, @"sort2": @2}, NO),
+ FSTTestDoc("collection/2", 0, @{@"sort1": @2, @"sort2": @2}, NO), // by key
+ FSTTestDoc("collection/3", 0, @{@"sort1": @2, @"sort2": @2}, NO), // by key
+ FSTTestDoc("collection/1", 0, @{@"sort1": @2, @"sort2": @3}, NO),
+ ];
+ // clang-format on
+
+ [self assertCorrectComparisonsWithArray:docs comparator:query.comparator];
+}
+
+- (void)testSortsDocumentsWithDescendingToo {
+ FSTQuery *query = FSTTestQuery("collection");
+ query =
+ [query queryByAddingSortOrder:[FSTSortOrder sortOrderWithFieldPath:testutil::Field("sort1")
+ ascending:NO]];
+ query =
+ [query queryByAddingSortOrder:[FSTSortOrder sortOrderWithFieldPath:testutil::Field("sort2")
+ ascending:NO]];
+
+ // clang-format off
+ NSArray<FSTDocument *> *docs =
+ @[FSTTestDoc("collection/1", 0, @{@"sort1": @2, @"sort2": @3}, NO),
+ FSTTestDoc("collection/3", 0, @{@"sort1": @2, @"sort2": @2}, NO),
+ FSTTestDoc("collection/2", 0, @{@"sort1": @2, @"sort2": @2}, NO), // by key
+ FSTTestDoc("collection/1", 0, @{@"sort1": @2, @"sort2": @2}, NO), // by key
+ FSTTestDoc("collection/1", 0, @{@"sort1": @2, @"sort2": @1}, NO),
+ FSTTestDoc("collection/1", 0, @{@"sort1": @1, @"sort2": @3}, NO),
+ FSTTestDoc("collection/3", 0, @{@"sort1": @1, @"sort2": @2}, NO),
+ FSTTestDoc("collection/2", 0, @{@"sort1": @1, @"sort2": @2}, NO), // by key
+ FSTTestDoc("collection/1", 0, @{@"sort1": @1, @"sort2": @2}, NO), // by key
+ FSTTestDoc("collection/1", 0, @{@"sort1": @1, @"sort2": @1}, NO),
+ ];
+ // clang-format on
+
+ [self assertCorrectComparisonsWithArray:docs comparator:query.comparator];
+}
+
+- (void)testEquality {
+ FSTQuery *q11 = FSTTestQuery("foo");
+ q11 = [q11 queryByAddingFilter:FSTTestFilter("i1", @"<", @(2))];
+ q11 = [q11 queryByAddingFilter:FSTTestFilter("i2", @"==", @(3))];
+ FSTQuery *q12 = FSTTestQuery("foo");
+ q12 = [q12 queryByAddingFilter:FSTTestFilter("i2", @"==", @(3))];
+ q12 = [q12 queryByAddingFilter:FSTTestFilter("i1", @"<", @(2))];
+
+ FSTQuery *q21 = FSTTestQuery("foo");
+ FSTQuery *q22 = FSTTestQuery("foo");
+
+ FSTQuery *q31 = FSTTestQuery("foo/bar");
+ FSTQuery *q32 = FSTTestQuery("foo/bar");
+
+ FSTQuery *q41 = FSTTestQuery("foo");
+ q41 = [q41 queryByAddingSortBy:"foo" ascending:YES];
+ q41 = [q41 queryByAddingSortBy:"bar" ascending:YES];
+ FSTQuery *q42 = FSTTestQuery("foo");
+ q42 = [q42 queryByAddingSortBy:"foo" ascending:YES];
+ q42 = [q42 queryByAddingSortBy:"bar" ascending:YES];
+ FSTQuery *q43Diff = FSTTestQuery("foo");
+ q43Diff = [q43Diff queryByAddingSortBy:"bar" ascending:YES];
+ q43Diff = [q43Diff queryByAddingSortBy:"foo" ascending:YES];
+
+ FSTQuery *q51 = FSTTestQuery("foo");
+ q51 = [q51 queryByAddingSortBy:"foo" ascending:YES];
+ q51 = [q51 queryByAddingFilter:FSTTestFilter("foo", @">", @(2))];
+ FSTQuery *q52 = FSTTestQuery("foo");
+ q52 = [q52 queryByAddingFilter:FSTTestFilter("foo", @">", @(2))];
+ q52 = [q52 queryByAddingSortBy:"foo" ascending:YES];
+ FSTQuery *q53Diff = FSTTestQuery("foo");
+ q53Diff = [q53Diff queryByAddingFilter:FSTTestFilter("bar", @">", @(2))];
+ q53Diff = [q53Diff queryByAddingSortBy:"bar" ascending:YES];
+
+ FSTQuery *q61 = FSTTestQuery("foo");
+ q61 = [q61 queryBySettingLimit:10];
+
+ // XCTAssertEqualObjects(q11, q12); // TODO(klimt): not canonical yet
+ XCTAssertNotEqualObjects(q11, q21);
+ XCTAssertNotEqualObjects(q11, q31);
+ XCTAssertNotEqualObjects(q11, q41);
+ XCTAssertNotEqualObjects(q11, q51);
+ XCTAssertNotEqualObjects(q11, q61);
+
+ XCTAssertEqualObjects(q21, q22);
+ XCTAssertNotEqualObjects(q21, q31);
+ XCTAssertNotEqualObjects(q21, q41);
+ XCTAssertNotEqualObjects(q21, q51);
+ XCTAssertNotEqualObjects(q21, q61);
+
+ XCTAssertEqualObjects(q31, q32);
+ XCTAssertNotEqualObjects(q31, q41);
+ XCTAssertNotEqualObjects(q31, q51);
+ XCTAssertNotEqualObjects(q31, q61);
+
+ XCTAssertEqualObjects(q41, q42);
+ XCTAssertNotEqualObjects(q41, q43Diff);
+ XCTAssertNotEqualObjects(q41, q51);
+ XCTAssertNotEqualObjects(q41, q61);
+
+ XCTAssertEqualObjects(q51, q52);
+ XCTAssertNotEqualObjects(q51, q53Diff);
+ XCTAssertNotEqualObjects(q51, q61);
+}
+
+- (void)testUniqueIds {
+ FSTQuery *q11 = FSTTestQuery("foo");
+ q11 = [q11 queryByAddingFilter:FSTTestFilter("i1", @"<", @(2))];
+ q11 = [q11 queryByAddingFilter:FSTTestFilter("i2", @"==", @(3))];
+ FSTQuery *q12 = FSTTestQuery("foo");
+ q12 = [q12 queryByAddingFilter:FSTTestFilter("i2", @"==", @(3))];
+ q12 = [q12 queryByAddingFilter:FSTTestFilter("i1", @"<", @(2))];
+
+ FSTQuery *q21 = FSTTestQuery("foo");
+ FSTQuery *q22 = FSTTestQuery("foo");
+
+ FSTQuery *q31 = FSTTestQuery("foo/bar");
+ FSTQuery *q32 = FSTTestQuery("foo/bar");
+
+ FSTQuery *q41 = FSTTestQuery("foo");
+ q41 = [q41 queryByAddingSortBy:"foo" ascending:YES];
+ q41 = [q41 queryByAddingSortBy:"bar" ascending:YES];
+ FSTQuery *q42 = FSTTestQuery("foo");
+ q42 = [q42 queryByAddingSortBy:"foo" ascending:YES];
+ q42 = [q42 queryByAddingSortBy:"bar" ascending:YES];
+ FSTQuery *q43Diff = FSTTestQuery("foo");
+ q43Diff = [q43Diff queryByAddingSortBy:"bar" ascending:YES];
+ q43Diff = [q43Diff queryByAddingSortBy:"foo" ascending:YES];
+
+ FSTQuery *q51 = FSTTestQuery("foo");
+ q51 = [q51 queryByAddingSortBy:"foo" ascending:YES];
+ q51 = [q51 queryByAddingFilter:FSTTestFilter("foo", @">", @(2))];
+ FSTQuery *q52 = FSTTestQuery("foo");
+ q52 = [q52 queryByAddingFilter:FSTTestFilter("foo", @">", @(2))];
+ q52 = [q52 queryByAddingSortBy:"foo" ascending:YES];
+ FSTQuery *q53Diff = FSTTestQuery("foo");
+ q53Diff = [q53Diff queryByAddingFilter:FSTTestFilter("bar", @">", @(2))];
+ q53Diff = [q53Diff queryByAddingSortBy:"bar" ascending:YES];
+
+ FSTQuery *q61 = FSTTestQuery("foo");
+ q61 = [q61 queryBySettingLimit:10];
+
+ // XCTAssertEqual(q11.hash, q12.hash); // TODO(klimt): not canonical yet
+ XCTAssertNotEqual(q11.hash, q21.hash);
+ XCTAssertNotEqual(q11.hash, q31.hash);
+ XCTAssertNotEqual(q11.hash, q41.hash);
+ XCTAssertNotEqual(q11.hash, q51.hash);
+ XCTAssertNotEqual(q11.hash, q61.hash);
+
+ XCTAssertEqual(q21.hash, q22.hash);
+ XCTAssertNotEqual(q21.hash, q31.hash);
+ XCTAssertNotEqual(q21.hash, q41.hash);
+ XCTAssertNotEqual(q21.hash, q51.hash);
+ XCTAssertNotEqual(q21.hash, q61.hash);
+
+ XCTAssertEqual(q31.hash, q32.hash);
+ XCTAssertNotEqual(q31.hash, q41.hash);
+ XCTAssertNotEqual(q31.hash, q51.hash);
+ XCTAssertNotEqual(q31.hash, q61.hash);
+
+ XCTAssertEqual(q41.hash, q42.hash);
+ XCTAssertNotEqual(q41.hash, q43Diff.hash);
+ XCTAssertNotEqual(q41.hash, q51.hash);
+ XCTAssertNotEqual(q41.hash, q61.hash);
+
+ XCTAssertEqual(q51.hash, q52.hash);
+ XCTAssertNotEqual(q51.hash, q53Diff.hash);
+ XCTAssertNotEqual(q51.hash, q61.hash);
+}
+
+- (void)testImplicitOrderBy {
+ FSTQuery *baseQuery = FSTTestQuery("foo");
+ // Default is ascending
+ XCTAssertEqualObjects(baseQuery.sortOrders,
+ @[ FSTTestOrderBy(FieldPath::kDocumentKeyPath, @"asc") ]);
+
+ // Explicit key ordering is respected
+ XCTAssertEqualObjects(
+ [baseQuery queryByAddingSortOrder:FSTTestOrderBy(FieldPath::kDocumentKeyPath, @"asc")]
+ .sortOrders,
+ @[ FSTTestOrderBy(FieldPath::kDocumentKeyPath, @"asc") ]);
+ XCTAssertEqualObjects(
+ [baseQuery queryByAddingSortOrder:FSTTestOrderBy(FieldPath::kDocumentKeyPath, @"desc")]
+ .sortOrders,
+ @[ FSTTestOrderBy(FieldPath::kDocumentKeyPath, @"desc") ]);
+
+ XCTAssertEqualObjects(
+ [[baseQuery queryByAddingSortOrder:FSTTestOrderBy("foo", @"asc")]
+ queryByAddingSortOrder:FSTTestOrderBy(FieldPath::kDocumentKeyPath, @"asc")]
+ .sortOrders,
+ (@[ FSTTestOrderBy("foo", @"asc"), FSTTestOrderBy(FieldPath::kDocumentKeyPath, @"asc") ]));
+
+ XCTAssertEqualObjects(
+ [[baseQuery queryByAddingSortOrder:FSTTestOrderBy("foo", @"asc")]
+ queryByAddingSortOrder:FSTTestOrderBy(FieldPath::kDocumentKeyPath, @"desc")]
+ .sortOrders,
+ (@[ FSTTestOrderBy("foo", @"asc"), FSTTestOrderBy(FieldPath::kDocumentKeyPath, @"desc") ]));
+
+ // Inequality filters add order bys
+ XCTAssertEqualObjects(
+ [baseQuery queryByAddingFilter:FSTTestFilter("foo", @"<", @5)].sortOrders,
+ (@[ FSTTestOrderBy("foo", @"asc"), FSTTestOrderBy(FieldPath::kDocumentKeyPath, @"asc") ]));
+
+ // Descending order by applies to implicit key ordering
+ XCTAssertEqualObjects(
+ [baseQuery queryByAddingSortOrder:FSTTestOrderBy("foo", @"desc")].sortOrders,
+ (@[ FSTTestOrderBy("foo", @"desc"), FSTTestOrderBy(FieldPath::kDocumentKeyPath, @"desc") ]));
+ XCTAssertEqualObjects([[baseQuery queryByAddingSortOrder:FSTTestOrderBy("foo", @"asc")]
+ queryByAddingSortOrder:FSTTestOrderBy("bar", @"desc")]
+ .sortOrders,
+ (@[
+ FSTTestOrderBy("foo", @"asc"), FSTTestOrderBy("bar", @"desc"),
+ FSTTestOrderBy(FieldPath::kDocumentKeyPath, @"desc")
+ ]));
+ XCTAssertEqualObjects([[baseQuery queryByAddingSortOrder:FSTTestOrderBy("foo", @"desc")]
+ queryByAddingSortOrder:FSTTestOrderBy("bar", @"asc")]
+ .sortOrders,
+ (@[
+ FSTTestOrderBy("foo", @"desc"), FSTTestOrderBy("bar", @"asc"),
+ FSTTestOrderBy(FieldPath::kDocumentKeyPath, @"asc")
+ ]));
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firestore/Example/Tests/Core/FSTSyncEngine+Testing.h b/Firestore/Example/Tests/Core/FSTSyncEngine+Testing.h
index ab0697b..5d5c981 100644
--- a/Firestore/Example/Tests/Core/FSTSyncEngine+Testing.h
+++ b/Firestore/Example/Tests/Core/FSTSyncEngine+Testing.h
@@ -16,16 +16,19 @@
#import <Foundation/Foundation.h>
+#include <map>
+
#import "Firestore/Source/Core/FSTSyncEngine.h"
-@class FSTDocumentKey;
+#include "Firestore/core/src/firebase/firestore/model/document_key.h"
NS_ASSUME_NONNULL_BEGIN
@interface FSTSyncEngine (Testing)
/** Returns the current set of limbo document keys and their associated target IDs. */
-- (NSDictionary<FSTDocumentKey *, FSTBoxedTargetID *> *)currentLimboDocuments;
+- (std::map<firebase::firestore::model::DocumentKey, firebase::firestore::model::TargetId>)
+ currentLimboDocuments;
@end
diff --git a/Firestore/Example/Tests/Core/FSTTargetIDGeneratorTests.m b/Firestore/Example/Tests/Core/FSTTargetIDGeneratorTests.m
deleted file mode 100644
index 6f54fd1..0000000
--- a/Firestore/Example/Tests/Core/FSTTargetIDGeneratorTests.m
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright 2017 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 "Firestore/Source/Core/FSTTargetIDGenerator.h"
-
-#import <XCTest/XCTest.h>
-
-NS_ASSUME_NONNULL_BEGIN
-
-@interface FSTTargetIDGenerator ()
-- (instancetype)initWithGeneratorID:(NSInteger)generatorID startingAfterID:(FSTTargetID)after;
-@end
-
-@interface FSTTargetIDGeneratorTests : XCTestCase
-@end
-
-@implementation FSTTargetIDGeneratorTests
-
-- (void)testConstructor {
- XCTAssertEqual([[[FSTTargetIDGenerator alloc] initWithGeneratorID:0 startingAfterID:0] nextID],
- 2);
- XCTAssertEqual([[[FSTTargetIDGenerator alloc] initWithGeneratorID:1 startingAfterID:0] nextID],
- 1);
-
- XCTAssertEqual([[FSTTargetIDGenerator generatorForLocalStoreStartingAfterID:0] nextID], 2);
- XCTAssertEqual([[FSTTargetIDGenerator generatorForSyncEngineStartingAfterID:0] nextID], 1);
-}
-
-- (void)testSkipPast {
- FSTTargetIDGenerator *gen =
- [[FSTTargetIDGenerator alloc] initWithGeneratorID:1 startingAfterID:-1];
- XCTAssertEqual([gen nextID], 1);
-
- gen = [[FSTTargetIDGenerator alloc] initWithGeneratorID:1 startingAfterID:2];
- XCTAssertEqual([gen nextID], 3);
-
- gen = [[FSTTargetIDGenerator alloc] initWithGeneratorID:1 startingAfterID:4];
- XCTAssertEqual([gen nextID], 5);
-
- for (int i = 4; i < 12; ++i) {
- FSTTargetIDGenerator *gen0 =
- [[FSTTargetIDGenerator alloc] initWithGeneratorID:0 startingAfterID:i];
- FSTTargetIDGenerator *gen1 =
- [[FSTTargetIDGenerator alloc] initWithGeneratorID:1 startingAfterID:i];
- XCTAssertEqual([gen0 nextID], i + 2 & ~1, @"Skip failed for index %d", i);
- XCTAssertEqual([gen1 nextID], i + 1 | 1, @"Skip failed for index %d", i);
- }
-
- gen = [[FSTTargetIDGenerator alloc] initWithGeneratorID:1 startingAfterID:12];
- XCTAssertEqual([gen nextID], 13);
-
- gen = [[FSTTargetIDGenerator alloc] initWithGeneratorID:0 startingAfterID:22];
- XCTAssertEqual([gen nextID], 24);
-}
-
-- (void)testIncrement {
- FSTTargetIDGenerator *gen =
- [[FSTTargetIDGenerator alloc] initWithGeneratorID:0 startingAfterID:0];
- XCTAssertEqual([gen nextID], 2);
- XCTAssertEqual([gen nextID], 4);
- XCTAssertEqual([gen nextID], 6);
- gen = [[FSTTargetIDGenerator alloc] initWithGeneratorID:0 startingAfterID:46];
- XCTAssertEqual([gen nextID], 48);
- XCTAssertEqual([gen nextID], 50);
- XCTAssertEqual([gen nextID], 52);
- XCTAssertEqual([gen nextID], 54);
-
- gen = [[FSTTargetIDGenerator alloc] initWithGeneratorID:1 startingAfterID:0];
- XCTAssertEqual([gen nextID], 1);
- XCTAssertEqual([gen nextID], 3);
- XCTAssertEqual([gen nextID], 5);
- gen = [[FSTTargetIDGenerator alloc] initWithGeneratorID:1 startingAfterID:46];
- XCTAssertEqual([gen nextID], 47);
- XCTAssertEqual([gen nextID], 49);
- XCTAssertEqual([gen nextID], 51);
- XCTAssertEqual([gen nextID], 53);
-}
-
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/Firestore/Example/Tests/Core/FSTTimestampTests.m b/Firestore/Example/Tests/Core/FSTTimestampTests.m
deleted file mode 100644
index a3765fe..0000000
--- a/Firestore/Example/Tests/Core/FSTTimestampTests.m
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright 2017 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 "Firestore/Source/Core/FSTTimestamp.h"
-
-#import <XCTest/XCTest.h>
-
-#import "Firestore/Source/Util/FSTAssert.h"
-
-#import "Firestore/Example/Tests/Util/FSTHelpers.h"
-
-NS_ASSUME_NONNULL_BEGIN
-
-@interface FSTTimestampTests : XCTestCase
-@end
-
-@implementation FSTTimestampTests
-
-- (void)testFromDate {
- // Very carefully construct an NSDate that won't lose precision with its milliseconds.
- NSDate *input = [NSDate dateWithTimeIntervalSinceReferenceDate:1.5];
-
- FSTTimestamp *actual = [FSTTimestamp timestampWithDate:input];
- static const int64_t kSecondsFromEpochToReferenceDate = 978307200;
- XCTAssertEqual(kSecondsFromEpochToReferenceDate + 1, actual.seconds);
- XCTAssertEqual(500000000, actual.nanos);
-
- FSTTimestamp *expected =
- [[FSTTimestamp alloc] initWithSeconds:(kSecondsFromEpochToReferenceDate + 1) nanos:500000000];
- XCTAssertEqualObjects(expected, actual);
-}
-
-- (void)testSO8601String {
- NSDate *date = FSTTestDate(1912, 4, 14, 23, 40, 0);
- FSTTimestamp *timestamp =
- [[FSTTimestamp alloc] initWithSeconds:(int64_t)date.timeIntervalSince1970 nanos:543000000];
- XCTAssertEqualObjects(timestamp.ISO8601String, @"1912-04-14T23:40:00.543000000Z");
-}
-
-- (void)testISO8601String_withLowMilliseconds {
- NSDate *date = FSTTestDate(1912, 4, 14, 23, 40, 0);
- FSTTimestamp *timestamp =
- [[FSTTimestamp alloc] initWithSeconds:(int64_t)date.timeIntervalSince1970 nanos:7000000];
- XCTAssertEqualObjects(timestamp.ISO8601String, @"1912-04-14T23:40:00.007000000Z");
-}
-
-- (void)testISO8601String_withLowNanos {
- FSTTimestamp *timestamp = [[FSTTimestamp alloc] initWithSeconds:0 nanos:1];
- XCTAssertEqualObjects(timestamp.ISO8601String, @"1970-01-01T00:00:00.000000001Z");
-}
-
-- (void)testISO8601String_withNegativeSeconds {
- FSTTimestamp *timestamp = [[FSTTimestamp alloc] initWithSeconds:-1 nanos:999999999];
- XCTAssertEqualObjects(timestamp.ISO8601String, @"1969-12-31T23:59:59.999999999Z");
-}
-
-- (void)testCompare {
- NSArray<FSTTimestamp *> *timestamps = @[
- [[FSTTimestamp alloc] initWithSeconds:12344 nanos:999999999],
- [[FSTTimestamp alloc] initWithSeconds:12345 nanos:0],
- [[FSTTimestamp alloc] initWithSeconds:12345 nanos:000000001],
- [[FSTTimestamp alloc] initWithSeconds:12345 nanos:99999999],
- [[FSTTimestamp alloc] initWithSeconds:12345 nanos:100000000],
- [[FSTTimestamp alloc] initWithSeconds:12345 nanos:100000001],
- [[FSTTimestamp alloc] initWithSeconds:12346 nanos:0],
- ];
- for (int i = 0; i < timestamps.count - 1; ++i) {
- XCTAssertEqual(NSOrderedAscending, [timestamps[i] compare:timestamps[i + 1]]);
- XCTAssertEqual(NSOrderedDescending, [timestamps[i + 1] compare:timestamps[i]]);
- }
-}
-
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/Firestore/Example/Tests/Core/FSTViewSnapshotTest.m b/Firestore/Example/Tests/Core/FSTViewSnapshotTest.mm
index 5d3787a..ca8954e 100644
--- a/Firestore/Example/Tests/Core/FSTViewSnapshotTest.m
+++ b/Firestore/Example/Tests/Core/FSTViewSnapshotTest.mm
@@ -21,7 +21,6 @@
#import "Firestore/Source/Core/FSTQuery.h"
#import "Firestore/Source/Model/FSTDocument.h"
#import "Firestore/Source/Model/FSTDocumentSet.h"
-#import "Firestore/Source/Model/FSTPath.h"
#import "Firestore/Example/Tests/Util/FSTHelpers.h"
@@ -33,7 +32,7 @@ NS_ASSUME_NONNULL_BEGIN
@implementation FSTViewSnapshotTests
- (void)testDocumentChangeConstructor {
- FSTDocument *doc = FSTTestDoc(@"a/b", 0, @{}, NO);
+ FSTDocument *doc = FSTTestDoc("a/b", 0, @{}, NO);
FSTDocumentViewChangeType type = FSTDocumentViewChangeTypeModified;
FSTDocumentViewChange *change = [FSTDocumentViewChange changeWithDocument:doc type:type];
XCTAssertEqual(change.document, doc);
@@ -43,15 +42,15 @@ NS_ASSUME_NONNULL_BEGIN
- (void)testTrack {
FSTDocumentViewChangeSet *set = [FSTDocumentViewChangeSet changeSet];
- FSTDocument *docAdded = FSTTestDoc(@"a/1", 0, @{}, NO);
- FSTDocument *docRemoved = FSTTestDoc(@"a/2", 0, @{}, NO);
- FSTDocument *docModified = FSTTestDoc(@"a/3", 0, @{}, NO);
+ FSTDocument *docAdded = FSTTestDoc("a/1", 0, @{}, NO);
+ FSTDocument *docRemoved = FSTTestDoc("a/2", 0, @{}, NO);
+ FSTDocument *docModified = FSTTestDoc("a/3", 0, @{}, NO);
- FSTDocument *docAddedThenModified = FSTTestDoc(@"b/1", 0, @{}, NO);
- FSTDocument *docAddedThenRemoved = FSTTestDoc(@"b/2", 0, @{}, NO);
- FSTDocument *docRemovedThenAdded = FSTTestDoc(@"b/3", 0, @{}, NO);
- FSTDocument *docModifiedThenRemoved = FSTTestDoc(@"b/4", 0, @{}, NO);
- FSTDocument *docModifiedThenModified = FSTTestDoc(@"b/5", 0, @{}, NO);
+ FSTDocument *docAddedThenModified = FSTTestDoc("b/1", 0, @{}, NO);
+ FSTDocument *docAddedThenRemoved = FSTTestDoc("b/2", 0, @{}, NO);
+ FSTDocument *docRemovedThenAdded = FSTTestDoc("b/3", 0, @{}, NO);
+ FSTDocument *docModifiedThenRemoved = FSTTestDoc("b/4", 0, @{}, NO);
+ FSTDocument *docModifiedThenModified = FSTTestDoc("b/5", 0, @{}, NO);
[set addChange:[FSTDocumentViewChange changeWithDocument:docAdded
type:FSTDocumentViewChangeTypeAdded]];
@@ -107,12 +106,12 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)testViewSnapshotConstructor {
- FSTQuery *query = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"a" ]]];
+ FSTQuery *query = FSTTestQuery("a");
FSTDocumentSet *documents = [FSTDocumentSet documentSetWithComparator:FSTDocumentComparatorByKey];
FSTDocumentSet *oldDocuments = documents;
- documents = [documents documentSetByAddingDocument:FSTTestDoc(@"c/a", 1, @{}, NO)];
+ documents = [documents documentSetByAddingDocument:FSTTestDoc("c/a", 1, @{}, NO)];
NSArray<FSTDocumentViewChange *> *documentChanges =
- @[ [FSTDocumentViewChange changeWithDocument:FSTTestDoc(@"c/a", 1, @{}, NO)
+ @[ [FSTDocumentViewChange changeWithDocument:FSTTestDoc("c/a", 1, @{}, NO)
type:FSTDocumentViewChangeTypeAdded] ];
BOOL fromCache = YES;
diff --git a/Firestore/Example/Tests/Core/FSTViewTests.m b/Firestore/Example/Tests/Core/FSTViewTests.mm
index e6c4510..63ce711 100644
--- a/Firestore/Example/Tests/Core/FSTViewTests.m
+++ b/Firestore/Example/Tests/Core/FSTViewTests.mm
@@ -25,11 +25,16 @@
#import "Firestore/Source/Model/FSTDocumentKey.h"
#import "Firestore/Source/Model/FSTDocumentSet.h"
#import "Firestore/Source/Model/FSTFieldValue.h"
-#import "Firestore/Source/Model/FSTPath.h"
#import "Firestore/Source/Remote/FSTRemoteEvent.h"
#import "Firestore/Example/Tests/Util/FSTHelpers.h"
+#include "Firestore/core/src/firebase/firestore/model/resource_path.h"
+#include "Firestore/core/test/firebase/firestore/testutil/testutil.h"
+
+namespace testutil = firebase::firestore::testutil;
+using firebase::firestore::model::ResourcePath;
+
NS_ASSUME_NONNULL_BEGIN
@interface FSTViewTests : XCTestCase
@@ -39,17 +44,16 @@ NS_ASSUME_NONNULL_BEGIN
/** Returns a new empty query to use for testing. */
- (FSTQuery *)queryForMessages {
- return [FSTQuery
- queryWithPath:[FSTResourcePath pathWithSegments:@[ @"rooms", @"eros", @"messages" ]]];
+ return [FSTQuery queryWithPath:ResourcePath{"rooms", "eros", "messages"}];
}
- (void)testAddsDocumentsBasedOnQuery {
FSTQuery *query = [self queryForMessages];
FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
- FSTDocument *doc1 = FSTTestDoc(@"rooms/eros/messages/1", 0, @{@"text" : @"msg1"}, NO);
- FSTDocument *doc2 = FSTTestDoc(@"rooms/eros/messages/2", 0, @{@"text" : @"msg2"}, NO);
- FSTDocument *doc3 = FSTTestDoc(@"rooms/other/messages/1", 0, @{@"text" : @"msg3"}, NO);
+ FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/1", 0, @{@"text" : @"msg1"}, NO);
+ FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/2", 0, @{@"text" : @"msg2"}, NO);
+ FSTDocument *doc3 = FSTTestDoc("rooms/other/messages/1", 0, @{@"text" : @"msg3"}, NO);
FSTViewSnapshot *_Nullable snapshot =
FSTTestApplyChanges(view, @[ doc1, doc2, doc3 ],
@@ -75,16 +79,16 @@ NS_ASSUME_NONNULL_BEGIN
FSTQuery *query = [self queryForMessages];
FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
- FSTDocument *doc1 = FSTTestDoc(@"rooms/eros/messages/1", 0, @{@"text" : @"msg1"}, NO);
- FSTDocument *doc2 = FSTTestDoc(@"rooms/eros/messages/2", 0, @{@"text" : @"msg2"}, NO);
- FSTDocument *doc3 = FSTTestDoc(@"rooms/eros/messages/3", 0, @{@"text" : @"msg3"}, NO);
+ FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/1", 0, @{@"text" : @"msg1"}, NO);
+ FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/2", 0, @{@"text" : @"msg2"}, NO);
+ FSTDocument *doc3 = FSTTestDoc("rooms/eros/messages/3", 0, @{@"text" : @"msg3"}, NO);
// initial state
FSTTestApplyChanges(view, @[ doc1, doc2 ], nil);
// delete doc2, add doc3
FSTViewSnapshot *snapshot =
- FSTTestApplyChanges(view, @[ FSTTestDeletedDoc(@"rooms/eros/messages/2", 0), doc3 ],
+ FSTTestApplyChanges(view, @[ FSTTestDeletedDoc("rooms/eros/messages/2", 0), doc3 ],
[FSTTargetChange changeWithDocuments:@[ doc1, doc3 ]
currentStatusUpdate:FSTCurrentStatusUpdateMarkCurrent]);
@@ -106,8 +110,8 @@ NS_ASSUME_NONNULL_BEGIN
FSTQuery *query = [self queryForMessages];
FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
- FSTDocument *doc1 = FSTTestDoc(@"rooms/eros/messages/1", 0, @{@"text" : @"msg1"}, NO);
- FSTDocument *doc2 = FSTTestDoc(@"rooms/eros/messages/2", 0, @{@"text" : @"msg2"}, NO);
+ FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/1", 0, @{@"text" : @"msg1"}, NO);
+ FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/2", 0, @{@"text" : @"msg2"}, NO);
// initial state
FSTTestApplyChanges(view, @[ doc1, doc2 ], nil);
@@ -128,17 +132,17 @@ NS_ASSUME_NONNULL_BEGIN
- (void)testFiltersDocumentsBasedOnQueryWithFilter {
FSTQuery *query = [self queryForMessages];
FSTRelationFilter *filter =
- [FSTRelationFilter filterWithField:FSTTestFieldPath(@"sort")
+ [FSTRelationFilter filterWithField:testutil::Field("sort")
filterOperator:FSTRelationFilterOperatorLessThanOrEqual
value:[FSTDoubleValue doubleValue:2]];
query = [query queryByAddingFilter:filter];
FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
- FSTDocument *doc1 = FSTTestDoc(@"rooms/eros/messages/1", 0, @{ @"sort" : @1 }, NO);
- FSTDocument *doc2 = FSTTestDoc(@"rooms/eros/messages/2", 0, @{ @"sort" : @2 }, NO);
- FSTDocument *doc3 = FSTTestDoc(@"rooms/eros/messages/3", 0, @{ @"sort" : @3 }, NO);
- FSTDocument *doc4 = FSTTestDoc(@"rooms/eros/messages/4", 0, @{}, NO); // no sort, no match
- FSTDocument *doc5 = FSTTestDoc(@"rooms/eros/messages/5", 0, @{ @"sort" : @1 }, NO);
+ FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/1", 0, @{ @"sort" : @1 }, NO);
+ FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/2", 0, @{ @"sort" : @2 }, NO);
+ FSTDocument *doc3 = FSTTestDoc("rooms/eros/messages/3", 0, @{ @"sort" : @3 }, NO);
+ FSTDocument *doc4 = FSTTestDoc("rooms/eros/messages/4", 0, @{}, NO); // no sort, no match
+ FSTDocument *doc5 = FSTTestDoc("rooms/eros/messages/5", 0, @{ @"sort" : @1 }, NO);
FSTViewSnapshot *snapshot = FSTTestApplyChanges(view, @[ doc1, doc2, doc3, doc4, doc5 ], nil);
@@ -160,16 +164,16 @@ NS_ASSUME_NONNULL_BEGIN
- (void)testUpdatesDocumentsBasedOnQueryWithFilter {
FSTQuery *query = [self queryForMessages];
FSTRelationFilter *filter =
- [FSTRelationFilter filterWithField:FSTTestFieldPath(@"sort")
+ [FSTRelationFilter filterWithField:testutil::Field("sort")
filterOperator:FSTRelationFilterOperatorLessThanOrEqual
value:[FSTDoubleValue doubleValue:2]];
query = [query queryByAddingFilter:filter];
FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
- FSTDocument *doc1 = FSTTestDoc(@"rooms/eros/messages/1", 0, @{ @"sort" : @1 }, NO);
- FSTDocument *doc2 = FSTTestDoc(@"rooms/eros/messages/2", 0, @{ @"sort" : @3 }, NO);
- FSTDocument *doc3 = FSTTestDoc(@"rooms/eros/messages/3", 0, @{ @"sort" : @2 }, NO);
- FSTDocument *doc4 = FSTTestDoc(@"rooms/eros/messages/4", 0, @{}, NO);
+ FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/1", 0, @{ @"sort" : @1 }, NO);
+ FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/2", 0, @{ @"sort" : @3 }, NO);
+ FSTDocument *doc3 = FSTTestDoc("rooms/eros/messages/3", 0, @{ @"sort" : @2 }, NO);
+ FSTDocument *doc4 = FSTTestDoc("rooms/eros/messages/4", 0, @{}, NO);
FSTViewSnapshot *snapshot = FSTTestApplyChanges(view, @[ doc1, doc2, doc3, doc4 ], nil);
@@ -177,9 +181,9 @@ NS_ASSUME_NONNULL_BEGIN
XCTAssertEqualObjects(snapshot.documents.arrayValue, (@[ doc1, doc3 ]));
- FSTDocument *newDoc2 = FSTTestDoc(@"rooms/eros/messages/2", 1, @{ @"sort" : @2 }, NO);
- FSTDocument *newDoc3 = FSTTestDoc(@"rooms/eros/messages/3", 1, @{ @"sort" : @3 }, NO);
- FSTDocument *newDoc4 = FSTTestDoc(@"rooms/eros/messages/4", 1, @{ @"sort" : @0 }, NO);
+ FSTDocument *newDoc2 = FSTTestDoc("rooms/eros/messages/2", 1, @{ @"sort" : @2 }, NO);
+ FSTDocument *newDoc3 = FSTTestDoc("rooms/eros/messages/3", 1, @{ @"sort" : @3 }, NO);
+ FSTDocument *newDoc4 = FSTTestDoc("rooms/eros/messages/4", 1, @{ @"sort" : @0 }, NO);
snapshot = FSTTestApplyChanges(view, @[ newDoc2, newDoc3, newDoc4 ], nil);
@@ -203,9 +207,9 @@ NS_ASSUME_NONNULL_BEGIN
query = [query queryBySettingLimit:2];
FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
- FSTDocument *doc1 = FSTTestDoc(@"rooms/eros/messages/1", 0, @{@"text" : @"msg1"}, NO);
- FSTDocument *doc2 = FSTTestDoc(@"rooms/eros/messages/2", 0, @{@"text" : @"msg2"}, NO);
- FSTDocument *doc3 = FSTTestDoc(@"rooms/eros/messages/3", 0, @{@"text" : @"msg3"}, NO);
+ FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/1", 0, @{@"text" : @"msg1"}, NO);
+ FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/2", 0, @{@"text" : @"msg2"}, NO);
+ FSTDocument *doc3 = FSTTestDoc("rooms/eros/messages/3", 0, @{@"text" : @"msg3"}, NO);
// initial state
FSTTestApplyChanges(view, @[ doc1, doc3 ], nil);
@@ -232,16 +236,15 @@ NS_ASSUME_NONNULL_BEGIN
- (void)testDoesntReportChangesForDocumentBeyondLimitOfQuery {
FSTQuery *query = [self queryForMessages];
- query =
- [query queryByAddingSortOrder:[FSTSortOrder sortOrderWithFieldPath:FSTTestFieldPath(@"num")
- ascending:YES]];
+ query = [query queryByAddingSortOrder:[FSTSortOrder sortOrderWithFieldPath:testutil::Field("num")
+ ascending:YES]];
query = [query queryBySettingLimit:2];
FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
- FSTDocument *doc1 = FSTTestDoc(@"rooms/eros/messages/1", 0, @{ @"num" : @1 }, NO);
- FSTDocument *doc2 = FSTTestDoc(@"rooms/eros/messages/2", 0, @{ @"num" : @2 }, NO);
- FSTDocument *doc3 = FSTTestDoc(@"rooms/eros/messages/3", 0, @{ @"num" : @3 }, NO);
- FSTDocument *doc4 = FSTTestDoc(@"rooms/eros/messages/4", 0, @{ @"num" : @4 }, NO);
+ FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/1", 0, @{ @"num" : @1 }, NO);
+ FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/2", 0, @{ @"num" : @2 }, NO);
+ FSTDocument *doc3 = FSTTestDoc("rooms/eros/messages/3", 0, @{ @"num" : @3 }, NO);
+ FSTDocument *doc4 = FSTTestDoc("rooms/eros/messages/4", 0, @{ @"num" : @4 }, NO);
// initial state
FSTTestApplyChanges(view, @[ doc1, doc2 ], nil);
@@ -250,7 +253,7 @@ NS_ASSUME_NONNULL_BEGIN
// doc2 will be modified + removed = removed
// doc3 will be added
// doc4 will be added + removed = nothing
- doc2 = FSTTestDoc(@"rooms/eros/messages/2", 1, @{ @"num" : @5 }, NO);
+ doc2 = FSTTestDoc("rooms/eros/messages/2", 1, @{ @"num" : @5 }, NO);
FSTViewDocumentChanges *viewDocChanges =
[view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc2, doc3, doc4 ])];
XCTAssertTrue(viewDocChanges.needsRefill);
@@ -282,9 +285,9 @@ NS_ASSUME_NONNULL_BEGIN
FSTQuery *query = [self queryForMessages];
FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
- FSTDocument *doc1 = FSTTestDoc(@"rooms/eros/messages/0", 0, @{}, NO);
- FSTDocument *doc2 = FSTTestDoc(@"rooms/eros/messages/1", 0, @{}, NO);
- FSTDocument *doc3 = FSTTestDoc(@"rooms/eros/messages/2", 0, @{}, NO);
+ FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/0", 0, @{}, NO);
+ FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/1", 0, @{}, NO);
+ FSTDocument *doc3 = FSTTestDoc("rooms/eros/messages/2", 0, @{}, NO);
FSTViewChange *change = [view
applyChangesToDocuments:[view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc1 ])]];
@@ -320,7 +323,7 @@ NS_ASSUME_NONNULL_BEGIN
@[ [FSTLimboDocumentChange changeWithType:FSTLimboDocumentChangeTypeAdded key:doc3.key] ]);
change = [view applyChangesToDocuments:[view computeChangesWithDocuments:FSTTestDocUpdates(@[
- FSTTestDeletedDoc(@"rooms/eros/messages/2",
+ FSTTestDeletedDoc("rooms/eros/messages/2",
1)
])]]; // remove
XCTAssertEqualObjects(
@@ -331,8 +334,8 @@ NS_ASSUME_NONNULL_BEGIN
- (void)testResumingQueryCreatesNoLimbos {
FSTQuery *query = [self queryForMessages];
- FSTDocument *doc1 = FSTTestDoc(@"rooms/eros/messages/0", 0, @{}, NO);
- FSTDocument *doc2 = FSTTestDoc(@"rooms/eros/messages/1", 0, @{}, NO);
+ FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/0", 0, @{}, NO);
+ FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/1", 0, @{}, NO);
// Unlike other cases, here the view is initialized with a set of previously synced documents
// which happens when listening to a previously listened-to query.
@@ -356,8 +359,8 @@ NS_ASSUME_NONNULL_BEGIN
- (void)testReturnsNeedsRefillOnDeleteInLimitQuery {
FSTQuery *query = [[self queryForMessages] queryBySettingLimit:2];
- FSTDocument *doc1 = FSTTestDoc(@"rooms/eros/messages/0", 0, @{}, NO);
- FSTDocument *doc2 = FSTTestDoc(@"rooms/eros/messages/1", 0, @{}, NO);
+ FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/0", 0, @{}, NO);
+ FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/1", 0, @{}, NO);
FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
// Start with a full view.
@@ -370,7 +373,7 @@ NS_ASSUME_NONNULL_BEGIN
// Remove one of the docs.
changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ FSTTestDeletedDoc(
- @"rooms/eros/messages/0", 0) ])];
+ "rooms/eros/messages/0", 0) ])];
[self assertDocSet:changes.documentSet containsDocs:@[ doc2 ]];
XCTAssertTrue(changes.needsRefill);
XCTAssertEqual(1, [changes.changeSet changes].count);
@@ -385,12 +388,12 @@ NS_ASSUME_NONNULL_BEGIN
- (void)testReturnsNeedsRefillOnReorderInLimitQuery {
FSTQuery *query = [self queryForMessages];
query =
- [query queryByAddingSortOrder:[FSTSortOrder sortOrderWithFieldPath:FSTTestFieldPath(@"order")
+ [query queryByAddingSortOrder:[FSTSortOrder sortOrderWithFieldPath:testutil::Field("order")
ascending:YES]];
query = [query queryBySettingLimit:2];
- FSTDocument *doc1 = FSTTestDoc(@"rooms/eros/messages/0", 0, @{ @"order" : @1 }, NO);
- FSTDocument *doc2 = FSTTestDoc(@"rooms/eros/messages/1", 0, @{ @"order" : @2 }, NO);
- FSTDocument *doc3 = FSTTestDoc(@"rooms/eros/messages/2", 0, @{ @"order" : @3 }, NO);
+ FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/0", 0, @{ @"order" : @1 }, NO);
+ FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/1", 0, @{ @"order" : @2 }, NO);
+ FSTDocument *doc3 = FSTTestDoc("rooms/eros/messages/2", 0, @{ @"order" : @3 }, NO);
FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
// Start with a full view.
@@ -402,7 +405,7 @@ NS_ASSUME_NONNULL_BEGIN
[view applyChangesToDocuments:changes];
// Move one of the docs.
- doc2 = FSTTestDoc(@"rooms/eros/messages/1", 1, @{ @"order" : @2000 }, NO);
+ doc2 = FSTTestDoc("rooms/eros/messages/1", 1, @{ @"order" : @2000 }, NO);
changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc2 ])];
[self assertDocSet:changes.documentSet containsDocs:@[ doc1, doc2 ]];
XCTAssertTrue(changes.needsRefill);
@@ -419,14 +422,14 @@ NS_ASSUME_NONNULL_BEGIN
- (void)testDoesntNeedRefillOnReorderWithinLimit {
FSTQuery *query = [self queryForMessages];
query =
- [query queryByAddingSortOrder:[FSTSortOrder sortOrderWithFieldPath:FSTTestFieldPath(@"order")
+ [query queryByAddingSortOrder:[FSTSortOrder sortOrderWithFieldPath:testutil::Field("order")
ascending:YES]];
query = [query queryBySettingLimit:3];
- FSTDocument *doc1 = FSTTestDoc(@"rooms/eros/messages/0", 0, @{ @"order" : @1 }, NO);
- FSTDocument *doc2 = FSTTestDoc(@"rooms/eros/messages/1", 0, @{ @"order" : @2 }, NO);
- FSTDocument *doc3 = FSTTestDoc(@"rooms/eros/messages/2", 0, @{ @"order" : @3 }, NO);
- FSTDocument *doc4 = FSTTestDoc(@"rooms/eros/messages/3", 0, @{ @"order" : @4 }, NO);
- FSTDocument *doc5 = FSTTestDoc(@"rooms/eros/messages/4", 0, @{ @"order" : @5 }, NO);
+ FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/0", 0, @{ @"order" : @1 }, NO);
+ FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/1", 0, @{ @"order" : @2 }, NO);
+ FSTDocument *doc3 = FSTTestDoc("rooms/eros/messages/2", 0, @{ @"order" : @3 }, NO);
+ FSTDocument *doc4 = FSTTestDoc("rooms/eros/messages/3", 0, @{ @"order" : @4 }, NO);
+ FSTDocument *doc5 = FSTTestDoc("rooms/eros/messages/4", 0, @{ @"order" : @5 }, NO);
FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
// Start with a full view.
@@ -438,7 +441,7 @@ NS_ASSUME_NONNULL_BEGIN
[view applyChangesToDocuments:changes];
// Move one of the docs.
- doc1 = FSTTestDoc(@"rooms/eros/messages/0", 1, @{ @"order" : @3 }, NO);
+ doc1 = FSTTestDoc("rooms/eros/messages/0", 1, @{ @"order" : @3 }, NO);
changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc1 ])];
[self assertDocSet:changes.documentSet containsDocs:@[ doc2, doc3, doc1 ]];
XCTAssertFalse(changes.needsRefill);
@@ -449,14 +452,14 @@ NS_ASSUME_NONNULL_BEGIN
- (void)testDoesntNeedRefillOnReorderAfterLimitQuery {
FSTQuery *query = [self queryForMessages];
query =
- [query queryByAddingSortOrder:[FSTSortOrder sortOrderWithFieldPath:FSTTestFieldPath(@"order")
+ [query queryByAddingSortOrder:[FSTSortOrder sortOrderWithFieldPath:testutil::Field("order")
ascending:YES]];
query = [query queryBySettingLimit:3];
- FSTDocument *doc1 = FSTTestDoc(@"rooms/eros/messages/0", 0, @{ @"order" : @1 }, NO);
- FSTDocument *doc2 = FSTTestDoc(@"rooms/eros/messages/1", 0, @{ @"order" : @2 }, NO);
- FSTDocument *doc3 = FSTTestDoc(@"rooms/eros/messages/2", 0, @{ @"order" : @3 }, NO);
- FSTDocument *doc4 = FSTTestDoc(@"rooms/eros/messages/3", 0, @{ @"order" : @4 }, NO);
- FSTDocument *doc5 = FSTTestDoc(@"rooms/eros/messages/4", 0, @{ @"order" : @5 }, NO);
+ FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/0", 0, @{ @"order" : @1 }, NO);
+ FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/1", 0, @{ @"order" : @2 }, NO);
+ FSTDocument *doc3 = FSTTestDoc("rooms/eros/messages/2", 0, @{ @"order" : @3 }, NO);
+ FSTDocument *doc4 = FSTTestDoc("rooms/eros/messages/3", 0, @{ @"order" : @4 }, NO);
+ FSTDocument *doc5 = FSTTestDoc("rooms/eros/messages/4", 0, @{ @"order" : @5 }, NO);
FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
// Start with a full view.
@@ -468,7 +471,7 @@ NS_ASSUME_NONNULL_BEGIN
[view applyChangesToDocuments:changes];
// Move one of the docs.
- doc4 = FSTTestDoc(@"rooms/eros/messages/3", 1, @{ @"order" : @6 }, NO);
+ doc4 = FSTTestDoc("rooms/eros/messages/3", 1, @{ @"order" : @6 }, NO);
changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc4 ])];
[self assertDocSet:changes.documentSet containsDocs:@[ doc1, doc2, doc3 ]];
XCTAssertFalse(changes.needsRefill);
@@ -478,8 +481,8 @@ NS_ASSUME_NONNULL_BEGIN
- (void)testDoesntNeedRefillForAdditionAfterTheLimit {
FSTQuery *query = [[self queryForMessages] queryBySettingLimit:2];
- FSTDocument *doc1 = FSTTestDoc(@"rooms/eros/messages/0", 0, @{}, NO);
- FSTDocument *doc2 = FSTTestDoc(@"rooms/eros/messages/1", 0, @{}, NO);
+ FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/0", 0, @{}, NO);
+ FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/1", 0, @{}, NO);
FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
// Start with a full view.
@@ -491,7 +494,7 @@ NS_ASSUME_NONNULL_BEGIN
[view applyChangesToDocuments:changes];
// Add a doc that is past the limit.
- FSTDocument *doc3 = FSTTestDoc(@"rooms/eros/messages/2", 1, @{}, NO);
+ FSTDocument *doc3 = FSTTestDoc("rooms/eros/messages/2", 1, @{}, NO);
changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc3 ])];
[self assertDocSet:changes.documentSet containsDocs:@[ doc1, doc2 ]];
XCTAssertFalse(changes.needsRefill);
@@ -501,8 +504,8 @@ NS_ASSUME_NONNULL_BEGIN
- (void)testDoesntNeedRefillForDeletionsWhenNotNearTheLimit {
FSTQuery *query = [[self queryForMessages] queryBySettingLimit:20];
- FSTDocument *doc1 = FSTTestDoc(@"rooms/eros/messages/0", 0, @{}, NO);
- FSTDocument *doc2 = FSTTestDoc(@"rooms/eros/messages/1", 0, @{}, NO);
+ FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/0", 0, @{}, NO);
+ FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/1", 0, @{}, NO);
FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
FSTViewDocumentChanges *changes =
@@ -514,7 +517,7 @@ NS_ASSUME_NONNULL_BEGIN
// Remove one of the docs.
changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ FSTTestDeletedDoc(
- @"rooms/eros/messages/1", 0) ])];
+ "rooms/eros/messages/1", 0) ])];
[self assertDocSet:changes.documentSet containsDocs:@[ doc1 ]];
XCTAssertFalse(changes.needsRefill);
XCTAssertEqual(1, [changes.changeSet changes].count);
@@ -523,8 +526,8 @@ NS_ASSUME_NONNULL_BEGIN
- (void)testHandlesApplyingIrrelevantDocs {
FSTQuery *query = [[self queryForMessages] queryBySettingLimit:2];
- FSTDocument *doc1 = FSTTestDoc(@"rooms/eros/messages/0", 0, @{}, NO);
- FSTDocument *doc2 = FSTTestDoc(@"rooms/eros/messages/1", 0, @{}, NO);
+ FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/0", 0, @{}, NO);
+ FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/1", 0, @{}, NO);
FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
// Start with a full view.
@@ -537,7 +540,7 @@ NS_ASSUME_NONNULL_BEGIN
// Remove a doc that isn't even in the results.
changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ FSTTestDeletedDoc(
- @"rooms/eros/messages/2", 0) ])];
+ "rooms/eros/messages/2", 0) ])];
[self assertDocSet:changes.documentSet containsDocs:@[ doc1, doc2 ]];
XCTAssertFalse(changes.needsRefill);
XCTAssertEqual(0, [changes.changeSet changes].count);
@@ -546,8 +549,8 @@ NS_ASSUME_NONNULL_BEGIN
- (void)testComputesMutatedKeys {
FSTQuery *query = [self queryForMessages];
- FSTDocument *doc1 = FSTTestDoc(@"rooms/eros/messages/0", 0, @{}, NO);
- FSTDocument *doc2 = FSTTestDoc(@"rooms/eros/messages/1", 0, @{}, NO);
+ FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/0", 0, @{}, NO);
+ FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/1", 0, @{}, NO);
FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
// Start with a full view.
@@ -556,15 +559,15 @@ NS_ASSUME_NONNULL_BEGIN
[view applyChangesToDocuments:changes];
XCTAssertEqualObjects(changes.mutatedKeys, FSTTestDocKeySet(@[]));
- FSTDocument *doc3 = FSTTestDoc(@"rooms/eros/messages/2", 0, @{}, YES);
+ FSTDocument *doc3 = FSTTestDoc("rooms/eros/messages/2", 0, @{}, YES);
changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc3 ])];
XCTAssertEqualObjects(changes.mutatedKeys, FSTTestDocKeySet(@[ doc3.key ]));
}
- (void)testRemovesKeysFromMutatedKeysWhenNewDocHasNoLocalChanges {
FSTQuery *query = [self queryForMessages];
- FSTDocument *doc1 = FSTTestDoc(@"rooms/eros/messages/0", 0, @{}, NO);
- FSTDocument *doc2 = FSTTestDoc(@"rooms/eros/messages/1", 0, @{}, YES);
+ FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/0", 0, @{}, NO);
+ FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/1", 0, @{}, YES);
FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
// Start with a full view.
@@ -573,7 +576,7 @@ NS_ASSUME_NONNULL_BEGIN
[view applyChangesToDocuments:changes];
XCTAssertEqualObjects(changes.mutatedKeys, FSTTestDocKeySet(@[ doc2.key ]));
- FSTDocument *doc2Prime = FSTTestDoc(@"rooms/eros/messages/1", 0, @{}, NO);
+ FSTDocument *doc2Prime = FSTTestDoc("rooms/eros/messages/1", 0, @{}, NO);
changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc2Prime ])];
[view applyChangesToDocuments:changes];
XCTAssertEqualObjects(changes.mutatedKeys, FSTTestDocKeySet(@[]));
@@ -581,8 +584,8 @@ NS_ASSUME_NONNULL_BEGIN
- (void)testRemembersLocalMutationsFromPreviousSnapshot {
FSTQuery *query = [self queryForMessages];
- FSTDocument *doc1 = FSTTestDoc(@"rooms/eros/messages/0", 0, @{}, NO);
- FSTDocument *doc2 = FSTTestDoc(@"rooms/eros/messages/1", 0, @{}, YES);
+ FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/0", 0, @{}, NO);
+ FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/1", 0, @{}, YES);
FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
// Start with a full view.
@@ -591,7 +594,7 @@ NS_ASSUME_NONNULL_BEGIN
[view applyChangesToDocuments:changes];
XCTAssertEqualObjects(changes.mutatedKeys, FSTTestDocKeySet(@[ doc2.key ]));
- FSTDocument *doc3 = FSTTestDoc(@"rooms/eros/messages/2", 0, @{}, NO);
+ FSTDocument *doc3 = FSTTestDoc("rooms/eros/messages/2", 0, @{}, NO);
changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc3 ])];
[view applyChangesToDocuments:changes];
XCTAssertEqualObjects(changes.mutatedKeys, FSTTestDocKeySet(@[ doc2.key ]));
@@ -599,8 +602,8 @@ NS_ASSUME_NONNULL_BEGIN
- (void)testRemembersLocalMutationsFromPreviousCallToComputeChangesWithDocuments {
FSTQuery *query = [self queryForMessages];
- FSTDocument *doc1 = FSTTestDoc(@"rooms/eros/messages/0", 0, @{}, NO);
- FSTDocument *doc2 = FSTTestDoc(@"rooms/eros/messages/1", 0, @{}, YES);
+ FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/0", 0, @{}, NO);
+ FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/1", 0, @{}, YES);
FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
// Start with a full view.
@@ -608,7 +611,7 @@ NS_ASSUME_NONNULL_BEGIN
[view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc1, doc2 ])];
XCTAssertEqualObjects(changes.mutatedKeys, FSTTestDocKeySet(@[ doc2.key ]));
- FSTDocument *doc3 = FSTTestDoc(@"rooms/eros/messages/2", 0, @{}, NO);
+ FSTDocument *doc3 = FSTTestDoc("rooms/eros/messages/2", 0, @{}, NO);
changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc3 ]) previousChanges:changes];
XCTAssertEqualObjects(changes.mutatedKeys, FSTTestDocKeySet(@[ doc2.key ]));
}
diff --git a/Firestore/Example/Tests/GoogleTest/FSTGoogleTestTests.mm b/Firestore/Example/Tests/GoogleTest/FSTGoogleTestTests.mm
index 4fc77ed..ec14880 100644
--- a/Firestore/Example/Tests/GoogleTest/FSTGoogleTestTests.mm
+++ b/Firestore/Example/Tests/GoogleTest/FSTGoogleTestTests.mm
@@ -17,7 +17,8 @@
#import <XCTest/XCTest.h>
#import <objc/runtime.h>
-#include "Firestore/Source/Util/FSTAssert.h"
+#import "Firestore/Source/Util/FSTAssert.h"
+
#include "gtest/gtest.h"
/**
diff --git a/Firestore/Example/Tests/Integration/API/FIRCursorTests.m b/Firestore/Example/Tests/Integration/API/FIRCursorTests.mm
index dc9da83..2188b8a 100644
--- a/Firestore/Example/Tests/Integration/API/FIRCursorTests.m
+++ b/Firestore/Example/Tests/Integration/API/FIRCursorTests.mm
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-@import FirebaseFirestore;
+#import <FirebaseFirestore/FirebaseFirestore.h>
#import <XCTest/XCTest.h>
@@ -192,4 +192,82 @@
XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot), (@[ @{ @"v" : @"d", @"sort" : @3.0 } ]));
}
+FIRTimestamp *TimestampWithMicros(int64_t seconds, int32_t micros) {
+ // Firestore only supports microsecond resolution, so use a microsecond as a minimum value for
+ // nanoseconds.
+ return [FIRTimestamp timestampWithSeconds:seconds nanoseconds:micros * 1000];
+}
+
+- (void)testTimestampsCanBePassedToQueriesAsLimits {
+ FIRCollectionReference *testCollection = [self collectionRefWithDocuments:@{
+ @"a" : @{@"timestamp" : TimestampWithMicros(100, 2)},
+ @"b" : @{@"timestamp" : TimestampWithMicros(100, 5)},
+ @"c" : @{@"timestamp" : TimestampWithMicros(100, 3)},
+ @"d" : @{@"timestamp" : TimestampWithMicros(100, 1)},
+ // Number of microseconds deliberately repeated.
+ @"e" : @{@"timestamp" : TimestampWithMicros(100, 5)},
+ @"f" : @{@"timestamp" : TimestampWithMicros(100, 4)},
+ }];
+ FIRQuery *query = [testCollection queryOrderedByField:@"timestamp"];
+ FIRQuerySnapshot *querySnapshot =
+ [self readDocumentSetForRef:[[query queryStartingAfterValues:@[ TimestampWithMicros(100, 2) ]]
+ queryEndingAtValues:@[ TimestampWithMicros(100, 5) ]]];
+ XCTAssertEqualObjects(FIRQuerySnapshotGetIDs(querySnapshot), (@[ @"c", @"f", @"b", @"e" ]));
+}
+
+- (void)testTimestampsCanBePassedToQueriesInWhereClause {
+ FIRTimestamp *currentTimestamp = [FIRTimestamp timestamp];
+ int64_t seconds = currentTimestamp.seconds;
+ int32_t micros = currentTimestamp.nanoseconds / 1000;
+ FIRCollectionReference *testCollection = [self collectionRefWithDocuments:@{
+ @"a" : @{
+ @"timestamp" : TimestampWithMicros(seconds, micros + 2),
+ },
+ @"b" : @{
+ @"timestamp" : TimestampWithMicros(seconds, micros - 1),
+ },
+ @"c" : @{
+ @"timestamp" : TimestampWithMicros(seconds, micros + 3),
+ },
+ @"d" : @{
+ @"timestamp" : TimestampWithMicros(seconds, micros),
+ },
+ @"e" : @{
+ @"timestamp" : TimestampWithMicros(seconds, micros + 1),
+ }
+ }];
+
+ FIRQuerySnapshot *querySnapshot = [self
+ readDocumentSetForRef:[[testCollection queryWhereField:@"timestamp"
+ isGreaterThanOrEqualTo:TimestampWithMicros(seconds, micros)]
+ queryWhereField:@"timestamp"
+ isLessThan:TimestampWithMicros(seconds, micros + 3)]];
+ XCTAssertEqualObjects(FIRQuerySnapshotGetIDs(querySnapshot), (@[ @"d", @"e", @"a" ]));
+}
+
+- (void)testTimestampsAreTruncatedToMicroseconds {
+ FIRTimestamp *nanos = [FIRTimestamp timestampWithSeconds:0 nanoseconds:123456789];
+ FIRTimestamp *micros = [FIRTimestamp timestampWithSeconds:0 nanoseconds:123456000];
+ FIRTimestamp *millis = [FIRTimestamp timestampWithSeconds:0 nanoseconds:123000000];
+ FIRCollectionReference *testCollection = [self collectionRefWithDocuments:@{
+ @"a" : @{@"timestamp" : nanos},
+ }];
+
+ FIRQuerySnapshot *querySnapshot =
+ [self readDocumentSetForRef:[testCollection queryWhereField:@"timestamp" isEqualTo:nanos]];
+ XCTAssertEqualObjects(FIRQuerySnapshotGetIDs(querySnapshot), (@[ @"a" ]));
+
+ // Because Timestamp should have been truncated to microseconds, the microsecond timestamp
+ // should be considered equal to the nanosecond one.
+ querySnapshot =
+ [self readDocumentSetForRef:[testCollection queryWhereField:@"timestamp" isEqualTo:micros]];
+ XCTAssertEqualObjects(FIRQuerySnapshotGetIDs(querySnapshot), (@[ @"a" ]));
+
+ // The truncation is just to the microseconds, however, so the millisecond timestamp should be
+ // treated as different and thus the query should return no results.
+ querySnapshot =
+ [self readDocumentSetForRef:[testCollection queryWhereField:@"timestamp" isEqualTo:millis]];
+ XCTAssertEqualObjects(FIRQuerySnapshotGetIDs(querySnapshot), (@[]));
+}
+
@end
diff --git a/Firestore/Example/Tests/Integration/API/FIRDatabaseTests.m b/Firestore/Example/Tests/Integration/API/FIRDatabaseTests.mm
index 087eb01..9b6febe 100644
--- a/Firestore/Example/Tests/Integration/API/FIRDatabaseTests.m
+++ b/Firestore/Example/Tests/Integration/API/FIRDatabaseTests.mm
@@ -14,13 +14,14 @@
* limitations under the License.
*/
-@import FirebaseFirestore;
+#import <FirebaseFirestore/FirebaseFirestore.h>
#import <XCTest/XCTest.h>
#import "Firestore/Example/Tests/Util/FSTIntegrationTestCase.h"
#import "Firestore/Source/API/FIRFirestore+Internal.h"
#import "Firestore/Source/Core/FSTFirestoreClient.h"
+#import "Firestore/Source/Util/FSTDispatchQueue.h"
@interface FIRDatabaseTests : FSTIntegrationTestCase
@end
@@ -83,6 +84,13 @@
XCTAssertFalse(result.exists);
}
+- (void)testCanRetrieveDocumentThatDoesNotExist {
+ FIRDocumentReference *doc = [[self.db collectionWithPath:@"rooms"] documentWithAutoID];
+ FIRDocumentSnapshot *result = [self readDocumentForRef:doc];
+ XCTAssertNil(result.data);
+ XCTAssertNil(result[@"foo"]);
+}
+
- (void)testCannotUpdateNonexistentDocument {
FIRDocumentReference *doc = [[self.db collectionWithPath:@"rooms"] documentWithAutoID];
@@ -136,7 +144,7 @@
[self expectationWithDescription:@"testCanMergeDataWithAnExistingDocumentUsingSet"];
[doc setData:mergeData
- options:[FIRSetOptions merge]
+ merge:YES
completion:^(NSError *error) {
XCTAssertNil(error);
[completed fulfill];
@@ -163,7 +171,7 @@
[self expectationWithDescription:@"testCanMergeDataWithAnExistingDocumentUsingSet"];
[doc setData:mergeData
- options:[FIRSetOptions merge]
+ merge:YES
completion:^(NSError *error) {
XCTAssertNil(error);
[completed fulfill];
@@ -173,7 +181,7 @@
FIRDocumentSnapshot *document = [self readDocumentForRef:doc];
XCTAssertEqual(document[@"updated"], @NO);
- XCTAssertTrue([document[@"time"] isKindOfClass:[NSDate class]]);
+ XCTAssertTrue([document[@"time"] isKindOfClass:[FIRTimestamp class]]);
}
- (void)testCanDeleteFieldUsingMerge {
@@ -195,7 +203,7 @@
[self expectationWithDescription:@"testCanMergeDataWithAnExistingDocumentUsingSet"];
[doc setData:mergeData
- options:[FIRSetOptions merge]
+ merge:YES
completion:^(NSError *error) {
XCTAssertNil(error);
[completed fulfill];
@@ -236,7 +244,7 @@
[self expectationWithDescription:@"testCanMergeDataWithAnExistingDocumentUsingSet"];
[doc setData:mergeData
- options:[FIRSetOptions merge]
+ merge:YES
completion:^(NSError *error) {
XCTAssertNil(error);
[completed fulfill];
@@ -362,32 +370,30 @@
__block XCTestExpectation *dataCompletion;
__block int callbacks = 0;
- FIRDocumentListenOptions *options =
- [[FIRDocumentListenOptions options] includeMetadataChanges:YES];
-
- id<FIRListenerRegistration> listenerRegistration =
- [docRef addSnapshotListenerWithOptions:options
- listener:^(FIRDocumentSnapshot *_Nullable doc, NSError *error) {
- callbacks++;
+ id<FIRListenerRegistration> listenerRegistration = [docRef
+ addSnapshotListenerWithIncludeMetadataChanges:YES
+ listener:^(FIRDocumentSnapshot *_Nullable doc,
+ NSError *error) {
+ callbacks++;
- if (callbacks == 1) {
- XCTAssertNotNil(doc);
- XCTAssertFalse(doc.exists);
- [emptyCompletion fulfill];
+ if (callbacks == 1) {
+ XCTAssertNotNil(doc);
+ XCTAssertFalse(doc.exists);
+ [emptyCompletion fulfill];
- } else if (callbacks == 2) {
- XCTAssertEqualObjects(doc.data, (@{ @"a" : @1 }));
- XCTAssertEqual(doc.metadata.hasPendingWrites, YES);
+ } else if (callbacks == 2) {
+ XCTAssertEqualObjects(doc.data, (@{ @"a" : @1 }));
+ XCTAssertEqual(doc.metadata.hasPendingWrites, YES);
- } else if (callbacks == 3) {
- XCTAssertEqualObjects(doc.data, (@{ @"a" : @1 }));
- XCTAssertEqual(doc.metadata.hasPendingWrites, NO);
- [dataCompletion fulfill];
+ } else if (callbacks == 3) {
+ XCTAssertEqualObjects(doc.data, (@{ @"a" : @1 }));
+ XCTAssertEqual(doc.metadata.hasPendingWrites, NO);
+ [dataCompletion fulfill];
- } else if (callbacks == 4) {
- XCTFail("Should not have received this callback");
- }
- }];
+ } else if (callbacks == 4) {
+ XCTFail("Should not have received this callback");
+ }
+ }];
[self awaitExpectations];
dataCompletion = [self expectationWithDescription:@"data snapshot"];
@@ -450,40 +456,38 @@
__block XCTestExpectation *changeCompletion;
__block int callbacks = 0;
- FIRDocumentListenOptions *options =
- [[FIRDocumentListenOptions options] includeMetadataChanges:YES];
+ id<FIRListenerRegistration> listenerRegistration = [docRef
+ addSnapshotListenerWithIncludeMetadataChanges:YES
+ listener:^(FIRDocumentSnapshot *_Nullable doc,
+ NSError *error) {
+ callbacks++;
- id<FIRListenerRegistration> listenerRegistration =
- [docRef addSnapshotListenerWithOptions:options
- listener:^(FIRDocumentSnapshot *_Nullable doc, NSError *error) {
- callbacks++;
-
- if (callbacks == 1) {
- XCTAssertEqualObjects(doc.data, initialData);
- XCTAssertEqual(doc.metadata.hasPendingWrites, NO);
- XCTAssertEqual(doc.metadata.isFromCache, YES);
-
- } else if (callbacks == 2) {
- XCTAssertEqualObjects(doc.data, initialData);
- XCTAssertEqual(doc.metadata.hasPendingWrites, NO);
- XCTAssertEqual(doc.metadata.isFromCache, NO);
- [initialCompletion fulfill];
-
- } else if (callbacks == 3) {
- XCTAssertEqualObjects(doc.data, changedData);
- XCTAssertEqual(doc.metadata.hasPendingWrites, YES);
- XCTAssertEqual(doc.metadata.isFromCache, NO);
-
- } else if (callbacks == 4) {
- XCTAssertEqualObjects(doc.data, changedData);
- XCTAssertEqual(doc.metadata.hasPendingWrites, NO);
- XCTAssertEqual(doc.metadata.isFromCache, NO);
- [changeCompletion fulfill];
-
- } else if (callbacks == 5) {
- XCTFail("Should not have received this callback");
- }
- }];
+ if (callbacks == 1) {
+ XCTAssertEqualObjects(doc.data, initialData);
+ XCTAssertEqual(doc.metadata.hasPendingWrites, NO);
+ XCTAssertEqual(doc.metadata.isFromCache, YES);
+
+ } else if (callbacks == 2) {
+ XCTAssertEqualObjects(doc.data, initialData);
+ XCTAssertEqual(doc.metadata.hasPendingWrites, NO);
+ XCTAssertEqual(doc.metadata.isFromCache, NO);
+ [initialCompletion fulfill];
+
+ } else if (callbacks == 3) {
+ XCTAssertEqualObjects(doc.data, changedData);
+ XCTAssertEqual(doc.metadata.hasPendingWrites, YES);
+ XCTAssertEqual(doc.metadata.isFromCache, NO);
+
+ } else if (callbacks == 4) {
+ XCTAssertEqualObjects(doc.data, changedData);
+ XCTAssertEqual(doc.metadata.hasPendingWrites, NO);
+ XCTAssertEqual(doc.metadata.isFromCache, NO);
+ [changeCompletion fulfill];
+
+ } else if (callbacks == 5) {
+ XCTFail("Should not have received this callback");
+ }
+ }];
[self awaitExpectations];
changeCompletion = [self expectationWithDescription:@"listen for changed data"];
@@ -540,39 +544,37 @@
[self writeDocumentRef:docRef data:initialData];
- FIRDocumentListenOptions *options =
- [[FIRDocumentListenOptions options] includeMetadataChanges:YES];
-
XCTestExpectation *initialCompletion = [self expectationWithDescription:@"initial data"];
__block XCTestExpectation *changeCompletion;
__block int callbacks = 0;
- id<FIRListenerRegistration> listenerRegistration =
- [docRef addSnapshotListenerWithOptions:options
- listener:^(FIRDocumentSnapshot *_Nullable doc, NSError *error) {
- callbacks++;
+ id<FIRListenerRegistration> listenerRegistration = [docRef
+ addSnapshotListenerWithIncludeMetadataChanges:YES
+ listener:^(FIRDocumentSnapshot *_Nullable doc,
+ NSError *error) {
+ callbacks++;
- if (callbacks == 1) {
- XCTAssertEqualObjects(doc.data, initialData);
- XCTAssertEqual(doc.metadata.hasPendingWrites, NO);
- XCTAssertEqual(doc.metadata.isFromCache, YES);
+ if (callbacks == 1) {
+ XCTAssertEqualObjects(doc.data, initialData);
+ XCTAssertEqual(doc.metadata.hasPendingWrites, NO);
+ XCTAssertEqual(doc.metadata.isFromCache, YES);
- } else if (callbacks == 2) {
- XCTAssertEqualObjects(doc.data, initialData);
- XCTAssertEqual(doc.metadata.hasPendingWrites, NO);
- XCTAssertEqual(doc.metadata.isFromCache, NO);
- [initialCompletion fulfill];
+ } else if (callbacks == 2) {
+ XCTAssertEqualObjects(doc.data, initialData);
+ XCTAssertEqual(doc.metadata.hasPendingWrites, NO);
+ XCTAssertEqual(doc.metadata.isFromCache, NO);
+ [initialCompletion fulfill];
- } else if (callbacks == 3) {
- XCTAssertFalse(doc.exists);
- XCTAssertEqual(doc.metadata.hasPendingWrites, NO);
- XCTAssertEqual(doc.metadata.isFromCache, NO);
- [changeCompletion fulfill];
+ } else if (callbacks == 3) {
+ XCTAssertFalse(doc.exists);
+ XCTAssertEqual(doc.metadata.hasPendingWrites, NO);
+ XCTAssertEqual(doc.metadata.isFromCache, NO);
+ [changeCompletion fulfill];
- } else if (callbacks == 4) {
- XCTFail("Should not have received this callback");
- }
- }];
+ } else if (callbacks == 4) {
+ XCTFail("Should not have received this callback");
+ }
+ }];
[self awaitExpectations];
changeCompletion = [self expectationWithDescription:@"listen for changed data"];
@@ -603,6 +605,7 @@
} else if (callbacks == 2) {
XCTAssertEqual(docSet.count, 1);
+ XCTAssertTrue([docSet.documents[0] isKindOfClass:[FIRQueryDocumentSnapshot class]]);
XCTAssertEqualObjects(docSet.documents[0].data, newData);
XCTAssertEqual(docSet.documents[0].metadata.hasPendingWrites, YES);
[changeCompletion fulfill];
@@ -844,7 +847,7 @@
FIRFirestore *firestore = doc.firestore;
NSDictionary<NSString *, id> *data = @{@"a" : @"b"};
- [firestore.client disableNetworkWithCompletion:^(NSError *error) {
+ [firestore disableNetworkWithCompletion:^(NSError *error) {
XCTAssertNil(error);
[doc setData:data
@@ -853,7 +856,7 @@
[writeEpectation fulfill];
}];
- [firestore.client enableNetworkWithCompletion:^(NSError *error) {
+ [firestore enableNetworkWithCompletion:^(NSError *error) {
XCTAssertNil(error);
[networkExpectation fulfill];
}];
@@ -883,7 +886,7 @@
__weak FIRDocumentReference *weakDoc = doc;
- [firestore.client disableNetworkWithCompletion:^(NSError *error) {
+ [firestore disableNetworkWithCompletion:^(NSError *error) {
XCTAssertNil(error);
[doc setData:data
completion:^(NSError *_Nullable error) {
@@ -904,7 +907,7 @@
// Verify that we are reading from cache.
XCTAssertTrue(snapshot.metadata.fromCache);
XCTAssertEqualObjects(snapshot.data, data);
- [firestore.client enableNetworkWithCompletion:^(NSError *error) {
+ [firestore enableNetworkWithCompletion:^(NSError *error) {
[networkExpectation fulfill];
}];
}];
@@ -918,7 +921,7 @@
FIRFirestore *firestore = doc.firestore;
[self writeDocumentRef:doc data:@{@"foo" : @"bar"}];
- [self waitForIdleFirestore:firestore];
+ [[self queueForFirestore:firestore] runDelayedCallbacksUntil:FSTTimerIDWriteStreamIdle];
[self writeDocumentRef:doc data:@{@"foo" : @"bar"}];
}
@@ -927,8 +930,29 @@
FIRFirestore *firestore = doc.firestore;
[self readSnapshotForRef:[self documentRef] requireOnline:YES];
- [self waitForIdleFirestore:firestore];
+ [[self queueForFirestore:firestore] runDelayedCallbacksUntil:FSTTimerIDListenStreamIdle];
[self readSnapshotForRef:[self documentRef] requireOnline:YES];
}
+- (void)testCanDisableNetwork {
+ FIRDocumentReference *doc = [self documentRef];
+ FIRFirestore *firestore = doc.firestore;
+
+ [firestore enableNetworkWithCompletion:[self completionForExpectationWithName:@"Enable network"]];
+ [self awaitExpectations];
+ [firestore
+ enableNetworkWithCompletion:[self completionForExpectationWithName:@"Enable network again"]];
+ [self awaitExpectations];
+ [firestore
+ disableNetworkWithCompletion:[self completionForExpectationWithName:@"Disable network"]];
+ [self awaitExpectations];
+ [firestore
+ disableNetworkWithCompletion:[self
+ completionForExpectationWithName:@"Disable network again"]];
+ [self awaitExpectations];
+ [firestore
+ enableNetworkWithCompletion:[self completionForExpectationWithName:@"Final enable network"]];
+ [self awaitExpectations];
+}
+
@end
diff --git a/Firestore/Example/Tests/Integration/API/FIRFieldsTests.m b/Firestore/Example/Tests/Integration/API/FIRFieldsTests.mm
index b647f52..30db5e3 100644
--- a/Firestore/Example/Tests/Integration/API/FIRFieldsTests.m
+++ b/Firestore/Example/Tests/Integration/API/FIRFieldsTests.mm
@@ -14,7 +14,8 @@
* limitations under the License.
*/
-@import FirebaseFirestore;
+#import <FirebaseFirestore/FIRTimestamp.h>
+#import <FirebaseFirestore/FirebaseFirestore.h>
#import <XCTest/XCTest.h>
@@ -25,6 +26,10 @@
@interface FIRFieldsTests : FSTIntegrationTestCase
@end
+NSDictionary<NSString *, id> *testDataWithTimestamps(FIRTimestamp *timestamp) {
+ return @{ @"timestamp" : timestamp, @"nested" : @{@"timestamp2" : timestamp} };
+}
+
@implementation FIRFieldsTests
- (NSDictionary<NSString *, id> *)testNestedDataNumbered:(int)number {
@@ -220,4 +225,83 @@
[self awaitExpectations];
}
+- (FIRDocumentSnapshot *)snapshotWithTimestamps:(FIRTimestamp *)timestamp {
+ FIRDocumentReference *doc = [self documentRef];
+ NSDictionary<NSString *, id> *data =
+ @{ @"timestamp" : timestamp,
+ @"nested" : @{@"timestamp2" : timestamp} };
+ [self writeDocumentRef:doc data:data];
+ return [self readDocumentForRef:doc];
+}
+
+// Note: timestampsInSnapshotsEnabled is set to "true" in FSTIntegrationTestCase, so this test is
+// not affected by the current default in FIRFirestoreSettings.
+- (void)testTimestampsInSnapshots {
+ FIRTimestamp *originalTimestamp = [FIRTimestamp timestampWithSeconds:100 nanoseconds:123456789];
+ FIRDocumentReference *doc = [self documentRef];
+ [self writeDocumentRef:doc data:testDataWithTimestamps(originalTimestamp)];
+
+ FIRDocumentSnapshot *snapshot = [self readDocumentForRef:doc];
+ NSDictionary<NSString *, id> *data = [snapshot data];
+ // Timestamp are currently truncated to microseconds after being written to the database.
+ FIRTimestamp *truncatedTimestamp =
+ [FIRTimestamp timestampWithSeconds:originalTimestamp.seconds
+ nanoseconds:originalTimestamp.nanoseconds / 1000 * 1000];
+
+ FIRTimestamp *timestampFromSnapshot = snapshot[@"timestamp"];
+ FIRTimestamp *timestampFromData = data[@"timestamp"];
+ XCTAssertEqualObjects(truncatedTimestamp, timestampFromData);
+ XCTAssertEqualObjects(timestampFromSnapshot, timestampFromData);
+
+ timestampFromSnapshot = snapshot[@"nested.timestamp2"];
+ timestampFromData = data[@"nested"][@"timestamp2"];
+ XCTAssertEqualObjects(truncatedTimestamp, timestampFromData);
+ XCTAssertEqualObjects(timestampFromSnapshot, timestampFromData);
+}
+@end
+
+@interface FIRTimestampsInSnapshotsLegacyBehaviorTests : FSTIntegrationTestCase
+@end
+
+@implementation FIRTimestampsInSnapshotsLegacyBehaviorTests
+
+- (void)setUp {
+ [super setUp];
+ // Settings can only be redefined before client is initialized, so this has to happen in setUp.
+ FIRFirestoreSettings *settings = self.db.settings;
+ settings.timestampsInSnapshotsEnabled = NO;
+ self.db.settings = settings;
+}
+
+- (void)testLegacyBehaviorForTimestampFields {
+ NSDate *originalDate = [NSDate date];
+ FIRDocumentReference *doc = [self documentRef];
+ [self writeDocumentRef:doc
+ data:testDataWithTimestamps([FIRTimestamp timestampWithDate:originalDate])];
+ FIRDocumentSnapshot *snapshot = [self readDocumentForRef:doc];
+ NSDictionary<NSString *, id> *data = [snapshot data];
+ double microsecond = 0.000001;
+
+ NSDate *timestampFromSnapshot = snapshot[@"timestamp"];
+ NSDate *timestampFromData = data[@"timestamp"];
+ XCTAssertEqualObjects(timestampFromSnapshot, timestampFromData);
+ XCTAssertEqualWithAccuracy([timestampFromSnapshot timeIntervalSince1970],
+ [originalDate timeIntervalSince1970], microsecond);
+
+ timestampFromSnapshot = snapshot[@"nested.timestamp2"];
+ timestampFromData = data[@"nested"][@"timestamp2"];
+ XCTAssertEqualObjects(timestampFromSnapshot, timestampFromData);
+ XCTAssertEqualWithAccuracy([timestampFromSnapshot timeIntervalSince1970],
+ [originalDate timeIntervalSince1970], microsecond);
+}
+
+- (void)testLegacyBehaviorForServerTimestampFields {
+ FIRDocumentReference *doc = [self documentRef];
+ [self writeDocumentRef:doc data:@{@"when" : [FIRFieldValue fieldValueForServerTimestamp]}];
+
+ FIRDocumentSnapshot *snapshot = [self readDocumentForRef:doc];
+ XCTAssertTrue([snapshot[@"when"] isKindOfClass:[NSDate class]]);
+ XCTAssertTrue([snapshot.data[@"when"] isKindOfClass:[NSDate class]]);
+}
+
@end
diff --git a/Firestore/Example/Tests/Integration/API/FIRListenerRegistrationTests.m b/Firestore/Example/Tests/Integration/API/FIRListenerRegistrationTests.mm
index 9751844..036ab32 100644
--- a/Firestore/Example/Tests/Integration/API/FIRListenerRegistrationTests.m
+++ b/Firestore/Example/Tests/Integration/API/FIRListenerRegistrationTests.mm
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-@import FirebaseFirestore;
+#import <FirebaseFirestore/FirebaseFirestore.h>
#import <XCTest/XCTest.h>
@@ -128,35 +128,4 @@
[two remove];
}
-- (void)testWatchSurvivesNetworkDisconnect {
- XCTestExpectation *testExpectiation =
- [self expectationWithDescription:@"testWatchSurvivesNetworkDisconnect"];
-
- FIRCollectionReference *collectionRef = [self collectionRef];
- FIRDocumentReference *docRef = [collectionRef documentWithAutoID];
-
- FIRFirestore *firestore = collectionRef.firestore;
-
- FIRQueryListenOptions *options = [[[FIRQueryListenOptions options]
- includeDocumentMetadataChanges:YES] includeQueryMetadataChanges:YES];
-
- [collectionRef addSnapshotListenerWithOptions:options
- listener:^(FIRQuerySnapshot *snapshot, NSError *error) {
- XCTAssertNil(error);
- if (!snapshot.empty && !snapshot.metadata.fromCache) {
- [testExpectiation fulfill];
- }
- }];
-
- [firestore.client disableNetworkWithCompletion:^(NSError *error) {
- XCTAssertNil(error);
- [docRef setData:@{@"foo" : @"bar"}];
- [firestore.client enableNetworkWithCompletion:^(NSError *error) {
- XCTAssertNil(error);
- }];
- }];
-
- [self awaitExpectations];
-}
-
@end
diff --git a/Firestore/Example/Tests/Integration/API/FIRQueryTests.m b/Firestore/Example/Tests/Integration/API/FIRQueryTests.mm
index 180b423..32d746e 100644
--- a/Firestore/Example/Tests/Integration/API/FIRQueryTests.m
+++ b/Firestore/Example/Tests/Integration/API/FIRQueryTests.mm
@@ -14,13 +14,14 @@
* limitations under the License.
*/
-@import FirebaseFirestore;
+#import <FirebaseFirestore/FirebaseFirestore.h>
#import <XCTest/XCTest.h>
-#import "Firestore/Source/Core/FSTFirestoreClient.h"
-
+#import "Firestore/Example/Tests/Util/FSTEventAccumulator.h"
#import "Firestore/Example/Tests/Util/FSTIntegrationTestCase.h"
+#import "Firestore/Source/API/FIRFirestore+Internal.h"
+#import "Firestore/Source/Core/FSTFirestoreClient.h"
@interface FIRQueryTests : FSTIntegrationTestCase
@end
@@ -111,6 +112,23 @@
XCTAssertEqualObjects(FIRQuerySnapshotGetIDs(snapshot), (@[ @"b", @"a" ]));
}
+- (void)testQueryWithPredicate {
+ FIRCollectionReference *collRef = [self collectionRefWithDocuments:@{
+ @"a" : @{@"a" : @1},
+ @"b" : @{@"a" : @2},
+ @"c" : @{@"a" : @3}
+ }];
+
+ NSPredicate *predicate = [NSPredicate predicateWithFormat:@"a < 3"];
+ FIRQuery *query = [collRef queryFilteredUsingPredicate:predicate];
+ query = [query queryOrderedByFieldPath:[[FIRFieldPath alloc] initWithFields:@[ @"a" ]]
+ descending:YES];
+
+ FIRQuerySnapshot *snapshot = [self readDocumentSetForRef:query];
+
+ XCTAssertEqualObjects(FIRQuerySnapshotGetIDs(snapshot), (@[ @"b", @"a" ]));
+}
+
- (void)testFilterOnInfinity {
FIRCollectionReference *collRef = [self collectionRefWithDocuments:@{
@"a" : @{@"inf" : @(INFINITY)},
@@ -194,4 +212,90 @@
XCTAssertEqualObjects(FIRQuerySnapshotGetData(docs), (@[ testDocs[@"ab"], testDocs[@"ba"] ]));
}
+- (void)testWatchSurvivesNetworkDisconnect {
+ XCTestExpectation *testExpectiation =
+ [self expectationWithDescription:@"testWatchSurvivesNetworkDisconnect"];
+
+ FIRCollectionReference *collectionRef = [self collectionRef];
+ FIRDocumentReference *docRef = [collectionRef documentWithAutoID];
+
+ FIRFirestore *firestore = collectionRef.firestore;
+
+ FIRQueryListenOptions *options = [[[FIRQueryListenOptions options]
+ includeDocumentMetadataChanges:YES] includeQueryMetadataChanges:YES];
+
+ [collectionRef addSnapshotListenerWithOptions:options
+ listener:^(FIRQuerySnapshot *snapshot, NSError *error) {
+ XCTAssertNil(error);
+ if (!snapshot.empty && !snapshot.metadata.fromCache) {
+ [testExpectiation fulfill];
+ }
+ }];
+
+ [firestore disableNetworkWithCompletion:^(NSError *error) {
+ XCTAssertNil(error);
+ [docRef setData:@{@"foo" : @"bar"}];
+ [firestore enableNetworkWithCompletion:^(NSError *error) {
+ XCTAssertNil(error);
+ }];
+ }];
+
+ [self awaitExpectations];
+}
+
+- (void)testQueriesFireFromCacheWhenOffline {
+ NSDictionary *testDocs = @{
+ @"a" : @{@"foo" : @1},
+ };
+ FIRCollectionReference *collection = [self collectionRefWithDocuments:testDocs];
+
+ FIRQueryListenOptions *options = [[[FIRQueryListenOptions options]
+ includeDocumentMetadataChanges:YES] includeQueryMetadataChanges:YES];
+ id<FIRListenerRegistration> registration =
+ [collection addSnapshotListenerWithOptions:options
+ listener:self.eventAccumulator.valueEventHandler];
+
+ FIRQuerySnapshot *querySnap = [self.eventAccumulator awaitEventWithName:@"initial event"];
+ XCTAssertEqualObjects(FIRQuerySnapshotGetData(querySnap), @[ @{ @"foo" : @1 } ]);
+ XCTAssertEqual(querySnap.metadata.isFromCache, NO);
+
+ [self disableNetwork];
+ querySnap = [self.eventAccumulator awaitEventWithName:@"offline event with isFromCache=YES"];
+ XCTAssertEqual(querySnap.metadata.isFromCache, YES);
+
+ // TODO(b/70631617): There's currently a backend bug that prevents us from using a resume token
+ // right away (against hexa at least). So we sleep. :-( :-( Anything over ~10ms seems to be
+ // sufficient.
+ [NSThread sleepForTimeInterval:0.2f];
+
+ [self enableNetwork];
+ querySnap = [self.eventAccumulator awaitEventWithName:@"back online event with isFromCache=NO"];
+ XCTAssertEqual(querySnap.metadata.isFromCache, NO);
+
+ [registration remove];
+}
+
+- (void)testCanHaveMultipleMutationsWhileOffline {
+ FIRCollectionReference *col = [self collectionRef];
+
+ // set a few docs to known values
+ NSDictionary *initialDocs =
+ @{ @"doc1" : @{@"key1" : @"value1"},
+ @"doc2" : @{@"key2" : @"value2"} };
+ [self writeAllDocuments:initialDocs toCollection:col];
+
+ // go offline for the rest of this test
+ [self disableNetwork];
+
+ // apply *multiple* mutations while offline
+ [[col documentWithPath:@"doc1"] setData:@{@"key1b" : @"value1b"}];
+ [[col documentWithPath:@"doc2"] setData:@{@"key2b" : @"value2b"}];
+
+ FIRQuerySnapshot *result = [self readDocumentSetForRef:col];
+ XCTAssertEqualObjects(FIRQuerySnapshotGetData(result), (@[
+ @{@"key1b" : @"value1b"},
+ @{@"key2b" : @"value2b"},
+ ]));
+}
+
@end
diff --git a/Firestore/Example/Tests/Integration/API/FIRServerTimestampTests.m b/Firestore/Example/Tests/Integration/API/FIRServerTimestampTests.m
deleted file mode 100644
index 2ee3966..0000000
--- a/Firestore/Example/Tests/Integration/API/FIRServerTimestampTests.m
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * Copyright 2017 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 FirebaseFirestore;
-
-#import <XCTest/XCTest.h>
-
-#import "Firestore/Source/Core/FSTFirestoreClient.h"
-
-#import "Firestore/Example/Tests/Util/FSTEventAccumulator.h"
-#import "Firestore/Example/Tests/Util/FSTIntegrationTestCase.h"
-
-@interface FIRServerTimestampTests : FSTIntegrationTestCase
-@end
-
-@implementation FIRServerTimestampTests {
- // Data written in tests via set.
- NSDictionary *_setData;
-
- // Base and update data used for update tests.
- NSDictionary *_initialData;
- NSDictionary *_updateData;
-
- // A document reference to read and write to.
- FIRDocumentReference *_docRef;
-
- // Accumulator used to capture events during the test.
- FSTEventAccumulator *_accumulator;
-
- // Listener registration for a listener maintained during the course of the test.
- id<FIRListenerRegistration> _listenerRegistration;
-}
-
-- (void)setUp {
- [super setUp];
-
- // Data written in tests via set.
- _setData = @{
- @"a" : @42,
- @"when" : [FIRFieldValue fieldValueForServerTimestamp],
- @"deep" : @{@"when" : [FIRFieldValue fieldValueForServerTimestamp]}
- };
-
- // Base and update data used for update tests.
- _initialData = @{ @"a" : @42 };
- _updateData = @{
- @"when" : [FIRFieldValue fieldValueForServerTimestamp],
- @"deep" : @{@"when" : [FIRFieldValue fieldValueForServerTimestamp]}
- };
-
- _docRef = [self documentRef];
- _accumulator = [FSTEventAccumulator accumulatorForTest:self];
- _listenerRegistration = [_docRef addSnapshotListener:_accumulator.handler];
-
- // Wait for initial nil snapshot to avoid potential races.
- FIRDocumentSnapshot *initialSnapshot = [_accumulator awaitEventWithName:@"initial event"];
- XCTAssertFalse(initialSnapshot.exists);
-}
-
-- (void)tearDown {
- [_listenerRegistration remove];
-
- [super tearDown];
-}
-
-// Returns the expected data, with an arbitrary timestamp substituted in.
-- (NSDictionary *)expectedDataWithTimestamp:(id _Nullable)timestamp {
- return @{ @"a" : @42, @"when" : timestamp, @"deep" : @{@"when" : timestamp} };
-}
-
-/** Writes _initialData and waits for the corresponding snapshot. */
-- (void)writeInitialData {
- [self writeDocumentRef:_docRef data:_initialData];
- FIRDocumentSnapshot *initialDataSnap = [_accumulator awaitEventWithName:@"Initial data event."];
- XCTAssertEqualObjects(initialDataSnap.data, _initialData);
-}
-
-/** Waits for a snapshot containing _setData but with NSNull for the timestamps. */
-- (void)waitForLocalEvent {
- FIRDocumentSnapshot *localSnap = [_accumulator awaitEventWithName:@"Local event."];
- XCTAssertEqualObjects(localSnap.data, [self expectedDataWithTimestamp:[NSNull null]]);
-}
-
-/** Waits for a snapshot containing _setData but with resolved server timestamps. */
-- (void)waitForRemoteEvent {
- // server event should have a resolved timestamp; verify it.
- FIRDocumentSnapshot *remoteSnap = [_accumulator awaitEventWithName:@"Remote event"];
- XCTAssertTrue(remoteSnap.exists);
- NSDate *when = remoteSnap[@"when"];
- XCTAssertTrue([when isKindOfClass:[NSDate class]]);
- // Tolerate up to 10 seconds of clock skew between client and server.
- XCTAssertEqualWithAccuracy(when.timeIntervalSinceNow, 0, 10);
-
- // Validate the rest of the document.
- XCTAssertEqualObjects(remoteSnap.data, [self expectedDataWithTimestamp:when]);
-}
-
-- (void)runTransactionBlock:(void (^)(FIRTransaction *transaction))transactionBlock {
- XCTestExpectation *expectation = [self expectationWithDescription:@"transaction complete"];
- [_docRef.firestore runTransactionWithBlock:^id(FIRTransaction *transaction, NSError **pError) {
- transactionBlock(transaction);
- return nil;
- }
- completion:^(id result, NSError *error) {
- XCTAssertNil(error);
- [expectation fulfill];
- }];
- [self awaitExpectations];
-}
-
-- (void)testServerTimestampsWorkViaSet {
- [self writeDocumentRef:_docRef data:_setData];
- [self waitForLocalEvent];
- [self waitForRemoteEvent];
-}
-
-- (void)testServerTimestampsWorkViaUpdate {
- [self writeInitialData];
- [self updateDocumentRef:_docRef data:_updateData];
- [self waitForLocalEvent];
- [self waitForRemoteEvent];
-}
-
-- (void)testServerTimestampsWorkViaTransactionSet {
- [self runTransactionBlock:^(FIRTransaction *transaction) {
- [transaction setData:_setData forDocument:_docRef];
- }];
-
- [self waitForRemoteEvent];
-}
-
-- (void)testServerTimestampsWorkViaTransactionUpdate {
- [self writeInitialData];
- [self runTransactionBlock:^(FIRTransaction *transaction) {
- [transaction updateData:_updateData forDocument:_docRef];
- }];
- [self waitForRemoteEvent];
-}
-
-- (void)testServerTimestampsFailViaUpdateOnNonexistentDocument {
- XCTestExpectation *expectation = [self expectationWithDescription:@"update complete"];
- [_docRef updateData:_updateData
- completion:^(NSError *error) {
- XCTAssertNotNil(error);
- XCTAssertEqualObjects(error.domain, FIRFirestoreErrorDomain);
- XCTAssertEqual(error.code, FIRFirestoreErrorCodeNotFound);
- [expectation fulfill];
- }];
- [self awaitExpectations];
-}
-
-- (void)testServerTimestampsFailViaTransactionUpdateOnNonexistentDocument {
- XCTestExpectation *expectation = [self expectationWithDescription:@"transaction complete"];
- [_docRef.firestore runTransactionWithBlock:^id(FIRTransaction *transaction, NSError **pError) {
- [transaction updateData:_updateData forDocument:_docRef];
- return nil;
- }
- completion:^(id result, NSError *error) {
- XCTAssertNotNil(error);
- XCTAssertEqualObjects(error.domain, FIRFirestoreErrorDomain);
- // TODO(b/35201829): This should be NotFound, but right now we retry transactions on any
- // error and so this turns into Aborted instead.
- // TODO(mikelehen): Actually it's FailedPrecondition, unlike Android. What do we want???
- XCTAssertEqual(error.code, FIRFirestoreErrorCodeFailedPrecondition);
- [expectation fulfill];
- }];
- [self awaitExpectations];
-}
-
-@end
diff --git a/Firestore/Example/Tests/Integration/API/FIRServerTimestampTests.mm b/Firestore/Example/Tests/Integration/API/FIRServerTimestampTests.mm
new file mode 100644
index 0000000..4d51434
--- /dev/null
+++ b/Firestore/Example/Tests/Integration/API/FIRServerTimestampTests.mm
@@ -0,0 +1,318 @@
+/*
+ * Copyright 2017 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 <FirebaseFirestore/FirebaseFirestore.h>
+
+#import <XCTest/XCTest.h>
+
+#import "Firestore/Example/Tests/Util/FSTEventAccumulator.h"
+#import "Firestore/Example/Tests/Util/FSTIntegrationTestCase.h"
+#import "Firestore/Source/API/FIRFirestore+Internal.h"
+#import "Firestore/Source/Core/FSTFirestoreClient.h"
+
+@interface FIRServerTimestampTests : FSTIntegrationTestCase
+@end
+
+@implementation FIRServerTimestampTests {
+ // Data written in tests via set.
+ NSDictionary *_setData;
+
+ // Base and update data used for update tests.
+ NSDictionary *_initialData;
+ NSDictionary *_updateData;
+
+ // A document reference to read and write to.
+ FIRDocumentReference *_docRef;
+
+ // Accumulator used to capture events during the test.
+ FSTEventAccumulator *_accumulator;
+
+ // Listener registration for a listener maintained during the course of the test.
+ id<FIRListenerRegistration> _listenerRegistration;
+
+ // Snapshot options that return the previous value for pending server timestamps.
+ FIRSnapshotOptions *_returnPreviousValue;
+ FIRSnapshotOptions *_returnEstimatedValue;
+}
+
+- (void)setUp {
+ [super setUp];
+
+ _returnPreviousValue =
+ [FIRSnapshotOptions serverTimestampBehavior:FIRServerTimestampBehaviorPrevious];
+ _returnEstimatedValue =
+ [FIRSnapshotOptions serverTimestampBehavior:FIRServerTimestampBehaviorEstimate];
+
+ // Data written in tests via set.
+ _setData = @{
+ @"a" : @42,
+ @"when" : [FIRFieldValue fieldValueForServerTimestamp],
+ @"deep" : @{@"when" : [FIRFieldValue fieldValueForServerTimestamp]}
+ };
+
+ // Base and update data used for update tests.
+ _initialData = @{ @"a" : @42 };
+ _updateData = @{
+ @"when" : [FIRFieldValue fieldValueForServerTimestamp],
+ @"deep" : @{@"when" : [FIRFieldValue fieldValueForServerTimestamp]}
+ };
+
+ _docRef = [self documentRef];
+ _accumulator = [FSTEventAccumulator accumulatorForTest:self];
+ _listenerRegistration = [_docRef addSnapshotListener:_accumulator.valueEventHandler];
+
+ // Wait for initial nil snapshot to avoid potential races.
+ FIRDocumentSnapshot *initialSnapshot = [_accumulator awaitEventWithName:@"initial event"];
+ XCTAssertFalse(initialSnapshot.exists);
+}
+
+- (void)tearDown {
+ [_listenerRegistration remove];
+
+ [super tearDown];
+}
+
+#pragma mark - Test Helpers
+
+/** Returns the expected data, with the specified timestamp substituted in. */
+- (NSDictionary *)expectedDataWithTimestamp:(nullable id)timestamp {
+ return @{ @"a" : @42, @"when" : timestamp, @"deep" : @{@"when" : timestamp} };
+}
+
+/** Writes _initialData and waits for the corresponding snapshot. */
+- (void)writeInitialData {
+ [self writeDocumentRef:_docRef data:_initialData];
+ FIRDocumentSnapshot *initialDataSnap = [_accumulator awaitEventWithName:@"Initial data event."];
+ XCTAssertEqualObjects(initialDataSnap.data, _initialData);
+}
+
+/** Waits for a snapshot with local writes. */
+- (FIRDocumentSnapshot *)waitForLocalEvent {
+ FIRDocumentSnapshot *snapshot;
+ do {
+ snapshot = [_accumulator awaitEventWithName:@"Local event."];
+ } while (!snapshot.metadata.hasPendingWrites);
+ return snapshot;
+}
+
+/** Waits for a snapshot that has no pending writes */
+- (FIRDocumentSnapshot *)waitForRemoteEvent {
+ FIRDocumentSnapshot *snapshot;
+ do {
+ snapshot = [_accumulator awaitEventWithName:@"Remote event."];
+ } while (snapshot.metadata.hasPendingWrites);
+ return snapshot;
+}
+
+/** Verifies a snapshot containing _setData but with NSNull for the timestamps. */
+- (void)verifyTimestampsAreNullInSnapshot:(FIRDocumentSnapshot *)snapshot {
+ XCTAssertEqualObjects(snapshot.data, [self expectedDataWithTimestamp:[NSNull null]]);
+}
+
+/** Verifies a snapshot containing _setData but with a local estimate for the timestamps. */
+- (void)verifyTimestampsAreEstimatedInSnapshot:(FIRDocumentSnapshot *)snapshot {
+ id timestamp = [snapshot valueForField:@"when" options:_returnEstimatedValue];
+ XCTAssertTrue([timestamp isKindOfClass:[FIRTimestamp class]]);
+ XCTAssertEqualObjects([snapshot dataWithOptions:_returnEstimatedValue],
+ [self expectedDataWithTimestamp:timestamp]);
+}
+
+/**
+ * Verifies a snapshot containing _setData but using the previous field value for server
+ * timestamps.
+ */
+- (void)verifyTimestampsInSnapshot:(FIRDocumentSnapshot *)snapshot
+ fromPreviousSnapshot:(nullable FIRDocumentSnapshot *)previousSnapshot {
+ if (previousSnapshot == nil) {
+ XCTAssertEqualObjects([snapshot dataWithOptions:_returnPreviousValue],
+ [self expectedDataWithTimestamp:[NSNull null]]);
+ } else {
+ XCTAssertEqualObjects([snapshot dataWithOptions:_returnPreviousValue],
+ [self expectedDataWithTimestamp:previousSnapshot[@"when"]]);
+ }
+}
+
+/** Verifies a snapshot containing _setData but with resolved server timestamps. */
+- (void)verifySnapshotWithResolvedTimestamps:(FIRDocumentSnapshot *)snapshot {
+ XCTAssertTrue(snapshot.exists);
+ FIRTimestamp *when = snapshot[@"when"];
+ XCTAssertTrue([when isKindOfClass:[FIRTimestamp class]]);
+ // Tolerate up to 10 seconds of clock skew between client and server.
+ XCTAssertEqualWithAccuracy(when.seconds, [FIRTimestamp timestamp].seconds, 10);
+
+ // Validate the rest of the document.
+ XCTAssertEqualObjects(snapshot.data, [self expectedDataWithTimestamp:when]);
+}
+
+/** Runs a transaction block. */
+- (void)runTransactionBlock:(void (^)(FIRTransaction *transaction))transactionBlock {
+ XCTestExpectation *expectation = [self expectationWithDescription:@"transaction complete"];
+ [_docRef.firestore runTransactionWithBlock:^id(FIRTransaction *transaction, NSError **pError) {
+ transactionBlock(transaction);
+ return nil;
+ }
+ completion:^(id result, NSError *error) {
+ XCTAssertNil(error);
+ [expectation fulfill];
+ }];
+ [self awaitExpectations];
+}
+
+#pragma mark - Test Cases
+
+- (void)testServerTimestampsWorkViaSet {
+ [self writeDocumentRef:_docRef data:_setData];
+ [self verifyTimestampsAreNullInSnapshot:[self waitForLocalEvent]];
+ [self verifySnapshotWithResolvedTimestamps:[self waitForRemoteEvent]];
+}
+
+- (void)testServerTimestampsWorkViaUpdate {
+ [self writeInitialData];
+ [self updateDocumentRef:_docRef data:_updateData];
+ [self verifyTimestampsAreNullInSnapshot:[self waitForLocalEvent]];
+ [self verifySnapshotWithResolvedTimestamps:[self waitForRemoteEvent]];
+}
+
+- (void)testServerTimestampsWithEstimatedValue {
+ [self writeDocumentRef:_docRef data:_setData];
+ [self verifyTimestampsAreEstimatedInSnapshot:[self waitForLocalEvent]];
+ [self verifySnapshotWithResolvedTimestamps:[self waitForRemoteEvent]];
+}
+
+- (void)testServerTimestampsWithPreviousValue {
+ [self writeDocumentRef:_docRef data:_setData];
+ [self verifyTimestampsInSnapshot:[self waitForLocalEvent] fromPreviousSnapshot:nil];
+ FIRDocumentSnapshot *remoteSnapshot = [self waitForRemoteEvent];
+
+ [_docRef updateData:_updateData];
+ [self verifyTimestampsInSnapshot:[self waitForLocalEvent] fromPreviousSnapshot:remoteSnapshot];
+
+ [self verifySnapshotWithResolvedTimestamps:[self waitForRemoteEvent]];
+}
+
+- (void)testServerTimestampsWithPreviousValueOfDifferentType {
+ [self writeDocumentRef:_docRef data:_setData];
+ [self verifyTimestampsInSnapshot:[self waitForLocalEvent] fromPreviousSnapshot:nil];
+ [self verifySnapshotWithResolvedTimestamps:[self waitForRemoteEvent]];
+
+ [_docRef updateData:@{@"a" : [FIRFieldValue fieldValueForServerTimestamp]}];
+ FIRDocumentSnapshot *localSnapshot = [self waitForLocalEvent];
+ XCTAssertEqualObjects([localSnapshot valueForField:@"a"], [NSNull null]);
+ XCTAssertEqualObjects([localSnapshot valueForField:@"a" options:_returnPreviousValue], @42);
+ XCTAssertTrue([[localSnapshot valueForField:@"a" options:_returnEstimatedValue]
+ isKindOfClass:[FIRTimestamp class]]);
+
+ FIRDocumentSnapshot *remoteSnapshot = [self waitForRemoteEvent];
+ XCTAssertTrue([[remoteSnapshot valueForField:@"a"] isKindOfClass:[FIRTimestamp class]]);
+ XCTAssertTrue([[remoteSnapshot valueForField:@"a" options:_returnPreviousValue]
+ isKindOfClass:[FIRTimestamp class]]);
+ XCTAssertTrue([[remoteSnapshot valueForField:@"a" options:_returnEstimatedValue]
+ isKindOfClass:[FIRTimestamp class]]);
+}
+
+- (void)testServerTimestampsWithConsecutiveUpdates {
+ [self writeDocumentRef:_docRef data:_setData];
+ [self verifyTimestampsInSnapshot:[self waitForLocalEvent] fromPreviousSnapshot:nil];
+ [self verifySnapshotWithResolvedTimestamps:[self waitForRemoteEvent]];
+
+ [self disableNetwork];
+
+ [_docRef updateData:@{@"a" : [FIRFieldValue fieldValueForServerTimestamp]}];
+ FIRDocumentSnapshot *localSnapshot = [self waitForLocalEvent];
+ XCTAssertEqualObjects([localSnapshot valueForField:@"a" options:_returnPreviousValue], @42);
+
+ [_docRef updateData:@{@"a" : [FIRFieldValue fieldValueForServerTimestamp]}];
+ localSnapshot = [self waitForLocalEvent];
+ XCTAssertEqualObjects([localSnapshot valueForField:@"a" options:_returnPreviousValue], @42);
+
+ [self enableNetwork];
+
+ FIRDocumentSnapshot *remoteSnapshot = [self waitForRemoteEvent];
+ XCTAssertTrue([[remoteSnapshot valueForField:@"a"] isKindOfClass:[FIRTimestamp class]]);
+}
+
+- (void)testServerTimestampsPreviousValueFromLocalMutation {
+ [self writeDocumentRef:_docRef data:_setData];
+ [self verifyTimestampsInSnapshot:[self waitForLocalEvent] fromPreviousSnapshot:nil];
+ [self verifySnapshotWithResolvedTimestamps:[self waitForRemoteEvent]];
+
+ [self disableNetwork];
+
+ [_docRef updateData:@{@"a" : [FIRFieldValue fieldValueForServerTimestamp]}];
+ FIRDocumentSnapshot *localSnapshot = [self waitForLocalEvent];
+ XCTAssertEqualObjects([localSnapshot valueForField:@"a" options:_returnPreviousValue], @42);
+
+ [_docRef updateData:@{ @"a" : @1337 }];
+ localSnapshot = [self waitForLocalEvent];
+ XCTAssertEqualObjects([localSnapshot valueForField:@"a"], @1337);
+
+ [_docRef updateData:@{@"a" : [FIRFieldValue fieldValueForServerTimestamp]}];
+ localSnapshot = [self waitForLocalEvent];
+ XCTAssertEqualObjects([localSnapshot valueForField:@"a" options:_returnPreviousValue], @1337);
+
+ [self enableNetwork];
+
+ FIRDocumentSnapshot *remoteSnapshot = [self waitForRemoteEvent];
+ XCTAssertTrue([[remoteSnapshot valueForField:@"a"] isKindOfClass:[FIRTimestamp class]]);
+}
+
+- (void)testServerTimestampsWorkViaTransactionSet {
+ [self runTransactionBlock:^(FIRTransaction *transaction) {
+ [transaction setData:_setData forDocument:_docRef];
+ }];
+
+ [self verifySnapshotWithResolvedTimestamps:[self waitForRemoteEvent]];
+}
+
+- (void)testServerTimestampsWorkViaTransactionUpdate {
+ [self writeInitialData];
+ [self runTransactionBlock:^(FIRTransaction *transaction) {
+ [transaction updateData:_updateData forDocument:_docRef];
+ }];
+ [self verifySnapshotWithResolvedTimestamps:[self waitForRemoteEvent]];
+}
+
+- (void)testServerTimestampsFailViaUpdateOnNonexistentDocument {
+ XCTestExpectation *expectation = [self expectationWithDescription:@"update complete"];
+ [_docRef updateData:_updateData
+ completion:^(NSError *error) {
+ XCTAssertNotNil(error);
+ XCTAssertEqualObjects(error.domain, FIRFirestoreErrorDomain);
+ XCTAssertEqual(error.code, FIRFirestoreErrorCodeNotFound);
+ [expectation fulfill];
+ }];
+ [self awaitExpectations];
+}
+
+- (void)testServerTimestampsFailViaTransactionUpdateOnNonexistentDocument {
+ XCTestExpectation *expectation = [self expectationWithDescription:@"transaction complete"];
+ [_docRef.firestore runTransactionWithBlock:^id(FIRTransaction *transaction, NSError **pError) {
+ [transaction updateData:_updateData forDocument:_docRef];
+ return nil;
+ }
+ completion:^(id result, NSError *error) {
+ XCTAssertNotNil(error);
+ XCTAssertEqualObjects(error.domain, FIRFirestoreErrorDomain);
+ // TODO(b/35201829): This should be NotFound, but right now we retry transactions on any
+ // error and so this turns into Aborted instead.
+ // TODO(mikelehen): Actually it's FailedPrecondition, unlike Android. What do we want???
+ XCTAssertEqual(error.code, FIRFirestoreErrorCodeFailedPrecondition);
+ [expectation fulfill];
+ }];
+ [self awaitExpectations];
+}
+
+@end
diff --git a/Firestore/Example/Tests/Integration/API/FIRTypeTests.m b/Firestore/Example/Tests/Integration/API/FIRTypeTests.mm
index 638835f..740cde0 100644
--- a/Firestore/Example/Tests/Integration/API/FIRTypeTests.m
+++ b/Firestore/Example/Tests/Integration/API/FIRTypeTests.mm
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-@import FirebaseFirestore;
+#import <FirebaseFirestore/FirebaseFirestore.h>
#import <XCTest/XCTest.h>
@@ -40,7 +40,8 @@
- (void)testCanReadAndWriteArrayFields {
[self assertSuccessfulRoundtrip:@{
- @"array" : @[ @1, @"foo", @{@"deep" : @YES}, [NSNull null] ]
+ @"array" : @[ @1, @"foo",
+ @{ @"deep" : @YES }, [NSNull null] ]
}];
}
@@ -55,9 +56,22 @@
}];
}
-- (void)testCanReadAndWriteTimestampFields {
+- (void)testCanReadAndWriteDateFields {
// Choose a value that can be converted losslessly between fixed point and double
- NSDate *timestamp = [NSDate dateWithTimeIntervalSince1970:1491847082.125];
+ NSDate *date = [NSDate dateWithTimeIntervalSince1970:1491847082.125];
+
+ // NSDates are read back as FIRTimestamps, so assertSuccessfulRoundtrip cannot be used here.
+ FIRDocumentReference *doc = [self.db documentWithPath:@"rooms/eros"];
+ [self writeDocumentRef:doc data:@{@"date" : date}];
+ FIRDocumentSnapshot *document = [self readDocumentForRef:doc];
+ XCTAssertTrue(document.exists);
+ XCTAssertEqualObjects(document.data, @{@"date" : [FIRTimestamp timestampWithDate:date]});
+}
+
+- (void)testCanReadAndWriteTimestampFields {
+ // Timestamps are currently truncated to microseconds on the backend, so only be precise to
+ // microseconds to ensure the value read back is exactly the same.
+ FIRTimestamp *timestamp = [FIRTimestamp timestampWithSeconds:123456 nanoseconds:123456000];
[self assertSuccessfulRoundtrip:@{@"timestamp" : timestamp}];
}
diff --git a/Firestore/Example/Tests/Integration/API/FIRValidationTests.m b/Firestore/Example/Tests/Integration/API/FIRValidationTests.mm
index a318c47..2c6a2a8 100644
--- a/Firestore/Example/Tests/Integration/API/FIRValidationTests.m
+++ b/Firestore/Example/Tests/Integration/API/FIRValidationTests.mm
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-@import FirebaseFirestore;
+#import <FirebaseFirestore/FirebaseFirestore.h>
#import <XCTest/XCTest.h>
@@ -132,7 +132,7 @@
}
- (void)testPathsWithEmptySegmentsFail {
- // We're only testing using collectionWithPath since the validation happens in FSTPath which is
+ // We're only testing using collectionWithPath since the validation happens in BasePath which is
// shared by all methods that accept paths.
// leading / trailing slashes are okay.
@@ -169,7 +169,7 @@
}
- (void)testWritesWithIndirectlyNestedArraysSucceed {
- NSDictionary<NSString *, id> *data = @{ @"nested-array" : @[ @1, @{@"foo" : @[ @2 ]} ] };
+ NSDictionary<NSString *, id> *data = @{ @"nested-array" : @[ @1, @{ @"foo" : @[ @2 ] } ] };
FIRDocumentReference *ref = [self documentRef];
FIRDocumentReference *ref2 = [self documentRef];
@@ -282,7 +282,7 @@
[self expectSet:@{@"foo" : [FIRFieldValue fieldValueForDelete]}
toFailWithReason:
@"FieldValue.delete() can only be used with updateData() and setData() with "
- @"SetOptions.merge()."];
+ @"merge:true (found in field foo)"];
}
- (void)testUpdatesWithNestedFieldValueDeleteFail {
@@ -304,7 +304,7 @@
FIRDocumentReference *badRef = [db2 documentWithPath:@"foo/bar"];
FIRWriteBatch *batch = [db1 batch];
FSTAssertThrows([batch setData:data forDocument:badRef], reason);
- FSTAssertThrows([batch setData:data forDocument:badRef options:[FIRSetOptions merge]], reason);
+ FSTAssertThrows([batch setData:data forDocument:badRef merge:YES], reason);
FSTAssertThrows([batch updateData:data forDocument:badRef], reason);
FSTAssertThrows([batch deleteDocument:badRef], reason);
}
@@ -322,7 +322,7 @@
[db1 runTransactionWithBlock:^id(FIRTransaction *txn, NSError **pError) {
FSTAssertThrows([txn getDocument:badRef error:nil], reason);
FSTAssertThrows([txn setData:data forDocument:badRef], reason);
- FSTAssertThrows([txn setData:data forDocument:badRef options:[FIRSetOptions merge]], reason);
+ FSTAssertThrows([txn setData:data forDocument:badRef merge:YES], reason);
FSTAssertThrows([txn updateData:data forDocument:badRef], reason);
FSTAssertThrows([txn deleteDocument:badRef], reason);
return nil;
diff --git a/Firestore/Example/Tests/Integration/API/FIRWriteBatchTests.m b/Firestore/Example/Tests/Integration/API/FIRWriteBatchTests.mm
index 562c29f..3f2d64b 100644
--- a/Firestore/Example/Tests/Integration/API/FIRWriteBatchTests.m
+++ b/Firestore/Example/Tests/Integration/API/FIRWriteBatchTests.mm
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-@import FirebaseFirestore;
+#import <FirebaseFirestore/FirebaseFirestore.h>
#import <XCTest/XCTest.h>
@@ -35,6 +35,29 @@
[self awaitExpectations];
}
+- (void)testCommitWithoutCompletionHandler {
+ FIRDocumentReference *doc = [self documentRef];
+ FIRWriteBatch *batch1 = [doc.firestore batch];
+ [batch1 setData:@{@"aa" : @"bb"} forDocument:doc];
+ [batch1 commitWithCompletion:nil];
+ FIRDocumentSnapshot *snapshot1 = [self readDocumentForRef:doc];
+ XCTAssertTrue(snapshot1.exists);
+ XCTAssertEqualObjects(snapshot1.data, @{@"aa" : @"bb"});
+
+ FIRWriteBatch *batch2 = [doc.firestore batch];
+ [batch2 setData:@{@"cc" : @"dd"} forDocument:doc];
+ [batch2 commit];
+
+ // TODO(b/70631617): There's currently a backend bug that prevents us from using a resume token
+ // right away (against hexa at least). So we sleep. :-( :-( Anything over ~10ms seems to be
+ // sufficient.
+ [NSThread sleepForTimeInterval:0.2f];
+
+ FIRDocumentSnapshot *snapshot2 = [self readDocumentForRef:doc];
+ XCTAssertTrue(snapshot2.exists);
+ XCTAssertEqualObjects(snapshot2.data, @{@"cc" : @"dd"});
+}
+
- (void)testSetDocuments {
FIRDocumentReference *doc = [self documentRef];
XCTestExpectation *batchExpectation = [self expectationWithDescription:@"batch written"];
@@ -56,12 +79,7 @@
XCTestExpectation *batchExpectation = [self expectationWithDescription:@"batch written"];
FIRWriteBatch *batch = [doc.firestore batch];
[batch setData:@{ @"a" : @"b", @"nested" : @{@"a" : @"b"} } forDocument:doc];
- [batch setData:@{
- @"c" : @"d",
- @"nested" : @{@"c" : @"d"}
- }
- forDocument:doc
- options:[FIRSetOptions merge]];
+ [batch setData:@{ @"c" : @"d", @"nested" : @{@"c" : @"d"} } forDocument:doc merge:YES];
[batch commitWithCompletion:^(NSError *error) {
XCTAssertNil(error);
[batchExpectation fulfill];
@@ -131,7 +149,7 @@
FSTEventAccumulator *accumulator = [FSTEventAccumulator accumulatorForTest:self];
[collection addSnapshotListenerWithOptions:[[FIRQueryListenOptions options]
includeQueryMetadataChanges:YES]
- listener:accumulator.handler];
+ listener:accumulator.valueEventHandler];
FIRQuerySnapshot *initialSnap = [accumulator awaitEventWithName:@"initial event"];
XCTAssertEqual(initialSnap.count, 0);
@@ -161,7 +179,7 @@
FSTEventAccumulator *accumulator = [FSTEventAccumulator accumulatorForTest:self];
[collection addSnapshotListenerWithOptions:[[FIRQueryListenOptions options]
includeQueryMetadataChanges:YES]
- listener:accumulator.handler];
+ listener:accumulator.valueEventHandler];
FIRQuerySnapshot *initialSnap = [accumulator awaitEventWithName:@"initial event"];
XCTAssertEqual(initialSnap.count, 0);
@@ -195,7 +213,7 @@
FSTEventAccumulator *accumulator = [FSTEventAccumulator accumulatorForTest:self];
[collection addSnapshotListenerWithOptions:[[FIRQueryListenOptions options]
includeQueryMetadataChanges:YES]
- listener:accumulator.handler];
+ listener:accumulator.valueEventHandler];
FIRQuerySnapshot *initialSnap = [accumulator awaitEventWithName:@"initial event"];
XCTAssertEqual(initialSnap.count, 0);
@@ -225,9 +243,7 @@
- (void)testCanWriteTheSameDocumentMultipleTimes {
FIRDocumentReference *doc = [self documentRef];
FSTEventAccumulator *accumulator = [FSTEventAccumulator accumulatorForTest:self];
- [doc
- addSnapshotListenerWithOptions:[[FIRDocumentListenOptions options] includeMetadataChanges:YES]
- listener:accumulator.handler];
+ [doc addSnapshotListenerWithIncludeMetadataChanges:YES listener:accumulator.valueEventHandler];
FIRDocumentSnapshot *initialSnap = [accumulator awaitEventWithName:@"initial event"];
XCTAssertFalse(initialSnap.exists);
diff --git a/Firestore/Example/Tests/Integration/FSTDatastoreTests.m b/Firestore/Example/Tests/Integration/FSTDatastoreTests.mm
index 047f059..ad911ce 100644
--- a/Firestore/Example/Tests/Integration/FSTDatastoreTests.m
+++ b/Firestore/Example/Tests/Integration/FSTDatastoreTests.mm
@@ -14,27 +14,23 @@
* limitations under the License.
*/
-@import FirebaseFirestore;
+#import <FirebaseFirestore/FirebaseFirestore.h>
+#import <FirebaseFirestore/FIRTimestamp.h>
#import <GRPCClient/GRPCCall+ChannelCredentials.h>
#import <GRPCClient/GRPCCall+Tests.h>
#import <XCTest/XCTest.h>
#import "Firestore/Source/API/FIRDocumentReference+Internal.h"
#import "Firestore/Source/API/FSTUserDataConverter.h"
-#import "Firestore/Source/Auth/FSTEmptyCredentialsProvider.h"
-#import "Firestore/Source/Core/FSTDatabaseInfo.h"
#import "Firestore/Source/Core/FSTFirestoreClient.h"
#import "Firestore/Source/Core/FSTQuery.h"
#import "Firestore/Source/Core/FSTSnapshotVersion.h"
-#import "Firestore/Source/Core/FSTTimestamp.h"
#import "Firestore/Source/Local/FSTQueryData.h"
-#import "Firestore/Source/Model/FSTDatabaseID.h"
#import "Firestore/Source/Model/FSTDocumentKey.h"
#import "Firestore/Source/Model/FSTFieldValue.h"
#import "Firestore/Source/Model/FSTMutation.h"
#import "Firestore/Source/Model/FSTMutationBatch.h"
-#import "Firestore/Source/Model/FSTPath.h"
#import "Firestore/Source/Remote/FSTDatastore.h"
#import "Firestore/Source/Remote/FSTRemoteEvent.h"
#import "Firestore/Source/Remote/FSTRemoteStore.h"
@@ -43,6 +39,19 @@
#import "Firestore/Example/Tests/Util/FSTIntegrationTestCase.h"
+#include "Firestore/core/src/firebase/firestore/auth/empty_credentials_provider.h"
+#include "Firestore/core/src/firebase/firestore/core/database_info.h"
+#include "Firestore/core/src/firebase/firestore/model/database_id.h"
+#include "Firestore/core/src/firebase/firestore/model/precondition.h"
+#include "Firestore/core/src/firebase/firestore/util/string_apple.h"
+
+namespace util = firebase::firestore::util;
+using firebase::firestore::auth::EmptyCredentialsProvider;
+using firebase::firestore::core::DatabaseInfo;
+using firebase::firestore::model::DatabaseId;
+using firebase::firestore::model::Precondition;
+using firebase::firestore::model::TargetId;
+
NS_ASSUME_NONNULL_BEGIN
@interface FSTRemoteStore (Tests)
@@ -120,7 +129,7 @@ NS_ASSUME_NONNULL_BEGIN
[expectation fulfill];
}
-- (void)rejectListenWithTargetID:(FSTBoxedTargetID *)targetID error:(NSError *)error {
+- (void)rejectListenWithTargetID:(const TargetId)targetID error:(NSError *)error {
FSTFail(@"Not implemented");
}
@@ -135,8 +144,9 @@ NS_ASSUME_NONNULL_BEGIN
@implementation FSTDatastoreTests {
FSTDispatchQueue *_testWorkerQueue;
FSTLocalStore *_localStore;
- id<FSTCredentialsProvider> _credentials;
+ EmptyCredentialsProvider _credentials;
+ DatabaseInfo _databaseInfo;
FSTDatastore *_datastore;
FSTRemoteStore *_remoteStore;
}
@@ -154,25 +164,22 @@ NS_ASSUME_NONNULL_BEGIN
[GRPCCall useInsecureConnectionsForHost:settings.host];
}
- FSTDatabaseID *databaseID =
- [FSTDatabaseID databaseIDWithProject:projectID database:kDefaultDatabaseID];
+ DatabaseId database_id(util::MakeStringView(projectID), DatabaseId::kDefault);
- FSTDatabaseInfo *databaseInfo = [FSTDatabaseInfo databaseInfoWithDatabaseID:databaseID
- persistenceKey:@"test-key"
- host:settings.host
- sslEnabled:settings.sslEnabled];
+ _databaseInfo = DatabaseInfo(database_id, "test-key", util::MakeStringView(settings.host),
+ settings.sslEnabled);
_testWorkerQueue = [FSTDispatchQueue
queueWith:dispatch_queue_create("com.google.firestore.FSTDatastoreTestsWorkerQueue",
DISPATCH_QUEUE_SERIAL)];
- _credentials = [[FSTEmptyCredentialsProvider alloc] init];
-
- _datastore = [FSTDatastore datastoreWithDatabase:databaseInfo
+ _datastore = [FSTDatastore datastoreWithDatabase:&_databaseInfo
workerDispatchQueue:_testWorkerQueue
- credentials:_credentials];
+ credentials:&_credentials];
- _remoteStore = [FSTRemoteStore remoteStoreWithLocalStore:_localStore datastore:_datastore];
+ _remoteStore = [[FSTRemoteStore alloc] initWithLocalStore:_localStore
+ datastore:_datastore
+ workerDispatchQueue:_testWorkerQueue];
[_testWorkerQueue dispatchAsync:^() {
[_remoteStore start];
@@ -210,7 +217,7 @@ NS_ASSUME_NONNULL_BEGIN
FSTSetMutation *mutation = [self setMutation];
FSTMutationBatch *batch = [[FSTMutationBatch alloc] initWithBatchID:23
- localWriteTime:[FSTTimestamp timestamp]
+ localWriteTime:[FIRTimestamp timestamp]
mutations:@[ mutation ]];
[_testWorkerQueue dispatchAsync:^{
[_remoteStore commitBatch:batch];
@@ -233,7 +240,7 @@ NS_ASSUME_NONNULL_BEGIN
initWithKey:[FSTDocumentKey keyWithPathString:@"rooms/eros"]
value:[[FSTObjectValue alloc]
initWithDictionary:@{@"name" : [FSTStringValue stringValue:@"Eros"]}]
- precondition:[FSTPrecondition none]];
+ precondition:Precondition::None()];
}
@end
diff --git a/Firestore/Example/Tests/Integration/FSTSmokeTests.m b/Firestore/Example/Tests/Integration/FSTSmokeTests.mm
index 847474a..cb726b8 100644
--- a/Firestore/Example/Tests/Integration/FSTSmokeTests.m
+++ b/Firestore/Example/Tests/Integration/FSTSmokeTests.mm
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-@import FirebaseFirestore;
+#import <FirebaseFirestore/FirebaseFirestore.h>
#import <XCTest/XCTest.h>
@@ -48,7 +48,7 @@
[self writeDocumentRef:writerRef data:data];
id<FIRListenerRegistration> listenerRegistration =
- [readerRef addSnapshotListener:self.eventAccumulator.handler];
+ [readerRef addSnapshotListener:self.eventAccumulator.valueEventHandler];
FIRDocumentSnapshot *doc = [self.eventAccumulator awaitEventWithName:@"snapshot"];
XCTAssertEqual([doc class], [FIRDocumentSnapshot class]);
@@ -62,7 +62,7 @@
[self readerAndWriterOnDocumentRef:^(NSString *path, FIRDocumentReference *readerRef,
FIRDocumentReference *writerRef) {
id<FIRListenerRegistration> listenerRegistration =
- [readerRef addSnapshotListener:self.eventAccumulator.handler];
+ [readerRef addSnapshotListener:self.eventAccumulator.valueEventHandler];
FIRDocumentSnapshot *doc1 = [self.eventAccumulator awaitEventWithName:@"null snapshot"];
XCTAssertFalse(doc1.exists);
@@ -82,7 +82,7 @@
- (void)testWillFireValueEventsForEmptyCollections {
FIRCollectionReference *collection = [self.db collectionWithPath:@"empty-collection"];
id<FIRListenerRegistration> listenerRegistration =
- [collection addSnapshotListener:self.eventAccumulator.handler];
+ [collection addSnapshotListener:self.eventAccumulator.valueEventHandler];
FIRQuerySnapshot *snap = [self.eventAccumulator awaitEventWithName:@"empty query snapshot"];
XCTAssertEqual([snap class], [FIRQuerySnapshot class]);
diff --git a/Firestore/Example/Tests/Integration/FSTStreamTests.m b/Firestore/Example/Tests/Integration/FSTStreamTests.mm
index bbdf372..7e37913 100644
--- a/Firestore/Example/Tests/Integration/FSTStreamTests.m
+++ b/Firestore/Example/Tests/Integration/FSTStreamTests.mm
@@ -16,21 +16,29 @@
#import <XCTest/XCTest.h>
+#import <GRPCClient/GRPCCall.h>
+
#import <FirebaseFirestore/FIRFirestoreSettings.h>
#import "Firestore/Example/Tests/Util/FSTHelpers.h"
#import "Firestore/Example/Tests/Util/FSTIntegrationTestCase.h"
-#import "Firestore/Example/Tests/Util/FSTTestDispatchQueue.h"
-#import "Firestore/Source/Auth/FSTEmptyCredentialsProvider.h"
-#import "Firestore/Source/Core/FSTDatabaseInfo.h"
-#import "Firestore/Source/Model/FSTDatabaseID.h"
#import "Firestore/Source/Remote/FSTDatastore.h"
#import "Firestore/Source/Remote/FSTStream.h"
#import "Firestore/Source/Util/FSTAssert.h"
+#include "Firestore/core/src/firebase/firestore/auth/empty_credentials_provider.h"
+#include "Firestore/core/src/firebase/firestore/core/database_info.h"
+#include "Firestore/core/src/firebase/firestore/model/database_id.h"
+#include "Firestore/core/src/firebase/firestore/util/string_apple.h"
+
+namespace util = firebase::firestore::util;
+using firebase::firestore::auth::EmptyCredentialsProvider;
+using firebase::firestore::core::DatabaseInfo;
+using firebase::firestore::model::DatabaseId;
+
/** Exposes otherwise private methods for testing. */
@interface FSTStream (Testing)
-- (void)writesFinishedWithError:(NSError *_Nullable)error;
+@property(nonatomic, strong, readwrite) id<GRXWriteable> callbackFilter;
@end
/**
@@ -127,9 +135,9 @@
@implementation FSTStreamTests {
dispatch_queue_t _testQueue;
- FSTTestDispatchQueue *_workerDispatchQueue;
- FSTDatabaseInfo *_databaseInfo;
- FSTEmptyCredentialsProvider *_credentials;
+ FSTDispatchQueue *_workerDispatchQueue;
+ DatabaseInfo _databaseInfo;
+ EmptyCredentialsProvider _credentials;
FSTStreamStatusDelegate *_delegate;
/** Single mutation to send to the write stream. */
@@ -140,18 +148,14 @@
[super setUp];
FIRFirestoreSettings *settings = [FSTIntegrationTestCase settings];
- FSTDatabaseID *databaseID =
- [FSTDatabaseID databaseIDWithProject:[FSTIntegrationTestCase projectID]
- database:kDefaultDatabaseID];
+ DatabaseId database_id(util::MakeStringView([FSTIntegrationTestCase projectID]),
+ DatabaseId::kDefault);
_testQueue = dispatch_queue_create("FSTStreamTestWorkerQueue", DISPATCH_QUEUE_SERIAL);
- _workerDispatchQueue = [[FSTTestDispatchQueue alloc] initWithQueue:_testQueue];
+ _workerDispatchQueue = [[FSTDispatchQueue alloc] initWithQueue:_testQueue];
- _databaseInfo = [FSTDatabaseInfo databaseInfoWithDatabaseID:databaseID
- persistenceKey:@"test-key"
- host:settings.host
- sslEnabled:settings.sslEnabled];
- _credentials = [[FSTEmptyCredentialsProvider alloc] init];
+ _databaseInfo = DatabaseInfo(database_id, "test-key", util::MakeStringView(settings.host),
+ settings.sslEnabled);
_delegate = [[FSTStreamStatusDelegate alloc] initWithTestCase:self queue:_workerDispatchQueue];
@@ -159,16 +163,16 @@
}
- (FSTWriteStream *)setUpWriteStream {
- FSTDatastore *datastore = [[FSTDatastore alloc] initWithDatabaseInfo:_databaseInfo
+ FSTDatastore *datastore = [[FSTDatastore alloc] initWithDatabaseInfo:&_databaseInfo
workerDispatchQueue:_workerDispatchQueue
- credentials:_credentials];
+ credentials:&_credentials];
return [datastore createWriteStream];
}
- (FSTWatchStream *)setUpWatchStream {
- FSTDatastore *datastore = [[FSTDatastore alloc] initWithDatabaseInfo:_databaseInfo
+ FSTDatastore *datastore = [[FSTDatastore alloc] initWithDatabaseInfo:&_databaseInfo
workerDispatchQueue:_workerDispatchQueue
- credentials:_credentials];
+ credentials:&_credentials];
return [datastore createWatchStream];
}
@@ -200,7 +204,9 @@
}];
// Simulate a final callback from GRPC
- [watchStream writesFinishedWithError:nil];
+ [_workerDispatchQueue dispatchAsync:^{
+ [watchStream.callbackFilter writesFinishedWithError:nil];
+ }];
[self verifyDelegateObservedStates:@[ @"watchStreamDidOpen" ]];
}
@@ -222,7 +228,9 @@
}];
// Simulate a final callback from GRPC
- [writeStream writesFinishedWithError:nil];
+ [_workerDispatchQueue dispatchAsync:^{
+ [writeStream.callbackFilter writesFinishedWithError:nil];
+ }];
[self verifyDelegateObservedStates:@[ @"writeStreamDidOpen" ]];
}
@@ -235,9 +243,9 @@
}];
// Writing before the handshake should throw
- dispatch_sync(_testQueue, ^{
+ [_workerDispatchQueue dispatchSync:^{
XCTAssertThrows([writeStream writeMutations:_mutations]);
- });
+ }];
[_delegate awaitNotificationFromBlock:^{
[writeStream writeHandshake];
@@ -269,13 +277,17 @@
[writeStream writeHandshake];
}];
- [_delegate awaitNotificationFromBlock:^{
+ [_workerDispatchQueue dispatchAsync:^{
[writeStream markIdle];
+ XCTAssertTrue(
+ [_workerDispatchQueue containsDelayedCallbackWithTimerID:FSTTimerIDWriteStreamIdle]);
}];
- dispatch_sync(_testQueue, ^{
+ [_workerDispatchQueue runDelayedCallbacksUntil:FSTTimerIDWriteStreamIdle];
+
+ [_workerDispatchQueue dispatchSync:^{
XCTAssertFalse([writeStream isOpen]);
- });
+ }];
[self verifyDelegateObservedStates:@[
@"writeStreamDidOpen", @"writeStreamDidCompleteHandshake", @"writeStreamWasInterrupted"
@@ -296,12 +308,16 @@
// Mark the stream idle, but immediately cancel the idle timer by issuing another write.
[_delegate awaitNotificationFromBlock:^{
[writeStream markIdle];
+ XCTAssertTrue(
+ [_workerDispatchQueue containsDelayedCallbackWithTimerID:FSTTimerIDWriteStreamIdle]);
[writeStream writeMutations:_mutations];
+ XCTAssertFalse(
+ [_workerDispatchQueue containsDelayedCallbackWithTimerID:FSTTimerIDWriteStreamIdle]);
}];
- dispatch_sync(_testQueue, ^{
+ [_workerDispatchQueue dispatchSync:^{
XCTAssertTrue([writeStream isOpen]);
- });
+ }];
[self verifyDelegateObservedStates:@[
@"writeStreamDidOpen", @"writeStreamDidCompleteHandshake",
diff --git a/Firestore/Example/Tests/Integration/FSTTransactionTests.m b/Firestore/Example/Tests/Integration/FSTTransactionTests.mm
index 2e828c9..999987b 100644
--- a/Firestore/Example/Tests/Integration/FSTTransactionTests.m
+++ b/Firestore/Example/Tests/Integration/FSTTransactionTests.mm
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-@import FirebaseFirestore;
+#import <FirebaseFirestore/FirebaseFirestore.h>
#import <XCTest/XCTest.h>
#include <libkern/OSAtomic.h>
@@ -203,12 +203,7 @@
XCTestExpectation *expectation = [self expectationWithDescription:@"transaction"];
[firestore runTransactionWithBlock:^id _Nullable(FIRTransaction *transaction, NSError **error) {
[transaction setData:@{ @"a" : @"b", @"nested" : @{@"a" : @"b"} } forDocument:doc];
- [transaction setData:@{
- @"c" : @"d",
- @"nested" : @{@"c" : @"d"}
- }
- forDocument:doc
- options:[FIRSetOptions merge]];
+ [transaction setData:@{ @"c" : @"d", @"nested" : @{@"c" : @"d"} } forDocument:doc merge:YES];
return @YES;
}
completion:^(id _Nullable result, NSError *_Nullable error) {
@@ -274,7 +269,6 @@
double newCount = ((NSNumber *)snapshot[@"count"]).doubleValue + 1.0;
[transaction setData:@{ @"count" : @(newCount) } forDocument:doc];
return @YES;
-
}
completion:^(id _Nullable result, NSError *_Nullable error) {
[expectation fulfill];
@@ -316,7 +310,6 @@
double newCount = ((NSNumber *)snapshot[@"count"]).doubleValue + 1.0;
[transaction updateData:@{ @"count" : @(newCount) } forDocument:doc];
return @YES;
-
}
completion:^(id _Nullable result, NSError *_Nullable error) {
[expectation fulfill];
diff --git a/Firestore/Example/Tests/Local/FSTEagerGarbageCollectorTests.m b/Firestore/Example/Tests/Local/FSTEagerGarbageCollectorTests.mm
index 1dd6d62..615b3f6 100644
--- a/Firestore/Example/Tests/Local/FSTEagerGarbageCollectorTests.m
+++ b/Firestore/Example/Tests/Local/FSTEagerGarbageCollectorTests.mm
@@ -18,10 +18,16 @@
#import <XCTest/XCTest.h>
+#include <set>
+
#import "Firestore/Source/Local/FSTReferenceSet.h"
#import "Firestore/Source/Model/FSTDocumentKey.h"
-#import "Firestore/Example/Tests/Util/FSTHelpers.h"
+#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+#include "Firestore/core/test/firebase/firestore/testutil/testutil.h"
+
+namespace testutil = firebase::firestore::testutil;
+using firebase::firestore::model::DocumentKey;
NS_ASSUME_NONNULL_BEGIN
@@ -35,13 +41,13 @@ NS_ASSUME_NONNULL_BEGIN
FSTReferenceSet *referenceSet = [[FSTReferenceSet alloc] init];
[gc addGarbageSource:referenceSet];
- FSTDocumentKey *key = [FSTDocumentKey keyWithPathString:@"foo/bar"];
+ DocumentKey key = testutil::Key("foo/bar");
[referenceSet addReferenceToKey:key forID:1];
- FSTAssertEqualSets([gc collectGarbage], @[]);
+ XCTAssertEqual([gc collectGarbage], std::set<DocumentKey>({}));
XCTAssertFalse([referenceSet isEmpty]);
[referenceSet removeReferenceToKey:key forID:1];
- FSTAssertEqualSets([gc collectGarbage], @[ key ]);
+ XCTAssertEqual([gc collectGarbage], std::set<DocumentKey>({key}));
XCTAssertTrue([referenceSet isEmpty]);
}
@@ -50,20 +56,20 @@ NS_ASSUME_NONNULL_BEGIN
FSTReferenceSet *referenceSet = [[FSTReferenceSet alloc] init];
[gc addGarbageSource:referenceSet];
- FSTDocumentKey *key1 = [FSTDocumentKey keyWithPathString:@"foo/bar"];
- FSTDocumentKey *key2 = [FSTDocumentKey keyWithPathString:@"foo/baz"];
- FSTDocumentKey *key3 = [FSTDocumentKey keyWithPathString:@"foo/blah"];
+ DocumentKey key1 = testutil::Key("foo/bar");
+ DocumentKey key2 = testutil::Key("foo/baz");
+ DocumentKey key3 = testutil::Key("foo/blah");
[referenceSet addReferenceToKey:key1 forID:1];
[referenceSet addReferenceToKey:key2 forID:1];
[referenceSet addReferenceToKey:key3 forID:2];
XCTAssertFalse([referenceSet isEmpty]);
[referenceSet removeReferencesForID:1];
- FSTAssertEqualSets([gc collectGarbage], (@[ key1, key2 ]));
+ XCTAssertEqual([gc collectGarbage], std::set<DocumentKey>({key1, key2}));
XCTAssertFalse([referenceSet isEmpty]);
[referenceSet removeReferencesForID:2];
- FSTAssertEqualSets([gc collectGarbage], @[ key3 ]);
+ XCTAssertEqual([gc collectGarbage], std::set<DocumentKey>({key3}));
XCTAssertTrue([referenceSet isEmpty]);
}
@@ -77,12 +83,12 @@ NS_ASSUME_NONNULL_BEGIN
[gc addGarbageSource:localViews];
[gc addGarbageSource:mutations];
- FSTDocumentKey *key1 = [FSTDocumentKey keyWithPathString:@"foo/bar"];
+ DocumentKey key1 = testutil::Key("foo/bar");
[remoteTargets addReferenceToKey:key1 forID:1];
[localViews addReferenceToKey:key1 forID:1];
[mutations addReferenceToKey:key1 forID:10];
- FSTDocumentKey *key2 = [FSTDocumentKey keyWithPathString:@"foo/baz"];
+ DocumentKey key2 = testutil::Key("foo/baz");
[mutations addReferenceToKey:key2 forID:10];
XCTAssertFalse([remoteTargets isEmpty]);
@@ -90,16 +96,16 @@ NS_ASSUME_NONNULL_BEGIN
XCTAssertFalse([mutations isEmpty]);
[localViews removeReferencesForID:1];
- FSTAssertEqualSets([gc collectGarbage], @[]);
+ XCTAssertEqual([gc collectGarbage], std::set<DocumentKey>({}));
[remoteTargets removeReferencesForID:1];
- FSTAssertEqualSets([gc collectGarbage], @[]);
+ XCTAssertEqual([gc collectGarbage], std::set<DocumentKey>({}));
[mutations removeReferenceToKey:key1 forID:10];
- FSTAssertEqualSets([gc collectGarbage], @[ key1 ]);
+ XCTAssertEqual([gc collectGarbage], std::set<DocumentKey>({key1}));
[mutations removeReferenceToKey:key2 forID:10];
- FSTAssertEqualSets([gc collectGarbage], @[ key2 ]);
+ XCTAssertEqual([gc collectGarbage], std::set<DocumentKey>({key2}));
XCTAssertTrue([remoteTargets isEmpty]);
XCTAssertTrue([localViews isEmpty]);
diff --git a/Firestore/Example/Tests/Local/FSTLevelDBKeyTests.mm b/Firestore/Example/Tests/Local/FSTLevelDBKeyTests.mm
index 998d23d..c4b7e29 100644
--- a/Firestore/Example/Tests/Local/FSTLevelDBKeyTests.mm
+++ b/Firestore/Example/Tests/Local/FSTLevelDBKeyTests.mm
@@ -18,10 +18,16 @@
#import <XCTest/XCTest.h>
-#import "Firestore/Source/Model/FSTPath.h"
+#include <string>
#import "Firestore/Example/Tests/Util/FSTHelpers.h"
+#include "Firestore/core/src/firebase/firestore/util/string_apple.h"
+#include "Firestore/core/test/firebase/firestore/testutil/testutil.h"
+
+namespace util = firebase::firestore::util;
+namespace testutil = firebase::firestore::testutil;
+
NS_ASSUME_NONNULL_BEGIN
@interface FSTLevelDBKeyTests : XCTestCase
@@ -37,7 +43,8 @@ static std::string RemoteDocKey(NSString *pathString) {
}
static std::string RemoteDocKeyPrefix(NSString *pathString) {
- return [FSTLevelDBRemoteDocumentKey keyPrefixWithResourcePath:FSTTestPath(pathString)];
+ return [FSTLevelDBRemoteDocumentKey
+ keyPrefixWithResourcePath:testutil::Resource(util::MakeStringView(pathString))];
}
static std::string DocMutationKey(NSString *userID, NSString *key, FSTBatchID batchID) {
@@ -199,7 +206,7 @@ static std::string DocTargetKey(NSString *key, FSTTargetID targetID) {
@"[document_mutation: userID=user1 incomplete key]");
auto key = [FSTLevelDBDocumentMutationKey keyPrefixWithUserID:@"user1"
- resourcePath:FSTTestPath(@"foo/bar")];
+ resourcePath:testutil::Resource("foo/bar")];
FSTAssertExpectedKeyDescription(key,
@"[document_mutation: userID=user1 key=foo/bar incomplete key]");
diff --git a/Firestore/Example/Tests/Local/FSTLevelDBLocalStoreTests.m b/Firestore/Example/Tests/Local/FSTLevelDBLocalStoreTests.mm
index f71f5c9..29caa84 100644
--- a/Firestore/Example/Tests/Local/FSTLevelDBLocalStoreTests.m
+++ b/Firestore/Example/Tests/Local/FSTLevelDBLocalStoreTests.mm
@@ -14,14 +14,10 @@
* limitations under the License.
*/
-#import "Firestore/Source/Local/FSTLocalStore.h"
-
-#import <XCTest/XCTest.h>
+#import "Firestore/Example/Tests/Local/FSTLocalStoreTests.h"
-#import "Firestore/Source/Auth/FSTUser.h"
#import "Firestore/Source/Local/FSTLevelDB.h"
-#import "Firestore/Example/Tests/Local/FSTLocalStoreTests.h"
#import "Firestore/Example/Tests/Local/FSTPersistenceTestHelpers.h"
NS_ASSUME_NONNULL_BEGIN
diff --git a/Firestore/Example/Tests/Local/FSTLevelDBMigrationsTests.mm b/Firestore/Example/Tests/Local/FSTLevelDBMigrationsTests.mm
new file mode 100644
index 0000000..3da8083
--- /dev/null
+++ b/Firestore/Example/Tests/Local/FSTLevelDBMigrationsTests.mm
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2018 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 <XCTest/XCTest.h>
+
+#include <memory>
+#include <string>
+
+#import "Firestore/Protos/objc/firestore/local/Target.pbobjc.h"
+#import "Firestore/Source/Local/FSTLevelDB.h"
+#import "Firestore/Source/Local/FSTLevelDBKey.h"
+#import "Firestore/Source/Local/FSTLevelDBMigrations.h"
+#import "Firestore/Source/Local/FSTLevelDBQueryCache.h"
+
+#include "Firestore/core/src/firebase/firestore/util/ordered_code.h"
+#include "leveldb/db.h"
+
+#import "Firestore/Example/Tests/Local/FSTPersistenceTestHelpers.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+using firebase::firestore::local::LevelDbTransaction;
+using firebase::firestore::util::OrderedCode;
+using leveldb::DB;
+using leveldb::Options;
+using leveldb::Status;
+
+@interface FSTLevelDBMigrationsTests : XCTestCase
+@end
+
+@implementation FSTLevelDBMigrationsTests {
+ std::shared_ptr<DB> _db;
+}
+
+- (void)setUp {
+ Options options;
+ options.error_if_exists = true;
+ options.create_if_missing = true;
+
+ NSString *dir = [FSTPersistenceTestHelpers levelDBDir];
+ DB *db;
+ Status status = DB::Open(options, [dir UTF8String], &db);
+ XCTAssert(status.ok(), @"Failed to create db: %s", status.ToString().c_str());
+ _db.reset(db);
+}
+
+- (void)tearDown {
+ _db.reset();
+}
+
+- (void)testAddsTargetGlobal {
+ FSTPBTargetGlobal *metadata = [FSTLevelDBQueryCache readTargetMetadataFromDB:_db];
+ XCTAssertNil(metadata, @"Not expecting metadata yet, we should have an empty db");
+ LevelDbTransaction transaction(_db.get(), "testAddsTargetGlobal");
+ [FSTLevelDBMigrations runMigrationsWithTransaction:&transaction];
+ transaction.Commit();
+ metadata = [FSTLevelDBQueryCache readTargetMetadataFromDB:_db];
+ XCTAssertNotNil(metadata, @"Migrations should have added the metadata");
+}
+
+- (void)testSetsVersionNumber {
+ LevelDbTransaction transaction(_db.get(), "testSetsVersionNumber");
+ FSTLevelDBSchemaVersion initial =
+ [FSTLevelDBMigrations schemaVersionWithTransaction:&transaction];
+ XCTAssertEqual(0, initial, "No version should be equivalent to 0");
+
+ // Pick an arbitrary high migration number and migrate to it.
+ [FSTLevelDBMigrations runMigrationsWithTransaction:&transaction];
+ FSTLevelDBSchemaVersion actual = [FSTLevelDBMigrations schemaVersionWithTransaction:&transaction];
+ XCTAssertGreaterThan(actual, 0, @"Expected to migrate to a schema version > 0");
+}
+
+- (void)testCountsQueries {
+ NSUInteger expected = 50;
+ {
+ // Setup some targets to be counted in the migration.
+ LevelDbTransaction transaction(_db.get(), "testCountsQueries setup");
+ for (int i = 0; i < expected; i++) {
+ std::string key = [FSTLevelDBTargetKey keyWithTargetID:i];
+ transaction.Put(key, "dummy");
+ }
+ // Add a dummy entry after the targets to make sure the iteration is correctly bounded.
+ // Use a table that would sort logically right after that table 'target'.
+ std::string dummyKey;
+ // Magic number that indicates a table name follows. Needed to mimic the prefix to the target
+ // table.
+ OrderedCode::WriteSignedNumIncreasing(&dummyKey, 5);
+ OrderedCode::WriteString(&dummyKey, "targetA");
+ transaction.Put(dummyKey, "dummy");
+ transaction.Commit();
+ }
+
+ {
+ LevelDbTransaction transaction(_db.get(), "testCountsQueries");
+ [FSTLevelDBMigrations runMigrationsWithTransaction:&transaction];
+ transaction.Commit();
+ FSTPBTargetGlobal *metadata = [FSTLevelDBQueryCache readTargetMetadataFromDB:_db];
+ XCTAssertEqual(expected, metadata.targetCount, @"Failed to count all of the targets we added");
+ }
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firestore/Example/Tests/Local/FSTLevelDBMutationQueueTests.mm b/Firestore/Example/Tests/Local/FSTLevelDBMutationQueueTests.mm
index 6c26fd9..60d9800 100644
--- a/Firestore/Example/Tests/Local/FSTLevelDBMutationQueueTests.mm
+++ b/Firestore/Example/Tests/Local/FSTLevelDBMutationQueueTests.mm
@@ -17,18 +17,20 @@
#import "Firestore/Source/Local/FSTLevelDBMutationQueue.h"
#import <XCTest/XCTest.h>
-#include <leveldb/db.h>
-#include "Firestore/Port/ordered_code.h"
+#include <string>
+
#import "Firestore/Protos/objc/firestore/local/Mutation.pbobjc.h"
-#import "Firestore/Source/Auth/FSTUser.h"
#import "Firestore/Source/Local/FSTLevelDB.h"
#import "Firestore/Source/Local/FSTLevelDBKey.h"
-#import "Firestore/Source/Local/FSTWriteGroup.h"
#import "Firestore/Example/Tests/Local/FSTMutationQueueTests.h"
#import "Firestore/Example/Tests/Local/FSTPersistenceTestHelpers.h"
+#include "Firestore/core/src/firebase/firestore/auth/user.h"
+#include "Firestore/core/src/firebase/firestore/util/ordered_code.h"
+#include "leveldb/db.h"
+
NS_ASSUME_NONNULL_BEGIN
using leveldb::DB;
@@ -36,7 +38,8 @@ using leveldb::Slice;
using leveldb::Status;
using leveldb::WriteOptions;
using Firestore::StringView;
-using Firestore::OrderedCode;
+using firebase::firestore::auth::User;
+using firebase::firestore::util::OrderedCode;
// A dummy mutation value, useful for testing code that's known to examine only mutation keys.
static const char *kDummy = "1";
@@ -68,12 +71,10 @@ std::string MutationLikeKey(StringView table, StringView userID, FSTBatchID batc
- (void)setUp {
[super setUp];
_db = [FSTPersistenceTestHelpers levelDBPersistence];
- self.mutationQueue = [_db mutationQueueForUser:[[FSTUser alloc] initWithUID:@"user"]];
+ self.mutationQueue = [_db mutationQueueForUser:User("user")];
self.persistence = _db;
- FSTWriteGroup *group = [self.persistence startGroupWithAction:@"Start MutationQueue"];
- [self.mutationQueue startWithGroup:group];
- [self.persistence commitGroup:group];
+ self.persistence.run("Setup", [&]() { [self.mutationQueue start]; });
}
- (void)testLoadNextBatchID_zeroWhenTotallyEmpty {
diff --git a/Firestore/Example/Tests/Local/FSTLevelDBQueryCacheTests.m b/Firestore/Example/Tests/Local/FSTLevelDBQueryCacheTests.mm
index 929ab9e..6e64998 100644
--- a/Firestore/Example/Tests/Local/FSTLevelDBQueryCacheTests.m
+++ b/Firestore/Example/Tests/Local/FSTLevelDBQueryCacheTests.mm
@@ -42,7 +42,6 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)tearDown {
- [self.queryCache shutdown];
self.persistence = nil;
self.queryCache = nil;
diff --git a/Firestore/Example/Tests/Local/FSTLevelDBRemoteDocumentCacheTests.mm b/Firestore/Example/Tests/Local/FSTLevelDBRemoteDocumentCacheTests.mm
index 1f84aa6..7baf1ef 100644
--- a/Firestore/Example/Tests/Local/FSTLevelDBRemoteDocumentCacheTests.mm
+++ b/Firestore/Example/Tests/Local/FSTLevelDBRemoteDocumentCacheTests.mm
@@ -14,20 +14,20 @@
* limitations under the License.
*/
-#import "Firestore/Example/Tests/Local/FSTRemoteDocumentCacheTests.h"
-
-#include <leveldb/db.h>
+#include <string>
-#include "Firestore/Port/ordered_code.h"
+#import "Firestore/Example/Tests/Local/FSTPersistenceTestHelpers.h"
+#import "Firestore/Example/Tests/Local/FSTRemoteDocumentCacheTests.h"
#import "Firestore/Source/Local/FSTLevelDB.h"
#import "Firestore/Source/Local/FSTLevelDBKey.h"
-#import "Firestore/Example/Tests/Local/FSTPersistenceTestHelpers.h"
+#include "Firestore/core/src/firebase/firestore/util/ordered_code.h"
+#include "leveldb/db.h"
NS_ASSUME_NONNULL_BEGIN
using leveldb::WriteOptions;
-using Firestore::OrderedCode;
+using firebase::firestore::util::OrderedCode;
// A dummy document value, useful for testing code that's known to examine only document keys.
static const char *kDummy = "1";
@@ -57,7 +57,6 @@ static const char *kDummy = "1";
}
- (void)tearDown {
- [self.remoteDocumentCache shutdown];
self.remoteDocumentCache = nil;
self.persistence = nil;
_db = nil;
diff --git a/Firestore/Example/Tests/Local/FSTLevelDBTransactionTests.mm b/Firestore/Example/Tests/Local/FSTLevelDBTransactionTests.mm
new file mode 100644
index 0000000..29f5d6c
--- /dev/null
+++ b/Firestore/Example/Tests/Local/FSTLevelDBTransactionTests.mm
@@ -0,0 +1,309 @@
+/*
+ * Copyright 2018 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 <XCTest/XCTest.h>
+
+#include <memory>
+#include <string>
+
+// This is out of order to satisfy the linter, which doesn't realize this is
+// the header corresponding to this test.
+// TODO(wilhuff): move this to the top once the test filename matches
+#include "Firestore/core/src/firebase/firestore/local/leveldb_transaction.h"
+
+#import "Firestore/Example/Tests/Local/FSTPersistenceTestHelpers.h"
+#import "Firestore/Protos/objc/firestore/local/Mutation.pbobjc.h"
+#import "Firestore/Protos/objc/firestore/local/Target.pbobjc.h"
+
+#include "Firestore/core/src/firebase/firestore/local/leveldb_key.h"
+#include "absl/strings/string_view.h"
+#include "leveldb/db.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+using leveldb::DB;
+using leveldb::Options;
+using leveldb::ReadOptions;
+using leveldb::WriteOptions;
+using leveldb::Status;
+using firebase::firestore::local::LevelDbMutationKey;
+using firebase::firestore::local::LevelDbTransaction;
+
+@interface FSTLevelDBTransactionTests : XCTestCase
+@end
+
+@implementation FSTLevelDBTransactionTests {
+ std::shared_ptr<DB> _db;
+}
+
+- (void)setUp {
+ Options options;
+ options.error_if_exists = true;
+ options.create_if_missing = true;
+
+ NSString *dir = [FSTPersistenceTestHelpers levelDBDir];
+ DB *db;
+ Status status = DB::Open(options, [dir UTF8String], &db);
+ XCTAssert(status.ok(), @"Failed to create db: %s", status.ToString().c_str());
+ _db.reset(db);
+}
+
+- (void)tearDown {
+ _db.reset();
+}
+
+- (void)testCreateTransaction {
+ LevelDbTransaction transaction(_db.get(), "testCreateTransaction");
+ std::string key = "key1";
+
+ transaction.Put(key, "value");
+ auto iter = transaction.NewIterator();
+ iter->Seek(key);
+ XCTAssertEqual(key, iter->key());
+ iter->Next();
+ XCTAssertFalse(iter->Valid());
+}
+
+- (void)testCanReadCommittedAndMutations {
+ const std::string committed_key1 = "c_key1";
+ const std::string committed_value1 = "c_value1";
+ const WriteOptions &writeOptions = LevelDbTransaction::DefaultWriteOptions();
+ // add two things committed, mutate one, add another mutation
+ // verify you can get the original committed, the mutation, and the addition
+ Status status = _db->Put(writeOptions, committed_key1, committed_value1);
+ XCTAssertTrue(status.ok());
+
+ const std::string committed_key2 = "c_key2";
+ const std::string committed_value2 = "c_value2";
+ status = _db->Put(writeOptions, committed_key2, committed_value2);
+ XCTAssertTrue(status.ok());
+
+ LevelDbTransaction transaction(_db.get(), "testCanReadCommittedAndMutations");
+ const std::string mutation_key1 = "m_key1";
+ const std::string mutation_value1 = "m_value1";
+ transaction.Put(mutation_key1, mutation_value1);
+
+ const std::string mutation_key2 = committed_key2;
+ const std::string mutation_value2 = "m_value2";
+ transaction.Put(mutation_key2, mutation_value2);
+
+ std::string value;
+ status = transaction.Get(committed_key1, &value);
+ XCTAssertTrue(status.ok());
+ XCTAssertEqual(value, committed_value1);
+
+ status = transaction.Get(mutation_key1, &value);
+ XCTAssertTrue(status.ok());
+ XCTAssertEqual(value, mutation_value1);
+
+ status = transaction.Get(committed_key2, &value);
+ XCTAssertTrue(status.ok());
+ XCTAssertEqual(value, mutation_value2);
+}
+
+- (void)testDeleteCommitted {
+ // add something committed, delete it, verify you can't read it
+ for (int i = 0; i < 3; ++i) {
+ Status status = _db->Put(LevelDbTransaction::DefaultWriteOptions(), "key_" + std::to_string(i),
+ "value_" + std::to_string(i));
+ XCTAssertTrue(status.ok());
+ }
+ LevelDbTransaction transaction(_db.get(), "testDeleteCommitted");
+ transaction.Put("key_1", "new_value");
+ std::string value;
+ Status status = transaction.Get("key_1", &value);
+ XCTAssertTrue(status.ok());
+ XCTAssertEqual(value, "new_value");
+
+ transaction.Delete("key_1");
+ status = transaction.Get("key_1", &value);
+ XCTAssertTrue(status.IsNotFound());
+
+ LevelDbTransaction::Iterator iter(&transaction);
+ iter.Seek("");
+ XCTAssertEqual(iter.key(), "key_0");
+ iter.Next();
+ XCTAssertEqual(iter.key(), "key_2");
+ iter.Next();
+ XCTAssertFalse(iter.Valid());
+}
+
+- (void)testMutateDeleted {
+ // delete something, then mutate it, then read it.
+ // Also include an actual deletion
+ for (int i = 0; i < 4; ++i) {
+ Status status = _db->Put(LevelDbTransaction::DefaultWriteOptions(), "key_" + std::to_string(i),
+ "value_" + std::to_string(i));
+ XCTAssertTrue(status.ok());
+ }
+ std::string value;
+ LevelDbTransaction transaction(_db.get(), "testMutateDeleted");
+ transaction.Delete("key_1");
+ Status status = transaction.Get("key_1", &value);
+ XCTAssertTrue(status.IsNotFound());
+
+ transaction.Put("key_1", "new_value");
+ status = transaction.Get("key_1", &value);
+ XCTAssertTrue(status.ok());
+ XCTAssertEqual(value, "new_value");
+
+ transaction.Delete("key_3");
+
+ LevelDbTransaction::Iterator iter(&transaction);
+ iter.Seek("");
+ XCTAssertEqual(iter.key(), "key_0");
+ iter.Next();
+ XCTAssertEqual(iter.key(), "key_1");
+ XCTAssertEqual(iter.value(), "new_value");
+ iter.Next();
+ XCTAssertEqual(iter.key(), "key_2");
+ iter.Next();
+ XCTAssertFalse(iter.Valid());
+
+ // Commit, then check underlying db.
+ transaction.Commit();
+
+ const ReadOptions &readOptions = LevelDbTransaction::DefaultReadOptions();
+ status = _db->Get(readOptions, "key_0", &value);
+ XCTAssertTrue(status.ok());
+ XCTAssertEqual("value_0", value);
+
+ status = _db->Get(readOptions, "key_1", &value);
+ XCTAssertTrue(status.ok());
+ XCTAssertEqual("new_value", value);
+
+ status = _db->Get(readOptions, "key_2", &value);
+ XCTAssertTrue(status.ok());
+ XCTAssertEqual("value_2", value);
+
+ status = _db->Get(readOptions, "key_3", &value);
+ XCTAssertTrue(status.IsNotFound());
+}
+
+- (void)testProtobufSupport {
+ LevelDbTransaction transaction(_db.get(), "testProtobufSupport");
+
+ FSTPBTarget *target = [FSTPBTarget message];
+ target.targetId = 1;
+ target.lastListenSequenceNumber = 2;
+
+ std::string key("theKey");
+ transaction.Put(key, target);
+
+ std::string value;
+ Status status = transaction.Get("theKey", &value);
+ NSData *result =
+ [[NSData alloc] initWithBytesNoCopy:(void *)value.data() length:value.size() freeWhenDone:NO];
+ NSError *error;
+ FSTPBTarget *parsed = [FSTPBTarget parseFromData:result error:&error];
+ XCTAssertNil(error);
+ XCTAssertTrue([target isEqual:parsed]);
+}
+
+- (void)testCanIterateAndDelete {
+ LevelDbTransaction transaction(_db.get(), "testCanIterateAndDelete");
+
+ for (int i = 0; i < 4; ++i) {
+ transaction.Put("key_" + std::to_string(i), "value_" + std::to_string(i));
+ }
+
+ auto it = transaction.NewIterator();
+ it->Seek("key_0");
+ for (int i = 0; i < 4; ++i) {
+ XCTAssertTrue(it->Valid());
+ const absl::string_view &key = it->key();
+ std::string expected = "key_" + std::to_string(i);
+ XCTAssertEqual(expected, key);
+ transaction.Delete(key);
+ it->Next();
+ }
+}
+
+- (void)testCanIterateFromDeletionToCommitted {
+ // Write keys key_0 and key_1
+ for (int i = 0; i < 2; ++i) {
+ Status status = _db->Put(LevelDbTransaction::DefaultWriteOptions(), "key_" + std::to_string(i),
+ "value_" + std::to_string(i));
+ XCTAssertTrue(status.ok());
+ }
+
+ // Create a transaction, iterate, deleting key_0. Verify we still iterate key_1.
+ LevelDbTransaction transaction(_db.get(), "testCanIterateFromDeletionToCommitted");
+ auto it = transaction.NewIterator();
+ it->Seek("key_0");
+ XCTAssertTrue(it->Valid());
+ XCTAssertEqual("key_0", it->key());
+ transaction.Delete("key_0");
+ it->Next();
+ XCTAssertTrue(it->Valid());
+ XCTAssertEqual("key_1", it->key());
+ it->Next();
+ XCTAssertFalse(it->Valid());
+}
+
+- (void)testDeletingAheadOfAnIterator {
+ // Write keys
+ for (int i = 0; i < 4; ++i) {
+ Status status = _db->Put(LevelDbTransaction::DefaultWriteOptions(), "key_" + std::to_string(i),
+ "value_" + std::to_string(i));
+ XCTAssertTrue(status.ok());
+ }
+
+ // Create a transaction, iterate to key_1, delete key_2. Verify we still iterate key_3.
+ LevelDbTransaction transaction(_db.get(), "testDeletingAheadOfAnIterator");
+ auto it = transaction.NewIterator();
+ it->Seek("key_0");
+ XCTAssertTrue(it->Valid());
+ XCTAssertEqual("key_0", it->key());
+ it->Next();
+ XCTAssertTrue(it->Valid());
+ XCTAssertEqual("key_1", it->key());
+ transaction.Delete("key_2");
+ it->Next();
+ XCTAssertTrue(it->Valid());
+ XCTAssertEqual("key_3", it->key());
+ XCTAssertTrue(it->Valid());
+ it->Next();
+ XCTAssertFalse(it->Valid());
+}
+
+- (void)testToString {
+ std::string key = LevelDbMutationKey::Key("user1", 42);
+ FSTPBWriteBatch *message = [FSTPBWriteBatch message];
+ message.batchId = 42;
+
+ LevelDbTransaction transaction(_db.get(), "testToString");
+ std::string description = transaction.ToString();
+ XCTAssertEqual(description, "<LevelDbTransaction testToString: 0 changes (0 bytes):>");
+
+ transaction.Put(key, message);
+ description = transaction.ToString();
+ XCTAssertEqual(description,
+ "<LevelDbTransaction testToString: 1 changes (2 bytes):\n"
+ " - Put [mutation: user_id=user1 batch_id=42] (2 bytes)>");
+
+ std::string key2 = LevelDbMutationKey::Key("user1", 43);
+ transaction.Delete(key2);
+ description = transaction.ToString();
+ XCTAssertEqual(description,
+ "<LevelDbTransaction testToString: 2 changes (2 bytes):\n"
+ " - Delete [mutation: user_id=user1 batch_id=43]\n"
+ " - Put [mutation: user_id=user1 batch_id=42] (2 bytes)>");
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firestore/Example/Tests/Local/FSTLocalSerializerTests.m b/Firestore/Example/Tests/Local/FSTLocalSerializerTests.mm
index 90f9ca3..362f46f 100644
--- a/Firestore/Example/Tests/Local/FSTLocalSerializerTests.m
+++ b/Firestore/Example/Tests/Local/FSTLocalSerializerTests.mm
@@ -16,6 +16,7 @@
#import "Firestore/Source/Local/FSTLocalSerializer.h"
+#import <FirebaseFirestore/FIRTimestamp.h>
#import <XCTest/XCTest.h>
#import "Firestore/Protos/objc/firestore/local/MaybeDocument.pbobjc.h"
@@ -29,19 +30,27 @@
#import "Firestore/Protos/objc/google/type/Latlng.pbobjc.h"
#import "Firestore/Source/Core/FSTQuery.h"
#import "Firestore/Source/Core/FSTSnapshotVersion.h"
-#import "Firestore/Source/Core/FSTTimestamp.h"
#import "Firestore/Source/Local/FSTQueryData.h"
-#import "Firestore/Source/Model/FSTDatabaseID.h"
#import "Firestore/Source/Model/FSTDocument.h"
#import "Firestore/Source/Model/FSTDocumentKey.h"
#import "Firestore/Source/Model/FSTFieldValue.h"
#import "Firestore/Source/Model/FSTMutation.h"
#import "Firestore/Source/Model/FSTMutationBatch.h"
-#import "Firestore/Source/Model/FSTPath.h"
#import "Firestore/Source/Remote/FSTSerializerBeta.h"
#import "Firestore/Example/Tests/Util/FSTHelpers.h"
+#include "Firestore/core/src/firebase/firestore/model/database_id.h"
+#include "Firestore/core/src/firebase/firestore/model/field_mask.h"
+#include "Firestore/core/src/firebase/firestore/model/precondition.h"
+#include "Firestore/core/src/firebase/firestore/util/string_apple.h"
+#include "Firestore/core/test/firebase/firestore/testutil/testutil.h"
+
+namespace testutil = firebase::firestore::testutil;
+using firebase::firestore::model::DatabaseId;
+using firebase::firestore::model::FieldMask;
+using firebase::firestore::model::Precondition;
+
NS_ASSUME_NONNULL_BEGIN
@interface FSTSerializerBeta (Test)
@@ -52,7 +61,9 @@ NS_ASSUME_NONNULL_BEGIN
- (GCFSValue *)encodedString:(NSString *)value;
@end
-@interface FSTLocalSerializerTests : XCTestCase
+@interface FSTLocalSerializerTests : XCTestCase {
+ DatabaseId _databaseId;
+}
@property(nonatomic, strong) FSTLocalSerializer *serializer;
@property(nonatomic, strong) FSTSerializerBeta *remoteSerializer;
@@ -62,22 +73,21 @@ NS_ASSUME_NONNULL_BEGIN
@implementation FSTLocalSerializerTests
- (void)setUp {
- FSTDatabaseID *databaseID = [FSTDatabaseID databaseIDWithProject:@"p" database:@"d"];
- self.remoteSerializer = [[FSTSerializerBeta alloc] initWithDatabaseID:databaseID];
+ _databaseId = DatabaseId("p", "d");
+ self.remoteSerializer = [[FSTSerializerBeta alloc] initWithDatabaseID:&_databaseId];
self.serializer = [[FSTLocalSerializer alloc] initWithRemoteSerializer:self.remoteSerializer];
}
- (void)testEncodesMutationBatch {
FSTMutation *set = FSTTestSetMutation(@"foo/bar", @{ @"a" : @"b", @"num" : @1 });
- FSTMutation *patch = [[FSTPatchMutation alloc]
- initWithKey:[FSTDocumentKey keyWithPathString:@"bar/baz"]
- fieldMask:[[FSTFieldMask alloc] initWithFields:@[ FSTTestFieldPath(@"a") ]]
- value:FSTTestObjectValue(
- @{ @"a" : @"b",
- @"num" : @1 })
- precondition:[FSTPrecondition preconditionWithExists:YES]];
+ FSTMutation *patch = [[FSTPatchMutation alloc] initWithKey:FSTTestDocKey(@"bar/baz")
+ fieldMask:FieldMask{testutil::Field("a")}
+ value:FSTTestObjectValue(
+ @{ @"a" : @"b",
+ @"num" : @1 })
+ precondition:Precondition::Exists(true)];
FSTMutation *del = FSTTestDeleteMutation(@"baz/quux");
- FSTTimestamp *writeTime = [FSTTimestamp timestamp];
+ FIRTimestamp *writeTime = [FIRTimestamp timestamp];
FSTMutationBatch *model = [[FSTMutationBatch alloc] initWithBatchID:42
localWriteTime:writeTime
mutations:@[ set, patch, del ]];
@@ -103,7 +113,7 @@ NS_ASSUME_NONNULL_BEGIN
GPBTimestamp *writeTimeProto = [GPBTimestamp message];
writeTimeProto.seconds = writeTime.seconds;
- writeTimeProto.nanos = writeTime.nanos;
+ writeTimeProto.nanos = writeTime.nanoseconds;
FSTPBWriteBatch *batchProto = [FSTPBWriteBatch message];
batchProto.batchId = 42;
@@ -119,7 +129,7 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)testEncodesDocumentAsMaybeDocument {
- FSTDocument *doc = FSTTestDoc(@"some/path", 42, @{@"foo" : @"bar"}, NO);
+ FSTDocument *doc = FSTTestDoc("some/path", 42, @{@"foo" : @"bar"}, NO);
FSTPBMaybeDocument *maybeDocProto = [FSTPBMaybeDocument message];
maybeDocProto.document = [GCFSDocument message];
@@ -136,7 +146,7 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)testEncodesDeletedDocumentAsMaybeDocument {
- FSTDeletedDocument *deletedDoc = FSTTestDeletedDoc(@"some/path", 42);
+ FSTDeletedDocument *deletedDoc = FSTTestDeletedDoc("some/path", 42);
FSTPBMaybeDocument *maybeDocProto = [FSTPBMaybeDocument message];
maybeDocProto.noDocument = [FSTPBNoDocument message];
@@ -150,13 +160,14 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)testEncodesQueryData {
- FSTQuery *query = FSTTestQuery(@"room");
+ FSTQuery *query = FSTTestQuery("room");
FSTTargetID targetID = 42;
FSTSnapshotVersion *version = FSTTestVersion(1039);
NSData *resumeToken = FSTTestResumeTokenFromSnapshotVersion(1039);
FSTQueryData *queryData = [[FSTQueryData alloc] initWithQuery:query
targetID:targetID
+ listenSequenceNumber:10
purpose:FSTQueryPurposeListen
snapshotVersion:version
resumeToken:resumeToken];
@@ -166,6 +177,7 @@ NS_ASSUME_NONNULL_BEGIN
FSTPBTarget *expected = [FSTPBTarget message];
expected.targetId = targetID;
+ expected.lastListenSequenceNumber = 10;
expected.snapshotVersion.nanos = 1039000;
expected.resumeToken = [resumeToken copy];
expected.query.parent = queryTarget.parent;
diff --git a/Firestore/Example/Tests/Local/FSTLocalStoreTests.h b/Firestore/Example/Tests/Local/FSTLocalStoreTests.h
index 8e06d82..e514954 100644
--- a/Firestore/Example/Tests/Local/FSTLocalStoreTests.h
+++ b/Firestore/Example/Tests/Local/FSTLocalStoreTests.h
@@ -16,7 +16,9 @@
#import <XCTest/XCTest.h>
-@class FSTLocalStore;
+#import "Firestore/Source/Local/FSTLocalStore.h"
+
+@protocol FSTPersistence;
NS_ASSUME_NONNULL_BEGIN
diff --git a/Firestore/Example/Tests/Local/FSTLocalStoreTests.m b/Firestore/Example/Tests/Local/FSTLocalStoreTests.mm
index 245e1c4..2ef3acf 100644
--- a/Firestore/Example/Tests/Local/FSTLocalStoreTests.m
+++ b/Firestore/Example/Tests/Local/FSTLocalStoreTests.mm
@@ -16,11 +16,10 @@
#import "Firestore/Source/Local/FSTLocalStore.h"
+#import <FirebaseFirestore/FIRTimestamp.h>
#import <XCTest/XCTest.h>
-#import "Firestore/Source/Auth/FSTUser.h"
#import "Firestore/Source/Core/FSTQuery.h"
-#import "Firestore/Source/Core/FSTTimestamp.h"
#import "Firestore/Source/Local/FSTEagerGarbageCollector.h"
#import "Firestore/Source/Local/FSTLocalWriteResult.h"
#import "Firestore/Source/Local/FSTNoOpGarbageCollector.h"
@@ -31,7 +30,6 @@
#import "Firestore/Source/Model/FSTDocumentSet.h"
#import "Firestore/Source/Model/FSTMutation.h"
#import "Firestore/Source/Model/FSTMutationBatch.h"
-#import "Firestore/Source/Model/FSTPath.h"
#import "Firestore/Source/Remote/FSTRemoteEvent.h"
#import "Firestore/Source/Remote/FSTWatchChange.h"
#import "Firestore/Source/Util/FSTClasses.h"
@@ -42,6 +40,10 @@
#import "Firestore/third_party/Immutable/Tests/FSTImmutableSortedDictionary+Testing.h"
#import "Firestore/third_party/Immutable/Tests/FSTImmutableSortedSet+Testing.h"
+#include "Firestore/core/src/firebase/firestore/auth/user.h"
+
+using firebase::firestore::auth::User;
+
NS_ASSUME_NONNULL_BEGIN
/** Creates a document version dictionary mapping the document in @a mutation to @a version. */
@@ -77,7 +79,7 @@ FSTDocumentVersionDictionary *FSTVersionDictionary(FSTMutation *mutation,
id<FSTGarbageCollector> garbageCollector = [[FSTEagerGarbageCollector alloc] init];
self.localStore = [[FSTLocalStore alloc] initWithPersistence:persistence
garbageCollector:garbageCollector
- initialUser:[FSTUser unauthenticatedUser]];
+ initialUser:User::Unauthenticated()];
[self.localStore start];
_batches = [NSMutableArray array];
@@ -86,7 +88,6 @@ FSTDocumentVersionDictionary *FSTVersionDictionary(FSTMutation *mutation,
}
- (void)tearDown {
- [self.localStore shutdown];
[self.localStorePersistence shutdown];
[super tearDown];
@@ -107,12 +108,10 @@ FSTDocumentVersionDictionary *FSTVersionDictionary(FSTMutation *mutation,
/** Restarts the local store using the FSTNoOpGarbageCollector instead of the default. */
- (void)restartWithNoopGarbageCollector {
- [self.localStore shutdown];
-
id<FSTGarbageCollector> garbageCollector = [[FSTNoOpGarbageCollector alloc] init];
self.localStore = [[FSTLocalStore alloc] initWithPersistence:self.localStorePersistence
garbageCollector:garbageCollector
- initialUser:[FSTUser unauthenticatedUser]];
+ initialUser:User::Unauthenticated()];
[self.localStore start];
}
@@ -124,7 +123,7 @@ FSTDocumentVersionDictionary *FSTVersionDictionary(FSTMutation *mutation,
FSTLocalWriteResult *result = [self.localStore locallyWriteMutations:mutations];
XCTAssertNotNil(result);
[self.batches addObject:[[FSTMutationBatch alloc] initWithBatchID:result.batchID
- localWriteTime:[FSTTimestamp timestamp]
+ localWriteTime:[FIRTimestamp timestamp]
mutations:mutations]];
self.lastChanges = result.changes;
}
@@ -196,8 +195,7 @@ FSTDocumentVersionDictionary *FSTVersionDictionary(FSTMutation *mutation,
NSEnumerator<NSString *> *keyPathEnumerator = keyPaths.objectEnumerator; \
[actual enumerateKeysAndObjectsUsingBlock:^(FSTDocumentKey * actualKey, \
FSTMaybeDocument * value, BOOL * stop) { \
- FSTDocumentKey *expectedKey = \
- [FSTDocumentKey keyWithPathString:[keyPathEnumerator nextObject]]; \
+ FSTDocumentKey *expectedKey = FSTTestDocKey([keyPathEnumerator nextObject]); \
XCTAssertEqualObjects(actualKey, expectedKey); \
XCTAssertTrue([value isKindOfClass:[FSTDeletedDocument class]]); \
}]; \
@@ -213,11 +211,11 @@ FSTDocumentVersionDictionary *FSTVersionDictionary(FSTMutation *mutation,
} while (0)
/** Asserts that the given local store does not contain the given document. */
-#define FSTAssertNotContains(keyPathString) \
- do { \
- FSTDocumentKey *key = [FSTDocumentKey keyWithPathString:keyPathString]; \
- FSTMaybeDocument *actual = [self.localStore readDocument:key]; \
- XCTAssertNil(actual); \
+#define FSTAssertNotContains(keyPathString) \
+ do { \
+ FSTDocumentKey *key = FSTTestDocKey(keyPathString); \
+ FSTMaybeDocument *actual = [self.localStore readDocument:key]; \
+ XCTAssertNil(actual); \
} while (0)
- (void)testMutationBatchKeys {
@@ -226,7 +224,7 @@ FSTDocumentVersionDictionary *FSTVersionDictionary(FSTMutation *mutation,
FSTMutation *set1 = FSTTestSetMutation(@"foo/bar", @{@"foo" : @"bar"});
FSTMutation *set2 = FSTTestSetMutation(@"bar/baz", @{@"bar" : @"baz"});
FSTMutationBatch *batch = [[FSTMutationBatch alloc] initWithBatchID:1
- localWriteTime:[FSTTimestamp timestamp]
+ localWriteTime:[FIRTimestamp timestamp]
mutations:@[ set1, set2 ]];
FSTDocumentKeySet *keys = [batch keys];
XCTAssertEqual(keys.count, 2);
@@ -236,111 +234,111 @@ FSTDocumentVersionDictionary *FSTVersionDictionary(FSTMutation *mutation,
if ([self isTestBaseClass]) return;
[self writeMutation:FSTTestSetMutation(@"foo/bar", @{@"foo" : @"bar"})];
- FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, YES) ]);
- FSTAssertContains(FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, YES));
+ FSTAssertChanged(@[ FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, YES) ]);
+ FSTAssertContains(FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, YES));
[self acknowledgeMutationWithVersion:0];
- FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, NO) ]);
- FSTAssertContains(FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, NO));
+ FSTAssertChanged(@[ FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, NO) ]);
+ FSTAssertContains(FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, NO));
}
- (void)testHandlesSetMutationThenDocument {
if ([self isTestBaseClass]) return;
[self writeMutation:FSTTestSetMutation(@"foo/bar", @{@"foo" : @"bar"})];
- FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, YES) ]);
- FSTAssertContains(FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, YES));
+ FSTAssertChanged(@[ FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, YES) ]);
+ FSTAssertContains(FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, YES));
[self applyRemoteEvent:FSTTestUpdateRemoteEvent(
- FSTTestDoc(@"foo/bar", 2, @{@"it" : @"changed"}, NO), @[ @1 ], @[])];
- FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 2, @{@"foo" : @"bar"}, YES) ]);
- FSTAssertContains(FSTTestDoc(@"foo/bar", 2, @{@"foo" : @"bar"}, YES));
+ FSTTestDoc("foo/bar", 2, @{@"it" : @"changed"}, NO), @[ @1 ], @[])];
+ FSTAssertChanged(@[ FSTTestDoc("foo/bar", 2, @{@"foo" : @"bar"}, YES) ]);
+ FSTAssertContains(FSTTestDoc("foo/bar", 2, @{@"foo" : @"bar"}, YES));
}
- (void)testHandlesAckThenRejectThenRemoteEvent {
if ([self isTestBaseClass]) return;
// Start a query that requires acks to be held.
- FSTQuery *query = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo" ]]];
+ FSTQuery *query = FSTTestQuery("foo");
[self allocateQuery:query];
[self writeMutation:FSTTestSetMutation(@"foo/bar", @{@"foo" : @"bar"})];
- FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, YES) ]);
- FSTAssertContains(FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, YES));
+ FSTAssertChanged(@[ FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, YES) ]);
+ FSTAssertContains(FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, YES));
// The last seen version is zero, so this ack must be held.
[self acknowledgeMutationWithVersion:1];
FSTAssertChanged(@[]);
- FSTAssertContains(FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, YES));
+ FSTAssertContains(FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, YES));
[self writeMutation:FSTTestSetMutation(@"bar/baz", @{@"bar" : @"baz"})];
- FSTAssertChanged(@[ FSTTestDoc(@"bar/baz", 0, @{@"bar" : @"baz"}, YES) ]);
- FSTAssertContains(FSTTestDoc(@"bar/baz", 0, @{@"bar" : @"baz"}, YES));
+ FSTAssertChanged(@[ FSTTestDoc("bar/baz", 0, @{@"bar" : @"baz"}, YES) ]);
+ FSTAssertContains(FSTTestDoc("bar/baz", 0, @{@"bar" : @"baz"}, YES));
[self rejectMutation];
FSTAssertRemoved(@[ @"bar/baz" ]);
FSTAssertNotContains(@"bar/baz");
[self applyRemoteEvent:FSTTestUpdateRemoteEvent(
- FSTTestDoc(@"foo/bar", 2, @{@"it" : @"changed"}, NO), @[ @1 ], @[])];
- FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 2, @{@"it" : @"changed"}, NO) ]);
- FSTAssertContains(FSTTestDoc(@"foo/bar", 2, @{@"it" : @"changed"}, NO));
+ FSTTestDoc("foo/bar", 2, @{@"it" : @"changed"}, NO), @[ @1 ], @[])];
+ FSTAssertChanged(@[ FSTTestDoc("foo/bar", 2, @{@"it" : @"changed"}, NO) ]);
+ FSTAssertContains(FSTTestDoc("foo/bar", 2, @{@"it" : @"changed"}, NO));
FSTAssertNotContains(@"bar/baz");
}
- (void)testHandlesDeletedDocumentThenSetMutationThenAck {
if ([self isTestBaseClass]) return;
- [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDeletedDoc(@"foo/bar", 2), @[ @1 ], @[])];
+ [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDeletedDoc("foo/bar", 2), @[ @1 ], @[])];
FSTAssertRemoved(@[ @"foo/bar" ]);
- FSTAssertContains(FSTTestDeletedDoc(@"foo/bar", 2));
+ FSTAssertContains(FSTTestDeletedDoc("foo/bar", 2));
[self writeMutation:FSTTestSetMutation(@"foo/bar", @{@"foo" : @"bar"})];
- FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, YES) ]);
- FSTAssertContains(FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, YES));
+ FSTAssertChanged(@[ FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, YES) ]);
+ FSTAssertContains(FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, YES));
[self acknowledgeMutationWithVersion:3];
- FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, NO) ]);
- FSTAssertContains(FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, NO));
+ FSTAssertChanged(@[ FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, NO) ]);
+ FSTAssertContains(FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, NO));
}
- (void)testHandlesSetMutationThenDeletedDocument {
if ([self isTestBaseClass]) return;
[self writeMutation:FSTTestSetMutation(@"foo/bar", @{@"foo" : @"bar"})];
- FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, YES) ]);
+ FSTAssertChanged(@[ FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, YES) ]);
- [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDeletedDoc(@"foo/bar", 2), @[ @1 ], @[])];
- FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, YES) ]);
- FSTAssertContains(FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, YES));
+ [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDeletedDoc("foo/bar", 2), @[ @1 ], @[])];
+ FSTAssertChanged(@[ FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, YES) ]);
+ FSTAssertContains(FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, YES));
}
- (void)testHandlesDocumentThenSetMutationThenAckThenDocument {
if ([self isTestBaseClass]) return;
- [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc(@"foo/bar", 2, @{@"it" : @"base"}, NO),
+ [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc("foo/bar", 2, @{@"it" : @"base"}, NO),
@[ @1 ], @[])];
- FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 2, @{@"it" : @"base"}, NO) ]);
- FSTAssertContains(FSTTestDoc(@"foo/bar", 2, @{@"it" : @"base"}, NO));
+ FSTAssertChanged(@[ FSTTestDoc("foo/bar", 2, @{@"it" : @"base"}, NO) ]);
+ FSTAssertContains(FSTTestDoc("foo/bar", 2, @{@"it" : @"base"}, NO));
[self writeMutation:FSTTestSetMutation(@"foo/bar", @{@"foo" : @"bar"})];
- FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 2, @{@"foo" : @"bar"}, YES) ]);
- FSTAssertContains(FSTTestDoc(@"foo/bar", 2, @{@"foo" : @"bar"}, YES));
+ FSTAssertChanged(@[ FSTTestDoc("foo/bar", 2, @{@"foo" : @"bar"}, YES) ]);
+ FSTAssertContains(FSTTestDoc("foo/bar", 2, @{@"foo" : @"bar"}, YES));
[self acknowledgeMutationWithVersion:3];
- FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 2, @{@"foo" : @"bar"}, NO) ]);
- FSTAssertContains(FSTTestDoc(@"foo/bar", 2, @{@"foo" : @"bar"}, NO));
+ FSTAssertChanged(@[ FSTTestDoc("foo/bar", 2, @{@"foo" : @"bar"}, NO) ]);
+ FSTAssertContains(FSTTestDoc("foo/bar", 2, @{@"foo" : @"bar"}, NO));
[self applyRemoteEvent:FSTTestUpdateRemoteEvent(
- FSTTestDoc(@"foo/bar", 3, @{@"it" : @"changed"}, NO), @[ @1 ], @[])];
- FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 3, @{@"it" : @"changed"}, NO) ]);
- FSTAssertContains(FSTTestDoc(@"foo/bar", 3, @{@"it" : @"changed"}, NO));
+ FSTTestDoc("foo/bar", 3, @{@"it" : @"changed"}, NO), @[ @1 ], @[])];
+ FSTAssertChanged(@[ FSTTestDoc("foo/bar", 3, @{@"it" : @"changed"}, NO) ]);
+ FSTAssertContains(FSTTestDoc("foo/bar", 3, @{@"it" : @"changed"}, NO));
}
- (void)testHandlesPatchWithoutPriorDocument {
if ([self isTestBaseClass]) return;
- [self writeMutation:FSTTestPatchMutation(@"foo/bar", @{@"foo" : @"bar"}, nil)];
+ [self writeMutation:FSTTestPatchMutation("foo/bar", @{@"foo" : @"bar"}, {})];
FSTAssertRemoved(@[ @"foo/bar" ]);
FSTAssertNotContains(@"foo/bar");
@@ -352,24 +350,24 @@ FSTDocumentVersionDictionary *FSTVersionDictionary(FSTMutation *mutation,
- (void)testHandlesPatchMutationThenDocumentThenAck {
if ([self isTestBaseClass]) return;
- [self writeMutation:FSTTestPatchMutation(@"foo/bar", @{@"foo" : @"bar"}, nil)];
+ [self writeMutation:FSTTestPatchMutation("foo/bar", @{@"foo" : @"bar"}, {})];
FSTAssertRemoved(@[ @"foo/bar" ]);
FSTAssertNotContains(@"foo/bar");
- [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc(@"foo/bar", 1, @{@"it" : @"base"}, NO),
+ [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc("foo/bar", 1, @{@"it" : @"base"}, NO),
@[ @1 ], @[])];
- FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 1, @{@"foo" : @"bar", @"it" : @"base"}, YES) ]);
- FSTAssertContains(FSTTestDoc(@"foo/bar", 1, @{@"foo" : @"bar", @"it" : @"base"}, YES));
+ FSTAssertChanged(@[ FSTTestDoc("foo/bar", 1, @{@"foo" : @"bar", @"it" : @"base"}, YES) ]);
+ FSTAssertContains(FSTTestDoc("foo/bar", 1, @{@"foo" : @"bar", @"it" : @"base"}, YES));
[self acknowledgeMutationWithVersion:2];
- FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 1, @{@"foo" : @"bar", @"it" : @"base"}, NO) ]);
- FSTAssertContains(FSTTestDoc(@"foo/bar", 1, @{@"foo" : @"bar", @"it" : @"base"}, NO));
+ FSTAssertChanged(@[ FSTTestDoc("foo/bar", 1, @{@"foo" : @"bar", @"it" : @"base"}, NO) ]);
+ FSTAssertContains(FSTTestDoc("foo/bar", 1, @{@"foo" : @"bar", @"it" : @"base"}, NO));
}
- (void)testHandlesPatchMutationThenAckThenDocument {
if ([self isTestBaseClass]) return;
- [self writeMutation:FSTTestPatchMutation(@"foo/bar", @{@"foo" : @"bar"}, nil)];
+ [self writeMutation:FSTTestPatchMutation("foo/bar", @{@"foo" : @"bar"}, {})];
FSTAssertRemoved(@[ @"foo/bar" ]);
FSTAssertNotContains(@"foo/bar");
@@ -377,10 +375,10 @@ FSTDocumentVersionDictionary *FSTVersionDictionary(FSTMutation *mutation,
FSTAssertRemoved(@[ @"foo/bar" ]);
FSTAssertNotContains(@"foo/bar");
- [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc(@"foo/bar", 1, @{@"it" : @"base"}, NO),
+ [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc("foo/bar", 1, @{@"it" : @"base"}, NO),
@[ @1 ], @[])];
- FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 1, @{@"it" : @"base"}, NO) ]);
- FSTAssertContains(FSTTestDoc(@"foo/bar", 1, @{@"it" : @"base"}, NO));
+ FSTAssertChanged(@[ FSTTestDoc("foo/bar", 1, @{@"it" : @"base"}, NO) ]);
+ FSTAssertContains(FSTTestDoc("foo/bar", 1, @{@"it" : @"base"}, NO));
}
- (void)testHandlesDeleteMutationThenAck {
@@ -388,28 +386,28 @@ FSTDocumentVersionDictionary *FSTVersionDictionary(FSTMutation *mutation,
[self writeMutation:FSTTestDeleteMutation(@"foo/bar")];
FSTAssertRemoved(@[ @"foo/bar" ]);
- FSTAssertContains(FSTTestDeletedDoc(@"foo/bar", 0));
+ FSTAssertContains(FSTTestDeletedDoc("foo/bar", 0));
[self acknowledgeMutationWithVersion:1];
FSTAssertRemoved(@[ @"foo/bar" ]);
- FSTAssertContains(FSTTestDeletedDoc(@"foo/bar", 0));
+ FSTAssertContains(FSTTestDeletedDoc("foo/bar", 0));
}
- (void)testHandlesDocumentThenDeleteMutationThenAck {
if ([self isTestBaseClass]) return;
- [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc(@"foo/bar", 1, @{@"it" : @"base"}, NO),
+ [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc("foo/bar", 1, @{@"it" : @"base"}, NO),
@[ @1 ], @[])];
- FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 1, @{@"it" : @"base"}, NO) ]);
- FSTAssertContains(FSTTestDoc(@"foo/bar", 1, @{@"it" : @"base"}, NO));
+ FSTAssertChanged(@[ FSTTestDoc("foo/bar", 1, @{@"it" : @"base"}, NO) ]);
+ FSTAssertContains(FSTTestDoc("foo/bar", 1, @{@"it" : @"base"}, NO));
[self writeMutation:FSTTestDeleteMutation(@"foo/bar")];
FSTAssertRemoved(@[ @"foo/bar" ]);
- FSTAssertContains(FSTTestDeletedDoc(@"foo/bar", 0));
+ FSTAssertContains(FSTTestDeletedDoc("foo/bar", 0));
[self acknowledgeMutationWithVersion:2];
FSTAssertRemoved(@[ @"foo/bar" ]);
- FSTAssertContains(FSTTestDeletedDoc(@"foo/bar", 0));
+ FSTAssertContains(FSTTestDeletedDoc("foo/bar", 0));
}
- (void)testHandlesDeleteMutationThenDocumentThenAck {
@@ -417,59 +415,59 @@ FSTDocumentVersionDictionary *FSTVersionDictionary(FSTMutation *mutation,
[self writeMutation:FSTTestDeleteMutation(@"foo/bar")];
FSTAssertRemoved(@[ @"foo/bar" ]);
- FSTAssertContains(FSTTestDeletedDoc(@"foo/bar", 0));
+ FSTAssertContains(FSTTestDeletedDoc("foo/bar", 0));
- [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc(@"foo/bar", 1, @{@"it" : @"base"}, NO),
+ [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc("foo/bar", 1, @{@"it" : @"base"}, NO),
@[ @1 ], @[])];
FSTAssertRemoved(@[ @"foo/bar" ]);
- FSTAssertContains(FSTTestDeletedDoc(@"foo/bar", 0));
+ FSTAssertContains(FSTTestDeletedDoc("foo/bar", 0));
[self acknowledgeMutationWithVersion:2];
FSTAssertRemoved(@[ @"foo/bar" ]);
- FSTAssertContains(FSTTestDeletedDoc(@"foo/bar", 0));
+ FSTAssertContains(FSTTestDeletedDoc("foo/bar", 0));
}
- (void)testHandlesDocumentThenDeletedDocumentThenDocument {
if ([self isTestBaseClass]) return;
- [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc(@"foo/bar", 1, @{@"it" : @"base"}, NO),
+ [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc("foo/bar", 1, @{@"it" : @"base"}, NO),
@[ @1 ], @[])];
- FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 1, @{@"it" : @"base"}, NO) ]);
- FSTAssertContains(FSTTestDoc(@"foo/bar", 1, @{@"it" : @"base"}, NO));
+ FSTAssertChanged(@[ FSTTestDoc("foo/bar", 1, @{@"it" : @"base"}, NO) ]);
+ FSTAssertContains(FSTTestDoc("foo/bar", 1, @{@"it" : @"base"}, NO));
- [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDeletedDoc(@"foo/bar", 2), @[ @1 ], @[])];
+ [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDeletedDoc("foo/bar", 2), @[ @1 ], @[])];
FSTAssertRemoved(@[ @"foo/bar" ]);
- FSTAssertContains(FSTTestDeletedDoc(@"foo/bar", 2));
+ FSTAssertContains(FSTTestDeletedDoc("foo/bar", 2));
[self applyRemoteEvent:FSTTestUpdateRemoteEvent(
- FSTTestDoc(@"foo/bar", 3, @{@"it" : @"changed"}, NO), @[ @1 ], @[])];
- FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 3, @{@"it" : @"changed"}, NO) ]);
- FSTAssertContains(FSTTestDoc(@"foo/bar", 3, @{@"it" : @"changed"}, NO));
+ FSTTestDoc("foo/bar", 3, @{@"it" : @"changed"}, NO), @[ @1 ], @[])];
+ FSTAssertChanged(@[ FSTTestDoc("foo/bar", 3, @{@"it" : @"changed"}, NO) ]);
+ FSTAssertContains(FSTTestDoc("foo/bar", 3, @{@"it" : @"changed"}, NO));
}
- (void)testHandlesSetMutationThenPatchMutationThenDocumentThenAckThenAck {
if ([self isTestBaseClass]) return;
[self writeMutation:FSTTestSetMutation(@"foo/bar", @{@"foo" : @"old"})];
- FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"old"}, YES) ]);
- FSTAssertContains(FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"old"}, YES));
+ FSTAssertChanged(@[ FSTTestDoc("foo/bar", 0, @{@"foo" : @"old"}, YES) ]);
+ FSTAssertContains(FSTTestDoc("foo/bar", 0, @{@"foo" : @"old"}, YES));
- [self writeMutation:FSTTestPatchMutation(@"foo/bar", @{@"foo" : @"bar"}, nil)];
- FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, YES) ]);
- FSTAssertContains(FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, YES));
+ [self writeMutation:FSTTestPatchMutation("foo/bar", @{@"foo" : @"bar"}, {})];
+ FSTAssertChanged(@[ FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, YES) ]);
+ FSTAssertContains(FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, YES));
- [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc(@"foo/bar", 1, @{@"it" : @"base"}, NO),
+ [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc("foo/bar", 1, @{@"it" : @"base"}, NO),
@[ @1 ], @[])];
- FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 1, @{@"foo" : @"bar"}, YES) ]);
- FSTAssertContains(FSTTestDoc(@"foo/bar", 1, @{@"foo" : @"bar"}, YES));
+ FSTAssertChanged(@[ FSTTestDoc("foo/bar", 1, @{@"foo" : @"bar"}, YES) ]);
+ FSTAssertContains(FSTTestDoc("foo/bar", 1, @{@"foo" : @"bar"}, YES));
[self acknowledgeMutationWithVersion:2]; // delete mutation
- FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 1, @{@"foo" : @"bar"}, YES) ]);
- FSTAssertContains(FSTTestDoc(@"foo/bar", 1, @{@"foo" : @"bar"}, YES));
+ FSTAssertChanged(@[ FSTTestDoc("foo/bar", 1, @{@"foo" : @"bar"}, YES) ]);
+ FSTAssertContains(FSTTestDoc("foo/bar", 1, @{@"foo" : @"bar"}, YES));
[self acknowledgeMutationWithVersion:3]; // patch mutation
- FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 1, @{@"foo" : @"bar"}, NO) ]);
- FSTAssertContains(FSTTestDoc(@"foo/bar", 1, @{@"foo" : @"bar"}, NO));
+ FSTAssertChanged(@[ FSTTestDoc("foo/bar", 1, @{@"foo" : @"bar"}, NO) ]);
+ FSTAssertContains(FSTTestDoc("foo/bar", 1, @{@"foo" : @"bar"}, NO));
}
- (void)testHandlesSetMutationAndPatchMutationTogether {
@@ -477,11 +475,11 @@ FSTDocumentVersionDictionary *FSTVersionDictionary(FSTMutation *mutation,
[self writeMutations:@[
FSTTestSetMutation(@"foo/bar", @{@"foo" : @"old"}),
- FSTTestPatchMutation(@"foo/bar", @{@"foo" : @"bar"}, nil)
+ FSTTestPatchMutation("foo/bar", @{@"foo" : @"bar"}, {})
]];
- FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, YES) ]);
- FSTAssertContains(FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, YES));
+ FSTAssertChanged(@[ FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, YES) ]);
+ FSTAssertContains(FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, YES));
}
- (void)testHandlesSetMutationThenPatchMutationThenReject {
@@ -489,14 +487,14 @@ FSTDocumentVersionDictionary *FSTVersionDictionary(FSTMutation *mutation,
[self writeMutation:FSTTestSetMutation(@"foo/bar", @{@"foo" : @"old"})];
[self acknowledgeMutationWithVersion:1];
- FSTAssertContains(FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"old"}, NO));
+ FSTAssertContains(FSTTestDoc("foo/bar", 0, @{@"foo" : @"old"}, NO));
- [self writeMutation:FSTTestPatchMutation(@"foo/bar", @{@"foo" : @"bar"}, nil)];
- FSTAssertContains(FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, YES));
+ [self writeMutation:FSTTestPatchMutation("foo/bar", @{@"foo" : @"bar"}, {})];
+ FSTAssertContains(FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, YES));
[self rejectMutation];
- FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"old"}, NO) ]);
- FSTAssertContains(FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"old"}, NO));
+ FSTAssertChanged(@[ FSTTestDoc("foo/bar", 0, @{@"foo" : @"old"}, NO) ]);
+ FSTAssertContains(FSTTestDoc("foo/bar", 0, @{@"foo" : @"old"}, NO));
}
- (void)testHandlesSetMutationsAndPatchMutationOfJustOneTogether {
@@ -505,15 +503,15 @@ FSTDocumentVersionDictionary *FSTVersionDictionary(FSTMutation *mutation,
[self writeMutations:@[
FSTTestSetMutation(@"foo/bar", @{@"foo" : @"old"}),
FSTTestSetMutation(@"bar/baz", @{@"bar" : @"baz"}),
- FSTTestPatchMutation(@"foo/bar", @{@"foo" : @"bar"}, nil)
+ FSTTestPatchMutation("foo/bar", @{@"foo" : @"bar"}, {})
]];
FSTAssertChanged((@[
- FSTTestDoc(@"bar/baz", 0, @{@"bar" : @"baz"}, YES),
- FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, YES)
+ FSTTestDoc("bar/baz", 0, @{@"bar" : @"baz"}, YES),
+ FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, YES)
]));
- FSTAssertContains(FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, YES));
- FSTAssertContains(FSTTestDoc(@"bar/baz", 0, @{@"bar" : @"baz"}, YES));
+ FSTAssertContains(FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, YES));
+ FSTAssertContains(FSTTestDoc("bar/baz", 0, @{@"bar" : @"baz"}, YES));
}
- (void)testHandlesDeleteMutationThenPatchMutationThenAckThenAck {
@@ -521,31 +519,31 @@ FSTDocumentVersionDictionary *FSTVersionDictionary(FSTMutation *mutation,
[self writeMutation:FSTTestDeleteMutation(@"foo/bar")];
FSTAssertRemoved(@[ @"foo/bar" ]);
- FSTAssertContains(FSTTestDeletedDoc(@"foo/bar", 0));
+ FSTAssertContains(FSTTestDeletedDoc("foo/bar", 0));
- [self writeMutation:FSTTestPatchMutation(@"foo/bar", @{@"foo" : @"bar"}, nil)];
+ [self writeMutation:FSTTestPatchMutation("foo/bar", @{@"foo" : @"bar"}, {})];
FSTAssertRemoved(@[ @"foo/bar" ]);
- FSTAssertContains(FSTTestDeletedDoc(@"foo/bar", 0));
+ FSTAssertContains(FSTTestDeletedDoc("foo/bar", 0));
[self acknowledgeMutationWithVersion:2]; // delete mutation
FSTAssertRemoved(@[ @"foo/bar" ]);
- FSTAssertContains(FSTTestDeletedDoc(@"foo/bar", 0));
+ FSTAssertContains(FSTTestDeletedDoc("foo/bar", 0));
[self acknowledgeMutationWithVersion:3]; // patch mutation
FSTAssertRemoved(@[ @"foo/bar" ]);
- FSTAssertContains(FSTTestDeletedDoc(@"foo/bar", 0));
+ FSTAssertContains(FSTTestDeletedDoc("foo/bar", 0));
}
- (void)testCollectsGarbageAfterChangeBatchWithNoTargetIDs {
if ([self isTestBaseClass]) return;
- [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDeletedDoc(@"foo/bar", 2), @[ @1 ], @[])];
+ [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDeletedDoc("foo/bar", 2), @[ @1 ], @[])];
FSTAssertRemoved(@[ @"foo/bar" ]);
[self collectGarbage];
FSTAssertNotContains(@"foo/bar");
- [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc(@"foo/bar", 2, @{@"foo" : @"bar"}, NO),
+ [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc("foo/bar", 2, @{@"foo" : @"bar"}, NO),
@[ @1 ], @[])];
[self collectGarbage];
FSTAssertNotContains(@"foo/bar");
@@ -554,16 +552,16 @@ FSTDocumentVersionDictionary *FSTVersionDictionary(FSTMutation *mutation,
- (void)testCollectsGarbageAfterChangeBatch {
if ([self isTestBaseClass]) return;
- FSTQuery *query = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo" ]]];
+ FSTQuery *query = FSTTestQuery("foo");
[self allocateQuery:query];
FSTAssertTargetID(2);
- [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc(@"foo/bar", 2, @{@"foo" : @"bar"}, NO),
+ [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc("foo/bar", 2, @{@"foo" : @"bar"}, NO),
@[ @2 ], @[])];
[self collectGarbage];
- FSTAssertContains(FSTTestDoc(@"foo/bar", 2, @{@"foo" : @"bar"}, NO));
+ FSTAssertContains(FSTTestDoc("foo/bar", 2, @{@"foo" : @"bar"}, NO));
- [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc(@"foo/bar", 2, @{@"foo" : @"baz"}, NO),
+ [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc("foo/bar", 2, @{@"foo" : @"baz"}, NO),
@[], @[ @2 ])];
[self collectGarbage];
@@ -573,27 +571,27 @@ FSTDocumentVersionDictionary *FSTVersionDictionary(FSTMutation *mutation,
- (void)testCollectsGarbageAfterAcknowledgedMutation {
if ([self isTestBaseClass]) return;
- [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"old"}, NO),
+ [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc("foo/bar", 0, @{@"foo" : @"old"}, NO),
@[ @1 ], @[])];
- [self writeMutation:FSTTestPatchMutation(@"foo/bar", @{@"foo" : @"bar"}, nil)];
+ [self writeMutation:FSTTestPatchMutation("foo/bar", @{@"foo" : @"bar"}, {})];
[self writeMutation:FSTTestSetMutation(@"foo/bah", @{@"foo" : @"bah"})];
[self writeMutation:FSTTestDeleteMutation(@"foo/baz")];
[self collectGarbage];
- FSTAssertContains(FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, YES));
- FSTAssertContains(FSTTestDoc(@"foo/bah", 0, @{@"foo" : @"bah"}, YES));
- FSTAssertContains(FSTTestDeletedDoc(@"foo/baz", 0));
+ FSTAssertContains(FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, YES));
+ FSTAssertContains(FSTTestDoc("foo/bah", 0, @{@"foo" : @"bah"}, YES));
+ FSTAssertContains(FSTTestDeletedDoc("foo/baz", 0));
[self acknowledgeMutationWithVersion:3];
[self collectGarbage];
FSTAssertNotContains(@"foo/bar");
- FSTAssertContains(FSTTestDoc(@"foo/bah", 0, @{@"foo" : @"bah"}, YES));
- FSTAssertContains(FSTTestDeletedDoc(@"foo/baz", 0));
+ FSTAssertContains(FSTTestDoc("foo/bah", 0, @{@"foo" : @"bah"}, YES));
+ FSTAssertContains(FSTTestDeletedDoc("foo/baz", 0));
[self acknowledgeMutationWithVersion:4];
[self collectGarbage];
FSTAssertNotContains(@"foo/bar");
FSTAssertNotContains(@"foo/bah");
- FSTAssertContains(FSTTestDeletedDoc(@"foo/baz", 0));
+ FSTAssertContains(FSTTestDeletedDoc("foo/baz", 0));
[self acknowledgeMutationWithVersion:5];
[self collectGarbage];
@@ -605,27 +603,27 @@ FSTDocumentVersionDictionary *FSTVersionDictionary(FSTMutation *mutation,
- (void)testCollectsGarbageAfterRejectedMutation {
if ([self isTestBaseClass]) return;
- [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"old"}, NO),
+ [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc("foo/bar", 0, @{@"foo" : @"old"}, NO),
@[ @1 ], @[])];
- [self writeMutation:FSTTestPatchMutation(@"foo/bar", @{@"foo" : @"bar"}, nil)];
+ [self writeMutation:FSTTestPatchMutation("foo/bar", @{@"foo" : @"bar"}, {})];
[self writeMutation:FSTTestSetMutation(@"foo/bah", @{@"foo" : @"bah"})];
[self writeMutation:FSTTestDeleteMutation(@"foo/baz")];
[self collectGarbage];
- FSTAssertContains(FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, YES));
- FSTAssertContains(FSTTestDoc(@"foo/bah", 0, @{@"foo" : @"bah"}, YES));
- FSTAssertContains(FSTTestDeletedDoc(@"foo/baz", 0));
+ FSTAssertContains(FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, YES));
+ FSTAssertContains(FSTTestDoc("foo/bah", 0, @{@"foo" : @"bah"}, YES));
+ FSTAssertContains(FSTTestDeletedDoc("foo/baz", 0));
[self rejectMutation]; // patch mutation
[self collectGarbage];
FSTAssertNotContains(@"foo/bar");
- FSTAssertContains(FSTTestDoc(@"foo/bah", 0, @{@"foo" : @"bah"}, YES));
- FSTAssertContains(FSTTestDeletedDoc(@"foo/baz", 0));
+ FSTAssertContains(FSTTestDoc("foo/bah", 0, @{@"foo" : @"bah"}, YES));
+ FSTAssertContains(FSTTestDeletedDoc("foo/baz", 0));
[self rejectMutation]; // set mutation
[self collectGarbage];
FSTAssertNotContains(@"foo/bar");
FSTAssertNotContains(@"foo/bah");
- FSTAssertContains(FSTTestDeletedDoc(@"foo/baz", 0));
+ FSTAssertContains(FSTTestDeletedDoc("foo/baz", 0));
[self rejectMutation]; // delete mutation
[self collectGarbage];
@@ -637,26 +635,26 @@ FSTDocumentVersionDictionary *FSTVersionDictionary(FSTMutation *mutation,
- (void)testPinsDocumentsInTheLocalView {
if ([self isTestBaseClass]) return;
- FSTQuery *query = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo" ]]];
+ FSTQuery *query = FSTTestQuery("foo");
[self allocateQuery:query];
FSTAssertTargetID(2);
- [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc(@"foo/bar", 1, @{@"foo" : @"bar"}, NO),
+ [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc("foo/bar", 1, @{@"foo" : @"bar"}, NO),
@[ @2 ], @[])];
[self writeMutation:FSTTestSetMutation(@"foo/baz", @{@"foo" : @"baz"})];
[self collectGarbage];
- FSTAssertContains(FSTTestDoc(@"foo/bar", 1, @{@"foo" : @"bar"}, NO));
- FSTAssertContains(FSTTestDoc(@"foo/baz", 0, @{@"foo" : @"baz"}, YES));
+ FSTAssertContains(FSTTestDoc("foo/bar", 1, @{@"foo" : @"bar"}, NO));
+ FSTAssertContains(FSTTestDoc("foo/baz", 0, @{@"foo" : @"baz"}, YES));
[self notifyLocalViewChanges:FSTTestViewChanges(query, @[ @"foo/bar", @"foo/baz" ], @[])];
- [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc(@"foo/bar", 1, @{@"foo" : @"bar"}, NO),
+ [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc("foo/bar", 1, @{@"foo" : @"bar"}, NO),
@[], @[ @2 ])];
- [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc(@"foo/baz", 2, @{@"foo" : @"baz"}, NO),
+ [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc("foo/baz", 2, @{@"foo" : @"baz"}, NO),
@[ @1 ], @[])];
[self acknowledgeMutationWithVersion:2];
[self collectGarbage];
- FSTAssertContains(FSTTestDoc(@"foo/bar", 1, @{@"foo" : @"bar"}, NO));
- FSTAssertContains(FSTTestDoc(@"foo/baz", 2, @{@"foo" : @"baz"}, NO));
+ FSTAssertContains(FSTTestDoc("foo/bar", 1, @{@"foo" : @"bar"}, NO));
+ FSTAssertContains(FSTTestDoc("foo/baz", 2, @{@"foo" : @"baz"}, NO));
[self notifyLocalViewChanges:FSTTestViewChanges(query, @[], @[ @"foo/bar", @"foo/baz" ])];
[self collectGarbage];
@@ -669,9 +667,9 @@ FSTDocumentVersionDictionary *FSTVersionDictionary(FSTMutation *mutation,
if ([self isTestBaseClass]) return;
FSTTargetID targetID = 321;
- [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc(@"foo/bar", 1, @{}, NO),
+ [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc("foo/bar", 1, @{}, NO),
@[ @(targetID) ], @[])];
- FSTAssertContains(FSTTestDoc(@"foo/bar", 1, @{}, NO));
+ FSTAssertContains(FSTTestDoc("foo/bar", 1, @{}, NO));
[self collectGarbage];
FSTAssertNotContains(@"foo/bar");
@@ -685,9 +683,9 @@ FSTDocumentVersionDictionary *FSTVersionDictionary(FSTMutation *mutation,
FSTTestSetMutation(@"foo/baz", @{@"foo" : @"baz"}),
FSTTestSetMutation(@"foo/bar/Foo/Bar", @{@"Foo" : @"Bar"})
]];
- FSTQuery *query = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo", @"bar" ]]];
+ FSTQuery *query = FSTTestQuery("foo/bar");
FSTDocumentDictionary *docs = [self.localStore executeQuery:query];
- XCTAssertEqualObjects([docs values], @[ FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, YES) ]);
+ XCTAssertEqualObjects([docs values], @[ FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, YES) ]);
}
- (void)testCanExecuteCollectionQueries {
@@ -700,33 +698,33 @@ FSTDocumentVersionDictionary *FSTVersionDictionary(FSTMutation *mutation,
FSTTestSetMutation(@"foo/bar/Foo/Bar", @{@"Foo" : @"Bar"}),
FSTTestSetMutation(@"fooo/blah", @{@"fooo" : @"blah"})
]];
- FSTQuery *query = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo" ]]];
+ FSTQuery *query = FSTTestQuery("foo");
FSTDocumentDictionary *docs = [self.localStore executeQuery:query];
XCTAssertEqualObjects([docs values], (@[
- FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, YES),
- FSTTestDoc(@"foo/baz", 0, @{@"foo" : @"baz"}, YES)
+ FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, YES),
+ FSTTestDoc("foo/baz", 0, @{@"foo" : @"baz"}, YES)
]));
}
- (void)testCanExecuteMixedCollectionQueries {
if ([self isTestBaseClass]) return;
- FSTQuery *query = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo" ]]];
+ FSTQuery *query = FSTTestQuery("foo");
[self allocateQuery:query];
FSTAssertTargetID(2);
- [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc(@"foo/baz", 10, @{@"a" : @"b"}, NO),
+ [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc("foo/baz", 10, @{@"a" : @"b"}, NO),
@[ @2 ], @[])];
- [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc(@"foo/bar", 20, @{@"a" : @"b"}, NO),
+ [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc("foo/bar", 20, @{@"a" : @"b"}, NO),
@[ @2 ], @[])];
[self.localStore locallyWriteMutations:@[ FSTTestSetMutation(@"foo/bonk", @{@"a" : @"b"}) ]];
FSTDocumentDictionary *docs = [self.localStore executeQuery:query];
XCTAssertEqualObjects([docs values], (@[
- FSTTestDoc(@"foo/bar", 20, @{@"a" : @"b"}, NO),
- FSTTestDoc(@"foo/baz", 10, @{@"a" : @"b"}, NO),
- FSTTestDoc(@"foo/bonk", 0, @{@"a" : @"b"}, YES)
+ FSTTestDoc("foo/bar", 20, @{@"a" : @"b"}, NO),
+ FSTTestDoc("foo/baz", 10, @{@"a" : @"b"}, NO),
+ FSTTestDoc("foo/bonk", 0, @{@"a" : @"b"}, YES)
]));
}
@@ -736,7 +734,7 @@ FSTDocumentVersionDictionary *FSTVersionDictionary(FSTMutation *mutation,
// This test only works in the absence of the FSTEagerGarbageCollector.
[self restartWithNoopGarbageCollector];
- FSTQuery *query = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo", @"bar" ]]];
+ FSTQuery *query = FSTTestQuery("foo/bar");
FSTQueryData *queryData = [self.localStore allocateQuery:query];
FSTBoxedTargetID *targetID = @(queryData.targetID);
NSData *resumeToken = FSTTestResumeTokenFromSnapshotVersion(1000);
@@ -770,13 +768,13 @@ FSTDocumentVersionDictionary *FSTVersionDictionary(FSTMutation *mutation,
if ([self isTestBaseClass]) return;
[self restartWithNoopGarbageCollector];
- FSTQuery *query = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo" ]]];
+ FSTQuery *query = FSTTestQuery("foo");
[self allocateQuery:query];
FSTAssertTargetID(2);
- [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc(@"foo/baz", 10, @{@"a" : @"b"}, NO),
+ [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc("foo/baz", 10, @{@"a" : @"b"}, NO),
@[ @2 ], @[])];
- [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc(@"foo/bar", 20, @{@"a" : @"b"}, NO),
+ [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc("foo/bar", 20, @{@"a" : @"b"}, NO),
@[ @2 ], @[])];
[self.localStore locallyWriteMutations:@[ FSTTestSetMutation(@"foo/bonk", @{@"a" : @"b"}) ]];
diff --git a/Firestore/Example/Tests/Local/FSTMemoryLocalStoreTests.m b/Firestore/Example/Tests/Local/FSTMemoryLocalStoreTests.mm
index b78239e..7708eed 100644
--- a/Firestore/Example/Tests/Local/FSTMemoryLocalStoreTests.m
+++ b/Firestore/Example/Tests/Local/FSTMemoryLocalStoreTests.mm
@@ -14,13 +14,10 @@
* limitations under the License.
*/
-#import "Firestore/Source/Local/FSTLocalStore.h"
-
-#import <XCTest/XCTest.h>
+#import "Firestore/Example/Tests/Local/FSTLocalStoreTests.h"
#import "Firestore/Source/Local/FSTMemoryPersistence.h"
-#import "Firestore/Example/Tests/Local/FSTLocalStoreTests.h"
#import "Firestore/Example/Tests/Local/FSTPersistenceTestHelpers.h"
NS_ASSUME_NONNULL_BEGIN
diff --git a/Firestore/Example/Tests/Local/FSTMemoryMutationQueueTests.m b/Firestore/Example/Tests/Local/FSTMemoryMutationQueueTests.mm
index ab7afee..5567078 100644
--- a/Firestore/Example/Tests/Local/FSTMemoryMutationQueueTests.m
+++ b/Firestore/Example/Tests/Local/FSTMemoryMutationQueueTests.mm
@@ -16,12 +16,15 @@
#import "Firestore/Source/Local/FSTMemoryMutationQueue.h"
-#import "Firestore/Source/Auth/FSTUser.h"
#import "Firestore/Source/Local/FSTMemoryPersistence.h"
#import "Firestore/Example/Tests/Local/FSTMutationQueueTests.h"
#import "Firestore/Example/Tests/Local/FSTPersistenceTestHelpers.h"
+#include "Firestore/core/src/firebase/firestore/auth/user.h"
+
+using firebase::firestore::auth::User;
+
@interface FSTMemoryMutationQueueTests : FSTMutationQueueTests
@end
@@ -35,8 +38,7 @@
[super setUp];
self.persistence = [FSTPersistenceTestHelpers memoryPersistence];
- self.mutationQueue =
- [self.persistence mutationQueueForUser:[[FSTUser alloc] initWithUID:@"user"]];
+ self.mutationQueue = [self.persistence mutationQueueForUser:User("user")];
}
@end
diff --git a/Firestore/Example/Tests/Local/FSTMemoryQueryCacheTests.m b/Firestore/Example/Tests/Local/FSTMemoryQueryCacheTests.mm
index fb7df6b..530c508 100644
--- a/Firestore/Example/Tests/Local/FSTMemoryQueryCacheTests.m
+++ b/Firestore/Example/Tests/Local/FSTMemoryQueryCacheTests.mm
@@ -42,7 +42,6 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)tearDown {
- [self.queryCache shutdown];
self.persistence = nil;
self.queryCache = nil;
diff --git a/Firestore/Example/Tests/Local/FSTMemoryRemoteDocumentCacheTests.m b/Firestore/Example/Tests/Local/FSTMemoryRemoteDocumentCacheTests.mm
index 162eef0..d5ab62d 100644
--- a/Firestore/Example/Tests/Local/FSTMemoryRemoteDocumentCacheTests.m
+++ b/Firestore/Example/Tests/Local/FSTMemoryRemoteDocumentCacheTests.mm
@@ -39,7 +39,6 @@
}
- (void)tearDown {
- [self.remoteDocumentCache shutdown];
self.persistence = nil;
self.remoteDocumentCache = nil;
diff --git a/Firestore/Example/Tests/Local/FSTMutationQueueTests.m b/Firestore/Example/Tests/Local/FSTMutationQueueTests.m
deleted file mode 100644
index f168ac9..0000000
--- a/Firestore/Example/Tests/Local/FSTMutationQueueTests.m
+++ /dev/null
@@ -1,511 +0,0 @@
-/*
- * Copyright 2017 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 "Firestore/Example/Tests/Local/FSTMutationQueueTests.h"
-
-#import "Firestore/Source/Auth/FSTUser.h"
-#import "Firestore/Source/Core/FSTQuery.h"
-#import "Firestore/Source/Core/FSTTimestamp.h"
-#import "Firestore/Source/Local/FSTEagerGarbageCollector.h"
-#import "Firestore/Source/Local/FSTMutationQueue.h"
-#import "Firestore/Source/Local/FSTPersistence.h"
-#import "Firestore/Source/Local/FSTWriteGroup.h"
-#import "Firestore/Source/Model/FSTMutation.h"
-#import "Firestore/Source/Model/FSTMutationBatch.h"
-
-#import "Firestore/Example/Tests/Util/FSTHelpers.h"
-
-NS_ASSUME_NONNULL_BEGIN
-
-@implementation FSTMutationQueueTests
-
-- (void)tearDown {
- [self.mutationQueue shutdown];
- [self.persistence shutdown];
- [super tearDown];
-}
-
-/**
- * Xcode will run tests from any class that extends XCTestCase, but this doesn't work for
- * FSTMutationQueueTests since it is incomplete without the implementations supplied by its
- * subclasses.
- */
-- (BOOL)isTestBaseClass {
- return [self class] == [FSTMutationQueueTests class];
-}
-
-- (void)testCountBatches {
- if ([self isTestBaseClass]) return;
-
- XCTAssertEqual(0, [self batchCount]);
- XCTAssertTrue([self.mutationQueue isEmpty]);
-
- FSTMutationBatch *batch1 = [self addMutationBatch];
- XCTAssertEqual(1, [self batchCount]);
- XCTAssertFalse([self.mutationQueue isEmpty]);
-
- FSTMutationBatch *batch2 = [self addMutationBatch];
- XCTAssertEqual(2, [self batchCount]);
-
- [self removeMutationBatches:@[ batch2 ]];
- XCTAssertEqual(1, [self batchCount]);
-
- [self removeMutationBatches:@[ batch1 ]];
- XCTAssertEqual(0, [self batchCount]);
- XCTAssertTrue([self.mutationQueue isEmpty]);
-}
-
-- (void)testAcknowledgeBatchID {
- if ([self isTestBaseClass]) return;
-
- // Initial state of an empty queue
- XCTAssertEqual([self.mutationQueue highestAcknowledgedBatchID], kFSTBatchIDUnknown);
-
- // Adding mutation batches should not change the highest acked batchID.
- FSTMutationBatch *batch1 = [self addMutationBatch];
- FSTMutationBatch *batch2 = [self addMutationBatch];
- FSTMutationBatch *batch3 = [self addMutationBatch];
- XCTAssertGreaterThan(batch1.batchID, kFSTBatchIDUnknown);
- XCTAssertGreaterThan(batch2.batchID, batch1.batchID);
- XCTAssertGreaterThan(batch3.batchID, batch2.batchID);
-
- XCTAssertEqual([self.mutationQueue highestAcknowledgedBatchID], kFSTBatchIDUnknown);
-
- [self acknowledgeBatch:batch1];
- [self acknowledgeBatch:batch2];
- XCTAssertEqual([self.mutationQueue highestAcknowledgedBatchID], batch2.batchID);
-
- [self removeMutationBatches:@[ batch1 ]];
- XCTAssertEqual([self.mutationQueue highestAcknowledgedBatchID], batch2.batchID);
-
- [self removeMutationBatches:@[ batch2 ]];
- XCTAssertEqual([self.mutationQueue highestAcknowledgedBatchID], batch2.batchID);
-
- // Batch 3 never acknowledged.
- [self removeMutationBatches:@[ batch3 ]];
- XCTAssertEqual([self.mutationQueue highestAcknowledgedBatchID], batch2.batchID);
-}
-
-- (void)testAcknowledgeThenRemove {
- if ([self isTestBaseClass]) return;
-
- FSTMutationBatch *batch1 = [self addMutationBatch];
-
- FSTWriteGroup *group = [self.persistence startGroupWithAction:NSStringFromSelector(_cmd)];
- [self.mutationQueue acknowledgeBatch:batch1 streamToken:nil group:group];
- [self.mutationQueue removeMutationBatches:@[ batch1 ] group:group];
- [self.persistence commitGroup:group];
-
- XCTAssertEqual([self batchCount], 0);
- XCTAssertEqual([self.mutationQueue highestAcknowledgedBatchID], batch1.batchID);
-}
-
-- (void)testHighestAcknowledgedBatchIDNeverExceedsNextBatchID {
- if ([self isTestBaseClass]) return;
-
- FSTMutationBatch *batch1 = [self addMutationBatch];
- FSTMutationBatch *batch2 = [self addMutationBatch];
- [self acknowledgeBatch:batch1];
- [self acknowledgeBatch:batch2];
- XCTAssertEqual([self.mutationQueue highestAcknowledgedBatchID], batch2.batchID);
-
- [self removeMutationBatches:@[ batch1, batch2 ]];
- XCTAssertEqual([self.mutationQueue highestAcknowledgedBatchID], batch2.batchID);
-
- // Restart the queue so that nextBatchID will be reset.
- [self.mutationQueue shutdown];
- self.mutationQueue =
- [self.persistence mutationQueueForUser:[[FSTUser alloc] initWithUID:@"user"]];
-
- FSTWriteGroup *group = [self.persistence startGroupWithAction:@"Start MutationQueue"];
- [self.mutationQueue startWithGroup:group];
- [self.persistence commitGroup:group];
-
- // Verify that on restart with an empty queue, nextBatchID falls to a lower value.
- XCTAssertLessThan(self.mutationQueue.nextBatchID, batch2.batchID);
-
- // As a result highestAcknowledgedBatchID must also reset lower.
- XCTAssertEqual([self.mutationQueue highestAcknowledgedBatchID], kFSTBatchIDUnknown);
-
- // The mutation queue will reset the next batchID after all mutations are removed so adding
- // another mutation will cause a collision.
- FSTMutationBatch *newBatch = [self addMutationBatch];
- XCTAssertEqual(newBatch.batchID, batch1.batchID);
-
- // Restart the queue with one unacknowledged batch in it.
- group = [self.persistence startGroupWithAction:@"Start MutationQueue"];
- [self.mutationQueue startWithGroup:group];
- [self.persistence commitGroup:group];
-
- XCTAssertEqual([self.mutationQueue nextBatchID], newBatch.batchID + 1);
-
- // highestAcknowledgedBatchID must still be kFSTBatchIDUnknown.
- XCTAssertEqual([self.mutationQueue highestAcknowledgedBatchID], kFSTBatchIDUnknown);
-}
-
-- (void)testLookupMutationBatch {
- if ([self isTestBaseClass]) return;
-
- // Searching on an empty queue should not find a non-existent batch
- FSTMutationBatch *notFound = [self.mutationQueue lookupMutationBatch:42];
- XCTAssertNil(notFound);
-
- NSMutableArray<FSTMutationBatch *> *batches = [self createBatches:10];
- NSArray<FSTMutationBatch *> *removed = [self makeHoles:@[ @2, @6, @7 ] inBatches:batches];
-
- // After removing, a batch should not be found
- for (NSUInteger i = 0; i < removed.count; i++) {
- notFound = [self.mutationQueue lookupMutationBatch:removed[i].batchID];
- XCTAssertNil(notFound);
- }
-
- // Remaining entries should still be found
- for (FSTMutationBatch *batch in batches) {
- FSTMutationBatch *found = [self.mutationQueue lookupMutationBatch:batch.batchID];
- XCTAssertEqual(found.batchID, batch.batchID);
- }
-
- // Even on a nonempty queue searching should not find a non-existent batch
- notFound = [self.mutationQueue lookupMutationBatch:42];
- XCTAssertNil(notFound);
-}
-
-- (void)testNextMutationBatchAfterBatchID {
- if ([self isTestBaseClass]) return;
-
- NSMutableArray<FSTMutationBatch *> *batches = [self createBatches:10];
-
- // This is an array of successors assuming the removals below will happen:
- NSArray<FSTMutationBatch *> *afters = @[ batches[3], batches[8], batches[8] ];
- NSArray<FSTMutationBatch *> *removed = [self makeHoles:@[ @2, @6, @7 ] inBatches:batches];
-
- for (NSUInteger i = 0; i < batches.count - 1; i++) {
- FSTMutationBatch *current = batches[i];
- FSTMutationBatch *next = batches[i + 1];
- FSTMutationBatch *found = [self.mutationQueue nextMutationBatchAfterBatchID:current.batchID];
- XCTAssertEqual(found.batchID, next.batchID);
- }
-
- for (NSUInteger i = 0; i < removed.count; i++) {
- FSTMutationBatch *current = removed[i];
- FSTMutationBatch *next = afters[i];
- FSTMutationBatch *found = [self.mutationQueue nextMutationBatchAfterBatchID:current.batchID];
- XCTAssertEqual(found.batchID, next.batchID);
- }
-
- FSTMutationBatch *first = batches[0];
- FSTMutationBatch *found = [self.mutationQueue nextMutationBatchAfterBatchID:first.batchID - 42];
- XCTAssertEqual(found.batchID, first.batchID);
-
- FSTMutationBatch *last = batches[batches.count - 1];
- FSTMutationBatch *notFound = [self.mutationQueue nextMutationBatchAfterBatchID:last.batchID];
- XCTAssertNil(notFound);
-}
-
-- (void)testAllMutationBatchesThroughBatchID {
- if ([self isTestBaseClass]) return;
-
- NSMutableArray<FSTMutationBatch *> *batches = [self createBatches:10];
- [self makeHoles:@[ @2, @6, @7 ] inBatches:batches];
-
- NSArray<FSTMutationBatch *> *found, *expected;
-
- found = [self.mutationQueue allMutationBatchesThroughBatchID:batches[0].batchID - 1];
- XCTAssertEqualObjects(found, (@[]));
-
- for (NSUInteger i = 0; i < batches.count; i++) {
- found = [self.mutationQueue allMutationBatchesThroughBatchID:batches[i].batchID];
- expected = [batches subarrayWithRange:NSMakeRange(0, i + 1)];
- XCTAssertEqualObjects(found, expected, @"for index %lu", (unsigned long)i);
- }
-}
-
-- (void)testAllMutationBatchesAffectingDocumentKey {
- if ([self isTestBaseClass]) return;
-
- NSArray<FSTMutation *> *mutations = @[
- FSTTestSetMutation(@"fob/bar",
- @{ @"a" : @1 }),
- FSTTestSetMutation(@"foo/bar",
- @{ @"a" : @1 }),
- FSTTestPatchMutation(@"foo/bar",
- @{ @"b" : @1 }, nil),
- FSTTestSetMutation(@"foo/bar/suffix/key",
- @{ @"a" : @1 }),
- FSTTestSetMutation(@"foo/baz",
- @{ @"a" : @1 }),
- FSTTestSetMutation(@"food/bar",
- @{ @"a" : @1 })
- ];
-
- // Store all the mutations.
- NSMutableArray<FSTMutationBatch *> *batches = [NSMutableArray array];
- FSTWriteGroup *group = [self.persistence startGroupWithAction:@"New mutation batch"];
- for (FSTMutation *mutation in mutations) {
- FSTMutationBatch *batch =
- [self.mutationQueue addMutationBatchWithWriteTime:[FSTTimestamp timestamp]
- mutations:@[ mutation ]
- group:group];
- [batches addObject:batch];
- }
- [self.persistence commitGroup:group];
-
- NSArray<FSTMutationBatch *> *expected = @[ batches[1], batches[2] ];
- NSArray<FSTMutationBatch *> *matches =
- [self.mutationQueue allMutationBatchesAffectingDocumentKey:FSTTestDocKey(@"foo/bar")];
-
- XCTAssertEqualObjects(matches, expected);
-}
-
-- (void)testAllMutationBatchesAffectingQuery {
- if ([self isTestBaseClass]) return;
-
- NSArray<FSTMutation *> *mutations = @[
- FSTTestSetMutation(@"fob/bar",
- @{ @"a" : @1 }),
- FSTTestSetMutation(@"foo/bar",
- @{ @"a" : @1 }),
- FSTTestPatchMutation(@"foo/bar",
- @{ @"b" : @1 }, nil),
- FSTTestSetMutation(@"foo/bar/suffix/key",
- @{ @"a" : @1 }),
- FSTTestSetMutation(@"foo/baz",
- @{ @"a" : @1 }),
- FSTTestSetMutation(@"food/bar",
- @{ @"a" : @1 })
- ];
-
- // Store all the mutations.
- NSMutableArray<FSTMutationBatch *> *batches = [NSMutableArray array];
- FSTWriteGroup *group = [self.persistence startGroupWithAction:@"New mutation batch"];
- for (FSTMutation *mutation in mutations) {
- FSTMutationBatch *batch =
- [self.mutationQueue addMutationBatchWithWriteTime:[FSTTimestamp timestamp]
- mutations:@[ mutation ]
- group:group];
- [batches addObject:batch];
- }
- [self.persistence commitGroup:group];
-
- NSArray<FSTMutationBatch *> *expected = @[ batches[1], batches[2], batches[4] ];
- FSTQuery *query = [FSTQuery queryWithPath:FSTTestPath(@"foo")];
- NSArray<FSTMutationBatch *> *matches =
- [self.mutationQueue allMutationBatchesAffectingQuery:query];
-
- XCTAssertEqualObjects(matches, expected);
-}
-
-- (void)testRemoveMutationBatches {
- if ([self isTestBaseClass]) return;
-
- NSMutableArray<FSTMutationBatch *> *batches = [self createBatches:10];
- FSTMutationBatch *last = batches[batches.count - 1];
-
- [self removeMutationBatches:@[ batches[0] ]];
- [batches removeObjectAtIndex:0];
- XCTAssertEqual([self batchCount], 9);
-
- NSArray<FSTMutationBatch *> *found;
-
- found = [self.mutationQueue allMutationBatchesThroughBatchID:last.batchID];
- XCTAssertEqualObjects(found, batches);
- XCTAssertEqual(found.count, 9);
-
- [self removeMutationBatches:@[ batches[0], batches[1], batches[2] ]];
- [batches removeObjectsInRange:NSMakeRange(0, 3)];
- XCTAssertEqual([self batchCount], 6);
-
- found = [self.mutationQueue allMutationBatchesThroughBatchID:last.batchID];
- XCTAssertEqualObjects(found, batches);
- XCTAssertEqual(found.count, 6);
-
- [self removeMutationBatches:@[ batches[batches.count - 1] ]];
- [batches removeObjectAtIndex:batches.count - 1];
- XCTAssertEqual([self batchCount], 5);
-
- found = [self.mutationQueue allMutationBatchesThroughBatchID:last.batchID];
- XCTAssertEqualObjects(found, batches);
- XCTAssertEqual(found.count, 5);
-
- [self removeMutationBatches:@[ batches[3] ]];
- [batches removeObjectAtIndex:3];
- XCTAssertEqual([self batchCount], 4);
-
- [self removeMutationBatches:@[ batches[1] ]];
- [batches removeObjectAtIndex:1];
- XCTAssertEqual([self batchCount], 3);
-
- found = [self.mutationQueue allMutationBatchesThroughBatchID:last.batchID];
- XCTAssertEqualObjects(found, batches);
- XCTAssertEqual(found.count, 3);
- XCTAssertFalse([self.mutationQueue isEmpty]);
-
- [self removeMutationBatches:batches];
- found = [self.mutationQueue allMutationBatchesThroughBatchID:last.batchID];
- XCTAssertEqualObjects(found, @[]);
- XCTAssertEqual(found.count, 0);
- XCTAssertTrue([self.mutationQueue isEmpty]);
-}
-
-- (void)testRemoveMutationBatchesEmitsGarbageEvents {
- if ([self isTestBaseClass]) return;
-
- FSTEagerGarbageCollector *garbageCollector = [[FSTEagerGarbageCollector alloc] init];
- [garbageCollector addGarbageSource:self.mutationQueue];
-
- NSMutableArray<FSTMutationBatch *> *batches = [NSMutableArray array];
- [batches addObjectsFromArray:@[
- [self addMutationBatchWithKey:@"foo/bar"],
- [self addMutationBatchWithKey:@"foo/ba"],
- [self addMutationBatchWithKey:@"foo/bar2"],
- [self addMutationBatchWithKey:@"foo/bar"],
- [self addMutationBatchWithKey:@"foo/bar/suffix/baz"],
- [self addMutationBatchWithKey:@"bar/baz"],
- ]];
-
- [self removeMutationBatches:@[ batches[0] ]];
- NSSet<FSTDocumentKey *> *garbage = [garbageCollector collectGarbage];
- FSTAssertEqualSets(garbage, @[]);
-
- [self removeMutationBatches:@[ batches[1] ]];
- garbage = [garbageCollector collectGarbage];
- FSTAssertEqualSets(garbage, @[ FSTTestDocKey(@"foo/ba") ]);
-
- [self removeMutationBatches:@[ batches[5] ]];
- garbage = [garbageCollector collectGarbage];
- FSTAssertEqualSets(garbage, @[ FSTTestDocKey(@"bar/baz") ]);
-
- [self removeMutationBatches:@[ batches[2], batches[3] ]];
- garbage = [garbageCollector collectGarbage];
- FSTAssertEqualSets(garbage, (@[ FSTTestDocKey(@"foo/bar"), FSTTestDocKey(@"foo/bar2") ]));
-
- [batches addObject:[self addMutationBatchWithKey:@"foo/bar/suffix/baz"]];
- garbage = [garbageCollector collectGarbage];
- FSTAssertEqualSets(garbage, @[]);
-
- [self removeMutationBatches:@[ batches[4], batches[6] ]];
- garbage = [garbageCollector collectGarbage];
- FSTAssertEqualSets(garbage, @[ FSTTestDocKey(@"foo/bar/suffix/baz") ]);
-}
-
-- (void)testStreamToken {
- if ([self isTestBaseClass]) return;
-
- NSData *streamToken1 = [@"token1" dataUsingEncoding:NSUTF8StringEncoding];
- NSData *streamToken2 = [@"token2" dataUsingEncoding:NSUTF8StringEncoding];
-
- FSTWriteGroup *group = [self.persistence startGroupWithAction:@"initial stream token"];
- [self.mutationQueue setLastStreamToken:streamToken1 group:group];
- [self.persistence commitGroup:group];
-
- FSTMutationBatch *batch1 = [self addMutationBatch];
- [self addMutationBatch];
-
- XCTAssertEqualObjects([self.mutationQueue lastStreamToken], streamToken1);
-
- group = [self.persistence startGroupWithAction:@"acknowledgeBatchID"];
- [self.mutationQueue acknowledgeBatch:batch1 streamToken:streamToken2 group:group];
- [self.persistence commitGroup:group];
-
- XCTAssertEqual(self.mutationQueue.highestAcknowledgedBatchID, batch1.batchID);
- XCTAssertEqualObjects([self.mutationQueue lastStreamToken], streamToken2);
-}
-
-/** Creates a new FSTMutationBatch with the next batch ID and a set of dummy mutations. */
-- (FSTMutationBatch *)addMutationBatch {
- return [self addMutationBatchWithKey:@"foo/bar"];
-}
-
-/**
- * Creates a new FSTMutationBatch with the given key, the next batch ID and a set of dummy
- * mutations.
- */
-- (FSTMutationBatch *)addMutationBatchWithKey:(NSString *)key {
- FSTSetMutation *mutation = FSTTestSetMutation(key, @{ @"a" : @1 });
-
- FSTWriteGroup *group = [self.persistence startGroupWithAction:@"New mutation batch"];
- FSTMutationBatch *batch =
- [self.mutationQueue addMutationBatchWithWriteTime:[FSTTimestamp timestamp]
- mutations:@[ mutation ]
- group:group];
- [self.persistence commitGroup:group];
- return batch;
-}
-
-/**
- * Creates an array of batches containing @a number dummy FSTMutationBatches. Each has a different
- * batchID.
- */
-- (NSMutableArray<FSTMutationBatch *> *)createBatches:(int)number {
- NSMutableArray<FSTMutationBatch *> *batches = [NSMutableArray array];
-
- for (int i = 0; i < number; i++) {
- FSTMutationBatch *batch = [self addMutationBatch];
- [batches addObject:batch];
- }
-
- return batches;
-}
-
-/**
- * Calls -acknowledgeBatch:streamToken:group: on the mutation queue in a new group and commits the
- * the group.
- */
-- (void)acknowledgeBatch:(FSTMutationBatch *)batch {
- FSTWriteGroup *group = [self.persistence startGroupWithAction:@"Ack batchID"];
- [self.mutationQueue acknowledgeBatch:batch streamToken:nil group:group];
- [self.persistence commitGroup:group];
-}
-
-/**
- * Calls -removeMutationBatches:group: on the mutation queue in a new group and commits the group.
- */
-- (void)removeMutationBatches:(NSArray<FSTMutationBatch *> *)batches {
- FSTWriteGroup *group = [self.persistence startGroupWithAction:@"Remove mutation batch"];
- [self.mutationQueue removeMutationBatches:batches group:group];
- [self.persistence commitGroup:group];
-}
-
-/** Returns the number of mutation batches in the mutation queue. */
-- (NSUInteger)batchCount {
- return [self.mutationQueue allMutationBatches].count;
-}
-
-/**
- * Removes entries from from the given @a batches and returns them.
- *
- * @param holes An array of indexes in the batches array; in increasing order. Indexes are relative
- * to the original state of the batches array, not any intermediate state that might occur.
- * @param batches The array to mutate, removing entries from it.
- * @return A new array containing all the entries that were removed from @a batches.
- */
-- (NSArray<FSTMutationBatch *> *)makeHoles:(NSArray<NSNumber *> *)holes
- inBatches:(NSMutableArray<FSTMutationBatch *> *)batches {
- NSMutableArray<FSTMutationBatch *> *removed = [NSMutableArray array];
- for (NSUInteger i = 0; i < holes.count; i++) {
- NSUInteger index = holes[i].unsignedIntegerValue - i;
- FSTMutationBatch *batch = batches[index];
- [self removeMutationBatches:@[ batch ]];
-
- [batches removeObjectAtIndex:index];
- [removed addObject:batch];
- }
- return removed;
-}
-
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/Firestore/Example/Tests/Local/FSTMutationQueueTests.mm b/Firestore/Example/Tests/Local/FSTMutationQueueTests.mm
new file mode 100644
index 0000000..5b5bdd1
--- /dev/null
+++ b/Firestore/Example/Tests/Local/FSTMutationQueueTests.mm
@@ -0,0 +1,538 @@
+/*
+ * Copyright 2017 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 "Firestore/Example/Tests/Local/FSTMutationQueueTests.h"
+
+#import <FirebaseFirestore/FIRTimestamp.h>
+
+#include <set>
+
+#import "Firestore/Source/Core/FSTQuery.h"
+#import "Firestore/Source/Local/FSTEagerGarbageCollector.h"
+#import "Firestore/Source/Local/FSTMutationQueue.h"
+#import "Firestore/Source/Local/FSTPersistence.h"
+#import "Firestore/Source/Model/FSTMutation.h"
+#import "Firestore/Source/Model/FSTMutationBatch.h"
+
+#import "Firestore/Example/Tests/Util/FSTHelpers.h"
+
+#include "Firestore/core/src/firebase/firestore/auth/user.h"
+#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+#include "Firestore/core/test/firebase/firestore/testutil/testutil.h"
+
+namespace testutil = firebase::firestore::testutil;
+using firebase::firestore::auth::User;
+using firebase::firestore::model::DocumentKey;
+
+NS_ASSUME_NONNULL_BEGIN
+
+@implementation FSTMutationQueueTests
+
+- (void)tearDown {
+ [self.persistence shutdown];
+ [super tearDown];
+}
+
+/**
+ * Xcode will run tests from any class that extends XCTestCase, but this doesn't work for
+ * FSTMutationQueueTests since it is incomplete without the implementations supplied by its
+ * subclasses.
+ */
+- (BOOL)isTestBaseClass {
+ return [self class] == [FSTMutationQueueTests class];
+}
+
+- (void)testCountBatches {
+ if ([self isTestBaseClass]) return;
+
+ self.persistence.run("testCountBatches", [&]() {
+ XCTAssertEqual(0, [self batchCount]);
+ XCTAssertTrue([self.mutationQueue isEmpty]);
+
+ FSTMutationBatch *batch1 = [self addMutationBatch];
+ XCTAssertEqual(1, [self batchCount]);
+ XCTAssertFalse([self.mutationQueue isEmpty]);
+
+ FSTMutationBatch *batch2 = [self addMutationBatch];
+ XCTAssertEqual(2, [self batchCount]);
+
+ [self.mutationQueue removeMutationBatches:@[ batch2 ]];
+ XCTAssertEqual(1, [self batchCount]);
+
+ [self.mutationQueue removeMutationBatches:@[ batch1 ]];
+ XCTAssertEqual(0, [self batchCount]);
+ XCTAssertTrue([self.mutationQueue isEmpty]);
+ });
+}
+
+- (void)testAcknowledgeBatchID {
+ if ([self isTestBaseClass]) return;
+
+ // Initial state of an empty queue
+ XCTAssertEqual([self.mutationQueue highestAcknowledgedBatchID], kFSTBatchIDUnknown);
+
+ // Adding mutation batches should not change the highest acked batchID.
+ self.persistence.run("testAcknowledgeBatchID", [&]() {
+ FSTMutationBatch *batch1 = [self addMutationBatch];
+ FSTMutationBatch *batch2 = [self addMutationBatch];
+ FSTMutationBatch *batch3 = [self addMutationBatch];
+ XCTAssertGreaterThan(batch1.batchID, kFSTBatchIDUnknown);
+ XCTAssertGreaterThan(batch2.batchID, batch1.batchID);
+ XCTAssertGreaterThan(batch3.batchID, batch2.batchID);
+
+ XCTAssertEqual([self.mutationQueue highestAcknowledgedBatchID], kFSTBatchIDUnknown);
+
+ [self.mutationQueue acknowledgeBatch:batch1 streamToken:nil];
+ [self.mutationQueue acknowledgeBatch:batch2 streamToken:nil];
+ XCTAssertEqual([self.mutationQueue highestAcknowledgedBatchID], batch2.batchID);
+
+ [self.mutationQueue removeMutationBatches:@[ batch1 ]];
+ XCTAssertEqual([self.mutationQueue highestAcknowledgedBatchID], batch2.batchID);
+
+ [self.mutationQueue removeMutationBatches:@[ batch2 ]];
+ XCTAssertEqual([self.mutationQueue highestAcknowledgedBatchID], batch2.batchID);
+
+ // Batch 3 never acknowledged.
+ [self.mutationQueue removeMutationBatches:@[ batch3 ]];
+ XCTAssertEqual([self.mutationQueue highestAcknowledgedBatchID], batch2.batchID);
+ });
+}
+
+- (void)testAcknowledgeThenRemove {
+ if ([self isTestBaseClass]) return;
+
+ self.persistence.run("testAcknowledgeThenRemove", [&]() {
+ FSTMutationBatch *batch1 = [self addMutationBatch];
+
+ [self.mutationQueue acknowledgeBatch:batch1 streamToken:nil];
+ [self.mutationQueue removeMutationBatches:@[ batch1 ]];
+
+ XCTAssertEqual([self batchCount], 0);
+ XCTAssertEqual([self.mutationQueue highestAcknowledgedBatchID], batch1.batchID);
+ });
+}
+
+- (void)testHighestAcknowledgedBatchIDNeverExceedsNextBatchID {
+ if ([self isTestBaseClass]) return;
+
+ FSTMutationBatch *batch1 =
+ self.persistence.run("testHighestAcknowledgedBatchIDNeverExceedsNextBatchID batch1",
+ [&]() -> FSTMutationBatch * { return [self addMutationBatch]; });
+ FSTMutationBatch *batch2 =
+ self.persistence.run("testHighestAcknowledgedBatchIDNeverExceedsNextBatchID batch2",
+ [&]() -> FSTMutationBatch * { return [self addMutationBatch]; });
+
+ self.persistence.run("testHighestAcknowledgedBatchIDNeverExceedsNextBatchID", [&]() {
+ [self.mutationQueue acknowledgeBatch:batch1 streamToken:nil];
+ [self.mutationQueue acknowledgeBatch:batch2 streamToken:nil];
+ XCTAssertEqual([self.mutationQueue highestAcknowledgedBatchID], batch2.batchID);
+
+ [self.mutationQueue removeMutationBatches:@[ batch1, batch2 ]];
+ XCTAssertEqual([self.mutationQueue highestAcknowledgedBatchID], batch2.batchID);
+ });
+
+ // Restart the queue so that nextBatchID will be reset.
+ FSTMutationBatch *batch = self.persistence.run(
+ "testHighestAcknowledgedBatchIDNeverExceedsNextBatchID restart", [&]() -> FSTMutationBatch * {
+ self.mutationQueue = [self.persistence mutationQueueForUser:User("user")];
+
+ [self.mutationQueue start];
+
+ // Verify that on restart with an empty queue, nextBatchID falls to a lower value.
+ XCTAssertLessThan(self.mutationQueue.nextBatchID, batch2.batchID);
+
+ // As a result highestAcknowledgedBatchID must also reset lower.
+ XCTAssertEqual([self.mutationQueue highestAcknowledgedBatchID], kFSTBatchIDUnknown);
+
+ // The mutation queue will reset the next batchID after all mutations are removed so adding
+ // another mutation will cause a collision.
+ FSTMutationBatch *newBatch = [self addMutationBatch];
+ XCTAssertEqual(newBatch.batchID, batch1.batchID);
+ return newBatch;
+ });
+ self.persistence.run("testHighestAcknowledgedBatchIDNeverExceedsNextBatchID restart2", [&]() {
+ // Restart the queue with one unacknowledged batch in it.
+ [self.mutationQueue start];
+
+ XCTAssertEqual([self.mutationQueue nextBatchID], batch.batchID + 1);
+
+ // highestAcknowledgedBatchID must still be kFSTBatchIDUnknown.
+ XCTAssertEqual([self.mutationQueue highestAcknowledgedBatchID], kFSTBatchIDUnknown);
+ });
+}
+
+- (void)testLookupMutationBatch {
+ if ([self isTestBaseClass]) return;
+
+ // Searching on an empty queue should not find a non-existent batch
+ self.persistence.run("testLookupMutationBatch", [&]() {
+ FSTMutationBatch *notFound = [self.mutationQueue lookupMutationBatch:42];
+ XCTAssertNil(notFound);
+
+ NSMutableArray<FSTMutationBatch *> *batches = [self createBatches:10];
+ NSArray<FSTMutationBatch *> *removed = [self makeHoles:@[ @2, @6, @7 ] inBatches:batches];
+
+ // After removing, a batch should not be found
+ for (NSUInteger i = 0; i < removed.count; i++) {
+ notFound = [self.mutationQueue lookupMutationBatch:removed[i].batchID];
+ XCTAssertNil(notFound);
+ }
+
+ // Remaining entries should still be found
+ for (FSTMutationBatch *batch in batches) {
+ FSTMutationBatch *found = [self.mutationQueue lookupMutationBatch:batch.batchID];
+ XCTAssertEqual(found.batchID, batch.batchID);
+ }
+
+ // Even on a nonempty queue searching should not find a non-existent batch
+ notFound = [self.mutationQueue lookupMutationBatch:42];
+ XCTAssertNil(notFound);
+ });
+}
+
+- (void)testNextMutationBatchAfterBatchID {
+ if ([self isTestBaseClass]) return;
+
+ self.persistence.run("testNextMutationBatchAfterBatchID", [&]() {
+ NSMutableArray<FSTMutationBatch *> *batches = [self createBatches:10];
+
+ // This is an array of successors assuming the removals below will happen:
+ NSArray<FSTMutationBatch *> *afters = @[ batches[3], batches[8], batches[8] ];
+ NSArray<FSTMutationBatch *> *removed = [self makeHoles:@[ @2, @6, @7 ] inBatches:batches];
+
+ for (NSUInteger i = 0; i < batches.count - 1; i++) {
+ FSTMutationBatch *current = batches[i];
+ FSTMutationBatch *next = batches[i + 1];
+ FSTMutationBatch *found = [self.mutationQueue nextMutationBatchAfterBatchID:current.batchID];
+ XCTAssertEqual(found.batchID, next.batchID);
+ }
+
+ for (NSUInteger i = 0; i < removed.count; i++) {
+ FSTMutationBatch *current = removed[i];
+ FSTMutationBatch *next = afters[i];
+ FSTMutationBatch *found = [self.mutationQueue nextMutationBatchAfterBatchID:current.batchID];
+ XCTAssertEqual(found.batchID, next.batchID);
+ }
+
+ FSTMutationBatch *first = batches[0];
+ FSTMutationBatch *found = [self.mutationQueue nextMutationBatchAfterBatchID:first.batchID - 42];
+ XCTAssertEqual(found.batchID, first.batchID);
+
+ FSTMutationBatch *last = batches[batches.count - 1];
+ FSTMutationBatch *notFound = [self.mutationQueue nextMutationBatchAfterBatchID:last.batchID];
+ XCTAssertNil(notFound);
+ });
+}
+
+- (void)testNextMutationBatchAfterBatchIDSkipsAcknowledgedBatches {
+ if ([self isTestBaseClass]) return;
+
+ NSMutableArray<FSTMutationBatch *> *batches = self.persistence.run(
+ "testNextMutationBatchAfterBatchIDSkipsAcknowledgedBatches newBatches",
+ [&]() -> NSMutableArray<FSTMutationBatch *> * {
+ NSMutableArray<FSTMutationBatch *> *newBatches = [self createBatches:3];
+ XCTAssertEqualObjects([self.mutationQueue nextMutationBatchAfterBatchID:kFSTBatchIDUnknown],
+ newBatches[0]);
+ return newBatches;
+ });
+ self.persistence.run("testNextMutationBatchAfterBatchIDSkipsAcknowledgedBatches", [&]() {
+ [self.mutationQueue acknowledgeBatch:batches[0] streamToken:nil];
+ XCTAssertEqualObjects([self.mutationQueue nextMutationBatchAfterBatchID:kFSTBatchIDUnknown],
+ batches[1]);
+ XCTAssertEqualObjects([self.mutationQueue nextMutationBatchAfterBatchID:batches[0].batchID],
+ batches[1]);
+ XCTAssertEqualObjects([self.mutationQueue nextMutationBatchAfterBatchID:batches[1].batchID],
+ batches[2]);
+ });
+}
+
+- (void)testAllMutationBatchesThroughBatchID {
+ if ([self isTestBaseClass]) return;
+
+ self.persistence.run("testAllMutationBatchesThroughBatchID", [&]() {
+ NSMutableArray<FSTMutationBatch *> *batches = [self createBatches:10];
+ [self makeHoles:@[ @2, @6, @7 ] inBatches:batches];
+
+ NSArray<FSTMutationBatch *> *found, *expected;
+
+ found = [self.mutationQueue allMutationBatchesThroughBatchID:batches[0].batchID - 1];
+ XCTAssertEqualObjects(found, (@[]));
+
+ for (NSUInteger i = 0; i < batches.count; i++) {
+ found = [self.mutationQueue allMutationBatchesThroughBatchID:batches[i].batchID];
+ expected = [batches subarrayWithRange:NSMakeRange(0, i + 1)];
+ XCTAssertEqualObjects(found, expected, @"for index %lu", (unsigned long)i);
+ }
+ });
+}
+
+- (void)testAllMutationBatchesAffectingDocumentKey {
+ if ([self isTestBaseClass]) return;
+
+ self.persistence.run("testAllMutationBatchesAffectingDocumentKey", [&]() {
+ NSArray<FSTMutation *> *mutations = @[
+ FSTTestSetMutation(@"fob/bar",
+ @{ @"a" : @1 }),
+ FSTTestSetMutation(@"foo/bar",
+ @{ @"a" : @1 }),
+ FSTTestPatchMutation("foo/bar",
+ @{ @"b" : @1 }, {}),
+ FSTTestSetMutation(@"foo/bar/suffix/key",
+ @{ @"a" : @1 }),
+ FSTTestSetMutation(@"foo/baz",
+ @{ @"a" : @1 }),
+ FSTTestSetMutation(@"food/bar",
+ @{ @"a" : @1 })
+ ];
+
+ // Store all the mutations.
+ NSMutableArray<FSTMutationBatch *> *batches = [NSMutableArray array];
+ for (FSTMutation *mutation in mutations) {
+ FSTMutationBatch *batch =
+ [self.mutationQueue addMutationBatchWithWriteTime:[FIRTimestamp timestamp]
+ mutations:@[ mutation ]];
+ [batches addObject:batch];
+ }
+
+ NSArray<FSTMutationBatch *> *expected = @[ batches[1], batches[2] ];
+ NSArray<FSTMutationBatch *> *matches =
+ [self.mutationQueue allMutationBatchesAffectingDocumentKey:testutil::Key("foo/bar")];
+
+ XCTAssertEqualObjects(matches, expected);
+ });
+}
+
+- (void)testAllMutationBatchesAffectingQuery {
+ if ([self isTestBaseClass]) return;
+
+ self.persistence.run("testAllMutationBatchesAffectingQuery", [&]() {
+ NSArray<FSTMutation *> *mutations = @[
+ FSTTestSetMutation(@"fob/bar",
+ @{ @"a" : @1 }),
+ FSTTestSetMutation(@"foo/bar",
+ @{ @"a" : @1 }),
+ FSTTestPatchMutation("foo/bar",
+ @{ @"b" : @1 }, {}),
+ FSTTestSetMutation(@"foo/bar/suffix/key",
+ @{ @"a" : @1 }),
+ FSTTestSetMutation(@"foo/baz",
+ @{ @"a" : @1 }),
+ FSTTestSetMutation(@"food/bar",
+ @{ @"a" : @1 })
+ ];
+
+ // Store all the mutations.
+ NSMutableArray<FSTMutationBatch *> *batches = [NSMutableArray array];
+ for (FSTMutation *mutation in mutations) {
+ FSTMutationBatch *batch =
+ [self.mutationQueue addMutationBatchWithWriteTime:[FIRTimestamp timestamp]
+ mutations:@[ mutation ]];
+ [batches addObject:batch];
+ }
+
+ NSArray<FSTMutationBatch *> *expected = @[ batches[1], batches[2], batches[4] ];
+ FSTQuery *query = FSTTestQuery("foo");
+ NSArray<FSTMutationBatch *> *matches =
+ [self.mutationQueue allMutationBatchesAffectingQuery:query];
+
+ XCTAssertEqualObjects(matches, expected);
+ });
+}
+
+- (void)testRemoveMutationBatches {
+ if ([self isTestBaseClass]) return;
+
+ self.persistence.run("testRemoveMutationBatches", [&]() {
+ NSMutableArray<FSTMutationBatch *> *batches = [self createBatches:10];
+
+ [self.mutationQueue removeMutationBatches:@[ batches[0] ]];
+ [batches removeObjectAtIndex:0];
+
+ FSTMutationBatch *last = batches[batches.count - 1];
+ XCTAssertEqual([self batchCount], 9);
+
+ NSArray<FSTMutationBatch *> *found;
+
+ found = [self.mutationQueue allMutationBatchesThroughBatchID:last.batchID];
+ XCTAssertEqualObjects(found, batches);
+ XCTAssertEqual(found.count, 9);
+
+ [self.mutationQueue removeMutationBatches:@[ batches[0], batches[1], batches[2] ]];
+ [batches removeObjectsInRange:NSMakeRange(0, 3)];
+ XCTAssertEqual([self batchCount], 6);
+
+ found = [self.mutationQueue allMutationBatchesThroughBatchID:last.batchID];
+ XCTAssertEqualObjects(found, batches);
+ XCTAssertEqual(found.count, 6);
+
+ [self.mutationQueue removeMutationBatches:@[ batches[batches.count - 1] ]];
+ [batches removeObjectAtIndex:batches.count - 1];
+ XCTAssertEqual([self batchCount], 5);
+
+ found = [self.mutationQueue allMutationBatchesThroughBatchID:last.batchID];
+ XCTAssertEqualObjects(found, batches);
+ XCTAssertEqual(found.count, 5);
+
+ [self.mutationQueue removeMutationBatches:@[ batches[3] ]];
+ [batches removeObjectAtIndex:3];
+ XCTAssertEqual([self batchCount], 4);
+
+ [self.mutationQueue removeMutationBatches:@[ batches[1] ]];
+ [batches removeObjectAtIndex:1];
+ XCTAssertEqual([self batchCount], 3);
+
+ found = [self.mutationQueue allMutationBatchesThroughBatchID:last.batchID];
+ XCTAssertEqualObjects(found, batches);
+ XCTAssertEqual(found.count, 3);
+ XCTAssertFalse([self.mutationQueue isEmpty]);
+
+ [self.mutationQueue removeMutationBatches:batches];
+ found = [self.mutationQueue allMutationBatchesThroughBatchID:last.batchID];
+ XCTAssertEqualObjects(found, @[]);
+ XCTAssertEqual(found.count, 0);
+ XCTAssertTrue([self.mutationQueue isEmpty]);
+ });
+}
+
+- (void)testRemoveMutationBatchesEmitsGarbageEvents {
+ if ([self isTestBaseClass]) return;
+
+ FSTEagerGarbageCollector *garbageCollector = [[FSTEagerGarbageCollector alloc] init];
+ [garbageCollector addGarbageSource:self.mutationQueue];
+
+ NSMutableArray<FSTMutationBatch *> *batches = [NSMutableArray array];
+ self.persistence.run("testRemoveMutationBatchesEmitsGarbageEvents", [&]() {
+ [batches addObjectsFromArray:@[
+ [self addMutationBatchWithKey:@"foo/bar"],
+ [self addMutationBatchWithKey:@"foo/ba"],
+ [self addMutationBatchWithKey:@"foo/bar2"],
+ [self addMutationBatchWithKey:@"foo/bar"],
+ [self addMutationBatchWithKey:@"foo/bar/suffix/baz"],
+ [self addMutationBatchWithKey:@"bar/baz"],
+ ]];
+
+ [self.mutationQueue removeMutationBatches:@[ batches[0] ]];
+ std::set<DocumentKey> garbage = [garbageCollector collectGarbage];
+ XCTAssertEqual(garbage, std::set<DocumentKey>({}));
+
+ [self.mutationQueue removeMutationBatches:@[ batches[1] ]];
+ garbage = [garbageCollector collectGarbage];
+ XCTAssertEqual(garbage, std::set<DocumentKey>({testutil::Key("foo/ba")}));
+
+ [self.mutationQueue removeMutationBatches:@[ batches[5] ]];
+ garbage = [garbageCollector collectGarbage];
+ XCTAssertEqual(garbage, std::set<DocumentKey>({testutil::Key("bar/baz")}));
+
+ [self.mutationQueue removeMutationBatches:@[ batches[2], batches[3] ]];
+ garbage = [garbageCollector collectGarbage];
+ XCTAssertEqual(garbage,
+ std::set<DocumentKey>({testutil::Key("foo/bar"), testutil::Key("foo/bar2")}));
+
+ [batches addObject:[self addMutationBatchWithKey:@"foo/bar/suffix/baz"]];
+ garbage = [garbageCollector collectGarbage];
+ XCTAssertEqual(garbage, std::set<DocumentKey>({}));
+
+ [self.mutationQueue removeMutationBatches:@[ batches[4], batches[6] ]];
+ garbage = [garbageCollector collectGarbage];
+ XCTAssertEqual(garbage, std::set<DocumentKey>({testutil::Key("foo/bar/suffix/baz")}));
+ });
+}
+
+- (void)testStreamToken {
+ if ([self isTestBaseClass]) return;
+
+ NSData *streamToken1 = [@"token1" dataUsingEncoding:NSUTF8StringEncoding];
+ NSData *streamToken2 = [@"token2" dataUsingEncoding:NSUTF8StringEncoding];
+
+ self.persistence.run("testStreamToken", [&]() {
+ [self.mutationQueue setLastStreamToken:streamToken1];
+
+ FSTMutationBatch *batch1 = [self addMutationBatch];
+ [self addMutationBatch];
+
+ XCTAssertEqualObjects([self.mutationQueue lastStreamToken], streamToken1);
+
+ [self.mutationQueue acknowledgeBatch:batch1 streamToken:streamToken2];
+ XCTAssertEqual(self.mutationQueue.highestAcknowledgedBatchID, batch1.batchID);
+ XCTAssertEqualObjects([self.mutationQueue lastStreamToken], streamToken2);
+ });
+}
+
+#pragma mark - Helpers
+
+/** Creates a new FSTMutationBatch with the next batch ID and a set of dummy mutations. */
+- (FSTMutationBatch *)addMutationBatch {
+ return [self addMutationBatchWithKey:@"foo/bar"];
+}
+
+/**
+ * Creates a new FSTMutationBatch with the given key, the next batch ID and a set of dummy
+ * mutations.
+ */
+- (FSTMutationBatch *)addMutationBatchWithKey:(NSString *)key {
+ FSTSetMutation *mutation = FSTTestSetMutation(key, @{ @"a" : @1 });
+
+ FSTMutationBatch *batch =
+ [self.mutationQueue addMutationBatchWithWriteTime:[FIRTimestamp timestamp]
+ mutations:@[ mutation ]];
+ return batch;
+}
+
+/**
+ * Creates an array of batches containing @a number dummy FSTMutationBatches. Each has a different
+ * batchID.
+ */
+- (NSMutableArray<FSTMutationBatch *> *)createBatches:(int)number {
+ NSMutableArray<FSTMutationBatch *> *batches = [NSMutableArray array];
+
+ for (int i = 0; i < number; i++) {
+ FSTMutationBatch *batch = [self addMutationBatch];
+ [batches addObject:batch];
+ }
+
+ return batches;
+}
+
+/** Returns the number of mutation batches in the mutation queue. */
+- (NSUInteger)batchCount {
+ return [self.mutationQueue allMutationBatches].count;
+}
+
+/**
+ * Removes entries from from the given @a batches and returns them.
+ *
+ * @param holes An array of indexes in the batches array; in increasing order. Indexes are relative
+ * to the original state of the batches array, not any intermediate state that might occur.
+ * @param batches The array to mutate, removing entries from it.
+ * @return A new array containing all the entries that were removed from @a batches.
+ */
+- (NSArray<FSTMutationBatch *> *)makeHoles:(NSArray<NSNumber *> *)holes
+ inBatches:(NSMutableArray<FSTMutationBatch *> *)batches {
+ NSMutableArray<FSTMutationBatch *> *removed = [NSMutableArray array];
+ for (NSUInteger i = 0; i < holes.count; i++) {
+ NSUInteger index = holes[i].unsignedIntegerValue - i;
+ FSTMutationBatch *batch = batches[index];
+ [self.mutationQueue removeMutationBatches:@[ batch ]];
+
+ [batches removeObjectAtIndex:index];
+ [removed addObject:batch];
+ }
+ return removed;
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firestore/Example/Tests/Local/FSTPersistenceTestHelpers.h b/Firestore/Example/Tests/Local/FSTPersistenceTestHelpers.h
index 936bacf..5859d4b 100644
--- a/Firestore/Example/Tests/Local/FSTPersistenceTestHelpers.h
+++ b/Firestore/Example/Tests/Local/FSTPersistenceTestHelpers.h
@@ -24,6 +24,12 @@ NS_ASSUME_NONNULL_BEGIN
@interface FSTPersistenceTestHelpers : NSObject
/**
+ * @return The directory where a leveldb instance can store data files. Any files that existed
+ * there will be deleted first.
+ */
++ (NSString *)levelDBDir;
+
+/**
* Creates and starts a new FSTLevelDB instance for testing, destroying any previous contents
* if they existed.
*
diff --git a/Firestore/Example/Tests/Local/FSTPersistenceTestHelpers.m b/Firestore/Example/Tests/Local/FSTPersistenceTestHelpers.mm
index c773b12..b59a062 100644
--- a/Firestore/Example/Tests/Local/FSTPersistenceTestHelpers.m
+++ b/Firestore/Example/Tests/Local/FSTPersistenceTestHelpers.mm
@@ -19,17 +19,20 @@
#import "Firestore/Source/Local/FSTLevelDB.h"
#import "Firestore/Source/Local/FSTLocalSerializer.h"
#import "Firestore/Source/Local/FSTMemoryPersistence.h"
-#import "Firestore/Source/Model/FSTDatabaseID.h"
#import "Firestore/Source/Remote/FSTSerializerBeta.h"
+#include "Firestore/core/src/firebase/firestore/model/database_id.h"
+#include "Firestore/core/src/firebase/firestore/util/string_apple.h"
+
+using firebase::firestore::model::DatabaseId;
+
NS_ASSUME_NONNULL_BEGIN
@implementation FSTPersistenceTestHelpers
-+ (FSTLevelDB *)levelDBPersistence {
++ (NSString *)levelDBDir {
NSError *error;
NSFileManager *files = [NSFileManager defaultManager];
-
NSString *dir =
[NSTemporaryDirectory() stringByAppendingPathComponent:@"FSTPersistenceTestHelpers"];
if ([files fileExistsAtPath:dir]) {
@@ -40,12 +43,19 @@ NS_ASSUME_NONNULL_BEGIN
format:@"Failed to clean up leveldb path %@: %@", dir, error];
}
}
+ return dir;
+}
+
++ (FSTLevelDB *)levelDBPersistence {
+ // This owns the DatabaseIds since we do not have FirestoreClient instance to own them.
+ static DatabaseId database_id{"p", "d"};
- FSTDatabaseID *databaseID = [FSTDatabaseID databaseIDWithProject:@"p" database:@"d"];
- FSTSerializerBeta *remoteSerializer = [[FSTSerializerBeta alloc] initWithDatabaseID:databaseID];
+ NSString *dir = [self levelDBDir];
+ FSTSerializerBeta *remoteSerializer = [[FSTSerializerBeta alloc] initWithDatabaseID:&database_id];
FSTLocalSerializer *serializer =
[[FSTLocalSerializer alloc] initWithRemoteSerializer:remoteSerializer];
FSTLevelDB *db = [[FSTLevelDB alloc] initWithDirectory:dir serializer:serializer];
+ NSError *error;
BOOL success = [db start:&error];
if (!success) {
[NSException raise:NSInternalInconsistencyException
diff --git a/Firestore/Example/Tests/Local/FSTQueryCacheTests.m b/Firestore/Example/Tests/Local/FSTQueryCacheTests.m
deleted file mode 100644
index 1fed440..0000000
--- a/Firestore/Example/Tests/Local/FSTQueryCacheTests.m
+++ /dev/null
@@ -1,375 +0,0 @@
-/*
- * Copyright 2017 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 "Firestore/Example/Tests/Local/FSTQueryCacheTests.h"
-
-#import "Firestore/Source/Core/FSTQuery.h"
-#import "Firestore/Source/Core/FSTSnapshotVersion.h"
-#import "Firestore/Source/Local/FSTEagerGarbageCollector.h"
-#import "Firestore/Source/Local/FSTPersistence.h"
-#import "Firestore/Source/Local/FSTQueryData.h"
-#import "Firestore/Source/Local/FSTWriteGroup.h"
-#import "Firestore/Source/Model/FSTDocumentKey.h"
-
-#import "Firestore/Example/Tests/Util/FSTHelpers.h"
-#import "Firestore/third_party/Immutable/Tests/FSTImmutableSortedSet+Testing.h"
-
-NS_ASSUME_NONNULL_BEGIN
-
-@implementation FSTQueryCacheTests {
- FSTQuery *_queryRooms;
-}
-
-- (void)setUp {
- [super setUp];
-
- _queryRooms = FSTTestQuery(@"rooms");
-}
-
-/**
- * Xcode will run tests from any class that extends XCTestCase, but this doesn't work for
- * FSTSpecTests since it is incomplete without the implementations supplied by its subclasses.
- */
-- (BOOL)isTestBaseClass {
- return [self class] == [FSTQueryCacheTests class];
-}
-
-- (void)testReadQueryNotInCache {
- if ([self isTestBaseClass]) return;
-
- XCTAssertNil([self.queryCache queryDataForQuery:_queryRooms]);
-}
-
-- (void)testSetAndReadAQuery {
- if ([self isTestBaseClass]) return;
-
- FSTQueryData *queryData = [self queryDataWithQuery:_queryRooms targetID:1 version:1];
- [self addQueryData:queryData];
-
- FSTQueryData *result = [self.queryCache queryDataForQuery:_queryRooms];
- XCTAssertEqualObjects(result.query, queryData.query);
- XCTAssertEqual(result.targetID, queryData.targetID);
- XCTAssertEqualObjects(result.resumeToken, queryData.resumeToken);
-}
-
-- (void)testCanonicalIDCollision {
- if ([self isTestBaseClass]) return;
-
- // Type information is currently lost in our canonicalID implementations so this currently an
- // easy way to force colliding canonicalIDs
- FSTQuery *q1 = [[FSTQuery queryWithPath:FSTTestPath(@"a")]
- queryByAddingFilter:FSTTestFilter(@"foo", @"==", @(1))];
- FSTQuery *q2 = [[FSTQuery queryWithPath:FSTTestPath(@"a")]
- queryByAddingFilter:FSTTestFilter(@"foo", @"==", @"1")];
- XCTAssertEqualObjects(q1.canonicalID, q2.canonicalID);
-
- FSTQueryData *data1 = [self queryDataWithQuery:q1 targetID:1 version:1];
- [self addQueryData:data1];
-
- // Using the other query should not return the query cache entry despite equal canonicalIDs.
- XCTAssertNil([self.queryCache queryDataForQuery:q2]);
- XCTAssertEqualObjects([self.queryCache queryDataForQuery:q1], data1);
-
- FSTQueryData *data2 = [self queryDataWithQuery:q2 targetID:2 version:1];
- [self addQueryData:data2];
-
- XCTAssertEqualObjects([self.queryCache queryDataForQuery:q1], data1);
- XCTAssertEqualObjects([self.queryCache queryDataForQuery:q2], data2);
-
- [self removeQueryData:data1];
- XCTAssertNil([self.queryCache queryDataForQuery:q1]);
- XCTAssertEqualObjects([self.queryCache queryDataForQuery:q2], data2);
-
- [self removeQueryData:data2];
- XCTAssertNil([self.queryCache queryDataForQuery:q1]);
- XCTAssertNil([self.queryCache queryDataForQuery:q2]);
-}
-
-- (void)testSetQueryToNewValue {
- if ([self isTestBaseClass]) return;
-
- FSTQueryData *queryData1 = [self queryDataWithQuery:_queryRooms targetID:1 version:1];
- [self addQueryData:queryData1];
-
- FSTQueryData *queryData2 = [self queryDataWithQuery:_queryRooms targetID:1 version:2];
- [self addQueryData:queryData2];
-
- FSTQueryData *result = [self.queryCache queryDataForQuery:_queryRooms];
- XCTAssertNotEqualObjects(queryData2.resumeToken, queryData1.resumeToken);
- XCTAssertNotEqualObjects(queryData2.snapshotVersion, queryData1.snapshotVersion);
- XCTAssertEqualObjects(result.resumeToken, queryData2.resumeToken);
- XCTAssertEqualObjects(result.snapshotVersion, queryData2.snapshotVersion);
-}
-
-- (void)testRemoveQuery {
- if ([self isTestBaseClass]) return;
-
- FSTQueryData *queryData1 = [self queryDataWithQuery:_queryRooms targetID:1 version:1];
- [self addQueryData:queryData1];
-
- [self removeQueryData:queryData1];
-
- FSTQueryData *result = [self.queryCache queryDataForQuery:_queryRooms];
- XCTAssertNil(result);
-}
-
-- (void)testRemoveNonExistentQuery {
- if ([self isTestBaseClass]) return;
-
- FSTQueryData *queryData = [self queryDataWithQuery:_queryRooms targetID:1 version:1];
-
- // no-op, but make sure it doesn't throw.
- XCTAssertNoThrow([self removeQueryData:queryData]);
-}
-
-- (void)testRemoveQueryRemovesMatchingKeysToo {
- if ([self isTestBaseClass]) return;
-
- FSTQueryData *rooms = [self queryDataWithQuery:_queryRooms targetID:1 version:1];
- [self addQueryData:rooms];
-
- FSTDocumentKey *key1 = [FSTDocumentKey keyWithPathString:@"rooms/foo"];
- FSTDocumentKey *key2 = [FSTDocumentKey keyWithPathString:@"rooms/bar"];
- [self addMatchingKey:key1 forTargetID:rooms.targetID];
- [self addMatchingKey:key2 forTargetID:rooms.targetID];
-
- XCTAssertTrue([self.queryCache containsKey:key1]);
- XCTAssertTrue([self.queryCache containsKey:key2]);
-
- [self removeQueryData:rooms];
- XCTAssertFalse([self.queryCache containsKey:key1]);
- XCTAssertFalse([self.queryCache containsKey:key2]);
-}
-
-- (void)testAddOrRemoveMatchingKeys {
- if ([self isTestBaseClass]) return;
-
- FSTDocumentKey *key = [FSTDocumentKey keyWithPathString:@"foo/bar"];
-
- XCTAssertFalse([self.queryCache containsKey:key]);
-
- [self addMatchingKey:key forTargetID:1];
- XCTAssertTrue([self.queryCache containsKey:key]);
-
- [self addMatchingKey:key forTargetID:2];
- XCTAssertTrue([self.queryCache containsKey:key]);
-
- [self removeMatchingKey:key forTargetID:1];
- XCTAssertTrue([self.queryCache containsKey:key]);
-
- [self removeMatchingKey:key forTargetID:2];
- XCTAssertFalse([self.queryCache containsKey:key]);
-}
-
-- (void)testRemoveMatchingKeysForTargetID {
- if ([self isTestBaseClass]) return;
-
- FSTDocumentKey *key1 = [FSTDocumentKey keyWithPathString:@"foo/bar"];
- FSTDocumentKey *key2 = [FSTDocumentKey keyWithPathString:@"foo/baz"];
- FSTDocumentKey *key3 = [FSTDocumentKey keyWithPathString:@"foo/blah"];
-
- [self addMatchingKey:key1 forTargetID:1];
- [self addMatchingKey:key2 forTargetID:1];
- [self addMatchingKey:key3 forTargetID:2];
- XCTAssertTrue([self.queryCache containsKey:key1]);
- XCTAssertTrue([self.queryCache containsKey:key2]);
- XCTAssertTrue([self.queryCache containsKey:key3]);
-
- [self removeMatchingKeysForTargetID:1];
- XCTAssertFalse([self.queryCache containsKey:key1]);
- XCTAssertFalse([self.queryCache containsKey:key2]);
- XCTAssertTrue([self.queryCache containsKey:key3]);
-
- [self removeMatchingKeysForTargetID:2];
- XCTAssertFalse([self.queryCache containsKey:key1]);
- XCTAssertFalse([self.queryCache containsKey:key2]);
- XCTAssertFalse([self.queryCache containsKey:key3]);
-}
-
-- (void)testRemoveEmitsGarbageEvents {
- if ([self isTestBaseClass]) return;
-
- FSTEagerGarbageCollector *garbageCollector = [[FSTEagerGarbageCollector alloc] init];
- [garbageCollector addGarbageSource:self.queryCache];
- FSTAssertEqualSets([garbageCollector collectGarbage], @[]);
-
- FSTQueryData *rooms = [self queryDataWithQuery:FSTTestQuery(@"rooms") targetID:1 version:1];
- FSTDocumentKey *room1 = [FSTDocumentKey keyWithPathString:@"rooms/bar"];
- FSTDocumentKey *room2 = [FSTDocumentKey keyWithPathString:@"rooms/foo"];
- [self addQueryData:rooms];
- [self addMatchingKey:room1 forTargetID:rooms.targetID];
- [self addMatchingKey:room2 forTargetID:rooms.targetID];
-
- FSTQueryData *halls = [self queryDataWithQuery:FSTTestQuery(@"halls") targetID:2 version:1];
- FSTDocumentKey *hall1 = [FSTDocumentKey keyWithPathString:@"halls/bar"];
- FSTDocumentKey *hall2 = [FSTDocumentKey keyWithPathString:@"halls/foo"];
- [self addQueryData:halls];
- [self addMatchingKey:hall1 forTargetID:halls.targetID];
- [self addMatchingKey:hall2 forTargetID:halls.targetID];
-
- FSTAssertEqualSets([garbageCollector collectGarbage], @[]);
-
- [self removeMatchingKey:room1 forTargetID:rooms.targetID];
- FSTAssertEqualSets([garbageCollector collectGarbage], @[ room1 ]);
-
- [self removeQueryData:rooms];
- FSTAssertEqualSets([garbageCollector collectGarbage], @[ room2 ]);
-
- [self removeMatchingKeysForTargetID:halls.targetID];
- FSTAssertEqualSets([garbageCollector collectGarbage], (@[ hall1, hall2 ]));
-}
-
-- (void)testMatchingKeysForTargetID {
- if ([self isTestBaseClass]) return;
-
- FSTDocumentKey *key1 = [FSTDocumentKey keyWithPathString:@"foo/bar"];
- FSTDocumentKey *key2 = [FSTDocumentKey keyWithPathString:@"foo/baz"];
- FSTDocumentKey *key3 = [FSTDocumentKey keyWithPathString:@"foo/blah"];
-
- [self addMatchingKey:key1 forTargetID:1];
- [self addMatchingKey:key2 forTargetID:1];
- [self addMatchingKey:key3 forTargetID:2];
-
- FSTAssertEqualSets([self.queryCache matchingKeysForTargetID:1], (@[ key1, key2 ]));
- FSTAssertEqualSets([self.queryCache matchingKeysForTargetID:2], @[ key3 ]);
-
- [self addMatchingKey:key1 forTargetID:2];
- FSTAssertEqualSets([self.queryCache matchingKeysForTargetID:1], (@[ key1, key2 ]));
- FSTAssertEqualSets([self.queryCache matchingKeysForTargetID:2], (@[ key1, key3 ]));
-}
-
-- (void)testHighestTargetID {
- if ([self isTestBaseClass]) return;
-
- XCTAssertEqual([self.queryCache highestTargetID], 0);
-
- FSTQueryData *query1 = [[FSTQueryData alloc] initWithQuery:FSTTestQuery(@"rooms")
- targetID:1
- purpose:FSTQueryPurposeListen];
- FSTDocumentKey *key1 = [FSTDocumentKey keyWithPathString:@"rooms/bar"];
- FSTDocumentKey *key2 = [FSTDocumentKey keyWithPathString:@"rooms/foo"];
- [self addQueryData:query1];
- [self addMatchingKey:key1 forTargetID:1];
- [self addMatchingKey:key2 forTargetID:1];
-
- FSTQueryData *query2 = [[FSTQueryData alloc] initWithQuery:FSTTestQuery(@"halls")
- targetID:2
- purpose:FSTQueryPurposeListen];
- FSTDocumentKey *key3 = [FSTDocumentKey keyWithPathString:@"halls/foo"];
- [self addQueryData:query2];
- [self addMatchingKey:key3 forTargetID:2];
- XCTAssertEqual([self.queryCache highestTargetID], 2);
-
- // TargetIDs never come down.
- [self removeQueryData:query2];
- XCTAssertEqual([self.queryCache highestTargetID], 2);
-
- // A query with an empty result set still counts.
- FSTQueryData *query3 = [[FSTQueryData alloc] initWithQuery:FSTTestQuery(@"garages")
- targetID:42
- purpose:FSTQueryPurposeListen];
- [self addQueryData:query3];
- XCTAssertEqual([self.queryCache highestTargetID], 42);
-
- [self removeQueryData:query1];
- XCTAssertEqual([self.queryCache highestTargetID], 42);
-
- [self removeQueryData:query3];
- XCTAssertEqual([self.queryCache highestTargetID], 42);
-
- // Verify that the highestTargetID even survives restarts.
- [self.queryCache shutdown];
- self.queryCache = [self.persistence queryCache];
- [self.queryCache start];
- XCTAssertEqual([self.queryCache highestTargetID], 42);
-}
-
-- (void)testLastRemoteSnapshotVersion {
- if ([self isTestBaseClass]) return;
-
- XCTAssertEqualObjects([self.queryCache lastRemoteSnapshotVersion],
- [FSTSnapshotVersion noVersion]);
-
- // Can set the snapshot version.
- FSTWriteGroup *group = [self.persistence startGroupWithAction:@"setLastRemoteSnapshotVersion"];
- [self.queryCache setLastRemoteSnapshotVersion:FSTTestVersion(42) group:group];
- [self.persistence commitGroup:group];
- XCTAssertEqualObjects([self.queryCache lastRemoteSnapshotVersion], FSTTestVersion(42));
-
- // Snapshot version persists restarts.
- self.queryCache = [self.persistence queryCache];
- [self.queryCache start];
- XCTAssertEqualObjects([self.queryCache lastRemoteSnapshotVersion], FSTTestVersion(42));
-}
-
-#pragma mark - Helpers
-
-/**
- * Creates a new FSTQueryData object from the given parameters, synthesizing a resume token from
- * the snapshot version.
- */
-- (FSTQueryData *)queryDataWithQuery:(FSTQuery *)query
- targetID:(FSTTargetID)targetID
- version:(FSTTestSnapshotVersion)version {
- NSData *resumeToken = FSTTestResumeTokenFromSnapshotVersion(version);
- return [[FSTQueryData alloc] initWithQuery:query
- targetID:targetID
- purpose:FSTQueryPurposeListen
- snapshotVersion:FSTTestVersion(version)
- resumeToken:resumeToken];
-}
-
-/** Adds the given query data to the queryCache under test, committing immediately. */
-- (void)addQueryData:(FSTQueryData *)queryData {
- FSTWriteGroup *group = [self.persistence startGroupWithAction:@"addQueryData"];
- [self.queryCache addQueryData:queryData group:group];
- [self.persistence commitGroup:group];
-}
-
-/** Removes the given query data from the queryCache under test, committing immediately. */
-- (void)removeQueryData:(FSTQueryData *)queryData {
- FSTWriteGroup *group = [self.persistence startGroupWithAction:@"removeQueryData"];
- [self.queryCache removeQueryData:queryData group:group];
- [self.persistence commitGroup:group];
-}
-
-- (void)addMatchingKey:(FSTDocumentKey *)key forTargetID:(FSTTargetID)targetID {
- FSTDocumentKeySet *keys = [FSTDocumentKeySet keySet];
- keys = [keys setByAddingObject:key];
-
- FSTWriteGroup *group = [self.persistence startGroupWithAction:@"addMatchingKeys"];
- [self.queryCache addMatchingKeys:keys forTargetID:targetID group:group];
- [self.persistence commitGroup:group];
-}
-
-- (void)removeMatchingKey:(FSTDocumentKey *)key forTargetID:(FSTTargetID)targetID {
- FSTDocumentKeySet *keys = [FSTDocumentKeySet keySet];
- keys = [keys setByAddingObject:key];
-
- FSTWriteGroup *group = [self.persistence startGroupWithAction:@"removeMatchingKeys"];
- [self.queryCache removeMatchingKeys:keys forTargetID:targetID group:group];
- [self.persistence commitGroup:group];
-}
-
-- (void)removeMatchingKeysForTargetID:(FSTTargetID)targetID {
- FSTWriteGroup *group = [self.persistence startGroupWithAction:@"removeMatchingKeysForTargetID"];
- [self.queryCache removeMatchingKeysForTargetID:targetID group:group];
- [self.persistence commitGroup:group];
-}
-
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/Firestore/Example/Tests/Local/FSTQueryCacheTests.mm b/Firestore/Example/Tests/Local/FSTQueryCacheTests.mm
new file mode 100644
index 0000000..429a83a
--- /dev/null
+++ b/Firestore/Example/Tests/Local/FSTQueryCacheTests.mm
@@ -0,0 +1,445 @@
+/*
+ * Copyright 2017 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 "Firestore/Example/Tests/Local/FSTQueryCacheTests.h"
+
+#include <set>
+
+#import "Firestore/Source/Core/FSTQuery.h"
+#import "Firestore/Source/Core/FSTSnapshotVersion.h"
+#import "Firestore/Source/Local/FSTEagerGarbageCollector.h"
+#import "Firestore/Source/Local/FSTPersistence.h"
+#import "Firestore/Source/Local/FSTQueryData.h"
+
+#import "Firestore/Example/Tests/Util/FSTHelpers.h"
+#import "Firestore/third_party/Immutable/Tests/FSTImmutableSortedSet+Testing.h"
+
+#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+#include "Firestore/core/test/firebase/firestore/testutil/testutil.h"
+
+namespace testutil = firebase::firestore::testutil;
+using firebase::firestore::model::DocumentKey;
+
+NS_ASSUME_NONNULL_BEGIN
+
+@implementation FSTQueryCacheTests {
+ FSTQuery *_queryRooms;
+ FSTListenSequenceNumber _previousSequenceNumber;
+ FSTTargetID _previousTargetID;
+ FSTTestSnapshotVersion _previousSnapshotVersion;
+}
+
+- (void)setUp {
+ [super setUp];
+
+ _queryRooms = FSTTestQuery("rooms");
+ _previousSequenceNumber = 1000;
+ _previousTargetID = 500;
+ _previousSnapshotVersion = 100;
+}
+
+/**
+ * Xcode will run tests from any class that extends XCTestCase, but this doesn't work for
+ * FSTSpecTests since it is incomplete without the implementations supplied by its subclasses.
+ */
+- (BOOL)isTestBaseClass {
+ return [self class] == [FSTQueryCacheTests class];
+}
+
+- (void)testReadQueryNotInCache {
+ if ([self isTestBaseClass]) return;
+
+ self.persistence.run("testReadQueryNotInCache",
+ [&]() { XCTAssertNil([self.queryCache queryDataForQuery:_queryRooms]); });
+}
+
+- (void)testSetAndReadAQuery {
+ if ([self isTestBaseClass]) return;
+
+ self.persistence.run("testSetAndReadAQuery", [&]() {
+ FSTQueryData *queryData = [self queryDataWithQuery:_queryRooms];
+ [self.queryCache addQueryData:queryData];
+
+ FSTQueryData *result = [self.queryCache queryDataForQuery:_queryRooms];
+ XCTAssertEqualObjects(result.query, queryData.query);
+ XCTAssertEqual(result.targetID, queryData.targetID);
+ XCTAssertEqualObjects(result.resumeToken, queryData.resumeToken);
+ });
+}
+
+- (void)testCanonicalIDCollision {
+ if ([self isTestBaseClass]) return;
+
+ self.persistence.run("testCanonicalIDCollision", [&]() {
+ // Type information is currently lost in our canonicalID implementations so this currently an
+ // easy way to force colliding canonicalIDs
+ FSTQuery *q1 = [FSTTestQuery("a") queryByAddingFilter:FSTTestFilter("foo", @"==", @(1))];
+ FSTQuery *q2 = [FSTTestQuery("a") queryByAddingFilter:FSTTestFilter("foo", @"==", @"1")];
+ XCTAssertEqualObjects(q1.canonicalID, q2.canonicalID);
+
+ FSTQueryData *data1 = [self queryDataWithQuery:q1];
+ [self.queryCache addQueryData:data1];
+
+ // Using the other query should not return the query cache entry despite equal canonicalIDs.
+ XCTAssertNil([self.queryCache queryDataForQuery:q2]);
+ XCTAssertEqualObjects([self.queryCache queryDataForQuery:q1], data1);
+
+ FSTQueryData *data2 = [self queryDataWithQuery:q2];
+ [self.queryCache addQueryData:data2];
+ XCTAssertEqual([self.queryCache count], 2);
+
+ XCTAssertEqualObjects([self.queryCache queryDataForQuery:q1], data1);
+ XCTAssertEqualObjects([self.queryCache queryDataForQuery:q2], data2);
+
+ [self.queryCache removeQueryData:data1];
+ XCTAssertNil([self.queryCache queryDataForQuery:q1]);
+ XCTAssertEqualObjects([self.queryCache queryDataForQuery:q2], data2);
+ XCTAssertEqual([self.queryCache count], 1);
+
+ [self.queryCache removeQueryData:data2];
+ XCTAssertNil([self.queryCache queryDataForQuery:q1]);
+ XCTAssertNil([self.queryCache queryDataForQuery:q2]);
+ XCTAssertEqual([self.queryCache count], 0);
+ });
+}
+
+- (void)testSetQueryToNewValue {
+ if ([self isTestBaseClass]) return;
+
+ self.persistence.run("testSetQueryToNewValue", [&]() {
+ FSTQueryData *queryData1 =
+ [self queryDataWithQuery:_queryRooms targetID:1 listenSequenceNumber:10 version:1];
+ [self.queryCache addQueryData:queryData1];
+
+ FSTQueryData *queryData2 =
+ [self queryDataWithQuery:_queryRooms targetID:1 listenSequenceNumber:10 version:2];
+ [self.queryCache addQueryData:queryData2];
+
+ FSTQueryData *result = [self.queryCache queryDataForQuery:_queryRooms];
+ XCTAssertNotEqualObjects(queryData2.resumeToken, queryData1.resumeToken);
+ XCTAssertNotEqualObjects(queryData2.snapshotVersion, queryData1.snapshotVersion);
+ XCTAssertEqualObjects(result.resumeToken, queryData2.resumeToken);
+ XCTAssertEqualObjects(result.snapshotVersion, queryData2.snapshotVersion);
+ });
+}
+
+- (void)testRemoveQuery {
+ if ([self isTestBaseClass]) return;
+
+ self.persistence.run("testRemoveQuery", [&]() {
+ FSTQueryData *queryData1 = [self queryDataWithQuery:_queryRooms];
+ [self.queryCache addQueryData:queryData1];
+
+ [self.queryCache removeQueryData:queryData1];
+
+ FSTQueryData *result = [self.queryCache queryDataForQuery:_queryRooms];
+ XCTAssertNil(result);
+ });
+}
+
+- (void)testRemoveNonExistentQuery {
+ if ([self isTestBaseClass]) return;
+
+ self.persistence.run("testRemoveNonExistentQuery", [&]() {
+ FSTQueryData *queryData = [self queryDataWithQuery:_queryRooms];
+
+ // no-op, but make sure it doesn't throw.
+ XCTAssertNoThrow([self.queryCache removeQueryData:queryData]);
+ });
+}
+
+- (void)testRemoveQueryRemovesMatchingKeysToo {
+ if ([self isTestBaseClass]) return;
+
+ self.persistence.run("testRemoveQueryRemovesMatchingKeysToo", [&]() {
+ FSTQueryData *rooms = [self queryDataWithQuery:_queryRooms];
+ [self.queryCache addQueryData:rooms];
+
+ DocumentKey key1 = testutil::Key("rooms/foo");
+ DocumentKey key2 = testutil::Key("rooms/bar");
+ [self addMatchingKey:key1 forTargetID:rooms.targetID];
+ [self addMatchingKey:key2 forTargetID:rooms.targetID];
+
+ XCTAssertTrue([self.queryCache containsKey:key1]);
+ XCTAssertTrue([self.queryCache containsKey:key2]);
+
+ [self.queryCache removeQueryData:rooms];
+ XCTAssertFalse([self.queryCache containsKey:key1]);
+ XCTAssertFalse([self.queryCache containsKey:key2]);
+ });
+}
+
+- (void)testAddOrRemoveMatchingKeys {
+ if ([self isTestBaseClass]) return;
+
+ self.persistence.run("testAddOrRemoveMatchingKeys", [&]() {
+ DocumentKey key = testutil::Key("foo/bar");
+
+ XCTAssertFalse([self.queryCache containsKey:key]);
+
+ [self addMatchingKey:key forTargetID:1];
+ XCTAssertTrue([self.queryCache containsKey:key]);
+
+ [self addMatchingKey:key forTargetID:2];
+ XCTAssertTrue([self.queryCache containsKey:key]);
+
+ [self removeMatchingKey:key forTargetID:1];
+ XCTAssertTrue([self.queryCache containsKey:key]);
+
+ [self removeMatchingKey:key forTargetID:2];
+ XCTAssertFalse([self.queryCache containsKey:key]);
+ });
+}
+
+- (void)testRemoveMatchingKeysForTargetID {
+ if ([self isTestBaseClass]) return;
+
+ self.persistence.run("testRemoveMatchingKeysForTargetID", [&]() {
+ DocumentKey key1 = testutil::Key("foo/bar");
+ DocumentKey key2 = testutil::Key("foo/baz");
+ DocumentKey key3 = testutil::Key("foo/blah");
+
+ [self addMatchingKey:key1 forTargetID:1];
+ [self addMatchingKey:key2 forTargetID:1];
+ [self addMatchingKey:key3 forTargetID:2];
+ XCTAssertTrue([self.queryCache containsKey:key1]);
+ XCTAssertTrue([self.queryCache containsKey:key2]);
+ XCTAssertTrue([self.queryCache containsKey:key3]);
+
+ [self.queryCache removeMatchingKeysForTargetID:1];
+ XCTAssertFalse([self.queryCache containsKey:key1]);
+ XCTAssertFalse([self.queryCache containsKey:key2]);
+ XCTAssertTrue([self.queryCache containsKey:key3]);
+
+ [self.queryCache removeMatchingKeysForTargetID:2];
+ XCTAssertFalse([self.queryCache containsKey:key1]);
+ XCTAssertFalse([self.queryCache containsKey:key2]);
+ XCTAssertFalse([self.queryCache containsKey:key3]);
+ });
+}
+
+- (void)testRemoveEmitsGarbageEvents {
+ if ([self isTestBaseClass]) return;
+
+ self.persistence.run("testRemoveEmitsGarbageEvents", [&]() {
+ FSTEagerGarbageCollector *garbageCollector = [[FSTEagerGarbageCollector alloc] init];
+ [garbageCollector addGarbageSource:self.queryCache];
+ XCTAssertEqual([garbageCollector collectGarbage], std::set<DocumentKey>({}));
+
+ FSTQueryData *rooms = [self queryDataWithQuery:FSTTestQuery("rooms")];
+ DocumentKey room1 = testutil::Key("rooms/bar");
+ DocumentKey room2 = testutil::Key("rooms/foo");
+ [self.queryCache addQueryData:rooms];
+ [self addMatchingKey:room1 forTargetID:rooms.targetID];
+ [self addMatchingKey:room2 forTargetID:rooms.targetID];
+
+ FSTQueryData *halls = [self queryDataWithQuery:FSTTestQuery("halls")];
+ DocumentKey hall1 = testutil::Key("halls/bar");
+ DocumentKey hall2 = testutil::Key("halls/foo");
+ [self.queryCache addQueryData:halls];
+ [self addMatchingKey:hall1 forTargetID:halls.targetID];
+ [self addMatchingKey:hall2 forTargetID:halls.targetID];
+
+ XCTAssertEqual([garbageCollector collectGarbage], std::set<DocumentKey>({}));
+
+ [self removeMatchingKey:room1 forTargetID:rooms.targetID];
+ XCTAssertEqual([garbageCollector collectGarbage], std::set<DocumentKey>({room1}));
+
+ [self.queryCache removeQueryData:rooms];
+ XCTAssertEqual([garbageCollector collectGarbage], std::set<DocumentKey>({room2}));
+
+ [self.queryCache removeMatchingKeysForTargetID:halls.targetID];
+ XCTAssertEqual([garbageCollector collectGarbage], std::set<DocumentKey>({hall1, hall2}));
+ });
+}
+
+- (void)testMatchingKeysForTargetID {
+ if ([self isTestBaseClass]) return;
+
+ self.persistence.run("testMatchingKeysForTargetID", [&]() {
+ DocumentKey key1 = testutil::Key("foo/bar");
+ DocumentKey key2 = testutil::Key("foo/baz");
+ DocumentKey key3 = testutil::Key("foo/blah");
+
+ [self addMatchingKey:key1 forTargetID:1];
+ [self addMatchingKey:key2 forTargetID:1];
+ [self addMatchingKey:key3 forTargetID:2];
+
+ FSTAssertEqualSets([self.queryCache matchingKeysForTargetID:1], (@[ key1, key2 ]));
+ FSTAssertEqualSets([self.queryCache matchingKeysForTargetID:2], @[ key3 ]);
+
+ [self addMatchingKey:key1 forTargetID:2];
+ FSTAssertEqualSets([self.queryCache matchingKeysForTargetID:1], (@[ key1, key2 ]));
+ FSTAssertEqualSets([self.queryCache matchingKeysForTargetID:2], (@[ key1, key3 ]));
+ });
+}
+
+- (void)testHighestListenSequenceNumber {
+ if ([self isTestBaseClass]) return;
+
+ self.persistence.run("testHighestListenSequenceNumber", [&]() {
+ FSTQueryData *query1 = [[FSTQueryData alloc] initWithQuery:FSTTestQuery("rooms")
+ targetID:1
+ listenSequenceNumber:10
+ purpose:FSTQueryPurposeListen];
+ [self.queryCache addQueryData:query1];
+ FSTQueryData *query2 = [[FSTQueryData alloc] initWithQuery:FSTTestQuery("halls")
+ targetID:2
+ listenSequenceNumber:20
+ purpose:FSTQueryPurposeListen];
+ [self.queryCache addQueryData:query2];
+ XCTAssertEqual([self.queryCache highestListenSequenceNumber], 20);
+
+ // TargetIDs never come down.
+ [self.queryCache removeQueryData:query2];
+ XCTAssertEqual([self.queryCache highestListenSequenceNumber], 20);
+
+ // A query with an empty result set still counts.
+ FSTQueryData *query3 = [[FSTQueryData alloc] initWithQuery:FSTTestQuery("garages")
+ targetID:42
+ listenSequenceNumber:100
+ purpose:FSTQueryPurposeListen];
+ [self.queryCache addQueryData:query3];
+ XCTAssertEqual([self.queryCache highestListenSequenceNumber], 100);
+
+ [self.queryCache removeQueryData:query1];
+ XCTAssertEqual([self.queryCache highestListenSequenceNumber], 100);
+
+ [self.queryCache removeQueryData:query3];
+ XCTAssertEqual([self.queryCache highestListenSequenceNumber], 100);
+ });
+
+ // Verify that the highestTargetID even survives restarts.
+ self.persistence.run("testHighestListenSequenceNumber restart", [&]() {
+ self.queryCache = [self.persistence queryCache];
+ [self.queryCache start];
+ XCTAssertEqual([self.queryCache highestListenSequenceNumber], 100);
+ });
+}
+
+- (void)testHighestTargetID {
+ if ([self isTestBaseClass]) return;
+
+ self.persistence.run("testHighestTargetID", [&]() {
+ XCTAssertEqual([self.queryCache highestTargetID], 0);
+
+ FSTQueryData *query1 = [[FSTQueryData alloc] initWithQuery:FSTTestQuery("rooms")
+ targetID:1
+ listenSequenceNumber:10
+ purpose:FSTQueryPurposeListen];
+ DocumentKey key1 = testutil::Key("rooms/bar");
+ DocumentKey key2 = testutil::Key("rooms/foo");
+ [self.queryCache addQueryData:query1];
+ [self addMatchingKey:key1 forTargetID:1];
+ [self addMatchingKey:key2 forTargetID:1];
+
+ FSTQueryData *query2 = [[FSTQueryData alloc] initWithQuery:FSTTestQuery("halls")
+ targetID:2
+ listenSequenceNumber:20
+ purpose:FSTQueryPurposeListen];
+ DocumentKey key3 = testutil::Key("halls/foo");
+ [self.queryCache addQueryData:query2];
+ [self addMatchingKey:key3 forTargetID:2];
+ XCTAssertEqual([self.queryCache highestTargetID], 2);
+
+ // TargetIDs never come down.
+ [self.queryCache removeQueryData:query2];
+ XCTAssertEqual([self.queryCache highestTargetID], 2);
+
+ // A query with an empty result set still counts.
+ FSTQueryData *query3 = [[FSTQueryData alloc] initWithQuery:FSTTestQuery("garages")
+ targetID:42
+ listenSequenceNumber:100
+ purpose:FSTQueryPurposeListen];
+ [self.queryCache addQueryData:query3];
+ XCTAssertEqual([self.queryCache highestTargetID], 42);
+
+ [self.queryCache removeQueryData:query1];
+ XCTAssertEqual([self.queryCache highestTargetID], 42);
+
+ [self.queryCache removeQueryData:query3];
+ XCTAssertEqual([self.queryCache highestTargetID], 42);
+ });
+
+ // Verify that the highestTargetID even survives restarts.
+ self.persistence.run("testHighestTargetID restart", [&]() {
+ self.queryCache = [self.persistence queryCache];
+ [self.queryCache start];
+ XCTAssertEqual([self.queryCache highestTargetID], 42);
+ });
+}
+
+- (void)testLastRemoteSnapshotVersion {
+ if ([self isTestBaseClass]) return;
+
+ self.persistence.run("testLastRemoteSnapshotVersion", [&]() {
+ XCTAssertEqualObjects([self.queryCache lastRemoteSnapshotVersion],
+ [FSTSnapshotVersion noVersion]);
+
+ // Can set the snapshot version.
+ [self.queryCache setLastRemoteSnapshotVersion:FSTTestVersion(42)];
+ XCTAssertEqualObjects([self.queryCache lastRemoteSnapshotVersion], FSTTestVersion(42));
+ });
+
+ // Snapshot version persists restarts.
+ self.queryCache = [self.persistence queryCache];
+ self.persistence.run("testLastRemoteSnapshotVersion restart", [&]() {
+ [self.queryCache start];
+ XCTAssertEqualObjects([self.queryCache lastRemoteSnapshotVersion], FSTTestVersion(42));
+ });
+}
+
+#pragma mark - Helpers
+
+/**
+ * Creates a new FSTQueryData object from the given parameters, synthesizing a resume token from
+ * the snapshot version.
+ */
+- (FSTQueryData *)queryDataWithQuery:(FSTQuery *)query {
+ return [self queryDataWithQuery:query
+ targetID:++_previousTargetID
+ listenSequenceNumber:++_previousSequenceNumber
+ version:++_previousSnapshotVersion];
+}
+
+- (FSTQueryData *)queryDataWithQuery:(FSTQuery *)query
+ targetID:(FSTTargetID)targetID
+ listenSequenceNumber:(FSTListenSequenceNumber)sequenceNumber
+ version:(FSTTestSnapshotVersion)version {
+ NSData *resumeToken = FSTTestResumeTokenFromSnapshotVersion(version);
+ return [[FSTQueryData alloc] initWithQuery:query
+ targetID:targetID
+ listenSequenceNumber:sequenceNumber
+ purpose:FSTQueryPurposeListen
+ snapshotVersion:FSTTestVersion(version)
+ resumeToken:resumeToken];
+}
+
+- (void)addMatchingKey:(const DocumentKey &)key forTargetID:(FSTTargetID)targetID {
+ FSTDocumentKeySet *keys = [FSTDocumentKeySet keySet];
+ keys = [keys setByAddingObject:key];
+ [self.queryCache addMatchingKeys:keys forTargetID:targetID];
+}
+
+- (void)removeMatchingKey:(const DocumentKey &)key forTargetID:(FSTTargetID)targetID {
+ FSTDocumentKeySet *keys = [FSTDocumentKeySet keySet];
+ keys = [keys setByAddingObject:key];
+ [self.queryCache removeMatchingKeys:keys forTargetID:targetID];
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firestore/Example/Tests/Local/FSTReferenceSetTests.m b/Firestore/Example/Tests/Local/FSTReferenceSetTests.mm
index 0b852a2..802117a 100644
--- a/Firestore/Example/Tests/Local/FSTReferenceSetTests.m
+++ b/Firestore/Example/Tests/Local/FSTReferenceSetTests.mm
@@ -18,6 +18,7 @@
#import <XCTest/XCTest.h>
+#import "Firestore/Example/Tests/Util/FSTHelpers.h"
#import "Firestore/Source/Model/FSTDocumentKey.h"
NS_ASSUME_NONNULL_BEGIN
@@ -28,7 +29,7 @@ NS_ASSUME_NONNULL_BEGIN
@implementation FSTReferenceSetTests
- (void)testAddOrRemoveReferences {
- FSTDocumentKey *key = [FSTDocumentKey keyWithPathString:@"foo/bar"];
+ FSTDocumentKey *key = FSTTestDocKey(@"foo/bar");
FSTReferenceSet *referenceSet = [[FSTReferenceSet alloc] init];
XCTAssertTrue([referenceSet isEmpty]);
@@ -53,9 +54,9 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)testRemoveAllReferencesForTargetID {
- FSTDocumentKey *key1 = [FSTDocumentKey keyWithPathString:@"foo/bar"];
- FSTDocumentKey *key2 = [FSTDocumentKey keyWithPathString:@"foo/baz"];
- FSTDocumentKey *key3 = [FSTDocumentKey keyWithPathString:@"foo/blah"];
+ FSTDocumentKey *key1 = FSTTestDocKey(@"foo/bar");
+ FSTDocumentKey *key2 = FSTTestDocKey(@"foo/baz");
+ FSTDocumentKey *key3 = FSTTestDocKey(@"foo/blah");
FSTReferenceSet *referenceSet = [[FSTReferenceSet alloc] init];
[referenceSet addReferenceToKey:key1 forID:1];
diff --git a/Firestore/Example/Tests/Local/FSTRemoteDocumentCacheTests.m b/Firestore/Example/Tests/Local/FSTRemoteDocumentCacheTests.m
deleted file mode 100644
index 16fe3bf..0000000
--- a/Firestore/Example/Tests/Local/FSTRemoteDocumentCacheTests.m
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Copyright 2017 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 "Firestore/Example/Tests/Local/FSTRemoteDocumentCacheTests.h"
-
-#import "Firestore/Source/Core/FSTQuery.h"
-#import "Firestore/Source/Local/FSTPersistence.h"
-#import "Firestore/Source/Local/FSTWriteGroup.h"
-#import "Firestore/Source/Model/FSTDocument.h"
-#import "Firestore/Source/Model/FSTDocumentKey.h"
-#import "Firestore/Source/Model/FSTDocumentSet.h"
-
-#import "Firestore/Example/Tests/Util/FSTHelpers.h"
-
-NS_ASSUME_NONNULL_BEGIN
-
-static NSString *const kDocPath = @"a/b";
-static NSString *const kLongDocPath = @"a/b/c/d/e/f";
-static const int kVersion = 42;
-
-@implementation FSTRemoteDocumentCacheTests {
- NSDictionary<NSString *, id> *_kDocData;
-}
-
-- (void)setUp {
- [super setUp];
-
- // essentially a constant, but can't be a compile-time one.
- _kDocData = @{ @"a" : @1, @"b" : @2 };
-}
-
-- (void)testReadDocumentNotInCache {
- if (!self.remoteDocumentCache) return;
-
- XCTAssertNil([self readEntryAtPath:kDocPath]);
-}
-
-// Helper for next two tests.
-- (void)setAndReadADocumentAtPath:(NSString *)path {
- FSTDocument *written = [self setTestDocumentAtPath:path];
- FSTMaybeDocument *read = [self readEntryAtPath:path];
- XCTAssertEqualObjects(read, written);
-}
-
-- (void)testSetAndReadADocument {
- if (!self.remoteDocumentCache) return;
-
- [self setAndReadADocumentAtPath:kDocPath];
-}
-
-- (void)testSetAndReadADocumentAtDeepPath {
- if (!self.remoteDocumentCache) return;
-
- [self setAndReadADocumentAtPath:kLongDocPath];
-}
-
-- (void)testSetAndReadDeletedDocument {
- if (!self.remoteDocumentCache) return;
-
- FSTDeletedDocument *deletedDoc = FSTTestDeletedDoc(kDocPath, kVersion);
- [self addEntry:deletedDoc];
-
- XCTAssertEqualObjects([self readEntryAtPath:kDocPath], deletedDoc);
-}
-
-- (void)testSetDocumentToNewValue {
- if (!self.remoteDocumentCache) return;
-
- [self setTestDocumentAtPath:kDocPath];
- FSTDocument *newDoc = FSTTestDoc(kDocPath, kVersion, @{ @"data" : @2 }, NO);
- [self addEntry:newDoc];
- XCTAssertEqualObjects([self readEntryAtPath:kDocPath], newDoc);
-}
-
-- (void)testRemoveDocument {
- if (!self.remoteDocumentCache) return;
-
- [self setTestDocumentAtPath:kDocPath];
- [self removeEntryAtPath:kDocPath];
-
- XCTAssertNil([self readEntryAtPath:kDocPath]);
-}
-
-- (void)testRemoveNonExistentDocument {
- if (!self.remoteDocumentCache) return;
-
- // no-op, but make sure it doesn't throw.
- XCTAssertNoThrow([self removeEntryAtPath:kDocPath]);
-}
-
-// TODO(mikelehen): Write more elaborate tests once we have more elaborate implementations.
-- (void)testDocumentsMatchingQuery {
- if (!self.remoteDocumentCache) return;
-
- // TODO(rsgowman): This just verifies that we do a prefix scan against the
- // query path. We'll need more tests once we add index support.
- [self setTestDocumentAtPath:@"a/1"];
- [self setTestDocumentAtPath:@"b/1"];
- [self setTestDocumentAtPath:@"b/2"];
- [self setTestDocumentAtPath:@"c/1"];
-
- FSTQuery *query = [FSTQuery queryWithPath:FSTTestPath(@"b")];
- FSTDocumentDictionary *results = [self.remoteDocumentCache documentsMatchingQuery:query];
- NSArray *expected =
- @[ FSTTestDoc(@"b/1", kVersion, _kDocData, NO), FSTTestDoc(@"b/2", kVersion, _kDocData, NO) ];
- XCTAssertEqual([results count], [expected count]);
- for (FSTDocument *doc in expected) {
- XCTAssertEqualObjects([results objectForKey:doc.key], doc);
- }
-}
-
-#pragma mark - Helpers
-
-- (FSTDocument *)setTestDocumentAtPath:(NSString *)path {
- FSTDocument *doc = FSTTestDoc(path, kVersion, _kDocData, NO);
- [self addEntry:doc];
- return doc;
-}
-
-- (void)addEntry:(FSTMaybeDocument *)maybeDoc {
- FSTWriteGroup *group = [self.persistence startGroupWithAction:@"addEntry"];
- [self.remoteDocumentCache addEntry:maybeDoc group:group];
- [self.persistence commitGroup:group];
-}
-
-- (FSTMaybeDocument *_Nullable)readEntryAtPath:(NSString *)path {
- return [self.remoteDocumentCache entryForKey:FSTTestDocKey(path)];
-}
-
-- (void)removeEntryAtPath:(NSString *)path {
- FSTWriteGroup *group = [self.persistence startGroupWithAction:@"removeEntryAtPath"];
- [self.remoteDocumentCache removeEntryForKey:FSTTestDocKey(path) group:group];
- [self.persistence commitGroup:group];
-}
-
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/Firestore/Example/Tests/Local/FSTRemoteDocumentCacheTests.mm b/Firestore/Example/Tests/Local/FSTRemoteDocumentCacheTests.mm
new file mode 100644
index 0000000..2e32591
--- /dev/null
+++ b/Firestore/Example/Tests/Local/FSTRemoteDocumentCacheTests.mm
@@ -0,0 +1,157 @@
+/*
+ * Copyright 2017 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 "Firestore/Example/Tests/Local/FSTRemoteDocumentCacheTests.h"
+
+#import "Firestore/Source/Core/FSTQuery.h"
+#import "Firestore/Source/Local/FSTPersistence.h"
+#import "Firestore/Source/Model/FSTDocument.h"
+#import "Firestore/Source/Model/FSTDocumentKey.h"
+#import "Firestore/Source/Model/FSTDocumentSet.h"
+
+#import "Firestore/Example/Tests/Util/FSTHelpers.h"
+
+#include "Firestore/core/src/firebase/firestore/util/string_apple.h"
+#include "Firestore/core/test/firebase/firestore/testutil/testutil.h"
+#include "absl/strings/string_view.h"
+
+namespace testutil = firebase::firestore::testutil;
+namespace util = firebase::firestore::util;
+
+NS_ASSUME_NONNULL_BEGIN
+
+static const char *kDocPath = "a/b";
+static const char *kLongDocPath = "a/b/c/d/e/f";
+static const int kVersion = 42;
+
+@implementation FSTRemoteDocumentCacheTests {
+ NSDictionary<NSString *, id> *_kDocData;
+}
+
+- (void)setUp {
+ [super setUp];
+
+ // essentially a constant, but can't be a compile-time one.
+ _kDocData = @{ @"a" : @1, @"b" : @2 };
+}
+
+- (void)testReadDocumentNotInCache {
+ if (!self.remoteDocumentCache) return;
+
+ self.persistence.run("testReadDocumentNotInCache", [&]() {
+ XCTAssertNil([self.remoteDocumentCache entryForKey:testutil::Key(kDocPath)]);
+ });
+}
+
+// Helper for next two tests.
+- (void)setAndReadADocumentAtPath:(const absl::string_view)path {
+ self.persistence.run("setAndReadADocumentAtPath", [&]() {
+ FSTDocument *written = [self setTestDocumentAtPath:path];
+ FSTMaybeDocument *read = [self.remoteDocumentCache entryForKey:testutil::Key(path)];
+ XCTAssertEqualObjects(read, written);
+ });
+}
+
+- (void)testSetAndReadADocument {
+ if (!self.remoteDocumentCache) return;
+
+ [self setAndReadADocumentAtPath:kDocPath];
+}
+
+- (void)testSetAndReadADocumentAtDeepPath {
+ if (!self.remoteDocumentCache) return;
+
+ [self setAndReadADocumentAtPath:kLongDocPath];
+}
+
+- (void)testSetAndReadDeletedDocument {
+ if (!self.remoteDocumentCache) return;
+
+ self.persistence.run("testSetAndReadDeletedDocument", [&]() {
+ FSTDeletedDocument *deletedDoc = FSTTestDeletedDoc(kDocPath, kVersion);
+ [self.remoteDocumentCache addEntry:deletedDoc];
+
+ XCTAssertEqualObjects([self.remoteDocumentCache entryForKey:testutil::Key(kDocPath)],
+ deletedDoc);
+ });
+}
+
+- (void)testSetDocumentToNewValue {
+ if (!self.remoteDocumentCache) return;
+
+ self.persistence.run("testSetDocumentToNewValue", [&]() {
+ [self setTestDocumentAtPath:kDocPath];
+ FSTDocument *newDoc = FSTTestDoc(kDocPath, kVersion, @{ @"data" : @2 }, NO);
+ [self.remoteDocumentCache addEntry:newDoc];
+ XCTAssertEqualObjects([self.remoteDocumentCache entryForKey:testutil::Key(kDocPath)], newDoc);
+ });
+}
+
+- (void)testRemoveDocument {
+ if (!self.remoteDocumentCache) return;
+
+ self.persistence.run("testRemoveDocument", [&]() {
+ [self setTestDocumentAtPath:kDocPath];
+ [self.remoteDocumentCache removeEntryForKey:testutil::Key(kDocPath)];
+
+ XCTAssertNil([self.remoteDocumentCache entryForKey:testutil::Key(kDocPath)]);
+ });
+}
+
+- (void)testRemoveNonExistentDocument {
+ if (!self.remoteDocumentCache) return;
+
+ self.persistence.run("testRemoveNonExistentDocument", [&]() {
+ // no-op, but make sure it doesn't throw.
+ XCTAssertNoThrow([self.remoteDocumentCache removeEntryForKey:testutil::Key(kDocPath)]);
+ });
+}
+
+// TODO(mikelehen): Write more elaborate tests once we have more elaborate implementations.
+- (void)testDocumentsMatchingQuery {
+ if (!self.remoteDocumentCache) return;
+
+ self.persistence.run("testDocumentsMatchingQuery", [&]() {
+ // TODO(rsgowman): This just verifies that we do a prefix scan against the
+ // query path. We'll need more tests once we add index support.
+ [self setTestDocumentAtPath:"a/1"];
+ [self setTestDocumentAtPath:"b/1"];
+ [self setTestDocumentAtPath:"b/2"];
+ [self setTestDocumentAtPath:"c/1"];
+
+ FSTQuery *query = FSTTestQuery("b");
+ FSTDocumentDictionary *results = [self.remoteDocumentCache documentsMatchingQuery:query];
+ NSArray *expected =
+ @[ FSTTestDoc("b/1", kVersion, _kDocData, NO), FSTTestDoc("b/2", kVersion, _kDocData, NO) ];
+ XCTAssertEqual([results count], [expected count]);
+ for (FSTDocument *doc in expected) {
+ XCTAssertEqualObjects([results objectForKey:doc.key], doc);
+ }
+ });
+}
+
+#pragma mark - Helpers
+// TODO(gsoltis): reevaluate if any of these helpers are still needed
+
+- (FSTDocument *)setTestDocumentAtPath:(const absl::string_view)path {
+ FSTDocument *doc = FSTTestDoc(path, kVersion, _kDocData, NO);
+ [self.remoteDocumentCache addEntry:doc];
+ return doc;
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firestore/Example/Tests/Local/FSTRemoteDocumentChangeBufferTests.m b/Firestore/Example/Tests/Local/FSTRemoteDocumentChangeBufferTests.m
deleted file mode 100644
index 1970779..0000000
--- a/Firestore/Example/Tests/Local/FSTRemoteDocumentChangeBufferTests.m
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright 2017 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 "Firestore/Source/Local/FSTRemoteDocumentChangeBuffer.h"
-
-#import <XCTest/XCTest.h>
-
-#import "Firestore/Source/Local/FSTLevelDB.h"
-#import "Firestore/Source/Local/FSTRemoteDocumentCache.h"
-#import "Firestore/Source/Model/FSTDocument.h"
-
-#import "Firestore/Example/Tests/Local/FSTPersistenceTestHelpers.h"
-#import "Firestore/Example/Tests/Util/FSTHelpers.h"
-
-NS_ASSUME_NONNULL_BEGIN
-
-@interface FSTRemoteDocumentChangeBufferTests : XCTestCase
-@end
-
-@implementation FSTRemoteDocumentChangeBufferTests {
- FSTLevelDB *_db;
- id<FSTRemoteDocumentCache> _remoteDocumentCache;
- FSTRemoteDocumentChangeBuffer *_remoteDocumentBuffer;
-
- FSTMaybeDocument *_kInitialADoc;
- FSTMaybeDocument *_kInitialBDoc;
-}
-
-- (void)setUp {
- [super setUp];
-
- _db = [FSTPersistenceTestHelpers levelDBPersistence];
- _remoteDocumentCache = [_db remoteDocumentCache];
-
- // Add a couple initial items to the cache.
- FSTWriteGroup *group = [_db startGroupWithAction:@"Add initial docs."];
- _kInitialADoc = FSTTestDoc(@"coll/a", 42, @{@"test" : @"data"}, NO);
- [_remoteDocumentCache addEntry:_kInitialADoc group:group];
-
- _kInitialBDoc =
- [FSTDeletedDocument documentWithKey:FSTTestDocKey(@"coll/b") version:FSTTestVersion(314)];
- [_remoteDocumentCache addEntry:_kInitialBDoc group:group];
- [_db commitGroup:group];
-
- _remoteDocumentBuffer =
- [FSTRemoteDocumentChangeBuffer changeBufferWithCache:_remoteDocumentCache];
-}
-
-- (void)tearDown {
- _remoteDocumentBuffer = nil;
- _remoteDocumentCache = nil;
- _db = nil;
-
- [super tearDown];
-}
-
-- (void)testReadUnchangedEntry {
- XCTAssertEqualObjects([_remoteDocumentBuffer entryForKey:FSTTestDocKey(@"coll/a")],
- _kInitialADoc);
-}
-
-- (void)testAddEntryAndReadItBack {
- FSTMaybeDocument *newADoc = FSTTestDoc(@"coll/a", 43, @{@"new" : @"data"}, NO);
- [_remoteDocumentBuffer addEntry:newADoc];
- XCTAssertEqualObjects([_remoteDocumentBuffer entryForKey:FSTTestDocKey(@"coll/a")], newADoc);
-
- // B should still be unchanged.
- XCTAssertEqualObjects([_remoteDocumentBuffer entryForKey:FSTTestDocKey(@"coll/b")],
- _kInitialBDoc);
-}
-
-- (void)testApplyChanges {
- FSTMaybeDocument *newADoc = FSTTestDoc(@"coll/a", 43, @{@"new" : @"data"}, NO);
- [_remoteDocumentBuffer addEntry:newADoc];
- XCTAssertEqualObjects([_remoteDocumentBuffer entryForKey:FSTTestDocKey(@"coll/a")], newADoc);
-
- // Reading directly against the cache should still yield the old result.
- XCTAssertEqualObjects([_remoteDocumentCache entryForKey:FSTTestDocKey(@"coll/a")], _kInitialADoc);
-
- FSTWriteGroup *group = [_db startGroupWithAction:@"Apply changes"];
- [_remoteDocumentBuffer applyToWriteGroup:group];
- [_db commitGroup:group];
-
- // Reading against the cache should now yield the new result.
- XCTAssertEqualObjects([_remoteDocumentCache entryForKey:FSTTestDocKey(@"coll/a")], newADoc);
-}
-
-- (void)testMethodsThrowAfterApply {
- FSTWriteGroup *group = [_db startGroupWithAction:@"Apply changes"];
- [_remoteDocumentBuffer applyToWriteGroup:group];
- [_db commitGroup:group];
-
- XCTAssertThrows([_remoteDocumentBuffer entryForKey:FSTTestDocKey(@"coll/a")]);
- XCTAssertThrows([_remoteDocumentBuffer addEntry:_kInitialADoc]);
- XCTAssertThrows([_remoteDocumentBuffer applyToWriteGroup:group]);
-}
-
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/Firestore/Example/Tests/Local/FSTWriteGroupTests.mm b/Firestore/Example/Tests/Local/FSTWriteGroupTests.mm
deleted file mode 100644
index edd05a3..0000000
--- a/Firestore/Example/Tests/Local/FSTWriteGroupTests.mm
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright 2017 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 "Firestore/Source/Local/FSTWriteGroup.h"
-
-#import <XCTest/XCTest.h>
-#include <leveldb/db.h>
-
-#import "Firestore/Protos/objc/firestore/local/Mutation.pbobjc.h"
-#import "Firestore/Source/Local/FSTLevelDB.h"
-#import "Firestore/Source/Local/FSTLevelDBKey.h"
-
-#import "Firestore/Example/Tests/Local/FSTPersistenceTestHelpers.h"
-
-using leveldb::ReadOptions;
-using leveldb::Status;
-
-NS_ASSUME_NONNULL_BEGIN
-
-@interface FSTWriteGroupTests : XCTestCase
-@end
-
-@implementation FSTWriteGroupTests {
- FSTLevelDB *_db;
-}
-
-- (void)setUp {
- [super setUp];
-
- _db = [FSTPersistenceTestHelpers levelDBPersistence];
-}
-
-- (void)tearDown {
- _db = nil;
-
- [super tearDown];
-}
-
-- (void)testCommit {
- std::string key = [FSTLevelDBMutationKey keyWithUserID:"user1" batchID:42];
- FSTPBWriteBatch *message = [FSTPBWriteBatch message];
- message.batchId = 42;
-
- // This is a test that shows that committing an empty group does not fail. There are no side
- // effects to verify though.
- FSTWriteGroup *group = [_db startGroupWithAction:@"Empty commit"];
- XCTAssertNoThrow([_db commitGroup:group]);
-
- group = [_db startGroupWithAction:@"Put"];
- [group setMessage:message forKey:key];
-
- std::string value;
- Status status = _db.ptr->Get(ReadOptions(), key, &value);
- XCTAssertTrue(status.IsNotFound());
-
- [_db commitGroup:group];
- status = _db.ptr->Get(ReadOptions(), key, &value);
- XCTAssertTrue(status.ok());
-
- group = [_db startGroupWithAction:@"Delete"];
- [group removeMessageForKey:key];
- status = _db.ptr->Get(ReadOptions(), key, &value);
- XCTAssertTrue(status.ok());
-
- [_db commitGroup:group];
- status = _db.ptr->Get(ReadOptions(), key, &value);
- XCTAssertTrue(status.IsNotFound());
-}
-
-- (void)testDescription {
- std::string key = [FSTLevelDBMutationKey keyWithUserID:"user1" batchID:42];
- FSTPBWriteBatch *message = [FSTPBWriteBatch message];
- message.batchId = 42;
-
- FSTWriteGroup *group = [FSTWriteGroup groupWithAction:@"Action"];
- XCTAssertEqualObjects([group description], @"<FSTWriteGroup for Action: 0 changes (0 bytes):>");
-
- [group setMessage:message forKey:key];
- XCTAssertEqualObjects([group description],
- @"<FSTWriteGroup for Action: 1 changes (2 bytes):\n"
- " - Put [mutation: userID=user1 batchID=42] (2 bytes)>");
-
- [group removeMessageForKey:key];
- XCTAssertEqualObjects([group description],
- @"<FSTWriteGroup for Action: 2 changes (2 bytes):\n"
- " - Put [mutation: userID=user1 batchID=42] (2 bytes)\n"
- " - Delete [mutation: userID=user1 batchID=42]>");
-}
-
-- (void)testCommittingWrongGroupThrows {
- // If you don't create the group through persistence, it should throw.
- FSTWriteGroup *group = [FSTWriteGroup groupWithAction:@"group"];
- XCTAssertThrows([_db commitGroup:group]);
-}
-
-- (void)testCommittingTwiceThrows {
- FSTWriteGroup *group = [_db startGroupWithAction:@"group"];
- [_db commitGroup:group];
- XCTAssertThrows([_db commitGroup:group]);
-}
-
-- (void)testNestingGroupsThrows {
- [_db startGroupWithAction:@"group1"];
- XCTAssertThrows([_db startGroupWithAction:@"group2"]);
-}
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/Firestore/Example/Tests/Local/StringViewTests.mm b/Firestore/Example/Tests/Local/StringViewTests.mm
index b30b43d..7c86924 100644
--- a/Firestore/Example/Tests/Local/StringViewTests.mm
+++ b/Firestore/Example/Tests/Local/StringViewTests.mm
@@ -17,7 +17,8 @@
#import "Firestore/Source/Local/StringView.h"
#import <XCTest/XCTest.h>
-#include <leveldb/slice.h>
+
+#include "leveldb/slice.h"
using Firestore::StringView;
diff --git a/Firestore/Example/Tests/Model/FSTDatabaseIDTests.m b/Firestore/Example/Tests/Model/FSTDatabaseIDTests.m
deleted file mode 100644
index cb1b19d..0000000
--- a/Firestore/Example/Tests/Model/FSTDatabaseIDTests.m
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright 2017 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 "Firestore/Source/Model/FSTDatabaseID.h"
-
-#import <XCTest/XCTest.h>
-
-NS_ASSUME_NONNULL_BEGIN
-
-@interface FSTDatabaseIDTests : XCTestCase
-@end
-
-@implementation FSTDatabaseIDTests
-
-- (void)testConstructor {
- FSTDatabaseID *databaseID = [FSTDatabaseID databaseIDWithProject:@"p" database:@"d"];
- XCTAssertEqualObjects(databaseID.projectID, @"p");
- XCTAssertEqualObjects(databaseID.databaseID, @"d");
- XCTAssertFalse([databaseID isDefaultDatabase]);
-}
-
-- (void)testDefaultDatabase {
- FSTDatabaseID *databaseID =
- [FSTDatabaseID databaseIDWithProject:@"p" database:kDefaultDatabaseID];
- XCTAssertEqualObjects(databaseID.projectID, @"p");
- XCTAssertEqualObjects(databaseID.databaseID, @"(default)");
- XCTAssertTrue([databaseID isDefaultDatabase]);
-}
-
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/Firestore/Example/Tests/Model/FSTDocumentKeyTests.m b/Firestore/Example/Tests/Model/FSTDocumentKeyTests.mm
index d66ee73..5e465f7 100644
--- a/Firestore/Example/Tests/Model/FSTDocumentKeyTests.m
+++ b/Firestore/Example/Tests/Model/FSTDocumentKeyTests.mm
@@ -18,7 +18,11 @@
#import <XCTest/XCTest.h>
-#import "Firestore/Source/Model/FSTPath.h"
+#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+#include "Firestore/core/src/firebase/firestore/model/resource_path.h"
+
+using firebase::firestore::model::DocumentKey;
+using firebase::firestore::model::ResourcePath;
NS_ASSUME_NONNULL_BEGIN
@@ -28,23 +32,22 @@ NS_ASSUME_NONNULL_BEGIN
@implementation FSTDocumentKeyTests
- (void)testConstructor {
- FSTResourcePath *path =
- [FSTResourcePath pathWithSegments:@[ @"rooms", @"firestore", @"messages", @"1" ]];
+ ResourcePath path{"rooms", "firestore", "messages", "1"};
FSTDocumentKey *key = [FSTDocumentKey keyWithPath:path];
XCTAssertEqual(path, key.path);
}
- (void)testComparison {
- FSTDocumentKey *key1 = [FSTDocumentKey keyWithSegments:@[ @"a", @"b", @"c", @"d" ]];
- FSTDocumentKey *key2 = [FSTDocumentKey keyWithSegments:@[ @"a", @"b", @"c", @"d" ]];
- FSTDocumentKey *key3 = [FSTDocumentKey keyWithSegments:@[ @"x", @"y", @"z", @"w" ]];
+ FSTDocumentKey *key1 = [FSTDocumentKey keyWithSegments:{"a", "b", "c", "d"}];
+ FSTDocumentKey *key2 = [FSTDocumentKey keyWithSegments:{"a", "b", "c", "d"}];
+ FSTDocumentKey *key3 = [FSTDocumentKey keyWithSegments:{"x", "y", "z", "w"}];
XCTAssertTrue([key1 isEqualToKey:key2]);
XCTAssertFalse([key1 isEqualToKey:key3]);
- FSTDocumentKey *empty = [FSTDocumentKey keyWithSegments:@[]];
- FSTDocumentKey *a = [FSTDocumentKey keyWithSegments:@[ @"a", @"a" ]];
- FSTDocumentKey *b = [FSTDocumentKey keyWithSegments:@[ @"b", @"b" ]];
- FSTDocumentKey *ab = [FSTDocumentKey keyWithSegments:@[ @"a", @"a", @"b", @"b" ]];
+ FSTDocumentKey *empty = [FSTDocumentKey keyWithSegments:{}];
+ FSTDocumentKey *a = [FSTDocumentKey keyWithSegments:{"a", "a"}];
+ FSTDocumentKey *b = [FSTDocumentKey keyWithSegments:{"b", "b"}];
+ FSTDocumentKey *ab = [FSTDocumentKey keyWithSegments:{"a", "a", "b", "b"}];
XCTAssertEqual(NSOrderedAscending, [empty compare:a]);
XCTAssertEqual(NSOrderedAscending, [a compare:b]);
@@ -55,6 +58,15 @@ NS_ASSUME_NONNULL_BEGIN
XCTAssertEqual(NSOrderedDescending, [ab compare:a]);
}
+- (void)testConverter {
+ const ResourcePath path{"rooms", "firestore", "messages", "1"};
+ FSTDocumentKey *objcKey = [FSTDocumentKey keyWithPath:path];
+ XCTAssertEqualObjects(objcKey, (FSTDocumentKey *)(DocumentKey{objcKey}));
+
+ DocumentKey cpp_key{path};
+ XCTAssertEqual(cpp_key, DocumentKey{(FSTDocumentKey *)(cpp_key)});
+}
+
@end
NS_ASSUME_NONNULL_END
diff --git a/Firestore/Example/Tests/Model/FSTDocumentSetTests.m b/Firestore/Example/Tests/Model/FSTDocumentSetTests.mm
index bf6cd21..65c4ddf 100644
--- a/Firestore/Example/Tests/Model/FSTDocumentSetTests.m
+++ b/Firestore/Example/Tests/Model/FSTDocumentSetTests.mm
@@ -37,10 +37,10 @@ NS_ASSUME_NONNULL_BEGIN
- (void)setUp {
[super setUp];
- _comp = FSTTestDocComparator(@"sort");
- _doc1 = FSTTestDoc(@"docs/1", 0, @{ @"sort" : @2 }, NO);
- _doc2 = FSTTestDoc(@"docs/2", 0, @{ @"sort" : @3 }, NO);
- _doc3 = FSTTestDoc(@"docs/3", 0, @{ @"sort" : @1 }, NO);
+ _comp = FSTTestDocComparator("sort");
+ _doc1 = FSTTestDoc("docs/1", 0, @{ @"sort" : @2 }, NO);
+ _doc2 = FSTTestDoc("docs/2", 0, @{ @"sort" : @3 }, NO);
+ _doc3 = FSTTestDoc("docs/3", 0, @{ @"sort" : @1 }, NO);
}
- (void)testCount {
@@ -79,14 +79,6 @@ NS_ASSUME_NONNULL_BEGIN
XCTAssertEqualObjects([[set documentEnumerator] allObjects], (@[ _doc3, _doc1, _doc2 ]));
}
-- (void)testPredecessorDocumentForKey {
- FSTDocumentSet *set = FSTTestDocSet(_comp, @[ _doc1, _doc2, _doc3 ]);
-
- XCTAssertNil([set predecessorDocumentForKey:_doc3.key]);
- XCTAssertEqualObjects([set predecessorDocumentForKey:_doc1.key], _doc3);
- XCTAssertEqualObjects([set predecessorDocumentForKey:_doc2.key], _doc1);
-}
-
- (void)testDeletes {
FSTDocumentSet *set = FSTTestDocSet(_comp, @[ _doc1, _doc2, _doc3 ]);
@@ -105,7 +97,7 @@ NS_ASSUME_NONNULL_BEGIN
- (void)testUpdates {
FSTDocumentSet *set = FSTTestDocSet(_comp, @[ _doc1, _doc2, _doc3 ]);
- FSTDocument *doc2Prime = FSTTestDoc(@"docs/2", 0, @{ @"sort" : @9 }, NO);
+ FSTDocument *doc2Prime = FSTTestDoc("docs/2", 0, @{ @"sort" : @9 }, NO);
set = [set documentSetByAddingDocument:doc2Prime];
XCTAssertEqual([set count], 3);
@@ -114,7 +106,7 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)testAddsDocsWithEqualComparisonValues {
- FSTDocument *doc4 = FSTTestDoc(@"docs/4", 0, @{ @"sort" : @2 }, NO);
+ FSTDocument *doc4 = FSTTestDoc("docs/4", 0, @{ @"sort" : @2 }, NO);
FSTDocumentSet *set = FSTTestDocSet(_comp, @[ _doc1, doc4 ]);
XCTAssertEqualObjects([[set documentEnumerator] allObjects], (@[ _doc1, doc4 ]));
diff --git a/Firestore/Example/Tests/Model/FSTDocumentTests.m b/Firestore/Example/Tests/Model/FSTDocumentTests.m
deleted file mode 100644
index e56ab34..0000000
--- a/Firestore/Example/Tests/Model/FSTDocumentTests.m
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright 2017 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 "Firestore/Source/Model/FSTDocument.h"
-
-#import <XCTest/XCTest.h>
-
-#import "Firestore/Source/Core/FSTSnapshotVersion.h"
-#import "Firestore/Source/Model/FSTDocumentKey.h"
-#import "Firestore/Source/Model/FSTFieldValue.h"
-#import "Firestore/Source/Model/FSTPath.h"
-
-#import "Firestore/Example/Tests/Util/FSTHelpers.h"
-
-NS_ASSUME_NONNULL_BEGIN
-
-@interface FSTDocumentTests : XCTestCase
-@end
-
-@implementation FSTDocumentTests
-
-- (void)testConstructor {
- FSTDocumentKey *key = [FSTDocumentKey keyWithPathString:@"messages/first"];
- FSTSnapshotVersion *version = FSTTestVersion(1);
- FSTObjectValue *data = FSTTestObjectValue(@{ @"a" : @1 });
- FSTDocument *doc =
- [FSTDocument documentWithData:data key:key version:version hasLocalMutations:NO];
-
- XCTAssertEqualObjects(doc.key, [FSTDocumentKey keyWithPathString:@"messages/first"]);
- XCTAssertEqualObjects(doc.version, version);
- XCTAssertEqualObjects(doc.data, data);
- XCTAssertEqual(doc.hasLocalMutations, NO);
-}
-
-- (void)testExtractsFields {
- FSTDocumentKey *key = [FSTDocumentKey keyWithPathString:@"rooms/eros"];
- FSTSnapshotVersion *version = FSTTestVersion(1);
- FSTObjectValue *data = FSTTestObjectValue(@{
- @"desc" : @"Discuss all the project related stuff",
- @"owner" : @{@"name" : @"Jonny", @"title" : @"scallywag"}
- });
- FSTDocument *doc =
- [FSTDocument documentWithData:data key:key version:version hasLocalMutations:NO];
-
- XCTAssertEqualObjects([doc fieldForPath:FSTTestFieldPath(@"desc")],
- [FSTStringValue stringValue:@"Discuss all the project related stuff"]);
- XCTAssertEqualObjects([doc fieldForPath:FSTTestFieldPath(@"owner.title")],
- [FSTStringValue stringValue:@"scallywag"]);
-}
-
-- (void)testIsEqual {
- FSTDocumentKey *key1 = [FSTDocumentKey keyWithPathString:@"messages/first"];
- FSTDocumentKey *key2 = [FSTDocumentKey keyWithPathString:@"messages/second"];
- FSTObjectValue *data1 = FSTTestObjectValue(@{ @"a" : @1 });
- FSTObjectValue *data2 = FSTTestObjectValue(@{ @"b" : @1 });
- FSTSnapshotVersion *version1 = FSTTestVersion(1);
-
- FSTDocument *doc1 =
- [FSTDocument documentWithData:data1 key:key1 version:version1 hasLocalMutations:NO];
- FSTDocument *doc2 =
- [FSTDocument documentWithData:data1 key:key1 version:version1 hasLocalMutations:NO];
-
- XCTAssertEqualObjects(doc1, doc2);
- XCTAssertEqualObjects(
- doc1, [FSTDocument documentWithData:FSTTestObjectValue(
- @{ @"a" : @1 })
- key:[FSTDocumentKey keyWithPathString:@"messages/first"]
- version:version1
- hasLocalMutations:NO]);
-
- FSTSnapshotVersion *version2 = FSTTestVersion(2);
- XCTAssertNotEqualObjects(
- doc1, [FSTDocument documentWithData:data2 key:key1 version:version1 hasLocalMutations:NO]);
- XCTAssertNotEqualObjects(
- doc1, [FSTDocument documentWithData:data1 key:key2 version:version1 hasLocalMutations:NO]);
- XCTAssertNotEqualObjects(
- doc1, [FSTDocument documentWithData:data1 key:key1 version:version2 hasLocalMutations:NO]);
- XCTAssertNotEqualObjects(
- doc1, [FSTDocument documentWithData:data1 key:key1 version:version1 hasLocalMutations:YES]);
-
- XCTAssertEqualObjects(
- [FSTDocument documentWithData:data1 key:key1 version:version1 hasLocalMutations:YES],
- [FSTDocument documentWithData:data1 key:key1 version:version1 hasLocalMutations:5]);
-}
-
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/Firestore/Example/Tests/Model/FSTDocumentTests.mm b/Firestore/Example/Tests/Model/FSTDocumentTests.mm
new file mode 100644
index 0000000..24858c5
--- /dev/null
+++ b/Firestore/Example/Tests/Model/FSTDocumentTests.mm
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2017 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 "Firestore/Source/Model/FSTDocument.h"
+
+#import <XCTest/XCTest.h>
+
+#import "Firestore/Source/Core/FSTSnapshotVersion.h"
+#import "Firestore/Source/Model/FSTFieldValue.h"
+
+#import "Firestore/Example/Tests/Util/FSTHelpers.h"
+
+#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+#include "Firestore/core/test/firebase/firestore/testutil/testutil.h"
+
+namespace testutil = firebase::firestore::testutil;
+using firebase::firestore::model::DocumentKey;
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface FSTDocumentTests : XCTestCase
+@end
+
+@implementation FSTDocumentTests
+
+- (void)testConstructor {
+ DocumentKey key = testutil::Key("messages/first");
+ FSTSnapshotVersion *version = FSTTestVersion(1);
+ FSTObjectValue *data = FSTTestObjectValue(@{ @"a" : @1 });
+ FSTDocument *doc =
+ [FSTDocument documentWithData:data key:key version:version hasLocalMutations:NO];
+
+ XCTAssertEqualObjects(doc.key, FSTTestDocKey(@"messages/first"));
+ XCTAssertEqualObjects(doc.version, version);
+ XCTAssertEqualObjects(doc.data, data);
+ XCTAssertEqual(doc.hasLocalMutations, NO);
+}
+
+- (void)testExtractsFields {
+ DocumentKey key = testutil::Key("rooms/eros");
+ FSTSnapshotVersion *version = FSTTestVersion(1);
+ FSTObjectValue *data = FSTTestObjectValue(@{
+ @"desc" : @"Discuss all the project related stuff",
+ @"owner" : @{@"name" : @"Jonny", @"title" : @"scallywag"}
+ });
+ FSTDocument *doc =
+ [FSTDocument documentWithData:data key:key version:version hasLocalMutations:NO];
+
+ XCTAssertEqualObjects([doc fieldForPath:testutil::Field("desc")],
+ [FSTStringValue stringValue:@"Discuss all the project related stuff"]);
+ XCTAssertEqualObjects([doc fieldForPath:testutil::Field("owner.title")],
+ [FSTStringValue stringValue:@"scallywag"]);
+}
+
+- (void)testIsEqual {
+ XCTAssertEqualObjects(FSTTestDoc("messages/first", 1,
+ @{ @"a" : @1 }, NO),
+ FSTTestDoc("messages/first", 1,
+ @{ @"a" : @1 }, NO));
+ XCTAssertNotEqualObjects(FSTTestDoc("messages/first", 1,
+ @{ @"a" : @1 }, NO),
+ FSTTestDoc("messages/first", 1,
+ @{ @"b" : @1 }, NO));
+ XCTAssertNotEqualObjects(FSTTestDoc("messages/first", 1,
+ @{ @"a" : @1 }, NO),
+ FSTTestDoc("messages/second", 1,
+ @{ @"b" : @1 }, NO));
+ XCTAssertNotEqualObjects(FSTTestDoc("messages/first", 1,
+ @{ @"a" : @1 }, NO),
+ FSTTestDoc("messages/first", 2,
+ @{ @"a" : @1 }, NO));
+ XCTAssertNotEqualObjects(FSTTestDoc("messages/first", 1,
+ @{ @"a" : @1 }, NO),
+ FSTTestDoc("messages/first", 1,
+ @{ @"a" : @1 }, YES));
+
+ XCTAssertEqualObjects(FSTTestDoc("messages/first", 1,
+ @{ @"a" : @1 }, YES),
+ FSTTestDoc("messages/first", 1,
+ @{ @"a" : @1 }, 5));
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firestore/Example/Tests/Model/FSTFieldValueTests.m b/Firestore/Example/Tests/Model/FSTFieldValueTests.mm
index acf95f0..d16a01d 100644
--- a/Firestore/Example/Tests/Model/FSTFieldValueTests.m
+++ b/Firestore/Example/Tests/Model/FSTFieldValueTests.mm
@@ -16,18 +16,24 @@
#import "Firestore/Source/Model/FSTFieldValue.h"
+#import <FirebaseFirestore/FIRGeoPoint.h>
+#import <FirebaseFirestore/FIRTimestamp.h>
#import <XCTest/XCTest.h>
-#import "FirebaseFirestore/FIRGeoPoint.h"
#import "Firestore/Source/API/FIRFirestore+Internal.h"
#import "Firestore/Source/API/FSTUserDataConverter.h"
-#import "Firestore/Source/Core/FSTTimestamp.h"
-#import "Firestore/Source/Model/FSTDatabaseID.h"
-#import "Firestore/Source/Model/FSTFieldValue.h"
-#import "Firestore/Source/Model/FSTPath.h"
+#import "Firestore/Example/Tests/API/FSTAPIHelpers.h"
#import "Firestore/Example/Tests/Util/FSTHelpers.h"
+#include "Firestore/core/src/firebase/firestore/model/database_id.h"
+#include "Firestore/core/src/firebase/firestore/util/string_apple.h"
+#include "Firestore/core/test/firebase/firestore/testutil/testutil.h"
+
+namespace testutil = firebase::firestore::testutil;
+namespace util = firebase::firestore::util;
+using firebase::firestore::model::DatabaseId;
+
/** Helper to wrap the values in a set of equality groups using FSTTestFieldValue(). */
NSArray *FSTWrapGroups(NSArray *groups) {
NSMutableArray *wrapped = [NSMutableArray array];
@@ -39,10 +45,12 @@ NSArray *FSTWrapGroups(NSArray *groups) {
// strings that can be used instead.
if ([value isEqual:@"server-timestamp-1"]) {
wrappedValue = [FSTServerTimestampValue
- serverTimestampValueWithLocalWriteTime:FSTTestTimestamp(2016, 5, 20, 10, 20, 0)];
+ serverTimestampValueWithLocalWriteTime:FSTTestTimestamp(2016, 5, 20, 10, 20, 0)
+ previousValue:nil];
} else if ([value isEqual:@"server-timestamp-2"]) {
wrappedValue = [FSTServerTimestampValue
- serverTimestampValueWithLocalWriteTime:FSTTestTimestamp(2016, 10, 21, 15, 32, 0)];
+ serverTimestampValueWithLocalWriteTime:FSTTestTimestamp(2016, 10, 21, 15, 32, 0)
+ previousValue:nil];
} else if ([value isKindOfClass:[FSTDocumentKeyReference class]]) {
// We directly convert these here so that the databaseIDs can be different.
FSTDocumentKeyReference *reference = (FSTDocumentKeyReference *)value;
@@ -218,10 +226,8 @@ union DoubleBits {
for (id value in values) {
FSTFieldValue *wrapped = FSTTestFieldValue(value);
XCTAssertEqualObjects([wrapped class], [FSTTimestampValue class]);
- XCTAssertEqualObjects([wrapped value], value);
-
- XCTAssertEqualObjects(((FSTTimestampValue *)wrapped).internalValue,
- [FSTTimestamp timestampWithDate:value]);
+ XCTAssertEqualObjects([[wrapped value] class], [FIRTimestamp class]);
+ XCTAssertEqualObjects([wrapped value], [FIRTimestamp timestampWithDate:value]);
}
}
@@ -246,14 +252,15 @@ union DoubleBits {
- (void)testWrapResourceNames {
NSArray *values = @[
- FSTTestRef(@"project", kDefaultDatabaseID, @"foo/bar"),
- FSTTestRef(@"project", kDefaultDatabaseID, @"foo/baz")
+ FSTTestRef("project", DatabaseId::kDefault, @"foo/bar"),
+ FSTTestRef("project", DatabaseId::kDefault, @"foo/baz")
];
for (FSTDocumentKeyReference *value in values) {
FSTFieldValue *wrapped = FSTTestFieldValue(value);
XCTAssertEqualObjects([wrapped class], [FSTReferenceValue class]);
XCTAssertEqualObjects([wrapped value], value.key);
- XCTAssertEqualObjects(((FSTDatabaseID *)wrapped).databaseID, value.databaseID);
+ XCTAssertTrue(*((FSTReferenceValue *)wrapped).databaseID ==
+ *(const DatabaseId *)(value.databaseID));
}
}
@@ -292,20 +299,20 @@ union DoubleBits {
FSTObjectValue *obj = FSTTestObjectValue(@{ @"foo" : @{@"a" : @YES, @"b" : @"string"} });
FSTAssertIsKindOfClass(obj, FSTObjectValue);
- FSTAssertIsKindOfClass([obj valueForPath:FSTTestFieldPath(@"foo")], FSTObjectValue);
- XCTAssertEqualObjects([obj valueForPath:FSTTestFieldPath(@"foo.a")], [FSTBooleanValue trueValue]);
- XCTAssertEqualObjects([obj valueForPath:FSTTestFieldPath(@"foo.b")],
+ FSTAssertIsKindOfClass([obj valueForPath:testutil::Field("foo")], FSTObjectValue);
+ XCTAssertEqualObjects([obj valueForPath:testutil::Field("foo.a")], [FSTBooleanValue trueValue]);
+ XCTAssertEqualObjects([obj valueForPath:testutil::Field("foo.b")],
[FSTStringValue stringValue:@"string"]);
- XCTAssertNil([obj valueForPath:FSTTestFieldPath(@"foo.a.b")]);
- XCTAssertNil([obj valueForPath:FSTTestFieldPath(@"bar")]);
- XCTAssertNil([obj valueForPath:FSTTestFieldPath(@"bar.a")]);
+ XCTAssertNil([obj valueForPath:testutil::Field("foo.a.b")]);
+ XCTAssertNil([obj valueForPath:testutil::Field("bar")]);
+ XCTAssertNil([obj valueForPath:testutil::Field("bar.a")]);
}
- (void)testOverwritesExistingFields {
FSTObjectValue *old = FSTTestObjectValue(@{@"a" : @"old"});
FSTObjectValue *mod =
- [old objectBySettingValue:FSTTestFieldValue(@"mod") forPath:FSTTestFieldPath(@"a")];
+ [old objectBySettingValue:FSTTestFieldValue(@"mod") forPath:testutil::Field("a")];
// Should return a new object, leaving the old one unmodified.
XCTAssertNotEqual(old, mod);
@@ -316,13 +323,13 @@ union DoubleBits {
- (void)testAddsNewFields {
FSTObjectValue *empty = [FSTObjectValue objectValue];
FSTObjectValue *mod =
- [empty objectBySettingValue:FSTTestFieldValue(@"mod") forPath:FSTTestFieldPath(@"a")];
+ [empty objectBySettingValue:FSTTestFieldValue(@"mod") forPath:testutil::Field("a")];
XCTAssertNotEqual(empty, mod);
XCTAssertEqualObjects(empty, FSTTestFieldValue(@{}));
XCTAssertEqualObjects(mod, FSTTestFieldValue(@{@"a" : @"mod"}));
FSTObjectValue *old = mod;
- mod = [old objectBySettingValue:FSTTestFieldValue(@1) forPath:FSTTestFieldPath(@"b")];
+ mod = [old objectBySettingValue:FSTTestFieldValue(@1) forPath:testutil::Field("b")];
XCTAssertNotEqual(old, mod);
XCTAssertEqualObjects(old, FSTTestFieldValue(@{@"a" : @"mod"}));
XCTAssertEqualObjects(mod, FSTTestFieldValue(@{ @"a" : @"mod", @"b" : @1 }));
@@ -331,7 +338,7 @@ union DoubleBits {
- (void)testImplicitlyCreatesObjects {
FSTObjectValue *old = FSTTestObjectValue(@{@"a" : @"old"});
FSTObjectValue *mod =
- [old objectBySettingValue:FSTTestFieldValue(@"mod") forPath:FSTTestFieldPath(@"b.c.d")];
+ [old objectBySettingValue:FSTTestFieldValue(@"mod") forPath:testutil::Field("b.c.d")];
XCTAssertNotEqual(old, mod);
XCTAssertEqualObjects(old, FSTTestFieldValue(@{@"a" : @"old"}));
XCTAssertEqualObjects(mod, FSTTestFieldValue(
@@ -342,7 +349,7 @@ union DoubleBits {
- (void)testCanOverwritePrimitivesWithObjects {
FSTObjectValue *old = FSTTestObjectValue(@{ @"a" : @{@"b" : @"old"} });
FSTObjectValue *mod =
- [old objectBySettingValue:FSTTestFieldValue(@{@"b" : @"mod"}) forPath:FSTTestFieldPath(@"a")];
+ [old objectBySettingValue:FSTTestFieldValue(@{@"b" : @"mod"}) forPath:testutil::Field("a")];
XCTAssertNotEqual(old, mod);
XCTAssertEqualObjects(old, FSTTestFieldValue(@{ @"a" : @{@"b" : @"old"} }));
XCTAssertEqualObjects(mod, FSTTestFieldValue(@{ @"a" : @{@"b" : @"mod"} }));
@@ -351,7 +358,7 @@ union DoubleBits {
- (void)testAddsToNestedObjects {
FSTObjectValue *old = FSTTestObjectValue(@{ @"a" : @{@"b" : @"old"} });
FSTObjectValue *mod =
- [old objectBySettingValue:FSTTestFieldValue(@"mod") forPath:FSTTestFieldPath(@"a.c")];
+ [old objectBySettingValue:FSTTestFieldValue(@"mod") forPath:testutil::Field("a.c")];
XCTAssertNotEqual(old, mod);
XCTAssertEqualObjects(old, FSTTestFieldValue(@{ @"a" : @{@"b" : @"old"} }));
XCTAssertEqualObjects(mod, FSTTestFieldValue(@{ @"a" : @{@"b" : @"old", @"c" : @"mod"} }));
@@ -359,12 +366,12 @@ union DoubleBits {
- (void)testDeletesKeys {
FSTObjectValue *old = FSTTestObjectValue(@{ @"a" : @1, @"b" : @2 });
- FSTObjectValue *mod = [old objectByDeletingPath:FSTTestFieldPath(@"a")];
+ FSTObjectValue *mod = [old objectByDeletingPath:testutil::Field("a")];
XCTAssertNotEqual(old, mod);
XCTAssertEqualObjects(old, FSTTestFieldValue(@{ @"a" : @1, @"b" : @2 }));
XCTAssertEqualObjects(mod, FSTTestFieldValue(@{ @"b" : @2 }));
- FSTObjectValue *empty = [mod objectByDeletingPath:FSTTestFieldPath(@"b")];
+ FSTObjectValue *empty = [mod objectByDeletingPath:testutil::Field("b")];
XCTAssertNotEqual(mod, empty);
XCTAssertEqualObjects(mod, FSTTestFieldValue(@{ @"b" : @2 }));
XCTAssertEqualObjects(empty, FSTTestFieldValue(@{}));
@@ -372,15 +379,15 @@ union DoubleBits {
- (void)testDeletesHandleMissingKeys {
FSTObjectValue *old = FSTTestObjectValue(@{ @"a" : @{@"b" : @1, @"c" : @2} });
- FSTObjectValue *mod = [old objectByDeletingPath:FSTTestFieldPath(@"b")];
+ FSTObjectValue *mod = [old objectByDeletingPath:testutil::Field("b")];
XCTAssertEqualObjects(old, mod);
XCTAssertEqualObjects(mod, FSTTestFieldValue(@{ @"a" : @{@"b" : @1, @"c" : @2} }));
- mod = [old objectByDeletingPath:FSTTestFieldPath(@"a.d")];
+ mod = [old objectByDeletingPath:testutil::Field("a.d")];
XCTAssertEqualObjects(old, mod);
XCTAssertEqualObjects(mod, FSTTestFieldValue(@{ @"a" : @{@"b" : @1, @"c" : @2} }));
- mod = [old objectByDeletingPath:FSTTestFieldPath(@"a.b.c")];
+ mod = [old objectByDeletingPath:testutil::Field("a.b.c")];
XCTAssertEqualObjects(old, mod);
XCTAssertEqualObjects(mod, FSTTestFieldValue(@{ @"a" : @{@"b" : @1, @"c" : @2} }));
}
@@ -388,19 +395,19 @@ union DoubleBits {
- (void)testDeletesNestedKeys {
FSTObjectValue *old = FSTTestObjectValue(
@{ @"a" : @{@"b" : @1, @"c" : @{@"d" : @2, @"e" : @3}} });
- FSTObjectValue *mod = [old objectByDeletingPath:FSTTestFieldPath(@"a.c.d")];
+ FSTObjectValue *mod = [old objectByDeletingPath:testutil::Field("a.c.d")];
XCTAssertNotEqual(old, mod);
XCTAssertEqualObjects(old, FSTTestFieldValue(
@{ @"a" : @{@"b" : @1, @"c" : @{@"d" : @2, @"e" : @3}} }));
XCTAssertEqualObjects(mod, FSTTestFieldValue(@{ @"a" : @{@"b" : @1, @"c" : @{@"e" : @3}} }));
old = mod;
- mod = [old objectByDeletingPath:FSTTestFieldPath(@"a.c")];
+ mod = [old objectByDeletingPath:testutil::Field("a.c")];
XCTAssertEqualObjects(old, FSTTestFieldValue(@{ @"a" : @{@"b" : @1, @"c" : @{@"e" : @3}} }));
XCTAssertEqualObjects(mod, FSTTestFieldValue(@{ @"a" : @{@"b" : @1} }));
old = mod;
- mod = [old objectByDeletingPath:FSTTestFieldPath(@"a")];
+ mod = [old objectByDeletingPath:testutil::Field("a")];
XCTAssertEqualObjects(old, FSTTestFieldValue(@{ @"a" : @{@"b" : @1} }));
XCTAssertEqualObjects(mod, FSTTestFieldValue(@{}));
}
@@ -414,6 +421,7 @@ union DoubleBits {
}
- (void)testValueEquality {
+ DatabaseId database_id = DatabaseId("project", DatabaseId::kDefault);
NSArray *groups = @[
@[ FSTTestFieldValue(@YES), [FSTBooleanValue booleanValue:YES] ],
@[ FSTTestFieldValue(@NO), [FSTBooleanValue booleanValue:NO] ],
@@ -435,30 +443,31 @@ union DoubleBits {
@[ FSTTestFieldValue(@"\u00e9a") ], // latin small letter e with acute accent
@[
FSTTestFieldValue(date1),
- [FSTTimestampValue timestampValue:[FSTTimestamp timestampWithDate:date1]]
+ [FSTTimestampValue timestampValue:[FIRTimestamp timestampWithDate:date1]]
],
@[ FSTTestFieldValue(date2) ],
@[
// NOTE: ServerTimestampValues can't be parsed via FSTTestFieldValue().
[FSTServerTimestampValue
- serverTimestampValueWithLocalWriteTime:[FSTTimestamp timestampWithDate:date1]],
+ serverTimestampValueWithLocalWriteTime:[FIRTimestamp timestampWithDate:date1]
+ previousValue:nil],
[FSTServerTimestampValue
- serverTimestampValueWithLocalWriteTime:[FSTTimestamp timestampWithDate:date1]]
+ serverTimestampValueWithLocalWriteTime:[FIRTimestamp timestampWithDate:date1]
+ previousValue:nil]
],
@[ [FSTServerTimestampValue
- serverTimestampValueWithLocalWriteTime:[FSTTimestamp timestampWithDate:date2]] ],
+ serverTimestampValueWithLocalWriteTime:[FIRTimestamp timestampWithDate:date2]
+ previousValue:nil] ],
@[
FSTTestFieldValue(FSTTestGeoPoint(0, 1)),
[FSTGeoPointValue geoPointValue:FSTTestGeoPoint(0, 1)]
],
@[ FSTTestFieldValue(FSTTestGeoPoint(1, 0)) ],
@[
- [FSTReferenceValue referenceValue:FSTTestDocKey(@"coll/doc1")
- databaseID:[FSTDatabaseID databaseIDWithProject:@"project"
- database:kDefaultDatabaseID]],
- FSTTestFieldValue(FSTTestRef(@"project", kDefaultDatabaseID, @"coll/doc1"))
+ [FSTReferenceValue referenceValue:FSTTestDocKey(@"coll/doc1") databaseID:&database_id],
+ FSTTestFieldValue(FSTTestRef("project", DatabaseId::kDefault, @"coll/doc1"))
],
- @[ FSTTestRef(@"project", @"(default)", @"coll/doc2") ],
+ @[ FSTTestRef("project", "(default)", @"coll/doc2") ],
@[ FSTTestFieldValue(@[ @"foo", @"bar" ]), FSTTestFieldValue(@[ @"foo", @"bar" ]) ],
@[ FSTTestFieldValue(@[ @"foo", @"bar", @"baz" ]) ], @[ FSTTestFieldValue(@[ @"foo" ]) ],
@[
@@ -519,9 +528,9 @@ union DoubleBits {
@[ FSTTestData(0, 1, 2, 4, 3, -1) ], @[ FSTTestData(255, -1) ],
// resource names
- @[ FSTTestRef(@"p1", @"d1", @"c1/doc1") ], @[ FSTTestRef(@"p1", @"d1", @"c1/doc2") ],
- @[ FSTTestRef(@"p1", @"d1", @"c10/doc1") ], @[ FSTTestRef(@"p1", @"d1", @"c2/doc1") ],
- @[ FSTTestRef(@"p1", @"d2", @"c1/doc1") ], @[ FSTTestRef(@"p2", @"d1", @"c1/doc1") ],
+ @[ FSTTestRef("p1", "d1", @"c1/doc1") ], @[ FSTTestRef("p1", "d1", @"c1/doc2") ],
+ @[ FSTTestRef("p1", "d1", @"c10/doc1") ], @[ FSTTestRef("p1", "d1", @"c2/doc1") ],
+ @[ FSTTestRef("p1", "d2", @"c1/doc1") ], @[ FSTTestRef("p2", "d1", @"c1/doc1") ],
// Geo points
@[ FSTTestGeoPoint(-90, -180) ], @[ FSTTestGeoPoint(-90, 0) ], @[ FSTTestGeoPoint(-90, 180) ],
@@ -560,16 +569,14 @@ union DoubleBits {
FSTObjectValue *value = FSTTestObjectValue(input);
id output = [value value];
{
- XCTAssertTrue([output[@"array"][1] isKindOfClass:[NSDate class]]);
- NSDate *actual = output[@"array"][1];
- XCTAssertEqualWithAccuracy(date.timeIntervalSince1970, actual.timeIntervalSince1970,
- 0.000000001);
+ XCTAssertTrue([output[@"array"][1] isKindOfClass:[FIRTimestamp class]]);
+ FIRTimestamp *actual = output[@"array"][1];
+ XCTAssertEqualObjects([FIRTimestamp timestampWithDate:date], actual);
}
{
- XCTAssertTrue([output[@"obj"][@"date"] isKindOfClass:[NSDate class]]);
- NSDate *actual = output[@"obj"][@"date"];
- XCTAssertEqualWithAccuracy(date.timeIntervalSince1970, actual.timeIntervalSince1970,
- 0.000000001);
+ XCTAssertTrue([output[@"obj"][@"date"] isKindOfClass:[FIRTimestamp class]]);
+ FIRTimestamp *actual = output[@"array"][1];
+ XCTAssertEqualObjects([FIRTimestamp timestampWithDate:date], actual);
}
}
diff --git a/Firestore/Example/Tests/Model/FSTMutationTests.m b/Firestore/Example/Tests/Model/FSTMutationTests.m
deleted file mode 100644
index 678755e..0000000
--- a/Firestore/Example/Tests/Model/FSTMutationTests.m
+++ /dev/null
@@ -1,216 +0,0 @@
-/*
- * Copyright 2017 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 "Firestore/Source/Model/FSTMutation.h"
-
-#import <XCTest/XCTest.h>
-
-#import "Firestore/Source/Core/FSTTimestamp.h"
-#import "Firestore/Source/Model/FSTDocument.h"
-#import "Firestore/Source/Model/FSTDocumentKey.h"
-#import "Firestore/Source/Model/FSTFieldValue.h"
-#import "Firestore/Source/Model/FSTPath.h"
-
-#import "Firestore/Example/Tests/Util/FSTHelpers.h"
-
-@interface FSTMutationTests : XCTestCase
-@end
-
-@implementation FSTMutationTests {
- FSTTimestamp *_timestamp;
-}
-
-- (void)setUp {
- _timestamp = [FSTTimestamp timestamp];
-}
-
-- (void)testAppliesSetsToDocuments {
- NSDictionary *docData = @{@"foo" : @"foo-value", @"baz" : @"baz-value"};
- FSTDocument *baseDoc = FSTTestDoc(@"collection/key", 0, docData, NO);
-
- FSTMutation *set = FSTTestSetMutation(@"collection/key", @{@"bar" : @"bar-value"});
- FSTMaybeDocument *setDoc = [set applyTo:baseDoc localWriteTime:_timestamp];
-
- NSDictionary *expectedData = @{@"bar" : @"bar-value"};
- XCTAssertEqualObjects(setDoc, FSTTestDoc(@"collection/key", 0, expectedData, YES));
-}
-
-- (void)testAppliesPatchesToDocuments {
- NSDictionary *docData = @{ @"foo" : @{@"bar" : @"bar-value"}, @"baz" : @"baz-value" };
- FSTDocument *baseDoc = FSTTestDoc(@"collection/key", 0, docData, NO);
-
- FSTMutation *patch =
- FSTTestPatchMutation(@"collection/key", @{@"foo.bar" : @"new-bar-value"}, nil);
- FSTMaybeDocument *patchedDoc = [patch applyTo:baseDoc localWriteTime:_timestamp];
-
- NSDictionary *expectedData = @{ @"foo" : @{@"bar" : @"new-bar-value"}, @"baz" : @"baz-value" };
- XCTAssertEqualObjects(patchedDoc, FSTTestDoc(@"collection/key", 0, expectedData, YES));
-}
-
-- (void)testDeletesValuesFromTheFieldMask {
- NSDictionary *docData = @{ @"foo" : @{@"bar" : @"bar-value", @"baz" : @"baz-value"} };
- FSTDocument *baseDoc = FSTTestDoc(@"collection/key", 0, docData, NO);
-
- FSTDocumentKey *key = [FSTDocumentKey keyWithPathString:@"collection/key"];
- FSTFieldMask *mask = [[FSTFieldMask alloc] initWithFields:@[ FSTTestFieldPath(@"foo.bar") ]];
- FSTMutation *patch = [[FSTPatchMutation alloc] initWithKey:key
- fieldMask:mask
- value:[FSTObjectValue objectValue]
- precondition:[FSTPrecondition none]];
- FSTMaybeDocument *patchedDoc = [patch applyTo:baseDoc localWriteTime:_timestamp];
-
- NSDictionary *expectedData = @{ @"foo" : @{@"baz" : @"baz-value"} };
- XCTAssertEqualObjects(patchedDoc, FSTTestDoc(@"collection/key", 0, expectedData, YES));
-}
-
-- (void)testPatchesPrimitiveValue {
- NSDictionary *docData = @{@"foo" : @"foo-value", @"baz" : @"baz-value"};
- FSTDocument *baseDoc = FSTTestDoc(@"collection/key", 0, docData, NO);
-
- FSTMutation *patch =
- FSTTestPatchMutation(@"collection/key", @{@"foo.bar" : @"new-bar-value"}, nil);
- FSTMaybeDocument *patchedDoc = [patch applyTo:baseDoc localWriteTime:_timestamp];
-
- NSDictionary *expectedData = @{ @"foo" : @{@"bar" : @"new-bar-value"}, @"baz" : @"baz-value" };
- XCTAssertEqualObjects(patchedDoc, FSTTestDoc(@"collection/key", 0, expectedData, YES));
-}
-
-- (void)testPatchingDeletedDocumentsDoesNothing {
- FSTMaybeDocument *baseDoc = FSTTestDeletedDoc(@"collection/key", 0);
- FSTMutation *patch = FSTTestPatchMutation(@"collection/key", @{@"foo" : @"bar"}, nil);
- FSTMaybeDocument *patchedDoc = [patch applyTo:baseDoc localWriteTime:_timestamp];
- XCTAssertEqualObjects(patchedDoc, baseDoc);
-}
-
-- (void)testAppliesLocalTransformsToDocuments {
- NSDictionary *docData = @{ @"foo" : @{@"bar" : @"bar-value"}, @"baz" : @"baz-value" };
- FSTDocument *baseDoc = FSTTestDoc(@"collection/key", 0, docData, NO);
-
- FSTMutation *transform = FSTTestTransformMutation(@"collection/key", @[ @"foo.bar" ]);
- FSTMaybeDocument *transformedDoc = [transform applyTo:baseDoc localWriteTime:_timestamp];
-
- // Server timestamps aren't parsed, so we manually insert it.
- FSTObjectValue *expectedData = FSTTestObjectValue(
- @{ @"foo" : @{@"bar" : @"<server-timestamp>"},
- @"baz" : @"baz-value" });
- expectedData =
- [expectedData objectBySettingValue:[FSTServerTimestampValue
- serverTimestampValueWithLocalWriteTime:_timestamp]
- forPath:FSTTestFieldPath(@"foo.bar")];
-
- FSTDocument *expectedDoc = [FSTDocument documentWithData:expectedData
- key:FSTTestDocKey(@"collection/key")
- version:FSTTestVersion(0)
- hasLocalMutations:YES];
-
- XCTAssertEqualObjects(transformedDoc, expectedDoc);
-}
-
-- (void)testAppliesServerAckedTransformsToDocuments {
- NSDictionary *docData = @{ @"foo" : @{@"bar" : @"bar-value"}, @"baz" : @"baz-value" };
- FSTDocument *baseDoc = FSTTestDoc(@"collection/key", 0, docData, NO);
-
- FSTMutation *transform = FSTTestTransformMutation(@"collection/key", @[ @"foo.bar" ]);
-
- FSTMutationResult *mutationResult = [[FSTMutationResult alloc]
- initWithVersion:FSTTestVersion(1)
- transformResults:@[ [FSTTimestampValue timestampValue:_timestamp] ]];
-
- FSTMaybeDocument *transformedDoc =
- [transform applyTo:baseDoc localWriteTime:_timestamp mutationResult:mutationResult];
-
- NSDictionary *expectedData =
- @{ @"foo" : @{@"bar" : _timestamp.approximateDateValue},
- @"baz" : @"baz-value" };
- XCTAssertEqualObjects(transformedDoc, FSTTestDoc(@"collection/key", 0, expectedData, NO));
-}
-
-- (void)testDeleteDeletes {
- NSDictionary *docData = @{@"foo" : @"bar"};
- FSTDocument *baseDoc = FSTTestDoc(@"collection/key", 0, docData, NO);
-
- FSTMutation *mutation = FSTTestDeleteMutation(@"collection/key");
- FSTMaybeDocument *result = [mutation applyTo:baseDoc localWriteTime:_timestamp];
- XCTAssertEqualObjects(result, FSTTestDeletedDoc(@"collection/key", 0));
-}
-
-- (void)testSetWithMutationResult {
- NSDictionary *docData = @{@"foo" : @"bar"};
- FSTDocument *baseDoc = FSTTestDoc(@"collection/key", 0, docData, NO);
-
- FSTMutation *set = FSTTestSetMutation(@"collection/key", @{@"foo" : @"new-bar"});
- FSTMutationResult *mutationResult =
- [[FSTMutationResult alloc] initWithVersion:FSTTestVersion(4) transformResults:nil];
- FSTMaybeDocument *setDoc =
- [set applyTo:baseDoc localWriteTime:_timestamp mutationResult:mutationResult];
-
- NSDictionary *expectedData = @{@"foo" : @"new-bar"};
- XCTAssertEqualObjects(setDoc, FSTTestDoc(@"collection/key", 0, expectedData, NO));
-}
-
-- (void)testPatchWithMutationResult {
- NSDictionary *docData = @{@"foo" : @"bar"};
- FSTDocument *baseDoc = FSTTestDoc(@"collection/key", 0, docData, NO);
-
- FSTMutation *patch = FSTTestPatchMutation(@"collection/key", @{@"foo" : @"new-bar"}, nil);
- FSTMutationResult *mutationResult =
- [[FSTMutationResult alloc] initWithVersion:FSTTestVersion(4) transformResults:nil];
- FSTMaybeDocument *patchedDoc =
- [patch applyTo:baseDoc localWriteTime:_timestamp mutationResult:mutationResult];
-
- NSDictionary *expectedData = @{@"foo" : @"new-bar"};
- XCTAssertEqualObjects(patchedDoc, FSTTestDoc(@"collection/key", 0, expectedData, NO));
-}
-
-#define ASSERT_VERSION_TRANSITION(mutation, base, expected) \
- do { \
- FSTMutationResult *mutationResult = \
- [[FSTMutationResult alloc] initWithVersion:FSTTestVersion(0) transformResults:nil]; \
- FSTMaybeDocument *actual = \
- [mutation applyTo:base localWriteTime:_timestamp mutationResult:mutationResult]; \
- XCTAssertEqualObjects(actual, expected); \
- } while (0);
-
-/**
- * Tests the transition table documented in FSTMutation.h.
- */
-- (void)testTransitions {
- FSTDocument *docV0 = FSTTestDoc(@"collection/key", 0, @{}, NO);
- FSTDeletedDocument *deletedV0 = FSTTestDeletedDoc(@"collection/key", 0);
-
- FSTDocument *docV3 = FSTTestDoc(@"collection/key", 3, @{}, NO);
- FSTDeletedDocument *deletedV3 = FSTTestDeletedDoc(@"collection/key", 3);
-
- FSTMutation *setMutation = FSTTestSetMutation(@"collection/key", @{});
- FSTMutation *patchMutation = FSTTestPatchMutation(@"collection/key", @{}, nil);
- FSTMutation *deleteMutation = FSTTestDeleteMutation(@"collection/key");
-
- ASSERT_VERSION_TRANSITION(setMutation, docV3, docV3);
- ASSERT_VERSION_TRANSITION(setMutation, deletedV3, docV0);
- ASSERT_VERSION_TRANSITION(setMutation, nil, docV0);
-
- ASSERT_VERSION_TRANSITION(patchMutation, docV3, docV3);
- ASSERT_VERSION_TRANSITION(patchMutation, deletedV3, deletedV3);
- ASSERT_VERSION_TRANSITION(patchMutation, nil, nil);
-
- ASSERT_VERSION_TRANSITION(deleteMutation, docV3, deletedV0);
- ASSERT_VERSION_TRANSITION(deleteMutation, deletedV3, deletedV0);
- ASSERT_VERSION_TRANSITION(deleteMutation, nil, deletedV0);
-}
-
-#undef ASSERT_TRANSITION
-
-@end
diff --git a/Firestore/Example/Tests/Model/FSTMutationTests.mm b/Firestore/Example/Tests/Model/FSTMutationTests.mm
new file mode 100644
index 0000000..56bf1c2
--- /dev/null
+++ b/Firestore/Example/Tests/Model/FSTMutationTests.mm
@@ -0,0 +1,292 @@
+/*
+ * Copyright 2017 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 "Firestore/Source/Model/FSTMutation.h"
+
+#import <FirebaseFirestore/FIRFieldValue.h>
+#import <FirebaseFirestore/FIRTimestamp.h>
+#import <XCTest/XCTest.h>
+
+#include <vector>
+
+#import "Firestore/Source/Model/FSTDocument.h"
+#import "Firestore/Source/Model/FSTFieldValue.h"
+
+#import "Firestore/Example/Tests/Util/FSTHelpers.h"
+
+#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+#include "Firestore/core/src/firebase/firestore/model/field_mask.h"
+#include "Firestore/core/src/firebase/firestore/model/field_transform.h"
+#include "Firestore/core/src/firebase/firestore/model/precondition.h"
+#include "Firestore/core/src/firebase/firestore/model/transform_operations.h"
+#include "Firestore/core/test/firebase/firestore/testutil/testutil.h"
+
+namespace testutil = firebase::firestore::testutil;
+using firebase::firestore::model::ArrayTransform;
+using firebase::firestore::model::DocumentKey;
+using firebase::firestore::model::FieldMask;
+using firebase::firestore::model::FieldPath;
+using firebase::firestore::model::FieldTransform;
+using firebase::firestore::model::Precondition;
+using firebase::firestore::model::TransformOperation;
+
+@interface FSTMutationTests : XCTestCase
+@end
+
+@implementation FSTMutationTests {
+ FIRTimestamp *_timestamp;
+}
+
+- (void)setUp {
+ _timestamp = [FIRTimestamp timestamp];
+}
+
+- (void)testAppliesSetsToDocuments {
+ NSDictionary *docData = @{@"foo" : @"foo-value", @"baz" : @"baz-value"};
+ FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, NO);
+
+ FSTMutation *set = FSTTestSetMutation(@"collection/key", @{@"bar" : @"bar-value"});
+ FSTMaybeDocument *setDoc = [set applyTo:baseDoc baseDocument:baseDoc localWriteTime:_timestamp];
+
+ NSDictionary *expectedData = @{@"bar" : @"bar-value"};
+ XCTAssertEqualObjects(setDoc, FSTTestDoc("collection/key", 0, expectedData, YES));
+}
+
+- (void)testAppliesPatchesToDocuments {
+ NSDictionary *docData = @{ @"foo" : @{@"bar" : @"bar-value"}, @"baz" : @"baz-value" };
+ FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, NO);
+
+ FSTMutation *patch = FSTTestPatchMutation("collection/key", @{@"foo.bar" : @"new-bar-value"}, {});
+ FSTMaybeDocument *patchedDoc =
+ [patch applyTo:baseDoc baseDocument:baseDoc localWriteTime:_timestamp];
+
+ NSDictionary *expectedData = @{ @"foo" : @{@"bar" : @"new-bar-value"}, @"baz" : @"baz-value" };
+ XCTAssertEqualObjects(patchedDoc, FSTTestDoc("collection/key", 0, expectedData, YES));
+}
+
+- (void)testDeletesValuesFromTheFieldMask {
+ NSDictionary *docData = @{ @"foo" : @{@"bar" : @"bar-value", @"baz" : @"baz-value"} };
+ FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, NO);
+
+ DocumentKey key = testutil::Key("collection/key");
+ FSTMutation *patch = [[FSTPatchMutation alloc] initWithKey:key
+ fieldMask:{testutil::Field("foo.bar")}
+ value:[FSTObjectValue objectValue]
+ precondition:Precondition::None()];
+ FSTMaybeDocument *patchedDoc =
+ [patch applyTo:baseDoc baseDocument:baseDoc localWriteTime:_timestamp];
+
+ NSDictionary *expectedData = @{ @"foo" : @{@"baz" : @"baz-value"} };
+ XCTAssertEqualObjects(patchedDoc, FSTTestDoc("collection/key", 0, expectedData, YES));
+}
+
+- (void)testPatchesPrimitiveValue {
+ NSDictionary *docData = @{@"foo" : @"foo-value", @"baz" : @"baz-value"};
+ FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, NO);
+
+ FSTMutation *patch = FSTTestPatchMutation("collection/key", @{@"foo.bar" : @"new-bar-value"}, {});
+ FSTMaybeDocument *patchedDoc =
+ [patch applyTo:baseDoc baseDocument:baseDoc localWriteTime:_timestamp];
+
+ NSDictionary *expectedData = @{ @"foo" : @{@"bar" : @"new-bar-value"}, @"baz" : @"baz-value" };
+ XCTAssertEqualObjects(patchedDoc, FSTTestDoc("collection/key", 0, expectedData, YES));
+}
+
+- (void)testPatchingDeletedDocumentsDoesNothing {
+ FSTMaybeDocument *baseDoc = FSTTestDeletedDoc("collection/key", 0);
+ FSTMutation *patch = FSTTestPatchMutation("collection/key", @{@"foo" : @"bar"}, {});
+ FSTMaybeDocument *patchedDoc =
+ [patch applyTo:baseDoc baseDocument:baseDoc localWriteTime:_timestamp];
+ XCTAssertEqualObjects(patchedDoc, baseDoc);
+}
+
+- (void)testAppliesLocalServerTimestampTransformToDocuments {
+ NSDictionary *docData = @{ @"foo" : @{@"bar" : @"bar-value"}, @"baz" : @"baz-value" };
+ FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, NO);
+
+ FSTMutation *transform = FSTTestTransformMutation(
+ @"collection/key", @{@"foo.bar" : [FIRFieldValue fieldValueForServerTimestamp]});
+ FSTMaybeDocument *transformedDoc =
+ [transform applyTo:baseDoc baseDocument:baseDoc localWriteTime:_timestamp];
+
+ // Server timestamps aren't parsed, so we manually insert it.
+ FSTObjectValue *expectedData = FSTTestObjectValue(
+ @{ @"foo" : @{@"bar" : @"<server-timestamp>"},
+ @"baz" : @"baz-value" });
+ expectedData =
+ [expectedData objectBySettingValue:[FSTServerTimestampValue
+ serverTimestampValueWithLocalWriteTime:_timestamp
+ previousValue:nil]
+ forPath:testutil::Field("foo.bar")];
+
+ FSTDocument *expectedDoc = [FSTDocument documentWithData:expectedData
+ key:FSTTestDocKey(@"collection/key")
+ version:FSTTestVersion(0)
+ hasLocalMutations:YES];
+
+ XCTAssertEqualObjects(transformedDoc, expectedDoc);
+}
+
+// NOTE: This is more a test of FSTUserDataConverter code than FSTMutation code but we don't have
+// unit tests for it currently. We could consider removing this test once we have integration tests.
+- (void)testCreateArrayUnionTransform {
+ FSTTransformMutation *transform = FSTTestTransformMutation(@"collection/key", @{
+ @"foo" : [FIRFieldValue fieldValueForArrayUnion:@[ @"tag" ]],
+ @"bar.baz" : [FIRFieldValue fieldValueForArrayUnion:@[ @YES, @[ @1, @2 ], @{@"a" : @"b"} ]]
+ });
+ XCTAssertEqual(transform.fieldTransforms.size(), 2);
+
+ const FieldTransform &first = transform.fieldTransforms[0];
+ XCTAssertEqual(first.path(), FieldPath({"foo"}));
+ {
+ std::vector<FSTFieldValue *> expectedElements{FSTTestFieldValue(@"tag")};
+ ArrayTransform expected(TransformOperation::Type::ArrayUnion, expectedElements);
+ XCTAssertEqual(static_cast<const ArrayTransform &>(first.transformation()), expected);
+ }
+
+ const FieldTransform &second = transform.fieldTransforms[1];
+ XCTAssertEqual(second.path(), FieldPath({"bar", "baz"}));
+ {
+ std::vector<FSTFieldValue *> expectedElements {
+ FSTTestFieldValue(@YES), FSTTestFieldValue(@[ @1, @2 ]), FSTTestFieldValue(@{@"a" : @"b"})
+ };
+ ArrayTransform expected(TransformOperation::Type::ArrayUnion, expectedElements);
+ XCTAssertEqual(static_cast<const ArrayTransform &>(second.transformation()), expected);
+ }
+}
+
+// NOTE: This is more a test of FSTUserDataConverter code than FSTMutation code but we don't have
+// unit tests for it currently. We could consider removing this test once we have integration tests.
+- (void)testCreateArrayRemoveTransform {
+ FSTTransformMutation *transform = FSTTestTransformMutation(@"collection/key", @{
+ @"foo" : [FIRFieldValue fieldValueForArrayRemove:@[ @"tag" ]],
+ });
+ XCTAssertEqual(transform.fieldTransforms.size(), 1);
+
+ const FieldTransform &first = transform.fieldTransforms[0];
+ XCTAssertEqual(first.path(), FieldPath({"foo"}));
+ {
+ std::vector<FSTFieldValue *> expectedElements{FSTTestFieldValue(@"tag")};
+ const ArrayTransform expected(TransformOperation::Type::ArrayRemove, expectedElements);
+ XCTAssertEqual(static_cast<const ArrayTransform &>(first.transformation()), expected);
+ }
+}
+
+- (void)testAppliesServerAckedTransformsToDocuments {
+ NSDictionary *docData = @{ @"foo" : @{@"bar" : @"bar-value"}, @"baz" : @"baz-value" };
+ FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, NO);
+
+ FSTMutation *transform = FSTTestTransformMutation(
+ @"collection/key", @{@"foo.bar" : [FIRFieldValue fieldValueForServerTimestamp]});
+
+ FSTMutationResult *mutationResult = [[FSTMutationResult alloc]
+ initWithVersion:FSTTestVersion(1)
+ transformResults:@[ [FSTTimestampValue timestampValue:_timestamp] ]];
+
+ FSTMaybeDocument *transformedDoc = [transform applyTo:baseDoc
+ baseDocument:baseDoc
+ localWriteTime:_timestamp
+ mutationResult:mutationResult];
+
+ NSDictionary *expectedData =
+ @{ @"foo" : @{@"bar" : _timestamp.dateValue},
+ @"baz" : @"baz-value" };
+ XCTAssertEqualObjects(transformedDoc, FSTTestDoc("collection/key", 0, expectedData, NO));
+}
+
+- (void)testDeleteDeletes {
+ NSDictionary *docData = @{@"foo" : @"bar"};
+ FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, NO);
+
+ FSTMutation *mutation = FSTTestDeleteMutation(@"collection/key");
+ FSTMaybeDocument *result =
+ [mutation applyTo:baseDoc baseDocument:baseDoc localWriteTime:_timestamp];
+ XCTAssertEqualObjects(result, FSTTestDeletedDoc("collection/key", 0));
+}
+
+- (void)testSetWithMutationResult {
+ NSDictionary *docData = @{@"foo" : @"bar"};
+ FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, NO);
+
+ FSTMutation *set = FSTTestSetMutation(@"collection/key", @{@"foo" : @"new-bar"});
+ FSTMutationResult *mutationResult =
+ [[FSTMutationResult alloc] initWithVersion:FSTTestVersion(4) transformResults:nil];
+ FSTMaybeDocument *setDoc = [set applyTo:baseDoc
+ baseDocument:baseDoc
+ localWriteTime:_timestamp
+ mutationResult:mutationResult];
+
+ NSDictionary *expectedData = @{@"foo" : @"new-bar"};
+ XCTAssertEqualObjects(setDoc, FSTTestDoc("collection/key", 0, expectedData, NO));
+}
+
+- (void)testPatchWithMutationResult {
+ NSDictionary *docData = @{@"foo" : @"bar"};
+ FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, NO);
+
+ FSTMutation *patch = FSTTestPatchMutation("collection/key", @{@"foo" : @"new-bar"}, {});
+ FSTMutationResult *mutationResult =
+ [[FSTMutationResult alloc] initWithVersion:FSTTestVersion(4) transformResults:nil];
+ FSTMaybeDocument *patchedDoc = [patch applyTo:baseDoc
+ baseDocument:baseDoc
+ localWriteTime:_timestamp
+ mutationResult:mutationResult];
+
+ NSDictionary *expectedData = @{@"foo" : @"new-bar"};
+ XCTAssertEqualObjects(patchedDoc, FSTTestDoc("collection/key", 0, expectedData, NO));
+}
+
+#define ASSERT_VERSION_TRANSITION(mutation, base, expected) \
+ do { \
+ FSTMutationResult *mutationResult = \
+ [[FSTMutationResult alloc] initWithVersion:FSTTestVersion(0) transformResults:nil]; \
+ FSTMaybeDocument *actual = [mutation applyTo:base \
+ baseDocument:base \
+ localWriteTime:_timestamp \
+ mutationResult:mutationResult]; \
+ XCTAssertEqualObjects(actual, expected); \
+ } while (0);
+
+/**
+ * Tests the transition table documented in FSTMutation.h.
+ */
+- (void)testTransitions {
+ FSTDocument *docV0 = FSTTestDoc("collection/key", 0, @{}, NO);
+ FSTDeletedDocument *deletedV0 = FSTTestDeletedDoc("collection/key", 0);
+
+ FSTDocument *docV3 = FSTTestDoc("collection/key", 3, @{}, NO);
+ FSTDeletedDocument *deletedV3 = FSTTestDeletedDoc("collection/key", 3);
+
+ FSTMutation *setMutation = FSTTestSetMutation(@"collection/key", @{});
+ FSTMutation *patchMutation = FSTTestPatchMutation("collection/key", {}, {});
+ FSTMutation *deleteMutation = FSTTestDeleteMutation(@"collection/key");
+
+ ASSERT_VERSION_TRANSITION(setMutation, docV3, docV3);
+ ASSERT_VERSION_TRANSITION(setMutation, deletedV3, docV0);
+ ASSERT_VERSION_TRANSITION(setMutation, nil, docV0);
+
+ ASSERT_VERSION_TRANSITION(patchMutation, docV3, docV3);
+ ASSERT_VERSION_TRANSITION(patchMutation, deletedV3, deletedV3);
+ ASSERT_VERSION_TRANSITION(patchMutation, nil, nil);
+
+ ASSERT_VERSION_TRANSITION(deleteMutation, docV3, deletedV0);
+ ASSERT_VERSION_TRANSITION(deleteMutation, deletedV3, deletedV0);
+ ASSERT_VERSION_TRANSITION(deleteMutation, nil, deletedV0);
+}
+
+#undef ASSERT_TRANSITION
+
+@end
diff --git a/Firestore/Example/Tests/Model/FSTPathTests.m b/Firestore/Example/Tests/Model/FSTPathTests.m
deleted file mode 100644
index b8529e5..0000000
--- a/Firestore/Example/Tests/Model/FSTPathTests.m
+++ /dev/null
@@ -1,196 +0,0 @@
-/*
- * Copyright 2017 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 "Firestore/Example/Tests/Util/FSTHelpers.h"
-#import "Firestore/Source/Model/FSTPath.h"
-
-#import <XCTest/XCTest.h>
-
-NS_ASSUME_NONNULL_BEGIN
-
-@interface FSTFieldPathTests : XCTestCase
-@end
-
-@implementation FSTFieldPathTests
-
-- (void)testConstructor {
- FSTFieldPath *path = [FSTFieldPath pathWithSegments:@[ @"rooms", @"Eros", @"messages" ]];
- XCTAssertEqual(3, path.length);
-}
-
-- (void)testIndexing {
- FSTFieldPath *path = [FSTFieldPath pathWithSegments:@[ @"rooms", @"Eros", @"messages" ]];
- XCTAssertEqualObjects(@"rooms", path.firstSegment);
- XCTAssertEqualObjects(@"rooms", [path segmentAtIndex:0]);
- XCTAssertEqualObjects(@"rooms", path[0]);
-
- XCTAssertEqualObjects(@"Eros", [path segmentAtIndex:1]);
- XCTAssertEqualObjects(@"Eros", path[1]);
-
- XCTAssertEqualObjects(@"messages", [path segmentAtIndex:2]);
- XCTAssertEqualObjects(@"messages", path[2]);
- XCTAssertEqualObjects(@"messages", path.lastSegment);
-}
-
-- (void)testPathByRemovingFirstSegment {
- FSTFieldPath *path = [FSTFieldPath pathWithSegments:@[ @"rooms", @"Eros", @"messages" ]];
- FSTFieldPath *same = [FSTFieldPath pathWithSegments:@[ @"rooms", @"Eros", @"messages" ]];
- FSTFieldPath *second = [FSTFieldPath pathWithSegments:@[ @"Eros", @"messages" ]];
- FSTFieldPath *third = [FSTFieldPath pathWithSegments:@[ @"messages" ]];
- FSTFieldPath *empty = [FSTFieldPath pathWithSegments:@[]];
-
- XCTAssertEqualObjects(second, path.pathByRemovingFirstSegment);
- XCTAssertEqualObjects(third, path.pathByRemovingFirstSegment.pathByRemovingFirstSegment);
- XCTAssertEqualObjects(
- empty, path.pathByRemovingFirstSegment.pathByRemovingFirstSegment.pathByRemovingFirstSegment);
- // unmodified original
- XCTAssertEqualObjects(same, path);
-}
-
-- (void)testPathByRemovingLastSegment {
- FSTFieldPath *path = [FSTFieldPath pathWithSegments:@[ @"rooms", @"Eros", @"messages" ]];
- FSTFieldPath *same = [FSTFieldPath pathWithSegments:@[ @"rooms", @"Eros", @"messages" ]];
- FSTFieldPath *second = [FSTFieldPath pathWithSegments:@[ @"rooms", @"Eros" ]];
- FSTFieldPath *third = [FSTFieldPath pathWithSegments:@[ @"rooms" ]];
- FSTFieldPath *empty = [FSTFieldPath pathWithSegments:@[]];
-
- XCTAssertEqualObjects(second, path.pathByRemovingLastSegment);
- XCTAssertEqualObjects(third, path.pathByRemovingLastSegment.pathByRemovingLastSegment);
- XCTAssertEqualObjects(
- empty, path.pathByRemovingLastSegment.pathByRemovingLastSegment.pathByRemovingLastSegment);
- // unmodified original
- XCTAssertEqualObjects(same, path);
-}
-
-- (void)testPathByAppendingSegment {
- FSTFieldPath *path = [FSTFieldPath pathWithSegments:@[ @"rooms" ]];
- FSTFieldPath *rooms = [FSTFieldPath pathWithSegments:@[ @"rooms" ]];
- FSTFieldPath *roomsEros = [FSTFieldPath pathWithSegments:@[ @"rooms", @"eros" ]];
- FSTFieldPath *roomsEros1 = [FSTFieldPath pathWithSegments:@[ @"rooms", @"eros", @"1" ]];
-
- XCTAssertEqualObjects(roomsEros, [path pathByAppendingSegment:@"eros"]);
- XCTAssertEqualObjects(roomsEros1,
- [[path pathByAppendingSegment:@"eros"] pathByAppendingSegment:@"1"]);
- // unmodified original
- XCTAssertEqualObjects(rooms, path);
-
- FSTFieldPath *sub = [FSTTestFieldPath(@"rooms.eros.1") pathByRemovingFirstSegment];
- FSTFieldPath *appended = [sub pathByAppendingSegment:@"2"];
- XCTAssertEqualObjects(appended, FSTTestFieldPath(@"eros.1.2"));
-}
-
-- (void)testPathComparison {
- FSTFieldPath *path1 = [FSTFieldPath pathWithSegments:@[ @"a", @"b", @"c" ]];
- FSTFieldPath *path2 = [FSTFieldPath pathWithSegments:@[ @"a", @"b", @"c" ]];
- FSTFieldPath *path3 = [FSTFieldPath pathWithSegments:@[ @"x", @"y", @"z" ]];
- XCTAssertTrue([path1 isEqual:path2]);
- XCTAssertFalse([path1 isEqual:path3]);
-
- FSTFieldPath *empty = [FSTFieldPath pathWithSegments:@[]];
- FSTFieldPath *a = [FSTFieldPath pathWithSegments:@[ @"a" ]];
- FSTFieldPath *b = [FSTFieldPath pathWithSegments:@[ @"b" ]];
- FSTFieldPath *ab = [FSTFieldPath pathWithSegments:@[ @"a", @"b" ]];
-
- XCTAssertEqual(NSOrderedAscending, [empty compare:a]);
- XCTAssertEqual(NSOrderedAscending, [a compare:b]);
- XCTAssertEqual(NSOrderedAscending, [a compare:ab]);
-
- XCTAssertEqual(NSOrderedDescending, [a compare:empty]);
- XCTAssertEqual(NSOrderedDescending, [b compare:a]);
- XCTAssertEqual(NSOrderedDescending, [ab compare:a]);
-}
-
-- (void)testIsPrefixOfPath {
- FSTFieldPath *empty = [FSTFieldPath pathWithSegments:@[]];
- FSTFieldPath *a = [FSTFieldPath pathWithSegments:@[ @"a" ]];
- FSTFieldPath *ab = [FSTFieldPath pathWithSegments:@[ @"a", @"b" ]];
- FSTFieldPath *abc = [FSTFieldPath pathWithSegments:@[ @"a", @"b", @"c" ]];
- FSTFieldPath *b = [FSTFieldPath pathWithSegments:@[ @"b" ]];
- FSTFieldPath *ba = [FSTFieldPath pathWithSegments:@[ @"b", @"a" ]];
-
- XCTAssertTrue([empty isPrefixOfPath:a]);
- XCTAssertTrue([empty isPrefixOfPath:ab]);
- XCTAssertTrue([empty isPrefixOfPath:abc]);
- XCTAssertTrue([empty isPrefixOfPath:empty]);
- XCTAssertTrue([empty isPrefixOfPath:b]);
- XCTAssertTrue([empty isPrefixOfPath:ba]);
-
- XCTAssertTrue([a isPrefixOfPath:a]);
- XCTAssertTrue([a isPrefixOfPath:ab]);
- XCTAssertTrue([a isPrefixOfPath:abc]);
- XCTAssertFalse([a isPrefixOfPath:empty]);
- XCTAssertFalse([a isPrefixOfPath:b]);
- XCTAssertFalse([a isPrefixOfPath:ba]);
-
- XCTAssertFalse([ab isPrefixOfPath:a]);
- XCTAssertTrue([ab isPrefixOfPath:ab]);
- XCTAssertTrue([ab isPrefixOfPath:abc]);
- XCTAssertFalse([ab isPrefixOfPath:empty]);
- XCTAssertFalse([ab isPrefixOfPath:b]);
- XCTAssertFalse([ab isPrefixOfPath:ba]);
-
- XCTAssertFalse([abc isPrefixOfPath:a]);
- XCTAssertFalse([abc isPrefixOfPath:ab]);
- XCTAssertTrue([abc isPrefixOfPath:abc]);
- XCTAssertFalse([abc isPrefixOfPath:empty]);
- XCTAssertFalse([abc isPrefixOfPath:b]);
- XCTAssertFalse([abc isPrefixOfPath:ba]);
-}
-
-- (void)testInvalidPaths {
- XCTAssertThrows(FSTTestFieldPath(@""));
- XCTAssertThrows(FSTTestFieldPath(@"."));
- XCTAssertThrows(FSTTestFieldPath(@".foo"));
- XCTAssertThrows(FSTTestFieldPath(@"foo."));
- XCTAssertThrows(FSTTestFieldPath(@"foo..bar"));
-}
-
-#define ASSERT_ROUND_TRIP(str, segments) \
- do { \
- FSTFieldPath *path = [FSTFieldPath pathWithServerFormat:str]; \
- XCTAssertEqual([path length], segments); \
- NSString *canonical = [path canonicalString]; \
- XCTAssertEqualObjects(canonical, str); \
- } while (0);
-
-- (void)testCanonicalString {
- ASSERT_ROUND_TRIP(@"foo", 1);
- ASSERT_ROUND_TRIP(@"foo.bar", 2);
- ASSERT_ROUND_TRIP(@"foo.bar.baz", 3);
- ASSERT_ROUND_TRIP(@"`.foo\\\\`", 1);
- ASSERT_ROUND_TRIP(@"`.foo\\\\`.`.foo`", 2);
- ASSERT_ROUND_TRIP(@"foo.`\\``.bar", 3);
-}
-
-#undef ASSERT_ROUND_TRIP
-
-- (void)testCanonicalStringOfSubstring {
- FSTFieldPath *path = [FSTFieldPath pathWithServerFormat:@"foo.bar.baz"];
- XCTAssertEqualObjects([path canonicalString], @"foo.bar.baz");
-
- FSTFieldPath *pathTail = [path pathByRemovingFirstSegment];
- XCTAssertEqualObjects([pathTail canonicalString], @"bar.baz");
-
- FSTFieldPath *pathHead = [path pathByRemovingLastSegment];
- XCTAssertEqualObjects([pathHead canonicalString], @"foo.bar");
-
- XCTAssertEqualObjects([[pathTail pathByRemovingLastSegment] canonicalString], @"bar");
- XCTAssertEqualObjects([[pathHead pathByRemovingFirstSegment] canonicalString], @"bar");
-}
-
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/Firestore/Example/Tests/Remote/FSTDatastoreTests.m b/Firestore/Example/Tests/Remote/FSTDatastoreTests.mm
index f3cc56f..6d6e912 100644
--- a/Firestore/Example/Tests/Remote/FSTDatastoreTests.m
+++ b/Firestore/Example/Tests/Remote/FSTDatastoreTests.mm
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-#import "FirebaseFirestore/FIRFirestoreErrors.h"
#import "Firestore/Source/Remote/FSTDatastore.h"
+#import <FirebaseFirestore/FIRFirestoreErrors.h>
#import <GRPCClient/GRPCCall.h>
#import <XCTest/XCTest.h>
diff --git a/Firestore/Example/Tests/Remote/FSTRemoteEventTests.m b/Firestore/Example/Tests/Remote/FSTRemoteEventTests.mm
index a947eb4..c4800d4 100644
--- a/Firestore/Example/Tests/Remote/FSTRemoteEventTests.m
+++ b/Firestore/Example/Tests/Remote/FSTRemoteEventTests.mm
@@ -60,8 +60,8 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)testWillAccumulateDocumentAddedAndRemovedEvents {
- FSTDocument *doc1 = FSTTestDoc(@"docs/1", 1, @{ @"value" : @1 }, NO);
- FSTDocument *doc2 = FSTTestDoc(@"docs/2", 2, @{ @"value" : @2 }, NO);
+ FSTDocument *doc1 = FSTTestDoc("docs/1", 1, @{ @"value" : @1 }, NO);
+ FSTDocument *doc2 = FSTTestDoc("docs/2", 2, @{ @"value" : @2 }, NO);
FSTWatchChange *change1 = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1, @2, @3 ]
removedTargetIDs:@[ @4, @5, @6 ]
@@ -79,9 +79,9 @@ NS_ASSUME_NONNULL_BEGIN
FSTRemoteEvent *event = [aggregator remoteEvent];
XCTAssertEqualObjects(event.snapshotVersion, FSTTestVersion(3));
- XCTAssertEqual(event.documentUpdates.count, 2);
- XCTAssertEqualObjects(event.documentUpdates[doc1.key], doc1);
- XCTAssertEqualObjects(event.documentUpdates[doc2.key], doc2);
+ XCTAssertEqual(event.documentUpdates.size(), 2);
+ XCTAssertEqualObjects(event.documentUpdates.at(doc1.key), doc1);
+ XCTAssertEqualObjects(event.documentUpdates.at(doc2.key), doc2);
XCTAssertEqual(event.targetChanges.count, 6);
@@ -111,8 +111,8 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)testWillIgnoreEventsForPendingTargets {
- FSTDocument *doc1 = FSTTestDoc(@"docs/1", 1, @{ @"value" : @1 }, NO);
- FSTDocument *doc2 = FSTTestDoc(@"docs/2", 2, @{ @"value" : @2 }, NO);
+ FSTDocument *doc1 = FSTTestDoc("docs/1", 1, @{ @"value" : @1 }, NO);
+ FSTDocument *doc2 = FSTTestDoc("docs/2", 2, @{ @"value" : @2 }, NO);
FSTWatchChange *change1 = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1 ]
removedTargetIDs:@[]
@@ -143,14 +143,14 @@ NS_ASSUME_NONNULL_BEGIN
XCTAssertEqualObjects(event.snapshotVersion, FSTTestVersion(3));
// doc1 is ignored because it was part of an inactive target, but doc2 is in the changes
// because it become active.
- XCTAssertEqual(event.documentUpdates.count, 1);
- XCTAssertEqualObjects(event.documentUpdates[doc2.key], doc2);
+ XCTAssertEqual(event.documentUpdates.size(), 1);
+ XCTAssertEqualObjects(event.documentUpdates.at(doc2.key), doc2);
XCTAssertEqual(event.targetChanges.count, 1);
}
- (void)testWillIgnoreEventsForRemovedTargets {
- FSTDocument *doc1 = FSTTestDoc(@"docs/1", 1, @{ @"value" : @1 }, NO);
+ FSTDocument *doc1 = FSTTestDoc("docs/1", 1, @{ @"value" : @1 }, NO);
FSTWatchChange *change1 = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1 ]
removedTargetIDs:@[]
@@ -170,16 +170,16 @@ NS_ASSUME_NONNULL_BEGIN
FSTRemoteEvent *event = [aggregator remoteEvent];
XCTAssertEqualObjects(event.snapshotVersion, FSTTestVersion(3));
// doc1 is ignored because it was part of an inactive target
- XCTAssertEqual(event.documentUpdates.count, 0);
+ XCTAssertEqual(event.documentUpdates.size(), 0);
// Target 1 is ignored because it was removed
XCTAssertEqual(event.targetChanges.count, 0);
}
- (void)testWillKeepResetMappingEvenWithUpdates {
- FSTDocument *doc1 = FSTTestDoc(@"docs/1", 1, @{ @"value" : @1 }, NO);
- FSTDocument *doc2 = FSTTestDoc(@"docs/2", 2, @{ @"value" : @2 }, NO);
- FSTDocument *doc3 = FSTTestDoc(@"docs/3", 3, @{ @"value" : @3 }, NO);
+ FSTDocument *doc1 = FSTTestDoc("docs/1", 1, @{ @"value" : @1 }, NO);
+ FSTDocument *doc2 = FSTTestDoc("docs/2", 2, @{ @"value" : @2 }, NO);
+ FSTDocument *doc3 = FSTTestDoc("docs/3", 3, @{ @"value" : @3 }, NO);
FSTWatchChange *change1 = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1 ]
removedTargetIDs:@[]
@@ -213,10 +213,10 @@ NS_ASSUME_NONNULL_BEGIN
FSTRemoteEvent *event = [aggregator remoteEvent];
XCTAssertEqualObjects(event.snapshotVersion, FSTTestVersion(3));
- XCTAssertEqual(event.documentUpdates.count, 3);
- XCTAssertEqualObjects(event.documentUpdates[doc1.key], doc1);
- XCTAssertEqualObjects(event.documentUpdates[doc2.key], doc2);
- XCTAssertEqualObjects(event.documentUpdates[doc3.key], doc3);
+ XCTAssertEqual(event.documentUpdates.size(), 3);
+ XCTAssertEqualObjects(event.documentUpdates.at(doc1.key), doc1);
+ XCTAssertEqualObjects(event.documentUpdates.at(doc2.key), doc2);
+ XCTAssertEqualObjects(event.documentUpdates.at(doc3.key), doc3);
XCTAssertEqual(event.targetChanges.count, 1);
@@ -237,7 +237,7 @@ NS_ASSUME_NONNULL_BEGIN
FSTRemoteEvent *event = [aggregator remoteEvent];
XCTAssertEqualObjects(event.snapshotVersion, FSTTestVersion(3));
- XCTAssertEqual(event.documentUpdates.count, 0);
+ XCTAssertEqual(event.documentUpdates.size(), 0);
XCTAssertEqual(event.targetChanges.count, 1);
@@ -247,8 +247,8 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)testWillHandleTargetAddAndRemovalInSameBatch {
- FSTDocument *doc1a = FSTTestDoc(@"docs/1", 1, @{ @"value" : @1 }, NO);
- FSTDocument *doc1b = FSTTestDoc(@"docs/1", 1, @{ @"value" : @2 }, NO);
+ FSTDocument *doc1a = FSTTestDoc("docs/1", 1, @{ @"value" : @1 }, NO);
+ FSTDocument *doc1b = FSTTestDoc("docs/1", 1, @{ @"value" : @2 }, NO);
FSTWatchChange *change1 = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1 ]
removedTargetIDs:@[ @2 ]
@@ -265,8 +265,8 @@ NS_ASSUME_NONNULL_BEGIN
FSTRemoteEvent *event = [aggregator remoteEvent];
XCTAssertEqualObjects(event.snapshotVersion, FSTTestVersion(3));
- XCTAssertEqual(event.documentUpdates.count, 1);
- XCTAssertEqualObjects(event.documentUpdates[doc1b.key], doc1b);
+ XCTAssertEqual(event.documentUpdates.size(), 1);
+ XCTAssertEqualObjects(event.documentUpdates.at(doc1b.key), doc1b);
XCTAssertEqual(event.targetChanges.count, 2);
@@ -289,7 +289,7 @@ NS_ASSUME_NONNULL_BEGIN
FSTRemoteEvent *event = [aggregator remoteEvent];
XCTAssertEqualObjects(event.snapshotVersion, FSTTestVersion(3));
- XCTAssertEqual(event.documentUpdates.count, 0);
+ XCTAssertEqual(event.documentUpdates.size(), 0);
XCTAssertEqual(event.targetChanges.count, 1);
FSTTargetChange *targetChange = event.targetChanges[@1];
XCTAssertEqualObjects(targetChange.mapping, [[FSTUpdateMapping alloc] init]);
@@ -298,8 +298,8 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)testTargetAddedChangeWillResetPreviousState {
- FSTDocument *doc1 = FSTTestDoc(@"docs/1", 1, @{ @"value" : @1 }, NO);
- FSTDocument *doc2 = FSTTestDoc(@"docs/2", 2, @{ @"value" : @2 }, NO);
+ FSTDocument *doc1 = FSTTestDoc("docs/1", 1, @{ @"value" : @1 }, NO);
+ FSTDocument *doc2 = FSTTestDoc("docs/2", 2, @{ @"value" : @2 }, NO);
FSTWatchChange *change1 = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1, @3 ]
removedTargetIDs:@[ @2 ]
@@ -331,9 +331,9 @@ NS_ASSUME_NONNULL_BEGIN
FSTRemoteEvent *event = [aggregator remoteEvent];
XCTAssertEqualObjects(event.snapshotVersion, FSTTestVersion(3));
- XCTAssertEqual(event.documentUpdates.count, 2);
- XCTAssertEqualObjects(event.documentUpdates[doc1.key], doc1);
- XCTAssertEqualObjects(event.documentUpdates[doc2.key], doc2);
+ XCTAssertEqual(event.documentUpdates.size(), 2);
+ XCTAssertEqualObjects(event.documentUpdates.at(doc1.key), doc1);
+ XCTAssertEqualObjects(event.documentUpdates.at(doc2.key), doc2);
// target 1 and 3 are affected (1 because of re-add), target 2 is not because of remove
XCTAssertEqual(event.targetChanges.count, 2);
@@ -364,7 +364,7 @@ NS_ASSUME_NONNULL_BEGIN
FSTRemoteEvent *event = [aggregator remoteEvent];
XCTAssertEqualObjects(event.snapshotVersion, FSTTestVersion(3));
- XCTAssertEqual(event.documentUpdates.count, 0);
+ XCTAssertEqual(event.documentUpdates.size(), 0);
XCTAssertEqual(event.targetChanges.count, 1);
XCTAssertEqualObjects(event.targetChanges[@1].mapping, [[FSTUpdateMapping alloc] init]);
XCTAssertEqual(event.targetChanges[@1].currentStatusUpdate, FSTCurrentStatusUpdateNone);
@@ -386,7 +386,7 @@ NS_ASSUME_NONNULL_BEGIN
FSTRemoteEvent *event = [aggregator remoteEvent];
XCTAssertEqualObjects(event.snapshotVersion, FSTTestVersion(3));
- XCTAssertEqual(event.documentUpdates.count, 0);
+ XCTAssertEqual(event.documentUpdates.size(), 0);
XCTAssertEqual(event.targetChanges.count, 0);
XCTAssertEqual(aggregator.existenceFilters.count, 2);
XCTAssertEqual(aggregator.existenceFilters[@1], filter1);
@@ -394,8 +394,8 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)testExistenceFilterMismatchResetsTarget {
- FSTDocument *doc1 = FSTTestDoc(@"docs/1", 1, @{ @"value" : @1 }, NO);
- FSTDocument *doc2 = FSTTestDoc(@"docs/2", 2, @{ @"value" : @2 }, NO);
+ FSTDocument *doc1 = FSTTestDoc("docs/1", 1, @{ @"value" : @1 }, NO);
+ FSTDocument *doc2 = FSTTestDoc("docs/2", 2, @{ @"value" : @2 }, NO);
FSTWatchChange *change1 = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1 ]
removedTargetIDs:@[]
@@ -418,9 +418,9 @@ NS_ASSUME_NONNULL_BEGIN
FSTRemoteEvent *event = [aggregator remoteEvent];
XCTAssertEqualObjects(event.snapshotVersion, FSTTestVersion(3));
- XCTAssertEqual(event.documentUpdates.count, 2);
- XCTAssertEqualObjects(event.documentUpdates[doc1.key], doc1);
- XCTAssertEqualObjects(event.documentUpdates[doc2.key], doc2);
+ XCTAssertEqual(event.documentUpdates.size(), 2);
+ XCTAssertEqualObjects(event.documentUpdates.at(doc1.key), doc1);
+ XCTAssertEqualObjects(event.documentUpdates.at(doc2.key), doc2);
XCTAssertEqual(event.targetChanges.count, 1);
@@ -443,11 +443,11 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)testDocumentUpdate {
- FSTDocument *doc1 = FSTTestDoc(@"docs/1", 1, @{ @"value" : @1 }, NO);
+ FSTDocument *doc1 = FSTTestDoc("docs/1", 1, @{ @"value" : @1 }, NO);
FSTDeletedDocument *deletedDoc1 =
[FSTDeletedDocument documentWithKey:doc1.key version:FSTTestVersion(3)];
- FSTDocument *doc2 = FSTTestDoc(@"docs/2", 2, @{ @"value" : @2 }, NO);
- FSTDocument *doc3 = FSTTestDoc(@"docs/3", 3, @{ @"value" : @3 }, NO);
+ FSTDocument *doc2 = FSTTestDoc("docs/2", 2, @{ @"value" : @2 }, NO);
+ FSTDocument *doc3 = FSTTestDoc("docs/3", 3, @{ @"value" : @3 }, NO);
FSTWatchChange *change1 = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1 ]
removedTargetIDs:@[]
@@ -465,22 +465,22 @@ NS_ASSUME_NONNULL_BEGIN
FSTRemoteEvent *event = [aggregator remoteEvent];
XCTAssertEqualObjects(event.snapshotVersion, FSTTestVersion(3));
- XCTAssertEqual(event.documentUpdates.count, 2);
- XCTAssertEqualObjects(event.documentUpdates[doc1.key], doc1);
- XCTAssertEqualObjects(event.documentUpdates[doc2.key], doc2);
+ XCTAssertEqual(event.documentUpdates.size(), 2);
+ XCTAssertEqualObjects(event.documentUpdates.at(doc1.key), doc1);
+ XCTAssertEqualObjects(event.documentUpdates.at(doc2.key), doc2);
// Update doc1
[event addDocumentUpdate:deletedDoc1];
[event addDocumentUpdate:doc3];
XCTAssertEqualObjects(event.snapshotVersion, FSTTestVersion(3));
- XCTAssertEqual(event.documentUpdates.count, 3);
+ XCTAssertEqual(event.documentUpdates.size(), 3);
// doc1 is replaced
- XCTAssertEqualObjects(event.documentUpdates[doc1.key], deletedDoc1);
+ XCTAssertEqualObjects(event.documentUpdates.at(doc1.key), deletedDoc1);
// doc2 is untouched
- XCTAssertEqualObjects(event.documentUpdates[doc2.key], doc2);
+ XCTAssertEqualObjects(event.documentUpdates.at(doc2.key), doc2);
// doc3 is new
- XCTAssertEqualObjects(event.documentUpdates[doc3.key], doc3);
+ XCTAssertEqualObjects(event.documentUpdates.at(doc3.key), doc3);
// Target is unchanged
XCTAssertEqual(event.targetChanges.count, 1);
diff --git a/Firestore/Example/Tests/Remote/FSTSerializerBetaTests.m b/Firestore/Example/Tests/Remote/FSTSerializerBetaTests.mm
index 528076f..a648cd8 100644
--- a/Firestore/Example/Tests/Remote/FSTSerializerBetaTests.m
+++ b/Firestore/Example/Tests/Remote/FSTSerializerBetaTests.mm
@@ -16,12 +16,16 @@
#import "Firestore/Source/Remote/FSTSerializerBeta.h"
+#import <FirebaseFirestore/FIRFieldPath.h>
+#import <FirebaseFirestore/FIRFieldValue.h>
+#import <FirebaseFirestore/FIRFirestoreErrors.h>
+#import <FirebaseFirestore/FIRGeoPoint.h>
+#import <FirebaseFirestore/FIRTimestamp.h>
#import <GRPCClient/GRPCCall.h>
#import <XCTest/XCTest.h>
-#import "FirebaseFirestore/FIRFieldPath.h"
-#import "FirebaseFirestore/FIRFirestoreErrors.h"
-#import "FirebaseFirestore/FIRGeoPoint.h"
+#include <vector>
+
#import "Firestore/Protos/objc/firestore/local/MaybeDocument.pbobjc.h"
#import "Firestore/Protos/objc/firestore/local/Mutation.pbobjc.h"
#import "Firestore/Protos/objc/google/firestore/v1beta1/Common.pbobjc.h"
@@ -33,19 +37,31 @@
#import "Firestore/Protos/objc/google/type/Latlng.pbobjc.h"
#import "Firestore/Source/Core/FSTQuery.h"
#import "Firestore/Source/Core/FSTSnapshotVersion.h"
-#import "Firestore/Source/Core/FSTTimestamp.h"
#import "Firestore/Source/Local/FSTQueryData.h"
-#import "Firestore/Source/Model/FSTDatabaseID.h"
#import "Firestore/Source/Model/FSTDocument.h"
#import "Firestore/Source/Model/FSTDocumentKey.h"
#import "Firestore/Source/Model/FSTFieldValue.h"
#import "Firestore/Source/Model/FSTMutation.h"
#import "Firestore/Source/Model/FSTMutationBatch.h"
-#import "Firestore/Source/Model/FSTPath.h"
#import "Firestore/Source/Remote/FSTWatchChange.h"
+#import "Firestore/Example/Tests/API/FSTAPIHelpers.h"
#import "Firestore/Example/Tests/Util/FSTHelpers.h"
+#include "Firestore/core/src/firebase/firestore/model/database_id.h"
+#include "Firestore/core/src/firebase/firestore/model/field_mask.h"
+#include "Firestore/core/src/firebase/firestore/model/field_transform.h"
+#include "Firestore/core/src/firebase/firestore/model/precondition.h"
+#include "Firestore/core/src/firebase/firestore/util/string_apple.h"
+#include "Firestore/core/test/firebase/firestore/testutil/testutil.h"
+
+namespace testutil = firebase::firestore::testutil;
+namespace util = firebase::firestore::util;
+using firebase::firestore::model::DatabaseId;
+using firebase::firestore::model::FieldMask;
+using firebase::firestore::model::FieldTransform;
+using firebase::firestore::model::Precondition;
+
NS_ASSUME_NONNULL_BEGIN
@interface FSTSerializerBeta (Test)
@@ -56,9 +72,9 @@ NS_ASSUME_NONNULL_BEGIN
- (GCFSValue *)encodedString:(NSString *)value;
- (GCFSValue *)encodedDate:(NSDate *)value;
-- (GCFSDocumentMask *)encodedFieldMask:(FSTFieldMask *)fieldMask;
+- (GCFSDocumentMask *)encodedFieldMask:(const FieldMask &)fieldMask;
- (NSMutableArray<GCFSDocumentTransform_FieldTransform *> *)encodedFieldTransforms:
- (NSArray<FSTFieldTransform *> *)fieldTransforms;
+ (const std::vector<FieldTransform> &)fieldTransforms;
- (GCFSStructuredQuery_Filter *)encodedRelationFilter:(FSTRelationFilter *)filter;
@end
@@ -78,15 +94,18 @@ NS_ASSUME_NONNULL_BEGIN
}
@end
-@interface FSTSerializerBetaTests : XCTestCase
+@interface FSTSerializerBetaTests : XCTestCase {
+ DatabaseId _databaseId;
+}
+
@property(nonatomic, strong) FSTSerializerBeta *serializer;
@end
@implementation FSTSerializerBetaTests
- (void)setUp {
- FSTDatabaseID *databaseID = [FSTDatabaseID databaseIDWithProject:@"p" database:@"d"];
- self.serializer = [[FSTSerializerBeta alloc] initWithDatabaseID:databaseID];
+ _databaseId = DatabaseId("p", "d");
+ self.serializer = [[FSTSerializerBeta alloc] initWithDatabaseID:&_databaseId];
}
- (void)testEncodesNull {
@@ -230,7 +249,8 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)testEncodesResourceNames {
- FSTDocumentKeyReference *reference = FSTTestRef(@"project", kDefaultDatabaseID, @"foo/bar");
+ FSTDocumentKeyReference *reference = FSTTestRef("project", DatabaseId::kDefault, @"foo/bar");
+ _databaseId = DatabaseId("project", DatabaseId::kDefault);
GCFSValue *proto = [GCFSValue message];
proto.referenceValue = @"projects/project/databases/(default)/documents/foo/bar";
@@ -266,7 +286,8 @@ NS_ASSUME_NONNULL_BEGIN
@"i" : @1,
@"n" : [NSNull null],
@"s" : @"foo",
- @"a" : @[ @2, @"bar", @{@"b" : @NO} ],
+ @"a" : @[ @2, @"bar",
+ @{ @"b" : @NO } ],
@"o" : @{
@"d" : @100,
@"nested" : @{@"e" : @(LLONG_MIN)},
@@ -325,11 +346,11 @@ NS_ASSUME_NONNULL_BEGIN
- (void)testEncodesPatchMutation {
FSTPatchMutation *mutation =
- FSTTestPatchMutation(@"docs/1",
+ FSTTestPatchMutation("docs/1",
@{ @"a" : @"b",
@"num" : @1,
@"some.de\\\\ep.th\\ing'" : @2 },
- nil);
+ {});
GCFSWrite *proto = [GCFSWrite message];
proto.update = [self.serializer encodedDocumentWithFields:mutation.value key:mutation.key];
proto.updateMask = [self.serializer encodedFieldMask:mutation.fieldMask];
@@ -347,7 +368,10 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)testEncodesTransformMutation {
- FSTTransformMutation *mutation = FSTTestTransformMutation(@"docs/1", @[ @"a", @"bar.baz" ]);
+ FSTTransformMutation *mutation = FSTTestTransformMutation(@"docs/1", @{
+ @"a" : [FIRFieldValue fieldValueForServerTimestamp],
+ @"bar.baz" : [FIRFieldValue fieldValueForServerTimestamp]
+ });
GCFSWrite *proto = [GCFSWrite message];
proto.transform = [GCFSDocumentTransform message];
proto.transform.document = [self.serializer encodedDocumentKey:mutation.key];
@@ -359,16 +383,16 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)testEncodesSetMutationWithPrecondition {
- FSTSetMutation *mutation = [[FSTSetMutation alloc]
- initWithKey:FSTTestDocKey(@"foo/bar")
- value:FSTTestObjectValue(
- @{ @"a" : @"b",
- @"num" : @1 })
- precondition:[FSTPrecondition preconditionWithUpdateTime:FSTTestVersion(4)]];
+ FSTSetMutation *mutation =
+ [[FSTSetMutation alloc] initWithKey:FSTTestDocKey(@"foo/bar")
+ value:FSTTestObjectValue(
+ @{ @"a" : @"b",
+ @"num" : @1 })
+ precondition:Precondition::UpdateTime(testutil::Version(4))];
GCFSWrite *proto = [GCFSWrite message];
proto.update = [self.serializer encodedDocumentWithFields:mutation.value key:mutation.key];
proto.currentDocument.updateTime =
- [self.serializer encodedTimestamp:[[FSTTimestamp alloc] initWithSeconds:0 nanos:4000]];
+ [self.serializer encodedTimestamp:[[FIRTimestamp alloc] initWithSeconds:0 nanoseconds:4000]];
[self assertRoundTripForMutation:mutation proto:proto];
}
@@ -393,28 +417,33 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)testEncodesListenRequestLabels {
- FSTQuery *query = FSTTestQuery(@"collection/key");
- FSTQueryData *queryData =
- [[FSTQueryData alloc] initWithQuery:query targetID:2 purpose:FSTQueryPurposeListen];
+ FSTQuery *query = FSTTestQuery("collection/key");
+ FSTQueryData *queryData = [[FSTQueryData alloc] initWithQuery:query
+ targetID:2
+ listenSequenceNumber:3
+ purpose:FSTQueryPurposeListen];
NSDictionary<NSString *, NSString *> *result =
[self.serializer encodedListenRequestLabelsForQueryData:queryData];
XCTAssertNil(result);
- queryData =
- [[FSTQueryData alloc] initWithQuery:query targetID:2 purpose:FSTQueryPurposeLimboResolution];
+ queryData = [[FSTQueryData alloc] initWithQuery:query
+ targetID:2
+ listenSequenceNumber:3
+ purpose:FSTQueryPurposeLimboResolution];
result = [self.serializer encodedListenRequestLabelsForQueryData:queryData];
XCTAssertEqualObjects(result, @{@"goog-listen-tags" : @"limbo-document"});
queryData = [[FSTQueryData alloc] initWithQuery:query
targetID:2
+ listenSequenceNumber:3
purpose:FSTQueryPurposeExistenceFilterMismatch];
result = [self.serializer encodedListenRequestLabelsForQueryData:queryData];
XCTAssertEqualObjects(result, @{@"goog-listen-tags" : @"existence-filter-mismatch"});
}
- (void)testEncodesRelationFilter {
- FSTRelationFilter *input = FSTTestFilter(@"item.part.top", @"==", @"food");
+ FSTRelationFilter *input = FSTTestFilter("item.part.top", @"==", @"food");
GCFSStructuredQuery_Filter *actual = [self.serializer encodedRelationFilter:input];
GCFSStructuredQuery_Filter *expected = [GCFSStructuredQuery_Filter message];
@@ -428,7 +457,7 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark - encodedQuery
- (void)testEncodesFirstLevelKeyQueries {
- FSTQuery *q = [FSTQuery queryWithPath:FSTTestPath(@"docs/1")];
+ FSTQuery *q = FSTTestQuery("docs/1");
FSTQueryData *model = [self queryDataForQuery:q];
GCFSTarget *expected = [GCFSTarget message];
@@ -439,7 +468,7 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)testEncodesFirstLevelAncestorQueries {
- FSTQuery *q = [FSTQuery queryWithPath:FSTTestPath(@"messages")];
+ FSTQuery *q = FSTTestQuery("messages");
FSTQueryData *model = [self queryDataForQuery:q];
GCFSTarget *expected = [GCFSTarget message];
@@ -455,7 +484,7 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)testEncodesNestedAncestorQueries {
- FSTQuery *q = [FSTQuery queryWithPath:FSTTestPath(@"rooms/1/messages/10/attachments")];
+ FSTQuery *q = FSTTestQuery("rooms/1/messages/10/attachments");
FSTQueryData *model = [self queryDataForQuery:q];
GCFSTarget *expected = [GCFSTarget message];
@@ -471,8 +500,7 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)testEncodesSingleFiltersAtFirstLevelCollections {
- FSTQuery *q = [[FSTQuery queryWithPath:FSTTestPath(@"docs")]
- queryByAddingFilter:FSTTestFilter(@"prop", @"<", @(42))];
+ FSTQuery *q = [FSTTestQuery("docs") queryByAddingFilter:FSTTestFilter("prop", @"<", @(42))];
FSTQueryData *model = [self queryDataForQuery:q];
GCFSTarget *expected = [GCFSTarget message];
@@ -495,9 +523,9 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)testEncodesMultipleFiltersOnDeeperCollections {
- FSTQuery *q = [[[FSTQuery queryWithPath:FSTTestPath(@"rooms/1/messages/10/attachments")]
- queryByAddingFilter:FSTTestFilter(@"prop", @">=", @(42))]
- queryByAddingFilter:FSTTestFilter(@"author", @"==", @"dimond")];
+ FSTQuery *q = [[FSTTestQuery("rooms/1/messages/10/attachments")
+ queryByAddingFilter:FSTTestFilter("prop", @">=", @(42))]
+ queryByAddingFilter:FSTTestFilter("author", @"==", @"dimond")];
FSTQueryData *model = [self queryDataForQuery:q];
GCFSTarget *expected = [GCFSTarget message];
@@ -544,10 +572,8 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)unaryFilterTestWithValue:(id)value
- expectedUnaryOperator:(GCFSStructuredQuery_UnaryFilter_Operator)
- operator{
- FSTQuery *q = [[FSTQuery queryWithPath:FSTTestPath(@"docs")]
- queryByAddingFilter:FSTTestFilter(@"prop", @"==", value)];
+ expectedUnaryOperator:(GCFSStructuredQuery_UnaryFilter_Operator)op {
+ FSTQuery *q = [FSTTestQuery("docs") queryByAddingFilter:FSTTestFilter("prop", @"==", value)];
FSTQueryData *model = [self queryDataForQuery:q];
GCFSTarget *expected = [GCFSTarget message];
@@ -560,15 +586,15 @@ NS_ASSUME_NONNULL_BEGIN
GCFSStructuredQuery_UnaryFilter *filter = expected.query.structuredQuery.where.unaryFilter;
filter.field.fieldPath = @"prop";
- filter.op = operator;
+ filter.op = op;
expected.targetId = 1;
[self assertRoundTripForQueryData:model proto:expected];
}
- (void)testEncodesSortOrders {
- FSTQuery *q = [[FSTQuery queryWithPath:FSTTestPath(@"docs")]
- queryByAddingSortOrder:[FSTSortOrder sortOrderWithFieldPath:FSTTestFieldPath(@"prop")
+ FSTQuery *q = [FSTTestQuery("docs")
+ queryByAddingSortOrder:[FSTSortOrder sortOrderWithFieldPath:testutil::Field("prop")
ascending:YES]];
FSTQueryData *model = [self queryDataForQuery:q];
@@ -587,8 +613,8 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)testEncodesSortOrdersDescending {
- FSTQuery *q = [[FSTQuery queryWithPath:FSTTestPath(@"rooms/1/messages/10/attachments")]
- queryByAddingSortOrder:[FSTSortOrder sortOrderWithFieldPath:FSTTestFieldPath(@"prop")
+ FSTQuery *q = [FSTTestQuery("rooms/1/messages/10/attachments")
+ queryByAddingSortOrder:[FSTSortOrder sortOrderWithFieldPath:testutil::Field("prop")
ascending:NO]];
FSTQueryData *model = [self queryDataForQuery:q];
@@ -607,7 +633,7 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)testEncodesLimits {
- FSTQuery *q = [[FSTQuery queryWithPath:FSTTestPath(@"docs")] queryBySettingLimit:26];
+ FSTQuery *q = [FSTTestQuery("docs") queryBySettingLimit:26];
FSTQueryData *model = [self queryDataForQuery:q];
GCFSTarget *expected = [GCFSTarget message];
@@ -624,9 +650,10 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)testEncodesResumeTokens {
- FSTQuery *q = [FSTQuery queryWithPath:FSTTestPath(@"docs")];
+ FSTQuery *q = FSTTestQuery("docs");
FSTQueryData *model = [[FSTQueryData alloc] initWithQuery:q
targetID:1
+ listenSequenceNumber:0
purpose:FSTQueryPurposeListen
snapshotVersion:[FSTSnapshotVersion noVersion]
resumeToken:FSTTestData(1, 2, 3, -1)];
@@ -647,6 +674,7 @@ NS_ASSUME_NONNULL_BEGIN
- (FSTQueryData *)queryDataForQuery:(FSTQuery *)query {
return [[FSTQueryData alloc] initWithQuery:query
targetID:1
+ listenSequenceNumber:0
purpose:FSTQueryPurposeListen
snapshotVersion:[FSTSnapshotVersion noVersion]
resumeToken:[NSData data]];
@@ -725,7 +753,7 @@ NS_ASSUME_NONNULL_BEGIN
initWithUpdatedTargetIDs:@[ @1, @2 ]
removedTargetIDs:@[]
documentKey:FSTTestDocKey(@"coll/1")
- document:FSTTestDoc(@"coll/1", 5, @{@"foo" : @"bar"}, NO)];
+ document:FSTTestDoc("coll/1", 5, @{@"foo" : @"bar"}, NO)];
GCFSListenResponse *listenResponse = [GCFSListenResponse message];
listenResponse.documentChange.document.name = @"projects/p/databases/d/documents/coll/1";
listenResponse.documentChange.document.updateTime.nanos = 5000;
@@ -744,7 +772,7 @@ NS_ASSUME_NONNULL_BEGIN
initWithUpdatedTargetIDs:@[ @2 ]
removedTargetIDs:@[ @1 ]
documentKey:FSTTestDocKey(@"coll/1")
- document:FSTTestDoc(@"coll/1", 5, @{@"foo" : @"bar"}, NO)];
+ document:FSTTestDoc("coll/1", 5, @{@"foo" : @"bar"}, NO)];
GCFSListenResponse *listenResponse = [GCFSListenResponse message];
listenResponse.documentChange.document.name = @"projects/p/databases/d/documents/coll/1";
listenResponse.documentChange.document.updateTime.nanos = 5000;
@@ -763,7 +791,7 @@ NS_ASSUME_NONNULL_BEGIN
[[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[]
removedTargetIDs:@[ @1, @2 ]
documentKey:FSTTestDocKey(@"coll/1")
- document:FSTTestDeletedDoc(@"coll/1", 5)];
+ document:FSTTestDeletedDoc("coll/1", 5)];
GCFSListenResponse *listenResponse = [GCFSListenResponse message];
listenResponse.documentDelete.document = @"projects/p/databases/d/documents/coll/1";
listenResponse.documentDelete.readTime.nanos = 5000;
diff --git a/Firestore/Example/Tests/Remote/FSTWatchChange+Testing.m b/Firestore/Example/Tests/Remote/FSTWatchChange+Testing.mm
index 6bb314d..6bb314d 100644
--- a/Firestore/Example/Tests/Remote/FSTWatchChange+Testing.m
+++ b/Firestore/Example/Tests/Remote/FSTWatchChange+Testing.mm
diff --git a/Firestore/Example/Tests/Remote/FSTWatchChangeTests.m b/Firestore/Example/Tests/Remote/FSTWatchChangeTests.mm
index df2496b..d707e3c 100644
--- a/Firestore/Example/Tests/Remote/FSTWatchChangeTests.m
+++ b/Firestore/Example/Tests/Remote/FSTWatchChangeTests.mm
@@ -32,7 +32,7 @@ NS_ASSUME_NONNULL_BEGIN
@implementation FSTWatchChangeTests
- (void)testDocumentChange {
- FSTMaybeDocument *doc = FSTTestDoc(@"a/b", 1, @{}, NO);
+ FSTMaybeDocument *doc = FSTTestDoc("a/b", 1, @{}, NO);
FSTDocumentWatchChange *change =
[[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1, @2, @3 ]
removedTargetIDs:@[ @4, @5 ]
diff --git a/Firestore/Example/Tests/SpecTests/FSTLevelDBSpecTests.m b/Firestore/Example/Tests/SpecTests/FSTLevelDBSpecTests.mm
index a67f667..a67f667 100644
--- a/Firestore/Example/Tests/SpecTests/FSTLevelDBSpecTests.m
+++ b/Firestore/Example/Tests/SpecTests/FSTLevelDBSpecTests.mm
diff --git a/Firestore/Example/Tests/SpecTests/FSTMemorySpecTests.m b/Firestore/Example/Tests/SpecTests/FSTMemorySpecTests.mm
index 3030ab5..3030ab5 100644
--- a/Firestore/Example/Tests/SpecTests/FSTMemorySpecTests.m
+++ b/Firestore/Example/Tests/SpecTests/FSTMemorySpecTests.mm
diff --git a/Firestore/Example/Tests/SpecTests/FSTMockDatastore.h b/Firestore/Example/Tests/SpecTests/FSTMockDatastore.h
index 5420c2e..e1ea2fb 100644
--- a/Firestore/Example/Tests/SpecTests/FSTMockDatastore.h
+++ b/Firestore/Example/Tests/SpecTests/FSTMockDatastore.h
@@ -34,8 +34,6 @@ NS_ASSUME_NONNULL_BEGIN
*/
@property(nonatomic) int writeStreamRequestCount;
-+ (instancetype)mockDatastoreWithWorkerDispatchQueue:(FSTDispatchQueue *)workerDispatchQueue;
-
#pragma mark - Watch Stream manipulation.
/** Injects an Added WatchChange containing the given targetIDs. */
diff --git a/Firestore/Example/Tests/SpecTests/FSTMockDatastore.m b/Firestore/Example/Tests/SpecTests/FSTMockDatastore.mm
index 9a1d719..6715b24 100644
--- a/Firestore/Example/Tests/SpecTests/FSTMockDatastore.m
+++ b/Firestore/Example/Tests/SpecTests/FSTMockDatastore.mm
@@ -16,11 +16,8 @@
#import "Firestore/Example/Tests/SpecTests/FSTMockDatastore.h"
-#import "Firestore/Source/Auth/FSTEmptyCredentialsProvider.h"
-#import "Firestore/Source/Core/FSTDatabaseInfo.h"
#import "Firestore/Source/Core/FSTSnapshotVersion.h"
#import "Firestore/Source/Local/FSTQueryData.h"
-#import "Firestore/Source/Model/FSTDatabaseID.h"
#import "Firestore/Source/Model/FSTMutation.h"
#import "Firestore/Source/Remote/FSTSerializerBeta.h"
#import "Firestore/Source/Remote/FSTStream.h"
@@ -29,6 +26,17 @@
#import "Firestore/Example/Tests/Remote/FSTWatchChange+Testing.h"
+#include "Firestore/core/src/firebase/firestore/auth/credentials_provider.h"
+#include "Firestore/core/src/firebase/firestore/auth/empty_credentials_provider.h"
+#include "Firestore/core/src/firebase/firestore/core/database_info.h"
+#include "Firestore/core/src/firebase/firestore/model/database_id.h"
+#include "Firestore/core/src/firebase/firestore/util/string_apple.h"
+
+using firebase::firestore::auth::CredentialsProvider;
+using firebase::firestore::auth::EmptyCredentialsProvider;
+using firebase::firestore::core::DatabaseInfo;
+using firebase::firestore::model::DatabaseId;
+
@class GRPCProtoCall;
NS_ASSUME_NONNULL_BEGIN
@@ -39,17 +47,17 @@ NS_ASSUME_NONNULL_BEGIN
- (instancetype)initWithDatastore:(FSTMockDatastore *)datastore
workerDispatchQueue:(FSTDispatchQueue *)workerDispatchQueue
- credentials:(id<FSTCredentialsProvider>)credentials
+ credentials:(CredentialsProvider *)credentials
serializer:(FSTSerializerBeta *)serializer NS_DESIGNATED_INITIALIZER;
-- (instancetype)initWithDatabase:(FSTDatabaseInfo *)database
+- (instancetype)initWithDatabase:(const DatabaseInfo *)database
workerDispatchQueue:(FSTDispatchQueue *)workerDispatchQueue
- credentials:(id<FSTCredentialsProvider>)credentials
+ credentials:(CredentialsProvider *)credentials
serializer:(FSTSerializerBeta *)serializer NS_UNAVAILABLE;
-- (instancetype)initWithDatabase:(FSTDatabaseInfo *)database
+- (instancetype)initWithDatabase:(const DatabaseInfo *)database
workerDispatchQueue:(FSTDispatchQueue *)workerDispatchQueue
- credentials:(id<FSTCredentialsProvider>)credentials
+ credentials:(CredentialsProvider *)credentials
responseMessageClass:(Class)responseMessageClass NS_UNAVAILABLE;
@property(nonatomic, assign) BOOL open;
@@ -64,7 +72,7 @@ NS_ASSUME_NONNULL_BEGIN
- (instancetype)initWithDatastore:(FSTMockDatastore *)datastore
workerDispatchQueue:(FSTDispatchQueue *)workerDispatchQueue
- credentials:(id<FSTCredentialsProvider>)credentials
+ credentials:(CredentialsProvider *)credentials
serializer:(FSTSerializerBeta *)serializer {
self = [super initWithDatabase:datastore.databaseInfo
workerDispatchQueue:workerDispatchQueue
@@ -165,17 +173,17 @@ NS_ASSUME_NONNULL_BEGIN
- (instancetype)initWithDatastore:(FSTMockDatastore *)datastore
workerDispatchQueue:(FSTDispatchQueue *)workerDispatchQueue
- credentials:(id<FSTCredentialsProvider>)credentials
+ credentials:(CredentialsProvider *)credentials
serializer:(FSTSerializerBeta *)serializer NS_DESIGNATED_INITIALIZER;
-- (instancetype)initWithDatabase:(FSTDatabaseInfo *)database
+- (instancetype)initWithDatabase:(const DatabaseInfo *)database
workerDispatchQueue:(FSTDispatchQueue *)workerDispatchQueue
- credentials:(id<FSTCredentialsProvider>)credentials
+ credentials:(CredentialsProvider *)credentials
serializer:(FSTSerializerBeta *)serializer NS_UNAVAILABLE;
-- (instancetype)initWithDatabase:(FSTDatabaseInfo *)database
+- (instancetype)initWithDatabase:(const DatabaseInfo *)database
workerDispatchQueue:(FSTDispatchQueue *)workerDispatchQueue
- credentials:(id<FSTCredentialsProvider>)credentials
+ credentials:(CredentialsProvider *)credentials
responseMessageClass:(Class)responseMessageClass NS_UNAVAILABLE;
@property(nonatomic, strong, readonly) FSTMockDatastore *datastore;
@@ -188,7 +196,7 @@ NS_ASSUME_NONNULL_BEGIN
- (instancetype)initWithDatastore:(FSTMockDatastore *)datastore
workerDispatchQueue:(FSTDispatchQueue *)workerDispatchQueue
- credentials:(id<FSTCredentialsProvider>)credentials
+ credentials:(CredentialsProvider *)credentials
serializer:(FSTSerializerBeta *)serializer {
self = [super initWithDatabase:datastore.databaseInfo
workerDispatchQueue:workerDispatchQueue
@@ -274,47 +282,33 @@ NS_ASSUME_NONNULL_BEGIN
/** Properties implemented in FSTDatastore that are nonpublic. */
@property(nonatomic, strong, readonly) FSTDispatchQueue *workerDispatchQueue;
-@property(nonatomic, strong, readonly) id<FSTCredentialsProvider> credentials;
+@property(nonatomic, assign, readonly) CredentialsProvider *credentials;
@end
@implementation FSTMockDatastore
-+ (instancetype)mockDatastoreWithWorkerDispatchQueue:(FSTDispatchQueue *)workerDispatchQueue {
- FSTDatabaseID *databaseID = [FSTDatabaseID databaseIDWithProject:@"project" database:@"database"];
- FSTDatabaseInfo *databaseInfo = [FSTDatabaseInfo databaseInfoWithDatabaseID:databaseID
- persistenceKey:@"persistence"
- host:@"host"
- sslEnabled:NO];
-
- FSTEmptyCredentialsProvider *credentials = [[FSTEmptyCredentialsProvider alloc] init];
-
- return [[FSTMockDatastore alloc] initWithDatabaseInfo:databaseInfo
- workerDispatchQueue:workerDispatchQueue
- credentials:credentials];
-}
-
#pragma mark - Overridden FSTDatastore methods.
- (FSTWatchStream *)createWatchStream {
- FSTAssert(self.databaseInfo, @"DatabaseInfo must not be nil");
+ // FSTAssert(self.databaseInfo, @"DatabaseInfo must not be nil");
self.watchStream = [[FSTMockWatchStream alloc]
initWithDatastore:self
workerDispatchQueue:self.workerDispatchQueue
credentials:self.credentials
serializer:[[FSTSerializerBeta alloc]
- initWithDatabaseID:self.databaseInfo.databaseID]];
+ initWithDatabaseID:&self.databaseInfo->database_id()]];
return self.watchStream;
}
- (FSTWriteStream *)createWriteStream {
- FSTAssert(self.databaseInfo, @"DatabaseInfo must not be nil");
+ // FSTAssert(self.databaseInfo, @"DatabaseInfo must not be nil");
self.writeStream = [[FSTMockWriteStream alloc]
initWithDatastore:self
workerDispatchQueue:self.workerDispatchQueue
credentials:self.credentials
serializer:[[FSTSerializerBeta alloc]
- initWithDatabaseID:self.databaseInfo.databaseID]];
+ initWithDatabaseID:&self.databaseInfo->database_id()]];
return self.writeStream;
}
diff --git a/Firestore/Example/Tests/SpecTests/FSTSpecTests.m b/Firestore/Example/Tests/SpecTests/FSTSpecTests.mm
index 2c1b8db..128f825 100644
--- a/Firestore/Example/Tests/SpecTests/FSTSpecTests.m
+++ b/Firestore/Example/Tests/SpecTests/FSTSpecTests.mm
@@ -16,14 +16,15 @@
#import "Firestore/Example/Tests/SpecTests/FSTSpecTests.h"
+#import <FirebaseFirestore/FIRFirestoreErrors.h>
#import <GRPCClient/GRPCCall.h>
-#import "FirebaseFirestore/FIRFirestoreErrors.h"
-#import "Firestore/Source/Auth/FSTUser.h"
+#include <map>
+#include <utility>
+
#import "Firestore/Source/Core/FSTEventManager.h"
#import "Firestore/Source/Core/FSTQuery.h"
#import "Firestore/Source/Core/FSTSnapshotVersion.h"
-#import "Firestore/Source/Core/FSTViewSnapshot.h"
#import "Firestore/Source/Local/FSTEagerGarbageCollector.h"
#import "Firestore/Source/Local/FSTNoOpGarbageCollector.h"
#import "Firestore/Source/Local/FSTPersistence.h"
@@ -32,17 +33,26 @@
#import "Firestore/Source/Model/FSTDocumentKey.h"
#import "Firestore/Source/Model/FSTFieldValue.h"
#import "Firestore/Source/Model/FSTMutation.h"
-#import "Firestore/Source/Model/FSTPath.h"
#import "Firestore/Source/Remote/FSTExistenceFilter.h"
#import "Firestore/Source/Remote/FSTWatchChange.h"
#import "Firestore/Source/Util/FSTAssert.h"
#import "Firestore/Source/Util/FSTClasses.h"
+#import "Firestore/Source/Util/FSTDispatchQueue.h"
#import "Firestore/Source/Util/FSTLogger.h"
#import "Firestore/Example/Tests/Remote/FSTWatchChange+Testing.h"
#import "Firestore/Example/Tests/SpecTests/FSTSyncEngineTestDriver.h"
#import "Firestore/Example/Tests/Util/FSTHelpers.h"
+#include "Firestore/core/src/firebase/firestore/auth/user.h"
+#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+#include "Firestore/core/src/firebase/firestore/util/string_apple.h"
+
+namespace util = firebase::firestore::util;
+using firebase::firestore::auth::User;
+using firebase::firestore::model::DocumentKey;
+using firebase::firestore::model::TargetId;
+
NS_ASSUME_NONNULL_BEGIN
// Disables all other tests; useful for debugging. Multiple tests can have this tag and they'll all
@@ -103,11 +113,11 @@ static NSString *const kNoIOSTag = @"no-ios";
- (nullable FSTQuery *)parseQuery:(id)querySpec {
if ([querySpec isKindOfClass:[NSString class]]) {
- return [FSTQuery queryWithPath:[FSTResourcePath pathWithString:querySpec]];
+ return FSTTestQuery(util::MakeStringView((NSString *)querySpec));
} else if ([querySpec isKindOfClass:[NSDictionary class]]) {
NSDictionary *queryDict = (NSDictionary *)querySpec;
NSString *path = queryDict[@"path"];
- __block FSTQuery *query = [FSTQuery queryWithPath:[FSTResourcePath pathWithString:path]];
+ __block FSTQuery *query = FSTTestQuery(util::MakeStringView(path));
if (queryDict[@"limit"]) {
NSNumber *limit = queryDict[@"limit"];
query = [query queryBySettingLimit:limit.integerValue];
@@ -116,14 +126,16 @@ static NSString *const kNoIOSTag = @"no-ios";
NSArray *filters = queryDict[@"filters"];
[filters enumerateObjectsUsingBlock:^(NSArray *_Nonnull filter, NSUInteger idx,
BOOL *_Nonnull stop) {
- query = [query queryByAddingFilter:FSTTestFilter(filter[0], filter[1], filter[2])];
+ query = [query queryByAddingFilter:FSTTestFilter(util::MakeStringView(filter[0]), filter[1],
+ filter[2])];
}];
}
if (queryDict[@"orderBys"]) {
NSArray *orderBys = queryDict[@"orderBys"];
[orderBys enumerateObjectsUsingBlock:^(NSArray *_Nonnull orderBy, NSUInteger idx,
BOOL *_Nonnull stop) {
- query = [query queryByAddingSortOrder:FSTTestOrderBy(orderBy[0], orderBy[1])];
+ query = [query
+ queryByAddingSortOrder:FSTTestOrderBy(util::MakeStringView(orderBy[0]), orderBy[1])];
}];
}
return query;
@@ -145,7 +157,9 @@ static NSString *const kNoIOSTag = @"no-ios";
}
}
NSNumber *version = change[1];
- FSTDocument *doc = FSTTestDoc(change[0], version.longLongValue, change[2], hasMutations);
+ XCTAssert([change[0] isKindOfClass:[NSString class]]);
+ FSTDocument *doc = FSTTestDoc(util::MakeStringView((NSString *)change[0]), version.longLongValue,
+ change[2], hasMutations);
return [FSTDocumentViewChange changeWithDocument:doc type:type];
}
@@ -156,7 +170,7 @@ static NSString *const kNoIOSTag = @"no-ios";
FSTTargetID actualID = [self.driver addUserListenerWithQuery:query];
FSTTargetID expectedID = [listenSpec[0] intValue];
- XCTAssertEqual(actualID, expectedID);
+ XCTAssertEqual(actualID, expectedID, @"targetID assigned to listen");
}
- (void)doUnlisten:(NSArray *)unlistenSpec {
@@ -169,7 +183,8 @@ static NSString *const kNoIOSTag = @"no-ios";
}
- (void)doPatch:(NSArray *)patchSpec {
- [self.driver writeUserMutation:FSTTestPatchMutation(patchSpec[0], patchSpec[1], nil)];
+ [self.driver
+ writeUserMutation:FSTTestPatchMutation(util::MakeStringView(patchSpec[0]), patchSpec[1], {})];
}
- (void)doDelete:(NSString *)key {
@@ -237,7 +252,7 @@ static NSString *const kNoIOSTag = @"no-ios";
}
} else if (watchEntity[@"doc"]) {
NSArray *docSpec = watchEntity[@"doc"];
- FSTDocumentKey *key = [FSTDocumentKey keyWithPathString:docSpec[0]];
+ FSTDocumentKey *key = FSTTestDocKey(docSpec[0]);
FSTObjectValue *value = FSTTestObjectValue(docSpec[2]);
FSTSnapshotVersion *version = [self parseVersion:docSpec[1]];
FSTMaybeDocument *doc =
@@ -249,7 +264,7 @@ static NSString *const kNoIOSTag = @"no-ios";
document:doc];
[self.driver receiveWatchChange:change snapshotVersion:[self parseVersion:watchSnapshot]];
} else if (watchEntity[@"key"]) {
- FSTDocumentKey *docKey = [FSTDocumentKey keyWithPathString:watchEntity[@"key"]];
+ FSTDocumentKey *docKey = FSTTestDocKey(watchEntity[@"key"]);
FSTWatchChange *change =
[[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[]
removedTargetIDs:watchEntity[@"removedTargets"]
@@ -285,6 +300,11 @@ static NSString *const kNoIOSTag = @"no-ios";
- (void)doWatchStreamClose:(NSDictionary *)closeSpec {
NSDictionary *errorSpec = closeSpec[@"error"];
int code = ((NSNumber *)(errorSpec[@"code"])).intValue;
+
+ NSNumber *runBackoffTimer = closeSpec[@"runBackoffTimer"];
+ // TODO(b/72313632): Incorporate backoff in iOS Spec Tests.
+ FSTAssert(runBackoffTimer.boolValue, @"iOS Spec Tests don't support backoff.");
+
[self.driver receiveWatchStreamError:code userInfo:errorSpec];
}
@@ -318,6 +338,27 @@ static NSString *const kNoIOSTag = @"no-ios";
}
}
+- (void)doRunTimer:(NSString *)timer {
+ FSTTimerID timerID;
+ if ([timer isEqualToString:@"all"]) {
+ timerID = FSTTimerIDAll;
+ } else if ([timer isEqualToString:@"listen_stream_idle"]) {
+ timerID = FSTTimerIDListenStreamIdle;
+ } else if ([timer isEqualToString:@"listen_stream_connection_backoff"]) {
+ timerID = FSTTimerIDListenStreamConnectionBackoff;
+ } else if ([timer isEqualToString:@"write_stream_idle"]) {
+ timerID = FSTTimerIDWriteStreamIdle;
+ } else if ([timer isEqualToString:@"write_stream_connection_backoff"]) {
+ timerID = FSTTimerIDWriteStreamConnectionBackoff;
+ } else if ([timer isEqualToString:@"online_state_timeout"]) {
+ timerID = FSTTimerIDOnlineStateTimeout;
+ } else {
+ FSTFail(@"runTimer spec step specified unknown timer: %@", timer);
+ }
+
+ [self.driver runTimer:timerID];
+}
+
- (void)doDisableNetwork {
[self.driver disableNetwork];
}
@@ -327,15 +368,17 @@ static NSString *const kNoIOSTag = @"no-ios";
}
- (void)doChangeUser:(id)UID {
- FSTUser *user = [UID isEqual:[NSNull null]] ? [FSTUser unauthenticatedUser]
- : [[FSTUser alloc] initWithUID:UID];
- [self.driver changeUser:user];
+ if ([UID isEqual:[NSNull null]]) {
+ UID = nil;
+ }
+ [self.driver changeUser:User::FromUid(UID)];
}
- (void)doRestart {
// Any outstanding user writes should be automatically re-sent, so we want to preserve them
// when re-creating the driver.
- FSTOutstandingWriteQueues *outstandingWrites = self.driver.outstandingWrites;
+ FSTOutstandingWriteQueues outstandingWrites = self.driver.outstandingWrites;
+ User currentUser = self.driver.currentUser;
[self.driver shutdown];
@@ -347,7 +390,7 @@ static NSString *const kNoIOSTag = @"no-ios";
self.driver = [[FSTSyncEngineTestDriver alloc] initWithPersistence:self.driverPersistence
garbageCollector:self.garbageCollector
- initialUser:self.driver.currentUser
+ initialUser:currentUser
outstandingWrites:outstandingWrites];
[self.driver start];
}
@@ -384,6 +427,8 @@ static NSString *const kNoIOSTag = @"no-ios";
[self doWriteAck:step[@"writeAck"]];
} else if (step[@"failWrite"]) {
[self doFailWrite:step[@"failWrite"]];
+ } else if (step[@"runTimer"]) {
+ [self doRunTimer:step[@"runTimer"]];
} else if (step[@"enableNetwork"]) {
if ([step[@"enableNetwork"] boolValue]) {
[self doEnableNetwork];
@@ -503,6 +548,7 @@ static NSString *const kNoIOSTag = @"no-ios";
expectedActiveTargets[@(targetID)] =
[[FSTQueryData alloc] initWithQuery:query
targetID:targetID
+ listenSequenceNumber:0
purpose:FSTQueryPurposeListen
snapshotVersion:[FSTSnapshotVersion noVersion]
resumeToken:resumeToken];
@@ -519,22 +565,22 @@ static NSString *const kNoIOSTag = @"no-ios";
- (void)validateLimboDocuments {
// Make a copy so it can modified while checking against the expected limbo docs.
- NSMutableDictionary<FSTDocumentKey *, FSTBoxedTargetID *> *actualLimboDocs =
- [NSMutableDictionary dictionaryWithDictionary:self.driver.currentLimboDocuments];
+ std::map<DocumentKey, TargetId> actualLimboDocs = self.driver.currentLimboDocuments;
// Validate that each limbo doc has an expected active target
- [actualLimboDocs enumerateKeysAndObjectsUsingBlock:^(FSTDocumentKey *key,
- FSTBoxedTargetID *targetID, BOOL *stop) {
- XCTAssertNotNil(self.driver.expectedActiveTargets[targetID],
+ for (const auto &kv : actualLimboDocs) {
+ XCTAssertNotNil(self.driver.expectedActiveTargets[@(kv.second)],
@"Found limbo doc without an expected active target");
- }];
+ }
for (FSTDocumentKey *expectedLimboDoc in self.driver.expectedLimboDocuments) {
- XCTAssertNotNil(actualLimboDocs[expectedLimboDoc],
- @"Expected doc to be in limbo, but was not: %@", expectedLimboDoc);
- [actualLimboDocs removeObjectForKey:expectedLimboDoc];
+ XCTAssert(actualLimboDocs.find(expectedLimboDoc) != actualLimboDocs.end(),
+ @"Expected doc to be in limbo, but was not: %@", expectedLimboDoc);
+ actualLimboDocs.erase(expectedLimboDoc);
}
- XCTAssertTrue(actualLimboDocs.count == 0, "Unexpected docs in limbo: %@", actualLimboDocs);
+ XCTAssertTrue(actualLimboDocs.empty(), "%lu Unexpected docs in limbo, the first one is <%s, %d>",
+ actualLimboDocs.size(), actualLimboDocs.begin()->first.ToString().c_str(),
+ actualLimboDocs.begin()->second);
}
- (void)validateActiveTargets {
diff --git a/Firestore/Example/Tests/SpecTests/FSTSyncEngineTestDriver.h b/Firestore/Example/Tests/SpecTests/FSTSyncEngineTestDriver.h
index 3d031bd..ac44cb5 100644
--- a/Firestore/Example/Tests/SpecTests/FSTSyncEngineTestDriver.h
+++ b/Firestore/Example/Tests/SpecTests/FSTSyncEngineTestDriver.h
@@ -16,7 +16,15 @@
#import <Foundation/Foundation.h>
+#include <map>
+#include <unordered_map>
+
#import "Firestore/Source/Core/FSTTypes.h"
+#import "Firestore/Source/Remote/FSTRemoteStore.h"
+#import "Firestore/Source/Util/FSTDispatchQueue.h"
+
+#include "Firestore/core/src/firebase/firestore/auth/user.h"
+#include "Firestore/core/src/firebase/firestore/model/document_key.h"
@class FSTDocumentKey;
@class FSTMutation;
@@ -24,7 +32,6 @@
@class FSTQuery;
@class FSTQueryData;
@class FSTSnapshotVersion;
-@class FSTUser;
@class FSTViewSnapshot;
@class FSTWatchChange;
@protocol FSTGarbageCollector;
@@ -53,7 +60,10 @@ NS_ASSUME_NONNULL_BEGIN
@end
/** Mapping of user => array of FSTMutations for that user. */
-typedef NSDictionary<FSTUser *, NSArray<FSTOutstandingWrite *> *> FSTOutstandingWriteQueues;
+typedef std::unordered_map<firebase::firestore::auth::User,
+ NSMutableArray<FSTOutstandingWrite *> *,
+ firebase::firestore::auth::HashUser>
+ FSTOutstandingWriteQueues;
/**
* A test driver for FSTSyncEngine that allows simulated event delivery and capture. As much as
@@ -76,7 +86,7 @@ typedef NSDictionary<FSTUser *, NSArray<FSTOutstandingWrite *> *> FSTOutstanding
*
* Each method on the driver injects a different event into the system.
*/
-@interface FSTSyncEngineTestDriver : NSObject
+@interface FSTSyncEngineTestDriver : NSObject <FSTOnlineStateDelegate>
/**
* Initializes the underlying FSTSyncEngine with the given local persistence implementation and
@@ -92,8 +102,8 @@ typedef NSDictionary<FSTUser *, NSArray<FSTOutstandingWrite *> *> FSTOutstanding
*/
- (instancetype)initWithPersistence:(id<FSTPersistence>)persistence
garbageCollector:(id<FSTGarbageCollector>)garbageCollector
- initialUser:(FSTUser *)initialUser
- outstandingWrites:(FSTOutstandingWriteQueues *)outstandingWrites
+ initialUser:(const firebase::firestore::auth::User &)initialUser
+ outstandingWrites:(const FSTOutstandingWriteQueues &)outstandingWrites
NS_DESIGNATED_INITIALIZER;
- (instancetype)init NS_UNAVAILABLE;
@@ -217,11 +227,16 @@ typedef NSDictionary<FSTUser *, NSArray<FSTOutstandingWrite *> *> FSTOutstanding
- (void)enableNetwork;
/**
+ * Runs a pending timer callback on the FSTDispatchQueue.
+ */
+- (void)runTimer:(FSTTimerID)timerID;
+
+/**
* Switches the FSTSyncEngine to a new user. The test driver tracks the outstanding mutations for
* each user, so future receiveWriteAck/Error operations will validate the write sent to the mock
* datastore matches the next outstanding write for that user.
*/
-- (void)changeUser:(FSTUser *)user;
+- (void)changeUser:(const firebase::firestore::auth::User &)user;
/**
* Returns all query events generated by the FSTSyncEngine in response to the event injection
@@ -229,6 +244,10 @@ typedef NSDictionary<FSTUser *, NSArray<FSTOutstandingWrite *> *> FSTOutstanding
*/
- (NSArray<FSTQueryEvent *> *)capturedEventsSinceLastCall;
+/** The current set of documents in limbo. */
+- (std::map<firebase::firestore::model::DocumentKey, firebase::firestore::model::TargetId>)
+ currentLimboDocuments;
+
/**
* The writes that have been sent to the FSTSyncEngine via writeUserMutation: but not yet
* acknowledged by calling receiveWriteAck/Error:. They are tracked per-user.
@@ -245,14 +264,10 @@ typedef NSDictionary<FSTUser *, NSArray<FSTOutstandingWrite *> *> FSTOutstanding
* sentWritesCount, but not necessarily, since the FSTRemoteStore limits the number of
* outstanding writes to the backend at a given time.
*/
-@property(nonatomic, strong, readonly) FSTOutstandingWriteQueues *outstandingWrites;
+@property(nonatomic, assign, readonly) const FSTOutstandingWriteQueues &outstandingWrites;
/** The current user for the FSTSyncEngine; determines which mutation queue is active. */
-@property(nonatomic, strong, readonly) FSTUser *currentUser;
-
-/** The current set of documents in limbo. */
-@property(nonatomic, strong, readonly)
- NSDictionary<FSTDocumentKey *, FSTBoxedTargetID *> *currentLimboDocuments;
+@property(nonatomic, assign, readonly) const firebase::firestore::auth::User &currentUser;
/** The expected set of documents in limbo. */
@property(nonatomic, strong, readwrite) NSSet<FSTDocumentKey *> *expectedLimboDocuments;
diff --git a/Firestore/Example/Tests/SpecTests/FSTSyncEngineTestDriver.m b/Firestore/Example/Tests/SpecTests/FSTSyncEngineTestDriver.mm
index 896a292..f167ce5 100644
--- a/Firestore/Example/Tests/SpecTests/FSTSyncEngineTestDriver.m
+++ b/Firestore/Example/Tests/SpecTests/FSTSyncEngineTestDriver.mm
@@ -16,10 +16,12 @@
#import "Firestore/Example/Tests/SpecTests/FSTSyncEngineTestDriver.h"
+#import <FirebaseFirestore/FIRFirestoreErrors.h>
#import <GRPCClient/GRPCCall.h>
-#import "FirebaseFirestore/FIRFirestoreErrors.h"
-#import "Firestore/Source/Auth/FSTUser.h"
+#include <map>
+#include <unordered_map>
+
#import "Firestore/Source/Core/FSTEventManager.h"
#import "Firestore/Source/Core/FSTQuery.h"
#import "Firestore/Source/Core/FSTSnapshotVersion.h"
@@ -30,12 +32,25 @@
#import "Firestore/Source/Remote/FSTDatastore.h"
#import "Firestore/Source/Remote/FSTWatchChange.h"
#import "Firestore/Source/Util/FSTAssert.h"
-#import "Firestore/Source/Util/FSTDispatchQueue.h"
#import "Firestore/Source/Util/FSTLogger.h"
#import "Firestore/Example/Tests/Core/FSTSyncEngine+Testing.h"
#import "Firestore/Example/Tests/SpecTests/FSTMockDatastore.h"
+#include "Firestore/core/src/firebase/firestore/auth/empty_credentials_provider.h"
+#include "Firestore/core/src/firebase/firestore/auth/user.h"
+#include "Firestore/core/src/firebase/firestore/core/database_info.h"
+#include "Firestore/core/src/firebase/firestore/model/database_id.h"
+#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+
+using firebase::firestore::auth::EmptyCredentialsProvider;
+using firebase::firestore::auth::HashUser;
+using firebase::firestore::auth::User;
+using firebase::firestore::core::DatabaseInfo;
+using firebase::firestore::model::DatabaseId;
+using firebase::firestore::model::DocumentKey;
+using firebase::firestore::model::TargetId;
+
NS_ASSUME_NONNULL_BEGIN
@implementation FSTQueryEvent
@@ -60,6 +75,7 @@ NS_ASSUME_NONNULL_BEGIN
@property(nonatomic, strong, readonly) FSTRemoteStore *remoteStore;
@property(nonatomic, strong, readonly) FSTLocalStore *localStore;
@property(nonatomic, strong, readonly) FSTSyncEngine *syncEngine;
+@property(nonatomic, strong, readonly) FSTDispatchQueue *dispatchQueue;
#pragma mark - Data structures for holding events sent by the watch stream.
@@ -71,47 +87,53 @@ NS_ASSUME_NONNULL_BEGIN
@property(nonatomic, strong, readonly)
NSMutableDictionary<FSTQuery *, FSTQueryListener *> *queryListeners;
-#pragma mark - Other data structures.
-@property(nonatomic, strong, readwrite) FSTUser *currentUser;
-
@end
@implementation FSTSyncEngineTestDriver {
// ivar is declared as mutable.
- NSMutableDictionary<FSTUser *, NSMutableArray<FSTOutstandingWrite *> *> *_outstandingWrites;
+ std::unordered_map<User, NSMutableArray<FSTOutstandingWrite *> *, HashUser> _outstandingWrites;
+
+ DatabaseInfo _databaseInfo;
+ User _currentUser;
+ EmptyCredentialsProvider _credentialProvider;
}
- (instancetype)initWithPersistence:(id<FSTPersistence>)persistence
garbageCollector:(id<FSTGarbageCollector>)garbageCollector {
return [self initWithPersistence:persistence
garbageCollector:garbageCollector
- initialUser:[FSTUser unauthenticatedUser]
- outstandingWrites:@{}];
+ initialUser:User::Unauthenticated()
+ outstandingWrites:{}];
}
- (instancetype)initWithPersistence:(id<FSTPersistence>)persistence
garbageCollector:(id<FSTGarbageCollector>)garbageCollector
- initialUser:(FSTUser *)initialUser
- outstandingWrites:(FSTOutstandingWriteQueues *)outstandingWrites {
+ initialUser:(const User &)initialUser
+ outstandingWrites:(const FSTOutstandingWriteQueues &)outstandingWrites {
if (self = [super init]) {
- // Create mutable copy of outstandingWrites.
- _outstandingWrites = [NSMutableDictionary dictionary];
- [outstandingWrites enumerateKeysAndObjectsUsingBlock:^(
- FSTUser *user, NSArray<FSTOutstandingWrite *> *writes, BOOL *stop) {
- _outstandingWrites[user] = [writes mutableCopy];
- }];
+ // Do a deep copy.
+ for (const auto &pair : outstandingWrites) {
+ _outstandingWrites[pair.first] = [pair.second mutableCopy];
+ }
_events = [NSMutableArray array];
+ _databaseInfo = {DatabaseId{"project", "database"}, "persistence", "host", false};
+
// Set up the sync engine and various stores.
- dispatch_queue_t mainQueue = dispatch_get_main_queue();
- FSTDispatchQueue *dispatchQueue = [FSTDispatchQueue queueWith:mainQueue];
+ dispatch_queue_t queue =
+ dispatch_queue_create("sync_engine_test_driver", DISPATCH_QUEUE_SERIAL);
+ _dispatchQueue = [FSTDispatchQueue queueWith:queue];
_localStore = [[FSTLocalStore alloc] initWithPersistence:persistence
garbageCollector:garbageCollector
initialUser:initialUser];
- _datastore = [FSTMockDatastore mockDatastoreWithWorkerDispatchQueue:dispatchQueue];
+ _datastore = [[FSTMockDatastore alloc] initWithDatabaseInfo:&_databaseInfo
+ workerDispatchQueue:_dispatchQueue
+ credentials:&_credentialProvider];
- _remoteStore = [FSTRemoteStore remoteStoreWithLocalStore:_localStore datastore:_datastore];
+ _remoteStore = [[FSTRemoteStore alloc] initWithLocalStore:_localStore
+ datastore:_datastore
+ workerDispatchQueue:_dispatchQueue];
_syncEngine = [[FSTSyncEngine alloc] initWithLocalStore:_localStore
remoteStore:_remoteStore
@@ -119,7 +141,7 @@ NS_ASSUME_NONNULL_BEGIN
_remoteStore.syncEngine = _syncEngine;
_eventManager = [FSTEventManager eventManagerWithSyncEngine:_syncEngine];
- _remoteStore.onlineStateDelegate = _eventManager;
+ _remoteStore.onlineStateDelegate = self;
// Set up internal event tracking for the spec tests.
NSMutableArray<FSTQueryEvent *> *events = [NSMutableArray array];
@@ -139,9 +161,24 @@ NS_ASSUME_NONNULL_BEGIN
return self;
}
+- (const FSTOutstandingWriteQueues &)outstandingWrites {
+ return _outstandingWrites;
+}
+
+- (const User &)currentUser {
+ return _currentUser;
+}
+
+- (void)applyChangedOnlineState:(FSTOnlineState)onlineState {
+ [self.syncEngine applyChangedOnlineState:onlineState];
+ [self.eventManager applyChangedOnlineState:onlineState];
+}
+
- (void)start {
- [self.localStore start];
- [self.remoteStore start];
+ [self.dispatchQueue dispatchSync:^{
+ [self.localStore start];
+ [self.remoteStore start];
+ }];
}
- (void)validateUsage {
@@ -152,8 +189,9 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)shutdown {
- [self.remoteStore shutdown];
- [self.localStore shutdown];
+ [self.dispatchQueue dispatchSync:^{
+ [self.remoteStore shutdown];
+ }];
}
- (void)validateNextWriteSent:(FSTMutation *)expectedWrite {
@@ -180,19 +218,29 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)disableNetwork {
- // Make sure to execute all writes that are currently queued. This allows us
- // to assert on the total number of requests sent before shutdown.
- [self.remoteStore fillWritePipeline];
- [self.remoteStore disableNetwork];
+ [self.dispatchQueue dispatchSync:^{
+ // Make sure to execute all writes that are currently queued. This allows us
+ // to assert on the total number of requests sent before shutdown.
+ [self.remoteStore fillWritePipeline];
+ [self.remoteStore disableNetwork];
+ }];
}
- (void)enableNetwork {
- [self.remoteStore enableNetwork];
+ [self.dispatchQueue dispatchSync:^{
+ [self.remoteStore enableNetwork];
+ }];
}
-- (void)changeUser:(FSTUser *)user {
- self.currentUser = user;
- [self.syncEngine userDidChange:user];
+- (void)runTimer:(FSTTimerID)timerID {
+ [self.dispatchQueue runDelayedCallbacksUntil:timerID];
+}
+
+- (void)changeUser:(const User &)user {
+ _currentUser = user;
+ [self.dispatchQueue dispatchSync:^{
+ [self.syncEngine userDidChange:user];
+ }];
}
- (FSTOutstandingWrite *)receiveWriteAckWithVersion:(FSTSnapshotVersion *)commitVersion
@@ -202,7 +250,9 @@ NS_ASSUME_NONNULL_BEGIN
[[self currentOutstandingWrites] removeObjectAtIndex:0];
[self validateNextWriteSent:write.write];
- [self.datastore ackWriteWithVersion:commitVersion mutationResults:mutationResults];
+ [self.dispatchQueue dispatchSync:^{
+ [self.datastore ackWriteWithVersion:commitVersion mutationResults:mutationResults];
+ }];
return write;
}
@@ -222,7 +272,9 @@ NS_ASSUME_NONNULL_BEGIN
}
FSTLog(@"Failing a write.");
- [self.datastore failWriteWithError:error];
+ [self.dispatchQueue dispatchSync:^{
+ [self.datastore failWriteWithError:error];
+ }];
return write;
}
@@ -250,13 +302,19 @@ NS_ASSUME_NONNULL_BEGIN
[self.events addObject:event];
}];
self.queryListeners[query] = listener;
- return [self.eventManager addListener:listener];
+ __block FSTTargetID targetID;
+ [self.dispatchQueue dispatchSync:^{
+ targetID = [self.eventManager addListener:listener];
+ }];
+ return targetID;
}
- (void)removeUserListenerWithQuery:(FSTQuery *)query {
FSTQueryListener *listener = self.queryListeners[query];
[self.queryListeners removeObjectForKey:query];
- [self.eventManager removeListener:listener];
+ [self.dispatchQueue dispatchSync:^{
+ [self.eventManager removeListener:listener];
+ }];
}
- (void)writeUserMutation:(FSTMutation *)mutation {
@@ -264,31 +322,37 @@ NS_ASSUME_NONNULL_BEGIN
write.write = mutation;
[[self currentOutstandingWrites] addObject:write];
FSTLog(@"sending a user write.");
- [self.syncEngine writeMutations:@[ mutation ]
- completion:^(NSError *_Nullable error) {
- FSTLog(@"A callback was called with error: %@", error);
- write.done = YES;
- write.error = error;
- }];
+ [self.dispatchQueue dispatchSync:^{
+ [self.syncEngine writeMutations:@[ mutation ]
+ completion:^(NSError *_Nullable error) {
+ FSTLog(@"A callback was called with error: %@", error);
+ write.done = YES;
+ write.error = error;
+ }];
+ }];
}
- (void)receiveWatchChange:(FSTWatchChange *)change
snapshotVersion:(FSTSnapshotVersion *_Nullable)snapshot {
- [self.datastore writeWatchChange:change snapshotVersion:snapshot];
+ [self.dispatchQueue dispatchSync:^{
+ [self.datastore writeWatchChange:change snapshotVersion:snapshot];
+ }];
}
- (void)receiveWatchStreamError:(int)errorCode userInfo:(NSDictionary<NSString *, id> *)userInfo {
NSError *error =
[NSError errorWithDomain:FIRFirestoreErrorDomain code:errorCode userInfo:userInfo];
- [self.datastore failWatchStreamWithError:error];
- // Unlike web, stream should re-open synchronously (if we have any listeners)
- if (self.queryListeners.count > 0) {
- FSTAssert(self.datastore.isWatchStreamOpen, @"Watch stream is open");
- }
+ [self.dispatchQueue dispatchSync:^{
+ [self.datastore failWatchStreamWithError:error];
+ // Unlike web, stream should re-open synchronously (if we have any listeners)
+ if (self.queryListeners.count > 0) {
+ FSTAssert(self.datastore.isWatchStreamOpen, @"Watch stream is open");
+ }
+ }];
}
-- (NSDictionary<FSTDocumentKey *, FSTBoxedTargetID *> *)currentLimboDocuments {
+- (std::map<DocumentKey, TargetId>)currentLimboDocuments {
return [self.syncEngine currentLimboDocuments];
}
@@ -299,10 +363,10 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark - Helper Methods
- (NSMutableArray<FSTOutstandingWrite *> *)currentOutstandingWrites {
- NSMutableArray<FSTOutstandingWrite *> *writes = _outstandingWrites[self.currentUser];
+ NSMutableArray<FSTOutstandingWrite *> *writes = _outstandingWrites[_currentUser];
if (!writes) {
writes = [NSMutableArray array];
- _outstandingWrites[self.currentUser] = writes;
+ _outstandingWrites[_currentUser] = writes;
}
return writes;
}
diff --git a/Firestore/Example/Tests/SpecTests/json/existence_filter_spec_test.json b/Firestore/Example/Tests/SpecTests/json/existence_filter_spec_test.json
index ab42241..abd2cf4 100644
--- a/Firestore/Example/Tests/SpecTests/json/existence_filter_spec_test.json
+++ b/Firestore/Example/Tests/SpecTests/json/existence_filter_spec_test.json
@@ -337,7 +337,8 @@
"error": {
"code": 14,
"message": "Simulated Backend Error"
- }
+ },
+ "runBackoffTimer": true
},
"stateExpect": {
"activeTargets": {
diff --git a/Firestore/Example/Tests/SpecTests/json/listen_spec_test.json b/Firestore/Example/Tests/SpecTests/json/listen_spec_test.json
index e607710..7bfe557 100644
--- a/Firestore/Example/Tests/SpecTests/json/listen_spec_test.json
+++ b/Firestore/Example/Tests/SpecTests/json/listen_spec_test.json
@@ -1608,7 +1608,19 @@
"stateExpect": {
"activeTargets": {},
"limboDocs": []
- }
+ },
+ "expect": [
+ {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "errorCode": 0,
+ "fromCache": true,
+ "hasPendingWrites": false
+ }
+ ]
},
{
"enableNetwork": true,
diff --git a/Firestore/Example/Tests/SpecTests/json/offline_spec_test.json b/Firestore/Example/Tests/SpecTests/json/offline_spec_test.json
index f542a6e..1af4c16 100644
--- a/Firestore/Example/Tests/SpecTests/json/offline_spec_test.json
+++ b/Firestore/Example/Tests/SpecTests/json/offline_spec_test.json
@@ -34,7 +34,8 @@
"error": {
"code": 14,
"message": "Simulated Backend Error"
- }
+ },
+ "runBackoffTimer": true
}
},
{
@@ -42,7 +43,8 @@
"error": {
"code": 14,
"message": "Simulated Backend Error"
- }
+ },
+ "runBackoffTimer": true
},
"expect": [
{
@@ -62,7 +64,8 @@
"error": {
"code": 14,
"message": "Simulated Backend Error"
- }
+ },
+ "runBackoffTimer": true
}
},
{
@@ -70,7 +73,8 @@
"error": {
"code": 14,
"message": "Simulated Backend Error"
- }
+ },
+ "runBackoffTimer": true
}
}
]
@@ -115,7 +119,8 @@
"error": {
"code": 14,
"message": "Simulated Backend Error"
- }
+ },
+ "runBackoffTimer": true
}
},
{
@@ -123,7 +128,8 @@
"error": {
"code": 14,
"message": "Simulated Backend Error"
- }
+ },
+ "runBackoffTimer": true
}
},
{
@@ -131,7 +137,8 @@
"error": {
"code": 14,
"message": "Simulated Backend Error"
- }
+ },
+ "runBackoffTimer": true
},
"expect": [
{
@@ -151,7 +158,8 @@
"error": {
"code": 14,
"message": "Simulated Backend Error"
- }
+ },
+ "runBackoffTimer": true
}
},
{
@@ -159,7 +167,8 @@
"error": {
"code": 14,
"message": "Simulated Backend Error"
- }
+ },
+ "runBackoffTimer": true
}
}
]
@@ -199,7 +208,8 @@
"error": {
"code": 14,
"message": "Simulated Backend Error"
- }
+ },
+ "runBackoffTimer": true
}
},
{
@@ -207,7 +217,8 @@
"error": {
"code": 14,
"message": "Simulated Backend Error"
- }
+ },
+ "runBackoffTimer": true
},
"expect": [
{
@@ -240,7 +251,8 @@
"error": {
"code": 14,
"message": "Simulated Backend Error"
- }
+ },
+ "runBackoffTimer": true
}
},
{
@@ -270,6 +282,640 @@
"error": {
"code": 14,
"message": "Simulated Backend Error"
+ },
+ "runBackoffTimer": true
+ }
+ },
+ {
+ "watchStreamClose": {
+ "error": {
+ "code": 14,
+ "message": "Simulated Backend Error"
+ },
+ "runBackoffTimer": true
+ },
+ "expect": [
+ {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "errorCode": 0,
+ "fromCache": true,
+ "hasPendingWrites": false
+ }
+ ]
+ }
+ ]
+ },
+ "Queries revert to fromCache=true when offline.": {
+ "describeName": "Offline:",
+ "itName": "Queries revert to fromCache=true when offline.",
+ "tags": [],
+ "config": {
+ "useGarbageCollection": true
+ },
+ "steps": [
+ {
+ "userListen": [
+ 2,
+ {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ }
+ ],
+ "stateExpect": {
+ "activeTargets": {
+ "2": {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "resumeToken": ""
+ }
+ }
+ }
+ },
+ {
+ "watchAck": [
+ 2
+ ]
+ },
+ {
+ "watchEntity": {
+ "docs": [
+ [
+ "collection/a",
+ 1000,
+ {
+ "key": "a"
+ }
+ ]
+ ],
+ "targets": [
+ 2
+ ]
+ }
+ },
+ {
+ "watchCurrent": [
+ [
+ 2
+ ],
+ "resume-token-1000"
+ ],
+ "watchSnapshot": 1000,
+ "expect": [
+ {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "added": [
+ [
+ "collection/a",
+ 1000,
+ {
+ "key": "a"
+ }
+ ]
+ ],
+ "errorCode": 0,
+ "fromCache": false,
+ "hasPendingWrites": false
+ }
+ ]
+ },
+ {
+ "watchStreamClose": {
+ "error": {
+ "code": 14,
+ "message": "Simulated Backend Error"
+ },
+ "runBackoffTimer": true
+ },
+ "stateExpect": {
+ "activeTargets": {
+ "2": {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "resumeToken": "resume-token-1000"
+ }
+ }
+ }
+ },
+ {
+ "watchStreamClose": {
+ "error": {
+ "code": 14,
+ "message": "Simulated Backend Error"
+ },
+ "runBackoffTimer": true
+ }
+ },
+ {
+ "watchStreamClose": {
+ "error": {
+ "code": 14,
+ "message": "Simulated Backend Error"
+ },
+ "runBackoffTimer": true
+ },
+ "expect": [
+ {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "errorCode": 0,
+ "fromCache": true,
+ "hasPendingWrites": false
+ }
+ ]
+ },
+ {
+ "watchAck": [
+ 2
+ ]
+ },
+ {
+ "watchEntity": {
+ "docs": [],
+ "targets": [
+ 2
+ ]
+ }
+ },
+ {
+ "watchCurrent": [
+ [
+ 2
+ ],
+ "resume-token-1000"
+ ],
+ "watchSnapshot": 1000,
+ "expect": [
+ {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "errorCode": 0,
+ "fromCache": false,
+ "hasPendingWrites": false
+ }
+ ]
+ }
+ ]
+ },
+ "Queries with limbo documents handle going offline.": {
+ "describeName": "Offline:",
+ "itName": "Queries with limbo documents handle going offline.",
+ "tags": [],
+ "config": {
+ "useGarbageCollection": true
+ },
+ "steps": [
+ {
+ "userListen": [
+ 2,
+ {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ }
+ ],
+ "stateExpect": {
+ "activeTargets": {
+ "2": {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "resumeToken": ""
+ }
+ }
+ }
+ },
+ {
+ "watchAck": [
+ 2
+ ]
+ },
+ {
+ "watchEntity": {
+ "docs": [
+ [
+ "collection/a",
+ 1000,
+ {
+ "key": "a"
+ }
+ ]
+ ],
+ "targets": [
+ 2
+ ]
+ }
+ },
+ {
+ "watchCurrent": [
+ [
+ 2
+ ],
+ "resume-token-1000"
+ ],
+ "watchSnapshot": 1000,
+ "expect": [
+ {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "added": [
+ [
+ "collection/a",
+ 1000,
+ {
+ "key": "a"
+ }
+ ]
+ ],
+ "errorCode": 0,
+ "fromCache": false,
+ "hasPendingWrites": false
+ }
+ ]
+ },
+ {
+ "watchReset": [
+ 2
+ ]
+ },
+ {
+ "watchCurrent": [
+ [
+ 2
+ ],
+ "resume-token-1001"
+ ],
+ "watchSnapshot": 1001,
+ "stateExpect": {
+ "limboDocs": [
+ "collection/a"
+ ],
+ "activeTargets": {
+ "1": {
+ "query": {
+ "path": "collection/a",
+ "filters": [],
+ "orderBys": []
+ },
+ "resumeToken": ""
+ },
+ "2": {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "resumeToken": ""
+ }
+ }
+ },
+ "expect": [
+ {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "errorCode": 0,
+ "fromCache": true,
+ "hasPendingWrites": false
+ }
+ ]
+ },
+ {
+ "watchStreamClose": {
+ "error": {
+ "code": 14,
+ "message": "Simulated Backend Error"
+ },
+ "runBackoffTimer": true
+ },
+ "stateExpect": {
+ "activeTargets": {
+ "1": {
+ "query": {
+ "path": "collection/a",
+ "filters": [],
+ "orderBys": []
+ },
+ "resumeToken": ""
+ },
+ "2": {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "resumeToken": "resume-token-1001"
+ }
+ }
+ }
+ },
+ {
+ "watchStreamClose": {
+ "error": {
+ "code": 14,
+ "message": "Simulated Backend Error"
+ },
+ "runBackoffTimer": true
+ }
+ },
+ {
+ "watchStreamClose": {
+ "error": {
+ "code": 14,
+ "message": "Simulated Backend Error"
+ },
+ "runBackoffTimer": true
+ }
+ },
+ {
+ "watchAck": [
+ 2
+ ]
+ },
+ {
+ "watchEntity": {
+ "docs": [],
+ "targets": [
+ 2
+ ]
+ }
+ },
+ {
+ "watchCurrent": [
+ [
+ 2
+ ],
+ "resume-token-1001"
+ ],
+ "watchSnapshot": 1001
+ },
+ {
+ "watchAck": [
+ 1
+ ]
+ },
+ {
+ "watchEntity": {
+ "docs": [],
+ "targets": [
+ 1
+ ]
+ }
+ },
+ {
+ "watchCurrent": [
+ [
+ 1
+ ],
+ "resume-token-1001"
+ ],
+ "watchSnapshot": 1001,
+ "expect": [
+ {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "removed": [
+ [
+ "collection/a",
+ 1000,
+ {
+ "key": "a"
+ }
+ ]
+ ],
+ "errorCode": 0,
+ "fromCache": false,
+ "hasPendingWrites": false
+ }
+ ],
+ "stateExpect": {
+ "limboDocs": [],
+ "activeTargets": {
+ "2": {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "resumeToken": "resume-token-1001"
+ }
+ }
+ }
+ }
+ ]
+ },
+ "OnlineState timeout triggers offline behavior": {
+ "describeName": "Offline:",
+ "itName": "OnlineState timeout triggers offline behavior",
+ "tags": [],
+ "config": {
+ "useGarbageCollection": true
+ },
+ "steps": [
+ {
+ "userListen": [
+ 2,
+ {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ }
+ ],
+ "stateExpect": {
+ "activeTargets": {
+ "2": {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "resumeToken": ""
+ }
+ }
+ }
+ },
+ {
+ "runTimer": "online_state_timeout",
+ "expect": [
+ {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "errorCode": 0,
+ "fromCache": true,
+ "hasPendingWrites": false
+ }
+ ]
+ },
+ {
+ "watchStreamClose": {
+ "error": {
+ "code": 14,
+ "message": "Simulated Backend Error"
+ },
+ "runBackoffTimer": true
+ }
+ },
+ {
+ "watchStreamClose": {
+ "error": {
+ "code": 14,
+ "message": "Simulated Backend Error"
+ },
+ "runBackoffTimer": true
+ }
+ },
+ {
+ "watchAck": [
+ 2
+ ]
+ },
+ {
+ "watchEntity": {
+ "docs": [
+ [
+ "collection/a",
+ 1000,
+ {
+ "key": "a"
+ }
+ ]
+ ],
+ "targets": [
+ 2
+ ]
+ }
+ },
+ {
+ "watchCurrent": [
+ [
+ 2
+ ],
+ "resume-token-1000"
+ ],
+ "watchSnapshot": 1000,
+ "expect": [
+ {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "added": [
+ [
+ "collection/a",
+ 1000,
+ {
+ "key": "a"
+ }
+ ]
+ ],
+ "errorCode": 0,
+ "fromCache": false,
+ "hasPendingWrites": false
+ }
+ ]
+ },
+ {
+ "runTimer": "all"
+ },
+ {
+ "watchStreamClose": {
+ "error": {
+ "code": 14,
+ "message": "Simulated Backend Error"
+ },
+ "runBackoffTimer": true
+ },
+ "stateExpect": {
+ "activeTargets": {
+ "2": {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "resumeToken": "resume-token-1000"
+ }
+ }
+ }
+ },
+ {
+ "runTimer": "online_state_timeout",
+ "expect": [
+ {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "errorCode": 0,
+ "fromCache": true,
+ "hasPendingWrites": false
+ }
+ ]
+ }
+ ]
+ },
+ "New queries return immediately with fromCache=true when offline due to stream failures.": {
+ "describeName": "Offline:",
+ "itName": "New queries return immediately with fromCache=true when offline due to stream failures.",
+ "tags": [],
+ "config": {
+ "useGarbageCollection": true
+ },
+ "steps": [
+ {
+ "userListen": [
+ 2,
+ {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ }
+ ],
+ "stateExpect": {
+ "activeTargets": {
+ "2": {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "resumeToken": ""
+ }
}
}
},
@@ -278,11 +924,110 @@
"error": {
"code": 14,
"message": "Simulated Backend Error"
+ },
+ "runBackoffTimer": true
+ }
+ },
+ {
+ "watchStreamClose": {
+ "error": {
+ "code": 14,
+ "message": "Simulated Backend Error"
+ },
+ "runBackoffTimer": true
+ },
+ "expect": [
+ {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "errorCode": 0,
+ "fromCache": true,
+ "hasPendingWrites": false
+ }
+ ]
+ },
+ {
+ "userListen": [
+ 4,
+ {
+ "path": "collection2",
+ "filters": [],
+ "orderBys": []
+ }
+ ],
+ "stateExpect": {
+ "activeTargets": {
+ "2": {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "resumeToken": ""
+ },
+ "4": {
+ "query": {
+ "path": "collection2",
+ "filters": [],
+ "orderBys": []
+ },
+ "resumeToken": ""
+ }
}
},
"expect": [
{
"query": {
+ "path": "collection2",
+ "filters": [],
+ "orderBys": []
+ },
+ "errorCode": 0,
+ "fromCache": true,
+ "hasPendingWrites": false
+ }
+ ]
+ }
+ ]
+ },
+ "New queries return immediately with fromCache=true when offline due to OnlineState timeout.": {
+ "describeName": "Offline:",
+ "itName": "New queries return immediately with fromCache=true when offline due to OnlineState timeout.",
+ "tags": [],
+ "config": {
+ "useGarbageCollection": true
+ },
+ "steps": [
+ {
+ "userListen": [
+ 2,
+ {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ }
+ ],
+ "stateExpect": {
+ "activeTargets": {
+ "2": {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "resumeToken": ""
+ }
+ }
+ }
+ },
+ {
+ "runTimer": "online_state_timeout",
+ "expect": [
+ {
+ "query": {
"path": "collection",
"filters": [],
"orderBys": []
@@ -292,6 +1037,48 @@
"hasPendingWrites": false
}
]
+ },
+ {
+ "userListen": [
+ 4,
+ {
+ "path": "collection2",
+ "filters": [],
+ "orderBys": []
+ }
+ ],
+ "stateExpect": {
+ "activeTargets": {
+ "2": {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "resumeToken": ""
+ },
+ "4": {
+ "query": {
+ "path": "collection2",
+ "filters": [],
+ "orderBys": []
+ },
+ "resumeToken": ""
+ }
+ }
+ },
+ "expect": [
+ {
+ "query": {
+ "path": "collection2",
+ "filters": [],
+ "orderBys": []
+ },
+ "errorCode": 0,
+ "fromCache": true,
+ "hasPendingWrites": false
+ }
+ ]
}
]
}
diff --git a/Firestore/Example/Tests/SpecTests/json/remote_store_spec_test.json b/Firestore/Example/Tests/SpecTests/json/remote_store_spec_test.json
index 26bb520..6852c90 100644
--- a/Firestore/Example/Tests/SpecTests/json/remote_store_spec_test.json
+++ b/Firestore/Example/Tests/SpecTests/json/remote_store_spec_test.json
@@ -488,7 +488,8 @@
"error": {
"code": 14,
"message": "Simulated Backend Error"
- }
+ },
+ "runBackoffTimer": true
}
},
{
@@ -543,5 +544,76 @@
]
}
]
+ },
+ "Handles user changes while offline (b/74749605).": {
+ "describeName": "Remote store:",
+ "itName": "Handles user changes while offline (b/74749605).",
+ "tags": [
+ "no-android",
+ "no-ios"
+ ],
+ "config": {
+ "useGarbageCollection": true
+ },
+ "steps": [
+ {
+ "userListen": [
+ 2,
+ {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ }
+ ],
+ "stateExpect": {
+ "activeTargets": {
+ "2": {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "resumeToken": ""
+ }
+ }
+ }
+ },
+ {
+ "watchStreamClose": {
+ "error": {
+ "code": 14,
+ "message": "Simulated Backend Error"
+ },
+ "runBackoffTimer": false
+ },
+ "stateExpect": {
+ "activeTargets": {}
+ }
+ },
+ {
+ "changeUser": "abc",
+ "stateExpect": {
+ "activeTargets": {
+ "2": {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "resumeToken": ""
+ }
+ }
+ }
+ },
+ {
+ "watchStreamClose": {
+ "error": {
+ "code": 14,
+ "message": "Simulated Backend Error"
+ },
+ "runBackoffTimer": true
+ }
+ }
+ ]
}
}
diff --git a/Firestore/Example/Tests/SpecTests/json/resume_token_spec_test.json b/Firestore/Example/Tests/SpecTests/json/resume_token_spec_test.json
index 25ea84a..f411d98 100644
--- a/Firestore/Example/Tests/SpecTests/json/resume_token_spec_test.json
+++ b/Firestore/Example/Tests/SpecTests/json/resume_token_spec_test.json
@@ -85,7 +85,8 @@
"error": {
"code": 14,
"message": "Simulated Backend Error"
- }
+ },
+ "runBackoffTimer": true
},
"stateExpect": {
"activeTargets": {
diff --git a/Firestore/Example/Tests/SpecTests/json/write_spec_test.json b/Firestore/Example/Tests/SpecTests/json/write_spec_test.json
index 8e3f5d5..d4d1e7c 100644
--- a/Firestore/Example/Tests/SpecTests/json/write_spec_test.json
+++ b/Firestore/Example/Tests/SpecTests/json/write_spec_test.json
@@ -3407,6 +3407,198 @@
}
]
},
+ "Held writes are not re-sent after disable/enable network.": {
+ "describeName": "Writes:",
+ "itName": "Held writes are not re-sent after disable/enable network.",
+ "tags": [],
+ "config": {
+ "useGarbageCollection": true
+ },
+ "steps": [
+ {
+ "userListen": [
+ 2,
+ {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ }
+ ],
+ "stateExpect": {
+ "activeTargets": {
+ "2": {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "resumeToken": ""
+ }
+ }
+ }
+ },
+ {
+ "watchAck": [
+ 2
+ ]
+ },
+ {
+ "watchEntity": {
+ "docs": [],
+ "targets": [
+ 2
+ ]
+ }
+ },
+ {
+ "watchCurrent": [
+ [
+ 2
+ ],
+ "resume-token-500"
+ ],
+ "watchSnapshot": 500,
+ "expect": [
+ {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "errorCode": 0,
+ "fromCache": false,
+ "hasPendingWrites": false
+ }
+ ]
+ },
+ {
+ "userSet": [
+ "collection/a",
+ {
+ "v": 1
+ }
+ ],
+ "expect": [
+ {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "added": [
+ [
+ "collection/a",
+ 0,
+ {
+ "v": 1
+ },
+ "local"
+ ]
+ ],
+ "errorCode": 0,
+ "fromCache": false,
+ "hasPendingWrites": true
+ }
+ ]
+ },
+ {
+ "writeAck": {
+ "version": 1000,
+ "expectUserCallback": true
+ },
+ "stateExpect": {
+ "writeStreamRequestCount": 2
+ }
+ },
+ {
+ "enableNetwork": false,
+ "stateExpect": {
+ "activeTargets": {},
+ "limboDocs": [],
+ "writeStreamRequestCount": 3
+ },
+ "expect": [
+ {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "errorCode": 0,
+ "fromCache": true,
+ "hasPendingWrites": true
+ }
+ ]
+ },
+ {
+ "enableNetwork": true,
+ "stateExpect": {
+ "activeTargets": {
+ "2": {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "resumeToken": "resume-token-500"
+ }
+ },
+ "writeStreamRequestCount": 3
+ }
+ },
+ {
+ "watchAck": [
+ 2
+ ]
+ },
+ {
+ "watchEntity": {
+ "docs": [
+ [
+ "collection/a",
+ 1000,
+ {
+ "v": 1
+ }
+ ]
+ ],
+ "targets": [
+ 2
+ ]
+ }
+ },
+ {
+ "watchCurrent": [
+ [
+ 2
+ ],
+ "resume-token-2000"
+ ],
+ "watchSnapshot": 2000,
+ "expect": [
+ {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "metadata": [
+ [
+ "collection/a",
+ 1000,
+ {
+ "v": 1
+ }
+ ]
+ ],
+ "errorCode": 0,
+ "fromCache": false,
+ "hasPendingWrites": false
+ }
+ ]
+ }
+ ]
+ },
"Held writes are released when there are no queries left.": {
"describeName": "Writes:",
"itName": "Held writes are released when there are no queries left.",
diff --git a/Firestore/Example/Tests/Util/FSTAssertTests.m b/Firestore/Example/Tests/Util/FSTAssertTests.mm
index 0cba03f..0cba03f 100644
--- a/Firestore/Example/Tests/Util/FSTAssertTests.m
+++ b/Firestore/Example/Tests/Util/FSTAssertTests.mm
diff --git a/Firestore/Example/Tests/Util/FSTComparisonTests.m b/Firestore/Example/Tests/Util/FSTComparisonTests.m
deleted file mode 100644
index 5632e64..0000000
--- a/Firestore/Example/Tests/Util/FSTComparisonTests.m
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * Copyright 2017 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 "Firestore/Source/Util/FSTComparison.h"
-
-#import <XCTest/XCTest.h>
-
-union DoubleBits {
- double d;
- uint64_t bits;
-};
-
-#define ASSERT_BIT_EQUALS(expected, actual) \
- do { \
- union DoubleBits expectedBits = {.d = expected}; \
- union DoubleBits actualBits = {.d = expected}; \
- if (expectedBits.bits != actualBits.bits) { \
- XCTFail(@"Expected <%f> to compare equal to <%f> with bits <%llX> equal to <%llX>", actual, \
- expected, actualBits.bits, expectedBits.bits); \
- } \
- } while (0);
-
-#define ASSERT_ORDERED_SAME(doubleValue, longValue) \
- do { \
- NSComparisonResult result = FSTCompareMixed(doubleValue, longValue); \
- if (result != NSOrderedSame) { \
- XCTFail(@"Expected <%f> to compare equal to <%lld>", doubleValue, longValue); \
- } \
- } while (0);
-
-#define ASSERT_ORDERED_DESCENDING(doubleValue, longValue) \
- do { \
- NSComparisonResult result = FSTCompareMixed(doubleValue, longValue); \
- if (result != NSOrderedDescending) { \
- XCTFail(@"Expected <%f> to compare equal to <%lld>", doubleValue, longValue); \
- } \
- } while (0);
-
-#define ASSERT_ORDERED_ASCENDING(doubleValue, longValue) \
- do { \
- NSComparisonResult result = FSTCompareMixed(doubleValue, longValue); \
- if (result != NSOrderedAscending) { \
- XCTFail(@"Expected <%f> to compare equal to <%lld>", doubleValue, longValue); \
- } \
- } while (0);
-
-@interface FSTComparisonTests : XCTestCase
-@end
-
-@implementation FSTComparisonTests
-
-- (void)testMixedComparison {
- // Infinities
- ASSERT_ORDERED_ASCENDING(-INFINITY, LLONG_MIN);
- ASSERT_ORDERED_ASCENDING(-INFINITY, LLONG_MAX);
- ASSERT_ORDERED_ASCENDING(-INFINITY, 0LL);
-
- ASSERT_ORDERED_DESCENDING(INFINITY, LLONG_MIN);
- ASSERT_ORDERED_DESCENDING(INFINITY, LLONG_MAX);
- ASSERT_ORDERED_DESCENDING(INFINITY, 0LL);
-
- // NaN
- ASSERT_ORDERED_ASCENDING(NAN, LLONG_MIN);
- ASSERT_ORDERED_ASCENDING(NAN, LLONG_MAX);
- ASSERT_ORDERED_ASCENDING(NAN, 0LL);
-
- // Large values (note DBL_MIN is positive and near zero).
- ASSERT_ORDERED_ASCENDING(-DBL_MAX, LLONG_MIN);
-
- // Tests around LLONG_MIN
- ASSERT_BIT_EQUALS((double)LLONG_MIN, -0x1.0p63);
- ASSERT_ORDERED_SAME(-0x1.0p63, LLONG_MIN);
- ASSERT_ORDERED_ASCENDING(-0x1.0p63, LLONG_MIN + 1);
-
- XCTAssertLessThan(-0x1.0000000000001p63, -0x1.0p63);
- ASSERT_ORDERED_ASCENDING(-0x1.0000000000001p63, LLONG_MIN);
- ASSERT_ORDERED_DESCENDING(-0x1.FFFFFFFFFFFFFp62, LLONG_MIN);
-
- // Tests around LLONG_MAX
- // Note LLONG_MAX cannot be exactly represented by a double, so the system rounds it to the
- // nearest double, which is 2^63. This number, in turn is larger than the maximum representable
- // as a long.
- ASSERT_BIT_EQUALS(0x1.0p63, (double)LLONG_MAX);
- ASSERT_ORDERED_DESCENDING(0x1.0p63, LLONG_MAX);
-
- // The largest value with an exactly long representation
- XCTAssertEqual((long)0x1.FFFFFFFFFFFFFp62, 0x7FFFFFFFFFFFFC00LL);
- ASSERT_ORDERED_SAME(0x1.FFFFFFFFFFFFFp62, 0x7FFFFFFFFFFFFC00LL);
-
- ASSERT_ORDERED_DESCENDING(0x1.FFFFFFFFFFFFFp62, 0x7FFFFFFFFFFFFB00LL);
- ASSERT_ORDERED_DESCENDING(0x1.FFFFFFFFFFFFFp62, 0x7FFFFFFFFFFFFBFFLL);
- ASSERT_ORDERED_ASCENDING(0x1.FFFFFFFFFFFFFp62, 0x7FFFFFFFFFFFFC01LL);
- ASSERT_ORDERED_ASCENDING(0x1.FFFFFFFFFFFFFp62, 0x7FFFFFFFFFFFFD00LL);
-
- ASSERT_ORDERED_ASCENDING(0x1.FFFFFFFFFFFFEp62, 0x7FFFFFFFFFFFFC00LL);
-
- // Tests around MAX_SAFE_INTEGER
- ASSERT_ORDERED_SAME(0x1.FFFFFFFFFFFFFp52, 0x1FFFFFFFFFFFFFLL);
- ASSERT_ORDERED_DESCENDING(0x1.FFFFFFFFFFFFFp52, 0x1FFFFFFFFFFFFELL);
- ASSERT_ORDERED_ASCENDING(0x1.FFFFFFFFFFFFEp52, 0x1FFFFFFFFFFFFFLL);
- ASSERT_ORDERED_ASCENDING(0x1.FFFFFFFFFFFFFp52, 0x20000000000000LL);
-
- // Tests around MIN_SAFE_INTEGER
- ASSERT_ORDERED_SAME(-0x1.FFFFFFFFFFFFFp52, -0x1FFFFFFFFFFFFFLL);
- ASSERT_ORDERED_ASCENDING(-0x1.FFFFFFFFFFFFFp52, -0x1FFFFFFFFFFFFELL);
- ASSERT_ORDERED_DESCENDING(-0x1.FFFFFFFFFFFFEp52, -0x1FFFFFFFFFFFFFLL);
- ASSERT_ORDERED_DESCENDING(-0x1.FFFFFFFFFFFFFp52, -0x20000000000000LL);
-
- // Tests around zero.
- ASSERT_ORDERED_SAME(-0.0, 0LL);
- ASSERT_ORDERED_SAME(0.0, 0LL);
-
- // The smallest representable positive value should be greater than zero
- ASSERT_ORDERED_DESCENDING(DBL_MIN, 0LL);
- ASSERT_ORDERED_ASCENDING(-DBL_MIN, 0LL);
-
- // Note that 0x1.0p-1074 is a hex floating point literal representing the minimum subnormal
- // number: <https://en.wikipedia.org/wiki/Denormal_number>.
- double minSubNormal = 0x1.0p-1074;
- ASSERT_ORDERED_DESCENDING(minSubNormal, 0LL);
- ASSERT_ORDERED_ASCENDING(-minSubNormal, 0LL);
-
- // Other sanity checks
- ASSERT_ORDERED_ASCENDING(0.5, 1LL);
- ASSERT_ORDERED_DESCENDING(0.5, 0LL);
- ASSERT_ORDERED_ASCENDING(1.5, 2LL);
- ASSERT_ORDERED_DESCENDING(1.5, 1LL);
-}
-
-@end
diff --git a/Firestore/Example/Tests/Util/FSTDispatchQueueTests.mm b/Firestore/Example/Tests/Util/FSTDispatchQueueTests.mm
new file mode 100644
index 0000000..60b1705
--- /dev/null
+++ b/Firestore/Example/Tests/Util/FSTDispatchQueueTests.mm
@@ -0,0 +1,266 @@
+/*
+ * Copyright 2018 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 "Firestore/Source/Util/FSTDispatchQueue.h"
+
+#import <XCTest/XCTest.h>
+
+#import "Firestore/Example/Tests/Util/XCTestCase+Await.h"
+
+// In these generic tests the specific TimerIDs don't matter.
+static const FSTTimerID timerID1 = FSTTimerIDListenStreamConnectionBackoff;
+static const FSTTimerID timerID2 = FSTTimerIDListenStreamIdle;
+static const FSTTimerID timerID3 = FSTTimerIDWriteStreamConnectionBackoff;
+
+@interface FSTDispatchQueueTests : XCTestCase
+@end
+
+@implementation FSTDispatchQueueTests {
+ dispatch_queue_t _underlyingQueue;
+ FSTDispatchQueue *_queue;
+ NSMutableArray *_completedSteps;
+ NSArray *_expectedSteps;
+ XCTestExpectation *_expectation;
+}
+
+- (void)setUp {
+ [super setUp];
+ _underlyingQueue = dispatch_queue_create("FSTDispatchQueueTests", DISPATCH_QUEUE_SERIAL);
+ _queue = [[FSTDispatchQueue alloc] initWithQueue:_underlyingQueue];
+ _completedSteps = [NSMutableArray array];
+ _expectedSteps = nil;
+}
+
+- (void)testDispatchAsyncBlocksSubmissionFromTasksOnTheQueue {
+ XCTestExpectation *expectation = [self expectationWithDescription:@"completion"];
+ __block NSException *caught = nil;
+ __block NSString *problem = nil;
+
+ [_queue dispatchAsync:^{
+ @try {
+ [self->_queue dispatchAsync:^{
+ }];
+ problem = @"Should have disallowed submission into the queue while running";
+ [expectation fulfill];
+ } @catch (NSException *ex) {
+ caught = ex;
+ [expectation fulfill];
+ }
+ }];
+
+ [self awaitExpectations];
+ XCTAssertNil(problem);
+ XCTAssertNotNil(caught);
+
+ XCTAssertEqualObjects(caught.name, NSInternalInconsistencyException);
+ XCTAssertTrue(
+ [caught.reason hasPrefix:@"FIRESTORE INTERNAL ASSERTION FAILED: "
+ @"dispatchAsync called when we are already running on target"]);
+}
+
+- (void)testDispatchAsyncAllowingSameQueueActuallyAllowsSameQueue {
+ XCTestExpectation *expectation = [self expectationWithDescription:@"completion"];
+ __block NSException *caught = nil;
+
+ [_queue dispatchAsync:^{
+ @try {
+ [self->_queue dispatchAsyncAllowingSameQueue:^{
+ [expectation fulfill];
+ }];
+ } @catch (NSException *ex) {
+ caught = ex;
+ [expectation fulfill];
+ }
+ }];
+
+ [self awaitExpectations];
+ XCTAssertNil(caught);
+}
+
+- (void)testDispatchAsyncAllowsSameQueueForUnownedActions {
+ XCTestExpectation *expectation = [self expectationWithDescription:@"completion"];
+ __block NSException *caught = nil;
+
+ // Simulate the case of an action that runs on our queue because e.g. it's run by a user-owned
+ // deinitializer that happened to be last held in one of our API methods.
+ dispatch_async(_underlyingQueue, ^{
+ @try {
+ [self->_queue dispatchAsync:^{
+ [expectation fulfill];
+ }];
+ } @catch (NSException *ex) {
+ caught = ex;
+ [expectation fulfill];
+ }
+ });
+
+ [self awaitExpectations];
+ XCTAssertNil(caught);
+}
+
+- (void)testDispatchSyncBlocksSubmissionFromTasksOnTheQueue {
+ XCTestExpectation *expectation = [self expectationWithDescription:@"completion"];
+ __block NSException *caught = nil;
+ __block NSString *problem = nil;
+
+ [_queue dispatchSync:^{
+ @try {
+ [self->_queue dispatchSync:^{
+ }];
+ problem = @"Should have disallowed submission into the queue while running";
+ [expectation fulfill];
+ } @catch (NSException *ex) {
+ caught = ex;
+ [expectation fulfill];
+ }
+ }];
+
+ [self awaitExpectations];
+ XCTAssertNil(problem);
+ XCTAssertNotNil(caught);
+
+ XCTAssertEqualObjects(caught.name, NSInternalInconsistencyException);
+ XCTAssertTrue(
+ [caught.reason hasPrefix:@"FIRESTORE INTERNAL ASSERTION FAILED: "
+ @"dispatchSync called when we are already running on target"]);
+}
+
+- (void)testVerifyIsCurrentQueueActuallyRequiresCurrentQueue {
+ XCTAssertNotEqualObjects(_underlyingQueue, dispatch_get_main_queue());
+
+ __block NSException *caught = nil;
+ @try {
+ // Run on the main queue not the FSTDispatchQueue's queue
+ [_queue verifyIsCurrentQueue];
+ } @catch (NSException *ex) {
+ caught = ex;
+ }
+ XCTAssertNotNil(caught);
+ XCTAssertTrue([caught.reason hasPrefix:@"FIRESTORE INTERNAL ASSERTION FAILED: "
+ @"We are running on the wrong dispatch queue"]);
+}
+
+- (void)testVerifyIsCurrentQueueRequiresOperationIsInProgress {
+ __block NSException *caught = nil;
+ dispatch_sync(_underlyingQueue, ^{
+ @try {
+ [_queue verifyIsCurrentQueue];
+ } @catch (NSException *ex) {
+ caught = ex;
+ }
+ });
+ XCTAssertNotNil(caught);
+ XCTAssertTrue(
+ [caught.reason hasPrefix:@"FIRESTORE INTERNAL ASSERTION FAILED: "
+ @"verifyIsCurrentQueue called outside enterCheckedOperation"]);
+}
+
+- (void)testVerifyIsCurrentQueueWorksWithOperationIsInProgress {
+ __block NSException *caught = nil;
+ [_queue dispatchSync:^{
+ @try {
+ [_queue verifyIsCurrentQueue];
+ } @catch (NSException *ex) {
+ caught = ex;
+ }
+ }];
+ XCTAssertNil(caught);
+}
+
+- (void)testEnterCheckedOperationDisallowsNesting {
+ __block NSException *caught = nil;
+ __block NSString *problem = nil;
+ [_queue dispatchSync:^{
+ @try {
+ [_queue enterCheckedOperation:^{
+ }];
+ problem = @"Should not have been able to enter nested enterCheckedOperation";
+ } @catch (NSException *ex) {
+ caught = ex;
+ }
+ }];
+ XCTAssertNil(problem);
+ XCTAssertNotNil(caught);
+ XCTAssertTrue([caught.reason
+ hasPrefix:@"FIRESTORE INTERNAL ASSERTION FAILED: "
+ @"enterCheckedOperation may not be called when an operation is in progress"]);
+}
+
+/**
+ * Helper to return a block that adds @(n) to _completedSteps when run and fulfils _expectation if
+ * the _completedSteps match the _expectedSteps.
+ */
+- (void (^)())blockForStep:(int)n {
+ return ^void() {
+ [self->_completedSteps addObject:@(n)];
+ if (self->_expectedSteps && self->_completedSteps.count >= self->_expectedSteps.count) {
+ XCTAssertEqualObjects(self->_completedSteps, self->_expectedSteps);
+ [self->_expectation fulfill];
+ }
+ };
+}
+
+- (void)testCanScheduleCallbacksInTheFuture {
+ _expectation = [self expectationWithDescription:@"Expected steps"];
+ _expectedSteps = @[ @1, @2, @3, @4 ];
+ [_queue dispatchAsync:[self blockForStep:1]];
+ [_queue dispatchAfterDelay:0.005 timerID:timerID1 block:[self blockForStep:4]];
+ [_queue dispatchAfterDelay:0.001 timerID:timerID2 block:[self blockForStep:3]];
+ [_queue dispatchAsync:[self blockForStep:2]];
+
+ [self awaitExpectations];
+}
+
+- (void)testCanCancelDelayedCallbacks {
+ _expectation = [self expectationWithDescription:@"Expected steps"];
+ _expectedSteps = @[ @1, @3 ];
+ // Queue everything from the queue to ensure nothing completes before we cancel.
+ [_queue dispatchAsync:^{
+ [_queue dispatchAsyncAllowingSameQueue:[self blockForStep:1]];
+ FSTDelayedCallback *step2Timer =
+ [_queue dispatchAfterDelay:.001 timerID:timerID1 block:[self blockForStep:2]];
+ [_queue dispatchAfterDelay:.005 timerID:timerID2 block:[self blockForStep:3]];
+
+ XCTAssertTrue([_queue containsDelayedCallbackWithTimerID:timerID1]);
+ [step2Timer cancel];
+ XCTAssertFalse([_queue containsDelayedCallbackWithTimerID:timerID1]);
+ }];
+
+ [self awaitExpectations];
+}
+
+- (void)testCanManuallyDrainAllDelayedCallbacksForTesting {
+ [_queue dispatchAsync:[self blockForStep:1]];
+ [_queue dispatchAfterDelay:20 timerID:timerID1 block:[self blockForStep:4]];
+ [_queue dispatchAfterDelay:10 timerID:timerID2 block:[self blockForStep:3]];
+ [_queue dispatchAsync:[self blockForStep:2]];
+
+ [_queue runDelayedCallbacksUntil:FSTTimerIDAll];
+ XCTAssertEqualObjects(_completedSteps, (@[ @1, @2, @3, @4 ]));
+}
+
+- (void)testCanManuallyDrainSpecificDelayedCallbacksForTesting {
+ [_queue dispatchAsync:[self blockForStep:1]];
+ [_queue dispatchAfterDelay:20 timerID:timerID1 block:[self blockForStep:5]];
+ [_queue dispatchAfterDelay:10 timerID:timerID2 block:[self blockForStep:3]];
+ [_queue dispatchAfterDelay:15 timerID:timerID3 block:[self blockForStep:4]];
+ [_queue dispatchAsync:[self blockForStep:2]];
+
+ [_queue runDelayedCallbacksUntil:timerID3];
+ XCTAssertEqualObjects(_completedSteps, (@[ @1, @2, @3, @4 ]));
+}
+
+@end
diff --git a/Firestore/Example/Tests/Util/FSTEventAccumulator.h b/Firestore/Example/Tests/Util/FSTEventAccumulator.h
index ae5392c..baa501b 100644
--- a/Firestore/Example/Tests/Util/FSTEventAccumulator.h
+++ b/Firestore/Example/Tests/Util/FSTEventAccumulator.h
@@ -23,7 +23,7 @@
NS_ASSUME_NONNULL_BEGIN
-typedef void (^FSTGenericEventHandler)(id _Nullable, NSError *error);
+typedef void (^FSTValueEventHandler)(id _Nullable, NSError *_Nullable error);
@interface FSTEventAccumulator : NSObject
@@ -35,7 +35,8 @@ typedef void (^FSTGenericEventHandler)(id _Nullable, NSError *error);
- (NSArray<id> *)awaitEvents:(NSUInteger)events name:(NSString *)name;
-@property(nonatomic, strong, readonly) FSTGenericEventHandler handler;
+@property(nonatomic, strong, readonly) FSTValueEventHandler valueEventHandler;
+
@end
NS_ASSUME_NONNULL_END
diff --git a/Firestore/Example/Tests/Util/FSTEventAccumulator.m b/Firestore/Example/Tests/Util/FSTEventAccumulator.mm
index b44ec67..c4c1602 100644
--- a/Firestore/Example/Tests/Util/FSTEventAccumulator.m
+++ b/Firestore/Example/Tests/Util/FSTEventAccumulator.mm
@@ -68,9 +68,8 @@ NS_ASSUME_NONNULL_BEGIN
return events[0];
}
-// Overrides the handler property
-- (void (^)(id _Nullable, NSError *))handler {
- return ^void(id _Nullable value, NSError *error) {
+- (void (^)(id _Nullable, NSError *_Nullable))valueEventHandler {
+ return ^void(id _Nullable value, NSError *_Nullable error) {
// We can't store nil in the _events array, but these are still interesting to tests so store
// NSNull instead.
id event = value ? value : [NSNull null];
diff --git a/Firestore/Example/Tests/Util/FSTHelpers.h b/Firestore/Example/Tests/Util/FSTHelpers.h
index 91ccbcf..9b5f96a 100644
--- a/Firestore/Example/Tests/Util/FSTHelpers.h
+++ b/Firestore/Example/Tests/Util/FSTHelpers.h
@@ -16,30 +16,34 @@
#import <Foundation/Foundation.h>
-#import "Firestore/Source/API/FIRDocumentReference+Internal.h"
+#include <map>
+#include <vector>
+
#import "Firestore/Source/Core/FSTTypes.h"
#import "Firestore/Source/Model/FSTDocumentDictionary.h"
#import "Firestore/Source/Model/FSTDocumentKeySet.h"
+#include "Firestore/core/src/firebase/firestore/model/field_path.h"
+#include "Firestore/core/src/firebase/firestore/model/field_value.h"
+#include "Firestore/core/src/firebase/firestore/model/resource_path.h"
+#include "absl/strings/string_view.h"
+
@class FIRGeoPoint;
@class FSTDeleteMutation;
@class FSTDeletedDocument;
@class FSTDocument;
@class FSTDocumentKeyReference;
@class FSTDocumentSet;
-@class FSTFieldPath;
@class FSTFieldValue;
@class FSTLocalViewChanges;
@class FSTPatchMutation;
@class FSTQuery;
@class FSTRemoteEvent;
-@class FSTResourceName;
-@class FSTResourcePath;
@class FSTSetMutation;
@class FSTSnapshotVersion;
@class FSTSortOrder;
@class FSTTargetChange;
-@class FSTTimestamp;
+@class FIRTimestamp;
@class FSTTransformMutation;
@class FSTView;
@class FSTViewSnapshot;
@@ -121,21 +125,33 @@ extern "C" {
} \
} while (0)
+static NSString *kExceptionPrefix = @"FIRESTORE INTERNAL ASSERTION FAILED: ";
+
+// Remove possible exception-prefix.
+inline NSString *FSTRemoveExceptionPrefix(NSString *exception) {
+ if ([exception hasPrefix:kExceptionPrefix]) {
+ return [exception substringFromIndex:kExceptionPrefix.length];
+ } else {
+ return exception;
+ }
+}
+
// Helper for validating API exceptions.
-#define FSTAssertThrows(expression, exceptionReason, ...) \
- ({ \
- BOOL __didThrow = NO; \
- @try { \
- (void)(expression); \
- } @catch (NSException * exception) { \
- __didThrow = YES; \
- XCTAssertEqualObjects(exception.reason, exceptionReason); \
- } \
- XCTAssertTrue(__didThrow, ##__VA_ARGS__); \
- })
-
-/** Creates a new FSTTimestamp from components. Note that year, month, and day are all one-based. */
-FSTTimestamp *FSTTestTimestamp(int year, int month, int day, int hour, int minute, int second);
+#define FSTAssertThrows(expression, exceptionReason, ...) \
+ do { \
+ BOOL didThrow = NO; \
+ @try { \
+ (void)(expression); \
+ } @catch (NSException * exception) { \
+ didThrow = YES; \
+ XCTAssertEqualObjects(FSTRemoveExceptionPrefix(exception.reason), \
+ FSTRemoveExceptionPrefix(exceptionReason)); \
+ } \
+ XCTAssertTrue(didThrow, ##__VA_ARGS__); \
+ } while (0)
+
+/** Creates a new FIRTimestamp from components. Note that year, month, and day are all one-based. */
+FIRTimestamp *FSTTestTimestamp(int year, int month, int day, int hour, int minute, int second);
/** Creates a new NSDate from components. Note that year, month, and day are all one-based. */
NSDate *FSTTestDate(int year, int month, int day, int hour, int minute, int second);
@@ -145,6 +161,8 @@ NSDate *FSTTestDate(int year, int month, int day, int hour, int minute, int seco
*/
NSData *FSTTestData(int bytes, ...);
+// Note that FIRGeoPoint is a model class in addition to an API class, so we put this helper here
+// instead of FSTAPIHelpers.h
/** Creates a new GeoPoint from the latitude and longitude values */
FIRGeoPoint *FSTTestGeoPoint(double latitude, double longitude);
@@ -155,8 +173,6 @@ FIRGeoPoint *FSTTestGeoPoint(double latitude, double longitude);
NSDateComponents *FSTTestDateComponents(
int year, int month, int day, int hour, int minute, int second);
-FSTFieldPath *FSTTestFieldPath(NSString *field);
-
/** Wraps a plain value into an FSTFieldValue instance. */
FSTFieldValue *FSTTestFieldValue(id _Nullable value);
@@ -176,39 +192,38 @@ typedef int64_t FSTTestSnapshotVersion;
FSTSnapshotVersion *FSTTestVersion(FSTTestSnapshotVersion version);
/** A convenience method for creating docs for tests. */
-FSTDocument *FSTTestDoc(NSString *path,
+FSTDocument *FSTTestDoc(const absl::string_view path,
FSTTestSnapshotVersion version,
NSDictionary<NSString *, id> *data,
BOOL hasMutations);
/** A convenience method for creating deleted docs for tests. */
-FSTDeletedDocument *FSTTestDeletedDoc(NSString *path, FSTTestSnapshotVersion version);
-
-/** A convenience method for creating resource paths from a path string. */
-FSTResourcePath *FSTTestPath(NSString *path);
+FSTDeletedDocument *FSTTestDeletedDoc(const absl::string_view path, FSTTestSnapshotVersion version);
/**
* A convenience method for creating a document reference from a path string.
*/
-FSTDocumentKeyReference *FSTTestRef(NSString *projectID, NSString *databaseID, NSString *path);
+FSTDocumentKeyReference *FSTTestRef(const absl::string_view projectID,
+ const absl::string_view databaseID,
+ NSString *path);
/** A convenience method for creating a query for the given path (without any other filters). */
-FSTQuery *FSTTestQuery(NSString *path);
+FSTQuery *FSTTestQuery(const absl::string_view path);
/**
* A convenience method to create a FSTFilter using a string representation for both field
* and operator (<, <=, ==, >=, >).
*/
-id<FSTFilter> FSTTestFilter(NSString *field, NSString *op, id value);
+id<FSTFilter> FSTTestFilter(const absl::string_view field, NSString *op, id value);
/** A convenience method for creating sort orders. */
-FSTSortOrder *FSTTestOrderBy(NSString *field, NSString *direction);
+FSTSortOrder *FSTTestOrderBy(const absl::string_view field, NSString *direction);
/**
* Creates an NSComparator that will compare FSTDocuments by the given fieldPath string then by
* key.
*/
-NSComparator FSTTestDocComparator(NSString *fieldPath);
+NSComparator FSTTestDocComparator(const absl::string_view fieldPath);
/**
* Creates a FSTDocumentSet based on the given comparator, initially containing the given
@@ -225,12 +240,17 @@ FSTViewSnapshot *_Nullable FSTTestApplyChanges(FSTView *view,
FSTSetMutation *FSTTestSetMutation(NSString *path, NSDictionary<NSString *, id> *values);
/** Creates a patch mutation for the document key at the given path. */
-FSTPatchMutation *FSTTestPatchMutation(NSString *path,
- NSDictionary<NSString *, id> *values,
- NSArray<FSTFieldPath *> *_Nullable updateMask);
+FSTPatchMutation *FSTTestPatchMutation(
+ const absl::string_view path,
+ NSDictionary<NSString *, id> *values,
+ const std::vector<firebase::firestore::model::FieldPath> &updateMask);
-FSTTransformMutation *FSTTestTransformMutation(NSString *path,
- NSArray<NSString *> *serverTimestampFields);
+/**
+ * Creates a FSTTransformMutation by parsing any FIRFieldValue sentinels in the provided data. The
+ * data is expected to use dotted-notation for nested fields (i.e.
+ * @{ @"foo.bar": [FIRFieldValue ...] } and must not contain any non-sentinel data.
+ */
+FSTTransformMutation *FSTTestTransformMutation(NSString *path, NSDictionary<NSString *, id> *data);
/** Creates a delete mutation for the document key at the given path. */
FSTDeleteMutation *FSTTestDeleteMutation(NSString *path);
diff --git a/Firestore/Example/Tests/Util/FSTHelpers.m b/Firestore/Example/Tests/Util/FSTHelpers.mm
index f01bddb..e1ed18c 100644
--- a/Firestore/Example/Tests/Util/FSTHelpers.m
+++ b/Firestore/Example/Tests/Util/FSTHelpers.mm
@@ -16,27 +16,58 @@
#import "Firestore/Example/Tests/Util/FSTHelpers.h"
-#import "FirebaseFirestore/FIRFieldPath.h"
-#import "FirebaseFirestore/FIRGeoPoint.h"
+#import <FirebaseFirestore/FIRFieldPath.h>
+#import <FirebaseFirestore/FIRGeoPoint.h>
+#import <FirebaseFirestore/FIRTimestamp.h>
+
+#include <cinttypes>
+#include <list>
+#include <map>
+#include <utility>
+#include <vector>
+
#import "Firestore/Source/API/FIRFieldPath+Internal.h"
#import "Firestore/Source/API/FSTUserDataConverter.h"
#import "Firestore/Source/Core/FSTQuery.h"
#import "Firestore/Source/Core/FSTSnapshotVersion.h"
-#import "Firestore/Source/Core/FSTTimestamp.h"
#import "Firestore/Source/Core/FSTView.h"
+#import "Firestore/Source/Core/FSTViewSnapshot.h"
#import "Firestore/Source/Local/FSTLocalViewChanges.h"
#import "Firestore/Source/Local/FSTQueryData.h"
-#import "Firestore/Source/Model/FSTDatabaseID.h"
#import "Firestore/Source/Model/FSTDocument.h"
#import "Firestore/Source/Model/FSTDocumentKey.h"
#import "Firestore/Source/Model/FSTDocumentSet.h"
#import "Firestore/Source/Model/FSTFieldValue.h"
#import "Firestore/Source/Model/FSTMutation.h"
-#import "Firestore/Source/Model/FSTPath.h"
#import "Firestore/Source/Remote/FSTRemoteEvent.h"
#import "Firestore/Source/Remote/FSTWatchChange.h"
#import "Firestore/Source/Util/FSTAssert.h"
+#include "Firestore/core/src/firebase/firestore/model/database_id.h"
+#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+#include "Firestore/core/src/firebase/firestore/model/field_mask.h"
+#include "Firestore/core/src/firebase/firestore/model/field_transform.h"
+#include "Firestore/core/src/firebase/firestore/model/field_value.h"
+#include "Firestore/core/src/firebase/firestore/model/precondition.h"
+#include "Firestore/core/src/firebase/firestore/model/resource_path.h"
+#include "Firestore/core/src/firebase/firestore/model/transform_operations.h"
+#include "Firestore/core/src/firebase/firestore/util/string_apple.h"
+#include "Firestore/core/test/firebase/firestore/testutil/testutil.h"
+#include "absl/memory/memory.h"
+
+namespace util = firebase::firestore::util;
+namespace testutil = firebase::firestore::testutil;
+using firebase::firestore::model::DatabaseId;
+using firebase::firestore::model::DocumentKey;
+using firebase::firestore::model::FieldMask;
+using firebase::firestore::model::FieldPath;
+using firebase::firestore::model::FieldTransform;
+using firebase::firestore::model::FieldValue;
+using firebase::firestore::model::Precondition;
+using firebase::firestore::model::ResourcePath;
+using firebase::firestore::model::ServerTimestampTransform;
+using firebase::firestore::model::TransformOperation;
+
NS_ASSUME_NONNULL_BEGIN
/** A string sentinel that can be used with FSTTestPatchMutation() to mark a field for deletion. */
@@ -45,9 +76,9 @@ static NSString *const kDeleteSentinel = @"<DELETE>";
static const int kMicrosPerSec = 1000000;
static const int kMillisPerSec = 1000;
-FSTTimestamp *FSTTestTimestamp(int year, int month, int day, int hour, int minute, int second) {
+FIRTimestamp *FSTTestTimestamp(int year, int month, int day, int hour, int minute, int second) {
NSDate *date = FSTTestDate(year, month, day, hour, minute, second);
- return [FSTTimestamp timestampWithDate:date];
+ return [FIRTimestamp timestampWithDate:date];
}
NSDate *FSTTestDate(int year, int month, int day, int hour, int minute, int second) {
@@ -91,18 +122,19 @@ NSDateComponents *FSTTestDateComponents(
return comps;
}
-FSTFieldPath *FSTTestFieldPath(NSString *field) {
- return [FIRFieldPath pathWithDotSeparatedString:field].internalValue;
-}
-
-FSTFieldValue *FSTTestFieldValue(id _Nullable value) {
- FSTDatabaseID *databaseID =
- [FSTDatabaseID databaseIDWithProject:@"project" database:kDefaultDatabaseID];
+FSTUserDataConverter *FSTTestUserDataConverter() {
+ // This owns the DatabaseIds since we do not have FirestoreClient instance to own them.
+ static DatabaseId database_id{"project", DatabaseId::kDefault};
FSTUserDataConverter *converter =
- [[FSTUserDataConverter alloc] initWithDatabaseID:databaseID
+ [[FSTUserDataConverter alloc] initWithDatabaseID:&database_id
preConverter:^id _Nullable(id _Nullable input) {
return input;
}];
+ return converter;
+}
+
+FSTFieldValue *FSTTestFieldValue(id _Nullable value) {
+ FSTUserDataConverter *converter = FSTTestUserDataConverter();
// HACK: We use parsedQueryValue: since it accepts scalars as well as arrays / objects, and
// our tests currently use FSTTestFieldValue() pretty generically so we don't know the intent.
return [converter parsedQueryValue:value];
@@ -130,49 +162,43 @@ FSTSnapshotVersion *FSTTestVersion(FSTTestSnapshotVersion versionMicroseconds) {
int64_t seconds = versionMicroseconds / kMicrosPerSec;
int32_t nanos = (int32_t)(versionMicroseconds % kMicrosPerSec) * kMillisPerSec;
- FSTTimestamp *timestamp = [[FSTTimestamp alloc] initWithSeconds:seconds nanos:nanos];
+ FIRTimestamp *timestamp = [[FIRTimestamp alloc] initWithSeconds:seconds nanoseconds:nanos];
return [FSTSnapshotVersion versionWithTimestamp:timestamp];
}
-FSTDocument *FSTTestDoc(NSString *path,
+FSTDocument *FSTTestDoc(const absl::string_view path,
FSTTestSnapshotVersion version,
NSDictionary<NSString *, id> *data,
BOOL hasMutations) {
- FSTDocumentKey *key = [FSTDocumentKey keyWithPathString:path];
+ DocumentKey key = testutil::Key(path);
return [FSTDocument documentWithData:FSTTestObjectValue(data)
key:key
version:FSTTestVersion(version)
hasLocalMutations:hasMutations];
}
-FSTDeletedDocument *FSTTestDeletedDoc(NSString *path, FSTTestSnapshotVersion version) {
- FSTDocumentKey *key = [FSTDocumentKey keyWithPathString:path];
+FSTDeletedDocument *FSTTestDeletedDoc(const absl::string_view path,
+ FSTTestSnapshotVersion version) {
+ DocumentKey key = testutil::Key(path);
return [FSTDeletedDocument documentWithKey:key version:FSTTestVersion(version)];
}
-static NSArray<NSString *> *FSTTestSplitPath(NSString *path) {
- if ([path isEqualToString:@""]) {
- return @[];
- } else {
- return [path componentsSeparatedByString:@"/"];
- }
-}
-
-FSTResourcePath *FSTTestPath(NSString *path) {
- return [FSTResourcePath pathWithSegments:FSTTestSplitPath(path)];
-}
-
-FSTDocumentKeyReference *FSTTestRef(NSString *projectID, NSString *database, NSString *path) {
- FSTDatabaseID *databaseID = [FSTDatabaseID databaseIDWithProject:projectID database:database];
- return [[FSTDocumentKeyReference alloc] initWithKey:FSTTestDocKey(path) databaseID:databaseID];
+FSTDocumentKeyReference *FSTTestRef(const absl::string_view projectID,
+ const absl::string_view database,
+ NSString *path) {
+ // This owns the DatabaseIds since we do not have FirestoreClient instance to own them.
+ static std::list<DatabaseId> database_ids;
+ database_ids.emplace_back(projectID, database);
+ return [[FSTDocumentKeyReference alloc] initWithKey:FSTTestDocKey(path)
+ databaseID:&database_ids.back()];
}
-FSTQuery *FSTTestQuery(NSString *path) {
- return [FSTQuery queryWithPath:FSTTestPath(path)];
+FSTQuery *FSTTestQuery(const absl::string_view path) {
+ return [FSTQuery queryWithPath:testutil::Resource(path)];
}
-id<FSTFilter> FSTTestFilter(NSString *field, NSString *opString, id value) {
- FSTFieldPath *path = FSTTestFieldPath(field);
+id<FSTFilter> FSTTestFilter(const absl::string_view field, NSString *opString, id value) {
+ const FieldPath path = testutil::Field(field);
FSTRelationFilterOperator op;
if ([opString isEqualToString:@"<"]) {
op = FSTRelationFilterOperatorLessThan;
@@ -200,8 +226,8 @@ id<FSTFilter> FSTTestFilter(NSString *field, NSString *opString, id value) {
}
}
-FSTSortOrder *FSTTestOrderBy(NSString *field, NSString *direction) {
- FSTFieldPath *path = FSTTestFieldPath(field);
+FSTSortOrder *FSTTestOrderBy(const absl::string_view field, NSString *direction) {
+ const FieldPath path = testutil::Field(field);
BOOL ascending;
if ([direction isEqualToString:@"asc"]) {
ascending = YES;
@@ -213,9 +239,9 @@ FSTSortOrder *FSTTestOrderBy(NSString *field, NSString *direction) {
return [FSTSortOrder sortOrderWithFieldPath:path ascending:ascending];
}
-NSComparator FSTTestDocComparator(NSString *fieldPath) {
- FSTQuery *query = [[FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"docs" ]]]
- queryByAddingSortOrder:[FSTSortOrder sortOrderWithFieldPath:FSTTestFieldPath(fieldPath)
+NSComparator FSTTestDocComparator(const absl::string_view fieldPath) {
+ FSTQuery *query = [FSTTestQuery("docs")
+ queryByAddingSortOrder:[FSTSortOrder sortOrderWithFieldPath:testutil::Field(fieldPath)
ascending:YES]];
return [query comparator];
}
@@ -229,53 +255,48 @@ FSTDocumentSet *FSTTestDocSet(NSComparator comp, NSArray<FSTDocument *> *docs) {
}
FSTSetMutation *FSTTestSetMutation(NSString *path, NSDictionary<NSString *, id> *values) {
- return [[FSTSetMutation alloc] initWithKey:[FSTDocumentKey keyWithPathString:path]
+ return [[FSTSetMutation alloc] initWithKey:FSTTestDocKey(path)
value:FSTTestObjectValue(values)
- precondition:[FSTPrecondition none]];
+ precondition:Precondition::None()];
}
-FSTPatchMutation *FSTTestPatchMutation(NSString *path,
+FSTPatchMutation *FSTTestPatchMutation(const absl::string_view path,
NSDictionary<NSString *, id> *values,
- NSArray<FSTFieldPath *> *_Nullable updateMask) {
- BOOL merge = updateMask != nil;
+ const std::vector<FieldPath> &updateMask) {
+ BOOL merge = !updateMask.empty();
__block FSTObjectValue *objectValue = [FSTObjectValue objectValue];
- NSMutableArray<FSTFieldPath *> *fieldMaskPaths = [NSMutableArray array];
+ __block std::vector<FieldPath> fieldMaskPaths{};
[values enumerateKeysAndObjectsUsingBlock:^(NSString *key, id value, BOOL *stop) {
- FSTFieldPath *path = FSTTestFieldPath(key);
- [fieldMaskPaths addObject:path];
+ const FieldPath path = testutil::Field(util::MakeStringView(key));
+ fieldMaskPaths.push_back(path);
if (![value isEqual:kDeleteSentinel]) {
FSTFieldValue *parsedValue = FSTTestFieldValue(value);
objectValue = [objectValue objectBySettingValue:parsedValue forPath:path];
}
}];
- FSTDocumentKey *key = [FSTDocumentKey keyWithPath:FSTTestPath(path)];
- FSTFieldMask *mask = [[FSTFieldMask alloc] initWithFields:merge ? updateMask : fieldMaskPaths];
+ DocumentKey key = testutil::Key(path);
+ FieldMask mask(merge ? updateMask : fieldMaskPaths);
return [[FSTPatchMutation alloc] initWithKey:key
fieldMask:mask
value:objectValue
- precondition:[FSTPrecondition preconditionWithExists:YES]];
+ precondition:Precondition::Exists(true)];
}
-// For now this only creates TransformMutations with server timestamps.
-FSTTransformMutation *FSTTestTransformMutation(NSString *path,
- NSArray<NSString *> *serverTimestampFields) {
- FSTDocumentKey *key = [FSTDocumentKey keyWithPath:FSTTestPath(path)];
- NSMutableArray<FSTFieldTransform *> *fieldTransforms = [NSMutableArray array];
- for (NSString *field in serverTimestampFields) {
- FSTFieldPath *fieldPath = FSTTestFieldPath(field);
- id<FSTTransformOperation> transformOp = [FSTServerTimestampTransform serverTimestampTransform];
- FSTFieldTransform *transform =
- [[FSTFieldTransform alloc] initWithPath:fieldPath transform:transformOp];
- [fieldTransforms addObject:transform];
- }
- return [[FSTTransformMutation alloc] initWithKey:key fieldTransforms:fieldTransforms];
+FSTTransformMutation *FSTTestTransformMutation(NSString *path, NSDictionary<NSString *, id> *data) {
+ FSTDocumentKey *key = [FSTDocumentKey keyWithPath:testutil::Resource(util::MakeStringView(path))];
+ FSTUserDataConverter *converter = FSTTestUserDataConverter();
+ FSTParsedUpdateData *result = [converter parsedUpdateData:data];
+ FSTCAssert(result.data.value.count == 0,
+ @"FSTTestTransformMutation() only expects transforms; no other data");
+ return [[FSTTransformMutation alloc] initWithKey:key
+ fieldTransforms:std::move(result.fieldTransforms)];
}
FSTDeleteMutation *FSTTestDeleteMutation(NSString *path) {
- return [[FSTDeleteMutation alloc] initWithKey:[FSTDocumentKey keyWithPathString:path]
- precondition:[FSTPrecondition none]];
+ return
+ [[FSTDeleteMutation alloc] initWithKey:FSTTestDocKey(path) precondition:Precondition::None()];
}
FSTMaybeDocumentDictionary *FSTTestDocUpdates(NSArray<FSTMaybeDocument *> *docs) {
@@ -334,12 +355,12 @@ FSTLocalViewChanges *FSTTestViewChanges(FSTQuery *query,
NSArray<NSString *> *removedKeys) {
FSTDocumentKeySet *added = [FSTDocumentKeySet keySet];
for (NSString *keyPath in addedKeys) {
- FSTDocumentKey *key = [FSTDocumentKey keyWithPathString:keyPath];
+ FSTDocumentKey *key = FSTTestDocKey(keyPath);
added = [added setByAddingObject:key];
}
FSTDocumentKeySet *removed = [FSTDocumentKeySet keySet];
for (NSString *keyPath in removedKeys) {
- FSTDocumentKey *key = [FSTDocumentKey keyWithPathString:keyPath];
+ FSTDocumentKey *key = FSTTestDocKey(keyPath);
removed = [removed setByAddingObject:key];
}
return [FSTLocalViewChanges changesForQuery:query addedKeys:added removedKeys:removed];
diff --git a/Firestore/Example/Tests/Util/FSTIntegrationTestCase.h b/Firestore/Example/Tests/Util/FSTIntegrationTestCase.h
index 88f9346..9c80799 100644
--- a/Firestore/Example/Tests/Util/FSTIntegrationTestCase.h
+++ b/Firestore/Example/Tests/Util/FSTIntegrationTestCase.h
@@ -18,6 +18,7 @@
#import <XCTest/XCTest.h>
#import "Firestore/Example/Tests/Util/XCTestCase+Await.h"
+#import "Firestore/Source/Core/FSTTypes.h"
@class FIRCollectionReference;
@class FIRDocumentSnapshot;
@@ -27,6 +28,7 @@
@class FIRFirestoreSettings;
@class FIRQuery;
@class FSTEventAccumulator;
+@class FSTDispatchQueue;
NS_ASSUME_NONNULL_BEGIN
@@ -60,8 +62,6 @@ extern "C" {
- (FIRCollectionReference *)collectionRefWithDocuments:
(NSDictionary<NSString *, NSDictionary<NSString *, id> *> *)documents;
-- (void)waitForIdleFirestore:(FIRFirestore *)firestore;
-
- (void)writeAllDocuments:(NSDictionary<NSString *, NSDictionary<NSString *, id> *> *)documents
toCollection:(FIRCollectionReference *)collection;
@@ -82,6 +82,12 @@ extern "C" {
- (void)deleteDocumentRef:(FIRDocumentReference *)ref;
+- (void)disableNetwork;
+
+- (void)enableNetwork;
+
+- (FSTDispatchQueue *)queueForFirestore:(FIRFirestore *)firestore;
+
/**
* "Blocks" the current thread/run loop until the block returns YES.
* Should only be called on the main thread.
diff --git a/Firestore/Example/Tests/Util/FSTIntegrationTestCase.mm b/Firestore/Example/Tests/Util/FSTIntegrationTestCase.mm
index 3d30a77..2b1f9d0 100644
--- a/Firestore/Example/Tests/Util/FSTIntegrationTestCase.mm
+++ b/Firestore/Example/Tests/Util/FSTIntegrationTestCase.mm
@@ -21,17 +21,27 @@
#import <GRPCClient/GRPCCall+ChannelArg.h>
#import <GRPCClient/GRPCCall+Tests.h>
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "Firestore/core/src/firebase/firestore/auth/empty_credentials_provider.h"
+#include "Firestore/core/src/firebase/firestore/model/database_id.h"
#include "Firestore/core/src/firebase/firestore/util/autoid.h"
+#include "Firestore/core/src/firebase/firestore/util/string_apple.h"
+#include "absl/memory/memory.h"
#import "Firestore/Source/API/FIRFirestore+Internal.h"
-#import "Firestore/Source/Auth/FSTEmptyCredentialsProvider.h"
+#import "Firestore/Source/Core/FSTFirestoreClient.h"
#import "Firestore/Source/Local/FSTLevelDB.h"
-#import "Firestore/Source/Model/FSTDatabaseID.h"
#import "Firestore/Source/Util/FSTDispatchQueue.h"
#import "Firestore/Example/Tests/Util/FSTEventAccumulator.h"
-#import "Firestore/Example/Tests/Util/FSTTestDispatchQueue.h"
+namespace util = firebase::firestore::util;
+using firebase::firestore::auth::CredentialsProvider;
+using firebase::firestore::auth::EmptyCredentialsProvider;
+using firebase::firestore::model::DatabaseId;
using firebase::firestore::util::CreateAutoId;
NS_ASSUME_NONNULL_BEGIN
@@ -120,6 +130,7 @@ NS_ASSUME_NONNULL_BEGIN
}
settings.host = host;
settings.persistenceEnabled = YES;
+ settings.timestampsInSnapshotsEnabled = YES;
NSLog(@"Configured integration test for %@ with SSL: %@", settings.host,
settings.sslEnabled ? @"YES" : @"NO");
return settings;
@@ -128,18 +139,19 @@ NS_ASSUME_NONNULL_BEGIN
- (FIRFirestore *)firestoreWithProjectID:(NSString *)projectID {
NSString *persistenceKey = [NSString stringWithFormat:@"db%lu", (unsigned long)_firestores.count];
- FSTTestDispatchQueue *workerDispatchQueue = [FSTTestDispatchQueue
+ FSTDispatchQueue *workerDispatchQueue = [FSTDispatchQueue
queueWith:dispatch_queue_create("com.google.firebase.firestore", DISPATCH_QUEUE_SERIAL)];
- FSTEmptyCredentialsProvider *credentialsProvider = [[FSTEmptyCredentialsProvider alloc] init];
-
FIRSetLoggerLevel(FIRLoggerLevelDebug);
// HACK: FIRFirestore expects a non-nil app, but for tests we cheat.
FIRApp *app = nil;
- FIRFirestore *firestore = [[FIRFirestore alloc] initWithProjectID:projectID
- database:kDefaultDatabaseID
+ std::unique_ptr<CredentialsProvider> credentials_provider =
+ absl::make_unique<firebase::firestore::auth::EmptyCredentialsProvider>();
+
+ FIRFirestore *firestore = [[FIRFirestore alloc] initWithProjectID:util::MakeStringView(projectID)
+ database:DatabaseId::kDefault
persistenceKey:persistenceKey
- credentialsProvider:credentialsProvider
+ credentialsProvider:std::move(credentials_provider)
workerDispatchQueue:workerDispatchQueue
firebaseApp:app];
@@ -149,20 +161,8 @@ NS_ASSUME_NONNULL_BEGIN
return firestore;
}
-- (void)waitForIdleFirestore:(FIRFirestore *)firestore {
- XCTestExpectation *expectation = [self expectationWithDescription:@"idle"];
- // Note that we wait on any task that is scheduled with a delay of 60s. Currently, the idle
- // timeout is the only task that uses this delay.
- [((FSTTestDispatchQueue *)firestore.workerDispatchQueue) fulfillOnExecution:expectation];
- [self awaitExpectations];
-}
-
- (void)shutdownFirestore:(FIRFirestore *)firestore {
- XCTestExpectation *shutdownCompletion = [self expectationWithDescription:@"shutdown"];
- [firestore shutdownWithCompletion:^(NSError *_Nullable error) {
- XCTAssertNil(error);
- [shutdownCompletion fulfill];
- }];
+ [firestore shutdownWithCompletion:[self completionForExpectationWithName:@"shutdown"]];
[self awaitExpectations];
}
@@ -245,14 +245,15 @@ NS_ASSUME_NONNULL_BEGIN
XCTestExpectation *expectation = [self expectationWithDescription:@"listener"];
id<FIRListenerRegistration> listener = [ref
- addSnapshotListenerWithOptions:[[FIRDocumentListenOptions options] includeMetadataChanges:YES]
- listener:^(FIRDocumentSnapshot *snapshot, NSError *error) {
- XCTAssertNil(error);
- if (!requireOnline || !snapshot.metadata.fromCache) {
- result = snapshot;
- [expectation fulfill];
- }
- }];
+ addSnapshotListenerWithIncludeMetadataChanges:YES
+ listener:^(FIRDocumentSnapshot *snapshot,
+ NSError *error) {
+ XCTAssertNil(error);
+ if (!requireOnline || !snapshot.metadata.fromCache) {
+ result = snapshot;
+ [expectation fulfill];
+ }
+ }];
[self awaitExpectations];
[listener remove];
@@ -261,34 +262,36 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)writeDocumentRef:(FIRDocumentReference *)ref data:(NSDictionary<NSString *, id> *)data {
- XCTestExpectation *expectation = [self expectationWithDescription:@"setData"];
- [ref setData:data
- completion:^(NSError *_Nullable error) {
- XCTAssertNil(error);
- [expectation fulfill];
- }];
+ [ref setData:data completion:[self completionForExpectationWithName:@"setData"]];
[self awaitExpectations];
}
- (void)updateDocumentRef:(FIRDocumentReference *)ref data:(NSDictionary<id, id> *)data {
- XCTestExpectation *expectation = [self expectationWithDescription:@"updateData"];
- [ref updateData:data
- completion:^(NSError *_Nullable error) {
- XCTAssertNil(error);
- [expectation fulfill];
- }];
+ [ref updateData:data completion:[self completionForExpectationWithName:@"updateData"]];
[self awaitExpectations];
}
- (void)deleteDocumentRef:(FIRDocumentReference *)ref {
- XCTestExpectation *expectation = [self expectationWithDescription:@"deleteDocument"];
- [ref deleteDocumentWithCompletion:^(NSError *_Nullable error) {
- XCTAssertNil(error);
- [expectation fulfill];
- }];
+ [ref deleteDocumentWithCompletion:[self completionForExpectationWithName:@"deleteDocument"]];
[self awaitExpectations];
}
+- (void)disableNetwork {
+ [self.db.client
+ disableNetworkWithCompletion:[self completionForExpectationWithName:@"Disable Network."]];
+ [self awaitExpectations];
+}
+
+- (void)enableNetwork {
+ [self.db.client
+ enableNetworkWithCompletion:[self completionForExpectationWithName:@"Enable Network."]];
+ [self awaitExpectations];
+}
+
+- (FSTDispatchQueue *)queueForFirestore:(FIRFirestore *)firestore {
+ return firestore.workerDispatchQueue;
+}
+
- (void)waitUntil:(BOOL (^)())predicate {
NSTimeInterval start = [NSDate timeIntervalSinceReferenceDate];
double waitSeconds = [self defaultExpectationWaitSeconds];
diff --git a/Firestore/Example/Tests/Util/FSTTestDispatchQueue.m b/Firestore/Example/Tests/Util/FSTTestDispatchQueue.m
deleted file mode 100644
index 8124cf2..0000000
--- a/Firestore/Example/Tests/Util/FSTTestDispatchQueue.m
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright 2017 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 "Firestore/Example/Tests/Util/FSTTestDispatchQueue.h"
-
-#import <XCTest/XCTestExpectation.h>
-
-#import "Firestore/Source/Util/FSTAssert.h"
-
-@interface FSTTestDispatchQueue ()
-
-@property(nonatomic, weak) XCTestExpectation* expectation;
-
-@end
-
-@implementation FSTTestDispatchQueue
-
-/** The delay used by the idle timeout */
-static const NSTimeInterval kIdleDispatchDelay = 60.0;
-
-/** The maximum delay we use in a test run. */
-static const NSTimeInterval kTestDispatchDelay = 1.0;
-
-+ (instancetype)queueWith:(dispatch_queue_t)dispatchQueue {
- return [[FSTTestDispatchQueue alloc] initWithQueue:dispatchQueue];
-}
-
-- (instancetype)initWithQueue:(dispatch_queue_t)dispatchQueue {
- return (self = [super initWithQueue:dispatchQueue]);
-}
-
-- (void)dispatchAfterDelay:(NSTimeInterval)delay block:(void (^)(void))block {
- [super dispatchAfterDelay:MIN(delay, kTestDispatchDelay)
- block:^() {
- block();
- if (delay == kIdleDispatchDelay) {
- [_expectation fulfill];
- _expectation = nil;
- }
- }];
-}
-
-- (void)fulfillOnExecution:(XCTestExpectation*)expectation {
- FSTAssert(_expectation == nil, @"Previous expectation still active");
- _expectation = expectation;
-}
-
-@end
diff --git a/Firestore/Example/Tests/Util/XCTestCase+Await.h b/Firestore/Example/Tests/Util/XCTestCase+Await.h
index 9d575f9..1afe8c0 100644
--- a/Firestore/Example/Tests/Util/XCTestCase+Await.h
+++ b/Firestore/Example/Tests/Util/XCTestCase+Await.h
@@ -16,6 +16,8 @@
#import <XCTest/XCTest.h>
+#import "Firestore/Source/Core/FSTTypes.h"
+
@interface XCTestCase (Await)
/**
@@ -29,4 +31,10 @@
*/
- (double)defaultExpectationWaitSeconds;
+/**
+ * Returns a completion block that fulfills a newly-created expectation with the specified
+ * name.
+ */
+- (FSTVoidErrorBlock)completionForExpectationWithName:(NSString *)expectationName;
+
@end
diff --git a/Firestore/Example/Tests/Util/XCTestCase+Await.m b/Firestore/Example/Tests/Util/XCTestCase+Await.mm
index 15c67ca..a5fefc9 100644
--- a/Firestore/Example/Tests/Util/XCTestCase+Await.m
+++ b/Firestore/Example/Tests/Util/XCTestCase+Await.mm
@@ -18,7 +18,9 @@
#import <Foundation/Foundation.h>
-static const double kExpectationWaitSeconds = 10.0;
+// TODO(b/72864027): Reduce this to 10 seconds again once we've resolved issues with Query
+// Conformance Tests flakiness or gotten answers from GRPC about reconnect delays.
+static const double kExpectationWaitSeconds = 25.0;
@implementation XCTestCase (Await)
@@ -35,4 +37,12 @@ static const double kExpectationWaitSeconds = 10.0;
return kExpectationWaitSeconds;
}
+- (FSTVoidErrorBlock)completionForExpectationWithName:(NSString *)expectationName {
+ XCTestExpectation *expectation = [self expectationWithDescription:expectationName];
+ return ^(NSError *error) {
+ XCTAssertNil(error);
+ [expectation fulfill];
+ };
+}
+
@end
diff --git a/Firestore/Port/absl/absl_attributes.h b/Firestore/Port/absl/absl_attributes.h
deleted file mode 100644
index d43930c..0000000
--- a/Firestore/Port/absl/absl_attributes.h
+++ /dev/null
@@ -1,644 +0,0 @@
-/*
- * Copyright 2017 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.
- */
-
-// Various macros for C++ attributes
-// Most macros here are exposing GCC or Clang features, and are stubbed out for
-// other compilers.
-// GCC attributes documentation:
-// https://gcc.gnu.org/onlinedocs/gcc-4.7.0/gcc/Function-Attributes.html
-// https://gcc.gnu.org/onlinedocs/gcc-4.7.0/gcc/Variable-Attributes.html
-// https://gcc.gnu.org/onlinedocs/gcc-4.7.0/gcc/Type-Attributes.html
-//
-// Most attributes in this file are already supported by GCC 4.7.
-// However, some of them are not supported in older version of Clang.
-// Thus, we check __has_attribute() first. If the check fails, we check if we
-// are on GCC and assume the attribute exists on GCC (which is verified on GCC
-// 4.7).
-//
-// For sanitizer-related attributes, define the following macros
-// using -D along with the given value for -fsanitize:
-// - ADDRESS_SANITIZER with -fsanitize=address (GCC 4.8+, Clang)
-// - MEMORY_SANITIZER with -fsanitize=memory (Clang)
-// - THREAD_SANITIZER with -fsanitize=thread (GCC 4.8+, Clang)
-// - UNDEFINED_BEHAVIOR_SANITIZER with -fsanitize=undefined (GCC 4.9+, Clang)
-// - CONTROL_FLOW_INTEGRITY with -fsanitize=cfi (Clang)
-// Since these are only supported by GCC and Clang now, we only check for
-// __GNUC__ (GCC or Clang) and the above macros.
-#ifndef THIRD_PARTY_ABSL_BASE_ATTRIBUTES_H_
-#define THIRD_PARTY_ABSL_BASE_ATTRIBUTES_H_
-
-// ABSL_HAVE_ATTRIBUTE is a function-like feature checking macro.
-// It's a wrapper around __has_attribute, which is defined by GCC 5+ and Clang.
-// It evaluates to a nonzero constant integer if the attribute is supported
-// or 0 if not.
-// It evaluates to zero if __has_attribute is not defined by the compiler.
-// GCC: https://gcc.gnu.org/gcc-5/changes.html
-// Clang: https://clang.llvm.org/docs/LanguageExtensions.html
-#ifdef __has_attribute
-#define ABSL_HAVE_ATTRIBUTE(x) __has_attribute(x)
-#else
-#define ABSL_HAVE_ATTRIBUTE(x) 0
-#endif
-
-// ABSL_HAVE_CPP_ATTRIBUTE is a function-like feature checking macro that
-// accepts C++11 style attributes. It's a wrapper around __has_cpp_attribute,
-// defined by ISO C++ SD-6
-// (http://en.cppreference.com/w/cpp/experimental/feature_test). If we don't
-// find __has_cpp_attribute, will evaluate to 0.
-#if defined(__cplusplus) && defined(__has_cpp_attribute)
-// NOTE: requiring __cplusplus above should not be necessary, but
-// works around https://bugs.llvm.org/show_bug.cgi?id=23435.
-#define ABSL_HAVE_CPP_ATTRIBUTE(x) __has_cpp_attribute(x)
-#else
-#define ABSL_HAVE_CPP_ATTRIBUTE(x) 0
-#endif
-
-// -----------------------------------------------------------------------------
-// Function Attributes
-// -----------------------------------------------------------------------------
-// GCC: https://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html
-// Clang: https://clang.llvm.org/docs/AttributeReference.html
-
-// PRINTF_ATTRIBUTE, SCANF_ATTRIBUTE
-// Tell the compiler to do printf format std::string checking if the
-// compiler supports it; see the 'format' attribute in
-// <http://gcc.gnu.org/onlinedocs/gcc-4.7.0/gcc/Function-Attributes.html>.
-//
-// N.B.: As the GCC manual states, "[s]ince non-static C++ methods
-// have an implicit 'this' argument, the arguments of such methods
-// should be counted from two, not one."
-#if ABSL_HAVE_ATTRIBUTE(format) || (defined(__GNUC__) && !defined(__clang__))
-#define ABSL_PRINTF_ATTRIBUTE(string_index, first_to_check) \
- __attribute__((__format__(__printf__, string_index, first_to_check)))
-#define ABSL_SCANF_ATTRIBUTE(string_index, first_to_check) \
- __attribute__((__format__(__scanf__, string_index, first_to_check)))
-#else
-#define ABSL_PRINTF_ATTRIBUTE(string_index, first_to_check)
-#define ABSL_SCANF_ATTRIBUTE(string_index, first_to_check)
-#endif
-
-// To be deleted macros. All macros are going te be renamed with ABSL_ prefix.
-#if ABSL_HAVE_ATTRIBUTE(format) || (defined(__GNUC__) && !defined(__clang__))
-#define PRINTF_ATTRIBUTE(string_index, first_to_check) \
- __attribute__((__format__(__printf__, string_index, first_to_check)))
-#define SCANF_ATTRIBUTE(string_index, first_to_check) \
- __attribute__((__format__(__scanf__, string_index, first_to_check)))
-#else
-#define PRINTF_ATTRIBUTE(string_index, first_to_check)
-#define SCANF_ATTRIBUTE(string_index, first_to_check)
-#endif
-
-// ATTRIBUTE_ALWAYS_INLINE, ATTRIBUTE_NOINLINE
-// For functions we want to force inline or not inline.
-// Introduced in gcc 3.1.
-#if ABSL_HAVE_ATTRIBUTE(always_inline) || (defined(__GNUC__) && !defined(__clang__))
-#define ABSL_ATTRIBUTE_ALWAYS_INLINE __attribute__((always_inline))
-#define ABSL_HAVE_ATTRIBUTE_ALWAYS_INLINE 1
-#else
-#define ABSL_ATTRIBUTE_ALWAYS_INLINE
-#endif
-
-#if ABSL_HAVE_ATTRIBUTE(noinline) || (defined(__GNUC__) && !defined(__clang__))
-#define ABSL_ATTRIBUTE_NOINLINE __attribute__((noinline))
-#define ABSL_HAVE_ATTRIBUTE_NOINLINE 1
-#else
-#define ABSL_ATTRIBUTE_NOINLINE
-#endif
-
-// To be deleted macros. All macros are going te be renamed with ABSL_ prefix.
-#if ABSL_HAVE_ATTRIBUTE(always_inline) || (defined(__GNUC__) && !defined(__clang__))
-#define ATTRIBUTE_ALWAYS_INLINE __attribute__((always_inline))
-#define HAVE_ATTRIBUTE_ALWAYS_INLINE 1
-#else
-#define ATTRIBUTE_ALWAYS_INLINE
-#endif
-
-#if ABSL_HAVE_ATTRIBUTE(noinline) || (defined(__GNUC__) && !defined(__clang__))
-#define ATTRIBUTE_NOINLINE __attribute__((noinline))
-#define HAVE_ATTRIBUTE_NOINLINE 1
-#else
-#define ATTRIBUTE_NOINLINE
-#endif
-
-// ATTRIBUTE_NO_TAIL_CALL
-// Prevent the compiler from optimizing away stack frames for functions which
-// end in a call to another function.
-#if ABSL_HAVE_ATTRIBUTE(disable_tail_calls)
-#define ABSL_HAVE_ATTRIBUTE_NO_TAIL_CALL 1
-#define ABSL_ATTRIBUTE_NO_TAIL_CALL __attribute__((disable_tail_calls))
-#elif defined(__GNUC__) && !defined(__clang__)
-#define ABSL_HAVE_ATTRIBUTE_NO_TAIL_CALL 1
-#define ABSL_ATTRIBUTE_NO_TAIL_CALL __attribute__((optimize("no-optimize-sibling-calls")))
-#else
-#define ABSL_ATTRIBUTE_NO_TAIL_CALL
-#define ABSL_HAVE_ATTRIBUTE_NO_TAIL_CALL 0
-#endif
-
-// To be deleted macros. All macros are going te be renamed with ABSL_ prefix.
-#if ABSL_HAVE_ATTRIBUTE(disable_tail_calls)
-#define HAVE_ATTRIBUTE_NO_TAIL_CALL 1
-#define ATTRIBUTE_NO_TAIL_CALL __attribute__((disable_tail_calls))
-#elif defined(__GNUC__) && !defined(__clang__)
-#define HAVE_ATTRIBUTE_NO_TAIL_CALL 1
-#define ATTRIBUTE_NO_TAIL_CALL __attribute__((optimize("no-optimize-sibling-calls")))
-#else
-#define ATTRIBUTE_NO_TAIL_CALL
-#define HAVE_ATTRIBUTE_NO_TAIL_CALL 0
-#endif
-
-// ATTRIBUTE_WEAK
-// For weak functions
-#if ABSL_HAVE_ATTRIBUTE(weak) || (defined(__GNUC__) && !defined(__clang__))
-#undef ABSL_ATTRIBUTE_WEAK
-#define ABSL_ATTRIBUTE_WEAK __attribute__((weak))
-#define ABSL_HAVE_ATTRIBUTE_WEAK 1
-#else
-#define ABSL_ATTRIBUTE_WEAK
-#define ABSL_HAVE_ATTRIBUTE_WEAK 0
-#endif
-
-// To be deleted macros. All macros are going te be renamed with ABSL_ prefix.
-#if ABSL_HAVE_ATTRIBUTE(weak) || (defined(__GNUC__) && !defined(__clang__))
-#undef ATTRIBUTE_WEAK
-#define ATTRIBUTE_WEAK __attribute__((weak))
-#define HAVE_ATTRIBUTE_WEAK 1
-#else
-#define ATTRIBUTE_WEAK
-#define HAVE_ATTRIBUTE_WEAK 0
-#endif
-
-// ATTRIBUTE_NONNULL
-// Tell the compiler either that a particular function parameter
-// should be a non-null pointer, or that all pointer arguments should
-// be non-null.
-//
-// Note: As the GCC manual states, "[s]ince non-static C++ methods
-// have an implicit 'this' argument, the arguments of such methods
-// should be counted from two, not one."
-//
-// Args are indexed starting at 1.
-// For non-static class member functions, the implicit "this" argument
-// is arg 1, and the first explicit argument is arg 2.
-// For static class member functions, there is no implicit "this", and
-// the first explicit argument is arg 1.
-//
-// /* arg_a cannot be null, but arg_b can */
-// void Function(void* arg_a, void* arg_b) ATTRIBUTE_NONNULL(1);
-//
-// class C {
-// /* arg_a cannot be null, but arg_b can */
-// void Method(void* arg_a, void* arg_b) ATTRIBUTE_NONNULL(2);
-//
-// /* arg_a cannot be null, but arg_b can */
-// static void StaticMethod(void* arg_a, void* arg_b) ATTRIBUTE_NONNULL(1);
-// };
-//
-// If no arguments are provided, then all pointer arguments should be non-null.
-//
-// /* No pointer arguments may be null. */
-// void Function(void* arg_a, void* arg_b, int arg_c) ATTRIBUTE_NONNULL();
-//
-// NOTE: The GCC nonnull attribute actually accepts a list of arguments, but
-// ATTRIBUTE_NONNULL does not.
-#if ABSL_HAVE_ATTRIBUTE(nonnull) || (defined(__GNUC__) && !defined(__clang__))
-#define ABSL_ATTRIBUTE_NONNULL(arg_index) __attribute__((nonnull(arg_index)))
-#else
-#define ABSL_ATTRIBUTE_NONNULL(...)
-#endif
-
-// To be deleted macros. All macros are going te be renamed with ABSL_ prefix.
-#if ABSL_HAVE_ATTRIBUTE(nonnull) || (defined(__GNUC__) && !defined(__clang__))
-#define ATTRIBUTE_NONNULL(arg_index) __attribute__((nonnull(arg_index)))
-#else
-#define ATTRIBUTE_NONNULL(...)
-#endif
-
-// ATTRIBUTE_NORETURN
-// Tell the compiler that a given function never returns
-#if ABSL_HAVE_ATTRIBUTE(noreturn) || (defined(__GNUC__) && !defined(__clang__))
-#define ABSL_ATTRIBUTE_NORETURN __attribute__((noreturn))
-#elif defined(_MSC_VER)
-#define ABSL_ATTRIBUTE_NORETURN __declspec(noreturn)
-#else
-#define ABSL_ATTRIBUTE_NORETURN
-#endif
-
-// To be deleted macros. All macros are going te be renamed with ABSL_ prefix.
-#if ABSL_HAVE_ATTRIBUTE(noreturn) || (defined(__GNUC__) && !defined(__clang__))
-#define ATTRIBUTE_NORETURN __attribute__((noreturn))
-#elif defined(_MSC_VER)
-#define ATTRIBUTE_NORETURN __declspec(noreturn)
-#else
-#define ATTRIBUTE_NORETURN
-#endif
-
-// ATTRIBUTE_NO_SANITIZE_ADDRESS
-// Tell AddressSanitizer (or other memory testing tools) to ignore a given
-// function. Useful for cases when a function reads random locations on stack,
-// calls _exit from a cloned subprocess, deliberately accesses buffer
-// out of bounds or does other scary things with memory.
-// NOTE: GCC supports AddressSanitizer(asan) since 4.8.
-// https://gcc.gnu.org/gcc-4.8/changes.html
-#if defined(__GNUC__) && defined(ADDRESS_SANITIZER)
-#define ABSL_ATTRIBUTE_NO_SANITIZE_ADDRESS __attribute__((no_sanitize_address))
-#else
-#define ABSL_ATTRIBUTE_NO_SANITIZE_ADDRESS
-#endif
-
-// To be deleted macros. All macros are going te be renamed with ABSL_ prefix.
-#if defined(__GNUC__) && defined(ADDRESS_SANITIZER)
-#define ATTRIBUTE_NO_SANITIZE_ADDRESS __attribute__((no_sanitize_address))
-#else
-#define ATTRIBUTE_NO_SANITIZE_ADDRESS
-#endif
-
-// ATTRIBUTE_NO_SANITIZE_MEMORY
-// Tell MemorySanitizer to relax the handling of a given function. All "Use of
-// uninitialized value" warnings from such functions will be suppressed, and all
-// values loaded from memory will be considered fully initialized.
-// This is similar to the ADDRESS_SANITIZER attribute above, but deals with
-// initializedness rather than addressability issues.
-// NOTE: MemorySanitizer(msan) is supported by Clang but not GCC.
-#if defined(__GNUC__) && defined(MEMORY_SANITIZER)
-#define ABSL_ATTRIBUTE_NO_SANITIZE_MEMORY __attribute__((no_sanitize_memory))
-#else
-#define ABSL_ATTRIBUTE_NO_SANITIZE_MEMORY
-#endif
-
-// To be deleted macros. All macros are going te be renamed with ABSL_ prefix.
-#if defined(__GNUC__) && defined(MEMORY_SANITIZER)
-#define ATTRIBUTE_NO_SANITIZE_MEMORY __attribute__((no_sanitize_memory))
-#else
-#define ATTRIBUTE_NO_SANITIZE_MEMORY
-#endif
-
-// ATTRIBUTE_NO_SANITIZE_THREAD
-// Tell ThreadSanitizer to not instrument a given function.
-// If you are adding this attribute, please cc dynamic-tools@ on the cl.
-// NOTE: GCC supports ThreadSanitizer(tsan) since 4.8.
-// https://gcc.gnu.org/gcc-4.8/changes.html
-#if defined(__GNUC__) && defined(THREAD_SANITIZER)
-#define ABSL_ATTRIBUTE_NO_SANITIZE_THREAD __attribute__((no_sanitize_thread))
-#else
-#define ABSL_ATTRIBUTE_NO_SANITIZE_THREAD
-#endif
-
-// To be deleted macros. All macros are going te be renamed with ABSL_ prefix.
-#if defined(__GNUC__) && defined(THREAD_SANITIZER)
-#define ATTRIBUTE_NO_SANITIZE_THREAD __attribute__((no_sanitize_thread))
-#else
-#define ATTRIBUTE_NO_SANITIZE_THREAD
-#endif
-
-// ATTRIBUTE_NO_SANITIZE_UNDEFINED
-// Tell UndefinedSanitizer to ignore a given function. Useful for cases
-// where certain behavior (eg. devision by zero) is being used intentionally.
-// NOTE: GCC supports UndefinedBehaviorSanitizer(ubsan) since 4.9.
-// https://gcc.gnu.org/gcc-4.9/changes.html
-#if defined(__GNUC__) && defined(UNDEFINED_BEHAVIOR_SANITIZER)
-#define ABSL_ATTRIBUTE_NO_SANITIZE_UNDEFINED __attribute__((no_sanitize("undefined")))
-#else
-#define ABSL_ATTRIBUTE_NO_SANITIZE_UNDEFINED
-#endif
-
-// To be deleted macros. All macros are going te be renamed with ABSL_ prefix.
-#if defined(__GNUC__) && defined(UNDEFINED_BEHAVIOR_SANITIZER)
-#define ATTRIBUTE_NO_SANITIZE_UNDEFINED __attribute__((no_sanitize("undefined")))
-#else
-#define ATTRIBUTE_NO_SANITIZE_UNDEFINED
-#endif
-
-// ATTRIBUTE_NO_SANITIZE_CFI
-// Tell ControlFlowIntegrity sanitizer to not instrument a given function.
-#if defined(__GNUC__) && defined(CONTROL_FLOW_INTEGRITY)
-#define ABSL_ATTRIBUTE_NO_SANITIZE_CFI __attribute__((no_sanitize("cfi")))
-#else
-#define ABSL_ATTRIBUTE_NO_SANITIZE_CFI
-#endif
-
-// To be deleted macros. All macros are going te be renamed with ABSL_ prefix.
-#if defined(__GNUC__) && defined(CONTROL_FLOW_INTEGRITY)
-#define ATTRIBUTE_NO_SANITIZE_CFI __attribute__((no_sanitize("cfi")))
-#else
-#define ATTRIBUTE_NO_SANITIZE_CFI
-#endif
-
-// ATTRIBUTE_SECTION
-// Labeled sections are not supported on Darwin/iOS.
-#ifdef ABSL_HAVE_ATTRIBUTE_SECTION
-#error ABSL_HAVE_ATTRIBUTE_SECTION cannot be directly set
-#elif (ABSL_HAVE_ATTRIBUTE(section) || (defined(__GNUC__) && !defined(__clang__))) && \
- !(defined(__APPLE__) && defined(__MACH__))
-#define ABSL_HAVE_ATTRIBUTE_SECTION 1
-//
-// Tell the compiler/linker to put a given function into a section and define
-// "__start_ ## name" and "__stop_ ## name" symbols to bracket the section.
-// This functionality is supported by GNU linker.
-// Any function with ATTRIBUTE_SECTION must not be inlined, or it will
-// be placed into whatever section its caller is placed into.
-//
-#ifndef ABSL_ATTRIBUTE_SECTION
-#define ABSL_ATTRIBUTE_SECTION(name) __attribute__((section(#name))) __attribute__((noinline))
-#endif
-
-// To be deleted macros. All macros are going te be renamed with ABSL_ prefix.
-#ifndef ATTRIBUTE_SECTION
-#define ATTRIBUTE_SECTION(name) __attribute__((section(#name))) __attribute__((noinline))
-#endif
-
-// Tell the compiler/linker to put a given variable into a section and define
-// "__start_ ## name" and "__stop_ ## name" symbols to bracket the section.
-// This functionality is supported by GNU linker.
-#ifndef ABSL_ATTRIBUTE_SECTION_VARIABLE
-#define ABSL_ATTRIBUTE_SECTION_VARIABLE(name) __attribute__((section(#name)))
-#endif
-
-// To be deleted macros. All macros are going te be renamed with ABSL_ prefix.
-#ifndef ATTRIBUTE_SECTION_VARIABLE
-#define ATTRIBUTE_SECTION_VARIABLE(name) __attribute__((section(#name)))
-#endif
-
-//
-// Weak section declaration to be used as a global declaration
-// for ATTRIBUTE_SECTION_START|STOP(name) to compile and link
-// even without functions with ATTRIBUTE_SECTION(name).
-// DEFINE_ATTRIBUTE_SECTION should be in the exactly one file; it's
-// a no-op on ELF but not on Mach-O.
-//
-#ifndef ABSL_DECLARE_ATTRIBUTE_SECTION_VARS
-#define ABSL_DECLARE_ATTRIBUTE_SECTION_VARS(name) \
- extern char __start_##name[] ATTRIBUTE_WEAK; \
- extern char __stop_##name[] ATTRIBUTE_WEAK
-#endif
-#ifndef ABSL_DEFINE_ATTRIBUTE_SECTION_VARS
-#define ABSL_INIT_ATTRIBUTE_SECTION_VARS(name)
-#define ABSL_DEFINE_ATTRIBUTE_SECTION_VARS(name)
-#endif
-
-// To be deleted macros. All macros are going te be renamed with ABSL_ prefix.
-#ifndef DECLARE_ATTRIBUTE_SECTION_VARS
-#define DECLARE_ATTRIBUTE_SECTION_VARS(name) \
- extern char __start_##name[] ATTRIBUTE_WEAK; \
- extern char __stop_##name[] ATTRIBUTE_WEAK
-#endif
-#ifndef DEFINE_ATTRIBUTE_SECTION_VARS
-#define INIT_ATTRIBUTE_SECTION_VARS(name)
-#define DEFINE_ATTRIBUTE_SECTION_VARS(name)
-#endif
-
-//
-// Return void* pointers to start/end of a section of code with
-// functions having ATTRIBUTE_SECTION(name).
-// Returns 0 if no such functions exits.
-// One must DECLARE_ATTRIBUTE_SECTION_VARS(name) for this to compile and link.
-//
-#define ABSL_ATTRIBUTE_SECTION_START(name) (reinterpret_cast<void *>(__start_##name))
-#define ABSL_ATTRIBUTE_SECTION_STOP(name) (reinterpret_cast<void *>(__stop_##name))
-
-// To be deleted macros. All macros are going te be renamed with ABSL_ prefix.
-#define ATTRIBUTE_SECTION_START(name) (reinterpret_cast<void *>(__start_##name))
-#define ATTRIBUTE_SECTION_STOP(name) (reinterpret_cast<void *>(__stop_##name))
-
-#else // !ABSL_HAVE_ATTRIBUTE_SECTION
-
-#define ABSL_HAVE_ATTRIBUTE_SECTION 0
-
-// provide dummy definitions
-#define ABSL_ATTRIBUTE_SECTION(name)
-#define ABSL_ATTRIBUTE_SECTION_VARIABLE(name)
-#define ABSL_INIT_ATTRIBUTE_SECTION_VARS(name)
-#define ABSL_DEFINE_ATTRIBUTE_SECTION_VARS(name)
-#define ABSL_DECLARE_ATTRIBUTE_SECTION_VARS(name)
-#define ABSL_ATTRIBUTE_SECTION_START(name) (reinterpret_cast<void *>(0))
-#define ABSL_ATTRIBUTE_SECTION_STOP(name) (reinterpret_cast<void *>(0))
-
-// To be deleted macros. All macros are going te be renamed with ABSL_ prefix.
-#define ATTRIBUTE_SECTION(name)
-#define ATTRIBUTE_SECTION_VARIABLE(name)
-#define INIT_ATTRIBUTE_SECTION_VARS(name)
-#define DEFINE_ATTRIBUTE_SECTION_VARS(name)
-#define DECLARE_ATTRIBUTE_SECTION_VARS(name)
-#define ATTRIBUTE_SECTION_START(name) (reinterpret_cast<void *>(0))
-#define ATTRIBUTE_SECTION_STOP(name) (reinterpret_cast<void *>(0))
-
-#endif // ATTRIBUTE_SECTION
-
-// ATTRIBUTE_STACK_ALIGN_FOR_OLD_LIBC
-// Support for aligning the stack on 32-bit x86.
-#if ABSL_HAVE_ATTRIBUTE(force_align_arg_pointer) || (defined(__GNUC__) && !defined(__clang__))
-#if defined(__i386__)
-#define ABSL_ATTRIBUTE_STACK_ALIGN_FOR_OLD_LIBC __attribute__((force_align_arg_pointer))
-#define ABSL_REQUIRE_STACK_ALIGN_TRAMPOLINE (0)
-#elif defined(__x86_64__)
-#define ABSL_REQUIRE_STACK_ALIGN_TRAMPOLINE (1)
-#define ABSL_ATTRIBUTE_STACK_ALIGN_FOR_OLD_LIBC
-#else // !__i386__ && !__x86_64
-#define ABSL_REQUIRE_STACK_ALIGN_TRAMPOLINE (0)
-#define ABSL_ATTRIBUTE_STACK_ALIGN_FOR_OLD_LIBC
-#endif // __i386__
-#else
-#define ABSL_ATTRIBUTE_STACK_ALIGN_FOR_OLD_LIBC
-#define ABSL_REQUIRE_STACK_ALIGN_TRAMPOLINE (0)
-#endif
-
-// To be deleted macros. All macros are going te be renamed with ABSL_ prefix.
-#if ABSL_HAVE_ATTRIBUTE(force_align_arg_pointer) || (defined(__GNUC__) && !defined(__clang__))
-#if defined(__i386__)
-#define ATTRIBUTE_STACK_ALIGN_FOR_OLD_LIBC __attribute__((force_align_arg_pointer))
-#define REQUIRE_STACK_ALIGN_TRAMPOLINE (0)
-#elif defined(__x86_64__)
-#define REQUIRE_STACK_ALIGN_TRAMPOLINE (1)
-#define ATTRIBUTE_STACK_ALIGN_FOR_OLD_LIBC
-#else // !__i386__ && !__x86_64
-#define REQUIRE_STACK_ALIGN_TRAMPOLINE (0)
-#define ATTRIBUTE_STACK_ALIGN_FOR_OLD_LIBC
-#endif // __i386__
-#else
-#define ATTRIBUTE_STACK_ALIGN_FOR_OLD_LIBC
-#define REQUIRE_STACK_ALIGN_TRAMPOLINE (0)
-#endif
-
-// MUST_USE_RESULT
-// Tell the compiler to warn about unused return values for functions declared
-// with this macro. The macro must appear as the very first part of a function
-// declaration or definition:
-//
-// MUST_USE_RESULT Sprocket* AllocateSprocket();
-//
-// This placement has the broadest compatibility with GCC, Clang, and MSVC, with
-// both defs and decls, and with GCC-style attributes, MSVC declspec, and C++11
-// attributes. Note: past advice was to place the macro after the argument list.
-#if ABSL_HAVE_ATTRIBUTE(warn_unused_result) || (defined(__GNUC__) && !defined(__clang__))
-#define ABSL_MUST_USE_RESULT __attribute__((warn_unused_result))
-#else
-#define ABSL_MUST_USE_RESULT
-#endif
-
-// To be deleted macros. All macros are going te be renamed with ABSL_ prefix.
-#if ABSL_HAVE_ATTRIBUTE(warn_unused_result) || (defined(__GNUC__) && !defined(__clang__))
-#define MUST_USE_RESULT __attribute__((warn_unused_result))
-#else
-#define MUST_USE_RESULT
-#endif
-
-// ATTRIBUTE_HOT, ATTRIBUTE_COLD
-// Tell GCC that a function is hot or cold. GCC can use this information to
-// improve static analysis, i.e. a conditional branch to a cold function
-// is likely to be not-taken.
-// This annotation is used for function declarations, e.g.:
-// int foo() ATTRIBUTE_HOT;
-#if ABSL_HAVE_ATTRIBUTE(hot) || (defined(__GNUC__) && !defined(__clang__))
-#define ABSL_ATTRIBUTE_HOT __attribute__((hot))
-#else
-#define ABSL_ATTRIBUTE_HOT
-#endif
-
-#if ABSL_HAVE_ATTRIBUTE(cold) || (defined(__GNUC__) && !defined(__clang__))
-#define ABSL_ATTRIBUTE_COLD __attribute__((cold))
-#else
-#define ABSL_ATTRIBUTE_COLD
-#endif
-
-// To be deleted macros. All macros are going te be renamed with ABSL_ prefix.
-#if ABSL_HAVE_ATTRIBUTE(hot) || (defined(__GNUC__) && !defined(__clang__))
-#define ATTRIBUTE_HOT __attribute__((hot))
-#else
-#define ATTRIBUTE_HOT
-#endif
-
-#if ABSL_HAVE_ATTRIBUTE(cold) || (defined(__GNUC__) && !defined(__clang__))
-#define ATTRIBUTE_COLD __attribute__((cold))
-#else
-#define ATTRIBUTE_COLD
-#endif
-
-// ABSL_XRAY_ALWAYS_INSTRUMENT, ABSL_XRAY_NEVER_INSTRUMENT, ABSL_XRAY_LOG_ARGS
-//
-// We define the ABSL_XRAY_ALWAYS_INSTRUMENT and ABSL_XRAY_NEVER_INSTRUMENT
-// macro used as an attribute to mark functions that must always or never be
-// instrumented by XRay. Currently, this is only supported in Clang/LLVM.
-//
-// For reference on the LLVM XRay instrumentation, see
-// http://llvm.org/docs/XRay.html.
-//
-// A function with the XRAY_ALWAYS_INSTRUMENT macro attribute in its declaration
-// will always get the XRay instrumentation sleds. These sleds may introduce
-// some binary size and runtime overhead and must be used sparingly.
-//
-// These attributes only take effect when the following conditions are met:
-//
-// - The file/target is built in at least C++11 mode, with a Clang compiler
-// that supports XRay attributes.
-// - The file/target is built with the -fxray-instrument flag set for the
-// Clang/LLVM compiler.
-// - The function is defined in the translation unit (the compiler honors the
-// attribute in either the definition or the declaration, and must match).
-//
-// There are cases when, even when building with XRay instrumentation, users
-// might want to control specifically which functions are instrumented for a
-// particular build using special-case lists provided to the compiler. These
-// special case lists are provided to Clang via the
-// -fxray-always-instrument=... and -fxray-never-instrument=... flags. The
-// attributes in source take precedence over these special-case lists.
-//
-// To disable the XRay attributes at build-time, users may define
-// ABSL_NO_XRAY_ATTRIBUTES. Do NOT define ABSL_NO_XRAY_ATTRIBUTES on specific
-// packages/targets, as this may lead to conflicting definitions of functions at
-// link-time.
-//
-#if ABSL_HAVE_CPP_ATTRIBUTE(clang::xray_always_instrument) && !defined(ABSL_NO_XRAY_ATTRIBUTES)
-#define ABSL_XRAY_ALWAYS_INSTRUMENT [[clang::xray_always_instrument]]
-#define ABSL_XRAY_NEVER_INSTRUMENT [[clang::xray_never_instrument]]
-#define ABSL_XRAY_LOG_ARGS(N) [[ clang::xray_always_instrument, clang::xray_log_args(N) ]]
-#else
-#define ABSL_XRAY_ALWAYS_INSTRUMENT
-#define ABSL_XRAY_NEVER_INSTRUMENT
-#define ABSL_XRAY_LOG_ARGS(N)
-#endif
-
-// -----------------------------------------------------------------------------
-// Variable Attributes
-// -----------------------------------------------------------------------------
-
-// ATTRIBUTE_UNUSED
-// Prevent the compiler from complaining about or optimizing away variables
-// that appear unused.
-#if ABSL_HAVE_ATTRIBUTE(unused) || (defined(__GNUC__) && !defined(__clang__))
-#undef ABSL_ATTRIBUTE_UNUSED
-#define ABSL_ATTRIBUTE_UNUSED __attribute__((__unused__))
-#else
-#define ABSL_ATTRIBUTE_UNUSED
-#endif
-
-// To be deleted macros. All macros are going te be renamed with ABSL_ prefix.
-#if ABSL_HAVE_ATTRIBUTE(unused) || (defined(__GNUC__) && !defined(__clang__))
-#undef ATTRIBUTE_UNUSED
-#define ATTRIBUTE_UNUSED __attribute__((__unused__))
-#else
-#define ATTRIBUTE_UNUSED
-#endif
-
-// ATTRIBUTE_INITIAL_EXEC
-// Tell the compiler to use "initial-exec" mode for a thread-local variable.
-// See http://people.redhat.com/drepper/tls.pdf for the gory details.
-#if ABSL_HAVE_ATTRIBUTE(tls_model) || (defined(__GNUC__) && !defined(__clang__))
-#define ABSL_ATTRIBUTE_INITIAL_EXEC __attribute__((tls_model("initial-exec")))
-#else
-#define ABSL_ATTRIBUTE_INITIAL_EXEC
-#endif
-
-// To be deleted macros. All macros are going te be renamed with ABSL_ prefix.
-#if ABSL_HAVE_ATTRIBUTE(tls_model) || (defined(__GNUC__) && !defined(__clang__))
-#define ATTRIBUTE_INITIAL_EXEC __attribute__((tls_model("initial-exec")))
-#else
-#define ATTRIBUTE_INITIAL_EXEC
-#endif
-
-// ATTRIBUTE_PACKED
-// Prevent the compiler from padding a structure to natural alignment
-#if ABSL_HAVE_ATTRIBUTE(packed) || (defined(__GNUC__) && !defined(__clang__))
-#define ABSL_ATTRIBUTE_PACKED __attribute__((__packed__))
-#else
-#define ABSL_ATTRIBUTE_PACKED
-#endif
-
-// To be deleted macros. All macros are going te be renamed with ABSL_ prefix.
-#if ABSL_HAVE_ATTRIBUTE(packed) || (defined(__GNUC__) && !defined(__clang__))
-#define ATTRIBUTE_PACKED __attribute__((__packed__))
-#else
-#define ATTRIBUTE_PACKED
-#endif
-
-// ABSL_CONST_INIT
-// A variable declaration annotated with the ABSL_CONST_INIT attribute will
-// not compile (on supported platforms) unless the variable has a constant
-// initializer. This is useful for variables with static and thread storage
-// duration, because it guarantees that they will not suffer from the so-called
-// "static init order fiasco".
-//
-// Sample usage:
-//
-// ABSL_CONST_INIT static MyType my_var = MakeMyType(...);
-//
-// Note that this attribute is redundant if the variable is declared constexpr.
-#if ABSL_HAVE_CPP_ATTRIBUTE(clang::require_constant_initialization)
-// NOLINTNEXTLINE(whitespace/braces) (b/36288871)
-#define ABSL_CONST_INIT [[clang::require_constant_initialization]]
-#else
-#define ABSL_CONST_INIT
-#endif // ABSL_HAVE_CPP_ATTRIBUTE(clang::require_constant_initialization)
-
-#endif // THIRD_PARTY_ABSL_BASE_ATTRIBUTES_H_
diff --git a/Firestore/Port/absl/absl_config.h b/Firestore/Port/absl/absl_config.h
deleted file mode 100644
index 70f4d86..0000000
--- a/Firestore/Port/absl/absl_config.h
+++ /dev/null
@@ -1,306 +0,0 @@
-/*
- * Copyright 2017 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.
- */
-
-// Defines preprocessor macros describing the presence of "features" available.
-// This facilitates writing portable code by parameterizing the compilation
-// based on the presence or lack of a feature.
-//
-// We define a feature as some interface we wish to program to: for example,
-// some library function or system call.
-//
-// For example, suppose a programmer wants to write a program that uses the
-// 'mmap' system call. Then one might write:
-//
-// #include "absl/base/config.h"
-//
-// #ifdef ABSL_HAVE_MMAP
-// #include "sys/mman.h"
-// #endif //ABSL_HAVE_MMAP
-//
-// ...
-// #ifdef ABSL_HAVE_MMAP
-// void *ptr = mmap(...);
-// ...
-// #endif // ABSL_HAVE_MMAP
-//
-// As a special note, using feature macros from config.h to determine whether
-// to include a particular header requires violating the style guide's required
-// ordering for headers: this is permitted.
-
-#ifndef THIRD_PARTY_ABSL_BASE_CONFIG_H_
-#define THIRD_PARTY_ABSL_BASE_CONFIG_H_
-
-// Included for the __GLIBC__ macro (or similar macros on other systems).
-#include <limits.h>
-
-#ifdef __cplusplus
-// Included for __GLIBCXX__, _LIBCPP_VERSION
-#include <cstddef>
-#endif // __cplusplus
-
-// If we're using glibc, make sure we meet a minimum version requirement
-// before we proceed much further.
-//
-// We have chosen glibc 2.12 as the minimum as it was tagged for release
-// in May, 2010 and includes some functionality used in Google software
-// (for instance pthread_setname_np):
-// https://sourceware.org/ml/libc-alpha/2010-05/msg00000.html
-#ifdef __GLIBC_PREREQ
-#if !__GLIBC_PREREQ(2, 12)
-#error "Minimum required version of glibc is 2.12."
-#endif
-#endif
-
-// ABSL_HAVE_BUILTIN is a function-like feature checking macro.
-// It's a wrapper around __has_builtin, which is defined by only clang now.
-// It evaluates to 1 if the builtin is supported or 0 if not.
-// Define it to avoid an extra level of #ifdef __has_builtin check.
-// http://releases.llvm.org/3.3/tools/clang/docs/LanguageExtensions.html
-#ifdef __has_builtin
-#define ABSL_HAVE_BUILTIN(x) __has_builtin(x)
-#else
-#define ABSL_HAVE_BUILTIN(x) 0
-#endif
-
-// ABSL_HAVE_STD_IS_TRIVIALLY_DESTRUCTIBLE is defined when
-// std::is_trivially_destructible<T> is supported.
-//
-// All supported compilers using libc++ have it, as does gcc >= 4.8
-// using libstdc++, as does Visual Studio.
-// https://gcc.gnu.org/onlinedocs/gcc-4.8.1/libstdc++/manual/manual/status.html#status.iso.2011
-// is the first version where std::is_trivially_destructible no longer
-// appeared as missing in the Type properties row.
-#ifdef ABSL_HAVE_STD_IS_TRIVIALLY_DESTRUCTIBLE
-#error ABSL_HAVE_STD_IS_TRIVIALLY_DESTRUCTIBLE cannot be directly set
-#elif defined(_LIBCPP_VERSION) || \
- (!defined(__clang__) && defined(__GNUC__) && defined(__GLIBCXX__) && \
- (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8))) || \
- defined(_MSC_VER)
-#define ABSL_HAVE_STD_IS_TRIVIALLY_DESTRUCTIBLE 1
-#endif
-
-// ABSL_HAVE_STD_IS_TRIVIALLY_CONSTRUCTIBLE is defined when
-// std::is_trivially_default_constructible<T> and
-// std::is_trivially_copy_constructible<T> are supported.
-//
-// ABSL_HAVE_STD_IS_TRIVIALLY_ASSIGNABLE is defined when
-// std::is_trivially_copy_assignable<T> is supported.
-//
-// Clang with libc++ supports it, as does gcc >= 5.1 with either
-// libc++ or libstdc++, as does Visual Studio.
-// https://gcc.gnu.org/gcc-5/changes.html lists as new
-// "std::is_trivially_constructible, std::is_trivially_assignable
-// etc."
-#if defined(ABSL_HAVE_STD_IS_TRIVIALLY_CONSTRUCTIBLE)
-#error ABSL_HAVE_STD_IS_TRIVIALLY_CONSTRUCTIBLE cannot be directly set
-#elif defined(ABSL_HAVE_STD_IS_TRIVIALLY_ASSIGNABLE)
-#error ABSL_HAVE_STD_IS_TRIVIALLY_ASSIGNABLE cannot directly set
-#elif (defined(__clang__) && defined(_LIBCPP_VERSION)) || \
- (!defined(__clang__) && defined(__GNUC__) && \
- (__GNUC__ > 5 || (__GNUC__ == 5 && __GNUC_MINOR__ >= 1)) && \
- (defined(_LIBCPP_VERSION) || defined(__GLIBCXX__))) || \
- defined(_MSC_VER)
-#define ABSL_HAVE_STD_IS_TRIVIALLY_CONSTRUCTIBLE 1
-#define ABSL_HAVE_STD_IS_TRIVIALLY_ASSIGNABLE 1
-#endif
-
-// ABSL_HAVE_THREAD_LOCAL is defined when C++11's thread_local is available.
-// Clang implements thread_local keyword but Xcode did not support the
-// implementation until Xcode 8.
-#ifdef ABSL_HAVE_THREAD_LOCAL
-#error ABSL_HAVE_THREAD_LOCAL cannot be directly set
-#elif !defined(__apple_build_version__) || __apple_build_version__ >= 8000042
-#define ABSL_HAVE_THREAD_LOCAL 1
-#endif
-
-// ABSL_HAVE_INTRINSIC_INT128 is defined when the implementation provides the
-// 128 bit integral type: __int128.
-//
-// __SIZEOF_INT128__ is defined by Clang and GCC when __int128 is supported.
-// Clang on ppc64 and aarch64 are exceptions where __int128 exists but has a
-// sporadic compiler crashing bug. Nvidia's nvcc also defines __GNUC__ and
-// __SIZEOF_INT128__ but not all versions that do this support __int128. Support
-// has been tested for versions >= 7.
-#ifdef ABSL_HAVE_INTRINSIC_INT128
-#error ABSL_HAVE_INTRINSIC_INT128 cannot be directly set
-#elif (defined(__clang__) && defined(__SIZEOF_INT128__) && !defined(__ppc64__) && \
- !defined(__aarch64__)) || \
- (defined(__CUDACC__) && defined(__SIZEOF_INT128__) && __CUDACC_VER__ >= 70000) || \
- (!defined(__clang__) && !defined(__CUDACC__) && defined(__GNUC__) && \
- defined(__SIZEOF_INT128__))
-#define ABSL_HAVE_INTRINSIC_INT128 1
-#endif
-
-// Operating system-specific features.
-//
-// Currently supported operating systems and associated preprocessor
-// symbols:
-//
-// Linux and Linux-derived __linux__
-// Android __ANDROID__ (implies __linux__)
-// Linux (non-Android) __linux__ && !__ANDROID__
-// Darwin (Mac OS X and iOS) __APPLE__ && __MACH__
-// Akaros (http://akaros.org) __ros__
-// Windows _WIN32
-// NaCL __native_client__
-// AsmJS __asmjs__
-// Fuschia __Fuchsia__
-//
-// Note that since Android defines both __ANDROID__ and __linux__, one
-// may probe for either Linux or Android by simply testing for __linux__.
-//
-
-// ABSL_HAVE_MMAP is defined when the system has an mmap(2) implementation
-// as defined in POSIX.1-2001.
-#ifdef ABSL_HAVE_MMAP
-#error ABSL_HAVE_MMAP cannot be directly set
-#elif defined(__linux__) || (defined(__APPLE__) && defined(__MACH__)) || defined(__ros__) || \
- defined(__native_client__) || defined(__asmjs__) || defined(__Fuchsia__)
-#define ABSL_HAVE_MMAP 1
-#endif
-
-// ABSL_HAS_PTHREAD_GETSCHEDPARAM is defined when the system implements the
-// pthread_(get|set)schedparam(3) functions as defined in POSIX.1-2001.
-#ifdef ABSL_HAVE_PTHREAD_GETSCHEDPARAM
-#error ABSL_HAVE_PTHREAD_GETSCHEDPARAM cannot be directly set
-#elif defined(__linux__) || (defined(__APPLE__) && defined(__MACH__)) || defined(__ros__)
-#define ABSL_HAVE_PTHREAD_GETSCHEDPARAM 1
-#endif
-
-// ABSL_HAVE_SCHED_YIELD is defined when the system implements
-// sched_yield(2) as defined in POSIX.1-2001.
-#ifdef ABSL_HAVE_SCHED_YIELD
-#error ABSL_HAVE_SCHED_YIELD cannot be directly set
-#elif defined(__linux__) || defined(__ros__) || defined(__native_client__)
-#define ABSL_HAVE_SCHED_YIELD 1
-#endif
-
-// ABSL_HAVE_SEMAPHORE_H is defined when the system supports the <semaphore.h>
-// header and sem_open(3) family of functions as standardized in POSIX.1-2001.
-//
-// Note: While Apple does have <semaphore.h> for both iOS and macOS, it is
-// explicity deprecated and will cause build failures if enabled for those
-// systems. We side-step the issue by not defining it here for Apple platforms.
-#ifdef ABSL_HAVE_SEMAPHORE_H
-#error ABSL_HAVE_SEMAPHORE_H cannot be directly set
-#elif defined(__linux__) || defined(__ros__)
-#define ABSL_HAVE_SEMAPHORE_H 1
-#endif
-
-// Library-specific features.
-#ifdef ABSL_HAVE_ALARM
-#error ABSL_HAVE_ALARM cannot be directly set
-#elif defined(__GOOGLE_GRTE_VERSION__)
-// feature tests for Google's GRTE
-#define ABSL_HAVE_ALARM 1
-#elif defined(__GLIBC__)
-// feature test for glibc
-#define ABSL_HAVE_ALARM 1
-#elif defined(_MSC_VER)
-// feature tests for Microsoft's library
-#elif defined(__native_client__)
-#else
-// other standard libraries
-#define ABSL_HAVE_ALARM 1
-#endif
-
-#if defined(_STLPORT_VERSION)
-#error "STLPort is not supported."
-#endif
-
-// -----------------------------------------------------------------------------
-// Endianness
-// -----------------------------------------------------------------------------
-// Define ABSL_IS_LITTLE_ENDIAN, ABSL_IS_BIG_ENDIAN.
-// Some compilers or system headers provide macros to specify endianness.
-// Unfortunately, there is no standard for the names of the macros or even of
-// the header files.
-// Reference: https://sourceforge.net/p/predef/wiki/Endianness/
-#if defined(ABSL_IS_BIG_ENDIAN) || defined(ABSL_IS_LITTLE_ENDIAN)
-#error "ABSL_IS_(BIG|LITTLE)_ENDIAN cannot be directly set."
-
-#elif defined(__GLIBC__) || defined(__linux__)
-// Operating systems that use the GNU C library generally provide <endian.h>
-// containing __BYTE_ORDER, __LITTLE_ENDIAN, __BIG_ENDIAN.
-#include <endian.h>
-
-#if __BYTE_ORDER == __LITTLE_ENDIAN
-#define ABSL_IS_LITTLE_ENDIAN 1
-#elif __BYTE_ORDER == __BIG_ENDIAN
-#define ABSL_IS_BIG_ENDIAN 1
-#else // __BYTE_ORDER != __LITTLE_ENDIAN && __BYTE_ORDER != __BIG_ENDIAN
-#error "Unknown endianness"
-#endif // __BYTE_ORDER
-
-#elif defined(__APPLE__) && defined(__MACH__)
-// Apple has <machine/endian.h> containing BYTE_ORDER, BIG_ENDIAN,
-// LITTLE_ENDIAN.
-#include <machine/endian.h> // NOLINT(build/include)
-
-#if BYTE_ORDER == LITTLE_ENDIAN
-#define ABSL_IS_LITTLE_ENDIAN 1
-#elif BYTE_ORDER == BIG_ENDIAN
-#define ABSL_IS_BIG_ENDIAN 1
-#else // BYTE_ORDER != LITTLE_ENDIAN && BYTE_ORDER != BIG_ENDIAN
-#error "Unknown endianness"
-#endif // BYTE_ORDER
-
-#elif defined(_WIN32)
-// Assume Windows is little-endian.
-#define ABSL_IS_LITTLE_ENDIAN 1
-
-#elif defined(__LITTLE_ENDIAN__) || defined(__ARMEL__) || defined(__THUMBEL__) || \
- defined(__AARCH64EL__) || defined(_MIPSEL) || defined(__MIPSEL) || defined(__MIPSEL__)
-#define ABSL_IS_LITTLE_ENDIAN 1
-
-#elif defined(__BIG_ENDIAN__) || defined(__ARMEB__) || defined(__THUMBEB__) || \
- defined(__AARCH64EB__) || defined(_MIPSEB) || defined(__MIPSEB) || defined(__MIPSEB__)
-#define ABSL_IS_BIG_ENDIAN 1
-
-#else
-#error "absl endian detection needs to be set up on your platform."
-#endif
-
-// ABSL_HAVE_EXCEPTIONS is defined when exceptions are enabled. Many
-// compilers support a "no exceptions" mode that disables exceptions.
-//
-// Generally, when ABSL_HAVE_EXCEPTIONS is not defined:
-//
-// - Code using `throw` and `try` may not compile.
-// - The `noexcept` specifier will still compile and behave as normal.
-// - The `noexcept` operator may still return `false`.
-//
-// For further details, consult the compiler's documentation.
-#ifdef ABSL_HAVE_EXCEPTIONS
-#error ABSL_HAVE_EXCEPTIONS cannot be directly set.
-
-#elif defined(__clang__)
-// TODO
-// Switch to using __cpp_exceptions when we no longer support versions < 3.6.
-// For details on this check, see:
-// https://goo.gl/PilDrJ
-#if defined(__EXCEPTIONS) && __has_feature(cxx_exceptions)
-#define ABSL_HAVE_EXCEPTIONS 1
-#endif // defined(__EXCEPTIONS) && __has_feature(cxx_exceptions)
-
-// Handle remaining special cases and default to exceptions being supported.
-#elif !(defined(__GNUC__) && (__GNUC__ < 5) && !defined(__EXCEPTIONS)) && \
- !(defined(__GNUC__) && (__GNUC__ >= 5) && !defined(__cpp_exceptions)) && \
- !(defined(_MSC_VER) && !defined(_CPPUNWIND))
-#define ABSL_HAVE_EXCEPTIONS 1
-#endif
-
-#endif // THIRD_PARTY_ABSL_BASE_CONFIG_H_
diff --git a/Firestore/Port/absl/absl_endian.h b/Firestore/Port/absl/absl_endian.h
deleted file mode 100644
index 2c51a27..0000000
--- a/Firestore/Port/absl/absl_endian.h
+++ /dev/null
@@ -1,342 +0,0 @@
-/*
- * Copyright 2017 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.
- */
-
-#ifndef ABSL_BASE_INTERNAL_ENDIAN_H_
-#define ABSL_BASE_INTERNAL_ENDIAN_H_
-
-// The following guarantees declaration of the byte swap functions
-#ifdef _MSC_VER
-#include <stdlib.h> // NOLINT(build/include)
-#elif defined(__APPLE__) && defined(__MACH__)
-// Mac OS X / Darwin features
-#include <libkern/OSByteOrder.h>
-#elif defined(__GLIBC__)
-#include <byteswap.h> // IWYU pragma: export
-#endif
-
-#include <cstdint>
-#include "Firestore/Port/absl/absl_port.h"
-
-namespace absl {
-
-// Use compiler byte-swapping intrinsics if they are available. 32-bit
-// and 64-bit versions are available in Clang and GCC as of GCC 4.3.0.
-// The 16-bit version is available in Clang and GCC only as of GCC 4.8.0.
-// For simplicity, we enable them all only for GCC 4.8.0 or later.
-#if defined(__clang__) || \
- (defined(__GNUC__) && ((__GNUC__ == 4 && __GNUC_MINOR__ >= 8) || __GNUC__ >= 5))
-inline uint64_t gbswap_64(uint64_t host_int) {
- return __builtin_bswap64(host_int);
-}
-inline uint32_t gbswap_32(uint32_t host_int) {
- return __builtin_bswap32(host_int);
-}
-inline uint16 gbswap_16(uint16 host_int) {
- return __builtin_bswap16(host_int);
-}
-
-#elif defined(_MSC_VER)
-inline uint64_t gbswap_64(uint64_t host_int) {
- return _byteswap_uint64(host_int);
-}
-inline uint32_t gbswap_32(uint32_t host_int) {
- return _byteswap_ulong(host_int);
-}
-inline uint16 gbswap_16(uint16 host_int) {
- return _byteswap_ushort(host_int);
-}
-
-#elif defined(__APPLE__) && defined(__MACH__)
-inline uint64_t gbswap_64(uint64_t host_int) {
- return OSSwapInt16(host_int);
-}
-inline uint32_t gbswap_32(uint32_t host_int) {
- return OSSwapInt32(host_int);
-}
-inline uint16 gbswap_16(uint16 host_int) {
- return OSSwapInt64(host_int);
-}
-
-#else
-inline uint64_t gbswap_64(uint64_t host_int) {
-#if defined(__GNUC__) && defined(__x86_64__) && !(defined(__APPLE__) && defined(__MACH__))
- // Adapted from /usr/include/byteswap.h. Not available on Mac.
- if (__builtin_constant_p(host_int)) {
- return __bswap_constant_64(host_int);
- } else {
- register uint64_t result;
- __asm__("bswap %0" : "=r"(result) : "0"(host_int));
- return result;
- }
-#elif defined(__GLIBC__)
- return bswap_64(host_int);
-#else
- return (((x & GG_ULONGLONG(0xFF)) << 56) | ((x & GG_ULONGLONG(0xFF00)) << 40) |
- ((x & GG_ULONGLONG(0xFF0000)) << 24) | ((x & GG_ULONGLONG(0xFF000000)) << 8) |
- ((x & GG_ULONGLONG(0xFF00000000)) >> 8) | ((x & GG_ULONGLONG(0xFF0000000000)) >> 24) |
- ((x & GG_ULONGLONG(0xFF000000000000)) >> 40) |
- ((x & GG_ULONGLONG(0xFF00000000000000)) >> 56));
-#endif // bswap_64
-}
-
-inline uint32_t gbswap_32(uint32_t host_int) {
-#if defined(__GLIBC__)
- return bswap_32(host_int);
-#else
- return (((x & 0xFF) << 24) | ((x & 0xFF00) << 8) | ((x & 0xFF0000) >> 8) |
- ((x & 0xFF000000) >> 24));
-#endif
-}
-
-inline uint16 gbswap_16(uint16 host_int) {
-#if defined(__GLIBC__)
- return bswap_16(host_int);
-#else
- return (uint16)(((x & 0xFF) << 8) | ((x & 0xFF00) >> 8)); // NOLINT
-#endif
-}
-
-#endif // intrinics available
-
-#ifdef ABSL_IS_LITTLE_ENDIAN
-
-// Definitions for ntohl etc. that don't require us to include
-// netinet/in.h. We wrap gbswap_32 and gbswap_16 in functions rather
-// than just #defining them because in debug mode, gcc doesn't
-// correctly handle the (rather involved) definitions of bswap_32.
-// gcc guarantees that inline functions are as fast as macros, so
-// this isn't a performance hit.
-inline uint16 ghtons(uint16 x) {
- return gbswap_16(x);
-}
-inline uint32_t ghtonl(uint32_t x) {
- return gbswap_32(x);
-}
-inline uint64_t ghtonll(uint64_t x) {
- return gbswap_64(x);
-}
-
-#elif defined ABSL_IS_BIG_ENDIAN
-
-// These definitions are simpler on big-endian machines
-// These are functions instead of macros to avoid self-assignment warnings
-// on calls such as "i = ghtnol(i);". This also provides type checking.
-inline uint16 ghtons(uint16 x) {
- return x;
-}
-inline uint32_t ghtonl(uint32_t x) {
- return x;
-}
-inline uint64_t ghtonll(uint64_t x) {
- return x;
-}
-
-#else
-#error \
- "Unsupported byte order: Either ABSL_IS_BIG_ENDIAN or " \
- "ABSL_IS_LITTLE_ENDIAN must be defined"
-#endif // byte order
-
-inline uint16 gntohs(uint16 x) {
- return ghtons(x);
-}
-inline uint32_t gntohl(uint32_t x) {
- return ghtonl(x);
-}
-inline uint64_t gntohll(uint64_t x) {
- return ghtonll(x);
-}
-
-// Utilities to convert numbers between the current hosts's native byte
-// order and little-endian byte order
-//
-// Load/Store methods are alignment safe
-namespace little_endian {
-// Conversion functions.
-#ifdef ABSL_IS_LITTLE_ENDIAN
-
-inline uint16 FromHost16(uint16 x) {
- return x;
-}
-inline uint16 ToHost16(uint16 x) {
- return x;
-}
-
-inline uint32_t FromHost32(uint32_t x) {
- return x;
-}
-inline uint32_t ToHost32(uint32_t x) {
- return x;
-}
-
-inline uint64_t FromHost64(uint64_t x) {
- return x;
-}
-inline uint64_t ToHost64(uint64_t x) {
- return x;
-}
-
-inline constexpr bool IsLittleEndian() {
- return true;
-}
-
-#elif defined ABSL_IS_BIG_ENDIAN
-
-inline uint16 FromHost16(uint16 x) {
- return gbswap_16(x);
-}
-inline uint16 ToHost16(uint16 x) {
- return gbswap_16(x);
-}
-
-inline uint32_t FromHost32(uint32_t x) {
- return gbswap_32(x);
-}
-inline uint32_t ToHost32(uint32_t x) {
- return gbswap_32(x);
-}
-
-inline uint64_t FromHost64(uint64_t x) {
- return gbswap_64(x);
-}
-inline uint64_t ToHost64(uint64_t x) {
- return gbswap_64(x);
-}
-
-inline constexpr bool IsLittleEndian() {
- return false;
-}
-
-#endif /* ENDIAN */
-
-// Functions to do unaligned loads and stores in little-endian order.
-inline uint16 Load16(const void *p) {
- return ToHost16(UNALIGNED_LOAD16(p));
-}
-
-inline void Store16(void *p, uint16 v) {
- UNALIGNED_STORE16(p, FromHost16(v));
-}
-
-inline uint32_t Load32(const void *p) {
- return ToHost32(UNALIGNED_LOAD32(p));
-}
-
-inline void Store32(void *p, uint32_t v) {
- UNALIGNED_STORE32(p, FromHost32(v));
-}
-
-inline uint64_t Load64(const void *p) {
- return ToHost64(UNALIGNED_LOAD64(p));
-}
-
-inline void Store64(void *p, uint64_t v) {
- UNALIGNED_STORE64(p, FromHost64(v));
-}
-
-} // namespace little_endian
-
-// Utilities to convert numbers between the current hosts's native byte
-// order and big-endian byte order (same as network byte order)
-//
-// Load/Store methods are alignment safe
-namespace big_endian {
-#ifdef ABSL_IS_LITTLE_ENDIAN
-
-inline uint16 FromHost16(uint16 x) {
- return gbswap_16(x);
-}
-inline uint16 ToHost16(uint16 x) {
- return gbswap_16(x);
-}
-
-inline uint32_t FromHost32(uint32_t x) {
- return gbswap_32(x);
-}
-inline uint32_t ToHost32(uint32_t x) {
- return gbswap_32(x);
-}
-
-inline uint64_t FromHost64(uint64_t x) {
- return gbswap_64(x);
-}
-inline uint64_t ToHost64(uint64_t x) {
- return gbswap_64(x);
-}
-
-inline constexpr bool IsLittleEndian() {
- return true;
-}
-
-#elif defined ABSL_IS_BIG_ENDIAN
-
-inline uint16 FromHost16(uint16 x) {
- return x;
-}
-inline uint16 ToHost16(uint16 x) {
- return x;
-}
-
-inline uint32_t FromHost32(uint32_t x) {
- return x;
-}
-inline uint32_t ToHost32(uint32_t x) {
- return x;
-}
-
-inline uint64_t FromHost64(uint64_t x) {
- return x;
-}
-inline uint64_t ToHost64(uint64_t x) {
- return x;
-}
-
-inline constexpr bool IsLittleEndian() {
- return false;
-}
-
-#endif /* ENDIAN */
-
-// Functions to do unaligned loads and stores in big-endian order.
-inline uint16 Load16(const void *p) {
- return ToHost16(UNALIGNED_LOAD16(p));
-}
-
-inline void Store16(void *p, uint16 v) {
- UNALIGNED_STORE16(p, FromHost16(v));
-}
-
-inline uint32_t Load32(const void *p) {
- return ToHost32(UNALIGNED_LOAD32(p));
-}
-
-inline void Store32(void *p, uint32_t v) {
- UNALIGNED_STORE32(p, FromHost32(v));
-}
-
-inline uint64_t Load64(const void *p) {
- return ToHost64(UNALIGNED_LOAD64(p));
-}
-
-inline void Store64(void *p, uint64_t v) {
- UNALIGNED_STORE64(p, FromHost64(v));
-}
-
-} // namespace big_endian
-
-} // namespace absl
-
-#endif // ABSL_BASE_INTERNAL_ENDIAN_H_
diff --git a/Firestore/Port/absl/absl_integral_types.h b/Firestore/Port/absl/absl_integral_types.h
deleted file mode 100644
index 47da9c1..0000000
--- a/Firestore/Port/absl/absl_integral_types.h
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
- * Copyright 2017 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.
- */
-
-// Basic integer type definitions for various platforms
-//
-// This code is compiled directly on many platforms, including client
-// platforms like Windows, Mac, and embedded systems. Before making
-// any changes here, make sure that you're not breaking any platforms.
-//
-
-#ifndef THIRD_PARTY_ABSL_BASE_INTEGRAL_TYPES_H_
-#define THIRD_PARTY_ABSL_BASE_INTEGRAL_TYPES_H_
-
-// These typedefs are also defined in base/swig/google.swig. In the
-// SWIG environment, we use those definitions and avoid duplicate
-// definitions here with an ifdef. The definitions should be the
-// same in both files, and ideally be only defined in this file.
-#ifndef SWIG
-// Standard typedefs
-// Signed integer types with width of exactly 8, 16, 32, or 64 bits
-// respectively, for use when exact sizes are required.
-typedef signed char schar;
-typedef signed char int8;
-typedef short int16;
-typedef int int32;
-#ifdef COMPILER_MSVC
-typedef __int64 int64;
-#else
-typedef long long int64;
-#endif /* COMPILER_MSVC */
-
-// NOTE: unsigned types are DANGEROUS in loops and other arithmetical
-// places. Use the signed types unless your variable represents a bit
-// pattern (eg a hash value) or you really need the extra bit. Do NOT
-// use 'unsigned' to express "this value should always be positive";
-// use assertions for this.
-
-// Unsigned integer types with width of exactly 8, 16, 32, or 64 bits
-// respectively, for use when exact sizes are required.
-typedef unsigned char uint8;
-typedef unsigned short uint16;
-typedef unsigned int uint32;
-#ifdef COMPILER_MSVC
-typedef unsigned __int64 uint64;
-#else
-typedef unsigned long long uint64;
-#endif /* COMPILER_MSVC */
-
-// A type to represent a Unicode code-point value. As of Unicode 4.0,
-// such values require up to 21 bits.
-// (For type-checking on pointers, make this explicitly signed,
-// and it should always be the signed version of whatever int32 is.)
-typedef signed int char32;
-
-// A type to represent a natural machine word (for e.g. efficiently
-// scanning through memory for checksums or index searching). Don't use
-// this for storing normal integers. Ideally this would be just
-// unsigned int, but our 64-bit architectures use the LP64 model
-// (http://en.wikipedia.org/wiki/64-bit_computing#64-bit_data_models), hence
-// their ints are only 32 bits. We want to use the same fundamental
-// type on all archs if possible to preserve *printf() compatability.
-typedef unsigned long uword_t;
-
-#endif /* SWIG */
-
-// long long macros to be used because gcc and vc++ use different suffixes,
-// and different size specifiers in format strings
-#undef GG_LONGLONG
-#undef GG_ULONGLONG
-#undef GG_LL_FORMAT
-
-#ifdef COMPILER_MSVC /* if Visual C++ */
-
-// VC++ long long suffixes
-#define GG_LONGLONG(x) x##I64
-#define GG_ULONGLONG(x) x##UI64
-
-// Length modifier in printf format std::string for int64's (e.g. within %d)
-#define GG_LL_FORMAT "I64" // As in printf("%I64d", ...)
-#define GG_LL_FORMAT_W L"I64"
-
-#else /* not Visual C++ */
-
-#define GG_LONGLONG(x) x##LL
-#define GG_ULONGLONG(x) x##ULL
-#define GG_LL_FORMAT "ll" // As in "%lld". Note that "q" is poor form also.
-#define GG_LL_FORMAT_W L"ll"
-
-#endif // COMPILER_MSVC
-
-// There are still some requirements that we build these headers in
-// C-compatibility mode. Unfortunately, -Wall doesn't like c-style
-// casts, and C doesn't know how to read braced-initialization for
-// integers.
-#if defined(__cplusplus)
-const uint8 kuint8max{0xFF};
-const uint16 kuint16max{0xFFFF};
-const uint32 kuint32max{0xFFFFFFFF};
-const uint64 kuint64max{GG_ULONGLONG(0xFFFFFFFFFFFFFFFF)};
-const int8 kint8min{~0x7F};
-const int8 kint8max{0x7F};
-const int16 kint16min{~0x7FFF};
-const int16 kint16max{0x7FFF};
-const int32 kint32min{~0x7FFFFFFF};
-const int32 kint32max{0x7FFFFFFF};
-const int64 kint64min{GG_LONGLONG(~0x7FFFFFFFFFFFFFFF)};
-const int64 kint64max{GG_LONGLONG(0x7FFFFFFFFFFFFFFF)};
-#else // not __cplusplus, this branch exists only for C-compat
-static const uint8 kuint8max = ((uint8)0xFF);
-static const uint16 kuint16max = ((uint16)0xFFFF);
-static const uint32 kuint32max = ((uint32)0xFFFFFFFF);
-static const uint64 kuint64max = ((uint64)GG_LONGLONG(0xFFFFFFFFFFFFFFFF));
-static const int8 kint8min = ((int8)~0x7F);
-static const int8 kint8max = ((int8)0x7F);
-static const int16 kint16min = ((int16)~0x7FFF);
-static const int16 kint16max = ((int16)0x7FFF);
-static const int32 kint32min = ((int32)~0x7FFFFFFF);
-static const int32 kint32max = ((int32)0x7FFFFFFF);
-static const int64 kint64min = ((int64)GG_LONGLONG(~0x7FFFFFFFFFFFFFFF));
-static const int64 kint64max = ((int64)GG_LONGLONG(0x7FFFFFFFFFFFFFFF));
-#endif // __cplusplus
-
-// The following are not real constants, but we list them so CodeSearch and
-// other tools find them, in case people are looking for the above constants
-// under different names:
-// kMaxUint8, kMaxUint16, kMaxUint32, kMaxUint64
-// kMinInt8, kMaxInt8, kMinInt16, kMaxInt16, kMinInt32, kMaxInt32,
-// kMinInt64, kMaxInt64
-
-// No object has kIllegalFprint as its Fingerprint.
-typedef uint64 Fprint;
-static const Fprint kIllegalFprint = 0;
-static const Fprint kMaxFprint = GG_ULONGLONG(0xFFFFFFFFFFFFFFFF);
-
-#endif // THIRD_PARTY_ABSL_BASE_INTEGRAL_TYPES_H_
diff --git a/Firestore/Port/absl/absl_port.h b/Firestore/Port/absl/absl_port.h
deleted file mode 100644
index eee21fc..0000000
--- a/Firestore/Port/absl/absl_port.h
+++ /dev/null
@@ -1,535 +0,0 @@
-/*
- * Copyright 2017 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.
- */
-
-// Various portability macros, type definitions, and inline functions
-// This file is used for both C and C++!
-//
-// These are weird things we need to do to get this compiling on
-// random systems (and on SWIG).
-//
-// This files is structured into the following high-level categories:
-// - Platform checks (OS, Compiler, C++, Library)
-// - Feature macros
-// - Utility macros
-// - Utility functions
-// - Type alias
-// - Predefined system/language macros
-// - Predefined system/language functions
-// - Compiler attributes (__attribute__)
-// - Performance optimization (alignment, branch prediction)
-// - Obsolete
-//
-
-#ifndef THIRD_PARTY_ABSL_BASE_PORT_H_
-#define THIRD_PARTY_ABSL_BASE_PORT_H_
-
-#include <assert.h>
-#include <limits.h> // So we can set the bounds of our types
-#include <stdlib.h> // for free()
-#include <string.h> // for memcpy()
-
-#include "Firestore/Port/absl/absl_attributes.h"
-#include "Firestore/Port/absl/absl_config.h"
-#include "Firestore/Port/absl/absl_integral_types.h"
-
-#ifdef SWIG
-%include "attributes.h"
-#endif
-
-// -----------------------------------------------------------------------------
-// Operating System Check
-// -----------------------------------------------------------------------------
-
-#if defined(__CYGWIN__)
-#error "Cygwin is not supported."
-#endif
-
-// -----------------------------------------------------------------------------
-// Compiler Check
-// -----------------------------------------------------------------------------
-
-// We support MSVC++ 14.0 update 2 and later.
-#if defined(_MSC_FULL_VER) && _MSC_FULL_VER < 190023918
-#error "This package requires Visual Studio 2015 Update 2 or higher"
-#endif
-
-// We support gcc 4.7 and later.
-#if defined(__GNUC__) && !defined(__clang__)
-#if __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 7)
-#error "This package requires gcc 4.7 or higher"
-#endif
-#endif
-
-// We support Apple Xcode clang 4.2.1 (version 421.11.65) and later.
-// This corresponds to Apple Xcode version 4.5.
-#if defined(__apple_build_version__) && __apple_build_version__ < 4211165
-#error "This package requires __apple_build_version__ of 4211165 or higher"
-#endif
-
-// -----------------------------------------------------------------------------
-// C++ Version Check
-// -----------------------------------------------------------------------------
-
-// Enforce C++11 as the minimum. Note that Visual Studio has not
-// advanced __cplusplus despite being good enough for our purposes, so
-// so we exempt it from the check.
-#if defined(__cplusplus) && !defined(_MSC_VER) && !defined(SWIG)
-#if __cplusplus < 201103L
-#error "C++ versions less than C++11 are not supported."
-#endif
-#endif
-
-// -----------------------------------------------------------------------------
-// C++ Standard Library Check
-// -----------------------------------------------------------------------------
-
-#if defined(__cplusplus)
-#include <cstddef>
-#if defined(_STLPORT_VERSION)
-#error "STLPort is not supported."
-#endif
-#endif
-
-// -----------------------------------------------------------------------------
-// Feature Macros
-// -----------------------------------------------------------------------------
-
-// ABSL_HAVE_TLS is defined to 1 when __thread should be supported.
-// We assume __thread is supported on Linux when compiled with Clang or compiled
-// against libstdc++ with _GLIBCXX_HAVE_TLS defined.
-#ifdef ABSL_HAVE_TLS
-#error ABSL_HAVE_TLS cannot be directly set
-#elif defined(__linux__) && (defined(__clang__) || defined(_GLIBCXX_HAVE_TLS))
-#define ABSL_HAVE_TLS 1
-#endif
-
-// -----------------------------------------------------------------------------
-// Utility Macros
-// -----------------------------------------------------------------------------
-
-// ABSL_FUNC_PTR_TO_CHAR_PTR
-// On some platforms, a "function pointer" points to a function descriptor
-// rather than directly to the function itself.
-// Use ABSL_FUNC_PTR_TO_CHAR_PTR(func) to get a char-pointer to the first
-// instruction of the function func.
-#if defined(__cplusplus)
-#if (defined(__powerpc__) && !(_CALL_ELF > 1)) || defined(__ia64)
-// use opd section for function descriptors on these platforms, the function
-// address is the first word of the descriptor
-namespace absl {
-enum { kPlatformUsesOPDSections = 1 };
-} // namespace absl
-#define ABSL_FUNC_PTR_TO_CHAR_PTR(func) (reinterpret_cast<char **>(func)[0])
-#else // not PPC or IA64
-namespace absl {
-enum { kPlatformUsesOPDSections = 0 };
-} // namespace absl
-#define ABSL_FUNC_PTR_TO_CHAR_PTR(func) (reinterpret_cast<char *>(func))
-#endif // PPC or IA64
-#endif // __cplusplus
-
-// -----------------------------------------------------------------------------
-// Utility Functions
-// -----------------------------------------------------------------------------
-
-#if defined(__cplusplus)
-namespace absl {
-constexpr char PathSeparator() {
-#ifdef _WIN32
- return '\\';
-#else
- return '/';
-#endif
-}
-} // namespace absl
-#endif // __cplusplus
-
-// -----------------------------------------------------------------------------
-// Type Alias
-// -----------------------------------------------------------------------------
-
-#ifdef _MSC_VER
-// uid_t
-// MSVC doesn't have uid_t
-typedef int uid_t;
-
-// pid_t
-// Defined all over the place.
-typedef int pid_t;
-
-// ssize_t
-// VC++ doesn't understand "ssize_t". SSIZE_T is defined in <basetsd.h>.
-#include <basetsd.h>
-typedef SSIZE_T ssize_t;
-#endif // _MSC_VER
-
-// -----------------------------------------------------------------------------
-// Predefined System/Language Macros
-// -----------------------------------------------------------------------------
-
-// MAP_ANONYMOUS
-#if defined(__APPLE__) && defined(__MACH__)
-// For mmap, Linux defines both MAP_ANONYMOUS and MAP_ANON and says MAP_ANON is
-// deprecated. In Darwin, MAP_ANON is all there is.
-#if !defined MAP_ANONYMOUS
-#define MAP_ANONYMOUS MAP_ANON
-#endif // !MAP_ANONYMOUS
-#endif // __APPLE__ && __MACH__
-
-// PATH_MAX
-// You say tomato, I say atotom
-#ifdef _MSC_VER
-#define PATH_MAX MAX_PATH
-#endif
-
-// -----------------------------------------------------------------------------
-// Performance Optimization
-// -----------------------------------------------------------------------------
-
-// Alignment
-
-// CACHELINE_SIZE, CACHELINE_ALIGNED
-// Deprecated: Use ABSL_CACHELINE_SIZE, ABSL_CACHELINE_ALIGNED.
-// Note: When C++17 is available, consider using the following:
-// - std::hardware_constructive_interference_size
-// - std::hardware_destructive_interference_size
-// See http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0154r1.html
-#if defined(__GNUC__)
-#if defined(__i386__) || defined(__x86_64__)
-#define CACHELINE_SIZE 64
-#define ABSL_CACHELINE_SIZE 64
-#elif defined(__powerpc64__)
-#define CACHELINE_SIZE 128
-#define ABSL_CACHELINE_SIZE 128
-#elif defined(__aarch64__)
-// We would need to read special register ctr_el0 to find out L1 dcache size.
-// This value is a good estimate based on a real aarch64 machine.
-#define CACHELINE_SIZE 64
-#define ABSL_CACHELINE_SIZE 64
-#elif defined(__arm__)
-// Cache line sizes for ARM: These values are not strictly correct since
-// cache line sizes depend on implementations, not architectures. There
-// are even implementations with cache line sizes configurable at boot
-// time.
-#if defined(__ARM_ARCH_5T__)
-#define CACHELINE_SIZE 32
-#define ABSL_CACHELINE_SIZE 32
-#elif defined(__ARM_ARCH_7A__)
-#define CACHELINE_SIZE 64
-#define ABSL_CACHELINE_SIZE 64
-#endif
-#endif
-
-#ifndef CACHELINE_SIZE
-// A reasonable default guess. Note that overestimates tend to waste more
-// space, while underestimates tend to waste more time.
-#define CACHELINE_SIZE 64
-#define ABSL_CACHELINE_SIZE 64
-#endif
-
-// On some compilers, expands to __attribute__((aligned(CACHELINE_SIZE))).
-// For compilers where this is not known to work, expands to nothing.
-//
-// No further guarantees are made here. The result of applying the macro
-// to variables and types is always implementation defined.
-//
-// WARNING: It is easy to use this attribute incorrectly, even to the point
-// of causing bugs that are difficult to diagnose, crash, etc. It does not
-// guarantee that objects are aligned to a cache line.
-//
-// Recommendations:
-//
-// 1) Consult compiler documentation; this comment is not kept in sync as
-// toolchains evolve.
-// 2) Verify your use has the intended effect. This often requires inspecting
-// the generated machine code.
-// 3) Prefer applying this attribute to individual variables. Avoid
-// applying it to types. This tends to localize the effect.
-#define CACHELINE_ALIGNED __attribute__((aligned(CACHELINE_SIZE)))
-#define ABSL_CACHELINE_ALIGNED __attribute__((aligned(ABSL_CACHELINE_SIZE)))
-
-#else // not GCC
-#define CACHELINE_SIZE 64
-#define ABSL_CACHELINE_SIZE 64
-#define CACHELINE_ALIGNED
-#define ABSL_CACHELINE_ALIGNED
-#endif
-
-// unaligned APIs
-
-// Portable handling of unaligned loads, stores, and copies.
-// On some platforms, like ARM, the copy functions can be more efficient
-// then a load and a store.
-//
-// It is possible to implement all of these these using constant-length memcpy
-// calls, which is portable and will usually be inlined into simple loads and
-// stores if the architecture supports it. However, such inlining usually
-// happens in a pass that's quite late in compilation, which means the resulting
-// loads and stores cannot participate in many other optimizations, leading to
-// overall worse code.
-
-// The unaligned API is C++ only. The declarations use C++ features
-// (namespaces, inline) which are absent or incompatible in C.
-#if defined(__cplusplus)
-
-#if defined(ADDRESS_SANITIZER) || defined(THREAD_SANITIZER) || defined(MEMORY_SANITIZER)
-// Consider we have an unaligned load/store of 4 bytes from address 0x...05.
-// AddressSanitizer will treat it as a 3-byte access to the range 05:07 and
-// will miss a bug if 08 is the first unaddressable byte.
-// ThreadSanitizer will also treat this as a 3-byte access to 05:07 and will
-// miss a race between this access and some other accesses to 08.
-// MemorySanitizer will correctly propagate the shadow on unaligned stores
-// and correctly report bugs on unaligned loads, but it may not properly
-// update and report the origin of the uninitialized memory.
-// For all three tools, replacing an unaligned access with a tool-specific
-// callback solves the problem.
-
-// Make sure uint16_t/uint32_t/uint64_t are defined.
-#include <stdint.h>
-
-extern "C" {
-uint16_t __sanitizer_unaligned_load16(const void *p);
-uint32_t __sanitizer_unaligned_load32(const void *p);
-uint64_t __sanitizer_unaligned_load64(const void *p);
-void __sanitizer_unaligned_store16(void *p, uint16_t v);
-void __sanitizer_unaligned_store32(void *p, uint32_t v);
-void __sanitizer_unaligned_store64(void *p, uint64_t v);
-} // extern "C"
-
-inline uint16 UNALIGNED_LOAD16(const void *p) {
- return __sanitizer_unaligned_load16(p);
-}
-
-inline uint32 UNALIGNED_LOAD32(const void *p) {
- return __sanitizer_unaligned_load32(p);
-}
-
-inline uint64 UNALIGNED_LOAD64(const void *p) {
- return __sanitizer_unaligned_load64(p);
-}
-
-inline void UNALIGNED_STORE16(void *p, uint16 v) {
- __sanitizer_unaligned_store16(p, v);
-}
-
-inline void UNALIGNED_STORE32(void *p, uint32 v) {
- __sanitizer_unaligned_store32(p, v);
-}
-
-inline void UNALIGNED_STORE64(void *p, uint64 v) {
- __sanitizer_unaligned_store64(p, v);
-}
-
-#elif defined(__x86_64__) || defined(_M_X64) || defined(__i386) || defined(_M_IX86) || \
- defined(__ppc__) || defined(__PPC__) || defined(__ppc64__) || defined(__PPC64__)
-
-// x86 and x86-64 can perform unaligned loads/stores directly;
-// modern PowerPC hardware can also do unaligned integer loads and stores;
-// but note: the FPU still sends unaligned loads and stores to a trap handler!
-
-#define UNALIGNED_LOAD16(_p) (*reinterpret_cast<const uint16 *>(_p))
-#define UNALIGNED_LOAD32(_p) (*reinterpret_cast<const uint32 *>(_p))
-#define UNALIGNED_LOAD64(_p) (*reinterpret_cast<const uint64 *>(_p))
-
-#define UNALIGNED_STORE16(_p, _val) (*reinterpret_cast<uint16 *>(_p) = (_val))
-#define UNALIGNED_STORE32(_p, _val) (*reinterpret_cast<uint32 *>(_p) = (_val))
-#define UNALIGNED_STORE64(_p, _val) (*reinterpret_cast<uint64 *>(_p) = (_val))
-
-#elif defined(__arm__) && !defined(__ARM_ARCH_5__) && !defined(__ARM_ARCH_5T__) && \
- !defined(__ARM_ARCH_5TE__) && !defined(__ARM_ARCH_5TEJ__) && !defined(__ARM_ARCH_6__) && \
- !defined(__ARM_ARCH_6J__) && !defined(__ARM_ARCH_6K__) && !defined(__ARM_ARCH_6Z__) && \
- !defined(__ARM_ARCH_6ZK__) && !defined(__ARM_ARCH_6T2__)
-
-// ARMv7 and newer support native unaligned accesses, but only of 16-bit
-// and 32-bit values (not 64-bit); older versions either raise a fatal signal,
-// do an unaligned read and rotate the words around a bit, or do the reads very
-// slowly (trip through kernel mode). There's no simple #define that says just
-// “ARMv7 or higher”, so we have to filter away all ARMv5 and ARMv6
-// sub-architectures. Newer gcc (>= 4.6) set an __ARM_FEATURE_ALIGNED #define,
-// so in time, maybe we can move on to that.
-//
-// This is a mess, but there's not much we can do about it.
-//
-// To further complicate matters, only LDR instructions (single reads) are
-// allowed to be unaligned, not LDRD (two reads) or LDM (many reads). Unless we
-// explicitly tell the compiler that these accesses can be unaligned, it can and
-// will combine accesses. On armcc, the way to signal this is done by accessing
-// through the type (uint32 __packed *), but GCC has no such attribute
-// (it ignores __attribute__((packed)) on individual variables). However,
-// we can tell it that a _struct_ is unaligned, which has the same effect,
-// so we do that.
-
-namespace base {
-namespace internal {
-
-struct Unaligned16Struct {
- uint16 value;
- uint8 dummy; // To make the size non-power-of-two.
-} ATTRIBUTE_PACKED;
-
-struct Unaligned32Struct {
- uint32 value;
- uint8 dummy; // To make the size non-power-of-two.
-} ATTRIBUTE_PACKED;
-
-} // namespace internal
-} // namespace base
-
-#define UNALIGNED_LOAD16(_p) \
- ((reinterpret_cast<const ::base::internal::Unaligned16Struct *>(_p))->value)
-#define UNALIGNED_LOAD32(_p) \
- ((reinterpret_cast<const ::base::internal::Unaligned32Struct *>(_p))->value)
-
-#define UNALIGNED_STORE16(_p, _val) \
- ((reinterpret_cast< ::base::internal::Unaligned16Struct *>(_p))->value = (_val))
-#define UNALIGNED_STORE32(_p, _val) \
- ((reinterpret_cast< ::base::internal::Unaligned32Struct *>(_p))->value = (_val))
-
-inline uint64 UNALIGNED_LOAD64(const void *p) {
- uint64 t;
- memcpy(&t, p, sizeof t);
- return t;
-}
-
-inline void UNALIGNED_STORE64(void *p, uint64 v) {
- memcpy(p, &v, sizeof v);
-}
-
-#else
-
-#define NEED_ALIGNED_LOADS
-
-// These functions are provided for architectures that don't support
-// unaligned loads and stores.
-
-inline uint16 UNALIGNED_LOAD16(const void *p) {
- uint16 t;
- memcpy(&t, p, sizeof t);
- return t;
-}
-
-inline uint32 UNALIGNED_LOAD32(const void *p) {
- uint32 t;
- memcpy(&t, p, sizeof t);
- return t;
-}
-
-inline uint64 UNALIGNED_LOAD64(const void *p) {
- uint64 t;
- memcpy(&t, p, sizeof t);
- return t;
-}
-
-inline void UNALIGNED_STORE16(void *p, uint16 v) {
- memcpy(p, &v, sizeof v);
-}
-
-inline void UNALIGNED_STORE32(void *p, uint32 v) {
- memcpy(p, &v, sizeof v);
-}
-
-inline void UNALIGNED_STORE64(void *p, uint64 v) {
- memcpy(p, &v, sizeof v);
-}
-
-#endif
-
-// The UNALIGNED_LOADW and UNALIGNED_STOREW macros load and store values
-// of type uword_t.
-#ifdef _LP64
-#define UNALIGNED_LOADW(_p) UNALIGNED_LOAD64(_p)
-#define UNALIGNED_STOREW(_p, _val) UNALIGNED_STORE64(_p, _val)
-#else
-#define UNALIGNED_LOADW(_p) UNALIGNED_LOAD32(_p)
-#define UNALIGNED_STOREW(_p, _val) UNALIGNED_STORE32(_p, _val)
-#endif
-
-inline void UnalignedCopy16(const void *src, void *dst) {
- UNALIGNED_STORE16(dst, UNALIGNED_LOAD16(src));
-}
-
-inline void UnalignedCopy32(const void *src, void *dst) {
- UNALIGNED_STORE32(dst, UNALIGNED_LOAD32(src));
-}
-
-inline void UnalignedCopy64(const void *src, void *dst) {
- if (sizeof(void *) == 8) {
- UNALIGNED_STORE64(dst, UNALIGNED_LOAD64(src));
- } else {
- const char *src_char = reinterpret_cast<const char *>(src);
- char *dst_char = reinterpret_cast<char *>(dst);
-
- UNALIGNED_STORE32(dst_char, UNALIGNED_LOAD32(src_char));
- UNALIGNED_STORE32(dst_char + 4, UNALIGNED_LOAD32(src_char + 4));
- }
-}
-
-#endif // defined(__cplusplus), end of unaligned API
-
-// PREDICT_TRUE, PREDICT_FALSE
-//
-// GCC can be told that a certain branch is not likely to be taken (for
-// instance, a CHECK failure), and use that information in static analysis.
-// Giving it this information can help it optimize for the common case in
-// the absence of better information (ie. -fprofile-arcs).
-#if ABSL_HAVE_BUILTIN(__builtin_expect) || (defined(__GNUC__) && !defined(__clang__))
-#define PREDICT_FALSE(x) (__builtin_expect(x, 0))
-#define PREDICT_TRUE(x) (__builtin_expect(!!(x), 1))
-#define ABSL_PREDICT_FALSE(x) (__builtin_expect(x, 0))
-#define ABSL_PREDICT_TRUE(x) (__builtin_expect(!!(x), 1))
-#else
-#define PREDICT_FALSE(x) x
-#define PREDICT_TRUE(x) x
-#define ABSL_PREDICT_FALSE(x) x
-#define ABSL_PREDICT_TRUE(x) x
-#endif
-
-// ABSL_ASSERT
-//
-// In C++11, `assert` can't be used portably within constexpr functions.
-// ABSL_ASSERT functions as a runtime assert but works in C++11 constexpr
-// functions. Example:
-//
-// constexpr double Divide(double a, double b) {
-// return ABSL_ASSERT(b != 0), a / b;
-// }
-//
-// This macro is inspired by
-// https://akrzemi1.wordpress.com/2017/05/18/asserts-in-constexpr-functions/
-#if defined(NDEBUG)
-#define ABSL_ASSERT(expr) (false ? (void)(expr) : (void)0)
-#else
-#define ABSL_ASSERT(expr) \
- (PREDICT_TRUE((expr)) ? (void)0 : [] { assert(false && #expr); }()) // NOLINT
-#endif
-
-// -----------------------------------------------------------------------------
-// Obsolete (to be removed)
-// -----------------------------------------------------------------------------
-
-// HAS_GLOBAL_STRING
-// Some platforms have a std::string class that is different from ::std::string
-// (although the interface is the same, of course). On other platforms,
-// std::string is the same as ::std::string.
-#if defined(__cplusplus) && !defined(SWIG)
-#include <string>
-#ifndef HAS_GLOBAL_STRING
-using std::basic_string;
-using std::string;
-#endif // HAS_GLOBAL_STRING
-#endif // SWIG, __cplusplus
-
-#endif // THIRD_PARTY_ABSL_BASE_PORT_H_
diff --git a/Firestore/Port/string_util.h b/Firestore/Port/string_util.h
deleted file mode 100644
index 6e85ba9..0000000
--- a/Firestore/Port/string_util.h
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright 2017 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.
- */
-
-// Useful string functions and so forth. This is a grab-bag file.
-//
-// These functions work fine for UTF-8 strings as long as you can
-// consider them to be just byte strings. For example, due to the
-// design of UTF-8 you do not need to worry about accidental matches,
-// as long as all your inputs are valid UTF-8 (use \uHHHH, not \xHH or \oOOO).
-
-#ifndef IPHONE_FIRESTORE_PORT_STRING_UTIL_H_
-#define IPHONE_FIRESTORE_PORT_STRING_UTIL_H_
-
-#include <string>
-
-namespace leveldb {
-class Slice;
-}
-
-namespace Firestore {
-
-// Returns the smallest lexicographically larger string of equal or smaller
-// length. Returns an empty string if there is no such successor (if the input
-// is empty or consists entirely of 0xff bytes).
-// Useful for calculating the smallest lexicographically larger string
-// that will not be prefixed by the input string.
-//
-// Examples:
-// "a" -> "b", "aaa" -> "aab", "aa\xff" -> "ab", "\xff" -> "", "" -> ""
-std::string PrefixSuccessor(leveldb::Slice prefix);
-
-// Returns the immediate lexicographically-following string. This is useful to
-// turn an inclusive range into something that can be used with Bigtable's
-// SetLimitRow():
-//
-// // Inclusive range [min_element, max_element].
-// string min_element = ...;
-// string max_element = ...;
-//
-// // Equivalent range [range_start, range_end).
-// string range_start = min_element;
-// string range_end = ImmediateSuccessor(max_element);
-//
-// WARNING: Returns the input string with a '\0' appended; if you call c_str()
-// on the result, it will compare equal to s.
-//
-// WARNING: Transforms "" -> "\0"; this doesn't account for Bigtable's special
-// treatment of "" as infinity.
-std::string ImmediateSuccessor(leveldb::Slice s);
-
-} // namespace Firestore
-
-#endif // IPHONE_FIRESTORE_PORT_STRING_UTIL_H_
diff --git a/Firestore/Protos/CMakeLists.txt b/Firestore/Protos/CMakeLists.txt
new file mode 100644
index 0000000..de33491
--- /dev/null
+++ b/Firestore/Protos/CMakeLists.txt
@@ -0,0 +1,52 @@
+cc_library(
+ firebase_firestore_protos_nanopb
+ SOURCES
+ nanopb/firestore/local/maybe_document.pb.c
+ nanopb/firestore/local/maybe_document.pb.h
+ nanopb/firestore/local/mutation.pb.c
+ nanopb/firestore/local/mutation.pb.h
+ nanopb/firestore/local/target.pb.c
+ nanopb/firestore/local/target.pb.h
+ nanopb/google/api/annotations.pb.c
+ nanopb/google/api/annotations.pb.h
+ nanopb/google/api/http.pb.c
+ nanopb/google/api/http.pb.h
+ nanopb/google/firestore/v1beta1/common.pb.c
+ nanopb/google/firestore/v1beta1/common.pb.h
+ nanopb/google/firestore/v1beta1/document.pb.c
+ nanopb/google/firestore/v1beta1/document.pb.h
+ nanopb/google/firestore/v1beta1/firestore.pb.c
+ nanopb/google/firestore/v1beta1/firestore.pb.h
+ nanopb/google/firestore/v1beta1/query.pb.c
+ nanopb/google/firestore/v1beta1/query.pb.h
+ nanopb/google/firestore/v1beta1/write.pb.c
+ nanopb/google/firestore/v1beta1/write.pb.h
+ nanopb/google/protobuf/any.pb.c
+ nanopb/google/protobuf/any.pb.h
+ nanopb/google/protobuf/empty.pb.c
+ nanopb/google/protobuf/empty.pb.h
+ nanopb/google/protobuf/struct.pb.c
+ nanopb/google/protobuf/struct.pb.h
+ nanopb/google/protobuf/timestamp.pb.c
+ nanopb/google/protobuf/timestamp.pb.h
+ nanopb/google/protobuf/wrappers.pb.c
+ nanopb/google/protobuf/wrappers.pb.h
+ nanopb/google/rpc/status.pb.c
+ nanopb/google/rpc/status.pb.h
+ nanopb/google/type/latlng.pb.c
+ nanopb/google/type/latlng.pb.h
+ DEPENDS
+ nanopb
+)
+
+target_compile_definitions(
+ firebase_firestore_protos_nanopb PUBLIC
+ -DPB_FIELD_16BIT
+)
+
+# TODO(rsgowman): this may be worth moving into cc_library, possibly via an
+# INCLUDE_DIRS or similar.
+target_include_directories(
+ firebase_firestore_protos_nanopb PUBLIC
+ ${FIREBASE_SOURCE_DIR}/Firestore/Protos/nanopb
+)
diff --git a/Firestore/Protos/FrameworkMaker.xcodeproj/project.pbxproj b/Firestore/Protos/FrameworkMaker.xcodeproj/project.pbxproj
index 51a61b8..4a1e9d4 100644
--- a/Firestore/Protos/FrameworkMaker.xcodeproj/project.pbxproj
+++ b/Firestore/Protos/FrameworkMaker.xcodeproj/project.pbxproj
@@ -201,11 +201,11 @@
);
inputPaths = (
"${SRCROOT}/Pods/Target Support Files/Pods-FrameworkMaker_iOS/Pods-FrameworkMaker_iOS-resources.sh",
- "$PODS_CONFIGURATION_BUILD_DIR/gRPC/gRPCCertificates.bundle",
+ "${PODS_CONFIGURATION_BUILD_DIR}/gRPC/gRPCCertificates.bundle",
);
name = "[CP] Copy Pods Resources";
outputPaths = (
- "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}",
+ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/gRPCCertificates.bundle",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
diff --git a/Firestore/Protos/README.md b/Firestore/Protos/README.md
index cb6d90e..fbede5b 100644
--- a/Firestore/Protos/README.md
+++ b/Firestore/Protos/README.md
@@ -1,11 +1,27 @@
## Usage
+First, make sure you have necessary prereqs for building:
+```
+brew install automake libtool protobuf
+```
+
+Take a nap while that completes. Then, build protobuf and nanopb:
+```
+cd firebase-ios-sdk
+mkdir -p build
+cd build
+cmake ..
+make -j protobuf nanopb
+```
+
+Next, build the protos:
```
cd firebase-ios-sdk/Firestore/Protos
./build-protos.sh
```
-Verify diffs, tests and make PR
+Verify diffs (you'll likely need to re-add copyright notices, etc.), make sure
+tests still pass, and create a PR.
### Script Details
diff --git a/Firestore/Protos/build-protos.sh b/Firestore/Protos/build-protos.sh
index 4cfb12e..a535f16 100755
--- a/Firestore/Protos/build-protos.sh
+++ b/Firestore/Protos/build-protos.sh
@@ -20,7 +20,21 @@ rm Podfile.lock
pod update
# Generate the objective C files from the protos.
-./Pods/!ProtoCompiler/protoc --plugin=protoc-gen-grpc=Pods/\!ProtoCompiler-gRPCPlugin/grpc_objective_c_plugin -I protos --objc_out=objc --grpc_out=objc `find protos -name *.proto -print | xargs`
+./Pods/!ProtoCompiler/protoc \
+ --plugin=protoc-gen-grpc=Pods/\!ProtoCompiler-gRPCPlugin/grpc_objective_c_plugin \
+ --plugin=../../build/external/nanopb/src/nanopb/generator/protoc-gen-nanopb \
+ -I protos --objc_out=objc --grpc_out=objc \
+ --nanopb_out="--options-file=protos/%s.options:nanopb" \
+ `find protos -name *.proto -print | xargs`
+
+# Remove "well-known" protos from objc. (We get these for free. We only need
+# them for nanopb.)
+rm -rf objc/google/protobuf/
+
+# If a proto uses a field named 'delete', nanopb happily uses that in the
+# message definition. Works fine for C; not so much for C++. Rename uses of this
+# to delete_ (which is how protoc does it for c++ files.)
+perl -i -pe 's/\bdelete\b/delete_/g' `find nanopb -type f`
# CocoaPods does not like paths in library imports, flatten them.
diff --git a/Firestore/Protos/nanopb/firestore/local/maybe_document.pb.c b/Firestore/Protos/nanopb/firestore/local/maybe_document.pb.c
new file mode 100644
index 0000000..7cd4035
--- /dev/null
+++ b/Firestore/Protos/nanopb/firestore/local/maybe_document.pb.c
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2018 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.
+ */
+
+/* Automatically generated nanopb constant definitions */
+/* Generated by nanopb-0.3.8 at Fri Feb 2 17:48:02 2018. */
+
+#include "maybe_document.pb.h"
+
+/* @@protoc_insertion_point(includes) */
+#if PB_PROTO_HEADER_VERSION != 30
+#error Regenerate this file with the current version of nanopb generator.
+#endif
+
+
+
+const pb_field_t firestore_client_NoDocument_fields[3] = {
+ PB_FIELD( 1, STRING , SINGULAR, CALLBACK, FIRST, firestore_client_NoDocument, name, name, 0),
+ PB_FIELD( 2, MESSAGE , SINGULAR, STATIC , OTHER, firestore_client_NoDocument, read_time, name, &google_protobuf_Timestamp_fields),
+ PB_LAST_FIELD
+};
+
+const pb_field_t firestore_client_MaybeDocument_fields[3] = {
+ PB_ONEOF_FIELD(document_type, 1, MESSAGE , ONEOF, STATIC , FIRST, firestore_client_MaybeDocument, no_document, no_document, &firestore_client_NoDocument_fields),
+ PB_ONEOF_FIELD(document_type, 2, MESSAGE , ONEOF, STATIC , UNION, firestore_client_MaybeDocument, document, document, &google_firestore_v1beta1_Document_fields),
+ PB_LAST_FIELD
+};
+
+
+/* Check that field information fits in pb_field_t */
+#if !defined(PB_FIELD_32BIT)
+/* If you get an error here, it means that you need to define PB_FIELD_32BIT
+ * compile-time option. You can do that in pb.h or on compiler command line.
+ *
+ * The reason you need to do this is that some of your messages contain tag
+ * numbers or field sizes that are larger than what can fit in 8 or 16 bit
+ * field descriptors.
+ */
+PB_STATIC_ASSERT((pb_membersize(firestore_client_NoDocument, read_time) < 65536 && pb_membersize(firestore_client_MaybeDocument, document_type.no_document) < 65536 && pb_membersize(firestore_client_MaybeDocument, document_type.document) < 65536), YOU_MUST_DEFINE_PB_FIELD_32BIT_FOR_MESSAGES_firestore_client_NoDocument_firestore_client_MaybeDocument)
+#endif
+
+#if !defined(PB_FIELD_16BIT) && !defined(PB_FIELD_32BIT)
+/* If you get an error here, it means that you need to define PB_FIELD_16BIT
+ * compile-time option. You can do that in pb.h or on compiler command line.
+ *
+ * The reason you need to do this is that some of your messages contain tag
+ * numbers or field sizes that are larger than what can fit in the default
+ * 8 bit descriptors.
+ */
+PB_STATIC_ASSERT((pb_membersize(firestore_client_NoDocument, read_time) < 256 && pb_membersize(firestore_client_MaybeDocument, document_type.no_document) < 256 && pb_membersize(firestore_client_MaybeDocument, document_type.document) < 256), YOU_MUST_DEFINE_PB_FIELD_16BIT_FOR_MESSAGES_firestore_client_NoDocument_firestore_client_MaybeDocument)
+#endif
+
+
+/* @@protoc_insertion_point(eof) */
diff --git a/Firestore/Protos/nanopb/firestore/local/maybe_document.pb.h b/Firestore/Protos/nanopb/firestore/local/maybe_document.pb.h
new file mode 100644
index 0000000..b159cd1
--- /dev/null
+++ b/Firestore/Protos/nanopb/firestore/local/maybe_document.pb.h
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2018 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.
+ */
+
+/* Automatically generated nanopb header */
+/* Generated by nanopb-0.3.8 at Fri Feb 2 17:48:02 2018. */
+
+#ifndef PB_FIRESTORE_CLIENT_MAYBE_DOCUMENT_PB_H_INCLUDED
+#define PB_FIRESTORE_CLIENT_MAYBE_DOCUMENT_PB_H_INCLUDED
+#include <pb.h>
+
+#include "google/firestore/v1beta1/document.pb.h"
+
+#include "google/protobuf/timestamp.pb.h"
+
+/* @@protoc_insertion_point(includes) */
+#if PB_PROTO_HEADER_VERSION != 30
+#error Regenerate this file with the current version of nanopb generator.
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Struct definitions */
+typedef struct _firestore_client_NoDocument {
+ pb_callback_t name;
+ google_protobuf_Timestamp read_time;
+/* @@protoc_insertion_point(struct:firestore_client_NoDocument) */
+} firestore_client_NoDocument;
+
+typedef struct _firestore_client_MaybeDocument {
+ pb_size_t which_document_type;
+ union {
+ firestore_client_NoDocument no_document;
+ google_firestore_v1beta1_Document document;
+ } document_type;
+/* @@protoc_insertion_point(struct:firestore_client_MaybeDocument) */
+} firestore_client_MaybeDocument;
+
+/* Default values for struct fields */
+
+/* Initializer values for message structs */
+#define firestore_client_NoDocument_init_default {{{NULL}, NULL}, google_protobuf_Timestamp_init_default}
+#define firestore_client_MaybeDocument_init_default {0, {firestore_client_NoDocument_init_default}}
+#define firestore_client_NoDocument_init_zero {{{NULL}, NULL}, google_protobuf_Timestamp_init_zero}
+#define firestore_client_MaybeDocument_init_zero {0, {firestore_client_NoDocument_init_zero}}
+
+/* Field tags (for use in manual encoding/decoding) */
+#define firestore_client_NoDocument_name_tag 1
+#define firestore_client_NoDocument_read_time_tag 2
+#define firestore_client_MaybeDocument_no_document_tag 1
+#define firestore_client_MaybeDocument_document_tag 2
+
+/* Struct field encoding specification for nanopb */
+extern const pb_field_t firestore_client_NoDocument_fields[3];
+extern const pb_field_t firestore_client_MaybeDocument_fields[3];
+
+/* Maximum encoded size of messages (where known) */
+/* firestore_client_NoDocument_size depends on runtime parameters */
+/* firestore_client_MaybeDocument_size depends on runtime parameters */
+
+/* Message IDs (where set with "msgid" option) */
+#ifdef PB_MSGID
+
+#define MAYBE_DOCUMENT_MESSAGES \
+
+
+#endif
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+/* @@protoc_insertion_point(eof) */
+
+#endif
diff --git a/Firestore/Protos/nanopb/firestore/local/mutation.pb.c b/Firestore/Protos/nanopb/firestore/local/mutation.pb.c
new file mode 100644
index 0000000..7dedb14
--- /dev/null
+++ b/Firestore/Protos/nanopb/firestore/local/mutation.pb.c
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2018 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.
+ */
+
+/* Automatically generated nanopb constant definitions */
+/* Generated by nanopb-0.3.8 at Fri Feb 2 17:48:02 2018. */
+
+#include "mutation.pb.h"
+
+/* @@protoc_insertion_point(includes) */
+#if PB_PROTO_HEADER_VERSION != 30
+#error Regenerate this file with the current version of nanopb generator.
+#endif
+
+
+
+const pb_field_t firestore_client_MutationQueue_fields[3] = {
+ PB_FIELD( 1, INT32 , SINGULAR, STATIC , FIRST, firestore_client_MutationQueue, last_acknowledged_batch_id, last_acknowledged_batch_id, 0),
+ PB_FIELD( 2, BYTES , SINGULAR, CALLBACK, OTHER, firestore_client_MutationQueue, last_stream_token, last_acknowledged_batch_id, 0),
+ PB_LAST_FIELD
+};
+
+const pb_field_t firestore_client_WriteBatch_fields[4] = {
+ PB_FIELD( 1, INT32 , SINGULAR, STATIC , FIRST, firestore_client_WriteBatch, batch_id, batch_id, 0),
+ PB_FIELD( 2, MESSAGE , REPEATED, CALLBACK, OTHER, firestore_client_WriteBatch, writes, batch_id, &google_firestore_v1beta1_Write_fields),
+ PB_FIELD( 3, MESSAGE , SINGULAR, STATIC , OTHER, firestore_client_WriteBatch, local_write_time, writes, &google_protobuf_Timestamp_fields),
+ PB_LAST_FIELD
+};
+
+
+/* Check that field information fits in pb_field_t */
+#if !defined(PB_FIELD_32BIT)
+/* If you get an error here, it means that you need to define PB_FIELD_32BIT
+ * compile-time option. You can do that in pb.h or on compiler command line.
+ *
+ * The reason you need to do this is that some of your messages contain tag
+ * numbers or field sizes that are larger than what can fit in 8 or 16 bit
+ * field descriptors.
+ */
+PB_STATIC_ASSERT((pb_membersize(firestore_client_WriteBatch, local_write_time) < 65536), YOU_MUST_DEFINE_PB_FIELD_32BIT_FOR_MESSAGES_firestore_client_MutationQueue_firestore_client_WriteBatch)
+#endif
+
+#if !defined(PB_FIELD_16BIT) && !defined(PB_FIELD_32BIT)
+/* If you get an error here, it means that you need to define PB_FIELD_16BIT
+ * compile-time option. You can do that in pb.h or on compiler command line.
+ *
+ * The reason you need to do this is that some of your messages contain tag
+ * numbers or field sizes that are larger than what can fit in the default
+ * 8 bit descriptors.
+ */
+PB_STATIC_ASSERT((pb_membersize(firestore_client_WriteBatch, local_write_time) < 256), YOU_MUST_DEFINE_PB_FIELD_16BIT_FOR_MESSAGES_firestore_client_MutationQueue_firestore_client_WriteBatch)
+#endif
+
+
+/* @@protoc_insertion_point(eof) */
diff --git a/Firestore/Protos/nanopb/firestore/local/mutation.pb.h b/Firestore/Protos/nanopb/firestore/local/mutation.pb.h
new file mode 100644
index 0000000..537d0cd
--- /dev/null
+++ b/Firestore/Protos/nanopb/firestore/local/mutation.pb.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2018 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.
+ */
+
+/* Automatically generated nanopb header */
+/* Generated by nanopb-0.3.8 at Fri Feb 2 17:48:02 2018. */
+
+#ifndef PB_FIRESTORE_CLIENT_MUTATION_PB_H_INCLUDED
+#define PB_FIRESTORE_CLIENT_MUTATION_PB_H_INCLUDED
+#include <pb.h>
+
+#include "google/firestore/v1beta1/write.pb.h"
+
+#include "google/protobuf/timestamp.pb.h"
+
+/* @@protoc_insertion_point(includes) */
+#if PB_PROTO_HEADER_VERSION != 30
+#error Regenerate this file with the current version of nanopb generator.
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Struct definitions */
+typedef struct _firestore_client_MutationQueue {
+ int32_t last_acknowledged_batch_id;
+ pb_callback_t last_stream_token;
+/* @@protoc_insertion_point(struct:firestore_client_MutationQueue) */
+} firestore_client_MutationQueue;
+
+typedef struct _firestore_client_WriteBatch {
+ int32_t batch_id;
+ pb_callback_t writes;
+ google_protobuf_Timestamp local_write_time;
+/* @@protoc_insertion_point(struct:firestore_client_WriteBatch) */
+} firestore_client_WriteBatch;
+
+/* Default values for struct fields */
+
+/* Initializer values for message structs */
+#define firestore_client_MutationQueue_init_default {0, {{NULL}, NULL}}
+#define firestore_client_WriteBatch_init_default {0, {{NULL}, NULL}, google_protobuf_Timestamp_init_default}
+#define firestore_client_MutationQueue_init_zero {0, {{NULL}, NULL}}
+#define firestore_client_WriteBatch_init_zero {0, {{NULL}, NULL}, google_protobuf_Timestamp_init_zero}
+
+/* Field tags (for use in manual encoding/decoding) */
+#define firestore_client_MutationQueue_last_acknowledged_batch_id_tag 1
+#define firestore_client_MutationQueue_last_stream_token_tag 2
+#define firestore_client_WriteBatch_batch_id_tag 1
+#define firestore_client_WriteBatch_writes_tag 2
+#define firestore_client_WriteBatch_local_write_time_tag 3
+
+/* Struct field encoding specification for nanopb */
+extern const pb_field_t firestore_client_MutationQueue_fields[3];
+extern const pb_field_t firestore_client_WriteBatch_fields[4];
+
+/* Maximum encoded size of messages (where known) */
+/* firestore_client_MutationQueue_size depends on runtime parameters */
+/* firestore_client_WriteBatch_size depends on runtime parameters */
+
+/* Message IDs (where set with "msgid" option) */
+#ifdef PB_MSGID
+
+#define MUTATION_MESSAGES \
+
+
+#endif
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+/* @@protoc_insertion_point(eof) */
+
+#endif
diff --git a/Firestore/Protos/nanopb/firestore/local/target.pb.c b/Firestore/Protos/nanopb/firestore/local/target.pb.c
new file mode 100644
index 0000000..d00d4a6
--- /dev/null
+++ b/Firestore/Protos/nanopb/firestore/local/target.pb.c
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2018 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.
+ */
+
+/* Automatically generated nanopb constant definitions */
+/* Generated by nanopb-0.3.8 at Mon Apr 9 15:08:47 2018. */
+
+#include "target.pb.h"
+
+/* @@protoc_insertion_point(includes) */
+#if PB_PROTO_HEADER_VERSION != 30
+#error Regenerate this file with the current version of nanopb generator.
+#endif
+
+
+
+const pb_field_t firestore_client_Target_fields[7] = {
+ PB_FIELD( 1, INT32 , SINGULAR, STATIC , FIRST, firestore_client_Target, target_id, target_id, 0),
+ PB_FIELD( 2, MESSAGE , SINGULAR, STATIC , OTHER, firestore_client_Target, snapshot_version, target_id, &google_protobuf_Timestamp_fields),
+ PB_FIELD( 3, BYTES , SINGULAR, CALLBACK, OTHER, firestore_client_Target, resume_token, snapshot_version, 0),
+ PB_FIELD( 4, INT64 , SINGULAR, STATIC , OTHER, firestore_client_Target, last_listen_sequence_number, resume_token, 0),
+ PB_ONEOF_FIELD(target_type, 5, MESSAGE , ONEOF, STATIC , OTHER, firestore_client_Target, query, last_listen_sequence_number, &google_firestore_v1beta1_Target_QueryTarget_fields),
+ PB_ONEOF_FIELD(target_type, 6, MESSAGE , ONEOF, STATIC , UNION, firestore_client_Target, documents, last_listen_sequence_number, &google_firestore_v1beta1_Target_DocumentsTarget_fields),
+ PB_LAST_FIELD
+};
+
+const pb_field_t firestore_client_TargetGlobal_fields[5] = {
+ PB_FIELD( 1, INT32 , SINGULAR, STATIC , FIRST, firestore_client_TargetGlobal, highest_target_id, highest_target_id, 0),
+ PB_FIELD( 2, INT64 , SINGULAR, STATIC , OTHER, firestore_client_TargetGlobal, highest_listen_sequence_number, highest_target_id, 0),
+ PB_FIELD( 3, MESSAGE , SINGULAR, STATIC , OTHER, firestore_client_TargetGlobal, last_remote_snapshot_version, highest_listen_sequence_number, &google_protobuf_Timestamp_fields),
+ PB_FIELD( 4, INT32 , SINGULAR, STATIC , OTHER, firestore_client_TargetGlobal, target_count, last_remote_snapshot_version, 0),
+ PB_LAST_FIELD
+};
+
+
+/* Check that field information fits in pb_field_t */
+#if !defined(PB_FIELD_32BIT)
+/* If you get an error here, it means that you need to define PB_FIELD_32BIT
+ * compile-time option. You can do that in pb.h or on compiler command line.
+ *
+ * The reason you need to do this is that some of your messages contain tag
+ * numbers or field sizes that are larger than what can fit in 8 or 16 bit
+ * field descriptors.
+ */
+PB_STATIC_ASSERT((pb_membersize(firestore_client_Target, target_type.query) < 65536 && pb_membersize(firestore_client_Target, target_type.documents) < 65536 && pb_membersize(firestore_client_Target, snapshot_version) < 65536 && pb_membersize(firestore_client_TargetGlobal, last_remote_snapshot_version) < 65536), YOU_MUST_DEFINE_PB_FIELD_32BIT_FOR_MESSAGES_firestore_client_Target_firestore_client_TargetGlobal)
+#endif
+
+#if !defined(PB_FIELD_16BIT) && !defined(PB_FIELD_32BIT)
+/* If you get an error here, it means that you need to define PB_FIELD_16BIT
+ * compile-time option. You can do that in pb.h or on compiler command line.
+ *
+ * The reason you need to do this is that some of your messages contain tag
+ * numbers or field sizes that are larger than what can fit in the default
+ * 8 bit descriptors.
+ */
+PB_STATIC_ASSERT((pb_membersize(firestore_client_Target, target_type.query) < 256 && pb_membersize(firestore_client_Target, target_type.documents) < 256 && pb_membersize(firestore_client_Target, snapshot_version) < 256 && pb_membersize(firestore_client_TargetGlobal, last_remote_snapshot_version) < 256), YOU_MUST_DEFINE_PB_FIELD_16BIT_FOR_MESSAGES_firestore_client_Target_firestore_client_TargetGlobal)
+#endif
+
+
+/* @@protoc_insertion_point(eof) */
diff --git a/Firestore/Protos/nanopb/firestore/local/target.pb.h b/Firestore/Protos/nanopb/firestore/local/target.pb.h
new file mode 100644
index 0000000..37b64a2
--- /dev/null
+++ b/Firestore/Protos/nanopb/firestore/local/target.pb.h
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2018 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.
+ */
+
+/* Automatically generated nanopb header */
+/* Generated by nanopb-0.3.8 at Mon Apr 9 15:08:47 2018. */
+
+#ifndef PB_FIRESTORE_CLIENT_TARGET_PB_H_INCLUDED
+#define PB_FIRESTORE_CLIENT_TARGET_PB_H_INCLUDED
+#include <pb.h>
+
+#include "google/firestore/v1beta1/firestore.pb.h"
+
+#include "google/protobuf/timestamp.pb.h"
+
+/* @@protoc_insertion_point(includes) */
+#if PB_PROTO_HEADER_VERSION != 30
+#error Regenerate this file with the current version of nanopb generator.
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Struct definitions */
+typedef struct _firestore_client_Target {
+ int32_t target_id;
+ google_protobuf_Timestamp snapshot_version;
+ pb_callback_t resume_token;
+ int64_t last_listen_sequence_number;
+ pb_size_t which_target_type;
+ union {
+ google_firestore_v1beta1_Target_QueryTarget query;
+ google_firestore_v1beta1_Target_DocumentsTarget documents;
+ } target_type;
+/* @@protoc_insertion_point(struct:firestore_client_Target) */
+} firestore_client_Target;
+
+typedef struct _firestore_client_TargetGlobal {
+ int32_t highest_target_id;
+ int64_t highest_listen_sequence_number;
+ google_protobuf_Timestamp last_remote_snapshot_version;
+ int32_t target_count;
+/* @@protoc_insertion_point(struct:firestore_client_TargetGlobal) */
+} firestore_client_TargetGlobal;
+
+/* Default values for struct fields */
+
+/* Initializer values for message structs */
+#define firestore_client_Target_init_default {0, google_protobuf_Timestamp_init_default, {{NULL}, NULL}, 0, 0, {google_firestore_v1beta1_Target_QueryTarget_init_default}}
+#define firestore_client_TargetGlobal_init_default {0, 0, google_protobuf_Timestamp_init_default, 0}
+#define firestore_client_Target_init_zero {0, google_protobuf_Timestamp_init_zero, {{NULL}, NULL}, 0, 0, {google_firestore_v1beta1_Target_QueryTarget_init_zero}}
+#define firestore_client_TargetGlobal_init_zero {0, 0, google_protobuf_Timestamp_init_zero, 0}
+
+/* Field tags (for use in manual encoding/decoding) */
+#define firestore_client_Target_query_tag 5
+#define firestore_client_Target_documents_tag 6
+#define firestore_client_Target_target_id_tag 1
+#define firestore_client_Target_snapshot_version_tag 2
+#define firestore_client_Target_resume_token_tag 3
+#define firestore_client_Target_last_listen_sequence_number_tag 4
+#define firestore_client_TargetGlobal_highest_target_id_tag 1
+#define firestore_client_TargetGlobal_highest_listen_sequence_number_tag 2
+#define firestore_client_TargetGlobal_last_remote_snapshot_version_tag 3
+#define firestore_client_TargetGlobal_target_count_tag 4
+
+/* Struct field encoding specification for nanopb */
+extern const pb_field_t firestore_client_Target_fields[7];
+extern const pb_field_t firestore_client_TargetGlobal_fields[5];
+
+/* Maximum encoded size of messages (where known) */
+/* firestore_client_Target_size depends on runtime parameters */
+#define firestore_client_TargetGlobal_size 57
+
+/* Message IDs (where set with "msgid" option) */
+#ifdef PB_MSGID
+
+#define TARGET_MESSAGES \
+
+
+#endif
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+/* @@protoc_insertion_point(eof) */
+
+#endif
diff --git a/Firestore/Protos/nanopb/google/api/annotations.pb.c b/Firestore/Protos/nanopb/google/api/annotations.pb.c
new file mode 100644
index 0000000..6da5206
--- /dev/null
+++ b/Firestore/Protos/nanopb/google/api/annotations.pb.c
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2018 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.
+ */
+
+/* Automatically generated nanopb constant definitions */
+/* Generated by nanopb-0.3.8 at Fri Feb 2 17:48:02 2018. */
+
+#include "annotations.pb.h"
+
+/* @@protoc_insertion_point(includes) */
+#if PB_PROTO_HEADER_VERSION != 30
+#error Regenerate this file with the current version of nanopb generator.
+#endif
+
+
+
+
+
+/* @@protoc_insertion_point(eof) */
diff --git a/Firestore/Protos/nanopb/google/api/annotations.pb.h b/Firestore/Protos/nanopb/google/api/annotations.pb.h
new file mode 100644
index 0000000..33c9ba8
--- /dev/null
+++ b/Firestore/Protos/nanopb/google/api/annotations.pb.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2018 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.
+ */
+
+/* Automatically generated nanopb header */
+/* Generated by nanopb-0.3.8 at Fri Feb 2 17:48:02 2018. */
+
+#ifndef PB_GOOGLE_API_ANNOTATIONS_PB_H_INCLUDED
+#define PB_GOOGLE_API_ANNOTATIONS_PB_H_INCLUDED
+#include <pb.h>
+
+#include "google/api/http.pb.h"
+
+/* @@protoc_insertion_point(includes) */
+#if PB_PROTO_HEADER_VERSION != 30
+#error Regenerate this file with the current version of nanopb generator.
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Extensions */
+/* Extension field google_api_http was skipped because only "optional"
+ type of extension fields is currently supported. */
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+/* @@protoc_insertion_point(eof) */
+
+#endif
diff --git a/Firestore/Protos/nanopb/google/api/http.pb.c b/Firestore/Protos/nanopb/google/api/http.pb.c
new file mode 100644
index 0000000..7a2cd21
--- /dev/null
+++ b/Firestore/Protos/nanopb/google/api/http.pb.c
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2018 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.
+ */
+
+/* Automatically generated nanopb constant definitions */
+/* Generated by nanopb-0.3.8 at Thu Apr 12 07:27:15 2018. */
+
+#include "http.pb.h"
+
+/* @@protoc_insertion_point(includes) */
+#if PB_PROTO_HEADER_VERSION != 30
+#error Regenerate this file with the current version of nanopb generator.
+#endif
+
+
+
+const pb_field_t google_api_Http_fields[3] = {
+ PB_FIELD( 1, MESSAGE , REPEATED, CALLBACK, FIRST, google_api_Http, rules, rules, &google_api_HttpRule_fields),
+ PB_FIELD( 2, BOOL , SINGULAR, STATIC , OTHER, google_api_Http, fully_decode_reserved_expansion, rules, 0),
+ PB_LAST_FIELD
+};
+
+const pb_field_t google_api_HttpRule_fields[10] = {
+ PB_FIELD( 1, STRING , SINGULAR, CALLBACK, FIRST, google_api_HttpRule, selector, selector, 0),
+ PB_FIELD( 2, STRING , SINGULAR, CALLBACK, OTHER, google_api_HttpRule, get, selector, 0),
+ PB_FIELD( 3, STRING , SINGULAR, CALLBACK, OTHER, google_api_HttpRule, put, get, 0),
+ PB_FIELD( 4, STRING , SINGULAR, CALLBACK, OTHER, google_api_HttpRule, post, put, 0),
+ PB_FIELD( 5, STRING , SINGULAR, CALLBACK, OTHER, google_api_HttpRule, delete_, post, 0),
+ PB_FIELD( 6, STRING , SINGULAR, CALLBACK, OTHER, google_api_HttpRule, patch, delete_, 0),
+ PB_FIELD( 7, STRING , SINGULAR, CALLBACK, OTHER, google_api_HttpRule, body, patch, 0),
+ PB_FIELD( 8, MESSAGE , SINGULAR, STATIC , OTHER, google_api_HttpRule, custom, body, &google_api_CustomHttpPattern_fields),
+ PB_FIELD( 11, MESSAGE , REPEATED, CALLBACK, OTHER, google_api_HttpRule, additional_bindings, custom, &google_api_HttpRule_fields),
+ PB_LAST_FIELD
+};
+
+const pb_field_t google_api_CustomHttpPattern_fields[3] = {
+ PB_FIELD( 1, STRING , SINGULAR, CALLBACK, FIRST, google_api_CustomHttpPattern, kind, kind, 0),
+ PB_FIELD( 2, STRING , SINGULAR, CALLBACK, OTHER, google_api_CustomHttpPattern, path, kind, 0),
+ PB_LAST_FIELD
+};
+
+
+/* Check that field information fits in pb_field_t */
+#if !defined(PB_FIELD_32BIT)
+/* If you get an error here, it means that you need to define PB_FIELD_32BIT
+ * compile-time option. You can do that in pb.h or on compiler command line.
+ *
+ * The reason you need to do this is that some of your messages contain tag
+ * numbers or field sizes that are larger than what can fit in 8 or 16 bit
+ * field descriptors.
+ */
+PB_STATIC_ASSERT((pb_membersize(google_api_HttpRule, custom) < 65536), YOU_MUST_DEFINE_PB_FIELD_32BIT_FOR_MESSAGES_google_api_Http_google_api_HttpRule_google_api_CustomHttpPattern)
+#endif
+
+#if !defined(PB_FIELD_16BIT) && !defined(PB_FIELD_32BIT)
+/* If you get an error here, it means that you need to define PB_FIELD_16BIT
+ * compile-time option. You can do that in pb.h or on compiler command line.
+ *
+ * The reason you need to do this is that some of your messages contain tag
+ * numbers or field sizes that are larger than what can fit in the default
+ * 8 bit descriptors.
+ */
+PB_STATIC_ASSERT((pb_membersize(google_api_HttpRule, custom) < 256), YOU_MUST_DEFINE_PB_FIELD_16BIT_FOR_MESSAGES_google_api_Http_google_api_HttpRule_google_api_CustomHttpPattern)
+#endif
+
+
+/* @@protoc_insertion_point(eof) */
diff --git a/Firestore/Protos/nanopb/google/api/http.pb.h b/Firestore/Protos/nanopb/google/api/http.pb.h
new file mode 100644
index 0000000..a7bbc46
--- /dev/null
+++ b/Firestore/Protos/nanopb/google/api/http.pb.h
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2018 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.
+ */
+
+/* Automatically generated nanopb header */
+/* Generated by nanopb-0.3.8 at Thu Apr 12 07:27:15 2018. */
+
+#ifndef PB_GOOGLE_API_HTTP_PB_H_INCLUDED
+#define PB_GOOGLE_API_HTTP_PB_H_INCLUDED
+#include <pb.h>
+
+/* @@protoc_insertion_point(includes) */
+#if PB_PROTO_HEADER_VERSION != 30
+#error Regenerate this file with the current version of nanopb generator.
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Struct definitions */
+typedef struct _google_api_CustomHttpPattern {
+ pb_callback_t kind;
+ pb_callback_t path;
+/* @@protoc_insertion_point(struct:google_api_CustomHttpPattern) */
+} google_api_CustomHttpPattern;
+
+typedef struct _google_api_Http {
+ pb_callback_t rules;
+ bool fully_decode_reserved_expansion;
+/* @@protoc_insertion_point(struct:google_api_Http) */
+} google_api_Http;
+
+typedef struct _google_api_HttpRule {
+ pb_callback_t selector;
+ pb_callback_t get;
+ pb_callback_t put;
+ pb_callback_t post;
+ pb_callback_t delete_;
+ pb_callback_t patch;
+ pb_callback_t body;
+ google_api_CustomHttpPattern custom;
+ pb_callback_t additional_bindings;
+/* @@protoc_insertion_point(struct:google_api_HttpRule) */
+} google_api_HttpRule;
+
+/* Default values for struct fields */
+
+/* Initializer values for message structs */
+#define google_api_Http_init_default {{{NULL}, NULL}, 0}
+#define google_api_HttpRule_init_default {{{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}, google_api_CustomHttpPattern_init_default, {{NULL}, NULL}}
+#define google_api_CustomHttpPattern_init_default {{{NULL}, NULL}, {{NULL}, NULL}}
+#define google_api_Http_init_zero {{{NULL}, NULL}, 0}
+#define google_api_HttpRule_init_zero {{{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}, google_api_CustomHttpPattern_init_zero, {{NULL}, NULL}}
+#define google_api_CustomHttpPattern_init_zero {{{NULL}, NULL}, {{NULL}, NULL}}
+
+/* Field tags (for use in manual encoding/decoding) */
+#define google_api_CustomHttpPattern_kind_tag 1
+#define google_api_CustomHttpPattern_path_tag 2
+#define google_api_Http_rules_tag 1
+#define google_api_Http_fully_decode_reserved_expansion_tag 2
+#define google_api_HttpRule_selector_tag 1
+#define google_api_HttpRule_get_tag 2
+#define google_api_HttpRule_put_tag 3
+#define google_api_HttpRule_post_tag 4
+#define google_api_HttpRule_delete_tag 5
+#define google_api_HttpRule_patch_tag 6
+#define google_api_HttpRule_custom_tag 8
+#define google_api_HttpRule_body_tag 7
+#define google_api_HttpRule_additional_bindings_tag 11
+
+/* Struct field encoding specification for nanopb */
+extern const pb_field_t google_api_Http_fields[3];
+extern const pb_field_t google_api_HttpRule_fields[10];
+extern const pb_field_t google_api_CustomHttpPattern_fields[3];
+
+/* Maximum encoded size of messages (where known) */
+/* google_api_Http_size depends on runtime parameters */
+/* google_api_HttpRule_size depends on runtime parameters */
+/* google_api_CustomHttpPattern_size depends on runtime parameters */
+
+/* Message IDs (where set with "msgid" option) */
+#ifdef PB_MSGID
+
+#define HTTP_MESSAGES \
+
+
+#endif
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+/* @@protoc_insertion_point(eof) */
+
+#endif
diff --git a/Firestore/Protos/nanopb/google/firestore/v1beta1/common.pb.c b/Firestore/Protos/nanopb/google/firestore/v1beta1/common.pb.c
new file mode 100644
index 0000000..de2cf65
--- /dev/null
+++ b/Firestore/Protos/nanopb/google/firestore/v1beta1/common.pb.c
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2018 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.
+ */
+
+/* Automatically generated nanopb constant definitions */
+/* Generated by nanopb-0.3.8 at Fri Feb 2 17:48:02 2018. */
+
+#include "common.pb.h"
+
+/* @@protoc_insertion_point(includes) */
+#if PB_PROTO_HEADER_VERSION != 30
+#error Regenerate this file with the current version of nanopb generator.
+#endif
+
+
+
+const pb_field_t google_firestore_v1beta1_DocumentMask_fields[2] = {
+ PB_FIELD( 1, STRING , REPEATED, CALLBACK, FIRST, google_firestore_v1beta1_DocumentMask, field_paths, field_paths, 0),
+ PB_LAST_FIELD
+};
+
+const pb_field_t google_firestore_v1beta1_Precondition_fields[3] = {
+ PB_ONEOF_FIELD(condition_type, 1, BOOL , ONEOF, STATIC , FIRST, google_firestore_v1beta1_Precondition, exists, exists, 0),
+ PB_ONEOF_FIELD(condition_type, 2, MESSAGE , ONEOF, STATIC , UNION, google_firestore_v1beta1_Precondition, update_time, update_time, &google_protobuf_Timestamp_fields),
+ PB_LAST_FIELD
+};
+
+const pb_field_t google_firestore_v1beta1_TransactionOptions_fields[3] = {
+ PB_ONEOF_FIELD(mode, 2, MESSAGE , ONEOF, STATIC , FIRST, google_firestore_v1beta1_TransactionOptions, read_only, read_only, &google_firestore_v1beta1_TransactionOptions_ReadOnly_fields),
+ PB_ONEOF_FIELD(mode, 3, MESSAGE , ONEOF, STATIC , UNION, google_firestore_v1beta1_TransactionOptions, read_write, read_write, &google_firestore_v1beta1_TransactionOptions_ReadWrite_fields),
+ PB_LAST_FIELD
+};
+
+const pb_field_t google_firestore_v1beta1_TransactionOptions_ReadWrite_fields[2] = {
+ PB_FIELD( 1, BYTES , SINGULAR, CALLBACK, FIRST, google_firestore_v1beta1_TransactionOptions_ReadWrite, retry_transaction, retry_transaction, 0),
+ PB_LAST_FIELD
+};
+
+const pb_field_t google_firestore_v1beta1_TransactionOptions_ReadOnly_fields[2] = {
+ PB_ONEOF_FIELD(consistency_selector, 2, MESSAGE , ONEOF, STATIC , FIRST, google_firestore_v1beta1_TransactionOptions_ReadOnly, read_time, read_time, &google_protobuf_Timestamp_fields),
+ PB_LAST_FIELD
+};
+
+
+/* Check that field information fits in pb_field_t */
+#if !defined(PB_FIELD_32BIT)
+/* If you get an error here, it means that you need to define PB_FIELD_32BIT
+ * compile-time option. You can do that in pb.h or on compiler command line.
+ *
+ * The reason you need to do this is that some of your messages contain tag
+ * numbers or field sizes that are larger than what can fit in 8 or 16 bit
+ * field descriptors.
+ */
+PB_STATIC_ASSERT((pb_membersize(google_firestore_v1beta1_Precondition, condition_type.update_time) < 65536 && pb_membersize(google_firestore_v1beta1_TransactionOptions, mode.read_only) < 65536 && pb_membersize(google_firestore_v1beta1_TransactionOptions, mode.read_write) < 65536 && pb_membersize(google_firestore_v1beta1_TransactionOptions_ReadOnly, consistency_selector.read_time) < 65536), YOU_MUST_DEFINE_PB_FIELD_32BIT_FOR_MESSAGES_google_firestore_v1beta1_DocumentMask_google_firestore_v1beta1_Precondition_google_firestore_v1beta1_TransactionOptions_google_firestore_v1beta1_TransactionOptions_ReadWrite_google_firestore_v1beta1_TransactionOptions_ReadOnly)
+#endif
+
+#if !defined(PB_FIELD_16BIT) && !defined(PB_FIELD_32BIT)
+/* If you get an error here, it means that you need to define PB_FIELD_16BIT
+ * compile-time option. You can do that in pb.h or on compiler command line.
+ *
+ * The reason you need to do this is that some of your messages contain tag
+ * numbers or field sizes that are larger than what can fit in the default
+ * 8 bit descriptors.
+ */
+PB_STATIC_ASSERT((pb_membersize(google_firestore_v1beta1_Precondition, condition_type.update_time) < 256 && pb_membersize(google_firestore_v1beta1_TransactionOptions, mode.read_only) < 256 && pb_membersize(google_firestore_v1beta1_TransactionOptions, mode.read_write) < 256 && pb_membersize(google_firestore_v1beta1_TransactionOptions_ReadOnly, consistency_selector.read_time) < 256), YOU_MUST_DEFINE_PB_FIELD_16BIT_FOR_MESSAGES_google_firestore_v1beta1_DocumentMask_google_firestore_v1beta1_Precondition_google_firestore_v1beta1_TransactionOptions_google_firestore_v1beta1_TransactionOptions_ReadWrite_google_firestore_v1beta1_TransactionOptions_ReadOnly)
+#endif
+
+
+/* @@protoc_insertion_point(eof) */
diff --git a/Firestore/Protos/nanopb/google/firestore/v1beta1/common.pb.h b/Firestore/Protos/nanopb/google/firestore/v1beta1/common.pb.h
new file mode 100644
index 0000000..277d9b8
--- /dev/null
+++ b/Firestore/Protos/nanopb/google/firestore/v1beta1/common.pb.h
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2018 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.
+ */
+
+/* Automatically generated nanopb header */
+/* Generated by nanopb-0.3.8 at Fri Feb 2 17:48:02 2018. */
+
+#ifndef PB_GOOGLE_FIRESTORE_V1BETA1_COMMON_PB_H_INCLUDED
+#define PB_GOOGLE_FIRESTORE_V1BETA1_COMMON_PB_H_INCLUDED
+#include <pb.h>
+
+#include "google/api/annotations.pb.h"
+
+#include "google/protobuf/timestamp.pb.h"
+
+/* @@protoc_insertion_point(includes) */
+#if PB_PROTO_HEADER_VERSION != 30
+#error Regenerate this file with the current version of nanopb generator.
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Struct definitions */
+typedef struct _google_firestore_v1beta1_DocumentMask {
+ pb_callback_t field_paths;
+/* @@protoc_insertion_point(struct:google_firestore_v1beta1_DocumentMask) */
+} google_firestore_v1beta1_DocumentMask;
+
+typedef struct _google_firestore_v1beta1_TransactionOptions_ReadWrite {
+ pb_callback_t retry_transaction;
+/* @@protoc_insertion_point(struct:google_firestore_v1beta1_TransactionOptions_ReadWrite) */
+} google_firestore_v1beta1_TransactionOptions_ReadWrite;
+
+typedef struct _google_firestore_v1beta1_Precondition {
+ pb_size_t which_condition_type;
+ union {
+ bool exists;
+ google_protobuf_Timestamp update_time;
+ } condition_type;
+/* @@protoc_insertion_point(struct:google_firestore_v1beta1_Precondition) */
+} google_firestore_v1beta1_Precondition;
+
+typedef struct _google_firestore_v1beta1_TransactionOptions_ReadOnly {
+ pb_size_t which_consistency_selector;
+ union {
+ google_protobuf_Timestamp read_time;
+ } consistency_selector;
+/* @@protoc_insertion_point(struct:google_firestore_v1beta1_TransactionOptions_ReadOnly) */
+} google_firestore_v1beta1_TransactionOptions_ReadOnly;
+
+typedef struct _google_firestore_v1beta1_TransactionOptions {
+ pb_size_t which_mode;
+ union {
+ google_firestore_v1beta1_TransactionOptions_ReadOnly read_only;
+ google_firestore_v1beta1_TransactionOptions_ReadWrite read_write;
+ } mode;
+/* @@protoc_insertion_point(struct:google_firestore_v1beta1_TransactionOptions) */
+} google_firestore_v1beta1_TransactionOptions;
+
+/* Default values for struct fields */
+
+/* Initializer values for message structs */
+#define google_firestore_v1beta1_DocumentMask_init_default {{{NULL}, NULL}}
+#define google_firestore_v1beta1_Precondition_init_default {0, {0}}
+#define google_firestore_v1beta1_TransactionOptions_init_default {0, {google_firestore_v1beta1_TransactionOptions_ReadOnly_init_default}}
+#define google_firestore_v1beta1_TransactionOptions_ReadWrite_init_default {{{NULL}, NULL}}
+#define google_firestore_v1beta1_TransactionOptions_ReadOnly_init_default {0, {google_protobuf_Timestamp_init_default}}
+#define google_firestore_v1beta1_DocumentMask_init_zero {{{NULL}, NULL}}
+#define google_firestore_v1beta1_Precondition_init_zero {0, {0}}
+#define google_firestore_v1beta1_TransactionOptions_init_zero {0, {google_firestore_v1beta1_TransactionOptions_ReadOnly_init_zero}}
+#define google_firestore_v1beta1_TransactionOptions_ReadWrite_init_zero {{{NULL}, NULL}}
+#define google_firestore_v1beta1_TransactionOptions_ReadOnly_init_zero {0, {google_protobuf_Timestamp_init_zero}}
+
+/* Field tags (for use in manual encoding/decoding) */
+#define google_firestore_v1beta1_DocumentMask_field_paths_tag 1
+#define google_firestore_v1beta1_TransactionOptions_ReadWrite_retry_transaction_tag 1
+#define google_firestore_v1beta1_Precondition_exists_tag 1
+#define google_firestore_v1beta1_Precondition_update_time_tag 2
+#define google_firestore_v1beta1_TransactionOptions_ReadOnly_read_time_tag 2
+#define google_firestore_v1beta1_TransactionOptions_read_only_tag 2
+#define google_firestore_v1beta1_TransactionOptions_read_write_tag 3
+
+/* Struct field encoding specification for nanopb */
+extern const pb_field_t google_firestore_v1beta1_DocumentMask_fields[2];
+extern const pb_field_t google_firestore_v1beta1_Precondition_fields[3];
+extern const pb_field_t google_firestore_v1beta1_TransactionOptions_fields[3];
+extern const pb_field_t google_firestore_v1beta1_TransactionOptions_ReadWrite_fields[2];
+extern const pb_field_t google_firestore_v1beta1_TransactionOptions_ReadOnly_fields[2];
+
+/* Maximum encoded size of messages (where known) */
+/* google_firestore_v1beta1_DocumentMask_size depends on runtime parameters */
+#define google_firestore_v1beta1_Precondition_size 24
+/* google_firestore_v1beta1_TransactionOptions_size depends on runtime parameters */
+/* google_firestore_v1beta1_TransactionOptions_ReadWrite_size depends on runtime parameters */
+#define google_firestore_v1beta1_TransactionOptions_ReadOnly_size 24
+
+/* Message IDs (where set with "msgid" option) */
+#ifdef PB_MSGID
+
+#define COMMON_MESSAGES \
+
+
+#endif
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+/* @@protoc_insertion_point(eof) */
+
+#endif
diff --git a/Firestore/Protos/nanopb/google/firestore/v1beta1/document.pb.c b/Firestore/Protos/nanopb/google/firestore/v1beta1/document.pb.c
new file mode 100644
index 0000000..862c884
--- /dev/null
+++ b/Firestore/Protos/nanopb/google/firestore/v1beta1/document.pb.c
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2018 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.
+ */
+
+/* Automatically generated nanopb constant definitions */
+/* Generated by nanopb-0.3.8 at Fri Feb 2 17:48:02 2018. */
+
+#include "document.pb.h"
+
+/* @@protoc_insertion_point(includes) */
+#if PB_PROTO_HEADER_VERSION != 30
+#error Regenerate this file with the current version of nanopb generator.
+#endif
+
+
+
+const pb_field_t google_firestore_v1beta1_Document_fields[5] = {
+ PB_FIELD( 1, STRING , SINGULAR, CALLBACK, FIRST, google_firestore_v1beta1_Document, name, name, 0),
+ PB_FIELD( 2, MESSAGE , REPEATED, CALLBACK, OTHER, google_firestore_v1beta1_Document, fields, name, &google_firestore_v1beta1_Document_FieldsEntry_fields),
+ PB_FIELD( 3, MESSAGE , SINGULAR, STATIC , OTHER, google_firestore_v1beta1_Document, create_time, fields, &google_protobuf_Timestamp_fields),
+ PB_FIELD( 4, MESSAGE , SINGULAR, STATIC , OTHER, google_firestore_v1beta1_Document, update_time, create_time, &google_protobuf_Timestamp_fields),
+ PB_LAST_FIELD
+};
+
+const pb_field_t google_firestore_v1beta1_Document_FieldsEntry_fields[3] = {
+ PB_FIELD( 1, STRING , SINGULAR, CALLBACK, FIRST, google_firestore_v1beta1_Document_FieldsEntry, key, key, 0),
+ PB_FIELD( 2, MESSAGE , SINGULAR, STATIC , OTHER, google_firestore_v1beta1_Document_FieldsEntry, value, key, &google_firestore_v1beta1_Value_fields),
+ PB_LAST_FIELD
+};
+
+const pb_field_t google_firestore_v1beta1_Value_fields[12] = {
+ PB_FIELD( 1, BOOL , SINGULAR, STATIC , FIRST, google_firestore_v1beta1_Value, boolean_value, boolean_value, 0),
+ PB_FIELD( 2, INT64 , SINGULAR, STATIC , OTHER, google_firestore_v1beta1_Value, integer_value, boolean_value, 0),
+ PB_FIELD( 3, DOUBLE , SINGULAR, STATIC , OTHER, google_firestore_v1beta1_Value, double_value, integer_value, 0),
+ PB_FIELD( 5, STRING , SINGULAR, CALLBACK, OTHER, google_firestore_v1beta1_Value, reference_value, double_value, 0),
+ PB_FIELD( 6, MESSAGE , SINGULAR, STATIC , OTHER, google_firestore_v1beta1_Value, map_value, reference_value, &google_firestore_v1beta1_MapValue_fields),
+ PB_FIELD( 8, MESSAGE , SINGULAR, STATIC , OTHER, google_firestore_v1beta1_Value, geo_point_value, map_value, &google_type_LatLng_fields),
+ PB_FIELD( 9, MESSAGE , SINGULAR, STATIC , OTHER, google_firestore_v1beta1_Value, array_value, geo_point_value, &google_firestore_v1beta1_ArrayValue_fields),
+ PB_FIELD( 10, MESSAGE , SINGULAR, STATIC , OTHER, google_firestore_v1beta1_Value, timestamp_value, array_value, &google_protobuf_Timestamp_fields),
+ PB_FIELD( 11, UENUM , SINGULAR, STATIC , OTHER, google_firestore_v1beta1_Value, null_value, timestamp_value, 0),
+ PB_FIELD( 17, STRING , SINGULAR, CALLBACK, OTHER, google_firestore_v1beta1_Value, string_value, null_value, 0),
+ PB_FIELD( 18, BYTES , SINGULAR, CALLBACK, OTHER, google_firestore_v1beta1_Value, bytes_value, string_value, 0),
+ PB_LAST_FIELD
+};
+
+const pb_field_t google_firestore_v1beta1_ArrayValue_fields[2] = {
+ PB_FIELD( 1, MESSAGE , REPEATED, CALLBACK, FIRST, google_firestore_v1beta1_ArrayValue, values, values, &google_firestore_v1beta1_Value_fields),
+ PB_LAST_FIELD
+};
+
+const pb_field_t google_firestore_v1beta1_MapValue_fields[2] = {
+ PB_FIELD( 1, MESSAGE , REPEATED, CALLBACK, FIRST, google_firestore_v1beta1_MapValue, fields, fields, &google_firestore_v1beta1_MapValue_FieldsEntry_fields),
+ PB_LAST_FIELD
+};
+
+const pb_field_t google_firestore_v1beta1_MapValue_FieldsEntry_fields[3] = {
+ PB_FIELD( 1, STRING , SINGULAR, CALLBACK, FIRST, google_firestore_v1beta1_MapValue_FieldsEntry, key, key, 0),
+ PB_FIELD( 2, MESSAGE , SINGULAR, STATIC , OTHER, google_firestore_v1beta1_MapValue_FieldsEntry, value, key, &google_firestore_v1beta1_Value_fields),
+ PB_LAST_FIELD
+};
+
+
+/* Check that field information fits in pb_field_t */
+#if !defined(PB_FIELD_32BIT)
+/* If you get an error here, it means that you need to define PB_FIELD_32BIT
+ * compile-time option. You can do that in pb.h or on compiler command line.
+ *
+ * The reason you need to do this is that some of your messages contain tag
+ * numbers or field sizes that are larger than what can fit in 8 or 16 bit
+ * field descriptors.
+ */
+PB_STATIC_ASSERT((pb_membersize(google_firestore_v1beta1_Document, create_time) < 65536 && pb_membersize(google_firestore_v1beta1_Document, update_time) < 65536 && pb_membersize(google_firestore_v1beta1_Document_FieldsEntry, value) < 65536 && pb_membersize(google_firestore_v1beta1_Value, timestamp_value) < 65536 && pb_membersize(google_firestore_v1beta1_Value, geo_point_value) < 65536 && pb_membersize(google_firestore_v1beta1_Value, array_value) < 65536 && pb_membersize(google_firestore_v1beta1_Value, map_value) < 65536 && pb_membersize(google_firestore_v1beta1_MapValue_FieldsEntry, value) < 65536), YOU_MUST_DEFINE_PB_FIELD_32BIT_FOR_MESSAGES_google_firestore_v1beta1_Document_google_firestore_v1beta1_Document_FieldsEntry_google_firestore_v1beta1_Value_google_firestore_v1beta1_ArrayValue_google_firestore_v1beta1_MapValue_google_firestore_v1beta1_MapValue_FieldsEntry)
+#endif
+
+#if !defined(PB_FIELD_16BIT) && !defined(PB_FIELD_32BIT)
+/* If you get an error here, it means that you need to define PB_FIELD_16BIT
+ * compile-time option. You can do that in pb.h or on compiler command line.
+ *
+ * The reason you need to do this is that some of your messages contain tag
+ * numbers or field sizes that are larger than what can fit in the default
+ * 8 bit descriptors.
+ */
+PB_STATIC_ASSERT((pb_membersize(google_firestore_v1beta1_Document, create_time) < 256 && pb_membersize(google_firestore_v1beta1_Document, update_time) < 256 && pb_membersize(google_firestore_v1beta1_Document_FieldsEntry, value) < 256 && pb_membersize(google_firestore_v1beta1_Value, timestamp_value) < 256 && pb_membersize(google_firestore_v1beta1_Value, geo_point_value) < 256 && pb_membersize(google_firestore_v1beta1_Value, array_value) < 256 && pb_membersize(google_firestore_v1beta1_Value, map_value) < 256 && pb_membersize(google_firestore_v1beta1_MapValue_FieldsEntry, value) < 256), YOU_MUST_DEFINE_PB_FIELD_16BIT_FOR_MESSAGES_google_firestore_v1beta1_Document_google_firestore_v1beta1_Document_FieldsEntry_google_firestore_v1beta1_Value_google_firestore_v1beta1_ArrayValue_google_firestore_v1beta1_MapValue_google_firestore_v1beta1_MapValue_FieldsEntry)
+#endif
+
+
+/* On some platforms (such as AVR), double is really float.
+ * These are not directly supported by nanopb, but see example_avr_double.
+ * To get rid of this error, remove any double fields from your .proto.
+ */
+PB_STATIC_ASSERT(sizeof(double) == 8, DOUBLE_MUST_BE_8_BYTES)
+
+/* @@protoc_insertion_point(eof) */
diff --git a/Firestore/Protos/nanopb/google/firestore/v1beta1/document.pb.h b/Firestore/Protos/nanopb/google/firestore/v1beta1/document.pb.h
new file mode 100644
index 0000000..180c1af
--- /dev/null
+++ b/Firestore/Protos/nanopb/google/firestore/v1beta1/document.pb.h
@@ -0,0 +1,155 @@
+/*
+ * Copyright 2018 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.
+ */
+
+/* Automatically generated nanopb header */
+/* Generated by nanopb-0.3.8 at Fri Feb 2 17:48:02 2018. */
+
+#ifndef PB_GOOGLE_FIRESTORE_V1BETA1_DOCUMENT_PB_H_INCLUDED
+#define PB_GOOGLE_FIRESTORE_V1BETA1_DOCUMENT_PB_H_INCLUDED
+#include <pb.h>
+
+#include "google/api/annotations.pb.h"
+
+#include "google/protobuf/struct.pb.h"
+
+#include "google/protobuf/timestamp.pb.h"
+
+#include "google/type/latlng.pb.h"
+
+/* @@protoc_insertion_point(includes) */
+#if PB_PROTO_HEADER_VERSION != 30
+#error Regenerate this file with the current version of nanopb generator.
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Struct definitions */
+typedef struct _google_firestore_v1beta1_ArrayValue {
+ pb_callback_t values;
+/* @@protoc_insertion_point(struct:google_firestore_v1beta1_ArrayValue) */
+} google_firestore_v1beta1_ArrayValue;
+
+typedef struct _google_firestore_v1beta1_MapValue {
+ pb_callback_t fields;
+/* @@protoc_insertion_point(struct:google_firestore_v1beta1_MapValue) */
+} google_firestore_v1beta1_MapValue;
+
+typedef struct _google_firestore_v1beta1_Document {
+ pb_callback_t name;
+ pb_callback_t fields;
+ google_protobuf_Timestamp create_time;
+ google_protobuf_Timestamp update_time;
+/* @@protoc_insertion_point(struct:google_firestore_v1beta1_Document) */
+} google_firestore_v1beta1_Document;
+
+typedef struct _google_firestore_v1beta1_Value {
+ bool boolean_value;
+ int64_t integer_value;
+ double double_value;
+ pb_callback_t reference_value;
+ google_firestore_v1beta1_MapValue map_value;
+ google_type_LatLng geo_point_value;
+ google_firestore_v1beta1_ArrayValue array_value;
+ google_protobuf_Timestamp timestamp_value;
+ google_protobuf_NullValue null_value;
+ pb_callback_t string_value;
+ pb_callback_t bytes_value;
+/* @@protoc_insertion_point(struct:google_firestore_v1beta1_Value) */
+} google_firestore_v1beta1_Value;
+
+typedef struct _google_firestore_v1beta1_Document_FieldsEntry {
+ pb_callback_t key;
+ google_firestore_v1beta1_Value value;
+/* @@protoc_insertion_point(struct:google_firestore_v1beta1_Document_FieldsEntry) */
+} google_firestore_v1beta1_Document_FieldsEntry;
+
+typedef struct _google_firestore_v1beta1_MapValue_FieldsEntry {
+ pb_callback_t key;
+ google_firestore_v1beta1_Value value;
+/* @@protoc_insertion_point(struct:google_firestore_v1beta1_MapValue_FieldsEntry) */
+} google_firestore_v1beta1_MapValue_FieldsEntry;
+
+/* Default values for struct fields */
+
+/* Initializer values for message structs */
+#define google_firestore_v1beta1_Document_init_default {{{NULL}, NULL}, {{NULL}, NULL}, google_protobuf_Timestamp_init_default, google_protobuf_Timestamp_init_default}
+#define google_firestore_v1beta1_Document_FieldsEntry_init_default {{{NULL}, NULL}, google_firestore_v1beta1_Value_init_default}
+#define google_firestore_v1beta1_Value_init_default {0, 0, 0, {{NULL}, NULL}, google_firestore_v1beta1_MapValue_init_default, google_type_LatLng_init_default, google_firestore_v1beta1_ArrayValue_init_default, google_protobuf_Timestamp_init_default, (google_protobuf_NullValue)0, {{NULL}, NULL}, {{NULL}, NULL}}
+#define google_firestore_v1beta1_ArrayValue_init_default {{{NULL}, NULL}}
+#define google_firestore_v1beta1_MapValue_init_default {{{NULL}, NULL}}
+#define google_firestore_v1beta1_MapValue_FieldsEntry_init_default {{{NULL}, NULL}, google_firestore_v1beta1_Value_init_default}
+#define google_firestore_v1beta1_Document_init_zero {{{NULL}, NULL}, {{NULL}, NULL}, google_protobuf_Timestamp_init_zero, google_protobuf_Timestamp_init_zero}
+#define google_firestore_v1beta1_Document_FieldsEntry_init_zero {{{NULL}, NULL}, google_firestore_v1beta1_Value_init_zero}
+#define google_firestore_v1beta1_Value_init_zero {0, 0, 0, {{NULL}, NULL}, google_firestore_v1beta1_MapValue_init_zero, google_type_LatLng_init_zero, google_firestore_v1beta1_ArrayValue_init_zero, google_protobuf_Timestamp_init_zero, (google_protobuf_NullValue)0, {{NULL}, NULL}, {{NULL}, NULL}}
+#define google_firestore_v1beta1_ArrayValue_init_zero {{{NULL}, NULL}}
+#define google_firestore_v1beta1_MapValue_init_zero {{{NULL}, NULL}}
+#define google_firestore_v1beta1_MapValue_FieldsEntry_init_zero {{{NULL}, NULL}, google_firestore_v1beta1_Value_init_zero}
+
+/* Field tags (for use in manual encoding/decoding) */
+#define google_firestore_v1beta1_ArrayValue_values_tag 1
+#define google_firestore_v1beta1_MapValue_fields_tag 1
+#define google_firestore_v1beta1_Document_name_tag 1
+#define google_firestore_v1beta1_Document_fields_tag 2
+#define google_firestore_v1beta1_Document_create_time_tag 3
+#define google_firestore_v1beta1_Document_update_time_tag 4
+#define google_firestore_v1beta1_Value_null_value_tag 11
+#define google_firestore_v1beta1_Value_boolean_value_tag 1
+#define google_firestore_v1beta1_Value_integer_value_tag 2
+#define google_firestore_v1beta1_Value_double_value_tag 3
+#define google_firestore_v1beta1_Value_timestamp_value_tag 10
+#define google_firestore_v1beta1_Value_string_value_tag 17
+#define google_firestore_v1beta1_Value_bytes_value_tag 18
+#define google_firestore_v1beta1_Value_reference_value_tag 5
+#define google_firestore_v1beta1_Value_geo_point_value_tag 8
+#define google_firestore_v1beta1_Value_array_value_tag 9
+#define google_firestore_v1beta1_Value_map_value_tag 6
+#define google_firestore_v1beta1_Document_FieldsEntry_key_tag 1
+#define google_firestore_v1beta1_Document_FieldsEntry_value_tag 2
+#define google_firestore_v1beta1_MapValue_FieldsEntry_key_tag 1
+#define google_firestore_v1beta1_MapValue_FieldsEntry_value_tag 2
+
+/* Struct field encoding specification for nanopb */
+extern const pb_field_t google_firestore_v1beta1_Document_fields[5];
+extern const pb_field_t google_firestore_v1beta1_Document_FieldsEntry_fields[3];
+extern const pb_field_t google_firestore_v1beta1_Value_fields[12];
+extern const pb_field_t google_firestore_v1beta1_ArrayValue_fields[2];
+extern const pb_field_t google_firestore_v1beta1_MapValue_fields[2];
+extern const pb_field_t google_firestore_v1beta1_MapValue_FieldsEntry_fields[3];
+
+/* Maximum encoded size of messages (where known) */
+/* google_firestore_v1beta1_Document_size depends on runtime parameters */
+/* google_firestore_v1beta1_Document_FieldsEntry_size depends on runtime parameters */
+/* google_firestore_v1beta1_Value_size depends on runtime parameters */
+/* google_firestore_v1beta1_ArrayValue_size depends on runtime parameters */
+/* google_firestore_v1beta1_MapValue_size depends on runtime parameters */
+/* google_firestore_v1beta1_MapValue_FieldsEntry_size depends on runtime parameters */
+
+/* Message IDs (where set with "msgid" option) */
+#ifdef PB_MSGID
+
+#define DOCUMENT_MESSAGES \
+
+
+#endif
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+/* @@protoc_insertion_point(eof) */
+
+#endif
diff --git a/Firestore/Protos/nanopb/google/firestore/v1beta1/firestore.pb.c b/Firestore/Protos/nanopb/google/firestore/v1beta1/firestore.pb.c
new file mode 100644
index 0000000..bc8eca9
--- /dev/null
+++ b/Firestore/Protos/nanopb/google/firestore/v1beta1/firestore.pb.c
@@ -0,0 +1,259 @@
+/*
+ * Copyright 2018 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.
+ */
+
+/* Automatically generated nanopb constant definitions */
+/* Generated by nanopb-0.3.8 at Fri Feb 2 17:48:02 2018. */
+
+#include "firestore.pb.h"
+
+/* @@protoc_insertion_point(includes) */
+#if PB_PROTO_HEADER_VERSION != 30
+#error Regenerate this file with the current version of nanopb generator.
+#endif
+
+
+
+const pb_field_t google_firestore_v1beta1_GetDocumentRequest_fields[5] = {
+ PB_FIELD( 1, STRING , SINGULAR, CALLBACK, FIRST, google_firestore_v1beta1_GetDocumentRequest, name, name, 0),
+ PB_FIELD( 2, MESSAGE , SINGULAR, STATIC , OTHER, google_firestore_v1beta1_GetDocumentRequest, mask, name, &google_firestore_v1beta1_DocumentMask_fields),
+ PB_FIELD( 3, BYTES , SINGULAR, CALLBACK, OTHER, google_firestore_v1beta1_GetDocumentRequest, transaction, mask, 0),
+ PB_FIELD( 5, MESSAGE , SINGULAR, STATIC , OTHER, google_firestore_v1beta1_GetDocumentRequest, read_time, transaction, &google_protobuf_Timestamp_fields),
+ PB_LAST_FIELD
+};
+
+const pb_field_t google_firestore_v1beta1_ListDocumentsRequest_fields[10] = {
+ PB_FIELD( 1, STRING , SINGULAR, CALLBACK, FIRST, google_firestore_v1beta1_ListDocumentsRequest, parent, parent, 0),
+ PB_FIELD( 2, STRING , SINGULAR, CALLBACK, OTHER, google_firestore_v1beta1_ListDocumentsRequest, collection_id, parent, 0),
+ PB_FIELD( 3, INT32 , SINGULAR, STATIC , OTHER, google_firestore_v1beta1_ListDocumentsRequest, page_size, collection_id, 0),
+ PB_FIELD( 4, STRING , SINGULAR, CALLBACK, OTHER, google_firestore_v1beta1_ListDocumentsRequest, page_token, page_size, 0),
+ PB_FIELD( 6, STRING , SINGULAR, CALLBACK, OTHER, google_firestore_v1beta1_ListDocumentsRequest, order_by, page_token, 0),
+ PB_FIELD( 7, MESSAGE , SINGULAR, STATIC , OTHER, google_firestore_v1beta1_ListDocumentsRequest, mask, order_by, &google_firestore_v1beta1_DocumentMask_fields),
+ PB_FIELD( 8, BYTES , SINGULAR, CALLBACK, OTHER, google_firestore_v1beta1_ListDocumentsRequest, transaction, mask, 0),
+ PB_FIELD( 10, MESSAGE , SINGULAR, STATIC , OTHER, google_firestore_v1beta1_ListDocumentsRequest, read_time, transaction, &google_protobuf_Timestamp_fields),
+ PB_FIELD( 12, BOOL , SINGULAR, STATIC , OTHER, google_firestore_v1beta1_ListDocumentsRequest, show_missing, read_time, 0),
+ PB_LAST_FIELD
+};
+
+const pb_field_t google_firestore_v1beta1_ListDocumentsResponse_fields[3] = {
+ PB_FIELD( 1, MESSAGE , REPEATED, CALLBACK, FIRST, google_firestore_v1beta1_ListDocumentsResponse, documents, documents, &google_firestore_v1beta1_Document_fields),
+ PB_FIELD( 2, STRING , SINGULAR, CALLBACK, OTHER, google_firestore_v1beta1_ListDocumentsResponse, next_page_token, documents, 0),
+ PB_LAST_FIELD
+};
+
+const pb_field_t google_firestore_v1beta1_CreateDocumentRequest_fields[6] = {
+ PB_FIELD( 1, STRING , SINGULAR, CALLBACK, FIRST, google_firestore_v1beta1_CreateDocumentRequest, parent, parent, 0),
+ PB_FIELD( 2, STRING , SINGULAR, CALLBACK, OTHER, google_firestore_v1beta1_CreateDocumentRequest, collection_id, parent, 0),
+ PB_FIELD( 3, STRING , SINGULAR, CALLBACK, OTHER, google_firestore_v1beta1_CreateDocumentRequest, document_id, collection_id, 0),
+ PB_FIELD( 4, MESSAGE , SINGULAR, STATIC , OTHER, google_firestore_v1beta1_CreateDocumentRequest, document, document_id, &google_firestore_v1beta1_Document_fields),
+ PB_FIELD( 5, MESSAGE , SINGULAR, STATIC , OTHER, google_firestore_v1beta1_CreateDocumentRequest, mask, document, &google_firestore_v1beta1_DocumentMask_fields),
+ PB_LAST_FIELD
+};
+
+const pb_field_t google_firestore_v1beta1_UpdateDocumentRequest_fields[5] = {
+ PB_FIELD( 1, MESSAGE , SINGULAR, STATIC , FIRST, google_firestore_v1beta1_UpdateDocumentRequest, document, document, &google_firestore_v1beta1_Document_fields),
+ PB_FIELD( 2, MESSAGE , SINGULAR, STATIC , OTHER, google_firestore_v1beta1_UpdateDocumentRequest, update_mask, document, &google_firestore_v1beta1_DocumentMask_fields),
+ PB_FIELD( 3, MESSAGE , SINGULAR, STATIC , OTHER, google_firestore_v1beta1_UpdateDocumentRequest, mask, update_mask, &google_firestore_v1beta1_DocumentMask_fields),
+ PB_FIELD( 4, MESSAGE , SINGULAR, STATIC , OTHER, google_firestore_v1beta1_UpdateDocumentRequest, current_document, mask, &google_firestore_v1beta1_Precondition_fields),
+ PB_LAST_FIELD
+};
+
+const pb_field_t google_firestore_v1beta1_DeleteDocumentRequest_fields[3] = {
+ PB_FIELD( 1, STRING , SINGULAR, CALLBACK, FIRST, google_firestore_v1beta1_DeleteDocumentRequest, name, name, 0),
+ PB_FIELD( 2, MESSAGE , SINGULAR, STATIC , OTHER, google_firestore_v1beta1_DeleteDocumentRequest, current_document, name, &google_firestore_v1beta1_Precondition_fields),
+ PB_LAST_FIELD
+};
+
+const pb_field_t google_firestore_v1beta1_BatchGetDocumentsRequest_fields[7] = {
+ PB_FIELD( 1, STRING , SINGULAR, CALLBACK, FIRST, google_firestore_v1beta1_BatchGetDocumentsRequest, database, database, 0),
+ PB_FIELD( 2, STRING , REPEATED, CALLBACK, OTHER, google_firestore_v1beta1_BatchGetDocumentsRequest, documents, database, 0),
+ PB_FIELD( 3, MESSAGE , SINGULAR, STATIC , OTHER, google_firestore_v1beta1_BatchGetDocumentsRequest, mask, documents, &google_firestore_v1beta1_DocumentMask_fields),
+ PB_FIELD( 4, BYTES , SINGULAR, CALLBACK, OTHER, google_firestore_v1beta1_BatchGetDocumentsRequest, transaction, mask, 0),
+ PB_FIELD( 5, MESSAGE , SINGULAR, STATIC , OTHER, google_firestore_v1beta1_BatchGetDocumentsRequest, new_transaction, transaction, &google_firestore_v1beta1_TransactionOptions_fields),
+ PB_FIELD( 7, MESSAGE , SINGULAR, STATIC , OTHER, google_firestore_v1beta1_BatchGetDocumentsRequest, read_time, new_transaction, &google_protobuf_Timestamp_fields),
+ PB_LAST_FIELD
+};
+
+const pb_field_t google_firestore_v1beta1_BatchGetDocumentsResponse_fields[5] = {
+ PB_FIELD( 1, MESSAGE , SINGULAR, STATIC , FIRST, google_firestore_v1beta1_BatchGetDocumentsResponse, found, found, &google_firestore_v1beta1_Document_fields),
+ PB_FIELD( 2, STRING , SINGULAR, CALLBACK, OTHER, google_firestore_v1beta1_BatchGetDocumentsResponse, missing, found, 0),
+ PB_FIELD( 3, BYTES , SINGULAR, CALLBACK, OTHER, google_firestore_v1beta1_BatchGetDocumentsResponse, transaction, missing, 0),
+ PB_FIELD( 4, MESSAGE , SINGULAR, STATIC , OTHER, google_firestore_v1beta1_BatchGetDocumentsResponse, read_time, transaction, &google_protobuf_Timestamp_fields),
+ PB_LAST_FIELD
+};
+
+const pb_field_t google_firestore_v1beta1_BeginTransactionRequest_fields[3] = {
+ PB_FIELD( 1, STRING , SINGULAR, CALLBACK, FIRST, google_firestore_v1beta1_BeginTransactionRequest, database, database, 0),
+ PB_FIELD( 2, MESSAGE , SINGULAR, STATIC , OTHER, google_firestore_v1beta1_BeginTransactionRequest, options, database, &google_firestore_v1beta1_TransactionOptions_fields),
+ PB_LAST_FIELD
+};
+
+const pb_field_t google_firestore_v1beta1_BeginTransactionResponse_fields[2] = {
+ PB_FIELD( 1, BYTES , SINGULAR, CALLBACK, FIRST, google_firestore_v1beta1_BeginTransactionResponse, transaction, transaction, 0),
+ PB_LAST_FIELD
+};
+
+const pb_field_t google_firestore_v1beta1_CommitRequest_fields[4] = {
+ PB_FIELD( 1, STRING , SINGULAR, CALLBACK, FIRST, google_firestore_v1beta1_CommitRequest, database, database, 0),
+ PB_FIELD( 2, MESSAGE , REPEATED, CALLBACK, OTHER, google_firestore_v1beta1_CommitRequest, writes, database, &google_firestore_v1beta1_Write_fields),
+ PB_FIELD( 3, BYTES , SINGULAR, CALLBACK, OTHER, google_firestore_v1beta1_CommitRequest, transaction, writes, 0),
+ PB_LAST_FIELD
+};
+
+const pb_field_t google_firestore_v1beta1_CommitResponse_fields[3] = {
+ PB_FIELD( 1, MESSAGE , REPEATED, CALLBACK, FIRST, google_firestore_v1beta1_CommitResponse, write_results, write_results, &google_firestore_v1beta1_WriteResult_fields),
+ PB_FIELD( 2, MESSAGE , SINGULAR, STATIC , OTHER, google_firestore_v1beta1_CommitResponse, commit_time, write_results, &google_protobuf_Timestamp_fields),
+ PB_LAST_FIELD
+};
+
+const pb_field_t google_firestore_v1beta1_RollbackRequest_fields[3] = {
+ PB_FIELD( 1, STRING , SINGULAR, CALLBACK, FIRST, google_firestore_v1beta1_RollbackRequest, database, database, 0),
+ PB_FIELD( 2, BYTES , SINGULAR, CALLBACK, OTHER, google_firestore_v1beta1_RollbackRequest, transaction, database, 0),
+ PB_LAST_FIELD
+};
+
+const pb_field_t google_firestore_v1beta1_RunQueryRequest_fields[6] = {
+ PB_FIELD( 1, STRING , SINGULAR, CALLBACK, FIRST, google_firestore_v1beta1_RunQueryRequest, parent, parent, 0),
+ PB_ONEOF_FIELD(query_type, 2, MESSAGE , ONEOF, STATIC , OTHER, google_firestore_v1beta1_RunQueryRequest, structured_query, parent, &google_firestore_v1beta1_StructuredQuery_fields),
+ PB_FIELD( 5, BYTES , SINGULAR, CALLBACK, OTHER, google_firestore_v1beta1_RunQueryRequest, transaction, query_type.structured_query, 0),
+ PB_FIELD( 6, MESSAGE , SINGULAR, STATIC , OTHER, google_firestore_v1beta1_RunQueryRequest, new_transaction, transaction, &google_firestore_v1beta1_TransactionOptions_fields),
+ PB_FIELD( 7, MESSAGE , SINGULAR, STATIC , OTHER, google_firestore_v1beta1_RunQueryRequest, read_time, new_transaction, &google_protobuf_Timestamp_fields),
+ PB_LAST_FIELD
+};
+
+const pb_field_t google_firestore_v1beta1_RunQueryResponse_fields[5] = {
+ PB_FIELD( 1, MESSAGE , SINGULAR, STATIC , FIRST, google_firestore_v1beta1_RunQueryResponse, document, document, &google_firestore_v1beta1_Document_fields),
+ PB_FIELD( 2, BYTES , SINGULAR, CALLBACK, OTHER, google_firestore_v1beta1_RunQueryResponse, transaction, document, 0),
+ PB_FIELD( 3, MESSAGE , SINGULAR, STATIC , OTHER, google_firestore_v1beta1_RunQueryResponse, read_time, transaction, &google_protobuf_Timestamp_fields),
+ PB_FIELD( 4, INT32 , SINGULAR, STATIC , OTHER, google_firestore_v1beta1_RunQueryResponse, skipped_results, read_time, 0),
+ PB_LAST_FIELD
+};
+
+const pb_field_t google_firestore_v1beta1_WriteRequest_fields[6] = {
+ PB_FIELD( 1, STRING , SINGULAR, CALLBACK, FIRST, google_firestore_v1beta1_WriteRequest, database, database, 0),
+ PB_FIELD( 2, STRING , SINGULAR, CALLBACK, OTHER, google_firestore_v1beta1_WriteRequest, stream_id, database, 0),
+ PB_FIELD( 3, MESSAGE , REPEATED, CALLBACK, OTHER, google_firestore_v1beta1_WriteRequest, writes, stream_id, &google_firestore_v1beta1_Write_fields),
+ PB_FIELD( 4, BYTES , SINGULAR, CALLBACK, OTHER, google_firestore_v1beta1_WriteRequest, stream_token, writes, 0),
+ PB_FIELD( 5, MESSAGE , REPEATED, CALLBACK, OTHER, google_firestore_v1beta1_WriteRequest, labels, stream_token, &google_firestore_v1beta1_WriteRequest_LabelsEntry_fields),
+ PB_LAST_FIELD
+};
+
+const pb_field_t google_firestore_v1beta1_WriteRequest_LabelsEntry_fields[3] = {
+ PB_FIELD( 1, STRING , SINGULAR, CALLBACK, FIRST, google_firestore_v1beta1_WriteRequest_LabelsEntry, key, key, 0),
+ PB_FIELD( 2, STRING , SINGULAR, CALLBACK, OTHER, google_firestore_v1beta1_WriteRequest_LabelsEntry, value, key, 0),
+ PB_LAST_FIELD
+};
+
+const pb_field_t google_firestore_v1beta1_WriteResponse_fields[5] = {
+ PB_FIELD( 1, STRING , SINGULAR, CALLBACK, FIRST, google_firestore_v1beta1_WriteResponse, stream_id, stream_id, 0),
+ PB_FIELD( 2, BYTES , SINGULAR, CALLBACK, OTHER, google_firestore_v1beta1_WriteResponse, stream_token, stream_id, 0),
+ PB_FIELD( 3, MESSAGE , REPEATED, CALLBACK, OTHER, google_firestore_v1beta1_WriteResponse, write_results, stream_token, &google_firestore_v1beta1_WriteResult_fields),
+ PB_FIELD( 4, MESSAGE , SINGULAR, STATIC , OTHER, google_firestore_v1beta1_WriteResponse, commit_time, write_results, &google_protobuf_Timestamp_fields),
+ PB_LAST_FIELD
+};
+
+const pb_field_t google_firestore_v1beta1_ListenRequest_fields[5] = {
+ PB_FIELD( 1, STRING , SINGULAR, CALLBACK, FIRST, google_firestore_v1beta1_ListenRequest, database, database, 0),
+ PB_ONEOF_FIELD(target_change, 2, MESSAGE , ONEOF, STATIC , OTHER, google_firestore_v1beta1_ListenRequest, add_target, database, &google_firestore_v1beta1_Target_fields),
+ PB_ONEOF_FIELD(target_change, 3, INT32 , ONEOF, STATIC , UNION, google_firestore_v1beta1_ListenRequest, remove_target, database, 0),
+ PB_FIELD( 4, MESSAGE , REPEATED, CALLBACK, OTHER, google_firestore_v1beta1_ListenRequest, labels, target_change.remove_target, &google_firestore_v1beta1_ListenRequest_LabelsEntry_fields),
+ PB_LAST_FIELD
+};
+
+const pb_field_t google_firestore_v1beta1_ListenRequest_LabelsEntry_fields[3] = {
+ PB_FIELD( 1, STRING , SINGULAR, CALLBACK, FIRST, google_firestore_v1beta1_ListenRequest_LabelsEntry, key, key, 0),
+ PB_FIELD( 2, STRING , SINGULAR, CALLBACK, OTHER, google_firestore_v1beta1_ListenRequest_LabelsEntry, value, key, 0),
+ PB_LAST_FIELD
+};
+
+const pb_field_t google_firestore_v1beta1_ListenResponse_fields[6] = {
+ PB_ONEOF_FIELD(response_type, 2, MESSAGE , ONEOF, STATIC , FIRST, google_firestore_v1beta1_ListenResponse, target_change, target_change, &google_firestore_v1beta1_TargetChange_fields),
+ PB_ONEOF_FIELD(response_type, 3, MESSAGE , ONEOF, STATIC , UNION, google_firestore_v1beta1_ListenResponse, document_change, document_change, &google_firestore_v1beta1_DocumentChange_fields),
+ PB_ONEOF_FIELD(response_type, 4, MESSAGE , ONEOF, STATIC , UNION, google_firestore_v1beta1_ListenResponse, document_delete, document_delete, &google_firestore_v1beta1_DocumentDelete_fields),
+ PB_ONEOF_FIELD(response_type, 5, MESSAGE , ONEOF, STATIC , UNION, google_firestore_v1beta1_ListenResponse, filter, filter, &google_firestore_v1beta1_ExistenceFilter_fields),
+ PB_ONEOF_FIELD(response_type, 6, MESSAGE , ONEOF, STATIC , UNION, google_firestore_v1beta1_ListenResponse, document_remove, document_remove, &google_firestore_v1beta1_DocumentRemove_fields),
+ PB_LAST_FIELD
+};
+
+const pb_field_t google_firestore_v1beta1_Target_fields[7] = {
+ PB_ONEOF_FIELD(target_type, 2, MESSAGE , ONEOF, STATIC , FIRST, google_firestore_v1beta1_Target, query, query, &google_firestore_v1beta1_Target_QueryTarget_fields),
+ PB_ONEOF_FIELD(target_type, 3, MESSAGE , ONEOF, STATIC , UNION, google_firestore_v1beta1_Target, documents, documents, &google_firestore_v1beta1_Target_DocumentsTarget_fields),
+ PB_FIELD( 4, BYTES , SINGULAR, CALLBACK, OTHER, google_firestore_v1beta1_Target, resume_token, target_type.documents, 0),
+ PB_FIELD( 5, INT32 , SINGULAR, STATIC , OTHER, google_firestore_v1beta1_Target, target_id, resume_token, 0),
+ PB_FIELD( 6, BOOL , SINGULAR, STATIC , OTHER, google_firestore_v1beta1_Target, once, target_id, 0),
+ PB_FIELD( 11, MESSAGE , SINGULAR, STATIC , OTHER, google_firestore_v1beta1_Target, read_time, once, &google_protobuf_Timestamp_fields),
+ PB_LAST_FIELD
+};
+
+const pb_field_t google_firestore_v1beta1_Target_DocumentsTarget_fields[2] = {
+ PB_FIELD( 2, STRING , REPEATED, CALLBACK, FIRST, google_firestore_v1beta1_Target_DocumentsTarget, documents, documents, 0),
+ PB_LAST_FIELD
+};
+
+const pb_field_t google_firestore_v1beta1_Target_QueryTarget_fields[3] = {
+ PB_FIELD( 1, STRING , SINGULAR, CALLBACK, FIRST, google_firestore_v1beta1_Target_QueryTarget, parent, parent, 0),
+ PB_ONEOF_FIELD(query_type, 2, MESSAGE , ONEOF, STATIC , OTHER, google_firestore_v1beta1_Target_QueryTarget, structured_query, parent, &google_firestore_v1beta1_StructuredQuery_fields),
+ PB_LAST_FIELD
+};
+
+const pb_field_t google_firestore_v1beta1_TargetChange_fields[6] = {
+ PB_FIELD( 1, UENUM , SINGULAR, STATIC , FIRST, google_firestore_v1beta1_TargetChange, target_change_type, target_change_type, 0),
+ PB_FIELD( 2, INT32 , REPEATED, CALLBACK, OTHER, google_firestore_v1beta1_TargetChange, target_ids, target_change_type, 0),
+ PB_FIELD( 3, MESSAGE , SINGULAR, STATIC , OTHER, google_firestore_v1beta1_TargetChange, cause, target_ids, &google_rpc_Status_fields),
+ PB_FIELD( 4, BYTES , SINGULAR, CALLBACK, OTHER, google_firestore_v1beta1_TargetChange, resume_token, cause, 0),
+ PB_FIELD( 6, MESSAGE , SINGULAR, STATIC , OTHER, google_firestore_v1beta1_TargetChange, read_time, resume_token, &google_protobuf_Timestamp_fields),
+ PB_LAST_FIELD
+};
+
+const pb_field_t google_firestore_v1beta1_ListCollectionIdsRequest_fields[4] = {
+ PB_FIELD( 1, STRING , SINGULAR, CALLBACK, FIRST, google_firestore_v1beta1_ListCollectionIdsRequest, parent, parent, 0),
+ PB_FIELD( 2, INT32 , SINGULAR, STATIC , OTHER, google_firestore_v1beta1_ListCollectionIdsRequest, page_size, parent, 0),
+ PB_FIELD( 3, STRING , SINGULAR, CALLBACK, OTHER, google_firestore_v1beta1_ListCollectionIdsRequest, page_token, page_size, 0),
+ PB_LAST_FIELD
+};
+
+const pb_field_t google_firestore_v1beta1_ListCollectionIdsResponse_fields[3] = {
+ PB_FIELD( 1, STRING , REPEATED, CALLBACK, FIRST, google_firestore_v1beta1_ListCollectionIdsResponse, collection_ids, collection_ids, 0),
+ PB_FIELD( 2, STRING , SINGULAR, CALLBACK, OTHER, google_firestore_v1beta1_ListCollectionIdsResponse, next_page_token, collection_ids, 0),
+ PB_LAST_FIELD
+};
+
+
+
+/* Check that field information fits in pb_field_t */
+#if !defined(PB_FIELD_32BIT)
+/* If you get an error here, it means that you need to define PB_FIELD_32BIT
+ * compile-time option. You can do that in pb.h or on compiler command line.
+ *
+ * The reason you need to do this is that some of your messages contain tag
+ * numbers or field sizes that are larger than what can fit in 8 or 16 bit
+ * field descriptors.
+ */
+PB_STATIC_ASSERT((pb_membersize(google_firestore_v1beta1_GetDocumentRequest, mask) < 65536 && pb_membersize(google_firestore_v1beta1_GetDocumentRequest, read_time) < 65536 && pb_membersize(google_firestore_v1beta1_ListDocumentsRequest, mask) < 65536 && pb_membersize(google_firestore_v1beta1_ListDocumentsRequest, read_time) < 65536 && pb_membersize(google_firestore_v1beta1_CreateDocumentRequest, document) < 65536 && pb_membersize(google_firestore_v1beta1_CreateDocumentRequest, mask) < 65536 && pb_membersize(google_firestore_v1beta1_UpdateDocumentRequest, document) < 65536 && pb_membersize(google_firestore_v1beta1_UpdateDocumentRequest, update_mask) < 65536 && pb_membersize(google_firestore_v1beta1_UpdateDocumentRequest, mask) < 65536 && pb_membersize(google_firestore_v1beta1_UpdateDocumentRequest, current_document) < 65536 && pb_membersize(google_firestore_v1beta1_DeleteDocumentRequest, current_document) < 65536 && pb_membersize(google_firestore_v1beta1_BatchGetDocumentsRequest, mask) < 65536 && pb_membersize(google_firestore_v1beta1_BatchGetDocumentsRequest, new_transaction) < 65536 && pb_membersize(google_firestore_v1beta1_BatchGetDocumentsRequest, read_time) < 65536 && pb_membersize(google_firestore_v1beta1_BatchGetDocumentsResponse, found) < 65536 && pb_membersize(google_firestore_v1beta1_BatchGetDocumentsResponse, read_time) < 65536 && pb_membersize(google_firestore_v1beta1_BeginTransactionRequest, options) < 65536 && pb_membersize(google_firestore_v1beta1_CommitResponse, commit_time) < 65536 && pb_membersize(google_firestore_v1beta1_RunQueryRequest, query_type.structured_query) < 65536 && pb_membersize(google_firestore_v1beta1_RunQueryRequest, new_transaction) < 65536 && pb_membersize(google_firestore_v1beta1_RunQueryRequest, read_time) < 65536 && pb_membersize(google_firestore_v1beta1_RunQueryResponse, document) < 65536 && pb_membersize(google_firestore_v1beta1_RunQueryResponse, read_time) < 65536 && pb_membersize(google_firestore_v1beta1_WriteResponse, commit_time) < 65536 && pb_membersize(google_firestore_v1beta1_ListenRequest, target_change.add_target) < 65536 && pb_membersize(google_firestore_v1beta1_ListenResponse, response_type.target_change) < 65536 && pb_membersize(google_firestore_v1beta1_ListenResponse, response_type.document_change) < 65536 && pb_membersize(google_firestore_v1beta1_ListenResponse, response_type.document_delete) < 65536 && pb_membersize(google_firestore_v1beta1_ListenResponse, response_type.filter) < 65536 && pb_membersize(google_firestore_v1beta1_ListenResponse, response_type.document_remove) < 65536 && pb_membersize(google_firestore_v1beta1_Target, target_type.query) < 65536 && pb_membersize(google_firestore_v1beta1_Target, target_type.documents) < 65536 && pb_membersize(google_firestore_v1beta1_Target, read_time) < 65536 && pb_membersize(google_firestore_v1beta1_Target_QueryTarget, query_type.structured_query) < 65536 && pb_membersize(google_firestore_v1beta1_TargetChange, cause) < 65536 && pb_membersize(google_firestore_v1beta1_TargetChange, read_time) < 65536), YOU_MUST_DEFINE_PB_FIELD_32BIT_FOR_MESSAGES_google_firestore_v1beta1_GetDocumentRequest_google_firestore_v1beta1_ListDocumentsRequest_google_firestore_v1beta1_ListDocumentsResponse_google_firestore_v1beta1_CreateDocumentRequest_google_firestore_v1beta1_UpdateDocumentRequest_google_firestore_v1beta1_DeleteDocumentRequest_google_firestore_v1beta1_BatchGetDocumentsRequest_google_firestore_v1beta1_BatchGetDocumentsResponse_google_firestore_v1beta1_BeginTransactionRequest_google_firestore_v1beta1_BeginTransactionResponse_google_firestore_v1beta1_CommitRequest_google_firestore_v1beta1_CommitResponse_google_firestore_v1beta1_RollbackRequest_google_firestore_v1beta1_RunQueryRequest_google_firestore_v1beta1_RunQueryResponse_google_firestore_v1beta1_WriteRequest_google_firestore_v1beta1_WriteRequest_LabelsEntry_google_firestore_v1beta1_WriteResponse_google_firestore_v1beta1_ListenRequest_google_firestore_v1beta1_ListenRequest_LabelsEntry_google_firestore_v1beta1_ListenResponse_google_firestore_v1beta1_Target_google_firestore_v1beta1_Target_DocumentsTarget_google_firestore_v1beta1_Target_QueryTarget_google_firestore_v1beta1_TargetChange_google_firestore_v1beta1_ListCollectionIdsRequest_google_firestore_v1beta1_ListCollectionIdsResponse)
+#endif
+
+#if !defined(PB_FIELD_16BIT) && !defined(PB_FIELD_32BIT)
+/* If you get an error here, it means that you need to define PB_FIELD_16BIT
+ * compile-time option. You can do that in pb.h or on compiler command line.
+ *
+ * The reason you need to do this is that some of your messages contain tag
+ * numbers or field sizes that are larger than what can fit in the default
+ * 8 bit descriptors.
+ */
+PB_STATIC_ASSERT((pb_membersize(google_firestore_v1beta1_GetDocumentRequest, mask) < 256 && pb_membersize(google_firestore_v1beta1_GetDocumentRequest, read_time) < 256 && pb_membersize(google_firestore_v1beta1_ListDocumentsRequest, mask) < 256 && pb_membersize(google_firestore_v1beta1_ListDocumentsRequest, read_time) < 256 && pb_membersize(google_firestore_v1beta1_CreateDocumentRequest, document) < 256 && pb_membersize(google_firestore_v1beta1_CreateDocumentRequest, mask) < 256 && pb_membersize(google_firestore_v1beta1_UpdateDocumentRequest, document) < 256 && pb_membersize(google_firestore_v1beta1_UpdateDocumentRequest, update_mask) < 256 && pb_membersize(google_firestore_v1beta1_UpdateDocumentRequest, mask) < 256 && pb_membersize(google_firestore_v1beta1_UpdateDocumentRequest, current_document) < 256 && pb_membersize(google_firestore_v1beta1_DeleteDocumentRequest, current_document) < 256 && pb_membersize(google_firestore_v1beta1_BatchGetDocumentsRequest, mask) < 256 && pb_membersize(google_firestore_v1beta1_BatchGetDocumentsRequest, new_transaction) < 256 && pb_membersize(google_firestore_v1beta1_BatchGetDocumentsRequest, read_time) < 256 && pb_membersize(google_firestore_v1beta1_BatchGetDocumentsResponse, found) < 256 && pb_membersize(google_firestore_v1beta1_BatchGetDocumentsResponse, read_time) < 256 && pb_membersize(google_firestore_v1beta1_BeginTransactionRequest, options) < 256 && pb_membersize(google_firestore_v1beta1_CommitResponse, commit_time) < 256 && pb_membersize(google_firestore_v1beta1_RunQueryRequest, query_type.structured_query) < 256 && pb_membersize(google_firestore_v1beta1_RunQueryRequest, new_transaction) < 256 && pb_membersize(google_firestore_v1beta1_RunQueryRequest, read_time) < 256 && pb_membersize(google_firestore_v1beta1_RunQueryResponse, document) < 256 && pb_membersize(google_firestore_v1beta1_RunQueryResponse, read_time) < 256 && pb_membersize(google_firestore_v1beta1_WriteResponse, commit_time) < 256 && pb_membersize(google_firestore_v1beta1_ListenRequest, target_change.add_target) < 256 && pb_membersize(google_firestore_v1beta1_ListenResponse, response_type.target_change) < 256 && pb_membersize(google_firestore_v1beta1_ListenResponse, response_type.document_change) < 256 && pb_membersize(google_firestore_v1beta1_ListenResponse, response_type.document_delete) < 256 && pb_membersize(google_firestore_v1beta1_ListenResponse, response_type.filter) < 256 && pb_membersize(google_firestore_v1beta1_ListenResponse, response_type.document_remove) < 256 && pb_membersize(google_firestore_v1beta1_Target, target_type.query) < 256 && pb_membersize(google_firestore_v1beta1_Target, target_type.documents) < 256 && pb_membersize(google_firestore_v1beta1_Target, read_time) < 256 && pb_membersize(google_firestore_v1beta1_Target_QueryTarget, query_type.structured_query) < 256 && pb_membersize(google_firestore_v1beta1_TargetChange, cause) < 256 && pb_membersize(google_firestore_v1beta1_TargetChange, read_time) < 256), YOU_MUST_DEFINE_PB_FIELD_16BIT_FOR_MESSAGES_google_firestore_v1beta1_GetDocumentRequest_google_firestore_v1beta1_ListDocumentsRequest_google_firestore_v1beta1_ListDocumentsResponse_google_firestore_v1beta1_CreateDocumentRequest_google_firestore_v1beta1_UpdateDocumentRequest_google_firestore_v1beta1_DeleteDocumentRequest_google_firestore_v1beta1_BatchGetDocumentsRequest_google_firestore_v1beta1_BatchGetDocumentsResponse_google_firestore_v1beta1_BeginTransactionRequest_google_firestore_v1beta1_BeginTransactionResponse_google_firestore_v1beta1_CommitRequest_google_firestore_v1beta1_CommitResponse_google_firestore_v1beta1_RollbackRequest_google_firestore_v1beta1_RunQueryRequest_google_firestore_v1beta1_RunQueryResponse_google_firestore_v1beta1_WriteRequest_google_firestore_v1beta1_WriteRequest_LabelsEntry_google_firestore_v1beta1_WriteResponse_google_firestore_v1beta1_ListenRequest_google_firestore_v1beta1_ListenRequest_LabelsEntry_google_firestore_v1beta1_ListenResponse_google_firestore_v1beta1_Target_google_firestore_v1beta1_Target_DocumentsTarget_google_firestore_v1beta1_Target_QueryTarget_google_firestore_v1beta1_TargetChange_google_firestore_v1beta1_ListCollectionIdsRequest_google_firestore_v1beta1_ListCollectionIdsResponse)
+#endif
+
+
+/* @@protoc_insertion_point(eof) */
diff --git a/Firestore/Protos/nanopb/google/firestore/v1beta1/firestore.pb.h b/Firestore/Protos/nanopb/google/firestore/v1beta1/firestore.pb.h
new file mode 100644
index 0000000..5bfbcf8
--- /dev/null
+++ b/Firestore/Protos/nanopb/google/firestore/v1beta1/firestore.pb.h
@@ -0,0 +1,508 @@
+/*
+ * Copyright 2018 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.
+ */
+
+/* Automatically generated nanopb header */
+/* Generated by nanopb-0.3.8 at Fri Feb 2 17:48:02 2018. */
+
+#ifndef PB_GOOGLE_FIRESTORE_V1BETA1_FIRESTORE_PB_H_INCLUDED
+#define PB_GOOGLE_FIRESTORE_V1BETA1_FIRESTORE_PB_H_INCLUDED
+#include <pb.h>
+
+#include "google/api/annotations.pb.h"
+
+#include "google/firestore/v1beta1/common.pb.h"
+
+#include "google/firestore/v1beta1/document.pb.h"
+
+#include "google/firestore/v1beta1/query.pb.h"
+
+#include "google/firestore/v1beta1/write.pb.h"
+
+#include "google/protobuf/empty.pb.h"
+
+#include "google/protobuf/timestamp.pb.h"
+
+#include "google/rpc/status.pb.h"
+
+/* @@protoc_insertion_point(includes) */
+#if PB_PROTO_HEADER_VERSION != 30
+#error Regenerate this file with the current version of nanopb generator.
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Enum definitions */
+typedef enum _google_firestore_v1beta1_TargetChange_TargetChangeType {
+ google_firestore_v1beta1_TargetChange_TargetChangeType_NO_CHANGE = 0,
+ google_firestore_v1beta1_TargetChange_TargetChangeType_ADD = 1,
+ google_firestore_v1beta1_TargetChange_TargetChangeType_REMOVE = 2,
+ google_firestore_v1beta1_TargetChange_TargetChangeType_CURRENT = 3,
+ google_firestore_v1beta1_TargetChange_TargetChangeType_RESET = 4
+} google_firestore_v1beta1_TargetChange_TargetChangeType;
+#define _google_firestore_v1beta1_TargetChange_TargetChangeType_MIN google_firestore_v1beta1_TargetChange_TargetChangeType_NO_CHANGE
+#define _google_firestore_v1beta1_TargetChange_TargetChangeType_MAX google_firestore_v1beta1_TargetChange_TargetChangeType_RESET
+#define _google_firestore_v1beta1_TargetChange_TargetChangeType_ARRAYSIZE ((google_firestore_v1beta1_TargetChange_TargetChangeType)(google_firestore_v1beta1_TargetChange_TargetChangeType_RESET+1))
+
+/* Struct definitions */
+typedef struct _google_firestore_v1beta1_BeginTransactionResponse {
+ pb_callback_t transaction;
+/* @@protoc_insertion_point(struct:google_firestore_v1beta1_BeginTransactionResponse) */
+} google_firestore_v1beta1_BeginTransactionResponse;
+
+typedef struct _google_firestore_v1beta1_CommitRequest {
+ pb_callback_t database;
+ pb_callback_t writes;
+ pb_callback_t transaction;
+/* @@protoc_insertion_point(struct:google_firestore_v1beta1_CommitRequest) */
+} google_firestore_v1beta1_CommitRequest;
+
+typedef struct _google_firestore_v1beta1_ListCollectionIdsResponse {
+ pb_callback_t collection_ids;
+ pb_callback_t next_page_token;
+/* @@protoc_insertion_point(struct:google_firestore_v1beta1_ListCollectionIdsResponse) */
+} google_firestore_v1beta1_ListCollectionIdsResponse;
+
+typedef struct _google_firestore_v1beta1_ListDocumentsResponse {
+ pb_callback_t documents;
+ pb_callback_t next_page_token;
+/* @@protoc_insertion_point(struct:google_firestore_v1beta1_ListDocumentsResponse) */
+} google_firestore_v1beta1_ListDocumentsResponse;
+
+typedef struct _google_firestore_v1beta1_ListenRequest_LabelsEntry {
+ pb_callback_t key;
+ pb_callback_t value;
+/* @@protoc_insertion_point(struct:google_firestore_v1beta1_ListenRequest_LabelsEntry) */
+} google_firestore_v1beta1_ListenRequest_LabelsEntry;
+
+typedef struct _google_firestore_v1beta1_RollbackRequest {
+ pb_callback_t database;
+ pb_callback_t transaction;
+/* @@protoc_insertion_point(struct:google_firestore_v1beta1_RollbackRequest) */
+} google_firestore_v1beta1_RollbackRequest;
+
+typedef struct _google_firestore_v1beta1_Target_DocumentsTarget {
+ pb_callback_t documents;
+/* @@protoc_insertion_point(struct:google_firestore_v1beta1_Target_DocumentsTarget) */
+} google_firestore_v1beta1_Target_DocumentsTarget;
+
+typedef struct _google_firestore_v1beta1_WriteRequest {
+ pb_callback_t database;
+ pb_callback_t stream_id;
+ pb_callback_t writes;
+ pb_callback_t stream_token;
+ pb_callback_t labels;
+/* @@protoc_insertion_point(struct:google_firestore_v1beta1_WriteRequest) */
+} google_firestore_v1beta1_WriteRequest;
+
+typedef struct _google_firestore_v1beta1_WriteRequest_LabelsEntry {
+ pb_callback_t key;
+ pb_callback_t value;
+/* @@protoc_insertion_point(struct:google_firestore_v1beta1_WriteRequest_LabelsEntry) */
+} google_firestore_v1beta1_WriteRequest_LabelsEntry;
+
+typedef struct _google_firestore_v1beta1_BatchGetDocumentsRequest {
+ pb_callback_t database;
+ pb_callback_t documents;
+ google_firestore_v1beta1_DocumentMask mask;
+ pb_callback_t transaction;
+ google_firestore_v1beta1_TransactionOptions new_transaction;
+ google_protobuf_Timestamp read_time;
+/* @@protoc_insertion_point(struct:google_firestore_v1beta1_BatchGetDocumentsRequest) */
+} google_firestore_v1beta1_BatchGetDocumentsRequest;
+
+typedef struct _google_firestore_v1beta1_BatchGetDocumentsResponse {
+ google_firestore_v1beta1_Document found;
+ pb_callback_t missing;
+ pb_callback_t transaction;
+ google_protobuf_Timestamp read_time;
+/* @@protoc_insertion_point(struct:google_firestore_v1beta1_BatchGetDocumentsResponse) */
+} google_firestore_v1beta1_BatchGetDocumentsResponse;
+
+typedef struct _google_firestore_v1beta1_BeginTransactionRequest {
+ pb_callback_t database;
+ google_firestore_v1beta1_TransactionOptions options;
+/* @@protoc_insertion_point(struct:google_firestore_v1beta1_BeginTransactionRequest) */
+} google_firestore_v1beta1_BeginTransactionRequest;
+
+typedef struct _google_firestore_v1beta1_CommitResponse {
+ pb_callback_t write_results;
+ google_protobuf_Timestamp commit_time;
+/* @@protoc_insertion_point(struct:google_firestore_v1beta1_CommitResponse) */
+} google_firestore_v1beta1_CommitResponse;
+
+typedef struct _google_firestore_v1beta1_CreateDocumentRequest {
+ pb_callback_t parent;
+ pb_callback_t collection_id;
+ pb_callback_t document_id;
+ google_firestore_v1beta1_Document document;
+ google_firestore_v1beta1_DocumentMask mask;
+/* @@protoc_insertion_point(struct:google_firestore_v1beta1_CreateDocumentRequest) */
+} google_firestore_v1beta1_CreateDocumentRequest;
+
+typedef struct _google_firestore_v1beta1_DeleteDocumentRequest {
+ pb_callback_t name;
+ google_firestore_v1beta1_Precondition current_document;
+/* @@protoc_insertion_point(struct:google_firestore_v1beta1_DeleteDocumentRequest) */
+} google_firestore_v1beta1_DeleteDocumentRequest;
+
+typedef struct _google_firestore_v1beta1_GetDocumentRequest {
+ pb_callback_t name;
+ google_firestore_v1beta1_DocumentMask mask;
+ pb_callback_t transaction;
+ google_protobuf_Timestamp read_time;
+/* @@protoc_insertion_point(struct:google_firestore_v1beta1_GetDocumentRequest) */
+} google_firestore_v1beta1_GetDocumentRequest;
+
+typedef struct _google_firestore_v1beta1_ListCollectionIdsRequest {
+ pb_callback_t parent;
+ int32_t page_size;
+ pb_callback_t page_token;
+/* @@protoc_insertion_point(struct:google_firestore_v1beta1_ListCollectionIdsRequest) */
+} google_firestore_v1beta1_ListCollectionIdsRequest;
+
+typedef struct _google_firestore_v1beta1_ListDocumentsRequest {
+ pb_callback_t parent;
+ pb_callback_t collection_id;
+ int32_t page_size;
+ pb_callback_t page_token;
+ pb_callback_t order_by;
+ google_firestore_v1beta1_DocumentMask mask;
+ pb_callback_t transaction;
+ google_protobuf_Timestamp read_time;
+ bool show_missing;
+/* @@protoc_insertion_point(struct:google_firestore_v1beta1_ListDocumentsRequest) */
+} google_firestore_v1beta1_ListDocumentsRequest;
+
+typedef struct _google_firestore_v1beta1_RunQueryRequest {
+ pb_callback_t parent;
+ pb_size_t which_query_type;
+ union {
+ google_firestore_v1beta1_StructuredQuery structured_query;
+ } query_type;
+ pb_callback_t transaction;
+ google_firestore_v1beta1_TransactionOptions new_transaction;
+ google_protobuf_Timestamp read_time;
+/* @@protoc_insertion_point(struct:google_firestore_v1beta1_RunQueryRequest) */
+} google_firestore_v1beta1_RunQueryRequest;
+
+typedef struct _google_firestore_v1beta1_RunQueryResponse {
+ google_firestore_v1beta1_Document document;
+ pb_callback_t transaction;
+ google_protobuf_Timestamp read_time;
+ int32_t skipped_results;
+/* @@protoc_insertion_point(struct:google_firestore_v1beta1_RunQueryResponse) */
+} google_firestore_v1beta1_RunQueryResponse;
+
+typedef struct _google_firestore_v1beta1_TargetChange {
+ google_firestore_v1beta1_TargetChange_TargetChangeType target_change_type;
+ pb_callback_t target_ids;
+ google_rpc_Status cause;
+ pb_callback_t resume_token;
+ google_protobuf_Timestamp read_time;
+/* @@protoc_insertion_point(struct:google_firestore_v1beta1_TargetChange) */
+} google_firestore_v1beta1_TargetChange;
+
+typedef struct _google_firestore_v1beta1_Target_QueryTarget {
+ pb_callback_t parent;
+ pb_size_t which_query_type;
+ union {
+ google_firestore_v1beta1_StructuredQuery structured_query;
+ } query_type;
+/* @@protoc_insertion_point(struct:google_firestore_v1beta1_Target_QueryTarget) */
+} google_firestore_v1beta1_Target_QueryTarget;
+
+typedef struct _google_firestore_v1beta1_UpdateDocumentRequest {
+ google_firestore_v1beta1_Document document;
+ google_firestore_v1beta1_DocumentMask update_mask;
+ google_firestore_v1beta1_DocumentMask mask;
+ google_firestore_v1beta1_Precondition current_document;
+/* @@protoc_insertion_point(struct:google_firestore_v1beta1_UpdateDocumentRequest) */
+} google_firestore_v1beta1_UpdateDocumentRequest;
+
+typedef struct _google_firestore_v1beta1_WriteResponse {
+ pb_callback_t stream_id;
+ pb_callback_t stream_token;
+ pb_callback_t write_results;
+ google_protobuf_Timestamp commit_time;
+/* @@protoc_insertion_point(struct:google_firestore_v1beta1_WriteResponse) */
+} google_firestore_v1beta1_WriteResponse;
+
+typedef struct _google_firestore_v1beta1_ListenResponse {
+ pb_size_t which_response_type;
+ union {
+ google_firestore_v1beta1_TargetChange target_change;
+ google_firestore_v1beta1_DocumentChange document_change;
+ google_firestore_v1beta1_DocumentDelete document_delete;
+ google_firestore_v1beta1_ExistenceFilter filter;
+ google_firestore_v1beta1_DocumentRemove document_remove;
+ } response_type;
+/* @@protoc_insertion_point(struct:google_firestore_v1beta1_ListenResponse) */
+} google_firestore_v1beta1_ListenResponse;
+
+typedef struct _google_firestore_v1beta1_Target {
+ pb_size_t which_target_type;
+ union {
+ google_firestore_v1beta1_Target_QueryTarget query;
+ google_firestore_v1beta1_Target_DocumentsTarget documents;
+ } target_type;
+ pb_callback_t resume_token;
+ int32_t target_id;
+ bool once;
+ google_protobuf_Timestamp read_time;
+/* @@protoc_insertion_point(struct:google_firestore_v1beta1_Target) */
+} google_firestore_v1beta1_Target;
+
+typedef struct _google_firestore_v1beta1_ListenRequest {
+ pb_callback_t database;
+ pb_size_t which_target_change;
+ union {
+ google_firestore_v1beta1_Target add_target;
+ int32_t remove_target;
+ } target_change;
+ pb_callback_t labels;
+/* @@protoc_insertion_point(struct:google_firestore_v1beta1_ListenRequest) */
+} google_firestore_v1beta1_ListenRequest;
+
+/* Default values for struct fields */
+
+/* Initializer values for message structs */
+#define google_firestore_v1beta1_GetDocumentRequest_init_default {{{NULL}, NULL}, google_firestore_v1beta1_DocumentMask_init_default, {{NULL}, NULL}, google_protobuf_Timestamp_init_default}
+#define google_firestore_v1beta1_ListDocumentsRequest_init_default {{{NULL}, NULL}, {{NULL}, NULL}, 0, {{NULL}, NULL}, {{NULL}, NULL}, google_firestore_v1beta1_DocumentMask_init_default, {{NULL}, NULL}, google_protobuf_Timestamp_init_default, 0}
+#define google_firestore_v1beta1_ListDocumentsResponse_init_default {{{NULL}, NULL}, {{NULL}, NULL}}
+#define google_firestore_v1beta1_CreateDocumentRequest_init_default {{{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}, google_firestore_v1beta1_Document_init_default, google_firestore_v1beta1_DocumentMask_init_default}
+#define google_firestore_v1beta1_UpdateDocumentRequest_init_default {google_firestore_v1beta1_Document_init_default, google_firestore_v1beta1_DocumentMask_init_default, google_firestore_v1beta1_DocumentMask_init_default, google_firestore_v1beta1_Precondition_init_default}
+#define google_firestore_v1beta1_DeleteDocumentRequest_init_default {{{NULL}, NULL}, google_firestore_v1beta1_Precondition_init_default}
+#define google_firestore_v1beta1_BatchGetDocumentsRequest_init_default {{{NULL}, NULL}, {{NULL}, NULL}, google_firestore_v1beta1_DocumentMask_init_default, {{NULL}, NULL}, google_firestore_v1beta1_TransactionOptions_init_default, google_protobuf_Timestamp_init_default}
+#define google_firestore_v1beta1_BatchGetDocumentsResponse_init_default {google_firestore_v1beta1_Document_init_default, {{NULL}, NULL}, {{NULL}, NULL}, google_protobuf_Timestamp_init_default}
+#define google_firestore_v1beta1_BeginTransactionRequest_init_default {{{NULL}, NULL}, google_firestore_v1beta1_TransactionOptions_init_default}
+#define google_firestore_v1beta1_BeginTransactionResponse_init_default {{{NULL}, NULL}}
+#define google_firestore_v1beta1_CommitRequest_init_default {{{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}}
+#define google_firestore_v1beta1_CommitResponse_init_default {{{NULL}, NULL}, google_protobuf_Timestamp_init_default}
+#define google_firestore_v1beta1_RollbackRequest_init_default {{{NULL}, NULL}, {{NULL}, NULL}}
+#define google_firestore_v1beta1_RunQueryRequest_init_default {{{NULL}, NULL}, 0, {google_firestore_v1beta1_StructuredQuery_init_default}, {{NULL}, NULL}, google_firestore_v1beta1_TransactionOptions_init_default, google_protobuf_Timestamp_init_default}
+#define google_firestore_v1beta1_RunQueryResponse_init_default {google_firestore_v1beta1_Document_init_default, {{NULL}, NULL}, google_protobuf_Timestamp_init_default, 0}
+#define google_firestore_v1beta1_WriteRequest_init_default {{{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}}
+#define google_firestore_v1beta1_WriteRequest_LabelsEntry_init_default {{{NULL}, NULL}, {{NULL}, NULL}}
+#define google_firestore_v1beta1_WriteResponse_init_default {{{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}, google_protobuf_Timestamp_init_default}
+#define google_firestore_v1beta1_ListenRequest_init_default {{{NULL}, NULL}, 0, {google_firestore_v1beta1_Target_init_default}, {{NULL}, NULL}}
+#define google_firestore_v1beta1_ListenRequest_LabelsEntry_init_default {{{NULL}, NULL}, {{NULL}, NULL}}
+#define google_firestore_v1beta1_ListenResponse_init_default {0, {google_firestore_v1beta1_TargetChange_init_default}}
+#define google_firestore_v1beta1_Target_init_default {0, {google_firestore_v1beta1_Target_QueryTarget_init_default}, {{NULL}, NULL}, 0, 0, google_protobuf_Timestamp_init_default}
+#define google_firestore_v1beta1_Target_DocumentsTarget_init_default {{{NULL}, NULL}}
+#define google_firestore_v1beta1_Target_QueryTarget_init_default {{{NULL}, NULL}, 0, {google_firestore_v1beta1_StructuredQuery_init_default}}
+#define google_firestore_v1beta1_TargetChange_init_default {(google_firestore_v1beta1_TargetChange_TargetChangeType)0, {{NULL}, NULL}, google_rpc_Status_init_default, {{NULL}, NULL}, google_protobuf_Timestamp_init_default}
+#define google_firestore_v1beta1_ListCollectionIdsRequest_init_default {{{NULL}, NULL}, 0, {{NULL}, NULL}}
+#define google_firestore_v1beta1_ListCollectionIdsResponse_init_default {{{NULL}, NULL}, {{NULL}, NULL}}
+#define google_firestore_v1beta1_GetDocumentRequest_init_zero {{{NULL}, NULL}, google_firestore_v1beta1_DocumentMask_init_zero, {{NULL}, NULL}, google_protobuf_Timestamp_init_zero}
+#define google_firestore_v1beta1_ListDocumentsRequest_init_zero {{{NULL}, NULL}, {{NULL}, NULL}, 0, {{NULL}, NULL}, {{NULL}, NULL}, google_firestore_v1beta1_DocumentMask_init_zero, {{NULL}, NULL}, google_protobuf_Timestamp_init_zero, 0}
+#define google_firestore_v1beta1_ListDocumentsResponse_init_zero {{{NULL}, NULL}, {{NULL}, NULL}}
+#define google_firestore_v1beta1_CreateDocumentRequest_init_zero {{{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}, google_firestore_v1beta1_Document_init_zero, google_firestore_v1beta1_DocumentMask_init_zero}
+#define google_firestore_v1beta1_UpdateDocumentRequest_init_zero {google_firestore_v1beta1_Document_init_zero, google_firestore_v1beta1_DocumentMask_init_zero, google_firestore_v1beta1_DocumentMask_init_zero, google_firestore_v1beta1_Precondition_init_zero}
+#define google_firestore_v1beta1_DeleteDocumentRequest_init_zero {{{NULL}, NULL}, google_firestore_v1beta1_Precondition_init_zero}
+#define google_firestore_v1beta1_BatchGetDocumentsRequest_init_zero {{{NULL}, NULL}, {{NULL}, NULL}, google_firestore_v1beta1_DocumentMask_init_zero, {{NULL}, NULL}, google_firestore_v1beta1_TransactionOptions_init_zero, google_protobuf_Timestamp_init_zero}
+#define google_firestore_v1beta1_BatchGetDocumentsResponse_init_zero {google_firestore_v1beta1_Document_init_zero, {{NULL}, NULL}, {{NULL}, NULL}, google_protobuf_Timestamp_init_zero}
+#define google_firestore_v1beta1_BeginTransactionRequest_init_zero {{{NULL}, NULL}, google_firestore_v1beta1_TransactionOptions_init_zero}
+#define google_firestore_v1beta1_BeginTransactionResponse_init_zero {{{NULL}, NULL}}
+#define google_firestore_v1beta1_CommitRequest_init_zero {{{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}}
+#define google_firestore_v1beta1_CommitResponse_init_zero {{{NULL}, NULL}, google_protobuf_Timestamp_init_zero}
+#define google_firestore_v1beta1_RollbackRequest_init_zero {{{NULL}, NULL}, {{NULL}, NULL}}
+#define google_firestore_v1beta1_RunQueryRequest_init_zero {{{NULL}, NULL}, 0, {google_firestore_v1beta1_StructuredQuery_init_zero}, {{NULL}, NULL}, google_firestore_v1beta1_TransactionOptions_init_zero, google_protobuf_Timestamp_init_zero}
+#define google_firestore_v1beta1_RunQueryResponse_init_zero {google_firestore_v1beta1_Document_init_zero, {{NULL}, NULL}, google_protobuf_Timestamp_init_zero, 0}
+#define google_firestore_v1beta1_WriteRequest_init_zero {{{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}}
+#define google_firestore_v1beta1_WriteRequest_LabelsEntry_init_zero {{{NULL}, NULL}, {{NULL}, NULL}}
+#define google_firestore_v1beta1_WriteResponse_init_zero {{{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}, google_protobuf_Timestamp_init_zero}
+#define google_firestore_v1beta1_ListenRequest_init_zero {{{NULL}, NULL}, 0, {google_firestore_v1beta1_Target_init_zero}, {{NULL}, NULL}}
+#define google_firestore_v1beta1_ListenRequest_LabelsEntry_init_zero {{{NULL}, NULL}, {{NULL}, NULL}}
+#define google_firestore_v1beta1_ListenResponse_init_zero {0, {google_firestore_v1beta1_TargetChange_init_zero}}
+#define google_firestore_v1beta1_Target_init_zero {0, {google_firestore_v1beta1_Target_QueryTarget_init_zero}, {{NULL}, NULL}, 0, 0, google_protobuf_Timestamp_init_zero}
+#define google_firestore_v1beta1_Target_DocumentsTarget_init_zero {{{NULL}, NULL}}
+#define google_firestore_v1beta1_Target_QueryTarget_init_zero {{{NULL}, NULL}, 0, {google_firestore_v1beta1_StructuredQuery_init_zero}}
+#define google_firestore_v1beta1_TargetChange_init_zero {(google_firestore_v1beta1_TargetChange_TargetChangeType)0, {{NULL}, NULL}, google_rpc_Status_init_zero, {{NULL}, NULL}, google_protobuf_Timestamp_init_zero}
+#define google_firestore_v1beta1_ListCollectionIdsRequest_init_zero {{{NULL}, NULL}, 0, {{NULL}, NULL}}
+#define google_firestore_v1beta1_ListCollectionIdsResponse_init_zero {{{NULL}, NULL}, {{NULL}, NULL}}
+
+/* Field tags (for use in manual encoding/decoding) */
+#define google_firestore_v1beta1_BeginTransactionResponse_transaction_tag 1
+#define google_firestore_v1beta1_CommitRequest_database_tag 1
+#define google_firestore_v1beta1_CommitRequest_writes_tag 2
+#define google_firestore_v1beta1_CommitRequest_transaction_tag 3
+#define google_firestore_v1beta1_ListCollectionIdsResponse_collection_ids_tag 1
+#define google_firestore_v1beta1_ListCollectionIdsResponse_next_page_token_tag 2
+#define google_firestore_v1beta1_ListDocumentsResponse_documents_tag 1
+#define google_firestore_v1beta1_ListDocumentsResponse_next_page_token_tag 2
+#define google_firestore_v1beta1_ListenRequest_LabelsEntry_key_tag 1
+#define google_firestore_v1beta1_ListenRequest_LabelsEntry_value_tag 2
+#define google_firestore_v1beta1_RollbackRequest_database_tag 1
+#define google_firestore_v1beta1_RollbackRequest_transaction_tag 2
+#define google_firestore_v1beta1_Target_DocumentsTarget_documents_tag 2
+#define google_firestore_v1beta1_WriteRequest_database_tag 1
+#define google_firestore_v1beta1_WriteRequest_stream_id_tag 2
+#define google_firestore_v1beta1_WriteRequest_writes_tag 3
+#define google_firestore_v1beta1_WriteRequest_stream_token_tag 4
+#define google_firestore_v1beta1_WriteRequest_labels_tag 5
+#define google_firestore_v1beta1_WriteRequest_LabelsEntry_key_tag 1
+#define google_firestore_v1beta1_WriteRequest_LabelsEntry_value_tag 2
+#define google_firestore_v1beta1_BatchGetDocumentsRequest_database_tag 1
+#define google_firestore_v1beta1_BatchGetDocumentsRequest_documents_tag 2
+#define google_firestore_v1beta1_BatchGetDocumentsRequest_mask_tag 3
+#define google_firestore_v1beta1_BatchGetDocumentsRequest_transaction_tag 4
+#define google_firestore_v1beta1_BatchGetDocumentsRequest_new_transaction_tag 5
+#define google_firestore_v1beta1_BatchGetDocumentsRequest_read_time_tag 7
+#define google_firestore_v1beta1_BatchGetDocumentsResponse_found_tag 1
+#define google_firestore_v1beta1_BatchGetDocumentsResponse_missing_tag 2
+#define google_firestore_v1beta1_BatchGetDocumentsResponse_transaction_tag 3
+#define google_firestore_v1beta1_BatchGetDocumentsResponse_read_time_tag 4
+#define google_firestore_v1beta1_BeginTransactionRequest_database_tag 1
+#define google_firestore_v1beta1_BeginTransactionRequest_options_tag 2
+#define google_firestore_v1beta1_CommitResponse_write_results_tag 1
+#define google_firestore_v1beta1_CommitResponse_commit_time_tag 2
+#define google_firestore_v1beta1_CreateDocumentRequest_parent_tag 1
+#define google_firestore_v1beta1_CreateDocumentRequest_collection_id_tag 2
+#define google_firestore_v1beta1_CreateDocumentRequest_document_id_tag 3
+#define google_firestore_v1beta1_CreateDocumentRequest_document_tag 4
+#define google_firestore_v1beta1_CreateDocumentRequest_mask_tag 5
+#define google_firestore_v1beta1_DeleteDocumentRequest_name_tag 1
+#define google_firestore_v1beta1_DeleteDocumentRequest_current_document_tag 2
+#define google_firestore_v1beta1_GetDocumentRequest_name_tag 1
+#define google_firestore_v1beta1_GetDocumentRequest_mask_tag 2
+#define google_firestore_v1beta1_GetDocumentRequest_transaction_tag 3
+#define google_firestore_v1beta1_GetDocumentRequest_read_time_tag 5
+#define google_firestore_v1beta1_ListCollectionIdsRequest_parent_tag 1
+#define google_firestore_v1beta1_ListCollectionIdsRequest_page_size_tag 2
+#define google_firestore_v1beta1_ListCollectionIdsRequest_page_token_tag 3
+#define google_firestore_v1beta1_ListDocumentsRequest_parent_tag 1
+#define google_firestore_v1beta1_ListDocumentsRequest_collection_id_tag 2
+#define google_firestore_v1beta1_ListDocumentsRequest_page_size_tag 3
+#define google_firestore_v1beta1_ListDocumentsRequest_page_token_tag 4
+#define google_firestore_v1beta1_ListDocumentsRequest_order_by_tag 6
+#define google_firestore_v1beta1_ListDocumentsRequest_mask_tag 7
+#define google_firestore_v1beta1_ListDocumentsRequest_transaction_tag 8
+#define google_firestore_v1beta1_ListDocumentsRequest_read_time_tag 10
+#define google_firestore_v1beta1_ListDocumentsRequest_show_missing_tag 12
+#define google_firestore_v1beta1_RunQueryRequest_structured_query_tag 2
+#define google_firestore_v1beta1_RunQueryRequest_parent_tag 1
+#define google_firestore_v1beta1_RunQueryRequest_transaction_tag 5
+#define google_firestore_v1beta1_RunQueryRequest_new_transaction_tag 6
+#define google_firestore_v1beta1_RunQueryRequest_read_time_tag 7
+#define google_firestore_v1beta1_RunQueryResponse_transaction_tag 2
+#define google_firestore_v1beta1_RunQueryResponse_document_tag 1
+#define google_firestore_v1beta1_RunQueryResponse_read_time_tag 3
+#define google_firestore_v1beta1_RunQueryResponse_skipped_results_tag 4
+#define google_firestore_v1beta1_TargetChange_target_change_type_tag 1
+#define google_firestore_v1beta1_TargetChange_target_ids_tag 2
+#define google_firestore_v1beta1_TargetChange_cause_tag 3
+#define google_firestore_v1beta1_TargetChange_resume_token_tag 4
+#define google_firestore_v1beta1_TargetChange_read_time_tag 6
+#define google_firestore_v1beta1_Target_QueryTarget_structured_query_tag 2
+#define google_firestore_v1beta1_Target_QueryTarget_parent_tag 1
+#define google_firestore_v1beta1_UpdateDocumentRequest_document_tag 1
+#define google_firestore_v1beta1_UpdateDocumentRequest_update_mask_tag 2
+#define google_firestore_v1beta1_UpdateDocumentRequest_mask_tag 3
+#define google_firestore_v1beta1_UpdateDocumentRequest_current_document_tag 4
+#define google_firestore_v1beta1_WriteResponse_stream_id_tag 1
+#define google_firestore_v1beta1_WriteResponse_stream_token_tag 2
+#define google_firestore_v1beta1_WriteResponse_write_results_tag 3
+#define google_firestore_v1beta1_WriteResponse_commit_time_tag 4
+#define google_firestore_v1beta1_ListenResponse_target_change_tag 2
+#define google_firestore_v1beta1_ListenResponse_document_change_tag 3
+#define google_firestore_v1beta1_ListenResponse_document_delete_tag 4
+#define google_firestore_v1beta1_ListenResponse_filter_tag 5
+#define google_firestore_v1beta1_ListenResponse_document_remove_tag 6
+#define google_firestore_v1beta1_Target_query_tag 2
+#define google_firestore_v1beta1_Target_documents_tag 3
+#define google_firestore_v1beta1_Target_resume_token_tag 4
+#define google_firestore_v1beta1_Target_read_time_tag 11
+#define google_firestore_v1beta1_Target_target_id_tag 5
+#define google_firestore_v1beta1_Target_once_tag 6
+#define google_firestore_v1beta1_ListenRequest_add_target_tag 2
+#define google_firestore_v1beta1_ListenRequest_remove_target_tag 3
+#define google_firestore_v1beta1_ListenRequest_database_tag 1
+#define google_firestore_v1beta1_ListenRequest_labels_tag 4
+
+/* Struct field encoding specification for nanopb */
+extern const pb_field_t google_firestore_v1beta1_GetDocumentRequest_fields[5];
+extern const pb_field_t google_firestore_v1beta1_ListDocumentsRequest_fields[10];
+extern const pb_field_t google_firestore_v1beta1_ListDocumentsResponse_fields[3];
+extern const pb_field_t google_firestore_v1beta1_CreateDocumentRequest_fields[6];
+extern const pb_field_t google_firestore_v1beta1_UpdateDocumentRequest_fields[5];
+extern const pb_field_t google_firestore_v1beta1_DeleteDocumentRequest_fields[3];
+extern const pb_field_t google_firestore_v1beta1_BatchGetDocumentsRequest_fields[7];
+extern const pb_field_t google_firestore_v1beta1_BatchGetDocumentsResponse_fields[5];
+extern const pb_field_t google_firestore_v1beta1_BeginTransactionRequest_fields[3];
+extern const pb_field_t google_firestore_v1beta1_BeginTransactionResponse_fields[2];
+extern const pb_field_t google_firestore_v1beta1_CommitRequest_fields[4];
+extern const pb_field_t google_firestore_v1beta1_CommitResponse_fields[3];
+extern const pb_field_t google_firestore_v1beta1_RollbackRequest_fields[3];
+extern const pb_field_t google_firestore_v1beta1_RunQueryRequest_fields[6];
+extern const pb_field_t google_firestore_v1beta1_RunQueryResponse_fields[5];
+extern const pb_field_t google_firestore_v1beta1_WriteRequest_fields[6];
+extern const pb_field_t google_firestore_v1beta1_WriteRequest_LabelsEntry_fields[3];
+extern const pb_field_t google_firestore_v1beta1_WriteResponse_fields[5];
+extern const pb_field_t google_firestore_v1beta1_ListenRequest_fields[5];
+extern const pb_field_t google_firestore_v1beta1_ListenRequest_LabelsEntry_fields[3];
+extern const pb_field_t google_firestore_v1beta1_ListenResponse_fields[6];
+extern const pb_field_t google_firestore_v1beta1_Target_fields[7];
+extern const pb_field_t google_firestore_v1beta1_Target_DocumentsTarget_fields[2];
+extern const pb_field_t google_firestore_v1beta1_Target_QueryTarget_fields[3];
+extern const pb_field_t google_firestore_v1beta1_TargetChange_fields[6];
+extern const pb_field_t google_firestore_v1beta1_ListCollectionIdsRequest_fields[4];
+extern const pb_field_t google_firestore_v1beta1_ListCollectionIdsResponse_fields[3];
+
+/* Maximum encoded size of messages (where known) */
+/* google_firestore_v1beta1_GetDocumentRequest_size depends on runtime parameters */
+/* google_firestore_v1beta1_ListDocumentsRequest_size depends on runtime parameters */
+/* google_firestore_v1beta1_ListDocumentsResponse_size depends on runtime parameters */
+/* google_firestore_v1beta1_CreateDocumentRequest_size depends on runtime parameters */
+#define google_firestore_v1beta1_UpdateDocumentRequest_size (44 + google_firestore_v1beta1_Document_size + google_firestore_v1beta1_DocumentMask_size + google_firestore_v1beta1_DocumentMask_size)
+/* google_firestore_v1beta1_DeleteDocumentRequest_size depends on runtime parameters */
+/* google_firestore_v1beta1_BatchGetDocumentsRequest_size depends on runtime parameters */
+/* google_firestore_v1beta1_BatchGetDocumentsResponse_size depends on runtime parameters */
+/* google_firestore_v1beta1_BeginTransactionRequest_size depends on runtime parameters */
+/* google_firestore_v1beta1_BeginTransactionResponse_size depends on runtime parameters */
+/* google_firestore_v1beta1_CommitRequest_size depends on runtime parameters */
+/* google_firestore_v1beta1_CommitResponse_size depends on runtime parameters */
+/* google_firestore_v1beta1_RollbackRequest_size depends on runtime parameters */
+/* google_firestore_v1beta1_RunQueryRequest_size depends on runtime parameters */
+/* google_firestore_v1beta1_RunQueryResponse_size depends on runtime parameters */
+/* google_firestore_v1beta1_WriteRequest_size depends on runtime parameters */
+/* google_firestore_v1beta1_WriteRequest_LabelsEntry_size depends on runtime parameters */
+/* google_firestore_v1beta1_WriteResponse_size depends on runtime parameters */
+/* google_firestore_v1beta1_ListenRequest_size depends on runtime parameters */
+/* google_firestore_v1beta1_ListenRequest_LabelsEntry_size depends on runtime parameters */
+/* google_firestore_v1beta1_ListenResponse_size depends on runtime parameters */
+/* google_firestore_v1beta1_Target_size depends on runtime parameters */
+/* google_firestore_v1beta1_Target_DocumentsTarget_size depends on runtime parameters */
+/* google_firestore_v1beta1_Target_QueryTarget_size depends on runtime parameters */
+/* google_firestore_v1beta1_TargetChange_size depends on runtime parameters */
+/* google_firestore_v1beta1_ListCollectionIdsRequest_size depends on runtime parameters */
+/* google_firestore_v1beta1_ListCollectionIdsResponse_size depends on runtime parameters */
+
+/* Message IDs (where set with "msgid" option) */
+#ifdef PB_MSGID
+
+#define FIRESTORE_MESSAGES \
+
+
+#endif
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+/* @@protoc_insertion_point(eof) */
+
+#endif
diff --git a/Firestore/Protos/nanopb/google/firestore/v1beta1/query.pb.c b/Firestore/Protos/nanopb/google/firestore/v1beta1/query.pb.c
new file mode 100644
index 0000000..4e68490
--- /dev/null
+++ b/Firestore/Protos/nanopb/google/firestore/v1beta1/query.pb.c
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2018 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.
+ */
+
+/* Automatically generated nanopb constant definitions */
+/* Generated by nanopb-0.3.8 at Fri Feb 2 17:48:02 2018. */
+
+#include "query.pb.h"
+
+/* @@protoc_insertion_point(includes) */
+#if PB_PROTO_HEADER_VERSION != 30
+#error Regenerate this file with the current version of nanopb generator.
+#endif
+
+
+
+const pb_field_t google_firestore_v1beta1_StructuredQuery_fields[9] = {
+ PB_FIELD( 1, MESSAGE , SINGULAR, STATIC , FIRST, google_firestore_v1beta1_StructuredQuery, select, select, &google_firestore_v1beta1_StructuredQuery_Projection_fields),
+ PB_FIELD( 2, MESSAGE , REPEATED, CALLBACK, OTHER, google_firestore_v1beta1_StructuredQuery, from, select, &google_firestore_v1beta1_StructuredQuery_CollectionSelector_fields),
+ PB_FIELD( 3, MESSAGE , SINGULAR, STATIC , OTHER, google_firestore_v1beta1_StructuredQuery, where, from, &google_firestore_v1beta1_StructuredQuery_Filter_fields),
+ PB_FIELD( 4, MESSAGE , REPEATED, CALLBACK, OTHER, google_firestore_v1beta1_StructuredQuery, order_by, where, &google_firestore_v1beta1_StructuredQuery_Order_fields),
+ PB_FIELD( 5, MESSAGE , SINGULAR, STATIC , OTHER, google_firestore_v1beta1_StructuredQuery, limit, order_by, &google_protobuf_Int32Value_fields),
+ PB_FIELD( 6, INT32 , SINGULAR, STATIC , OTHER, google_firestore_v1beta1_StructuredQuery, offset, limit, 0),
+ PB_FIELD( 7, MESSAGE , SINGULAR, STATIC , OTHER, google_firestore_v1beta1_StructuredQuery, start_at, offset, &google_firestore_v1beta1_Cursor_fields),
+ PB_FIELD( 8, MESSAGE , SINGULAR, STATIC , OTHER, google_firestore_v1beta1_StructuredQuery, end_at, start_at, &google_firestore_v1beta1_Cursor_fields),
+ PB_LAST_FIELD
+};
+
+const pb_field_t google_firestore_v1beta1_StructuredQuery_CollectionSelector_fields[3] = {
+ PB_FIELD( 2, STRING , SINGULAR, CALLBACK, FIRST, google_firestore_v1beta1_StructuredQuery_CollectionSelector, collection_id, collection_id, 0),
+ PB_FIELD( 3, BOOL , SINGULAR, STATIC , OTHER, google_firestore_v1beta1_StructuredQuery_CollectionSelector, all_descendants, collection_id, 0),
+ PB_LAST_FIELD
+};
+
+const pb_field_t google_firestore_v1beta1_StructuredQuery_Filter_fields[4] = {
+ PB_ONEOF_FIELD(filter_type, 1, MESSAGE , ONEOF, STATIC , FIRST, google_firestore_v1beta1_StructuredQuery_Filter, composite_filter, composite_filter, &google_firestore_v1beta1_StructuredQuery_CompositeFilter_fields),
+ PB_ONEOF_FIELD(filter_type, 2, MESSAGE , ONEOF, STATIC , UNION, google_firestore_v1beta1_StructuredQuery_Filter, field_filter, field_filter, &google_firestore_v1beta1_StructuredQuery_FieldFilter_fields),
+ PB_ONEOF_FIELD(filter_type, 3, MESSAGE , ONEOF, STATIC , UNION, google_firestore_v1beta1_StructuredQuery_Filter, unary_filter, unary_filter, &google_firestore_v1beta1_StructuredQuery_UnaryFilter_fields),
+ PB_LAST_FIELD
+};
+
+const pb_field_t google_firestore_v1beta1_StructuredQuery_CompositeFilter_fields[3] = {
+ PB_FIELD( 1, UENUM , SINGULAR, STATIC , FIRST, google_firestore_v1beta1_StructuredQuery_CompositeFilter, op, op, 0),
+ PB_FIELD( 2, MESSAGE , REPEATED, CALLBACK, OTHER, google_firestore_v1beta1_StructuredQuery_CompositeFilter, filters, op, &google_firestore_v1beta1_StructuredQuery_Filter_fields),
+ PB_LAST_FIELD
+};
+
+const pb_field_t google_firestore_v1beta1_StructuredQuery_FieldFilter_fields[4] = {
+ PB_FIELD( 1, MESSAGE , SINGULAR, STATIC , FIRST, google_firestore_v1beta1_StructuredQuery_FieldFilter, field, field, &google_firestore_v1beta1_StructuredQuery_FieldReference_fields),
+ PB_FIELD( 2, UENUM , SINGULAR, STATIC , OTHER, google_firestore_v1beta1_StructuredQuery_FieldFilter, op, field, 0),
+ PB_FIELD( 3, MESSAGE , SINGULAR, STATIC , OTHER, google_firestore_v1beta1_StructuredQuery_FieldFilter, value, op, &google_firestore_v1beta1_Value_fields),
+ PB_LAST_FIELD
+};
+
+const pb_field_t google_firestore_v1beta1_StructuredQuery_UnaryFilter_fields[3] = {
+ PB_FIELD( 1, UENUM , SINGULAR, STATIC , FIRST, google_firestore_v1beta1_StructuredQuery_UnaryFilter, op, op, 0),
+ PB_ONEOF_FIELD(operand_type, 2, MESSAGE , ONEOF, STATIC , OTHER, google_firestore_v1beta1_StructuredQuery_UnaryFilter, field, op, &google_firestore_v1beta1_StructuredQuery_FieldReference_fields),
+ PB_LAST_FIELD
+};
+
+const pb_field_t google_firestore_v1beta1_StructuredQuery_Order_fields[3] = {
+ PB_FIELD( 1, MESSAGE , SINGULAR, STATIC , FIRST, google_firestore_v1beta1_StructuredQuery_Order, field, field, &google_firestore_v1beta1_StructuredQuery_FieldReference_fields),
+ PB_FIELD( 2, UENUM , SINGULAR, STATIC , OTHER, google_firestore_v1beta1_StructuredQuery_Order, direction, field, 0),
+ PB_LAST_FIELD
+};
+
+const pb_field_t google_firestore_v1beta1_StructuredQuery_FieldReference_fields[2] = {
+ PB_FIELD( 2, STRING , SINGULAR, CALLBACK, FIRST, google_firestore_v1beta1_StructuredQuery_FieldReference, field_path, field_path, 0),
+ PB_LAST_FIELD
+};
+
+const pb_field_t google_firestore_v1beta1_StructuredQuery_Projection_fields[2] = {
+ PB_FIELD( 2, MESSAGE , REPEATED, CALLBACK, FIRST, google_firestore_v1beta1_StructuredQuery_Projection, fields, fields, &google_firestore_v1beta1_StructuredQuery_FieldReference_fields),
+ PB_LAST_FIELD
+};
+
+const pb_field_t google_firestore_v1beta1_Cursor_fields[3] = {
+ PB_FIELD( 1, MESSAGE , REPEATED, CALLBACK, FIRST, google_firestore_v1beta1_Cursor, values, values, &google_firestore_v1beta1_Value_fields),
+ PB_FIELD( 2, BOOL , SINGULAR, STATIC , OTHER, google_firestore_v1beta1_Cursor, before, values, 0),
+ PB_LAST_FIELD
+};
+
+
+
+
+
+
+/* Check that field information fits in pb_field_t */
+#if !defined(PB_FIELD_32BIT)
+/* If you get an error here, it means that you need to define PB_FIELD_32BIT
+ * compile-time option. You can do that in pb.h or on compiler command line.
+ *
+ * The reason you need to do this is that some of your messages contain tag
+ * numbers or field sizes that are larger than what can fit in 8 or 16 bit
+ * field descriptors.
+ */
+PB_STATIC_ASSERT((pb_membersize(google_firestore_v1beta1_StructuredQuery, select) < 65536 && pb_membersize(google_firestore_v1beta1_StructuredQuery, where) < 65536 && pb_membersize(google_firestore_v1beta1_StructuredQuery, start_at) < 65536 && pb_membersize(google_firestore_v1beta1_StructuredQuery, end_at) < 65536 && pb_membersize(google_firestore_v1beta1_StructuredQuery, limit) < 65536 && pb_membersize(google_firestore_v1beta1_StructuredQuery_Filter, filter_type.composite_filter) < 65536 && pb_membersize(google_firestore_v1beta1_StructuredQuery_Filter, filter_type.field_filter) < 65536 && pb_membersize(google_firestore_v1beta1_StructuredQuery_Filter, filter_type.unary_filter) < 65536 && pb_membersize(google_firestore_v1beta1_StructuredQuery_FieldFilter, field) < 65536 && pb_membersize(google_firestore_v1beta1_StructuredQuery_FieldFilter, value) < 65536 && pb_membersize(google_firestore_v1beta1_StructuredQuery_UnaryFilter, operand_type.field) < 65536 && pb_membersize(google_firestore_v1beta1_StructuredQuery_Order, field) < 65536), YOU_MUST_DEFINE_PB_FIELD_32BIT_FOR_MESSAGES_google_firestore_v1beta1_StructuredQuery_google_firestore_v1beta1_StructuredQuery_CollectionSelector_google_firestore_v1beta1_StructuredQuery_Filter_google_firestore_v1beta1_StructuredQuery_CompositeFilter_google_firestore_v1beta1_StructuredQuery_FieldFilter_google_firestore_v1beta1_StructuredQuery_UnaryFilter_google_firestore_v1beta1_StructuredQuery_Order_google_firestore_v1beta1_StructuredQuery_FieldReference_google_firestore_v1beta1_StructuredQuery_Projection_google_firestore_v1beta1_Cursor)
+#endif
+
+#if !defined(PB_FIELD_16BIT) && !defined(PB_FIELD_32BIT)
+/* If you get an error here, it means that you need to define PB_FIELD_16BIT
+ * compile-time option. You can do that in pb.h or on compiler command line.
+ *
+ * The reason you need to do this is that some of your messages contain tag
+ * numbers or field sizes that are larger than what can fit in the default
+ * 8 bit descriptors.
+ */
+PB_STATIC_ASSERT((pb_membersize(google_firestore_v1beta1_StructuredQuery, select) < 256 && pb_membersize(google_firestore_v1beta1_StructuredQuery, where) < 256 && pb_membersize(google_firestore_v1beta1_StructuredQuery, start_at) < 256 && pb_membersize(google_firestore_v1beta1_StructuredQuery, end_at) < 256 && pb_membersize(google_firestore_v1beta1_StructuredQuery, limit) < 256 && pb_membersize(google_firestore_v1beta1_StructuredQuery_Filter, filter_type.composite_filter) < 256 && pb_membersize(google_firestore_v1beta1_StructuredQuery_Filter, filter_type.field_filter) < 256 && pb_membersize(google_firestore_v1beta1_StructuredQuery_Filter, filter_type.unary_filter) < 256 && pb_membersize(google_firestore_v1beta1_StructuredQuery_FieldFilter, field) < 256 && pb_membersize(google_firestore_v1beta1_StructuredQuery_FieldFilter, value) < 256 && pb_membersize(google_firestore_v1beta1_StructuredQuery_UnaryFilter, operand_type.field) < 256 && pb_membersize(google_firestore_v1beta1_StructuredQuery_Order, field) < 256), YOU_MUST_DEFINE_PB_FIELD_16BIT_FOR_MESSAGES_google_firestore_v1beta1_StructuredQuery_google_firestore_v1beta1_StructuredQuery_CollectionSelector_google_firestore_v1beta1_StructuredQuery_Filter_google_firestore_v1beta1_StructuredQuery_CompositeFilter_google_firestore_v1beta1_StructuredQuery_FieldFilter_google_firestore_v1beta1_StructuredQuery_UnaryFilter_google_firestore_v1beta1_StructuredQuery_Order_google_firestore_v1beta1_StructuredQuery_FieldReference_google_firestore_v1beta1_StructuredQuery_Projection_google_firestore_v1beta1_Cursor)
+#endif
+
+
+/* @@protoc_insertion_point(eof) */
diff --git a/Firestore/Protos/nanopb/google/firestore/v1beta1/query.pb.h b/Firestore/Protos/nanopb/google/firestore/v1beta1/query.pb.h
new file mode 100644
index 0000000..75b8168
--- /dev/null
+++ b/Firestore/Protos/nanopb/google/firestore/v1beta1/query.pb.h
@@ -0,0 +1,241 @@
+/*
+ * Copyright 2018 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.
+ */
+
+/* Automatically generated nanopb header */
+/* Generated by nanopb-0.3.8 at Thu Apr 12 07:27:15 2018. */
+
+#ifndef PB_GOOGLE_FIRESTORE_V1BETA1_QUERY_PB_H_INCLUDED
+#define PB_GOOGLE_FIRESTORE_V1BETA1_QUERY_PB_H_INCLUDED
+#include <pb.h>
+
+#include "google/api/annotations.pb.h"
+
+#include "google/firestore/v1beta1/document.pb.h"
+
+#include "google/protobuf/wrappers.pb.h"
+
+/* @@protoc_insertion_point(includes) */
+#if PB_PROTO_HEADER_VERSION != 30
+#error Regenerate this file with the current version of nanopb generator.
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Enum definitions */
+typedef enum _google_firestore_v1beta1_StructuredQuery_Direction {
+ google_firestore_v1beta1_StructuredQuery_Direction_DIRECTION_UNSPECIFIED = 0,
+ google_firestore_v1beta1_StructuredQuery_Direction_ASCENDING = 1,
+ google_firestore_v1beta1_StructuredQuery_Direction_DESCENDING = 2
+} google_firestore_v1beta1_StructuredQuery_Direction;
+#define _google_firestore_v1beta1_StructuredQuery_Direction_MIN google_firestore_v1beta1_StructuredQuery_Direction_DIRECTION_UNSPECIFIED
+#define _google_firestore_v1beta1_StructuredQuery_Direction_MAX google_firestore_v1beta1_StructuredQuery_Direction_DESCENDING
+#define _google_firestore_v1beta1_StructuredQuery_Direction_ARRAYSIZE ((google_firestore_v1beta1_StructuredQuery_Direction)(google_firestore_v1beta1_StructuredQuery_Direction_DESCENDING+1))
+
+typedef enum _google_firestore_v1beta1_StructuredQuery_CompositeFilter_Operator {
+ google_firestore_v1beta1_StructuredQuery_CompositeFilter_Operator_OPERATOR_UNSPECIFIED = 0,
+ google_firestore_v1beta1_StructuredQuery_CompositeFilter_Operator_AND = 1
+} google_firestore_v1beta1_StructuredQuery_CompositeFilter_Operator;
+#define _google_firestore_v1beta1_StructuredQuery_CompositeFilter_Operator_MIN google_firestore_v1beta1_StructuredQuery_CompositeFilter_Operator_OPERATOR_UNSPECIFIED
+#define _google_firestore_v1beta1_StructuredQuery_CompositeFilter_Operator_MAX google_firestore_v1beta1_StructuredQuery_CompositeFilter_Operator_AND
+#define _google_firestore_v1beta1_StructuredQuery_CompositeFilter_Operator_ARRAYSIZE ((google_firestore_v1beta1_StructuredQuery_CompositeFilter_Operator)(google_firestore_v1beta1_StructuredQuery_CompositeFilter_Operator_AND+1))
+
+typedef enum _google_firestore_v1beta1_StructuredQuery_FieldFilter_Operator {
+ google_firestore_v1beta1_StructuredQuery_FieldFilter_Operator_OPERATOR_UNSPECIFIED = 0,
+ google_firestore_v1beta1_StructuredQuery_FieldFilter_Operator_LESS_THAN = 1,
+ google_firestore_v1beta1_StructuredQuery_FieldFilter_Operator_LESS_THAN_OR_EQUAL = 2,
+ google_firestore_v1beta1_StructuredQuery_FieldFilter_Operator_GREATER_THAN = 3,
+ google_firestore_v1beta1_StructuredQuery_FieldFilter_Operator_GREATER_THAN_OR_EQUAL = 4,
+ google_firestore_v1beta1_StructuredQuery_FieldFilter_Operator_EQUAL = 5,
+ google_firestore_v1beta1_StructuredQuery_FieldFilter_Operator_ARRAY_CONTAINS = 7
+} google_firestore_v1beta1_StructuredQuery_FieldFilter_Operator;
+#define _google_firestore_v1beta1_StructuredQuery_FieldFilter_Operator_MIN google_firestore_v1beta1_StructuredQuery_FieldFilter_Operator_OPERATOR_UNSPECIFIED
+#define _google_firestore_v1beta1_StructuredQuery_FieldFilter_Operator_MAX google_firestore_v1beta1_StructuredQuery_FieldFilter_Operator_ARRAY_CONTAINS
+#define _google_firestore_v1beta1_StructuredQuery_FieldFilter_Operator_ARRAYSIZE ((google_firestore_v1beta1_StructuredQuery_FieldFilter_Operator)(google_firestore_v1beta1_StructuredQuery_FieldFilter_Operator_ARRAY_CONTAINS+1))
+
+typedef enum _google_firestore_v1beta1_StructuredQuery_UnaryFilter_Operator {
+ google_firestore_v1beta1_StructuredQuery_UnaryFilter_Operator_OPERATOR_UNSPECIFIED = 0,
+ google_firestore_v1beta1_StructuredQuery_UnaryFilter_Operator_IS_NAN = 2,
+ google_firestore_v1beta1_StructuredQuery_UnaryFilter_Operator_IS_NULL = 3
+} google_firestore_v1beta1_StructuredQuery_UnaryFilter_Operator;
+#define _google_firestore_v1beta1_StructuredQuery_UnaryFilter_Operator_MIN google_firestore_v1beta1_StructuredQuery_UnaryFilter_Operator_OPERATOR_UNSPECIFIED
+#define _google_firestore_v1beta1_StructuredQuery_UnaryFilter_Operator_MAX google_firestore_v1beta1_StructuredQuery_UnaryFilter_Operator_IS_NULL
+#define _google_firestore_v1beta1_StructuredQuery_UnaryFilter_Operator_ARRAYSIZE ((google_firestore_v1beta1_StructuredQuery_UnaryFilter_Operator)(google_firestore_v1beta1_StructuredQuery_UnaryFilter_Operator_IS_NULL+1))
+
+/* Struct definitions */
+typedef struct _google_firestore_v1beta1_StructuredQuery_FieldReference {
+ pb_callback_t field_path;
+/* @@protoc_insertion_point(struct:google_firestore_v1beta1_StructuredQuery_FieldReference) */
+} google_firestore_v1beta1_StructuredQuery_FieldReference;
+
+typedef struct _google_firestore_v1beta1_StructuredQuery_Projection {
+ pb_callback_t fields;
+/* @@protoc_insertion_point(struct:google_firestore_v1beta1_StructuredQuery_Projection) */
+} google_firestore_v1beta1_StructuredQuery_Projection;
+
+typedef struct _google_firestore_v1beta1_Cursor {
+ pb_callback_t values;
+ bool before;
+/* @@protoc_insertion_point(struct:google_firestore_v1beta1_Cursor) */
+} google_firestore_v1beta1_Cursor;
+
+typedef struct _google_firestore_v1beta1_StructuredQuery_CollectionSelector {
+ pb_callback_t collection_id;
+ bool all_descendants;
+/* @@protoc_insertion_point(struct:google_firestore_v1beta1_StructuredQuery_CollectionSelector) */
+} google_firestore_v1beta1_StructuredQuery_CollectionSelector;
+
+typedef struct _google_firestore_v1beta1_StructuredQuery_CompositeFilter {
+ google_firestore_v1beta1_StructuredQuery_CompositeFilter_Operator op;
+ pb_callback_t filters;
+/* @@protoc_insertion_point(struct:google_firestore_v1beta1_StructuredQuery_CompositeFilter) */
+} google_firestore_v1beta1_StructuredQuery_CompositeFilter;
+
+typedef struct _google_firestore_v1beta1_StructuredQuery_FieldFilter {
+ google_firestore_v1beta1_StructuredQuery_FieldReference field;
+ google_firestore_v1beta1_StructuredQuery_FieldFilter_Operator op;
+ google_firestore_v1beta1_Value value;
+/* @@protoc_insertion_point(struct:google_firestore_v1beta1_StructuredQuery_FieldFilter) */
+} google_firestore_v1beta1_StructuredQuery_FieldFilter;
+
+typedef struct _google_firestore_v1beta1_StructuredQuery_Order {
+ google_firestore_v1beta1_StructuredQuery_FieldReference field;
+ google_firestore_v1beta1_StructuredQuery_Direction direction;
+/* @@protoc_insertion_point(struct:google_firestore_v1beta1_StructuredQuery_Order) */
+} google_firestore_v1beta1_StructuredQuery_Order;
+
+typedef struct _google_firestore_v1beta1_StructuredQuery_UnaryFilter {
+ google_firestore_v1beta1_StructuredQuery_UnaryFilter_Operator op;
+ pb_size_t which_operand_type;
+ union {
+ google_firestore_v1beta1_StructuredQuery_FieldReference field;
+ } operand_type;
+/* @@protoc_insertion_point(struct:google_firestore_v1beta1_StructuredQuery_UnaryFilter) */
+} google_firestore_v1beta1_StructuredQuery_UnaryFilter;
+
+typedef struct _google_firestore_v1beta1_StructuredQuery_Filter {
+ pb_size_t which_filter_type;
+ union {
+ google_firestore_v1beta1_StructuredQuery_CompositeFilter composite_filter;
+ google_firestore_v1beta1_StructuredQuery_FieldFilter field_filter;
+ google_firestore_v1beta1_StructuredQuery_UnaryFilter unary_filter;
+ } filter_type;
+/* @@protoc_insertion_point(struct:google_firestore_v1beta1_StructuredQuery_Filter) */
+} google_firestore_v1beta1_StructuredQuery_Filter;
+
+typedef struct _google_firestore_v1beta1_StructuredQuery {
+ google_firestore_v1beta1_StructuredQuery_Projection select;
+ pb_callback_t from;
+ google_firestore_v1beta1_StructuredQuery_Filter where;
+ pb_callback_t order_by;
+ google_protobuf_Int32Value limit;
+ int32_t offset;
+ google_firestore_v1beta1_Cursor start_at;
+ google_firestore_v1beta1_Cursor end_at;
+/* @@protoc_insertion_point(struct:google_firestore_v1beta1_StructuredQuery) */
+} google_firestore_v1beta1_StructuredQuery;
+
+/* Default values for struct fields */
+
+/* Initializer values for message structs */
+#define google_firestore_v1beta1_StructuredQuery_init_default {google_firestore_v1beta1_StructuredQuery_Projection_init_default, {{NULL}, NULL}, google_firestore_v1beta1_StructuredQuery_Filter_init_default, {{NULL}, NULL}, google_protobuf_Int32Value_init_default, 0, google_firestore_v1beta1_Cursor_init_default, google_firestore_v1beta1_Cursor_init_default}
+#define google_firestore_v1beta1_StructuredQuery_CollectionSelector_init_default {{{NULL}, NULL}, 0}
+#define google_firestore_v1beta1_StructuredQuery_Filter_init_default {0, {google_firestore_v1beta1_StructuredQuery_CompositeFilter_init_default}}
+#define google_firestore_v1beta1_StructuredQuery_CompositeFilter_init_default {(google_firestore_v1beta1_StructuredQuery_CompositeFilter_Operator)0, {{NULL}, NULL}}
+#define google_firestore_v1beta1_StructuredQuery_FieldFilter_init_default {google_firestore_v1beta1_StructuredQuery_FieldReference_init_default, (google_firestore_v1beta1_StructuredQuery_FieldFilter_Operator)0, google_firestore_v1beta1_Value_init_default}
+#define google_firestore_v1beta1_StructuredQuery_UnaryFilter_init_default {(google_firestore_v1beta1_StructuredQuery_UnaryFilter_Operator)0, 0, {google_firestore_v1beta1_StructuredQuery_FieldReference_init_default}}
+#define google_firestore_v1beta1_StructuredQuery_Order_init_default {google_firestore_v1beta1_StructuredQuery_FieldReference_init_default, (google_firestore_v1beta1_StructuredQuery_Direction)0}
+#define google_firestore_v1beta1_StructuredQuery_FieldReference_init_default {{{NULL}, NULL}}
+#define google_firestore_v1beta1_StructuredQuery_Projection_init_default {{{NULL}, NULL}}
+#define google_firestore_v1beta1_Cursor_init_default {{{NULL}, NULL}, 0}
+#define google_firestore_v1beta1_StructuredQuery_init_zero {google_firestore_v1beta1_StructuredQuery_Projection_init_zero, {{NULL}, NULL}, google_firestore_v1beta1_StructuredQuery_Filter_init_zero, {{NULL}, NULL}, google_protobuf_Int32Value_init_zero, 0, google_firestore_v1beta1_Cursor_init_zero, google_firestore_v1beta1_Cursor_init_zero}
+#define google_firestore_v1beta1_StructuredQuery_CollectionSelector_init_zero {{{NULL}, NULL}, 0}
+#define google_firestore_v1beta1_StructuredQuery_Filter_init_zero {0, {google_firestore_v1beta1_StructuredQuery_CompositeFilter_init_zero}}
+#define google_firestore_v1beta1_StructuredQuery_CompositeFilter_init_zero {(google_firestore_v1beta1_StructuredQuery_CompositeFilter_Operator)0, {{NULL}, NULL}}
+#define google_firestore_v1beta1_StructuredQuery_FieldFilter_init_zero {google_firestore_v1beta1_StructuredQuery_FieldReference_init_zero, (google_firestore_v1beta1_StructuredQuery_FieldFilter_Operator)0, google_firestore_v1beta1_Value_init_zero}
+#define google_firestore_v1beta1_StructuredQuery_UnaryFilter_init_zero {(google_firestore_v1beta1_StructuredQuery_UnaryFilter_Operator)0, 0, {google_firestore_v1beta1_StructuredQuery_FieldReference_init_zero}}
+#define google_firestore_v1beta1_StructuredQuery_Order_init_zero {google_firestore_v1beta1_StructuredQuery_FieldReference_init_zero, (google_firestore_v1beta1_StructuredQuery_Direction)0}
+#define google_firestore_v1beta1_StructuredQuery_FieldReference_init_zero {{{NULL}, NULL}}
+#define google_firestore_v1beta1_StructuredQuery_Projection_init_zero {{{NULL}, NULL}}
+#define google_firestore_v1beta1_Cursor_init_zero {{{NULL}, NULL}, 0}
+
+/* Field tags (for use in manual encoding/decoding) */
+#define google_firestore_v1beta1_StructuredQuery_FieldReference_field_path_tag 2
+#define google_firestore_v1beta1_StructuredQuery_Projection_fields_tag 2
+#define google_firestore_v1beta1_Cursor_values_tag 1
+#define google_firestore_v1beta1_Cursor_before_tag 2
+#define google_firestore_v1beta1_StructuredQuery_CollectionSelector_collection_id_tag 2
+#define google_firestore_v1beta1_StructuredQuery_CollectionSelector_all_descendants_tag 3
+#define google_firestore_v1beta1_StructuredQuery_CompositeFilter_op_tag 1
+#define google_firestore_v1beta1_StructuredQuery_CompositeFilter_filters_tag 2
+#define google_firestore_v1beta1_StructuredQuery_FieldFilter_field_tag 1
+#define google_firestore_v1beta1_StructuredQuery_FieldFilter_op_tag 2
+#define google_firestore_v1beta1_StructuredQuery_FieldFilter_value_tag 3
+#define google_firestore_v1beta1_StructuredQuery_Order_field_tag 1
+#define google_firestore_v1beta1_StructuredQuery_Order_direction_tag 2
+#define google_firestore_v1beta1_StructuredQuery_UnaryFilter_field_tag 2
+#define google_firestore_v1beta1_StructuredQuery_UnaryFilter_op_tag 1
+#define google_firestore_v1beta1_StructuredQuery_Filter_composite_filter_tag 1
+#define google_firestore_v1beta1_StructuredQuery_Filter_field_filter_tag 2
+#define google_firestore_v1beta1_StructuredQuery_Filter_unary_filter_tag 3
+#define google_firestore_v1beta1_StructuredQuery_select_tag 1
+#define google_firestore_v1beta1_StructuredQuery_from_tag 2
+#define google_firestore_v1beta1_StructuredQuery_where_tag 3
+#define google_firestore_v1beta1_StructuredQuery_order_by_tag 4
+#define google_firestore_v1beta1_StructuredQuery_start_at_tag 7
+#define google_firestore_v1beta1_StructuredQuery_end_at_tag 8
+#define google_firestore_v1beta1_StructuredQuery_offset_tag 6
+#define google_firestore_v1beta1_StructuredQuery_limit_tag 5
+
+/* Struct field encoding specification for nanopb */
+extern const pb_field_t google_firestore_v1beta1_StructuredQuery_fields[9];
+extern const pb_field_t google_firestore_v1beta1_StructuredQuery_CollectionSelector_fields[3];
+extern const pb_field_t google_firestore_v1beta1_StructuredQuery_Filter_fields[4];
+extern const pb_field_t google_firestore_v1beta1_StructuredQuery_CompositeFilter_fields[3];
+extern const pb_field_t google_firestore_v1beta1_StructuredQuery_FieldFilter_fields[4];
+extern const pb_field_t google_firestore_v1beta1_StructuredQuery_UnaryFilter_fields[3];
+extern const pb_field_t google_firestore_v1beta1_StructuredQuery_Order_fields[3];
+extern const pb_field_t google_firestore_v1beta1_StructuredQuery_FieldReference_fields[2];
+extern const pb_field_t google_firestore_v1beta1_StructuredQuery_Projection_fields[2];
+extern const pb_field_t google_firestore_v1beta1_Cursor_fields[3];
+
+/* Maximum encoded size of messages (where known) */
+/* google_firestore_v1beta1_StructuredQuery_size depends on runtime parameters */
+/* google_firestore_v1beta1_StructuredQuery_CollectionSelector_size depends on runtime parameters */
+/* google_firestore_v1beta1_StructuredQuery_Filter_size depends on runtime parameters */
+/* google_firestore_v1beta1_StructuredQuery_CompositeFilter_size depends on runtime parameters */
+#define google_firestore_v1beta1_StructuredQuery_FieldFilter_size (14 + google_firestore_v1beta1_StructuredQuery_FieldReference_size + google_firestore_v1beta1_Value_size)
+/* google_firestore_v1beta1_StructuredQuery_UnaryFilter_size depends on runtime parameters */
+#define google_firestore_v1beta1_StructuredQuery_Order_size (8 + google_firestore_v1beta1_StructuredQuery_FieldReference_size)
+/* google_firestore_v1beta1_StructuredQuery_FieldReference_size depends on runtime parameters */
+/* google_firestore_v1beta1_StructuredQuery_Projection_size depends on runtime parameters */
+/* google_firestore_v1beta1_Cursor_size depends on runtime parameters */
+
+/* Message IDs (where set with "msgid" option) */
+#ifdef PB_MSGID
+
+#define QUERY_MESSAGES \
+
+
+#endif
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+/* @@protoc_insertion_point(eof) */
+
+#endif
diff --git a/Firestore/Protos/nanopb/google/firestore/v1beta1/write.pb.c b/Firestore/Protos/nanopb/google/firestore/v1beta1/write.pb.c
new file mode 100644
index 0000000..26542e0
--- /dev/null
+++ b/Firestore/Protos/nanopb/google/firestore/v1beta1/write.pb.c
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2018 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.
+ */
+
+/* Automatically generated nanopb constant definitions */
+/* Generated by nanopb-0.3.8 at Thu Apr 12 07:27:15 2018. */
+
+#include "write.pb.h"
+
+/* @@protoc_insertion_point(includes) */
+#if PB_PROTO_HEADER_VERSION != 30
+#error Regenerate this file with the current version of nanopb generator.
+#endif
+
+
+
+const pb_field_t google_firestore_v1beta1_Write_fields[6] = {
+ PB_FIELD( 1, MESSAGE , SINGULAR, STATIC , FIRST, google_firestore_v1beta1_Write, update, update, &google_firestore_v1beta1_Document_fields),
+ PB_FIELD( 2, STRING , SINGULAR, CALLBACK, OTHER, google_firestore_v1beta1_Write, delete_, update, 0),
+ PB_FIELD( 3, MESSAGE , SINGULAR, STATIC , OTHER, google_firestore_v1beta1_Write, update_mask, delete_, &google_firestore_v1beta1_DocumentMask_fields),
+ PB_FIELD( 4, MESSAGE , SINGULAR, STATIC , OTHER, google_firestore_v1beta1_Write, current_document, update_mask, &google_firestore_v1beta1_Precondition_fields),
+ PB_FIELD( 6, MESSAGE , SINGULAR, STATIC , OTHER, google_firestore_v1beta1_Write, transform, current_document, &google_firestore_v1beta1_DocumentTransform_fields),
+ PB_LAST_FIELD
+};
+
+const pb_field_t google_firestore_v1beta1_DocumentTransform_fields[3] = {
+ PB_FIELD( 1, STRING , SINGULAR, CALLBACK, FIRST, google_firestore_v1beta1_DocumentTransform, document, document, 0),
+ PB_FIELD( 2, MESSAGE , REPEATED, CALLBACK, OTHER, google_firestore_v1beta1_DocumentTransform, field_transforms, document, &google_firestore_v1beta1_DocumentTransform_FieldTransform_fields),
+ PB_LAST_FIELD
+};
+
+const pb_field_t google_firestore_v1beta1_DocumentTransform_FieldTransform_fields[5] = {
+ PB_FIELD( 1, STRING , SINGULAR, CALLBACK, FIRST, google_firestore_v1beta1_DocumentTransform_FieldTransform, field_path, field_path, 0),
+ PB_ONEOF_FIELD(transform_type, 2, ENUM , ONEOF, STATIC , OTHER, google_firestore_v1beta1_DocumentTransform_FieldTransform, set_to_server_value, field_path, 0),
+ PB_ONEOF_FIELD(transform_type, 6, MESSAGE , ONEOF, STATIC , UNION, google_firestore_v1beta1_DocumentTransform_FieldTransform, append_missing_elements, field_path, &google_firestore_v1beta1_ArrayValue_fields),
+ PB_ONEOF_FIELD(transform_type, 7, MESSAGE , ONEOF, STATIC , UNION, google_firestore_v1beta1_DocumentTransform_FieldTransform, remove_all_from_array, field_path, &google_firestore_v1beta1_ArrayValue_fields),
+ PB_LAST_FIELD
+};
+
+const pb_field_t google_firestore_v1beta1_WriteResult_fields[3] = {
+ PB_FIELD( 1, MESSAGE , SINGULAR, STATIC , FIRST, google_firestore_v1beta1_WriteResult, update_time, update_time, &google_protobuf_Timestamp_fields),
+ PB_FIELD( 2, MESSAGE , REPEATED, CALLBACK, OTHER, google_firestore_v1beta1_WriteResult, transform_results, update_time, &google_firestore_v1beta1_Value_fields),
+ PB_LAST_FIELD
+};
+
+const pb_field_t google_firestore_v1beta1_DocumentChange_fields[4] = {
+ PB_FIELD( 1, MESSAGE , SINGULAR, STATIC , FIRST, google_firestore_v1beta1_DocumentChange, document, document, &google_firestore_v1beta1_Document_fields),
+ PB_FIELD( 5, INT32 , REPEATED, CALLBACK, OTHER, google_firestore_v1beta1_DocumentChange, target_ids, document, 0),
+ PB_FIELD( 6, INT32 , REPEATED, CALLBACK, OTHER, google_firestore_v1beta1_DocumentChange, removed_target_ids, target_ids, 0),
+ PB_LAST_FIELD
+};
+
+const pb_field_t google_firestore_v1beta1_DocumentDelete_fields[4] = {
+ PB_FIELD( 1, STRING , SINGULAR, CALLBACK, FIRST, google_firestore_v1beta1_DocumentDelete, document, document, 0),
+ PB_FIELD( 4, MESSAGE , SINGULAR, STATIC , OTHER, google_firestore_v1beta1_DocumentDelete, read_time, document, &google_protobuf_Timestamp_fields),
+ PB_FIELD( 6, INT32 , REPEATED, CALLBACK, OTHER, google_firestore_v1beta1_DocumentDelete, removed_target_ids, read_time, 0),
+ PB_LAST_FIELD
+};
+
+const pb_field_t google_firestore_v1beta1_DocumentRemove_fields[4] = {
+ PB_FIELD( 1, STRING , SINGULAR, CALLBACK, FIRST, google_firestore_v1beta1_DocumentRemove, document, document, 0),
+ PB_FIELD( 2, INT32 , REPEATED, CALLBACK, OTHER, google_firestore_v1beta1_DocumentRemove, removed_target_ids, document, 0),
+ PB_FIELD( 4, MESSAGE , SINGULAR, STATIC , OTHER, google_firestore_v1beta1_DocumentRemove, read_time, removed_target_ids, &google_protobuf_Timestamp_fields),
+ PB_LAST_FIELD
+};
+
+const pb_field_t google_firestore_v1beta1_ExistenceFilter_fields[3] = {
+ PB_FIELD( 1, INT32 , SINGULAR, STATIC , FIRST, google_firestore_v1beta1_ExistenceFilter, target_id, target_id, 0),
+ PB_FIELD( 2, INT32 , SINGULAR, STATIC , OTHER, google_firestore_v1beta1_ExistenceFilter, count, target_id, 0),
+ PB_LAST_FIELD
+};
+
+
+
+/* Check that field information fits in pb_field_t */
+#if !defined(PB_FIELD_32BIT)
+/* If you get an error here, it means that you need to define PB_FIELD_32BIT
+ * compile-time option. You can do that in pb.h or on compiler command line.
+ *
+ * The reason you need to do this is that some of your messages contain tag
+ * numbers or field sizes that are larger than what can fit in 8 or 16 bit
+ * field descriptors.
+ */
+PB_STATIC_ASSERT((pb_membersize(google_firestore_v1beta1_Write, update) < 65536 && pb_membersize(google_firestore_v1beta1_Write, transform) < 65536 && pb_membersize(google_firestore_v1beta1_Write, update_mask) < 65536 && pb_membersize(google_firestore_v1beta1_Write, current_document) < 65536 && pb_membersize(google_firestore_v1beta1_DocumentTransform_FieldTransform, transform_type.append_missing_elements) < 65536 && pb_membersize(google_firestore_v1beta1_DocumentTransform_FieldTransform, transform_type.remove_all_from_array) < 65536 && pb_membersize(google_firestore_v1beta1_WriteResult, update_time) < 65536 && pb_membersize(google_firestore_v1beta1_DocumentChange, document) < 65536 && pb_membersize(google_firestore_v1beta1_DocumentDelete, read_time) < 65536 && pb_membersize(google_firestore_v1beta1_DocumentRemove, read_time) < 65536), YOU_MUST_DEFINE_PB_FIELD_32BIT_FOR_MESSAGES_google_firestore_v1beta1_Write_google_firestore_v1beta1_DocumentTransform_google_firestore_v1beta1_DocumentTransform_FieldTransform_google_firestore_v1beta1_WriteResult_google_firestore_v1beta1_DocumentChange_google_firestore_v1beta1_DocumentDelete_google_firestore_v1beta1_DocumentRemove_google_firestore_v1beta1_ExistenceFilter)
+#endif
+
+#if !defined(PB_FIELD_16BIT) && !defined(PB_FIELD_32BIT)
+/* If you get an error here, it means that you need to define PB_FIELD_16BIT
+ * compile-time option. You can do that in pb.h or on compiler command line.
+ *
+ * The reason you need to do this is that some of your messages contain tag
+ * numbers or field sizes that are larger than what can fit in the default
+ * 8 bit descriptors.
+ */
+PB_STATIC_ASSERT((pb_membersize(google_firestore_v1beta1_Write, update) < 256 && pb_membersize(google_firestore_v1beta1_Write, transform) < 256 && pb_membersize(google_firestore_v1beta1_Write, update_mask) < 256 && pb_membersize(google_firestore_v1beta1_Write, current_document) < 256 && pb_membersize(google_firestore_v1beta1_DocumentTransform_FieldTransform, transform_type.append_missing_elements) < 256 && pb_membersize(google_firestore_v1beta1_DocumentTransform_FieldTransform, transform_type.remove_all_from_array) < 256 && pb_membersize(google_firestore_v1beta1_WriteResult, update_time) < 256 && pb_membersize(google_firestore_v1beta1_DocumentChange, document) < 256 && pb_membersize(google_firestore_v1beta1_DocumentDelete, read_time) < 256 && pb_membersize(google_firestore_v1beta1_DocumentRemove, read_time) < 256), YOU_MUST_DEFINE_PB_FIELD_16BIT_FOR_MESSAGES_google_firestore_v1beta1_Write_google_firestore_v1beta1_DocumentTransform_google_firestore_v1beta1_DocumentTransform_FieldTransform_google_firestore_v1beta1_WriteResult_google_firestore_v1beta1_DocumentChange_google_firestore_v1beta1_DocumentDelete_google_firestore_v1beta1_DocumentRemove_google_firestore_v1beta1_ExistenceFilter)
+#endif
+
+
+/* @@protoc_insertion_point(eof) */
diff --git a/Firestore/Protos/nanopb/google/firestore/v1beta1/write.pb.h b/Firestore/Protos/nanopb/google/firestore/v1beta1/write.pb.h
new file mode 100644
index 0000000..5a272b2
--- /dev/null
+++ b/Firestore/Protos/nanopb/google/firestore/v1beta1/write.pb.h
@@ -0,0 +1,189 @@
+/*
+ * Copyright 2018 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.
+ */
+
+/* Automatically generated nanopb header */
+/* Generated by nanopb-0.3.8 at Thu Apr 12 07:27:15 2018. */
+
+#ifndef PB_GOOGLE_FIRESTORE_V1BETA1_WRITE_PB_H_INCLUDED
+#define PB_GOOGLE_FIRESTORE_V1BETA1_WRITE_PB_H_INCLUDED
+#include <pb.h>
+
+#include "google/api/annotations.pb.h"
+
+#include "google/firestore/v1beta1/common.pb.h"
+
+#include "google/firestore/v1beta1/document.pb.h"
+
+#include "google/protobuf/timestamp.pb.h"
+
+/* @@protoc_insertion_point(includes) */
+#if PB_PROTO_HEADER_VERSION != 30
+#error Regenerate this file with the current version of nanopb generator.
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Enum definitions */
+typedef enum _google_firestore_v1beta1_DocumentTransform_FieldTransform_ServerValue {
+ google_firestore_v1beta1_DocumentTransform_FieldTransform_ServerValue_SERVER_VALUE_UNSPECIFIED = 0,
+ google_firestore_v1beta1_DocumentTransform_FieldTransform_ServerValue_REQUEST_TIME = 1
+} google_firestore_v1beta1_DocumentTransform_FieldTransform_ServerValue;
+#define _google_firestore_v1beta1_DocumentTransform_FieldTransform_ServerValue_MIN google_firestore_v1beta1_DocumentTransform_FieldTransform_ServerValue_SERVER_VALUE_UNSPECIFIED
+#define _google_firestore_v1beta1_DocumentTransform_FieldTransform_ServerValue_MAX google_firestore_v1beta1_DocumentTransform_FieldTransform_ServerValue_REQUEST_TIME
+#define _google_firestore_v1beta1_DocumentTransform_FieldTransform_ServerValue_ARRAYSIZE ((google_firestore_v1beta1_DocumentTransform_FieldTransform_ServerValue)(google_firestore_v1beta1_DocumentTransform_FieldTransform_ServerValue_REQUEST_TIME+1))
+
+/* Struct definitions */
+typedef struct _google_firestore_v1beta1_DocumentTransform {
+ pb_callback_t document;
+ pb_callback_t field_transforms;
+/* @@protoc_insertion_point(struct:google_firestore_v1beta1_DocumentTransform) */
+} google_firestore_v1beta1_DocumentTransform;
+
+typedef struct _google_firestore_v1beta1_DocumentChange {
+ google_firestore_v1beta1_Document document;
+ pb_callback_t target_ids;
+ pb_callback_t removed_target_ids;
+/* @@protoc_insertion_point(struct:google_firestore_v1beta1_DocumentChange) */
+} google_firestore_v1beta1_DocumentChange;
+
+typedef struct _google_firestore_v1beta1_DocumentDelete {
+ pb_callback_t document;
+ google_protobuf_Timestamp read_time;
+ pb_callback_t removed_target_ids;
+/* @@protoc_insertion_point(struct:google_firestore_v1beta1_DocumentDelete) */
+} google_firestore_v1beta1_DocumentDelete;
+
+typedef struct _google_firestore_v1beta1_DocumentRemove {
+ pb_callback_t document;
+ pb_callback_t removed_target_ids;
+ google_protobuf_Timestamp read_time;
+/* @@protoc_insertion_point(struct:google_firestore_v1beta1_DocumentRemove) */
+} google_firestore_v1beta1_DocumentRemove;
+
+typedef struct _google_firestore_v1beta1_DocumentTransform_FieldTransform {
+ pb_callback_t field_path;
+ pb_size_t which_transform_type;
+ union {
+ google_firestore_v1beta1_DocumentTransform_FieldTransform_ServerValue set_to_server_value;
+ google_firestore_v1beta1_ArrayValue append_missing_elements;
+ google_firestore_v1beta1_ArrayValue remove_all_from_array;
+ } transform_type;
+/* @@protoc_insertion_point(struct:google_firestore_v1beta1_DocumentTransform_FieldTransform) */
+} google_firestore_v1beta1_DocumentTransform_FieldTransform;
+
+typedef struct _google_firestore_v1beta1_ExistenceFilter {
+ int32_t target_id;
+ int32_t count;
+/* @@protoc_insertion_point(struct:google_firestore_v1beta1_ExistenceFilter) */
+} google_firestore_v1beta1_ExistenceFilter;
+
+typedef struct _google_firestore_v1beta1_Write {
+ google_firestore_v1beta1_Document update;
+ pb_callback_t delete_;
+ google_firestore_v1beta1_DocumentMask update_mask;
+ google_firestore_v1beta1_Precondition current_document;
+ google_firestore_v1beta1_DocumentTransform transform;
+/* @@protoc_insertion_point(struct:google_firestore_v1beta1_Write) */
+} google_firestore_v1beta1_Write;
+
+typedef struct _google_firestore_v1beta1_WriteResult {
+ google_protobuf_Timestamp update_time;
+ pb_callback_t transform_results;
+/* @@protoc_insertion_point(struct:google_firestore_v1beta1_WriteResult) */
+} google_firestore_v1beta1_WriteResult;
+
+/* Default values for struct fields */
+
+/* Initializer values for message structs */
+#define google_firestore_v1beta1_Write_init_default {google_firestore_v1beta1_Document_init_default, {{NULL}, NULL}, google_firestore_v1beta1_DocumentMask_init_default, google_firestore_v1beta1_Precondition_init_default, google_firestore_v1beta1_DocumentTransform_init_default}
+#define google_firestore_v1beta1_DocumentTransform_init_default {{{NULL}, NULL}, {{NULL}, NULL}}
+#define google_firestore_v1beta1_DocumentTransform_FieldTransform_init_default {{{NULL}, NULL}, 0, {(google_firestore_v1beta1_DocumentTransform_FieldTransform_ServerValue)0}}
+#define google_firestore_v1beta1_WriteResult_init_default {google_protobuf_Timestamp_init_default, {{NULL}, NULL}}
+#define google_firestore_v1beta1_DocumentChange_init_default {google_firestore_v1beta1_Document_init_default, {{NULL}, NULL}, {{NULL}, NULL}}
+#define google_firestore_v1beta1_DocumentDelete_init_default {{{NULL}, NULL}, google_protobuf_Timestamp_init_default, {{NULL}, NULL}}
+#define google_firestore_v1beta1_DocumentRemove_init_default {{{NULL}, NULL}, {{NULL}, NULL}, google_protobuf_Timestamp_init_default}
+#define google_firestore_v1beta1_ExistenceFilter_init_default {0, 0}
+#define google_firestore_v1beta1_Write_init_zero {google_firestore_v1beta1_Document_init_zero, {{NULL}, NULL}, google_firestore_v1beta1_DocumentMask_init_zero, google_firestore_v1beta1_Precondition_init_zero, google_firestore_v1beta1_DocumentTransform_init_zero}
+#define google_firestore_v1beta1_DocumentTransform_init_zero {{{NULL}, NULL}, {{NULL}, NULL}}
+#define google_firestore_v1beta1_DocumentTransform_FieldTransform_init_zero {{{NULL}, NULL}, 0, {(google_firestore_v1beta1_DocumentTransform_FieldTransform_ServerValue)0}}
+#define google_firestore_v1beta1_WriteResult_init_zero {google_protobuf_Timestamp_init_zero, {{NULL}, NULL}}
+#define google_firestore_v1beta1_DocumentChange_init_zero {google_firestore_v1beta1_Document_init_zero, {{NULL}, NULL}, {{NULL}, NULL}}
+#define google_firestore_v1beta1_DocumentDelete_init_zero {{{NULL}, NULL}, google_protobuf_Timestamp_init_zero, {{NULL}, NULL}}
+#define google_firestore_v1beta1_DocumentRemove_init_zero {{{NULL}, NULL}, {{NULL}, NULL}, google_protobuf_Timestamp_init_zero}
+#define google_firestore_v1beta1_ExistenceFilter_init_zero {0, 0}
+
+/* Field tags (for use in manual encoding/decoding) */
+#define google_firestore_v1beta1_DocumentTransform_document_tag 1
+#define google_firestore_v1beta1_DocumentTransform_field_transforms_tag 2
+#define google_firestore_v1beta1_DocumentChange_document_tag 1
+#define google_firestore_v1beta1_DocumentChange_target_ids_tag 5
+#define google_firestore_v1beta1_DocumentChange_removed_target_ids_tag 6
+#define google_firestore_v1beta1_DocumentDelete_document_tag 1
+#define google_firestore_v1beta1_DocumentDelete_removed_target_ids_tag 6
+#define google_firestore_v1beta1_DocumentDelete_read_time_tag 4
+#define google_firestore_v1beta1_DocumentRemove_document_tag 1
+#define google_firestore_v1beta1_DocumentRemove_removed_target_ids_tag 2
+#define google_firestore_v1beta1_DocumentRemove_read_time_tag 4
+#define google_firestore_v1beta1_DocumentTransform_FieldTransform_set_to_server_value_tag 2
+#define google_firestore_v1beta1_DocumentTransform_FieldTransform_append_missing_elements_tag 6
+#define google_firestore_v1beta1_DocumentTransform_FieldTransform_remove_all_from_array_tag 7
+#define google_firestore_v1beta1_DocumentTransform_FieldTransform_field_path_tag 1
+#define google_firestore_v1beta1_ExistenceFilter_target_id_tag 1
+#define google_firestore_v1beta1_ExistenceFilter_count_tag 2
+#define google_firestore_v1beta1_Write_update_tag 1
+#define google_firestore_v1beta1_Write_delete_tag 2
+#define google_firestore_v1beta1_Write_transform_tag 6
+#define google_firestore_v1beta1_Write_update_mask_tag 3
+#define google_firestore_v1beta1_Write_current_document_tag 4
+#define google_firestore_v1beta1_WriteResult_update_time_tag 1
+#define google_firestore_v1beta1_WriteResult_transform_results_tag 2
+
+/* Struct field encoding specification for nanopb */
+extern const pb_field_t google_firestore_v1beta1_Write_fields[6];
+extern const pb_field_t google_firestore_v1beta1_DocumentTransform_fields[3];
+extern const pb_field_t google_firestore_v1beta1_DocumentTransform_FieldTransform_fields[5];
+extern const pb_field_t google_firestore_v1beta1_WriteResult_fields[3];
+extern const pb_field_t google_firestore_v1beta1_DocumentChange_fields[4];
+extern const pb_field_t google_firestore_v1beta1_DocumentDelete_fields[4];
+extern const pb_field_t google_firestore_v1beta1_DocumentRemove_fields[4];
+extern const pb_field_t google_firestore_v1beta1_ExistenceFilter_fields[3];
+
+/* Maximum encoded size of messages (where known) */
+/* google_firestore_v1beta1_Write_size depends on runtime parameters */
+/* google_firestore_v1beta1_DocumentTransform_size depends on runtime parameters */
+/* google_firestore_v1beta1_DocumentTransform_FieldTransform_size depends on runtime parameters */
+/* google_firestore_v1beta1_WriteResult_size depends on runtime parameters */
+/* google_firestore_v1beta1_DocumentChange_size depends on runtime parameters */
+/* google_firestore_v1beta1_DocumentDelete_size depends on runtime parameters */
+/* google_firestore_v1beta1_DocumentRemove_size depends on runtime parameters */
+#define google_firestore_v1beta1_ExistenceFilter_size 22
+
+/* Message IDs (where set with "msgid" option) */
+#ifdef PB_MSGID
+
+#define WRITE_MESSAGES \
+
+
+#endif
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+/* @@protoc_insertion_point(eof) */
+
+#endif
diff --git a/Firestore/Protos/nanopb/google/protobuf/any.pb.c b/Firestore/Protos/nanopb/google/protobuf/any.pb.c
new file mode 100644
index 0000000..b28d0ba
--- /dev/null
+++ b/Firestore/Protos/nanopb/google/protobuf/any.pb.c
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2018 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.
+ */
+
+/* Automatically generated nanopb constant definitions */
+/* Generated by nanopb-0.3.8 at Mon Feb 12 11:03:06 2018. */
+
+#include "any.pb.h"
+
+/* @@protoc_insertion_point(includes) */
+#if PB_PROTO_HEADER_VERSION != 30
+#error Regenerate this file with the current version of nanopb generator.
+#endif
+
+
+
+const pb_field_t google_protobuf_Any_fields[3] = {
+ PB_FIELD( 1, STRING , SINGULAR, CALLBACK, FIRST, google_protobuf_Any, type_url, type_url, 0),
+ PB_FIELD( 2, BYTES , SINGULAR, CALLBACK, OTHER, google_protobuf_Any, value, type_url, 0),
+ PB_LAST_FIELD
+};
+
+
+/* @@protoc_insertion_point(eof) */
diff --git a/Firestore/Protos/nanopb/google/protobuf/any.pb.h b/Firestore/Protos/nanopb/google/protobuf/any.pb.h
new file mode 100644
index 0000000..10a722e
--- /dev/null
+++ b/Firestore/Protos/nanopb/google/protobuf/any.pb.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2018 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.
+ */
+
+/* Automatically generated nanopb header */
+/* Generated by nanopb-0.3.8 at Mon Feb 12 11:03:06 2018. */
+
+#ifndef PB_GOOGLE_PROTOBUF_ANY_PB_H_INCLUDED
+#define PB_GOOGLE_PROTOBUF_ANY_PB_H_INCLUDED
+#include <pb.h>
+
+/* @@protoc_insertion_point(includes) */
+#if PB_PROTO_HEADER_VERSION != 30
+#error Regenerate this file with the current version of nanopb generator.
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Struct definitions */
+typedef struct _google_protobuf_Any {
+ pb_callback_t type_url;
+ pb_callback_t value;
+/* @@protoc_insertion_point(struct:google_protobuf_Any) */
+} google_protobuf_Any;
+
+/* Default values for struct fields */
+
+/* Initializer values for message structs */
+#define google_protobuf_Any_init_default {{{NULL}, NULL}, {{NULL}, NULL}}
+#define google_protobuf_Any_init_zero {{{NULL}, NULL}, {{NULL}, NULL}}
+
+/* Field tags (for use in manual encoding/decoding) */
+#define google_protobuf_Any_type_url_tag 1
+#define google_protobuf_Any_value_tag 2
+
+/* Struct field encoding specification for nanopb */
+extern const pb_field_t google_protobuf_Any_fields[3];
+
+/* Maximum encoded size of messages (where known) */
+/* google_protobuf_Any_size depends on runtime parameters */
+
+/* Message IDs (where set with "msgid" option) */
+#ifdef PB_MSGID
+
+#define ANY_MESSAGES \
+
+
+#endif
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+/* @@protoc_insertion_point(eof) */
+
+#endif
diff --git a/Firestore/Protos/nanopb/google/protobuf/empty.pb.c b/Firestore/Protos/nanopb/google/protobuf/empty.pb.c
new file mode 100644
index 0000000..050af9c
--- /dev/null
+++ b/Firestore/Protos/nanopb/google/protobuf/empty.pb.c
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2018 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.
+ */
+
+/* Automatically generated nanopb constant definitions */
+/* Generated by nanopb-0.3.8 at Mon Feb 12 11:03:06 2018. */
+
+#include "empty.pb.h"
+
+/* @@protoc_insertion_point(includes) */
+#if PB_PROTO_HEADER_VERSION != 30
+#error Regenerate this file with the current version of nanopb generator.
+#endif
+
+
+
+const pb_field_t google_protobuf_Empty_fields[1] = {
+ PB_LAST_FIELD
+};
+
+
+/* @@protoc_insertion_point(eof) */
diff --git a/Firestore/Protos/nanopb/google/protobuf/empty.pb.h b/Firestore/Protos/nanopb/google/protobuf/empty.pb.h
new file mode 100644
index 0000000..466e1fd
--- /dev/null
+++ b/Firestore/Protos/nanopb/google/protobuf/empty.pb.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2018 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.
+ */
+
+/* Automatically generated nanopb header */
+/* Generated by nanopb-0.3.8 at Mon Feb 12 11:03:06 2018. */
+
+#ifndef PB_GOOGLE_PROTOBUF_EMPTY_PB_H_INCLUDED
+#define PB_GOOGLE_PROTOBUF_EMPTY_PB_H_INCLUDED
+#include <pb.h>
+
+/* @@protoc_insertion_point(includes) */
+#if PB_PROTO_HEADER_VERSION != 30
+#error Regenerate this file with the current version of nanopb generator.
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Struct definitions */
+typedef struct _google_protobuf_Empty {
+ char dummy_field;
+/* @@protoc_insertion_point(struct:google_protobuf_Empty) */
+} google_protobuf_Empty;
+
+/* Default values for struct fields */
+
+/* Initializer values for message structs */
+#define google_protobuf_Empty_init_default {0}
+#define google_protobuf_Empty_init_zero {0}
+
+/* Field tags (for use in manual encoding/decoding) */
+
+/* Struct field encoding specification for nanopb */
+extern const pb_field_t google_protobuf_Empty_fields[1];
+
+/* Maximum encoded size of messages (where known) */
+#define google_protobuf_Empty_size 0
+
+/* Message IDs (where set with "msgid" option) */
+#ifdef PB_MSGID
+
+#define EMPTY_MESSAGES \
+
+
+#endif
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+/* @@protoc_insertion_point(eof) */
+
+#endif
diff --git a/Firestore/Protos/nanopb/google/protobuf/struct.pb.c b/Firestore/Protos/nanopb/google/protobuf/struct.pb.c
new file mode 100644
index 0000000..2826aab
--- /dev/null
+++ b/Firestore/Protos/nanopb/google/protobuf/struct.pb.c
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2018 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.
+ */
+
+/* Automatically generated nanopb constant definitions */
+/* Generated by nanopb-0.3.8 at Fri Feb 2 17:48:02 2018. */
+
+#include "struct.pb.h"
+
+/* @@protoc_insertion_point(includes) */
+#if PB_PROTO_HEADER_VERSION != 30
+#error Regenerate this file with the current version of nanopb generator.
+#endif
+
+
+
+const pb_field_t google_protobuf_Struct_fields[2] = {
+ PB_FIELD( 1, MESSAGE , REPEATED, CALLBACK, FIRST, google_protobuf_Struct, fields, fields, &google_protobuf_Struct_FieldsEntry_fields),
+ PB_LAST_FIELD
+};
+
+const pb_field_t google_protobuf_Struct_FieldsEntry_fields[3] = {
+ PB_FIELD( 1, STRING , SINGULAR, CALLBACK, FIRST, google_protobuf_Struct_FieldsEntry, key, key, 0),
+ PB_FIELD( 2, MESSAGE , SINGULAR, STATIC , OTHER, google_protobuf_Struct_FieldsEntry, value, key, &google_protobuf_Value_fields),
+ PB_LAST_FIELD
+};
+
+const pb_field_t google_protobuf_Value_fields[7] = {
+ PB_FIELD( 1, UENUM , SINGULAR, STATIC , FIRST, google_protobuf_Value, null_value, null_value, 0),
+ PB_FIELD( 2, DOUBLE , SINGULAR, STATIC , OTHER, google_protobuf_Value, number_value, null_value, 0),
+ PB_FIELD( 3, STRING , SINGULAR, CALLBACK, OTHER, google_protobuf_Value, string_value, number_value, 0),
+ PB_FIELD( 4, BOOL , SINGULAR, STATIC , OTHER, google_protobuf_Value, bool_value, string_value, 0),
+ PB_FIELD( 5, MESSAGE , SINGULAR, STATIC , OTHER, google_protobuf_Value, struct_value, bool_value, &google_protobuf_Struct_fields),
+ PB_FIELD( 6, MESSAGE , SINGULAR, STATIC , OTHER, google_protobuf_Value, list_value, struct_value, &google_protobuf_ListValue_fields),
+ PB_LAST_FIELD
+};
+
+const pb_field_t google_protobuf_ListValue_fields[2] = {
+ PB_FIELD( 1, MESSAGE , REPEATED, CALLBACK, FIRST, google_protobuf_ListValue, values, values, &google_protobuf_Value_fields),
+ PB_LAST_FIELD
+};
+
+
+
+/* Check that field information fits in pb_field_t */
+#if !defined(PB_FIELD_32BIT)
+/* If you get an error here, it means that you need to define PB_FIELD_32BIT
+ * compile-time option. You can do that in pb.h or on compiler command line.
+ *
+ * The reason you need to do this is that some of your messages contain tag
+ * numbers or field sizes that are larger than what can fit in 8 or 16 bit
+ * field descriptors.
+ */
+PB_STATIC_ASSERT((pb_membersize(google_protobuf_Struct_FieldsEntry, value) < 65536 && pb_membersize(google_protobuf_Value, struct_value) < 65536 && pb_membersize(google_protobuf_Value, list_value) < 65536), YOU_MUST_DEFINE_PB_FIELD_32BIT_FOR_MESSAGES_google_protobuf_Struct_google_protobuf_Struct_FieldsEntry_google_protobuf_Value_google_protobuf_ListValue)
+#endif
+
+#if !defined(PB_FIELD_16BIT) && !defined(PB_FIELD_32BIT)
+/* If you get an error here, it means that you need to define PB_FIELD_16BIT
+ * compile-time option. You can do that in pb.h or on compiler command line.
+ *
+ * The reason you need to do this is that some of your messages contain tag
+ * numbers or field sizes that are larger than what can fit in the default
+ * 8 bit descriptors.
+ */
+PB_STATIC_ASSERT((pb_membersize(google_protobuf_Struct_FieldsEntry, value) < 256 && pb_membersize(google_protobuf_Value, struct_value) < 256 && pb_membersize(google_protobuf_Value, list_value) < 256), YOU_MUST_DEFINE_PB_FIELD_16BIT_FOR_MESSAGES_google_protobuf_Struct_google_protobuf_Struct_FieldsEntry_google_protobuf_Value_google_protobuf_ListValue)
+#endif
+
+
+/* On some platforms (such as AVR), double is really float.
+ * These are not directly supported by nanopb, but see example_avr_double.
+ * To get rid of this error, remove any double fields from your .proto.
+ */
+PB_STATIC_ASSERT(sizeof(double) == 8, DOUBLE_MUST_BE_8_BYTES)
+
+/* @@protoc_insertion_point(eof) */
diff --git a/Firestore/Protos/nanopb/google/protobuf/struct.pb.h b/Firestore/Protos/nanopb/google/protobuf/struct.pb.h
new file mode 100644
index 0000000..b325b60
--- /dev/null
+++ b/Firestore/Protos/nanopb/google/protobuf/struct.pb.h
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2018 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.
+ */
+
+/* Automatically generated nanopb header */
+/* Generated by nanopb-0.3.8 at Fri Feb 2 17:48:02 2018. */
+
+#ifndef PB_GOOGLE_PROTOBUF_STRUCT_PB_H_INCLUDED
+#define PB_GOOGLE_PROTOBUF_STRUCT_PB_H_INCLUDED
+#include <pb.h>
+
+/* @@protoc_insertion_point(includes) */
+#if PB_PROTO_HEADER_VERSION != 30
+#error Regenerate this file with the current version of nanopb generator.
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Enum definitions */
+typedef enum _google_protobuf_NullValue {
+ google_protobuf_NullValue_NULL_VALUE = 0
+} google_protobuf_NullValue;
+#define _google_protobuf_NullValue_MIN google_protobuf_NullValue_NULL_VALUE
+#define _google_protobuf_NullValue_MAX google_protobuf_NullValue_NULL_VALUE
+#define _google_protobuf_NullValue_ARRAYSIZE ((google_protobuf_NullValue)(google_protobuf_NullValue_NULL_VALUE+1))
+
+/* Struct definitions */
+typedef struct _google_protobuf_ListValue {
+ pb_callback_t values;
+/* @@protoc_insertion_point(struct:google_protobuf_ListValue) */
+} google_protobuf_ListValue;
+
+typedef struct _google_protobuf_Struct {
+ pb_callback_t fields;
+/* @@protoc_insertion_point(struct:google_protobuf_Struct) */
+} google_protobuf_Struct;
+
+typedef struct _google_protobuf_Value {
+ google_protobuf_NullValue null_value;
+ double number_value;
+ pb_callback_t string_value;
+ bool bool_value;
+ google_protobuf_Struct struct_value;
+ google_protobuf_ListValue list_value;
+/* @@protoc_insertion_point(struct:google_protobuf_Value) */
+} google_protobuf_Value;
+
+typedef struct _google_protobuf_Struct_FieldsEntry {
+ pb_callback_t key;
+ google_protobuf_Value value;
+/* @@protoc_insertion_point(struct:google_protobuf_Struct_FieldsEntry) */
+} google_protobuf_Struct_FieldsEntry;
+
+/* Default values for struct fields */
+
+/* Initializer values for message structs */
+#define google_protobuf_Struct_init_default {{{NULL}, NULL}}
+#define google_protobuf_Struct_FieldsEntry_init_default {{{NULL}, NULL}, google_protobuf_Value_init_default}
+#define google_protobuf_Value_init_default {(google_protobuf_NullValue)0, 0, {{NULL}, NULL}, 0, google_protobuf_Struct_init_default, google_protobuf_ListValue_init_default}
+#define google_protobuf_ListValue_init_default {{{NULL}, NULL}}
+#define google_protobuf_Struct_init_zero {{{NULL}, NULL}}
+#define google_protobuf_Struct_FieldsEntry_init_zero {{{NULL}, NULL}, google_protobuf_Value_init_zero}
+#define google_protobuf_Value_init_zero {(google_protobuf_NullValue)0, 0, {{NULL}, NULL}, 0, google_protobuf_Struct_init_zero, google_protobuf_ListValue_init_zero}
+#define google_protobuf_ListValue_init_zero {{{NULL}, NULL}}
+
+/* Field tags (for use in manual encoding/decoding) */
+#define google_protobuf_ListValue_values_tag 1
+#define google_protobuf_Struct_fields_tag 1
+#define google_protobuf_Value_null_value_tag 1
+#define google_protobuf_Value_number_value_tag 2
+#define google_protobuf_Value_string_value_tag 3
+#define google_protobuf_Value_bool_value_tag 4
+#define google_protobuf_Value_struct_value_tag 5
+#define google_protobuf_Value_list_value_tag 6
+#define google_protobuf_Struct_FieldsEntry_key_tag 1
+#define google_protobuf_Struct_FieldsEntry_value_tag 2
+
+/* Struct field encoding specification for nanopb */
+extern const pb_field_t google_protobuf_Struct_fields[2];
+extern const pb_field_t google_protobuf_Struct_FieldsEntry_fields[3];
+extern const pb_field_t google_protobuf_Value_fields[7];
+extern const pb_field_t google_protobuf_ListValue_fields[2];
+
+/* Maximum encoded size of messages (where known) */
+/* google_protobuf_Struct_size depends on runtime parameters */
+/* google_protobuf_Struct_FieldsEntry_size depends on runtime parameters */
+/* google_protobuf_Value_size depends on runtime parameters */
+/* google_protobuf_ListValue_size depends on runtime parameters */
+
+/* Message IDs (where set with "msgid" option) */
+#ifdef PB_MSGID
+
+#define STRUCT_MESSAGES \
+
+
+#endif
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+/* @@protoc_insertion_point(eof) */
+
+#endif
diff --git a/Firestore/Protos/nanopb/google/protobuf/timestamp.pb.c b/Firestore/Protos/nanopb/google/protobuf/timestamp.pb.c
new file mode 100644
index 0000000..4f03c19
--- /dev/null
+++ b/Firestore/Protos/nanopb/google/protobuf/timestamp.pb.c
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2018 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.
+ */
+
+/* Automatically generated nanopb constant definitions */
+/* Generated by nanopb-0.3.8 at Fri Feb 2 17:48:02 2018. */
+
+#include "timestamp.pb.h"
+
+/* @@protoc_insertion_point(includes) */
+#if PB_PROTO_HEADER_VERSION != 30
+#error Regenerate this file with the current version of nanopb generator.
+#endif
+
+
+
+const pb_field_t google_protobuf_Timestamp_fields[3] = {
+ PB_FIELD( 1, INT64 , SINGULAR, STATIC , FIRST, google_protobuf_Timestamp, seconds, seconds, 0),
+ PB_FIELD( 2, INT32 , SINGULAR, STATIC , OTHER, google_protobuf_Timestamp, nanos, seconds, 0),
+ PB_LAST_FIELD
+};
+
+
+/* @@protoc_insertion_point(eof) */
diff --git a/Firestore/Protos/nanopb/google/protobuf/timestamp.pb.h b/Firestore/Protos/nanopb/google/protobuf/timestamp.pb.h
new file mode 100644
index 0000000..d7be977
--- /dev/null
+++ b/Firestore/Protos/nanopb/google/protobuf/timestamp.pb.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2018 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.
+ */
+
+/* Automatically generated nanopb header */
+/* Generated by nanopb-0.3.8 at Fri Feb 2 17:48:02 2018. */
+
+#ifndef PB_GOOGLE_PROTOBUF_TIMESTAMP_PB_H_INCLUDED
+#define PB_GOOGLE_PROTOBUF_TIMESTAMP_PB_H_INCLUDED
+#include <pb.h>
+
+/* @@protoc_insertion_point(includes) */
+#if PB_PROTO_HEADER_VERSION != 30
+#error Regenerate this file with the current version of nanopb generator.
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Struct definitions */
+typedef struct _google_protobuf_Timestamp {
+ int64_t seconds;
+ int32_t nanos;
+/* @@protoc_insertion_point(struct:google_protobuf_Timestamp) */
+} google_protobuf_Timestamp;
+
+/* Default values for struct fields */
+
+/* Initializer values for message structs */
+#define google_protobuf_Timestamp_init_default {0, 0}
+#define google_protobuf_Timestamp_init_zero {0, 0}
+
+/* Field tags (for use in manual encoding/decoding) */
+#define google_protobuf_Timestamp_seconds_tag 1
+#define google_protobuf_Timestamp_nanos_tag 2
+
+/* Struct field encoding specification for nanopb */
+extern const pb_field_t google_protobuf_Timestamp_fields[3];
+
+/* Maximum encoded size of messages (where known) */
+#define google_protobuf_Timestamp_size 22
+
+/* Message IDs (where set with "msgid" option) */
+#ifdef PB_MSGID
+
+#define TIMESTAMP_MESSAGES \
+
+
+#endif
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+/* @@protoc_insertion_point(eof) */
+
+#endif
diff --git a/Firestore/Protos/nanopb/google/protobuf/wrappers.pb.c b/Firestore/Protos/nanopb/google/protobuf/wrappers.pb.c
new file mode 100644
index 0000000..41ab3c6
--- /dev/null
+++ b/Firestore/Protos/nanopb/google/protobuf/wrappers.pb.c
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2018 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.
+ */
+
+/* Automatically generated nanopb constant definitions */
+/* Generated by nanopb-0.3.8 at Mon Feb 12 11:03:06 2018. */
+
+#include "wrappers.pb.h"
+
+/* @@protoc_insertion_point(includes) */
+#if PB_PROTO_HEADER_VERSION != 30
+#error Regenerate this file with the current version of nanopb generator.
+#endif
+
+
+
+const pb_field_t google_protobuf_DoubleValue_fields[2] = {
+ PB_FIELD( 1, DOUBLE , SINGULAR, STATIC , FIRST, google_protobuf_DoubleValue, value, value, 0),
+ PB_LAST_FIELD
+};
+
+const pb_field_t google_protobuf_FloatValue_fields[2] = {
+ PB_FIELD( 1, FLOAT , SINGULAR, STATIC , FIRST, google_protobuf_FloatValue, value, value, 0),
+ PB_LAST_FIELD
+};
+
+const pb_field_t google_protobuf_Int64Value_fields[2] = {
+ PB_FIELD( 1, INT64 , SINGULAR, STATIC , FIRST, google_protobuf_Int64Value, value, value, 0),
+ PB_LAST_FIELD
+};
+
+const pb_field_t google_protobuf_UInt64Value_fields[2] = {
+ PB_FIELD( 1, UINT64 , SINGULAR, STATIC , FIRST, google_protobuf_UInt64Value, value, value, 0),
+ PB_LAST_FIELD
+};
+
+const pb_field_t google_protobuf_Int32Value_fields[2] = {
+ PB_FIELD( 1, INT32 , SINGULAR, STATIC , FIRST, google_protobuf_Int32Value, value, value, 0),
+ PB_LAST_FIELD
+};
+
+const pb_field_t google_protobuf_UInt32Value_fields[2] = {
+ PB_FIELD( 1, UINT32 , SINGULAR, STATIC , FIRST, google_protobuf_UInt32Value, value, value, 0),
+ PB_LAST_FIELD
+};
+
+const pb_field_t google_protobuf_BoolValue_fields[2] = {
+ PB_FIELD( 1, BOOL , SINGULAR, STATIC , FIRST, google_protobuf_BoolValue, value, value, 0),
+ PB_LAST_FIELD
+};
+
+const pb_field_t google_protobuf_StringValue_fields[2] = {
+ PB_FIELD( 1, STRING , SINGULAR, CALLBACK, FIRST, google_protobuf_StringValue, value, value, 0),
+ PB_LAST_FIELD
+};
+
+const pb_field_t google_protobuf_BytesValue_fields[2] = {
+ PB_FIELD( 1, BYTES , SINGULAR, CALLBACK, FIRST, google_protobuf_BytesValue, value, value, 0),
+ PB_LAST_FIELD
+};
+
+
+/* On some platforms (such as AVR), double is really float.
+ * These are not directly supported by nanopb, but see example_avr_double.
+ * To get rid of this error, remove any double fields from your .proto.
+ */
+PB_STATIC_ASSERT(sizeof(double) == 8, DOUBLE_MUST_BE_8_BYTES)
+
+/* @@protoc_insertion_point(eof) */
diff --git a/Firestore/Protos/nanopb/google/protobuf/wrappers.pb.h b/Firestore/Protos/nanopb/google/protobuf/wrappers.pb.h
new file mode 100644
index 0000000..0e98785
--- /dev/null
+++ b/Firestore/Protos/nanopb/google/protobuf/wrappers.pb.h
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2018 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.
+ */
+
+/* Automatically generated nanopb header */
+/* Generated by nanopb-0.3.8 at Mon Feb 12 11:03:06 2018. */
+
+#ifndef PB_GOOGLE_PROTOBUF_WRAPPERS_PB_H_INCLUDED
+#define PB_GOOGLE_PROTOBUF_WRAPPERS_PB_H_INCLUDED
+#include <pb.h>
+
+/* @@protoc_insertion_point(includes) */
+#if PB_PROTO_HEADER_VERSION != 30
+#error Regenerate this file with the current version of nanopb generator.
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Struct definitions */
+typedef struct _google_protobuf_BytesValue {
+ pb_callback_t value;
+/* @@protoc_insertion_point(struct:google_protobuf_BytesValue) */
+} google_protobuf_BytesValue;
+
+typedef struct _google_protobuf_StringValue {
+ pb_callback_t value;
+/* @@protoc_insertion_point(struct:google_protobuf_StringValue) */
+} google_protobuf_StringValue;
+
+typedef struct _google_protobuf_BoolValue {
+ bool value;
+/* @@protoc_insertion_point(struct:google_protobuf_BoolValue) */
+} google_protobuf_BoolValue;
+
+typedef struct _google_protobuf_DoubleValue {
+ double value;
+/* @@protoc_insertion_point(struct:google_protobuf_DoubleValue) */
+} google_protobuf_DoubleValue;
+
+typedef struct _google_protobuf_FloatValue {
+ float value;
+/* @@protoc_insertion_point(struct:google_protobuf_FloatValue) */
+} google_protobuf_FloatValue;
+
+typedef struct _google_protobuf_Int32Value {
+ int32_t value;
+/* @@protoc_insertion_point(struct:google_protobuf_Int32Value) */
+} google_protobuf_Int32Value;
+
+typedef struct _google_protobuf_Int64Value {
+ int64_t value;
+/* @@protoc_insertion_point(struct:google_protobuf_Int64Value) */
+} google_protobuf_Int64Value;
+
+typedef struct _google_protobuf_UInt32Value {
+ uint32_t value;
+/* @@protoc_insertion_point(struct:google_protobuf_UInt32Value) */
+} google_protobuf_UInt32Value;
+
+typedef struct _google_protobuf_UInt64Value {
+ uint64_t value;
+/* @@protoc_insertion_point(struct:google_protobuf_UInt64Value) */
+} google_protobuf_UInt64Value;
+
+/* Default values for struct fields */
+
+/* Initializer values for message structs */
+#define google_protobuf_DoubleValue_init_default {0}
+#define google_protobuf_FloatValue_init_default {0}
+#define google_protobuf_Int64Value_init_default {0}
+#define google_protobuf_UInt64Value_init_default {0}
+#define google_protobuf_Int32Value_init_default {0}
+#define google_protobuf_UInt32Value_init_default {0}
+#define google_protobuf_BoolValue_init_default {0}
+#define google_protobuf_StringValue_init_default {{{NULL}, NULL}}
+#define google_protobuf_BytesValue_init_default {{{NULL}, NULL}}
+#define google_protobuf_DoubleValue_init_zero {0}
+#define google_protobuf_FloatValue_init_zero {0}
+#define google_protobuf_Int64Value_init_zero {0}
+#define google_protobuf_UInt64Value_init_zero {0}
+#define google_protobuf_Int32Value_init_zero {0}
+#define google_protobuf_UInt32Value_init_zero {0}
+#define google_protobuf_BoolValue_init_zero {0}
+#define google_protobuf_StringValue_init_zero {{{NULL}, NULL}}
+#define google_protobuf_BytesValue_init_zero {{{NULL}, NULL}}
+
+/* Field tags (for use in manual encoding/decoding) */
+#define google_protobuf_BytesValue_value_tag 1
+#define google_protobuf_StringValue_value_tag 1
+#define google_protobuf_BoolValue_value_tag 1
+#define google_protobuf_DoubleValue_value_tag 1
+#define google_protobuf_FloatValue_value_tag 1
+#define google_protobuf_Int32Value_value_tag 1
+#define google_protobuf_Int64Value_value_tag 1
+#define google_protobuf_UInt32Value_value_tag 1
+#define google_protobuf_UInt64Value_value_tag 1
+
+/* Struct field encoding specification for nanopb */
+extern const pb_field_t google_protobuf_DoubleValue_fields[2];
+extern const pb_field_t google_protobuf_FloatValue_fields[2];
+extern const pb_field_t google_protobuf_Int64Value_fields[2];
+extern const pb_field_t google_protobuf_UInt64Value_fields[2];
+extern const pb_field_t google_protobuf_Int32Value_fields[2];
+extern const pb_field_t google_protobuf_UInt32Value_fields[2];
+extern const pb_field_t google_protobuf_BoolValue_fields[2];
+extern const pb_field_t google_protobuf_StringValue_fields[2];
+extern const pb_field_t google_protobuf_BytesValue_fields[2];
+
+/* Maximum encoded size of messages (where known) */
+#define google_protobuf_DoubleValue_size 9
+#define google_protobuf_FloatValue_size 5
+#define google_protobuf_Int64Value_size 11
+#define google_protobuf_UInt64Value_size 11
+#define google_protobuf_Int32Value_size 11
+#define google_protobuf_UInt32Value_size 6
+#define google_protobuf_BoolValue_size 2
+/* google_protobuf_StringValue_size depends on runtime parameters */
+/* google_protobuf_BytesValue_size depends on runtime parameters */
+
+/* Message IDs (where set with "msgid" option) */
+#ifdef PB_MSGID
+
+#define WRAPPERS_MESSAGES \
+
+
+#endif
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+/* @@protoc_insertion_point(eof) */
+
+#endif
diff --git a/Firestore/Protos/nanopb/google/rpc/status.pb.c b/Firestore/Protos/nanopb/google/rpc/status.pb.c
new file mode 100644
index 0000000..dbdccce
--- /dev/null
+++ b/Firestore/Protos/nanopb/google/rpc/status.pb.c
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2018 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.
+ */
+
+/* Automatically generated nanopb constant definitions */
+/* Generated by nanopb-0.3.8 at Fri Feb 2 17:48:02 2018. */
+
+#include "status.pb.h"
+
+/* @@protoc_insertion_point(includes) */
+#if PB_PROTO_HEADER_VERSION != 30
+#error Regenerate this file with the current version of nanopb generator.
+#endif
+
+
+
+const pb_field_t google_rpc_Status_fields[4] = {
+ PB_FIELD( 1, INT32 , SINGULAR, STATIC , FIRST, google_rpc_Status, code, code, 0),
+ PB_FIELD( 2, STRING , SINGULAR, CALLBACK, OTHER, google_rpc_Status, message, code, 0),
+ PB_FIELD( 3, MESSAGE , REPEATED, CALLBACK, OTHER, google_rpc_Status, details, message, &google_protobuf_Any_fields),
+ PB_LAST_FIELD
+};
+
+
+/* @@protoc_insertion_point(eof) */
diff --git a/Firestore/Protos/nanopb/google/rpc/status.pb.h b/Firestore/Protos/nanopb/google/rpc/status.pb.h
new file mode 100644
index 0000000..afcbab9
--- /dev/null
+++ b/Firestore/Protos/nanopb/google/rpc/status.pb.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2018 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.
+ */
+
+/* Automatically generated nanopb header */
+/* Generated by nanopb-0.3.8 at Fri Feb 2 17:48:02 2018. */
+
+#ifndef PB_GOOGLE_RPC_STATUS_PB_H_INCLUDED
+#define PB_GOOGLE_RPC_STATUS_PB_H_INCLUDED
+#include <pb.h>
+
+#include "google/protobuf/any.pb.h"
+
+/* @@protoc_insertion_point(includes) */
+#if PB_PROTO_HEADER_VERSION != 30
+#error Regenerate this file with the current version of nanopb generator.
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Struct definitions */
+typedef struct _google_rpc_Status {
+ int32_t code;
+ pb_callback_t message;
+ pb_callback_t details;
+/* @@protoc_insertion_point(struct:google_rpc_Status) */
+} google_rpc_Status;
+
+/* Default values for struct fields */
+
+/* Initializer values for message structs */
+#define google_rpc_Status_init_default {0, {{NULL}, NULL}, {{NULL}, NULL}}
+#define google_rpc_Status_init_zero {0, {{NULL}, NULL}, {{NULL}, NULL}}
+
+/* Field tags (for use in manual encoding/decoding) */
+#define google_rpc_Status_code_tag 1
+#define google_rpc_Status_message_tag 2
+#define google_rpc_Status_details_tag 3
+
+/* Struct field encoding specification for nanopb */
+extern const pb_field_t google_rpc_Status_fields[4];
+
+/* Maximum encoded size of messages (where known) */
+/* google_rpc_Status_size depends on runtime parameters */
+
+/* Message IDs (where set with "msgid" option) */
+#ifdef PB_MSGID
+
+#define STATUS_MESSAGES \
+
+
+#endif
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+/* @@protoc_insertion_point(eof) */
+
+#endif
diff --git a/Firestore/Protos/nanopb/google/type/latlng.pb.c b/Firestore/Protos/nanopb/google/type/latlng.pb.c
new file mode 100644
index 0000000..b5f6424
--- /dev/null
+++ b/Firestore/Protos/nanopb/google/type/latlng.pb.c
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2018 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.
+ */
+
+/* Automatically generated nanopb constant definitions */
+/* Generated by nanopb-0.3.8 at Fri Feb 2 17:48:02 2018. */
+
+#include "latlng.pb.h"
+
+/* @@protoc_insertion_point(includes) */
+#if PB_PROTO_HEADER_VERSION != 30
+#error Regenerate this file with the current version of nanopb generator.
+#endif
+
+
+
+const pb_field_t google_type_LatLng_fields[3] = {
+ PB_FIELD( 1, DOUBLE , SINGULAR, STATIC , FIRST, google_type_LatLng, latitude, latitude, 0),
+ PB_FIELD( 2, DOUBLE , SINGULAR, STATIC , OTHER, google_type_LatLng, longitude, latitude, 0),
+ PB_LAST_FIELD
+};
+
+
+/* On some platforms (such as AVR), double is really float.
+ * These are not directly supported by nanopb, but see example_avr_double.
+ * To get rid of this error, remove any double fields from your .proto.
+ */
+PB_STATIC_ASSERT(sizeof(double) == 8, DOUBLE_MUST_BE_8_BYTES)
+
+/* @@protoc_insertion_point(eof) */
diff --git a/Firestore/Protos/nanopb/google/type/latlng.pb.h b/Firestore/Protos/nanopb/google/type/latlng.pb.h
new file mode 100644
index 0000000..fa5703b
--- /dev/null
+++ b/Firestore/Protos/nanopb/google/type/latlng.pb.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2018 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.
+ */
+
+/* Automatically generated nanopb header */
+/* Generated by nanopb-0.3.8 at Fri Feb 2 17:48:02 2018. */
+
+#ifndef PB_GOOGLE_TYPE_LATLNG_PB_H_INCLUDED
+#define PB_GOOGLE_TYPE_LATLNG_PB_H_INCLUDED
+#include <pb.h>
+
+/* @@protoc_insertion_point(includes) */
+#if PB_PROTO_HEADER_VERSION != 30
+#error Regenerate this file with the current version of nanopb generator.
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Struct definitions */
+typedef struct _google_type_LatLng {
+ double latitude;
+ double longitude;
+/* @@protoc_insertion_point(struct:google_type_LatLng) */
+} google_type_LatLng;
+
+/* Default values for struct fields */
+
+/* Initializer values for message structs */
+#define google_type_LatLng_init_default {0, 0}
+#define google_type_LatLng_init_zero {0, 0}
+
+/* Field tags (for use in manual encoding/decoding) */
+#define google_type_LatLng_latitude_tag 1
+#define google_type_LatLng_longitude_tag 2
+
+/* Struct field encoding specification for nanopb */
+extern const pb_field_t google_type_LatLng_fields[3];
+
+/* Maximum encoded size of messages (where known) */
+#define google_type_LatLng_size 18
+
+/* Message IDs (where set with "msgid" option) */
+#ifdef PB_MSGID
+
+#define LATLNG_MESSAGES \
+
+
+#endif
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+/* @@protoc_insertion_point(eof) */
+
+#endif
diff --git a/Firestore/Protos/objc/firestore/local/Target.pbobjc.h b/Firestore/Protos/objc/firestore/local/Target.pbobjc.h
index d8bf49c..0672a6e 100644
--- a/Firestore/Protos/objc/firestore/local/Target.pbobjc.h
+++ b/Firestore/Protos/objc/firestore/local/Target.pbobjc.h
@@ -160,6 +160,7 @@ typedef GPB_ENUM(FSTPBTargetGlobal_FieldNumber) {
FSTPBTargetGlobal_FieldNumber_HighestTargetId = 1,
FSTPBTargetGlobal_FieldNumber_HighestListenSequenceNumber = 2,
FSTPBTargetGlobal_FieldNumber_LastRemoteSnapshotVersion = 3,
+ FSTPBTargetGlobal_FieldNumber_TargetCount = 4,
};
/**
@@ -197,6 +198,9 @@ typedef GPB_ENUM(FSTPBTargetGlobal_FieldNumber) {
/** Test to see if @c lastRemoteSnapshotVersion has been set. */
@property(nonatomic, readwrite) BOOL hasLastRemoteSnapshotVersion;
+/** On platforms that need it, holds the number of targets persisted. */
+@property(nonatomic, readwrite) int32_t targetCount;
+
@end
NS_ASSUME_NONNULL_END
diff --git a/Firestore/Protos/objc/firestore/local/Target.pbobjc.m b/Firestore/Protos/objc/firestore/local/Target.pbobjc.m
index 6f6ccf2..567c86d 100644
--- a/Firestore/Protos/objc/firestore/local/Target.pbobjc.m
+++ b/Firestore/Protos/objc/firestore/local/Target.pbobjc.m
@@ -183,10 +183,12 @@ void FSTPBTarget_ClearTargetTypeOneOfCase(FSTPBTarget *message) {
@dynamic highestTargetId;
@dynamic highestListenSequenceNumber;
@dynamic hasLastRemoteSnapshotVersion, lastRemoteSnapshotVersion;
+@dynamic targetCount;
typedef struct FSTPBTargetGlobal__storage_ {
uint32_t _has_storage_[1];
int32_t highestTargetId;
+ int32_t targetCount;
GPBTimestamp *lastRemoteSnapshotVersion;
int64_t highestListenSequenceNumber;
} FSTPBTargetGlobal__storage_;
@@ -224,6 +226,15 @@ typedef struct FSTPBTargetGlobal__storage_ {
.flags = GPBFieldOptional,
.dataType = GPBDataTypeMessage,
},
+ {
+ .name = "targetCount",
+ .dataTypeSpecific.className = NULL,
+ .number = FSTPBTargetGlobal_FieldNumber_TargetCount,
+ .hasIndex = 3,
+ .offset = (uint32_t)offsetof(FSTPBTargetGlobal__storage_, targetCount),
+ .flags = GPBFieldOptional,
+ .dataType = GPBDataTypeInt32,
+ },
};
GPBDescriptor *localDescriptor =
[GPBDescriptor allocDescriptorForClass:[FSTPBTargetGlobal class]
diff --git a/Firestore/Protos/objc/google/api/HTTP.pbobjc.h b/Firestore/Protos/objc/google/api/HTTP.pbobjc.h
index 9cc00dc..c9e5bb5 100644
--- a/Firestore/Protos/objc/google/api/HTTP.pbobjc.h
+++ b/Firestore/Protos/objc/google/api/HTTP.pbobjc.h
@@ -67,10 +67,11 @@ NS_ASSUME_NONNULL_BEGIN
typedef GPB_ENUM(GAPIHttp_FieldNumber) {
GAPIHttp_FieldNumber_RulesArray = 1,
+ GAPIHttp_FieldNumber_FullyDecodeReservedExpansion = 2,
};
/**
- * Defines the HTTP configuration for a service. It contains a list of
+ * Defines the HTTP configuration for an API service. It contains a list of
* [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method
* to one or more HTTP REST API methods.
**/
@@ -85,6 +86,16 @@ typedef GPB_ENUM(GAPIHttp_FieldNumber) {
/** The number of items in @c rulesArray without causing the array to be created. */
@property(nonatomic, readonly) NSUInteger rulesArray_Count;
+/**
+ * When set to true, URL path parmeters will be fully URI-decoded except in
+ * cases of single segment matches in reserved expansion, where "%2F" will be
+ * left encoded.
+ *
+ * The default behavior is to not decode RFC 6570 reserved characters in multi
+ * segment matches.
+ **/
+@property(nonatomic, readwrite) BOOL fullyDecodeReservedExpansion;
+
@end
#pragma mark - GAPIHttpRule
@@ -113,11 +124,11 @@ typedef GPB_ENUM(GAPIHttpRule_Pattern_OneOfCase) {
/**
* `HttpRule` defines the mapping of an RPC method to one or more HTTP
- * REST APIs. The mapping determines what portions of the request
- * message are populated from the path, query parameters, or body of
- * the HTTP request. The mapping is typically specified as an
- * `google.api.http` annotation, see "google/api/annotations.proto"
- * for details.
+ * REST API methods. The mapping specifies how different portions of the RPC
+ * request message are mapped to URL path, URL query parameters, and
+ * HTTP request body. The mapping is typically specified as an
+ * `google.api.http` annotation on the RPC method,
+ * see "google/api/annotations.proto" for details.
*
* The mapping consists of a field specifying the path template and
* method kind. The path template can refer to fields in the request
@@ -165,6 +176,11 @@ typedef GPB_ENUM(GAPIHttpRule_Pattern_OneOfCase) {
* parameters. Assume the following definition of the request message:
*
*
+ * service Messaging {
+ * rpc GetMessage(GetMessageRequest) returns (Message) {
+ * option (google.api.http).get = "/v1/messages/{message_id}";
+ * }
+ * }
* message GetMessageRequest {
* message SubMessage {
* string subfield = 1;
@@ -277,7 +293,7 @@ typedef GPB_ENUM(GAPIHttpRule_Pattern_OneOfCase) {
* to the request message are as follows:
*
* 1. The `body` field specifies either `*` or a field path, or is
- * omitted. If omitted, it assumes there is no HTTP body.
+ * omitted. If omitted, it indicates there is no HTTP request body.
* 2. Leaf fields (recursive expansion of nested messages in the
* request) can be classified into three types:
* (a) Matched in the URL template.
@@ -296,28 +312,34 @@ typedef GPB_ENUM(GAPIHttpRule_Pattern_OneOfCase) {
* FieldPath = IDENT { "." IDENT } ;
* Verb = ":" LITERAL ;
*
- * The syntax `*` matches a single path segment. It follows the semantics of
- * [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2 Simple String
- * Expansion.
+ * The syntax `*` matches a single path segment. The syntax `**` matches zero
+ * or more path segments, which must be the last part of the path except the
+ * `Verb`. The syntax `LITERAL` matches literal text in the path.
*
- * The syntax `**` matches zero or more path segments. It follows the semantics
- * of [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.3 Reserved
- * Expansion. NOTE: it must be the last segment in the path except the Verb.
- *
- * The syntax `LITERAL` matches literal text in the URL path.
- *
- * The syntax `Variable` matches the entire path as specified by its template;
- * this nested template must not contain further variables. If a variable
+ * The syntax `Variable` matches part of the URL path as specified by its
+ * template. A variable template must not contain other variables. If a variable
* matches a single path segment, its template may be omitted, e.g. `{var}`
* is equivalent to `{var=*}`.
*
+ * If a variable contains exactly one path segment, such as `"{var}"` or
+ * `"{var=*}"`, when such a variable is expanded into a URL path, all characters
+ * except `[-_.~0-9a-zA-Z]` are percent-encoded. Such variables show up in the
+ * Discovery Document as `{var}`.
+ *
+ * If a variable contains one or more path segments, such as `"{var=foo/\*}"`
+ * or `"{var=**}"`, when such a variable is expanded into a URL path, all
+ * characters except `[-_.~/0-9a-zA-Z]` are percent-encoded. Such variables
+ * show up in the Discovery Document as `{+var}`.
+ *
+ * NOTE: While the single segment variable matches the semantics of
+ * [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2
+ * Simple String Expansion, the multi segment variable **does not** match
+ * RFC 6570 Reserved Expansion. The reason is that the Reserved Expansion
+ * does not expand special characters like `?` and `#`, which would lead
+ * to invalid URLs.
+ *
* NOTE: the field paths in variables and in the `body` must not refer to
* repeated fields or map fields.
- *
- * Use CustomHttpPattern to specify any HTTP method that is not included in the
- * `pattern` field, such as HEAD, or "*" to leave the HTTP method unspecified for
- * a given URL path rule. The wild-card rule is useful for services that provide
- * content to Web (HTML) clients.
**/
@interface GAPIHttpRule : GPBMessage
@@ -350,7 +372,12 @@ typedef GPB_ENUM(GAPIHttpRule_Pattern_OneOfCase) {
/** Used for updating a resource. */
@property(nonatomic, readwrite, copy, null_resettable) NSString *patch;
-/** Custom pattern is used for defining custom verbs. */
+/**
+ * The custom pattern is used for specifying an HTTP method that is not
+ * included in the `pattern` field, such as HEAD, or "*" to leave the
+ * HTTP method unspecified for this rule. The wild-card rule is useful
+ * for services that provide content to Web (HTML) clients.
+ **/
@property(nonatomic, readwrite, strong, null_resettable) GAPICustomHttpPattern *custom;
/**
diff --git a/Firestore/Protos/objc/google/api/HTTP.pbobjc.m b/Firestore/Protos/objc/google/api/HTTP.pbobjc.m
index 5adf41c..67d8732 100644
--- a/Firestore/Protos/objc/google/api/HTTP.pbobjc.m
+++ b/Firestore/Protos/objc/google/api/HTTP.pbobjc.m
@@ -65,6 +65,7 @@ static GPBFileDescriptor *GAPIHTTPRoot_FileDescriptor(void) {
@implementation GAPIHttp
@dynamic rulesArray, rulesArray_Count;
+@dynamic fullyDecodeReservedExpansion;
typedef struct GAPIHttp__storage_ {
uint32_t _has_storage_[1];
@@ -86,6 +87,15 @@ typedef struct GAPIHttp__storage_ {
.flags = GPBFieldRepeated,
.dataType = GPBDataTypeMessage,
},
+ {
+ .name = "fullyDecodeReservedExpansion",
+ .dataTypeSpecific.className = NULL,
+ .number = GAPIHttp_FieldNumber_FullyDecodeReservedExpansion,
+ .hasIndex = 0,
+ .offset = 1, // Stored in _has_storage_ to save space.
+ .flags = GPBFieldOptional,
+ .dataType = GPBDataTypeBool,
+ },
};
GPBDescriptor *localDescriptor =
[GPBDescriptor allocDescriptorForClass:[GAPIHttp class]
diff --git a/Firestore/Protos/objc/google/firestore/v1beta1/Document.pbobjc.h b/Firestore/Protos/objc/google/firestore/v1beta1/Document.pbobjc.h
index 3c5bfb1..b960b00 100644
--- a/Firestore/Protos/objc/google/firestore/v1beta1/Document.pbobjc.h
+++ b/Firestore/Protos/objc/google/firestore/v1beta1/Document.pbobjc.h
@@ -95,7 +95,7 @@ typedef GPB_ENUM(GCFSDocument_FieldNumber) {
* The map keys represent field names.
*
* A simple field name contains only characters `a` to `z`, `A` to `Z`,
- * `0` to `9`, or `_`, and must not start with `0` to `9` or `_`. For example,
+ * `0` to `9`, or `_`, and must not start with `0` to `9`. For example,
* `foo_bar_17`.
*
* Field names matching the regular expression `__.*__` are reserved. Reserved
@@ -133,7 +133,7 @@ typedef GPB_ENUM(GCFSDocument_FieldNumber) {
/**
* Output only. The time at which the document was last changed.
*
- * This value is initally set to the `create_time` then increases
+ * This value is initially set to the `create_time` then increases
* monotonically with each change to the document. It can also be
* compared to values from other documents and the `read_time` of a query.
**/
diff --git a/Firestore/Protos/objc/google/firestore/v1beta1/Firestore.pbobjc.h b/Firestore/Protos/objc/google/firestore/v1beta1/Firestore.pbobjc.h
index 0acd8c0..11f55a9 100644
--- a/Firestore/Protos/objc/google/firestore/v1beta1/Firestore.pbobjc.h
+++ b/Firestore/Protos/objc/google/firestore/v1beta1/Firestore.pbobjc.h
@@ -642,10 +642,7 @@ typedef GPB_ENUM(GCFSCommitRequest_FieldNumber) {
/** The number of items in @c writesArray without causing the array to be created. */
@property(nonatomic, readonly) NSUInteger writesArray_Count;
-/**
- * If non-empty, applies all writes in this transaction, and commits it.
- * Otherwise, applies the writes as if they were in their own transaction.
- **/
+/** If set, applies all writes in this transaction, and commits it. */
@property(nonatomic, readwrite, copy, null_resettable) NSData *transaction;
@end
diff --git a/Firestore/Protos/objc/google/firestore/v1beta1/Firestore.pbrpc.h b/Firestore/Protos/objc/google/firestore/v1beta1/Firestore.pbrpc.h
index d7f127b..991c3f0 100644
--- a/Firestore/Protos/objc/google/firestore/v1beta1/Firestore.pbrpc.h
+++ b/Firestore/Protos/objc/google/firestore/v1beta1/Firestore.pbrpc.h
@@ -14,29 +14,57 @@
* limitations under the License.
*/
-#import "Firestore/Protos/objc/google/firestore/v1beta1/Firestore.pbobjc.h"
+#if !GPB_GRPC_FORWARD_DECLARE_MESSAGE_PROTO
+#import "Firestore.pbobjc.h"
+#endif
#import <ProtoRPC/ProtoService.h>
#import <ProtoRPC/ProtoRPC.h>
#import <RxLibrary/GRXWriteable.h>
#import <RxLibrary/GRXWriter.h>
-#import "Firestore/Protos/objc/google/api/Annotations.pbobjc.h"
-#import "Firestore/Protos/objc/google/firestore/v1beta1/Common.pbobjc.h"
-#import "Firestore/Protos/objc/google/firestore/v1beta1/Document.pbobjc.h"
-#import "Firestore/Protos/objc/google/firestore/v1beta1/Query.pbobjc.h"
-#import "Firestore/Protos/objc/google/firestore/v1beta1/Write.pbobjc.h"
-#if GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS
- #import <Protobuf/Empty.pbobjc.h>
-#else
- #import "Empty.pbobjc.h"
-#endif
-#if GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS
- #import <Protobuf/Timestamp.pbobjc.h>
+#if GPB_GRPC_FORWARD_DECLARE_MESSAGE_PROTO
+ @class GCFSBatchGetDocumentsRequest;
+ @class GCFSBatchGetDocumentsResponse;
+ @class GCFSBeginTransactionRequest;
+ @class GCFSBeginTransactionResponse;
+ @class GCFSCommitRequest;
+ @class GCFSCommitResponse;
+ @class GCFSCreateDocumentRequest;
+ @class GCFSDeleteDocumentRequest;
+ @class GCFSDocument;
+ @class GCFSGetDocumentRequest;
+ @class GCFSListCollectionIdsRequest;
+ @class GCFSListCollectionIdsResponse;
+ @class GCFSListDocumentsRequest;
+ @class GCFSListDocumentsResponse;
+ @class GCFSListenRequest;
+ @class GCFSListenResponse;
+ @class GCFSRollbackRequest;
+ @class GCFSRunQueryRequest;
+ @class GCFSRunQueryResponse;
+ @class GCFSUpdateDocumentRequest;
+ @class GCFSWriteRequest;
+ @class GCFSWriteResponse;
+ @class GPBEmpty;
#else
- #import "Timestamp.pbobjc.h"
+ #import "Annotations.pbobjc.h"
+ #import "Common.pbobjc.h"
+ #import "Document.pbobjc.h"
+ #import "Query.pbobjc.h"
+ #import "Write.pbobjc.h"
+ #if GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS
+ #import <Protobuf/Empty.pbobjc.h>
+ #else
+ #import "Empty.pbobjc.h"
+ #endif
+ #if GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS
+ #import <Protobuf/Timestamp.pbobjc.h>
+ #else
+ #import "Timestamp.pbobjc.h"
+ #endif
+ #import "Status.pbobjc.h"
#endif
-#import "Firestore/Protos/objc/google/rpc/Status.pbobjc.h"
NS_ASSUME_NONNULL_BEGIN
diff --git a/Firestore/Protos/objc/google/firestore/v1beta1/Firestore.pbrpc.m b/Firestore/Protos/objc/google/firestore/v1beta1/Firestore.pbrpc.m
index eec4c9a..29359b6 100644
--- a/Firestore/Protos/objc/google/firestore/v1beta1/Firestore.pbrpc.m
+++ b/Firestore/Protos/objc/google/firestore/v1beta1/Firestore.pbrpc.m
@@ -14,10 +14,27 @@
* limitations under the License.
*/
-#import "Firestore/Protos/objc/google/firestore/v1beta1/Firestore.pbrpc.h"
+#import "Firestore.pbrpc.h"
+#import "Firestore.pbobjc.h"
#import <ProtoRPC/ProtoRPC.h>
#import <RxLibrary/GRXWriter+Immediate.h>
+#import "Annotations.pbobjc.h"
+#import "Common.pbobjc.h"
+#import "Document.pbobjc.h"
+#import "Query.pbobjc.h"
+#import "Write.pbobjc.h"
+#if GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS
+ #import <Protobuf/Empty.pbobjc.h>
+#else
+ #import "Empty.pbobjc.h"
+#endif
+#if GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS
+ #import <Protobuf/Timestamp.pbobjc.h>
+#else
+ #import "Timestamp.pbobjc.h"
+#endif
+#import "Status.pbobjc.h"
@implementation GCFSFirestore
diff --git a/Firestore/Protos/objc/google/firestore/v1beta1/Query.pbobjc.h b/Firestore/Protos/objc/google/firestore/v1beta1/Query.pbobjc.h
index c2d80e7..7e32934 100644
--- a/Firestore/Protos/objc/google/firestore/v1beta1/Query.pbobjc.h
+++ b/Firestore/Protos/objc/google/firestore/v1beta1/Query.pbobjc.h
@@ -140,6 +140,9 @@ typedef GPB_ENUM(GCFSStructuredQuery_FieldFilter_Operator) {
/** Equal. */
GCFSStructuredQuery_FieldFilter_Operator_Equal = 5,
+
+ /** Contains. Requires that the field is an array. */
+ GCFSStructuredQuery_FieldFilter_Operator_ArrayContains = 7,
};
GPBEnumDescriptor *GCFSStructuredQuery_FieldFilter_Operator_EnumDescriptor(void);
diff --git a/Firestore/Protos/objc/google/firestore/v1beta1/Query.pbobjc.m b/Firestore/Protos/objc/google/firestore/v1beta1/Query.pbobjc.m
index 804a5d0..124930a 100644
--- a/Firestore/Protos/objc/google/firestore/v1beta1/Query.pbobjc.m
+++ b/Firestore/Protos/objc/google/firestore/v1beta1/Query.pbobjc.m
@@ -542,7 +542,7 @@ GPBEnumDescriptor *GCFSStructuredQuery_FieldFilter_Operator_EnumDescriptor(void)
static const char *valueNames =
"OperatorUnspecified\000LessThan\000LessThanOrE"
"qual\000GreaterThan\000GreaterThanOrEqual\000Equa"
- "l\000";
+ "l\000ArrayContains\000";
static const int32_t values[] = {
GCFSStructuredQuery_FieldFilter_Operator_OperatorUnspecified,
GCFSStructuredQuery_FieldFilter_Operator_LessThan,
@@ -550,6 +550,7 @@ GPBEnumDescriptor *GCFSStructuredQuery_FieldFilter_Operator_EnumDescriptor(void)
GCFSStructuredQuery_FieldFilter_Operator_GreaterThan,
GCFSStructuredQuery_FieldFilter_Operator_GreaterThanOrEqual,
GCFSStructuredQuery_FieldFilter_Operator_Equal,
+ GCFSStructuredQuery_FieldFilter_Operator_ArrayContains,
};
GPBEnumDescriptor *worker =
[GPBEnumDescriptor allocDescriptorForName:GPBNSStringifySymbol(GCFSStructuredQuery_FieldFilter_Operator)
@@ -572,6 +573,7 @@ BOOL GCFSStructuredQuery_FieldFilter_Operator_IsValidValue(int32_t value__) {
case GCFSStructuredQuery_FieldFilter_Operator_GreaterThan:
case GCFSStructuredQuery_FieldFilter_Operator_GreaterThanOrEqual:
case GCFSStructuredQuery_FieldFilter_Operator_Equal:
+ case GCFSStructuredQuery_FieldFilter_Operator_ArrayContains:
return YES;
default:
return NO;
diff --git a/Firestore/Protos/objc/google/firestore/v1beta1/Write.pbobjc.h b/Firestore/Protos/objc/google/firestore/v1beta1/Write.pbobjc.h
index c3c4498..539bfa3 100644
--- a/Firestore/Protos/objc/google/firestore/v1beta1/Write.pbobjc.h
+++ b/Firestore/Protos/objc/google/firestore/v1beta1/Write.pbobjc.h
@@ -43,6 +43,7 @@
CF_EXTERN_C_BEGIN
+@class GCFSArrayValue;
@class GCFSDocument;
@class GCFSDocumentMask;
@class GCFSDocumentTransform;
@@ -66,7 +67,10 @@ typedef GPB_ENUM(GCFSDocumentTransform_FieldTransform_ServerValue) {
/** Unspecified. This value must not be used. */
GCFSDocumentTransform_FieldTransform_ServerValue_ServerValueUnspecified = 0,
- /** The time at which the server processed the request. */
+ /**
+ * The time at which the server processed the request, with millisecond
+ * precision.
+ **/
GCFSDocumentTransform_FieldTransform_ServerValue_RequestTime = 1,
};
@@ -100,7 +104,6 @@ typedef GPB_ENUM(GCFSWrite_FieldNumber) {
GCFSWrite_FieldNumber_Delete_p = 2,
GCFSWrite_FieldNumber_UpdateMask = 3,
GCFSWrite_FieldNumber_CurrentDocument = 4,
- GCFSWrite_FieldNumber_Verify = 5,
GCFSWrite_FieldNumber_Transform = 6,
};
@@ -108,7 +111,6 @@ typedef GPB_ENUM(GCFSWrite_Operation_OneOfCase) {
GCFSWrite_Operation_OneOfCase_GPBUnsetOneOfCase = 0,
GCFSWrite_Operation_OneOfCase_Update = 1,
GCFSWrite_Operation_OneOfCase_Delete_p = 2,
- GCFSWrite_Operation_OneOfCase_Verify = 5,
GCFSWrite_Operation_OneOfCase_Transform = 6,
};
@@ -130,13 +132,6 @@ typedef GPB_ENUM(GCFSWrite_Operation_OneOfCase) {
@property(nonatomic, readwrite, copy, null_resettable) NSString *delete_p;
/**
- * The name of a document on which to verify the `current_document`
- * precondition.
- * This only requires read access to the document.
- **/
-@property(nonatomic, readwrite, copy, null_resettable) NSString *verify;
-
-/**
* Applies a tranformation to a document.
* At most one `transform` per document is allowed in a given request.
* An `update` cannot follow a `transform` on the same document in a given
@@ -148,9 +143,10 @@ typedef GPB_ENUM(GCFSWrite_Operation_OneOfCase) {
* The fields to update in this write.
*
* This field can be set only when the operation is `update`.
- * None of the field paths in the mask may contain a reserved name.
- * If the document exists on the server and has fields not referenced in the
- * mask, they are left unchanged.
+ * If the mask is not set for an `update` and the document exists, any
+ * existing data will be overwritten.
+ * If the mask is set and the document on the server has fields not covered by
+ * the mask, they are left unchanged.
* Fields referenced in the mask, but not present in the input document, are
* deleted from the document on the server.
* The field paths in this mask must not contain a reserved field name.
@@ -193,6 +189,7 @@ typedef GPB_ENUM(GCFSDocumentTransform_FieldNumber) {
/**
* The list of transformations to apply to the fields of the document, in
* order.
+ * This must not be empty.
**/
@property(nonatomic, readwrite, strong, null_resettable) NSMutableArray<GCFSDocumentTransform_FieldTransform*> *fieldTransformsArray;
/** The number of items in @c fieldTransformsArray without causing the array to be created. */
@@ -205,11 +202,15 @@ typedef GPB_ENUM(GCFSDocumentTransform_FieldNumber) {
typedef GPB_ENUM(GCFSDocumentTransform_FieldTransform_FieldNumber) {
GCFSDocumentTransform_FieldTransform_FieldNumber_FieldPath = 1,
GCFSDocumentTransform_FieldTransform_FieldNumber_SetToServerValue = 2,
+ GCFSDocumentTransform_FieldTransform_FieldNumber_AppendMissingElements = 6,
+ GCFSDocumentTransform_FieldTransform_FieldNumber_RemoveAllFromArray_p = 7,
};
typedef GPB_ENUM(GCFSDocumentTransform_FieldTransform_TransformType_OneOfCase) {
GCFSDocumentTransform_FieldTransform_TransformType_OneOfCase_GPBUnsetOneOfCase = 0,
GCFSDocumentTransform_FieldTransform_TransformType_OneOfCase_SetToServerValue = 2,
+ GCFSDocumentTransform_FieldTransform_TransformType_OneOfCase_AppendMissingElements = 6,
+ GCFSDocumentTransform_FieldTransform_TransformType_OneOfCase_RemoveAllFromArray_p = 7,
};
/**
@@ -229,6 +230,36 @@ typedef GPB_ENUM(GCFSDocumentTransform_FieldTransform_TransformType_OneOfCase) {
/** Sets the field to the given server value. */
@property(nonatomic, readwrite) GCFSDocumentTransform_FieldTransform_ServerValue setToServerValue;
+/**
+ * Append the given elements in order if they are not already present in
+ * the current field value.
+ * If the field is not an array, or if the field does not yet exist, it is
+ * first set to the empty array.
+ *
+ * Equivalent numbers of different types (e.g. 3L and 3.0) are
+ * considered equal when checking if a value is missing.
+ * NaN is equal to NaN, and Null is equal to Null.
+ * If the input contains multiple equivalent values, only the first will
+ * be considered.
+ *
+ * The corresponding transform_result will be the null value.
+ **/
+@property(nonatomic, readwrite, strong, null_resettable) GCFSArrayValue *appendMissingElements;
+
+/**
+ * Remove all of the given elements from the array in the field.
+ * If the field is not an array, or if the field does not yet exist, it is
+ * set to the empty array.
+ *
+ * Equivalent numbers of the different types (e.g. 3L and 3.0) are
+ * considered equal when deciding whether an element should be removed.
+ * NaN is equal to NaN, and Null is equal to Null.
+ * This will remove all equivalent values if there are duplicates.
+ *
+ * The corresponding transform_result will be the null value.
+ **/
+@property(nonatomic, readwrite, strong, null_resettable) GCFSArrayValue *removeAllFromArray_p;
+
@end
/**
diff --git a/Firestore/Protos/objc/google/firestore/v1beta1/Write.pbobjc.m b/Firestore/Protos/objc/google/firestore/v1beta1/Write.pbobjc.m
index e6fd0f4..591e634 100644
--- a/Firestore/Protos/objc/google/firestore/v1beta1/Write.pbobjc.m
+++ b/Firestore/Protos/objc/google/firestore/v1beta1/Write.pbobjc.m
@@ -74,7 +74,6 @@ static GPBFileDescriptor *GCFSWriteRoot_FileDescriptor(void) {
@dynamic operationOneOfCase;
@dynamic update;
@dynamic delete_p;
-@dynamic verify;
@dynamic transform;
@dynamic hasUpdateMask, updateMask;
@dynamic hasCurrentDocument, currentDocument;
@@ -85,7 +84,6 @@ typedef struct GCFSWrite__storage_ {
NSString *delete_p;
GCFSDocumentMask *updateMask;
GCFSPrecondition *currentDocument;
- NSString *verify;
GCFSDocumentTransform *transform;
} GCFSWrite__storage_;
@@ -132,15 +130,6 @@ typedef struct GCFSWrite__storage_ {
.dataType = GPBDataTypeMessage,
},
{
- .name = "verify",
- .dataTypeSpecific.className = NULL,
- .number = GCFSWrite_FieldNumber_Verify,
- .hasIndex = -1,
- .offset = (uint32_t)offsetof(GCFSWrite__storage_, verify),
- .flags = GPBFieldOptional,
- .dataType = GPBDataTypeString,
- },
- {
.name = "transform",
.dataTypeSpecific.className = GPBStringifySymbol(GCFSDocumentTransform),
.number = GCFSWrite_FieldNumber_Transform,
@@ -238,11 +227,15 @@ typedef struct GCFSDocumentTransform__storage_ {
@dynamic transformTypeOneOfCase;
@dynamic fieldPath;
@dynamic setToServerValue;
+@dynamic appendMissingElements;
+@dynamic removeAllFromArray_p;
typedef struct GCFSDocumentTransform_FieldTransform__storage_ {
uint32_t _has_storage_[2];
GCFSDocumentTransform_FieldTransform_ServerValue setToServerValue;
NSString *fieldPath;
+ GCFSArrayValue *appendMissingElements;
+ GCFSArrayValue *removeAllFromArray_p;
} GCFSDocumentTransform_FieldTransform__storage_;
// This method is threadsafe because it is initially called
@@ -269,6 +262,24 @@ typedef struct GCFSDocumentTransform_FieldTransform__storage_ {
.flags = (GPBFieldFlags)(GPBFieldOptional | GPBFieldHasEnumDescriptor),
.dataType = GPBDataTypeEnum,
},
+ {
+ .name = "appendMissingElements",
+ .dataTypeSpecific.className = GPBStringifySymbol(GCFSArrayValue),
+ .number = GCFSDocumentTransform_FieldTransform_FieldNumber_AppendMissingElements,
+ .hasIndex = -1,
+ .offset = (uint32_t)offsetof(GCFSDocumentTransform_FieldTransform__storage_, appendMissingElements),
+ .flags = GPBFieldOptional,
+ .dataType = GPBDataTypeMessage,
+ },
+ {
+ .name = "removeAllFromArray_p",
+ .dataTypeSpecific.className = GPBStringifySymbol(GCFSArrayValue),
+ .number = GCFSDocumentTransform_FieldTransform_FieldNumber_RemoveAllFromArray_p,
+ .hasIndex = -1,
+ .offset = (uint32_t)offsetof(GCFSDocumentTransform_FieldTransform__storage_, removeAllFromArray_p),
+ .flags = GPBFieldOptional,
+ .dataType = GPBDataTypeMessage,
+ },
};
GPBDescriptor *localDescriptor =
[GPBDescriptor allocDescriptorForClass:[GCFSDocumentTransform_FieldTransform class]
diff --git a/Firestore/Protos/protos/firestore/local/target.proto b/Firestore/Protos/protos/firestore/local/target.proto
index 7f34515..7f0a886 100644
--- a/Firestore/Protos/protos/firestore/local/target.proto
+++ b/Firestore/Protos/protos/firestore/local/target.proto
@@ -87,4 +87,7 @@ message TargetGlobal {
// This is updated whenever our we get a TargetChange with a read_time and
// empty target_ids.
google.protobuf.Timestamp last_remote_snapshot_version = 3;
+
+ // On platforms that need it, holds the number of targets persisted.
+ int32 target_count = 4;
}
diff --git a/Firestore/Protos/protos/google/api/http.options b/Firestore/Protos/protos/google/api/http.options
new file mode 100644
index 0000000..6ddde53
--- /dev/null
+++ b/Firestore/Protos/protos/google/api/http.options
@@ -0,0 +1 @@
+google.api.HttpRule.pattern no_unions:true
diff --git a/Firestore/Protos/protos/google/api/http.proto b/Firestore/Protos/protos/google/api/http.proto
index 5f8538a..78d515d 100644
--- a/Firestore/Protos/protos/google/api/http.proto
+++ b/Firestore/Protos/protos/google/api/http.proto
@@ -1,4 +1,4 @@
-// Copyright 2016 Google Inc.
+// Copyright 2018 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -24,7 +24,7 @@ option java_package = "com.google.api";
option objc_class_prefix = "GAPI";
-// Defines the HTTP configuration for a service. It contains a list of
+// Defines the HTTP configuration for an API service. It contains a list of
// [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method
// to one or more HTTP REST API methods.
message Http {
@@ -32,14 +32,22 @@ message Http {
//
// **NOTE:** All service configuration rules follow "last one wins" order.
repeated HttpRule rules = 1;
+
+ // When set to true, URL path parmeters will be fully URI-decoded except in
+ // cases of single segment matches in reserved expansion, where "%2F" will be
+ // left encoded.
+ //
+ // The default behavior is to not decode RFC 6570 reserved characters in multi
+ // segment matches.
+ bool fully_decode_reserved_expansion = 2;
}
// `HttpRule` defines the mapping of an RPC method to one or more HTTP
-// REST APIs. The mapping determines what portions of the request
-// message are populated from the path, query parameters, or body of
-// the HTTP request. The mapping is typically specified as an
-// `google.api.http` annotation, see "google/api/annotations.proto"
-// for details.
+// REST API methods. The mapping specifies how different portions of the RPC
+// request message are mapped to URL path, URL query parameters, and
+// HTTP request body. The mapping is typically specified as an
+// `google.api.http` annotation on the RPC method,
+// see "google/api/annotations.proto" for details.
//
// The mapping consists of a field specifying the path template and
// method kind. The path template can refer to fields in the request
@@ -87,6 +95,11 @@ message Http {
// parameters. Assume the following definition of the request message:
//
//
+// service Messaging {
+// rpc GetMessage(GetMessageRequest) returns (Message) {
+// option (google.api.http).get = "/v1/messages/{message_id}";
+// }
+// }
// message GetMessageRequest {
// message SubMessage {
// string subfield = 1;
@@ -199,7 +212,7 @@ message Http {
// to the request message are as follows:
//
// 1. The `body` field specifies either `*` or a field path, or is
-// omitted. If omitted, it assumes there is no HTTP body.
+// omitted. If omitted, it indicates there is no HTTP request body.
// 2. Leaf fields (recursive expansion of nested messages in the
// request) can be classified into three types:
// (a) Matched in the URL template.
@@ -218,28 +231,34 @@ message Http {
// FieldPath = IDENT { "." IDENT } ;
// Verb = ":" LITERAL ;
//
-// The syntax `*` matches a single path segment. It follows the semantics of
-// [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2 Simple String
-// Expansion.
+// The syntax `*` matches a single path segment. The syntax `**` matches zero
+// or more path segments, which must be the last part of the path except the
+// `Verb`. The syntax `LITERAL` matches literal text in the path.
//
-// The syntax `**` matches zero or more path segments. It follows the semantics
-// of [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.3 Reserved
-// Expansion. NOTE: it must be the last segment in the path except the Verb.
-//
-// The syntax `LITERAL` matches literal text in the URL path.
-//
-// The syntax `Variable` matches the entire path as specified by its template;
-// this nested template must not contain further variables. If a variable
+// The syntax `Variable` matches part of the URL path as specified by its
+// template. A variable template must not contain other variables. If a variable
// matches a single path segment, its template may be omitted, e.g. `{var}`
// is equivalent to `{var=*}`.
//
+// If a variable contains exactly one path segment, such as `"{var}"` or
+// `"{var=*}"`, when such a variable is expanded into a URL path, all characters
+// except `[-_.~0-9a-zA-Z]` are percent-encoded. Such variables show up in the
+// Discovery Document as `{var}`.
+//
+// If a variable contains one or more path segments, such as `"{var=foo/*}"`
+// or `"{var=**}"`, when such a variable is expanded into a URL path, all
+// characters except `[-_.~/0-9a-zA-Z]` are percent-encoded. Such variables
+// show up in the Discovery Document as `{+var}`.
+//
+// NOTE: While the single segment variable matches the semantics of
+// [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2
+// Simple String Expansion, the multi segment variable **does not** match
+// RFC 6570 Reserved Expansion. The reason is that the Reserved Expansion
+// does not expand special characters like `?` and `#`, which would lead
+// to invalid URLs.
+//
// NOTE: the field paths in variables and in the `body` must not refer to
// repeated fields or map fields.
-//
-// Use CustomHttpPattern to specify any HTTP method that is not included in the
-// `pattern` field, such as HEAD, or "*" to leave the HTTP method unspecified for
-// a given URL path rule. The wild-card rule is useful for services that provide
-// content to Web (HTML) clients.
message HttpRule {
// Selects methods to which this rule applies.
//
@@ -265,7 +284,10 @@ message HttpRule {
// Used for updating a resource.
string patch = 6;
- // Custom pattern is used for defining custom verbs.
+ // The custom pattern is used for specifying an HTTP method that is not
+ // included in the `pattern` field, such as HEAD, or "*" to leave the
+ // HTTP method unspecified for this rule. The wild-card rule is useful
+ // for services that provide content to Web (HTML) clients.
CustomHttpPattern custom = 8;
}
diff --git a/Firestore/Protos/protos/google/firestore/v1beta1/common.proto b/Firestore/Protos/protos/google/firestore/v1beta1/common.proto
index e624323..5ceb7b9 100644
--- a/Firestore/Protos/protos/google/firestore/v1beta1/common.proto
+++ b/Firestore/Protos/protos/google/firestore/v1beta1/common.proto
@@ -1,4 +1,4 @@
-// Copyright 2017 Google Inc.
+// Copyright 2018 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -25,6 +25,7 @@ option java_multiple_files = true;
option java_outer_classname = "CommonProto";
option java_package = "com.google.firestore.v1beta1";
option objc_class_prefix = "GCFS";
+option php_namespace = "Google\\Cloud\\Firestore\\V1beta1";
// A set of field paths on a document.
diff --git a/Firestore/Protos/protos/google/firestore/v1beta1/document.options b/Firestore/Protos/protos/google/firestore/v1beta1/document.options
new file mode 100644
index 0000000..e671c43
--- /dev/null
+++ b/Firestore/Protos/protos/google/firestore/v1beta1/document.options
@@ -0,0 +1 @@
+google.firestore.v1beta1.Value.value_type no_unions:true
diff --git a/Firestore/Protos/protos/google/firestore/v1beta1/document.proto b/Firestore/Protos/protos/google/firestore/v1beta1/document.proto
index cf6001d..cd84c7a 100644
--- a/Firestore/Protos/protos/google/firestore/v1beta1/document.proto
+++ b/Firestore/Protos/protos/google/firestore/v1beta1/document.proto
@@ -1,4 +1,4 @@
-// Copyright 2017 Google Inc.
+// Copyright 2018 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -27,6 +27,7 @@ option java_multiple_files = true;
option java_outer_classname = "DocumentProto";
option java_package = "com.google.firestore.v1beta1";
option objc_class_prefix = "GCFS";
+option php_namespace = "Google\\Cloud\\Firestore\\V1beta1";
// A Firestore document.
@@ -42,7 +43,7 @@ message Document {
// The map keys represent field names.
//
// A simple field name contains only characters `a` to `z`, `A` to `Z`,
- // `0` to `9`, or `_`, and must not start with `0` to `9` or `_`. For example,
+ // `0` to `9`, or `_`, and must not start with `0` to `9`. For example,
// `foo_bar_17`.
//
// Field names matching the regular expression `__.*__` are reserved. Reserved
@@ -72,7 +73,7 @@ message Document {
// Output only. The time at which the document was last changed.
//
- // This value is initally set to the `create_time` then increases
+ // This value is initially set to the `create_time` then increases
// monotonically with each change to the document. It can also be
// compared to values from other documents and the `read_time` of a query.
google.protobuf.Timestamp update_time = 4;
diff --git a/Firestore/Protos/protos/google/firestore/v1beta1/firestore.options b/Firestore/Protos/protos/google/firestore/v1beta1/firestore.options
new file mode 100644
index 0000000..71a05d4
--- /dev/null
+++ b/Firestore/Protos/protos/google/firestore/v1beta1/firestore.options
@@ -0,0 +1,6 @@
+google.firestore.v1beta1.GetDocumentRequest.consistency_selector no_unions:true
+google.firestore.v1beta1.ListDocumentsRequest.consistency_selector no_unions:true
+google.firestore.v1beta1.RunQueryRequest.consistency_selector no_unions:true
+google.firestore.v1beta1.BatchGetDocumentsRequest.consistency_selector no_unions:true
+google.firestore.v1beta1.BatchGetDocumentsResponse.result no_unions:true
+google.firestore.v1beta1.Target.resume_type no_unions:true
diff --git a/Firestore/Protos/protos/google/firestore/v1beta1/firestore.proto b/Firestore/Protos/protos/google/firestore/v1beta1/firestore.proto
index 3939caa..c7e8561 100644
--- a/Firestore/Protos/protos/google/firestore/v1beta1/firestore.proto
+++ b/Firestore/Protos/protos/google/firestore/v1beta1/firestore.proto
@@ -1,4 +1,4 @@
-// Copyright 2017 Google Inc.
+// Copyright 2018 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -31,6 +31,7 @@ option java_multiple_files = true;
option java_outer_classname = "FirestoreProto";
option java_package = "com.google.firestore.v1beta1";
option objc_class_prefix = "GCFS";
+option php_namespace = "Google\\Cloud\\Firestore\\V1beta1";
// Specification of the Firestore API.
@@ -55,27 +56,39 @@ option objc_class_prefix = "GCFS";
service Firestore {
// Gets a single document.
rpc GetDocument(GetDocumentRequest) returns (Document) {
- option (google.api.http) = { get: "/v1beta1/{name=projects/*/databases/*/documents/*/**}" };
+ option (google.api.http) = {
+ get: "/v1beta1/{name=projects/*/databases/*/documents/*/**}"
+ };
}
// Lists documents.
rpc ListDocuments(ListDocumentsRequest) returns (ListDocumentsResponse) {
- option (google.api.http) = { get: "/v1beta1/{parent=projects/*/databases/*/documents/*/**}/{collection_id}" };
+ option (google.api.http) = {
+ get: "/v1beta1/{parent=projects/*/databases/*/documents/*/**}/{collection_id}"
+ };
}
// Creates a new document.
rpc CreateDocument(CreateDocumentRequest) returns (Document) {
- option (google.api.http) = { post: "/v1beta1/{parent=projects/*/databases/*/documents/**}/{collection_id}" body: "document" };
+ option (google.api.http) = {
+ post: "/v1beta1/{parent=projects/*/databases/*/documents/**}/{collection_id}"
+ body: "document"
+ };
}
// Updates or inserts a document.
rpc UpdateDocument(UpdateDocumentRequest) returns (Document) {
- option (google.api.http) = { patch: "/v1beta1/{document.name=projects/*/databases/*/documents/*/**}" body: "document" };
+ option (google.api.http) = {
+ patch: "/v1beta1/{document.name=projects/*/databases/*/documents/*/**}"
+ body: "document"
+ };
}
// Deletes a document.
rpc DeleteDocument(DeleteDocumentRequest) returns (google.protobuf.Empty) {
- option (google.api.http) = { delete: "/v1beta1/{name=projects/*/databases/*/documents/*/**}" };
+ option (google.api.http) = {
+ delete: "/v1beta1/{name=projects/*/databases/*/documents/*/**}"
+ };
}
// Gets multiple documents.
@@ -83,42 +96,74 @@ service Firestore {
// Documents returned by this method are not guaranteed to be returned in the
// same order that they were requested.
rpc BatchGetDocuments(BatchGetDocumentsRequest) returns (stream BatchGetDocumentsResponse) {
- option (google.api.http) = { post: "/v1beta1/{database=projects/*/databases/*}/documents:batchGet" body: "*" };
+ option (google.api.http) = {
+ post: "/v1beta1/{database=projects/*/databases/*}/documents:batchGet"
+ body: "*"
+ };
}
// Starts a new transaction.
rpc BeginTransaction(BeginTransactionRequest) returns (BeginTransactionResponse) {
- option (google.api.http) = { post: "/v1beta1/{database=projects/*/databases/*}/documents:beginTransaction" body: "*" };
+ option (google.api.http) = {
+ post: "/v1beta1/{database=projects/*/databases/*}/documents:beginTransaction"
+ body: "*"
+ };
}
// Commits a transaction, while optionally updating documents.
rpc Commit(CommitRequest) returns (CommitResponse) {
- option (google.api.http) = { post: "/v1beta1/{database=projects/*/databases/*}/documents:commit" body: "*" };
+ option (google.api.http) = {
+ post: "/v1beta1/{database=projects/*/databases/*}/documents:commit"
+ body: "*"
+ };
}
// Rolls back a transaction.
rpc Rollback(RollbackRequest) returns (google.protobuf.Empty) {
- option (google.api.http) = { post: "/v1beta1/{database=projects/*/databases/*}/documents:rollback" body: "*" };
+ option (google.api.http) = {
+ post: "/v1beta1/{database=projects/*/databases/*}/documents:rollback"
+ body: "*"
+ };
}
// Runs a query.
rpc RunQuery(RunQueryRequest) returns (stream RunQueryResponse) {
- option (google.api.http) = { post: "/v1beta1/{parent=projects/*/databases/*/documents}:runQuery" body: "*" };
+ option (google.api.http) = {
+ post: "/v1beta1/{parent=projects/*/databases/*/documents}:runQuery"
+ body: "*"
+ additional_bindings {
+ post: "/v1beta1/{parent=projects/*/databases/*/documents/*/**}:runQuery"
+ body: "*"
+ }
+ };
}
// Streams batches of document updates and deletes, in order.
rpc Write(stream WriteRequest) returns (stream WriteResponse) {
- option (google.api.http) = { post: "/v1beta1/{database=projects/*/databases/*}/documents:write" body: "*" };
+ option (google.api.http) = {
+ post: "/v1beta1/{database=projects/*/databases/*}/documents:write"
+ body: "*"
+ };
}
// Listens to changes.
rpc Listen(stream ListenRequest) returns (stream ListenResponse) {
- option (google.api.http) = { post: "/v1beta1/{database=projects/*/databases/*}/documents:listen" body: "*" };
+ option (google.api.http) = {
+ post: "/v1beta1/{database=projects/*/databases/*}/documents:listen"
+ body: "*"
+ };
}
// Lists all the collection IDs underneath a document.
rpc ListCollectionIds(ListCollectionIdsRequest) returns (ListCollectionIdsResponse) {
- option (google.api.http) = { post: "/v1beta1/{parent=projects/*/databases/*/documents}:listCollectionIds" body: "*" };
+ option (google.api.http) = {
+ post: "/v1beta1/{parent=projects/*/databases/*/documents}:listCollectionIds"
+ body: "*"
+ additional_bindings {
+ post: "/v1beta1/{parent=projects/*/databases/*/documents/*/**}:listCollectionIds"
+ body: "*"
+ }
+ };
}
}
@@ -356,8 +401,7 @@ message CommitRequest {
// Always executed atomically and in order.
repeated Write writes = 2;
- // If non-empty, applies all writes in this transaction, and commits it.
- // Otherwise, applies the writes as if they were in their own transaction.
+ // If set, applies all writes in this transaction, and commits it.
bytes transaction = 3;
}
diff --git a/Firestore/Protos/protos/google/firestore/v1beta1/query.proto b/Firestore/Protos/protos/google/firestore/v1beta1/query.proto
index d19b022..5f5b0cf 100644
--- a/Firestore/Protos/protos/google/firestore/v1beta1/query.proto
+++ b/Firestore/Protos/protos/google/firestore/v1beta1/query.proto
@@ -1,4 +1,4 @@
-// Copyright 2017 Google Inc.
+// Copyright 2018 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -26,6 +26,7 @@ option java_multiple_files = true;
option java_outer_classname = "QueryProto";
option java_package = "com.google.firestore.v1beta1";
option objc_class_prefix = "GCFS";
+option php_namespace = "Google\\Cloud\\Firestore\\V1beta1";
// A Firestore query.
@@ -98,6 +99,9 @@ message StructuredQuery {
// Equal.
EQUAL = 5;
+
+ // Contains. Requires that the field is an array.
+ ARRAY_CONTAINS = 7;
}
// The field to filter by.
diff --git a/Firestore/Protos/protos/google/firestore/v1beta1/write.options b/Firestore/Protos/protos/google/firestore/v1beta1/write.options
new file mode 100644
index 0000000..41c6a34
--- /dev/null
+++ b/Firestore/Protos/protos/google/firestore/v1beta1/write.options
@@ -0,0 +1 @@
+google.firestore.v1beta1.Write.operation no_unions:true
diff --git a/Firestore/Protos/protos/google/firestore/v1beta1/write.proto b/Firestore/Protos/protos/google/firestore/v1beta1/write.proto
index b6e9d5f..3f4732b 100644
--- a/Firestore/Protos/protos/google/firestore/v1beta1/write.proto
+++ b/Firestore/Protos/protos/google/firestore/v1beta1/write.proto
@@ -1,4 +1,4 @@
-// Copyright 2017 Google Inc.
+// Copyright 2018 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -27,6 +27,7 @@ option java_multiple_files = true;
option java_outer_classname = "WriteProto";
option java_package = "com.google.firestore.v1beta1";
option objc_class_prefix = "GCFS";
+option php_namespace = "Google\\Cloud\\Firestore\\V1beta1";
// A write on a document.
@@ -40,11 +41,6 @@ message Write {
// `projects/{project_id}/databases/{database_id}/documents/{document_path}`.
string delete = 2;
- // The name of a document on which to verify the `current_document`
- // precondition.
- // This only requires read access to the document.
- string verify = 5;
-
// Applies a tranformation to a document.
// At most one `transform` per document is allowed in a given request.
// An `update` cannot follow a `transform` on the same document in a given
@@ -55,9 +51,10 @@ message Write {
// The fields to update in this write.
//
// This field can be set only when the operation is `update`.
- // None of the field paths in the mask may contain a reserved name.
- // If the document exists on the server and has fields not referenced in the
- // mask, they are left unchanged.
+ // If the mask is not set for an `update` and the document exists, any
+ // existing data will be overwritten.
+ // If the mask is set and the document on the server has fields not covered by
+ // the mask, they are left unchanged.
// Fields referenced in the mask, but not present in the input document, are
// deleted from the document on the server.
// The field paths in this mask must not contain a reserved field name.
@@ -78,7 +75,8 @@ message DocumentTransform {
// Unspecified. This value must not be used.
SERVER_VALUE_UNSPECIFIED = 0;
- // The time at which the server processed the request.
+ // The time at which the server processed the request, with millisecond
+ // precision.
REQUEST_TIME = 1;
}
@@ -90,6 +88,32 @@ message DocumentTransform {
oneof transform_type {
// Sets the field to the given server value.
ServerValue set_to_server_value = 2;
+
+ // Append the given elements in order if they are not already present in
+ // the current field value.
+ // If the field is not an array, or if the field does not yet exist, it is
+ // first set to the empty array.
+ //
+ // Equivalent numbers of different types (e.g. 3L and 3.0) are
+ // considered equal when checking if a value is missing.
+ // NaN is equal to NaN, and Null is equal to Null.
+ // If the input contains multiple equivalent values, only the first will
+ // be considered.
+ //
+ // The corresponding transform_result will be the null value.
+ ArrayValue append_missing_elements = 6;
+
+ // Remove all of the given elements from the array in the field.
+ // If the field is not an array, or if the field does not yet exist, it is
+ // set to the empty array.
+ //
+ // Equivalent numbers of the different types (e.g. 3L and 3.0) are
+ // considered equal when deciding whether an element should be removed.
+ // NaN is equal to NaN, and Null is equal to Null.
+ // This will remove all equivalent values if there are duplicates.
+ //
+ // The corresponding transform_result will be the null value.
+ ArrayValue remove_all_from_array = 7;
}
}
@@ -98,6 +122,7 @@ message DocumentTransform {
// The list of transformations to apply to the fields of the document, in
// order.
+ // This must not be empty.
repeated FieldTransform field_transforms = 2;
}
diff --git a/Firestore/Protos/protos/google/protobuf/any.proto b/Firestore/Protos/protos/google/protobuf/any.proto
new file mode 100644
index 0000000..4932942
--- /dev/null
+++ b/Firestore/Protos/protos/google/protobuf/any.proto
@@ -0,0 +1,154 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc. All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+syntax = "proto3";
+
+package google.protobuf;
+
+option csharp_namespace = "Google.Protobuf.WellKnownTypes";
+option go_package = "github.com/golang/protobuf/ptypes/any";
+option java_package = "com.google.protobuf";
+option java_outer_classname = "AnyProto";
+option java_multiple_files = true;
+option objc_class_prefix = "GPB";
+
+// `Any` contains an arbitrary serialized protocol buffer message along with a
+// URL that describes the type of the serialized message.
+//
+// Protobuf library provides support to pack/unpack Any values in the form
+// of utility functions or additional generated methods of the Any type.
+//
+// Example 1: Pack and unpack a message in C++.
+//
+// Foo foo = ...;
+// Any any;
+// any.PackFrom(foo);
+// ...
+// if (any.UnpackTo(&foo)) {
+// ...
+// }
+//
+// Example 2: Pack and unpack a message in Java.
+//
+// Foo foo = ...;
+// Any any = Any.pack(foo);
+// ...
+// if (any.is(Foo.class)) {
+// foo = any.unpack(Foo.class);
+// }
+//
+// Example 3: Pack and unpack a message in Python.
+//
+// foo = Foo(...)
+// any = Any()
+// any.Pack(foo)
+// ...
+// if any.Is(Foo.DESCRIPTOR):
+// any.Unpack(foo)
+// ...
+//
+// Example 4: Pack and unpack a message in Go
+//
+// foo := &pb.Foo{...}
+// any, err := ptypes.MarshalAny(foo)
+// ...
+// foo := &pb.Foo{}
+// if err := ptypes.UnmarshalAny(any, foo); err != nil {
+// ...
+// }
+//
+// The pack methods provided by protobuf library will by default use
+// 'type.googleapis.com/full.type.name' as the type URL and the unpack
+// methods only use the fully qualified type name after the last '/'
+// in the type URL, for example "foo.bar.com/x/y.z" will yield type
+// name "y.z".
+//
+//
+// JSON
+// ====
+// The JSON representation of an `Any` value uses the regular
+// representation of the deserialized, embedded message, with an
+// additional field `@type` which contains the type URL. Example:
+//
+// package google.profile;
+// message Person {
+// string first_name = 1;
+// string last_name = 2;
+// }
+//
+// {
+// "@type": "type.googleapis.com/google.profile.Person",
+// "firstName": <string>,
+// "lastName": <string>
+// }
+//
+// If the embedded message type is well-known and has a custom JSON
+// representation, that representation will be embedded adding a field
+// `value` which holds the custom JSON in addition to the `@type`
+// field. Example (for message [google.protobuf.Duration][]):
+//
+// {
+// "@type": "type.googleapis.com/google.protobuf.Duration",
+// "value": "1.212s"
+// }
+//
+message Any {
+ // A URL/resource name that uniquely identifies the type of the serialized
+ // protocol buffer message. The last segment of the URL's path must represent
+ // the fully qualified name of the type (as in
+ // `path/google.protobuf.Duration`). The name should be in a canonical form
+ // (e.g., leading "." is not accepted).
+ //
+ // In practice, teams usually precompile into the binary all types that they
+ // expect it to use in the context of Any. However, for URLs which use the
+ // scheme `http`, `https`, or no scheme, one can optionally set up a type
+ // server that maps type URLs to message definitions as follows:
+ //
+ // * If no scheme is provided, `https` is assumed.
+ // * An HTTP GET on the URL must yield a [google.protobuf.Type][]
+ // value in binary format, or produce an error.
+ // * Applications are allowed to cache lookup results based on the
+ // URL, or have them precompiled into a binary to avoid any
+ // lookup. Therefore, binary compatibility needs to be preserved
+ // on changes to types. (Use versioned type names to manage
+ // breaking changes.)
+ //
+ // Note: this functionality is not currently available in the official
+ // protobuf release, and it is not used for type URLs beginning with
+ // type.googleapis.com.
+ //
+ // Schemes other than `http`, `https` (or the empty scheme) might be
+ // used with implementation specific semantics.
+ //
+ string type_url = 1;
+
+ // Must be a valid serialized protocol buffer of the above specified type.
+ bytes value = 2;
+}
diff --git a/Firestore/Protos/protos/google/protobuf/empty.proto b/Firestore/Protos/protos/google/protobuf/empty.proto
new file mode 100644
index 0000000..03cacd2
--- /dev/null
+++ b/Firestore/Protos/protos/google/protobuf/empty.proto
@@ -0,0 +1,52 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc. All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+syntax = "proto3";
+
+package google.protobuf;
+
+option csharp_namespace = "Google.Protobuf.WellKnownTypes";
+option go_package = "github.com/golang/protobuf/ptypes/empty";
+option java_package = "com.google.protobuf";
+option java_outer_classname = "EmptyProto";
+option java_multiple_files = true;
+option objc_class_prefix = "GPB";
+option cc_enable_arenas = true;
+
+// A generic empty message that you can re-use to avoid defining duplicated
+// empty messages in your APIs. A typical example is to use it as the request
+// or the response type of an API method. For instance:
+//
+// service Foo {
+// rpc Bar(google.protobuf.Empty) returns (google.protobuf.Empty);
+// }
+//
+// The JSON representation for `Empty` is empty JSON object `{}`.
+message Empty {}
diff --git a/Firestore/Protos/protos/google/protobuf/struct.options b/Firestore/Protos/protos/google/protobuf/struct.options
new file mode 100644
index 0000000..56fa2d0
--- /dev/null
+++ b/Firestore/Protos/protos/google/protobuf/struct.options
@@ -0,0 +1 @@
+google.protobuf.Value.kind no_unions:true
diff --git a/Firestore/Protos/protos/google/protobuf/struct.proto b/Firestore/Protos/protos/google/protobuf/struct.proto
new file mode 100644
index 0000000..7d7808e
--- /dev/null
+++ b/Firestore/Protos/protos/google/protobuf/struct.proto
@@ -0,0 +1,96 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc. All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+syntax = "proto3";
+
+package google.protobuf;
+
+option csharp_namespace = "Google.Protobuf.WellKnownTypes";
+option cc_enable_arenas = true;
+option go_package = "github.com/golang/protobuf/ptypes/struct;structpb";
+option java_package = "com.google.protobuf";
+option java_outer_classname = "StructProto";
+option java_multiple_files = true;
+option objc_class_prefix = "GPB";
+
+
+// `Struct` represents a structured data value, consisting of fields
+// which map to dynamically typed values. In some languages, `Struct`
+// might be supported by a native representation. For example, in
+// scripting languages like JS a struct is represented as an
+// object. The details of that representation are described together
+// with the proto support for the language.
+//
+// The JSON representation for `Struct` is JSON object.
+message Struct {
+ // Unordered map of dynamically typed values.
+ map<string, Value> fields = 1;
+}
+
+// `Value` represents a dynamically typed value which can be either
+// null, a number, a string, a boolean, a recursive struct value, or a
+// list of values. A producer of value is expected to set one of that
+// variants, absence of any variant indicates an error.
+//
+// The JSON representation for `Value` is JSON value.
+message Value {
+ // The kind of value.
+ oneof kind {
+ // Represents a null value.
+ NullValue null_value = 1;
+ // Represents a double value.
+ double number_value = 2;
+ // Represents a string value.
+ string string_value = 3;
+ // Represents a boolean value.
+ bool bool_value = 4;
+ // Represents a structured value.
+ Struct struct_value = 5;
+ // Represents a repeated `Value`.
+ ListValue list_value = 6;
+ }
+}
+
+// `NullValue` is a singleton enumeration to represent the null value for the
+// `Value` type union.
+//
+// The JSON representation for `NullValue` is JSON `null`.
+enum NullValue {
+ // Null value.
+ NULL_VALUE = 0;
+}
+
+// `ListValue` is a wrapper around a repeated field of values.
+//
+// The JSON representation for `ListValue` is JSON array.
+message ListValue {
+ // Repeated field of dynamically typed values.
+ repeated Value values = 1;
+}
diff --git a/Firestore/Protos/protos/google/protobuf/timestamp.proto b/Firestore/Protos/protos/google/protobuf/timestamp.proto
new file mode 100644
index 0000000..eafb3fa
--- /dev/null
+++ b/Firestore/Protos/protos/google/protobuf/timestamp.proto
@@ -0,0 +1,135 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc. All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+syntax = "proto3";
+
+package google.protobuf;
+
+option csharp_namespace = "Google.Protobuf.WellKnownTypes";
+option cc_enable_arenas = true;
+option go_package = "github.com/golang/protobuf/ptypes/timestamp";
+option java_package = "com.google.protobuf";
+option java_outer_classname = "TimestampProto";
+option java_multiple_files = true;
+option objc_class_prefix = "GPB";
+
+// A Timestamp represents a point in time independent of any time zone
+// or calendar, represented as seconds and fractions of seconds at
+// nanosecond resolution in UTC Epoch time. It is encoded using the
+// Proleptic Gregorian Calendar which extends the Gregorian calendar
+// backwards to year one. It is encoded assuming all minutes are 60
+// seconds long, i.e. leap seconds are "smeared" so that no leap second
+// table is needed for interpretation. Range is from
+// 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z.
+// By restricting to that range, we ensure that we can convert to
+// and from RFC 3339 date strings.
+// See [https://www.ietf.org/rfc/rfc3339.txt](https://www.ietf.org/rfc/rfc3339.txt).
+//
+// # Examples
+//
+// Example 1: Compute Timestamp from POSIX `time()`.
+//
+// Timestamp timestamp;
+// timestamp.set_seconds(time(NULL));
+// timestamp.set_nanos(0);
+//
+// Example 2: Compute Timestamp from POSIX `gettimeofday()`.
+//
+// struct timeval tv;
+// gettimeofday(&tv, NULL);
+//
+// Timestamp timestamp;
+// timestamp.set_seconds(tv.tv_sec);
+// timestamp.set_nanos(tv.tv_usec * 1000);
+//
+// Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`.
+//
+// FILETIME ft;
+// GetSystemTimeAsFileTime(&ft);
+// UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
+//
+// // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z
+// // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z.
+// Timestamp timestamp;
+// timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL));
+// timestamp.set_nanos((INT32) ((ticks % 10000000) * 100));
+//
+// Example 4: Compute Timestamp from Java `System.currentTimeMillis()`.
+//
+// long millis = System.currentTimeMillis();
+//
+// Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000)
+// .setNanos((int) ((millis % 1000) * 1000000)).build();
+//
+//
+// Example 5: Compute Timestamp from current time in Python.
+//
+// timestamp = Timestamp()
+// timestamp.GetCurrentTime()
+//
+// # JSON Mapping
+//
+// In JSON format, the Timestamp type is encoded as a string in the
+// [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the
+// format is "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z"
+// where {year} is always expressed using four digits while {month}, {day},
+// {hour}, {min}, and {sec} are zero-padded to two digits each. The fractional
+// seconds, which can go up to 9 digits (i.e. up to 1 nanosecond resolution),
+// are optional. The "Z" suffix indicates the timezone ("UTC"); the timezone
+// is required. A proto3 JSON serializer should always use UTC (as indicated by
+// "Z") when printing the Timestamp type and a proto3 JSON parser should be
+// able to accept both UTC and other timezones (as indicated by an offset).
+//
+// For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past
+// 01:30 UTC on January 15, 2017.
+//
+// In JavaScript, one can convert a Date object to this format using the
+// standard [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString]
+// method. In Python, a standard `datetime.datetime` object can be converted
+// to this format using [`strftime`](https://docs.python.org/2/library/time.html#time.strftime)
+// with the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one
+// can use the Joda Time's [`ISODateTimeFormat.dateTime()`](
+// http://www.joda.org/joda-time/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime--
+// ) to obtain a formatter capable of generating timestamps in this format.
+//
+//
+message Timestamp {
+
+ // Represents seconds of UTC time since Unix epoch
+ // 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to
+ // 9999-12-31T23:59:59Z inclusive.
+ int64 seconds = 1;
+
+ // Non-negative fractions of a second at nanosecond resolution. Negative
+ // second values with fractions must still have non-negative nanos values
+ // that count forward in time. Must be from 0 to 999,999,999
+ // inclusive.
+ int32 nanos = 2;
+}
diff --git a/Firestore/Protos/protos/google/protobuf/wrappers.proto b/Firestore/Protos/protos/google/protobuf/wrappers.proto
new file mode 100644
index 0000000..0194763
--- /dev/null
+++ b/Firestore/Protos/protos/google/protobuf/wrappers.proto
@@ -0,0 +1,118 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc. All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// Wrappers for primitive (non-message) types. These types are useful
+// for embedding primitives in the `google.protobuf.Any` type and for places
+// where we need to distinguish between the absence of a primitive
+// typed field and its default value.
+
+syntax = "proto3";
+
+package google.protobuf;
+
+option csharp_namespace = "Google.Protobuf.WellKnownTypes";
+option cc_enable_arenas = true;
+option go_package = "github.com/golang/protobuf/ptypes/wrappers";
+option java_package = "com.google.protobuf";
+option java_outer_classname = "WrappersProto";
+option java_multiple_files = true;
+option objc_class_prefix = "GPB";
+
+// Wrapper message for `double`.
+//
+// The JSON representation for `DoubleValue` is JSON number.
+message DoubleValue {
+ // The double value.
+ double value = 1;
+}
+
+// Wrapper message for `float`.
+//
+// The JSON representation for `FloatValue` is JSON number.
+message FloatValue {
+ // The float value.
+ float value = 1;
+}
+
+// Wrapper message for `int64`.
+//
+// The JSON representation for `Int64Value` is JSON string.
+message Int64Value {
+ // The int64 value.
+ int64 value = 1;
+}
+
+// Wrapper message for `uint64`.
+//
+// The JSON representation for `UInt64Value` is JSON string.
+message UInt64Value {
+ // The uint64 value.
+ uint64 value = 1;
+}
+
+// Wrapper message for `int32`.
+//
+// The JSON representation for `Int32Value` is JSON number.
+message Int32Value {
+ // The int32 value.
+ int32 value = 1;
+}
+
+// Wrapper message for `uint32`.
+//
+// The JSON representation for `UInt32Value` is JSON number.
+message UInt32Value {
+ // The uint32 value.
+ uint32 value = 1;
+}
+
+// Wrapper message for `bool`.
+//
+// The JSON representation for `BoolValue` is JSON `true` and `false`.
+message BoolValue {
+ // The bool value.
+ bool value = 1;
+}
+
+// Wrapper message for `string`.
+//
+// The JSON representation for `StringValue` is JSON string.
+message StringValue {
+ // The string value.
+ string value = 1;
+}
+
+// Wrapper message for `bytes`.
+//
+// The JSON representation for `BytesValue` is JSON string.
+message BytesValue {
+ // The bytes value.
+ bytes value = 1;
+}
diff --git a/Firestore/Source/API/FIRCollectionReference+Internal.h b/Firestore/Source/API/FIRCollectionReference+Internal.h
index 1d00cbb..9d36ede 100644
--- a/Firestore/Source/API/FIRCollectionReference+Internal.h
+++ b/Firestore/Source/API/FIRCollectionReference+Internal.h
@@ -16,13 +16,14 @@
#import "FIRCollectionReference.h"
-NS_ASSUME_NONNULL_BEGIN
+#include "Firestore/core/src/firebase/firestore/model/resource_path.h"
-@class FSTResourcePath;
+NS_ASSUME_NONNULL_BEGIN
/** Internal FIRCollectionReference API we don't want exposed in our public header files. */
@interface FIRCollectionReference (Internal)
-+ (instancetype)referenceWithPath:(FSTResourcePath *)path firestore:(FIRFirestore *)firestore;
++ (instancetype)referenceWithPath:(const firebase::firestore::model::ResourcePath &)path
+ firestore:(FIRFirestore *)firestore;
@end
NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/API/FIRCollectionReference.mm b/Firestore/Source/API/FIRCollectionReference.mm
index 92cccc6..3f1559e 100644
--- a/Firestore/Source/API/FIRCollectionReference.mm
+++ b/Firestore/Source/API/FIRCollectionReference.mm
@@ -15,6 +15,7 @@
*/
#import "FIRCollectionReference.h"
+#import "FIRFirestore.h"
#include "Firestore/core/src/firebase/firestore/util/autoid.h"
@@ -22,17 +23,22 @@
#import "Firestore/Source/API/FIRQuery+Internal.h"
#import "Firestore/Source/API/FIRQuery_Init.h"
#import "Firestore/Source/Core/FSTQuery.h"
-#import "Firestore/Source/Model/FSTDocumentKey.h"
-#import "Firestore/Source/Model/FSTPath.h"
#import "Firestore/Source/Util/FSTAssert.h"
#import "Firestore/Source/Util/FSTUsageValidation.h"
+#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+#include "Firestore/core/src/firebase/firestore/model/resource_path.h"
+#include "Firestore/core/src/firebase/firestore/util/string_apple.h"
+
+namespace util = firebase::firestore::util;
+using firebase::firestore::model::DocumentKey;
+using firebase::firestore::model::ResourcePath;
using firebase::firestore::util::CreateAutoId;
NS_ASSUME_NONNULL_BEGIN
@interface FIRCollectionReference ()
-- (instancetype)initWithPath:(FSTResourcePath *)path
+- (instancetype)initWithPath:(const ResourcePath &)path
firestore:(FIRFirestore *)firestore NS_DESIGNATED_INITIALIZER;
// Mark the super class designated initializer unavailable.
@@ -42,19 +48,19 @@ NS_ASSUME_NONNULL_BEGIN
@end
@implementation FIRCollectionReference (Internal)
-+ (instancetype)referenceWithPath:(FSTResourcePath *)path firestore:(FIRFirestore *)firestore {
++ (instancetype)referenceWithPath:(const ResourcePath &)path firestore:(FIRFirestore *)firestore {
return [[FIRCollectionReference alloc] initWithPath:path firestore:firestore];
}
@end
@implementation FIRCollectionReference
-- (instancetype)initWithPath:(FSTResourcePath *)path firestore:(FIRFirestore *)firestore {
- if (path.length % 2 != 1) {
+- (instancetype)initWithPath:(const ResourcePath &)path firestore:(FIRFirestore *)firestore {
+ if (path.size() % 2 != 1) {
FSTThrowInvalidArgument(
@"Invalid collection reference. Collection references must have an odd "
- "number of segments, but %@ has %d",
- path.canonicalString, path.length);
+ "number of segments, but %s has %zu",
+ path.CanonicalString().c_str(), path.size());
}
self = [super initWithQuery:[FSTQuery queryWithPath:path] firestore:firestore];
return self;
@@ -65,30 +71,50 @@ NS_ASSUME_NONNULL_BEGIN
FSTFail(@"Use FIRCollectionReference initWithPath: initializer.");
}
+// NSObject Methods
+- (BOOL)isEqual:(nullable id)other {
+ if (other == self) return YES;
+ if (![[other class] isEqual:[self class]]) return NO;
+
+ return [self isEqualToReference:other];
+}
+
+- (BOOL)isEqualToReference:(nullable FIRCollectionReference *)reference {
+ if (self == reference) return YES;
+ if (reference == nil) return NO;
+ return [self.firestore isEqual:reference.firestore] && [self.query isEqual:reference.query];
+}
+
+- (NSUInteger)hash {
+ NSUInteger hash = [self.firestore hash];
+ hash = hash * 31u + [self.query hash];
+ return hash;
+}
+
- (NSString *)collectionID {
- return [self.query.path lastSegment];
+ return util::WrapNSString(self.query.path.last_segment());
}
- (FIRDocumentReference *_Nullable)parent {
- FSTResourcePath *parentPath = [self.query.path pathByRemovingLastSegment];
- if (parentPath.isEmpty) {
+ const ResourcePath parentPath = self.query.path.PopLast();
+ if (parentPath.empty()) {
return nil;
} else {
- FSTDocumentKey *key = [FSTDocumentKey keyWithPath:parentPath];
+ DocumentKey key{parentPath};
return [FIRDocumentReference referenceWithKey:key firestore:self.firestore];
}
}
- (NSString *)path {
- return [self.query.path canonicalString];
+ return util::WrapNSString(self.query.path.CanonicalString());
}
- (FIRDocumentReference *)documentWithPath:(NSString *)documentPath {
if (!documentPath) {
FSTThrowInvalidArgument(@"Document path cannot be nil.");
}
- FSTResourcePath *subPath = [FSTResourcePath pathWithString:documentPath];
- FSTResourcePath *path = [self.query.path pathByAppendingPath:subPath];
+ const ResourcePath subPath = ResourcePath::FromString(util::MakeStringView(documentPath));
+ const ResourcePath path = self.query.path.Append(subPath);
return [FIRDocumentReference referenceWithPath:path firestore:self.firestore];
}
@@ -105,9 +131,7 @@ NS_ASSUME_NONNULL_BEGIN
}
- (FIRDocumentReference *)documentWithAutoID {
- NSString *autoID = [NSString stringWithUTF8String:CreateAutoId().c_str()];
- FSTDocumentKey *key =
- [FSTDocumentKey keyWithPath:[self.query.path pathByAppendingSegment:autoID]];
+ const DocumentKey key{self.query.path.Append(CreateAutoId())};
return [FIRDocumentReference referenceWithKey:key firestore:self.firestore];
}
diff --git a/Firestore/Source/API/FIRDocumentChange+Internal.h b/Firestore/Source/API/FIRDocumentChange+Internal.h
index 7e2e5c6..7c9723c 100644
--- a/Firestore/Source/API/FIRDocumentChange+Internal.h
+++ b/Firestore/Source/API/FIRDocumentChange+Internal.h
@@ -16,6 +16,7 @@
#import "FIRDocumentChange.h"
+@class FIRFirestore;
@class FSTViewSnapshot;
NS_ASSUME_NONNULL_BEGIN
diff --git a/Firestore/Source/API/FIRDocumentChange.m b/Firestore/Source/API/FIRDocumentChange.mm
index 970dc90..d1d9999 100644
--- a/Firestore/Source/API/FIRDocumentChange.m
+++ b/Firestore/Source/API/FIRDocumentChange.mm
@@ -57,11 +57,11 @@ NS_ASSUME_NONNULL_BEGIN
NSUInteger index = 0;
NSMutableArray<FIRDocumentChange *> *changes = [NSMutableArray array];
for (FSTDocumentViewChange *change in snapshot.documentChanges) {
- FIRDocumentSnapshot *document =
- [FIRDocumentSnapshot snapshotWithFirestore:firestore
- documentKey:change.document.key
- document:change.document
- fromCache:snapshot.isFromCache];
+ FIRQueryDocumentSnapshot *document =
+ [FIRQueryDocumentSnapshot snapshotWithFirestore:firestore
+ documentKey:change.document.key
+ document:change.document
+ fromCache:snapshot.isFromCache];
FSTAssert(change.type == FSTDocumentViewChangeTypeAdded,
@"Invalid event type for first snapshot");
FSTAssert(!lastDocument ||
@@ -79,11 +79,11 @@ NS_ASSUME_NONNULL_BEGIN
FSTDocumentSet *indexTracker = snapshot.oldDocuments;
NSMutableArray<FIRDocumentChange *> *changes = [NSMutableArray array];
for (FSTDocumentViewChange *change in snapshot.documentChanges) {
- FIRDocumentSnapshot *document =
- [FIRDocumentSnapshot snapshotWithFirestore:firestore
- documentKey:change.document.key
- document:change.document
- fromCache:snapshot.isFromCache];
+ FIRQueryDocumentSnapshot *document =
+ [FIRQueryDocumentSnapshot snapshotWithFirestore:firestore
+ documentKey:change.document.key
+ document:change.document
+ fromCache:snapshot.isFromCache];
NSUInteger oldIndex = NSNotFound;
NSUInteger newIndex = NSNotFound;
@@ -112,7 +112,7 @@ NS_ASSUME_NONNULL_BEGIN
@implementation FIRDocumentChange
- (instancetype)initWithType:(FIRDocumentChangeType)type
- document:(FIRDocumentSnapshot *)document
+ document:(FIRQueryDocumentSnapshot *)document
oldIndex:(NSUInteger)oldIndex
newIndex:(NSUInteger)newIndex {
if (self = [super init]) {
diff --git a/Firestore/Source/API/FIRDocumentReference+Internal.h b/Firestore/Source/API/FIRDocumentReference+Internal.h
index 5e12ddc..eb078ca 100644
--- a/Firestore/Source/API/FIRDocumentReference+Internal.h
+++ b/Firestore/Source/API/FIRDocumentReference+Internal.h
@@ -16,18 +16,20 @@
#import "FIRDocumentReference.h"
-NS_ASSUME_NONNULL_BEGIN
+#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+#include "Firestore/core/src/firebase/firestore/model/resource_path.h"
-@class FSTDocumentKey;
-@class FSTResourcePath;
+NS_ASSUME_NONNULL_BEGIN
/** Internal FIRDocumentReference API we don't want exposed in our public header files. */
@interface FIRDocumentReference (Internal)
-+ (instancetype)referenceWithPath:(FSTResourcePath *)path firestore:(FIRFirestore *)firestore;
-+ (instancetype)referenceWithKey:(FSTDocumentKey *)key firestore:(FIRFirestore *)firestore;
++ (instancetype)referenceWithPath:(const firebase::firestore::model::ResourcePath &)path
+ firestore:(FIRFirestore *)firestore;
++ (instancetype)referenceWithKey:(firebase::firestore::model::DocumentKey)key
+ firestore:(FIRFirestore *)firestore;
-@property(nonatomic, strong, readonly) FSTDocumentKey *key;
+- (const firebase::firestore::model::DocumentKey &)key;
@end
diff --git a/Firestore/Source/API/FIRDocumentReference.m b/Firestore/Source/API/FIRDocumentReference.mm
index 1c80ea9..2423472 100644
--- a/Firestore/Source/API/FIRDocumentReference.m
+++ b/Firestore/Source/API/FIRDocumentReference.mm
@@ -18,6 +18,9 @@
#import <GRPCClient/GRPCCall.h>
+#include <memory>
+#include <utility>
+
#import "FIRFirestoreErrors.h"
#import "FIRSnapshotMetadata.h"
#import "Firestore/Source/API/FIRCollectionReference+Internal.h"
@@ -25,86 +28,43 @@
#import "Firestore/Source/API/FIRDocumentSnapshot+Internal.h"
#import "Firestore/Source/API/FIRFirestore+Internal.h"
#import "Firestore/Source/API/FIRListenerRegistration+Internal.h"
-#import "Firestore/Source/API/FIRSetOptions+Internal.h"
#import "Firestore/Source/API/FSTUserDataConverter.h"
#import "Firestore/Source/Core/FSTEventManager.h"
#import "Firestore/Source/Core/FSTFirestoreClient.h"
#import "Firestore/Source/Core/FSTQuery.h"
-#import "Firestore/Source/Model/FSTDocumentKey.h"
#import "Firestore/Source/Model/FSTDocumentSet.h"
#import "Firestore/Source/Model/FSTFieldValue.h"
#import "Firestore/Source/Model/FSTMutation.h"
-#import "Firestore/Source/Model/FSTPath.h"
#import "Firestore/Source/Util/FSTAssert.h"
#import "Firestore/Source/Util/FSTAsyncQueryListener.h"
#import "Firestore/Source/Util/FSTUsageValidation.h"
-NS_ASSUME_NONNULL_BEGIN
-
-#pragma mark - FIRDocumentListenOptions
-
-@interface FIRDocumentListenOptions ()
-
-- (instancetype)initWithIncludeMetadataChanges:(BOOL)includeMetadataChanges
- NS_DESIGNATED_INITIALIZER;
-
-@end
-
-@implementation FIRDocumentListenOptions
-
-+ (instancetype)options {
- return [[FIRDocumentListenOptions alloc] init];
-}
-
-- (instancetype)initWithIncludeMetadataChanges:(BOOL)includeMetadataChanges {
- if (self = [super init]) {
- _includeMetadataChanges = includeMetadataChanges;
- }
- return self;
-}
-
-- (instancetype)init {
- return [self initWithIncludeMetadataChanges:NO];
-}
+#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+#include "Firestore/core/src/firebase/firestore/model/precondition.h"
+#include "Firestore/core/src/firebase/firestore/model/resource_path.h"
+#include "Firestore/core/src/firebase/firestore/util/string_apple.h"
-- (instancetype)includeMetadataChanges:(BOOL)includeMetadataChanges {
- return [[FIRDocumentListenOptions alloc] initWithIncludeMetadataChanges:includeMetadataChanges];
-}
+namespace util = firebase::firestore::util;
+using firebase::firestore::model::DocumentKey;
+using firebase::firestore::model::Precondition;
+using firebase::firestore::model::ResourcePath;
-@end
+NS_ASSUME_NONNULL_BEGIN
#pragma mark - FIRDocumentReference
@interface FIRDocumentReference ()
-- (instancetype)initWithKey:(FSTDocumentKey *)key
+- (instancetype)initWithKey:(DocumentKey)key
firestore:(FIRFirestore *)firestore NS_DESIGNATED_INITIALIZER;
-@property(nonatomic, strong, readonly) FSTDocumentKey *key;
@end
-@implementation FIRDocumentReference (Internal)
-
-+ (instancetype)referenceWithPath:(FSTResourcePath *)path firestore:(FIRFirestore *)firestore {
- if (path.length % 2 != 0) {
- FSTThrowInvalidArgument(
- @"Invalid document reference. Document references must have an even "
- "number of segments, but %@ has %d",
- path.canonicalString, path.length);
- }
- return
- [FIRDocumentReference referenceWithKey:[FSTDocumentKey keyWithPath:path] firestore:firestore];
-}
-
-+ (instancetype)referenceWithKey:(FSTDocumentKey *)key firestore:(FIRFirestore *)firestore {
- return [[FIRDocumentReference alloc] initWithKey:key firestore:firestore];
+@implementation FIRDocumentReference {
+ DocumentKey _key;
}
-@end
-
-@implementation FIRDocumentReference
-
-- (instancetype)initWithKey:(FSTDocumentKey *)key firestore:(FIRFirestore *)firestore {
+- (instancetype)initWithKey:(DocumentKey)key firestore:(FIRFirestore *)firestore {
if (self = [super init]) {
- _key = key;
+ _key = std::move(key);
_firestore = firestore;
}
return self;
@@ -114,7 +74,7 @@ NS_ASSUME_NONNULL_BEGIN
- (BOOL)isEqual:(nullable id)other {
if (other == self) return YES;
- if (!other || ![[other class] isEqual:[self class]]) return NO;
+ if (![[other class] isEqual:[self class]]) return NO;
return [self isEqualToReference:other];
}
@@ -122,63 +82,59 @@ NS_ASSUME_NONNULL_BEGIN
- (BOOL)isEqualToReference:(nullable FIRDocumentReference *)reference {
if (self == reference) return YES;
if (reference == nil) return NO;
- if (self.firestore != reference.firestore && ![self.firestore isEqual:reference.firestore])
- return NO;
- if (self.key != reference.key && ![self.key isEqualToKey:reference.key]) return NO;
- return YES;
+ return [self.firestore isEqual:reference.firestore] && self.key == reference.key;
}
- (NSUInteger)hash {
NSUInteger hash = [self.firestore hash];
- hash = hash * 31u + [self.key hash];
+ hash = hash * 31u + self.key.Hash();
return hash;
}
#pragma mark - Public Methods
- (NSString *)documentID {
- return [self.key.path lastSegment];
+ return util::WrapNSString(self.key.path().last_segment());
}
- (FIRCollectionReference *)parent {
- FSTResourcePath *parentPath = [self.key.path pathByRemovingLastSegment];
- return [FIRCollectionReference referenceWithPath:parentPath firestore:self.firestore];
+ return
+ [FIRCollectionReference referenceWithPath:self.key.path().PopLast() firestore:self.firestore];
}
- (NSString *)path {
- return [self.key.path canonicalString];
+ return util::WrapNSString(self.key.path().CanonicalString());
}
- (FIRCollectionReference *)collectionWithPath:(NSString *)collectionPath {
if (!collectionPath) {
FSTThrowInvalidArgument(@"Collection path cannot be nil.");
}
- FSTResourcePath *subPath = [FSTResourcePath pathWithString:collectionPath];
- FSTResourcePath *path = [self.key.path pathByAppendingPath:subPath];
+ const ResourcePath subPath = ResourcePath::FromString(util::MakeStringView(collectionPath));
+ const ResourcePath path = self.key.path().Append(subPath);
return [FIRCollectionReference referenceWithPath:path firestore:self.firestore];
}
- (void)setData:(NSDictionary<NSString *, id> *)documentData {
- return [self setData:documentData options:[FIRSetOptions overwrite] completion:nil];
+ return [self setData:documentData merge:NO completion:nil];
}
-- (void)setData:(NSDictionary<NSString *, id> *)documentData options:(FIRSetOptions *)options {
- return [self setData:documentData options:options completion:nil];
+- (void)setData:(NSDictionary<NSString *, id> *)documentData merge:(BOOL)merge {
+ return [self setData:documentData merge:merge completion:nil];
}
- (void)setData:(NSDictionary<NSString *, id> *)documentData
completion:(nullable void (^)(NSError *_Nullable error))completion {
- return [self setData:documentData options:[FIRSetOptions overwrite] completion:completion];
+ return [self setData:documentData merge:NO completion:completion];
}
- (void)setData:(NSDictionary<NSString *, id> *)documentData
- options:(FIRSetOptions *)options
+ merge:(BOOL)merge
completion:(nullable void (^)(NSError *_Nullable error))completion {
- FSTParsedSetData *parsed = options.isMerge
- ? [self.firestore.dataConverter parsedMergeData:documentData]
- : [self.firestore.dataConverter parsedSetData:documentData];
+ FSTParsedSetData *parsed = merge ? [self.firestore.dataConverter parsedMergeData:documentData]
+ : [self.firestore.dataConverter parsedSetData:documentData];
return [self.firestore.client
- writeMutations:[parsed mutationsWithKey:self.key precondition:[FSTPrecondition none]]
+ writeMutations:[parsed mutationsWithKey:self.key precondition:Precondition::None()]
completion:completion];
}
@@ -190,8 +146,7 @@ NS_ASSUME_NONNULL_BEGIN
completion:(nullable void (^)(NSError *_Nullable error))completion {
FSTParsedUpdateData *parsed = [self.firestore.dataConverter parsedUpdateData:fields];
return [self.firestore.client
- writeMutations:[parsed mutationsWithKey:self.key
- precondition:[FSTPrecondition preconditionWithExists:YES]]
+ writeMutations:[parsed mutationsWithKey:self.key precondition:Precondition::Exists(true)]
completion:completion];
}
@@ -201,16 +156,15 @@ NS_ASSUME_NONNULL_BEGIN
- (void)deleteDocumentWithCompletion:(nullable void (^)(NSError *_Nullable error))completion {
FSTDeleteMutation *mutation =
- [[FSTDeleteMutation alloc] initWithKey:self.key precondition:[FSTPrecondition none]];
+ [[FSTDeleteMutation alloc] initWithKey:self.key precondition:Precondition::None()];
return [self.firestore.client writeMutations:@[ mutation ] completion:completion];
}
- (void)getDocumentWithCompletion:(void (^)(FIRDocumentSnapshot *_Nullable document,
NSError *_Nullable error))completion {
- FSTListenOptions *listenOptions =
- [[FSTListenOptions alloc] initWithIncludeQueryMetadataChanges:YES
- includeDocumentMetadataChanges:YES
- waitForSyncWhenOnline:YES];
+ FSTListenOptions *options = [[FSTListenOptions alloc] initWithIncludeQueryMetadataChanges:YES
+ includeDocumentMetadataChanges:YES
+ waitForSyncWhenOnline:YES];
dispatch_semaphore_t registered = dispatch_semaphore_create(0);
__block id<FIRListenerRegistration> listenerRegistration;
@@ -247,28 +201,28 @@ NS_ASSUME_NONNULL_BEGIN
}
};
- listenerRegistration =
- [self addSnapshotListenerInternalWithOptions:listenOptions listener:listener];
+ listenerRegistration = [self addSnapshotListenerInternalWithOptions:options listener:listener];
dispatch_semaphore_signal(registered);
}
- (id<FIRListenerRegistration>)addSnapshotListener:(FIRDocumentSnapshotBlock)listener {
- return [self addSnapshotListenerWithOptions:nil listener:listener];
+ return [self addSnapshotListenerWithIncludeMetadataChanges:NO listener:listener];
}
-- (id<FIRListenerRegistration>)addSnapshotListenerWithOptions:
- (nullable FIRDocumentListenOptions *)options
- listener:(FIRDocumentSnapshotBlock)listener {
- return [self addSnapshotListenerInternalWithOptions:[self internalOptions:options]
- listener:listener];
+- (id<FIRListenerRegistration>)
+addSnapshotListenerWithIncludeMetadataChanges:(BOOL)includeMetadataChanges
+ listener:(FIRDocumentSnapshotBlock)listener {
+ FSTListenOptions *options =
+ [self internalOptionsForIncludeMetadataChanges:includeMetadataChanges];
+ return [self addSnapshotListenerInternalWithOptions:options listener:listener];
}
- (id<FIRListenerRegistration>)
addSnapshotListenerInternalWithOptions:(FSTListenOptions *)internalOptions
listener:(FIRDocumentSnapshotBlock)listener {
FIRFirestore *firestore = self.firestore;
- FSTQuery *query = [FSTQuery queryWithPath:self.key.path];
- FSTDocumentKey *key = self.key;
+ FSTQuery *query = [FSTQuery queryWithPath:self.key.path()];
+ const DocumentKey key = self.key;
FSTViewSnapshotHandler snapshotHandler = ^(FSTViewSnapshot *snapshot, NSError *error) {
if (error) {
@@ -300,11 +254,34 @@ addSnapshotListenerInternalWithOptions:(FSTListenOptions *)internalOptions
}
/** Converts the public API options object to the internal options object. */
-- (FSTListenOptions *)internalOptions:(nullable FIRDocumentListenOptions *)options {
- return
- [[FSTListenOptions alloc] initWithIncludeQueryMetadataChanges:options.includeMetadataChanges
- includeDocumentMetadataChanges:options.includeMetadataChanges
- waitForSyncWhenOnline:NO];
+- (FSTListenOptions *)internalOptionsForIncludeMetadataChanges:(BOOL)includeMetadataChanges {
+ return [[FSTListenOptions alloc] initWithIncludeQueryMetadataChanges:includeMetadataChanges
+ includeDocumentMetadataChanges:includeMetadataChanges
+ waitForSyncWhenOnline:NO];
+}
+
+@end
+
+#pragma mark - FIRDocumentReference (Internal)
+
+@implementation FIRDocumentReference (Internal)
+
++ (instancetype)referenceWithPath:(const ResourcePath &)path firestore:(FIRFirestore *)firestore {
+ if (path.size() % 2 != 0) {
+ FSTThrowInvalidArgument(
+ @"Invalid document reference. Document references must have an even "
+ "number of segments, but %s has %zu",
+ path.CanonicalString().c_str(), path.size());
+ }
+ return [FIRDocumentReference referenceWithKey:DocumentKey{path} firestore:firestore];
+}
+
++ (instancetype)referenceWithKey:(DocumentKey)key firestore:(FIRFirestore *)firestore {
+ return [[FIRDocumentReference alloc] initWithKey:std::move(key) firestore:firestore];
+}
+
+- (const firebase::firestore::model::DocumentKey &)key {
+ return _key;
}
@end
diff --git a/Firestore/Source/API/FIRDocumentSnapshot+Internal.h b/Firestore/Source/API/FIRDocumentSnapshot+Internal.h
index f2776f0..f4cef9a 100644
--- a/Firestore/Source/API/FIRDocumentSnapshot+Internal.h
+++ b/Firestore/Source/API/FIRDocumentSnapshot+Internal.h
@@ -16,9 +16,10 @@
#import "FIRDocumentSnapshot.h"
+#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+
@class FIRFirestore;
@class FSTDocument;
-@class FSTDocumentKey;
NS_ASSUME_NONNULL_BEGIN
@@ -26,7 +27,7 @@ NS_ASSUME_NONNULL_BEGIN
@interface FIRDocumentSnapshot (Internal)
+ (instancetype)snapshotWithFirestore:(FIRFirestore *)firestore
- documentKey:(FSTDocumentKey *)documentKey
+ documentKey:(firebase::firestore::model::DocumentKey)documentKey
document:(nullable FSTDocument *)document
fromCache:(BOOL)fromCache;
diff --git a/Firestore/Source/API/FIRDocumentSnapshot.m b/Firestore/Source/API/FIRDocumentSnapshot.m
deleted file mode 100644
index b78472e..0000000
--- a/Firestore/Source/API/FIRDocumentSnapshot.m
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * Copyright 2017 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 "FIRDocumentSnapshot.h"
-
-#import "Firestore/Source/API/FIRDocumentReference+Internal.h"
-#import "Firestore/Source/API/FIRFieldPath+Internal.h"
-#import "Firestore/Source/API/FIRFirestore+Internal.h"
-#import "Firestore/Source/API/FIRSnapshotMetadata+Internal.h"
-#import "Firestore/Source/Model/FSTDatabaseID.h"
-#import "Firestore/Source/Model/FSTDocument.h"
-#import "Firestore/Source/Model/FSTDocumentKey.h"
-#import "Firestore/Source/Model/FSTFieldValue.h"
-#import "Firestore/Source/Model/FSTPath.h"
-#import "Firestore/Source/Util/FSTUsageValidation.h"
-
-NS_ASSUME_NONNULL_BEGIN
-
-@interface FIRDocumentSnapshot ()
-
-- (instancetype)initWithFirestore:(FIRFirestore *)firestore
- documentKey:(FSTDocumentKey *)documentKey
- document:(nullable FSTDocument *)document
- fromCache:(BOOL)fromCache NS_DESIGNATED_INITIALIZER;
-
-@property(nonatomic, strong, readonly) FIRFirestore *firestore;
-@property(nonatomic, strong, readonly) FSTDocumentKey *internalKey;
-@property(nonatomic, strong, readonly, nullable) FSTDocument *internalDocument;
-@property(nonatomic, assign, readonly) BOOL fromCache;
-
-@end
-
-@implementation FIRDocumentSnapshot (Internal)
-
-+ (instancetype)snapshotWithFirestore:(FIRFirestore *)firestore
- documentKey:(FSTDocumentKey *)documentKey
- document:(nullable FSTDocument *)document
- fromCache:(BOOL)fromCache {
- return [[FIRDocumentSnapshot alloc] initWithFirestore:firestore
- documentKey:documentKey
- document:document
- fromCache:fromCache];
-}
-
-@end
-
-@implementation FIRDocumentSnapshot {
- FIRSnapshotMetadata *_cachedMetadata;
-}
-
-@dynamic metadata;
-
-- (instancetype)initWithFirestore:(FIRFirestore *)firestore
- documentKey:(FSTDocumentKey *)documentKey
- document:(nullable FSTDocument *)document
- fromCache:(BOOL)fromCache {
- if (self = [super init]) {
- _firestore = firestore;
- _internalKey = documentKey;
- _internalDocument = document;
- _fromCache = fromCache;
- }
- return self;
-}
-
-@dynamic exists;
-
-- (BOOL)exists {
- return _internalDocument != nil;
-}
-
-- (FIRDocumentReference *)reference {
- return [FIRDocumentReference referenceWithKey:self.internalKey firestore:self.firestore];
-}
-
-- (NSString *)documentID {
- return [self.internalKey.path lastSegment];
-}
-
-- (FIRSnapshotMetadata *)metadata {
- if (!_cachedMetadata) {
- _cachedMetadata = [FIRSnapshotMetadata
- snapshotMetadataWithPendingWrites:self.internalDocument.hasLocalMutations
- fromCache:self.fromCache];
- }
- return _cachedMetadata;
-}
-
-- (NSDictionary<NSString *, id> *)data {
- FSTDocument *document = self.internalDocument;
-
- if (!document) {
- FSTThrowInvalidUsage(
- @"NonExistentDocumentException",
- @"Document '%@' doesn't exist. "
- @"Check document.exists to make sure the document exists before calling document.data.",
- self.internalKey);
- }
-
- return [self convertedObject:[self.internalDocument data]];
-}
-
-- (nullable id)objectForKeyedSubscript:(id)key {
- FIRFieldPath *fieldPath;
-
- if ([key isKindOfClass:[NSString class]]) {
- fieldPath = [FIRFieldPath pathWithDotSeparatedString:key];
- } else if ([key isKindOfClass:[FIRFieldPath class]]) {
- fieldPath = key;
- } else {
- FSTThrowInvalidArgument(@"Subscript key must be an NSString or FIRFieldPath.");
- }
-
- FSTFieldValue *fieldValue = [[self.internalDocument data] valueForPath:fieldPath.internalValue];
- return [self convertedValue:fieldValue];
-}
-
-- (id)convertedValue:(FSTFieldValue *)value {
- if ([value isKindOfClass:[FSTObjectValue class]]) {
- return [self convertedObject:(FSTObjectValue *)value];
- } else if ([value isKindOfClass:[FSTArrayValue class]]) {
- return [self convertedArray:(FSTArrayValue *)value];
- } else if ([value isKindOfClass:[FSTReferenceValue class]]) {
- FSTReferenceValue *ref = (FSTReferenceValue *)value;
- FSTDatabaseID *refDatabase = ref.databaseID;
- FSTDatabaseID *database = self.firestore.databaseID;
- if (![refDatabase isEqualToDatabaseId:database]) {
- // TODO(b/32073923): Log this as a proper warning.
- NSLog(
- @"WARNING: Document %@ contains a document reference within a different database "
- "(%@/%@) which is not supported. It will be treated as a reference within the "
- "current database (%@/%@) instead.",
- self.reference.path, refDatabase.projectID, refDatabase.databaseID, database.projectID,
- database.databaseID);
- }
- return [FIRDocumentReference referenceWithKey:ref.value firestore:self.firestore];
- } else {
- return value.value;
- }
-}
-
-- (NSDictionary<NSString *, id> *)convertedObject:(FSTObjectValue *)objectValue {
- NSMutableDictionary *result = [NSMutableDictionary dictionary];
- [objectValue.internalValue
- enumerateKeysAndObjectsUsingBlock:^(NSString *key, FSTFieldValue *value, BOOL *stop) {
- result[key] = [self convertedValue:value];
- }];
- return result;
-}
-
-- (NSArray<id> *)convertedArray:(FSTArrayValue *)arrayValue {
- NSArray<FSTFieldValue *> *internalValue = arrayValue.internalValue;
- NSMutableArray *result = [NSMutableArray arrayWithCapacity:internalValue.count];
- [internalValue enumerateObjectsUsingBlock:^(id value, NSUInteger idx, BOOL *stop) {
- [result addObject:[self convertedValue:value]];
- }];
- return result;
-}
-
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/API/FIRDocumentSnapshot.mm b/Firestore/Source/API/FIRDocumentSnapshot.mm
new file mode 100644
index 0000000..0fd59f4
--- /dev/null
+++ b/Firestore/Source/API/FIRDocumentSnapshot.mm
@@ -0,0 +1,273 @@
+/*
+ * Copyright 2017 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 "FIRDocumentSnapshot.h"
+
+#include <utility>
+
+#import "FIRFirestoreSettings.h"
+
+#import "Firestore/Source/API/FIRDocumentReference+Internal.h"
+#import "Firestore/Source/API/FIRFieldPath+Internal.h"
+#import "Firestore/Source/API/FIRFirestore+Internal.h"
+#import "Firestore/Source/API/FIRSnapshotMetadata+Internal.h"
+#import "Firestore/Source/API/FIRSnapshotOptions+Internal.h"
+#import "Firestore/Source/Model/FSTDocument.h"
+#import "Firestore/Source/Model/FSTFieldValue.h"
+#import "Firestore/Source/Util/FSTAssert.h"
+#import "Firestore/Source/Util/FSTUsageValidation.h"
+
+#include "Firestore/core/src/firebase/firestore/model/database_id.h"
+#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+#include "Firestore/core/src/firebase/firestore/util/string_apple.h"
+
+namespace util = firebase::firestore::util;
+using firebase::firestore::model::DatabaseId;
+using firebase::firestore::model::DocumentKey;
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface FIRDocumentSnapshot ()
+
+- (instancetype)initWithFirestore:(FIRFirestore *)firestore
+ documentKey:(DocumentKey)documentKey
+ document:(nullable FSTDocument *)document
+ fromCache:(BOOL)fromCache NS_DESIGNATED_INITIALIZER;
+
+- (const DocumentKey &)internalKey;
+
+@property(nonatomic, strong, readonly) FIRFirestore *firestore;
+@property(nonatomic, strong, readonly, nullable) FSTDocument *internalDocument;
+@property(nonatomic, assign, readonly) BOOL fromCache;
+
+@end
+
+@implementation FIRDocumentSnapshot (Internal)
+
++ (instancetype)snapshotWithFirestore:(FIRFirestore *)firestore
+ documentKey:(DocumentKey)documentKey
+ document:(nullable FSTDocument *)document
+ fromCache:(BOOL)fromCache {
+ return [[[self class] alloc] initWithFirestore:firestore
+ documentKey:std::move(documentKey)
+ document:document
+ fromCache:fromCache];
+}
+
+@end
+
+@implementation FIRDocumentSnapshot {
+ FIRSnapshotMetadata *_cachedMetadata;
+ DocumentKey _internalKey;
+}
+
+@dynamic metadata;
+
+- (instancetype)initWithFirestore:(FIRFirestore *)firestore
+ documentKey:(DocumentKey)documentKey
+ document:(nullable FSTDocument *)document
+ fromCache:(BOOL)fromCache {
+ if (self = [super init]) {
+ _firestore = firestore;
+ _internalKey = std::move(documentKey);
+ _internalDocument = document;
+ _fromCache = fromCache;
+ }
+ return self;
+}
+
+- (const DocumentKey &)internalKey {
+ return _internalKey;
+}
+
+// NSObject Methods
+- (BOOL)isEqual:(nullable id)other {
+ if (other == self) return YES;
+ // self class could be FIRDocumentSnapshot or subtype. So we compare with base type explicitly.
+ if (![other isKindOfClass:[FIRDocumentSnapshot class]]) return NO;
+
+ return [self isEqualToSnapshot:other];
+}
+
+- (BOOL)isEqualToSnapshot:(nullable FIRDocumentSnapshot *)snapshot {
+ if (self == snapshot) return YES;
+ if (snapshot == nil) return NO;
+
+ return [self.firestore isEqual:snapshot.firestore] && self.internalKey == snapshot.internalKey &&
+ (self.internalDocument == snapshot.internalDocument ||
+ [self.internalDocument isEqual:snapshot.internalDocument]) &&
+ self.fromCache == snapshot.fromCache;
+}
+
+- (NSUInteger)hash {
+ NSUInteger hash = [self.firestore hash];
+ hash = hash * 31u + self.internalKey.Hash();
+ hash = hash * 31u + [self.internalDocument hash];
+ hash = hash * 31u + (self.fromCache ? 1 : 0);
+ return hash;
+}
+
+@dynamic exists;
+
+- (BOOL)exists {
+ return _internalDocument != nil;
+}
+
+- (FIRDocumentReference *)reference {
+ return [FIRDocumentReference referenceWithKey:self.internalKey firestore:self.firestore];
+}
+
+- (NSString *)documentID {
+ return util::WrapNSString(self.internalKey.path().last_segment());
+}
+
+- (FIRSnapshotMetadata *)metadata {
+ if (!_cachedMetadata) {
+ _cachedMetadata = [FIRSnapshotMetadata
+ snapshotMetadataWithPendingWrites:self.internalDocument.hasLocalMutations
+ fromCache:self.fromCache];
+ }
+ return _cachedMetadata;
+}
+
+- (nullable NSDictionary<NSString *, id> *)data {
+ return [self dataWithOptions:[FIRSnapshotOptions defaultOptions]];
+}
+
+- (nullable NSDictionary<NSString *, id> *)dataWithOptions:(FIRSnapshotOptions *)options {
+ return self.internalDocument == nil
+ ? nil
+ : [self convertedObject:[self.internalDocument data]
+ options:[FSTFieldValueOptions
+ optionsForSnapshotOptions:options
+ timestampsInSnapshotsEnabled:
+ self.firestore.settings.timestampsInSnapshotsEnabled]];
+}
+
+- (nullable id)valueForField:(id)field {
+ return [self valueForField:field options:[FIRSnapshotOptions defaultOptions]];
+}
+
+- (nullable id)valueForField:(id)field options:(FIRSnapshotOptions *)options {
+ FIRFieldPath *fieldPath;
+
+ if ([field isKindOfClass:[NSString class]]) {
+ fieldPath = [FIRFieldPath pathWithDotSeparatedString:field];
+ } else if ([field isKindOfClass:[FIRFieldPath class]]) {
+ fieldPath = field;
+ } else {
+ FSTThrowInvalidArgument(@"Subscript key must be an NSString or FIRFieldPath.");
+ }
+
+ FSTFieldValue *fieldValue = [[self.internalDocument data] valueForPath:fieldPath.internalValue];
+ return fieldValue == nil
+ ? nil
+ : [self convertedValue:fieldValue
+ options:[FSTFieldValueOptions
+ optionsForSnapshotOptions:options
+ timestampsInSnapshotsEnabled:
+ self.firestore.settings.timestampsInSnapshotsEnabled]];
+}
+
+- (nullable id)objectForKeyedSubscript:(id)key {
+ return [self valueForField:key];
+}
+
+- (id)convertedValue:(FSTFieldValue *)value options:(FSTFieldValueOptions *)options {
+ if ([value isKindOfClass:[FSTObjectValue class]]) {
+ return [self convertedObject:(FSTObjectValue *)value options:options];
+ } else if ([value isKindOfClass:[FSTArrayValue class]]) {
+ return [self convertedArray:(FSTArrayValue *)value options:options];
+ } else if ([value isKindOfClass:[FSTReferenceValue class]]) {
+ FSTReferenceValue *ref = (FSTReferenceValue *)value;
+ const DatabaseId *refDatabase = ref.databaseID;
+ const DatabaseId *database = self.firestore.databaseID;
+ if (*refDatabase != *database) {
+ // TODO(b/32073923): Log this as a proper warning.
+ NSLog(
+ @"WARNING: Document %@ contains a document reference within a different database "
+ "(%s/%s) which is not supported. It will be treated as a reference within the "
+ "current database (%s/%s) instead.",
+ self.reference.path, refDatabase->project_id().c_str(),
+ refDatabase->database_id().c_str(), database->project_id().c_str(),
+ database->database_id().c_str());
+ }
+ return [FIRDocumentReference referenceWithKey:[ref valueWithOptions:options]
+ firestore:self.firestore];
+ } else {
+ return [value valueWithOptions:options];
+ }
+}
+
+- (NSDictionary<NSString *, id> *)convertedObject:(FSTObjectValue *)objectValue
+ options:(FSTFieldValueOptions *)options {
+ NSMutableDictionary *result = [NSMutableDictionary dictionary];
+ [objectValue.internalValue
+ enumerateKeysAndObjectsUsingBlock:^(NSString *key, FSTFieldValue *value, BOOL *stop) {
+ result[key] = [self convertedValue:value options:options];
+ }];
+ return result;
+}
+
+- (NSArray<id> *)convertedArray:(FSTArrayValue *)arrayValue
+ options:(FSTFieldValueOptions *)options {
+ NSArray<FSTFieldValue *> *internalValue = arrayValue.internalValue;
+ NSMutableArray *result = [NSMutableArray arrayWithCapacity:internalValue.count];
+ [internalValue enumerateObjectsUsingBlock:^(id value, NSUInteger idx, BOOL *stop) {
+ [result addObject:[self convertedValue:value options:options]];
+ }];
+ return result;
+}
+
+@end
+
+@interface FIRQueryDocumentSnapshot ()
+
+- (instancetype)initWithFirestore:(FIRFirestore *)firestore
+ documentKey:(DocumentKey)documentKey
+ document:(FSTDocument *)document
+ fromCache:(BOOL)fromCache NS_DESIGNATED_INITIALIZER;
+
+@end
+
+@implementation FIRQueryDocumentSnapshot
+
+- (instancetype)initWithFirestore:(FIRFirestore *)firestore
+ documentKey:(DocumentKey)documentKey
+ document:(FSTDocument *)document
+ fromCache:(BOOL)fromCache {
+ self = [super initWithFirestore:firestore
+ documentKey:std::move(documentKey)
+ document:document
+ fromCache:fromCache];
+ return self;
+}
+
+- (NSDictionary<NSString *, id> *)data {
+ NSDictionary<NSString *, id> *data = [super data];
+ FSTAssert(data, @"Document in a QueryDocumentSnapshot should exist");
+ return data;
+}
+
+- (NSDictionary<NSString *, id> *)dataWithOptions:(FIRSnapshotOptions *)options {
+ NSDictionary<NSString *, id> *data = [super dataWithOptions:options];
+ FSTAssert(data, @"Document in a QueryDocumentSnapshot should exist");
+ return data;
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/API/FIRFieldPath+Internal.h b/Firestore/Source/API/FIRFieldPath+Internal.h
index 227cdad..6fb6add 100644
--- a/Firestore/Source/API/FIRFieldPath+Internal.h
+++ b/Firestore/Source/API/FIRFieldPath+Internal.h
@@ -16,16 +16,16 @@
#import "FIRFieldPath.h"
-@class FSTFieldPath;
+#include "Firestore/core/src/firebase/firestore/model/field_path.h"
NS_ASSUME_NONNULL_BEGIN
@interface FIRFieldPath ()
-- (instancetype)initPrivate:(FSTFieldPath *)path NS_DESIGNATED_INITIALIZER;
-
/** Internal field path representation */
-@property(nonatomic, strong, readonly) FSTFieldPath *internalValue;
+- (const firebase::firestore::model::FieldPath &)internalValue;
+
+- (instancetype)initPrivate:(firebase::firestore::model::FieldPath)path NS_DESIGNATED_INITIALIZER;
@end
diff --git a/Firestore/Source/API/FIRFieldPath.m b/Firestore/Source/API/FIRFieldPath.mm
index d0a70c0..d0d8714 100644
--- a/Firestore/Source/API/FIRFieldPath.m
+++ b/Firestore/Source/API/FIRFieldPath.mm
@@ -14,13 +14,31 @@
* limitations under the License.
*/
-#import "Firestore/Source/API/FIRFieldPath+Internal.h"
+#import "FIRFieldPath.h"
+
+#include <functional>
+#include <string>
+#include <utility>
+#include <vector>
-#import "Firestore/Source/Model/FSTPath.h"
+#import "Firestore/Source/API/FIRFieldPath+Internal.h"
#import "Firestore/Source/Util/FSTUsageValidation.h"
+#include "Firestore/core/src/firebase/firestore/model/field_path.h"
+#include "Firestore/core/src/firebase/firestore/util/string_apple.h"
+
+namespace util = firebase::firestore::util;
+using firebase::firestore::model::FieldPath;
+
NS_ASSUME_NONNULL_BEGIN
+@interface FIRFieldPath () {
+ /** Internal field path representation */
+ firebase::firestore::model::FieldPath _internalValue;
+}
+
+@end
+
@implementation FIRFieldPath
- (instancetype)initWithFields:(NSArray<NSString *> *)fieldNames {
@@ -28,22 +46,25 @@ NS_ASSUME_NONNULL_BEGIN
FSTThrowInvalidArgument(@"Invalid field path. Provided names must not be empty.");
}
+ std::vector<std::string> field_names{};
+ field_names.reserve(fieldNames.count);
for (int i = 0; i < fieldNames.count; ++i) {
if (fieldNames[i].length == 0) {
FSTThrowInvalidArgument(@"Invalid field name at index %d. Field names must not be empty.", i);
}
+ field_names.emplace_back(util::MakeString(fieldNames[i]));
}
- return [self initPrivate:[FSTFieldPath pathWithSegments:fieldNames]];
+ return [self initPrivate:FieldPath(std::move(field_names))];
}
+ (instancetype)documentID {
- return [[FIRFieldPath alloc] initPrivate:FSTFieldPath.keyFieldPath];
+ return [[FIRFieldPath alloc] initPrivate:FieldPath::KeyFieldPath()];
}
-- (instancetype)initPrivate:(FSTFieldPath *)fieldPath {
+- (instancetype)initPrivate:(FieldPath)fieldPath {
if (self = [super init]) {
- _internalValue = fieldPath;
+ _internalValue = std::move(fieldPath);
}
return self;
}
@@ -77,10 +98,10 @@ NS_ASSUME_NONNULL_BEGIN
}
- (id)copyWithZone:(NSZone *__nullable)zone {
- return [[[self class] alloc] initPrivate:self.internalValue];
+ return [[[self class] alloc] initPrivate:_internalValue];
}
-- (BOOL)isEqual:(id)object {
+- (BOOL)isEqual:(nullable id)object {
if (self == object) {
return YES;
}
@@ -89,11 +110,15 @@ NS_ASSUME_NONNULL_BEGIN
return NO;
}
- return [self.internalValue isEqual:((FIRFieldPath *)object).internalValue];
+ return _internalValue == ((FIRFieldPath *)object)->_internalValue;
}
- (NSUInteger)hash {
- return [self.internalValue hash];
+ return _internalValue.Hash();
+}
+
+- (const firebase::firestore::model::FieldPath &)internalValue {
+ return _internalValue;
}
@end
diff --git a/Firestore/Source/API/FIRFieldValue+Internal.h b/Firestore/Source/API/FIRFieldValue+Internal.h
index 1b4a99c..1618cd4 100644
--- a/Firestore/Source/API/FIRFieldValue+Internal.h
+++ b/Firestore/Source/API/FIRFieldValue+Internal.h
@@ -18,6 +18,14 @@
NS_ASSUME_NONNULL_BEGIN
+@interface FIRFieldValue (Internal)
+/**
+ * The method name (e.g. "FieldValue.delete()") that was used to create this FIRFieldValue
+ * instance, for use in error messages, etc.
+ */
+@property(nonatomic, strong, readonly) NSString *methodName;
+@end
+
/**
* FIRFieldValue class for field deletes. Exposed internally so code can do isKindOfClass checks on
* it.
@@ -34,4 +42,16 @@ NS_ASSUME_NONNULL_BEGIN
- (instancetype)init NS_UNAVAILABLE;
@end
+/** FIRFieldValue class for array unions. */
+@interface FSTArrayUnionFieldValue : FIRFieldValue
+- (instancetype)init NS_UNAVAILABLE;
+@property(strong, nonatomic, readonly) NSArray<id> *elements;
+@end
+
+/** FIRFieldValue class for array removes. */
+@interface FSTArrayRemoveFieldValue : FIRFieldValue
+- (instancetype)init NS_UNAVAILABLE;
+@property(strong, nonatomic, readonly) NSArray<id> *elements;
+@end
+
NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/API/FIRFieldValue.m b/Firestore/Source/API/FIRFieldValue.mm
index 7ae4fb0..e0ed8c7 100644
--- a/Firestore/Source/API/FIRFieldValue.m
+++ b/Firestore/Source/API/FIRFieldValue.mm
@@ -46,6 +46,10 @@ NS_ASSUME_NONNULL_BEGIN
return sharedInstance;
}
+- (NSString *)methodName {
+ return @"FieldValue.delete()";
+}
+
@end
#pragma mark - FSTServerTimestampFieldValue
@@ -72,6 +76,40 @@ NS_ASSUME_NONNULL_BEGIN
return sharedInstance;
}
+- (NSString *)methodName {
+ return @"FieldValue.serverTimestamp()";
+}
+
+@end
+
+#pragma mark - FSTArrayUnionFieldValue
+
+@interface FSTArrayUnionFieldValue ()
+- (instancetype)initWithElements:(NSArray<id> *)elements;
+@end
+
+@implementation FSTArrayUnionFieldValue
+- (instancetype)initWithElements:(NSArray<id> *)elements {
+ if (self = [super initPrivate]) {
+ _elements = elements;
+ }
+ return self;
+}
+@end
+
+#pragma mark - FSTArrayRemoveFieldValue
+
+@interface FSTArrayRemoveFieldValue ()
+- (instancetype)initWithElements:(NSArray<id> *)elements;
+@end
+
+@implementation FSTArrayRemoveFieldValue
+- (instancetype)initWithElements:(NSArray<id> *)elements {
+ if (self = [super initPrivate]) {
+ _elements = elements;
+ }
+ return self;
+}
@end
#pragma mark - FIRFieldValue
@@ -91,6 +129,14 @@ NS_ASSUME_NONNULL_BEGIN
return [FSTServerTimestampFieldValue serverTimestampFieldValue];
}
++ (instancetype)fieldValueForArrayUnion:(NSArray<id> *)elements {
+ return [[FSTArrayUnionFieldValue alloc] initWithElements:elements];
+}
+
++ (instancetype)fieldValueForArrayRemove:(NSArray<id> *)elements {
+ return [[FSTArrayRemoveFieldValue alloc] initWithElements:elements];
+}
+
@end
NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/API/FIRFirestore+Internal.h b/Firestore/Source/API/FIRFirestore+Internal.h
index c2e995a..a52533d 100644
--- a/Firestore/Source/API/FIRFirestore+Internal.h
+++ b/Firestore/Source/API/FIRFirestore+Internal.h
@@ -16,13 +16,17 @@
#import "FIRFirestore.h"
+#include <memory>
+
+#include "Firestore/core/src/firebase/firestore/auth/credentials_provider.h"
+#include "Firestore/core/src/firebase/firestore/model/database_id.h"
+#include "absl/strings/string_view.h"
+
NS_ASSUME_NONNULL_BEGIN
-@class FSTDatabaseID;
@class FSTDispatchQueue;
@class FSTFirestoreClient;
@class FSTUserDataConverter;
-@protocol FSTCredentialsProvider;
@interface FIRFirestore (/* Init */)
@@ -30,10 +34,11 @@ NS_ASSUME_NONNULL_BEGIN
* Initializes a Firestore object with all the required parameters directly. This exists so that
* tests can create FIRFirestore objects without needing FIRApp.
*/
-- (instancetype)initWithProjectID:(NSString *)projectID
- database:(NSString *)database
+- (instancetype)initWithProjectID:(const absl::string_view)projectID
+ database:(const absl::string_view)database
persistenceKey:(NSString *)persistenceKey
- credentialsProvider:(id<FSTCredentialsProvider>)credentialsProvider
+ credentialsProvider:(std::unique_ptr<firebase::firestore::auth::CredentialsProvider>)
+ credentialsProvider
workerDispatchQueue:(FSTDispatchQueue *)workerDispatchQueue
firebaseApp:(FIRApp *)app;
@@ -54,7 +59,8 @@ NS_ASSUME_NONNULL_BEGIN
- (void)shutdownWithCompletion:(nullable void (^)(NSError *_Nullable error))completion
NS_SWIFT_NAME(shutdown(completion:));
-@property(nonatomic, strong, readonly) FSTDatabaseID *databaseID;
+// FIRFirestore ownes the DatabaseId instance.
+@property(nonatomic, assign, readonly) const firebase::firestore::model::DatabaseId *databaseID;
@property(nonatomic, strong, readonly) FSTFirestoreClient *client;
@property(nonatomic, strong, readonly) FSTUserDataConverter *dataConverter;
diff --git a/Firestore/Source/API/FIRFirestore.m b/Firestore/Source/API/FIRFirestore.m
deleted file mode 100644
index 7814ce1..0000000
--- a/Firestore/Source/API/FIRFirestore.m
+++ /dev/null
@@ -1,284 +0,0 @@
-/*
- * Copyright 2017 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 "FIRFirestore.h"
-
-#import <FirebaseCore/FIRApp.h>
-#import <FirebaseCore/FIRLogger.h>
-#import <FirebaseCore/FIROptions.h>
-
-#import "FIRFirestoreSettings.h"
-#import "Firestore/Source/API/FIRCollectionReference+Internal.h"
-#import "Firestore/Source/API/FIRDocumentReference+Internal.h"
-#import "Firestore/Source/API/FIRFirestore+Internal.h"
-#import "Firestore/Source/API/FIRTransaction+Internal.h"
-#import "Firestore/Source/API/FIRWriteBatch+Internal.h"
-#import "Firestore/Source/API/FSTUserDataConverter.h"
-
-#import "Firestore/Source/Auth/FSTCredentialsProvider.h"
-#import "Firestore/Source/Core/FSTDatabaseInfo.h"
-#import "Firestore/Source/Core/FSTFirestoreClient.h"
-#import "Firestore/Source/Model/FSTDatabaseID.h"
-#import "Firestore/Source/Model/FSTDocumentKey.h"
-#import "Firestore/Source/Model/FSTPath.h"
-#import "Firestore/Source/Util/FSTAssert.h"
-#import "Firestore/Source/Util/FSTDispatchQueue.h"
-#import "Firestore/Source/Util/FSTLogger.h"
-#import "Firestore/Source/Util/FSTUsageValidation.h"
-
-NS_ASSUME_NONNULL_BEGIN
-
-NSString *const FIRFirestoreErrorDomain = @"FIRFirestoreErrorDomain";
-
-@interface FIRFirestore ()
-
-@property(nonatomic, strong) FSTDatabaseID *databaseID;
-@property(nonatomic, strong) NSString *persistenceKey;
-@property(nonatomic, strong) id<FSTCredentialsProvider> credentialsProvider;
-@property(nonatomic, strong) FSTDispatchQueue *workerDispatchQueue;
-
-@property(nonatomic, strong) FSTFirestoreClient *client;
-@property(nonatomic, strong, readonly) FSTUserDataConverter *dataConverter;
-
-@end
-
-@implementation FIRFirestore {
- FIRFirestoreSettings *_settings;
-}
-
-+ (NSMutableDictionary<NSString *, FIRFirestore *> *)instances {
- static dispatch_once_t token = 0;
- static NSMutableDictionary<NSString *, FIRFirestore *> *instances;
- dispatch_once(&token, ^{
- instances = [NSMutableDictionary dictionary];
- });
- return instances;
-}
-
-+ (instancetype)firestore {
- FIRApp *app = [FIRApp defaultApp];
- if (!app) {
- FSTThrowInvalidUsage(@"FIRAppNotConfiguredException",
- @"Failed to get FirebaseApp instance. Please call FirebaseApp.configure() "
- @"before using Firestore");
- }
- return [self firestoreForApp:app database:kDefaultDatabaseID];
-}
-
-+ (instancetype)firestoreForApp:(FIRApp *)app {
- return [self firestoreForApp:app database:kDefaultDatabaseID];
-}
-
-// TODO(b/62410906): make this public
-+ (instancetype)firestoreForApp:(FIRApp *)app database:(NSString *)database {
- if (!app) {
- FSTThrowInvalidArgument(
- @"FirebaseApp instance may not be nil. Use FirebaseApp.app() if you'd "
- "like to use the default FirebaseApp instance.");
- }
- if (!database) {
- FSTThrowInvalidArgument(
- @"database identifier may not be nil. Use '%@' if you want the default "
- "database",
- kDefaultDatabaseID);
- }
- NSString *key = [NSString stringWithFormat:@"%@|%@", app.name, database];
-
- NSMutableDictionary<NSString *, FIRFirestore *> *instances = self.instances;
- @synchronized(instances) {
- FIRFirestore *firestore = instances[key];
- if (!firestore) {
- NSString *projectID = app.options.projectID;
- FSTAssert(projectID, @"FirebaseOptions.projectID cannot be nil.");
-
- FSTDispatchQueue *workerDispatchQueue = [FSTDispatchQueue
- queueWith:dispatch_queue_create("com.google.firebase.firestore", DISPATCH_QUEUE_SERIAL)];
-
- id<FSTCredentialsProvider> credentialsProvider;
- credentialsProvider = [[FSTFirebaseCredentialsProvider alloc] initWithApp:app];
-
- NSString *persistenceKey = app.name;
-
- firestore = [[FIRFirestore alloc] initWithProjectID:projectID
- database:database
- persistenceKey:persistenceKey
- credentialsProvider:credentialsProvider
- workerDispatchQueue:workerDispatchQueue
- firebaseApp:app];
- instances[key] = firestore;
- }
-
- return firestore;
- }
-}
-
-- (instancetype)initWithProjectID:(NSString *)projectID
- database:(NSString *)database
- persistenceKey:(NSString *)persistenceKey
- credentialsProvider:(id<FSTCredentialsProvider>)credentialsProvider
- workerDispatchQueue:(FSTDispatchQueue *)workerDispatchQueue
- firebaseApp:(FIRApp *)app {
- if (self = [super init]) {
- _databaseID = [FSTDatabaseID databaseIDWithProject:projectID database:database];
- FSTPreConverterBlock block = ^id _Nullable(id _Nullable input) {
- if ([input isKindOfClass:[FIRDocumentReference class]]) {
- FIRDocumentReference *documentReference = (FIRDocumentReference *)input;
- return [[FSTDocumentKeyReference alloc] initWithKey:documentReference.key
- databaseID:documentReference.firestore.databaseID];
- } else {
- return input;
- }
- };
- _dataConverter =
- [[FSTUserDataConverter alloc] initWithDatabaseID:_databaseID preConverter:block];
- _persistenceKey = persistenceKey;
- _credentialsProvider = credentialsProvider;
- _workerDispatchQueue = workerDispatchQueue;
- _app = app;
- _settings = [[FIRFirestoreSettings alloc] init];
- }
- return self;
-}
-
-- (FIRFirestoreSettings *)settings {
- // Disallow mutation of our internal settings
- return [_settings copy];
-}
-
-- (void)setSettings:(FIRFirestoreSettings *)settings {
- // As a special exception, don't throw if the same settings are passed repeatedly. This should
- // make it more friendly to create a Firestore instance.
- if (_client && ![_settings isEqual:settings]) {
- FSTThrowInvalidUsage(@"FIRIllegalStateException",
- @"Firestore instance has already been started and its settings can no "
- "longer be changed. You can only set settings before calling any "
- "other methods on a Firestore instance.");
- }
- _settings = [settings copy];
-}
-
-/**
- * Ensures that the FirestoreClient is configured.
- * @return self
- */
-- (instancetype)firestoreWithConfiguredClient {
- if (!_client) {
- // These values are validated elsewhere; this is just double-checking:
- FSTAssert(_settings.host, @"FirestoreSettings.host cannot be nil.");
- FSTAssert(_settings.dispatchQueue, @"FirestoreSettings.dispatchQueue cannot be nil.");
-
- FSTDatabaseInfo *databaseInfo =
- [FSTDatabaseInfo databaseInfoWithDatabaseID:_databaseID
- persistenceKey:_persistenceKey
- host:_settings.host
- sslEnabled:_settings.sslEnabled];
-
- FSTDispatchQueue *userDispatchQueue = [FSTDispatchQueue queueWith:_settings.dispatchQueue];
-
- _client = [FSTFirestoreClient clientWithDatabaseInfo:databaseInfo
- usePersistence:_settings.persistenceEnabled
- credentialsProvider:_credentialsProvider
- userDispatchQueue:userDispatchQueue
- workerDispatchQueue:_workerDispatchQueue];
- }
- return self;
-}
-
-- (FIRCollectionReference *)collectionWithPath:(NSString *)collectionPath {
- if (!collectionPath) {
- FSTThrowInvalidArgument(@"Collection path cannot be nil.");
- }
- FSTResourcePath *path = [FSTResourcePath pathWithString:collectionPath];
- return
- [FIRCollectionReference referenceWithPath:path firestore:self.firestoreWithConfiguredClient];
-}
-
-- (FIRDocumentReference *)documentWithPath:(NSString *)documentPath {
- if (!documentPath) {
- FSTThrowInvalidArgument(@"Document path cannot be nil.");
- }
- FSTResourcePath *path = [FSTResourcePath pathWithString:documentPath];
- return [FIRDocumentReference referenceWithPath:path firestore:self.firestoreWithConfiguredClient];
-}
-
-- (void)runTransactionWithBlock:(id _Nullable (^)(FIRTransaction *, NSError **))updateBlock
- dispatchQueue:(dispatch_queue_t)queue
- completion:
- (void (^)(id _Nullable result, NSError *_Nullable error))completion {
- // We wrap the function they provide in order to use internal implementation classes for
- // FSTTransaction, and to run the user callback block on the proper queue.
- if (!updateBlock) {
- FSTThrowInvalidArgument(@"Transaction block cannot be nil.");
- } else if (!completion) {
- FSTThrowInvalidArgument(@"Transaction completion block cannot be nil.");
- }
-
- FSTTransactionBlock wrappedUpdate =
- ^(FSTTransaction *internalTransaction,
- void (^internalCompletion)(id _Nullable, NSError *_Nullable)) {
- FIRTransaction *transaction =
- [FIRTransaction transactionWithFSTTransaction:internalTransaction firestore:self];
- dispatch_async(queue, ^{
- NSError *_Nullable error = nil;
- id _Nullable result = updateBlock(transaction, &error);
- if (error) {
- // Force the result to be nil in the case of an error, in case the user set both.
- result = nil;
- }
- internalCompletion(result, error);
- });
- };
- [self firestoreWithConfiguredClient];
- [self.client transactionWithRetries:5 updateBlock:wrappedUpdate completion:completion];
-}
-
-- (FIRWriteBatch *)batch {
- return [FIRWriteBatch writeBatchWithFirestore:[self firestoreWithConfiguredClient]];
-}
-
-- (void)runTransactionWithBlock:(id _Nullable (^)(FIRTransaction *, NSError **error))updateBlock
- completion:
- (void (^)(id _Nullable result, NSError *_Nullable error))completion {
- static dispatch_queue_t transactionDispatchQueue;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- transactionDispatchQueue = dispatch_queue_create("com.google.firebase.firestore.transaction",
- DISPATCH_QUEUE_CONCURRENT);
- });
- [self runTransactionWithBlock:updateBlock
- dispatchQueue:transactionDispatchQueue
- completion:completion];
-}
-
-- (void)shutdownWithCompletion:(nullable void (^)(NSError *_Nullable error))completion {
- if (!self.client) {
- completion(nil);
- return;
- }
- return [self.client shutdownWithCompletion:completion];
-}
-
-+ (BOOL)isLoggingEnabled {
- return FIRIsLoggableLevel(FIRLoggerLevelDebug, NO);
-}
-
-+ (void)enableLogging:(BOOL)logging {
- FIRSetLoggerLevel(logging ? FIRLoggerLevelDebug : FIRLoggerLevelNotice);
-}
-
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/API/FIRFirestore.mm b/Firestore/Source/API/FIRFirestore.mm
new file mode 100644
index 0000000..fe461d6
--- /dev/null
+++ b/Firestore/Source/API/FIRFirestore.mm
@@ -0,0 +1,394 @@
+/*
+ * Copyright 2017 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 "FIRFirestore.h"
+
+#import <FirebaseCore/FIRApp.h>
+#import <FirebaseCore/FIRAppInternal.h>
+#import <FirebaseCore/FIRLogger.h>
+#import <FirebaseCore/FIROptions.h>
+
+#include <memory>
+#include <utility>
+
+#import "FIRFirestoreSettings.h"
+#import "Firestore/Source/API/FIRCollectionReference+Internal.h"
+#import "Firestore/Source/API/FIRDocumentReference+Internal.h"
+#import "Firestore/Source/API/FIRFirestore+Internal.h"
+#import "Firestore/Source/API/FIRTransaction+Internal.h"
+#import "Firestore/Source/API/FIRWriteBatch+Internal.h"
+#import "Firestore/Source/API/FSTUserDataConverter.h"
+#import "Firestore/Source/Core/FSTFirestoreClient.h"
+#import "Firestore/Source/Util/FSTAssert.h"
+#import "Firestore/Source/Util/FSTDispatchQueue.h"
+#import "Firestore/Source/Util/FSTLogger.h"
+#import "Firestore/Source/Util/FSTUsageValidation.h"
+
+#include "Firestore/core/src/firebase/firestore/auth/credentials_provider.h"
+#include "Firestore/core/src/firebase/firestore/auth/firebase_credentials_provider_apple.h"
+#include "Firestore/core/src/firebase/firestore/core/database_info.h"
+#include "Firestore/core/src/firebase/firestore/model/database_id.h"
+#include "Firestore/core/src/firebase/firestore/model/resource_path.h"
+#include "Firestore/core/src/firebase/firestore/util/string_apple.h"
+#include "absl/memory/memory.h"
+
+namespace util = firebase::firestore::util;
+using firebase::firestore::auth::CredentialsProvider;
+using firebase::firestore::auth::FirebaseCredentialsProvider;
+using firebase::firestore::core::DatabaseInfo;
+using firebase::firestore::model::DatabaseId;
+using firebase::firestore::model::ResourcePath;
+
+NS_ASSUME_NONNULL_BEGIN
+
+extern "C" NSString *const FIRFirestoreErrorDomain = @"FIRFirestoreErrorDomain";
+
+@interface FIRFirestore () {
+ /** The actual owned DatabaseId instance is allocated in FIRFirestore. */
+ DatabaseId _databaseID;
+ std::unique_ptr<CredentialsProvider> _credentialsProvider;
+}
+
+@property(nonatomic, strong) NSString *persistenceKey;
+@property(nonatomic, strong) FSTDispatchQueue *workerDispatchQueue;
+
+// Note that `client` is updated after initialization, but marking this readwrite would generate an
+// incorrect setter (since we make the assignment to `client` inside an `@synchronized` block.
+@property(nonatomic, strong, readonly) FSTFirestoreClient *client;
+@property(nonatomic, strong, readonly) FSTUserDataConverter *dataConverter;
+
+@end
+
+@implementation FIRFirestore {
+ // All guarded by @synchronized(self)
+ FIRFirestoreSettings *_settings;
+ FSTFirestoreClient *_client;
+}
+
++ (NSMutableDictionary<NSString *, FIRFirestore *> *)instances {
+ static dispatch_once_t token = 0;
+ static NSMutableDictionary<NSString *, FIRFirestore *> *instances;
+ dispatch_once(&token, ^{
+ instances = [NSMutableDictionary dictionary];
+ });
+ return instances;
+}
+
++ (void)initialize {
+ NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
+ [center addObserverForName:kFIRAppDeleteNotification
+ object:nil
+ queue:nil
+ usingBlock:^(NSNotification *_Nonnull note) {
+ NSString *appName = note.userInfo[kFIRAppNameKey];
+ if (appName == nil) return;
+
+ NSMutableDictionary *instances = [self instances];
+ @synchronized(instances) {
+ // Since the key for instances isn't just the app name, iterate over all the
+ // keys to get the one(s) we have to delete. There could be multiple in case
+ // the user calls firestoreForApp:database:.
+ NSMutableArray *keysToDelete = [[NSMutableArray alloc] init];
+ NSString *keyPrefix = [NSString stringWithFormat:@"%@|", appName];
+ for (NSString *key in instances.allKeys) {
+ if ([key hasPrefix:keyPrefix]) {
+ [keysToDelete addObject:key];
+ }
+ }
+
+ // Loop through the keys found and delete them from the stored instances.
+ for (NSString *key in keysToDelete) {
+ [instances removeObjectForKey:key];
+ }
+ }
+ }];
+}
+
++ (instancetype)firestore {
+ FIRApp *app = [FIRApp defaultApp];
+ if (!app) {
+ FSTThrowInvalidUsage(@"FIRAppNotConfiguredException",
+ @"Failed to get FirebaseApp instance. Please call FirebaseApp.configure() "
+ @"before using Firestore");
+ }
+ return [self firestoreForApp:app database:util::WrapNSStringNoCopy(DatabaseId::kDefault)];
+}
+
++ (instancetype)firestoreForApp:(FIRApp *)app {
+ return [self firestoreForApp:app database:util::WrapNSStringNoCopy(DatabaseId::kDefault)];
+}
+
+// TODO(b/62410906): make this public
++ (instancetype)firestoreForApp:(FIRApp *)app database:(NSString *)database {
+ if (!app) {
+ FSTThrowInvalidArgument(
+ @"FirebaseApp instance may not be nil. Use FirebaseApp.app() if you'd "
+ "like to use the default FirebaseApp instance.");
+ }
+ if (!database) {
+ FSTThrowInvalidArgument(
+ @"database identifier may not be nil. Use '%s' if you want the default "
+ "database",
+ DatabaseId::kDefault);
+ }
+
+ // Note: If the key format changes, please change the code that detects FIRApps being deleted
+ // contained in +initialize. It checks for the app's name followed by a | character.
+ NSString *key = [NSString stringWithFormat:@"%@|%@", app.name, database];
+
+ NSMutableDictionary<NSString *, FIRFirestore *> *instances = self.instances;
+ @synchronized(instances) {
+ FIRFirestore *firestore = instances[key];
+ if (!firestore) {
+ NSString *projectID = app.options.projectID;
+ FSTAssert(projectID, @"FirebaseOptions.projectID cannot be nil.");
+
+ FSTDispatchQueue *workerDispatchQueue = [FSTDispatchQueue
+ queueWith:dispatch_queue_create("com.google.firebase.firestore", DISPATCH_QUEUE_SERIAL)];
+
+ std::unique_ptr<CredentialsProvider> credentials_provider =
+ absl::make_unique<FirebaseCredentialsProvider>(app);
+
+ NSString *persistenceKey = app.name;
+
+ firestore = [[FIRFirestore alloc] initWithProjectID:util::MakeStringView(projectID)
+ database:util::MakeStringView(database)
+ persistenceKey:persistenceKey
+ credentialsProvider:std::move(credentials_provider)
+ workerDispatchQueue:workerDispatchQueue
+ firebaseApp:app];
+ instances[key] = firestore;
+ }
+
+ return firestore;
+ }
+}
+
+- (instancetype)initWithProjectID:(const absl::string_view)projectID
+ database:(const absl::string_view)database
+ persistenceKey:(NSString *)persistenceKey
+ credentialsProvider:(std::unique_ptr<CredentialsProvider>)credentialsProvider
+ workerDispatchQueue:(FSTDispatchQueue *)workerDispatchQueue
+ firebaseApp:(FIRApp *)app {
+ if (self = [super init]) {
+ _databaseID = DatabaseId(projectID, database);
+ FSTPreConverterBlock block = ^id _Nullable(id _Nullable input) {
+ if ([input isKindOfClass:[FIRDocumentReference class]]) {
+ FIRDocumentReference *documentReference = (FIRDocumentReference *)input;
+ return [[FSTDocumentKeyReference alloc] initWithKey:documentReference.key
+ databaseID:documentReference.firestore.databaseID];
+ } else {
+ return input;
+ }
+ };
+ _dataConverter =
+ [[FSTUserDataConverter alloc] initWithDatabaseID:&_databaseID preConverter:block];
+ _persistenceKey = persistenceKey;
+ _credentialsProvider = std::move(credentialsProvider);
+ _workerDispatchQueue = workerDispatchQueue;
+ _app = app;
+ _settings = [[FIRFirestoreSettings alloc] init];
+ }
+ return self;
+}
+
+- (FIRFirestoreSettings *)settings {
+ @synchronized(self) {
+ // Disallow mutation of our internal settings
+ return [_settings copy];
+ }
+}
+
+- (void)setSettings:(FIRFirestoreSettings *)settings {
+ @synchronized(self) {
+ // As a special exception, don't throw if the same settings are passed repeatedly. This should
+ // make it more friendly to create a Firestore instance.
+ if (_client && ![_settings isEqual:settings]) {
+ FSTThrowInvalidUsage(@"FIRIllegalStateException",
+ @"Firestore instance has already been started and its settings can no "
+ "longer be changed. You can only set settings before calling any "
+ "other methods on a Firestore instance.");
+ }
+ _settings = [settings copy];
+ }
+}
+
+/**
+ * Ensures that the FirestoreClient is configured and returns it.
+ */
+- (FSTFirestoreClient *)client {
+ [self ensureClientConfigured];
+ return _client;
+}
+
+- (void)ensureClientConfigured {
+ @synchronized(self) {
+ if (!_client) {
+ // These values are validated elsewhere; this is just double-checking:
+ FSTAssert(_settings.host, @"FirestoreSettings.host cannot be nil.");
+ FSTAssert(_settings.dispatchQueue, @"FirestoreSettings.dispatchQueue cannot be nil.");
+
+ if (!_settings.timestampsInSnapshotsEnabled) {
+ FSTWarn(
+ @"The behavior for system Date objects stored in Firestore is going to change "
+ "AND YOUR APP MAY BREAK.\n"
+ "To hide this warning and ensure your app does not break, you need to add "
+ "the following code to your app before calling any other Cloud Firestore methods:\n"
+ "\n"
+ "let db = Firestore.firestore()\n"
+ "let settings = db.settings\n"
+ "settings.areTimestampsInSnapshotsEnabled = true\n"
+ "db.settings = settings\n"
+ "\n"
+ "With this change, timestamps stored in Cloud Firestore will be read back as "
+ "Firebase Timestamp objects instead of as system Date objects. So you will "
+ "also need to update code expecting a Date to instead expect a Timestamp. "
+ "For example:\n"
+ "\n"
+ "// old:\n"
+ "let date: Date = documentSnapshot.get(\"created_at\") as! Date\n"
+ "// new:\n"
+ "let timestamp: Timestamp = documentSnapshot.get(\"created_at\") as! Timestamp\n"
+ "let date: Date = timestamp.dateValue()\n"
+ "\n"
+ "Please audit all existing usages of Date when you enable the new behavior. In a "
+ "future release, the behavior will be changed to the new behavior, so if you do not "
+ "follow these steps, YOUR APP MAY BREAK.");
+ }
+
+ const DatabaseInfo database_info(*self.databaseID, util::MakeStringView(_persistenceKey),
+ util::MakeStringView(_settings.host), _settings.sslEnabled);
+
+ FSTDispatchQueue *userDispatchQueue = [FSTDispatchQueue queueWith:_settings.dispatchQueue];
+
+ _client = [FSTFirestoreClient clientWithDatabaseInfo:database_info
+ usePersistence:_settings.persistenceEnabled
+ credentialsProvider:_credentialsProvider.get()
+ userDispatchQueue:userDispatchQueue
+ workerDispatchQueue:_workerDispatchQueue];
+ }
+ }
+}
+
+- (FIRCollectionReference *)collectionWithPath:(NSString *)collectionPath {
+ if (!collectionPath) {
+ FSTThrowInvalidArgument(@"Collection path cannot be nil.");
+ }
+ [self ensureClientConfigured];
+ const ResourcePath path = ResourcePath::FromString(util::MakeStringView(collectionPath));
+ return [FIRCollectionReference referenceWithPath:path firestore:self];
+}
+
+- (FIRDocumentReference *)documentWithPath:(NSString *)documentPath {
+ if (!documentPath) {
+ FSTThrowInvalidArgument(@"Document path cannot be nil.");
+ }
+ [self ensureClientConfigured];
+ const ResourcePath path = ResourcePath::FromString(util::MakeStringView(documentPath));
+ return [FIRDocumentReference referenceWithPath:path firestore:self];
+}
+
+- (void)runTransactionWithBlock:(id _Nullable (^)(FIRTransaction *, NSError **))updateBlock
+ dispatchQueue:(dispatch_queue_t)queue
+ completion:
+ (void (^)(id _Nullable result, NSError *_Nullable error))completion {
+ // We wrap the function they provide in order to use internal implementation classes for
+ // FSTTransaction, and to run the user callback block on the proper queue.
+ if (!updateBlock) {
+ FSTThrowInvalidArgument(@"Transaction block cannot be nil.");
+ } else if (!completion) {
+ FSTThrowInvalidArgument(@"Transaction completion block cannot be nil.");
+ }
+
+ FSTTransactionBlock wrappedUpdate =
+ ^(FSTTransaction *internalTransaction,
+ void (^internalCompletion)(id _Nullable, NSError *_Nullable)) {
+ FIRTransaction *transaction =
+ [FIRTransaction transactionWithFSTTransaction:internalTransaction firestore:self];
+ dispatch_async(queue, ^{
+ NSError *_Nullable error = nil;
+ id _Nullable result = updateBlock(transaction, &error);
+ if (error) {
+ // Force the result to be nil in the case of an error, in case the user set both.
+ result = nil;
+ }
+ internalCompletion(result, error);
+ });
+ };
+ [self.client transactionWithRetries:5 updateBlock:wrappedUpdate completion:completion];
+}
+
+- (FIRWriteBatch *)batch {
+ [self ensureClientConfigured];
+
+ return [FIRWriteBatch writeBatchWithFirestore:self];
+}
+
+- (void)runTransactionWithBlock:(id _Nullable (^)(FIRTransaction *, NSError **error))updateBlock
+ completion:
+ (void (^)(id _Nullable result, NSError *_Nullable error))completion {
+ static dispatch_queue_t transactionDispatchQueue;
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ transactionDispatchQueue = dispatch_queue_create("com.google.firebase.firestore.transaction",
+ DISPATCH_QUEUE_CONCURRENT);
+ });
+ [self runTransactionWithBlock:updateBlock
+ dispatchQueue:transactionDispatchQueue
+ completion:completion];
+}
+
+- (void)shutdownWithCompletion:(nullable void (^)(NSError *_Nullable error))completion {
+ FSTFirestoreClient *client;
+ @synchronized(self) {
+ client = _client;
+ _client = nil;
+ }
+
+ if (!client) {
+ // We should be dispatching the callback on the user dispatch queue but if the client is nil
+ // here that queue was never created.
+ completion(nil);
+ } else {
+ [client shutdownWithCompletion:completion];
+ }
+}
+
++ (BOOL)isLoggingEnabled {
+ return FIRIsLoggableLevel(FIRLoggerLevelDebug, NO);
+}
+
++ (void)enableLogging:(BOOL)logging {
+ FIRSetLoggerLevel(logging ? FIRLoggerLevelDebug : FIRLoggerLevelNotice);
+}
+
+- (void)enableNetworkWithCompletion:(nullable void (^)(NSError *_Nullable error))completion {
+ [self ensureClientConfigured];
+ [self.client enableNetworkWithCompletion:completion];
+}
+
+- (void)disableNetworkWithCompletion:(nullable void (^)(NSError *_Nullable))completion {
+ [self ensureClientConfigured];
+ [self.client disableNetworkWithCompletion:completion];
+}
+
+- (const DatabaseId *)databaseID {
+ return &_databaseID;
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/API/FIRFirestoreSettings.m b/Firestore/Source/API/FIRFirestoreSettings.mm
index 9677ff6..8f998ec 100644
--- a/Firestore/Source/API/FIRFirestoreSettings.m
+++ b/Firestore/Source/API/FIRFirestoreSettings.mm
@@ -23,6 +23,8 @@ NS_ASSUME_NONNULL_BEGIN
static NSString *const kDefaultHost = @"firestore.googleapis.com";
static const BOOL kDefaultSSLEnabled = YES;
static const BOOL kDefaultPersistenceEnabled = YES;
+// TODO(b/73820332): flip the default.
+static const BOOL kDefaultTimestampsInSnapshotsEnabled = NO;
@implementation FIRFirestoreSettings
@@ -32,6 +34,7 @@ static const BOOL kDefaultPersistenceEnabled = YES;
_sslEnabled = kDefaultSSLEnabled;
_dispatchQueue = dispatch_get_main_queue();
_persistenceEnabled = kDefaultPersistenceEnabled;
+ _timestampsInSnapshotsEnabled = kDefaultTimestampsInSnapshotsEnabled;
}
return self;
}
@@ -47,7 +50,8 @@ static const BOOL kDefaultPersistenceEnabled = YES;
return [self.host isEqual:otherSettings.host] &&
self.isSSLEnabled == otherSettings.isSSLEnabled &&
self.dispatchQueue == otherSettings.dispatchQueue &&
- self.isPersistenceEnabled == otherSettings.isPersistenceEnabled;
+ self.isPersistenceEnabled == otherSettings.isPersistenceEnabled &&
+ self.timestampsInSnapshotsEnabled == otherSettings.timestampsInSnapshotsEnabled;
}
- (NSUInteger)hash {
@@ -55,6 +59,7 @@ static const BOOL kDefaultPersistenceEnabled = YES;
result = 31 * result + (self.isSSLEnabled ? 1231 : 1237);
// Ignore the dispatchQueue to avoid having to deal with sizeof(dispatch_queue_t).
result = 31 * result + (self.isPersistenceEnabled ? 1231 : 1237);
+ result = 31 * result + (self.timestampsInSnapshotsEnabled ? 1231 : 1237);
return result;
}
@@ -64,6 +69,7 @@ static const BOOL kDefaultPersistenceEnabled = YES;
copy.sslEnabled = _sslEnabled;
copy.dispatchQueue = _dispatchQueue;
copy.persistenceEnabled = _persistenceEnabled;
+ copy.timestampsInSnapshotsEnabled = _timestampsInSnapshotsEnabled;
return copy;
}
diff --git a/Firestore/Source/API/FIRFirestoreVersion.m b/Firestore/Source/API/FIRFirestoreVersion.mm
index 4f8bb28..8ebe814 100644
--- a/Firestore/Source/API/FIRFirestoreVersion.m
+++ b/Firestore/Source/API/FIRFirestoreVersion.mm
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+#import "Firestore/Source/API/FIRFirestoreVersion.h"
+
#ifndef FIRFirestore_VERSION
#error "FIRFirestore_VERSION is not defined: add -DFIRFirestore_VERSION=... to the build invocation"
#endif
@@ -25,5 +27,5 @@
#define STR(x) STR_EXPAND(x)
#define STR_EXPAND(x) #x
-const unsigned char *const FirebaseFirestoreVersionString =
+extern "C" const unsigned char *const FirebaseFirestoreVersionString =
(const unsigned char *const)STR(FIRFirestore_VERSION);
diff --git a/Firestore/Source/API/FIRGeoPoint.m b/Firestore/Source/API/FIRGeoPoint.mm
index 72e9e7d..8d89633 100644
--- a/Firestore/Source/API/FIRGeoPoint.m
+++ b/Firestore/Source/API/FIRGeoPoint.mm
@@ -16,9 +16,14 @@
#import "Firestore/Source/API/FIRGeoPoint+Internal.h"
-#import "Firestore/Source/Util/FSTComparison.h"
+#import "Firestore/core/src/firebase/firestore/util/comparison.h"
+
#import "Firestore/Source/Util/FSTUsageValidation.h"
+using firebase::firestore::util::DoubleBitwiseEquals;
+using firebase::firestore::util::DoubleBitwiseHash;
+using firebase::firestore::util::WrapCompare;
+
NS_ASSUME_NONNULL_BEGIN
@implementation FIRGeoPoint
@@ -45,11 +50,11 @@ NS_ASSUME_NONNULL_BEGIN
}
- (NSComparisonResult)compare:(FIRGeoPoint *)other {
- NSComparisonResult result = FSTCompareDoubles(self.latitude, other.latitude);
+ NSComparisonResult result = WrapCompare<double>(self.latitude, other.latitude);
if (result != NSOrderedSame) {
return result;
} else {
- return FSTCompareDoubles(self.longitude, other.longitude);
+ return WrapCompare<double>(self.longitude, other.longitude);
}
}
@@ -67,12 +72,12 @@ NS_ASSUME_NONNULL_BEGIN
return NO;
}
FIRGeoPoint *otherGeoPoint = (FIRGeoPoint *)other;
- return FSTDoubleBitwiseEquals(self.latitude, otherGeoPoint.latitude) &&
- FSTDoubleBitwiseEquals(self.longitude, otherGeoPoint.longitude);
+ return DoubleBitwiseEquals(self.latitude, otherGeoPoint.latitude) &&
+ DoubleBitwiseEquals(self.longitude, otherGeoPoint.longitude);
}
- (NSUInteger)hash {
- return 31 * FSTDoubleBitwiseHash(self.latitude) + FSTDoubleBitwiseHash(self.longitude);
+ return 31 * DoubleBitwiseHash(self.latitude) + DoubleBitwiseHash(self.longitude);
}
/** Implements NSCopying without actually copying because geopoints are immutable. */
diff --git a/Firestore/Source/API/FIRListenerRegistration.m b/Firestore/Source/API/FIRListenerRegistration.mm
index 9f4ddd5..9f4ddd5 100644
--- a/Firestore/Source/API/FIRListenerRegistration.m
+++ b/Firestore/Source/API/FIRListenerRegistration.mm
diff --git a/Firestore/Source/API/FIRQuery.m b/Firestore/Source/API/FIRQuery.mm
index 12e79c5..9cdc572 100644
--- a/Firestore/Source/API/FIRQuery.m
+++ b/Firestore/Source/API/FIRQuery.mm
@@ -31,13 +31,21 @@
#import "Firestore/Source/Core/FSTFirestoreClient.h"
#import "Firestore/Source/Core/FSTQuery.h"
#import "Firestore/Source/Model/FSTDocument.h"
-#import "Firestore/Source/Model/FSTDocumentKey.h"
#import "Firestore/Source/Model/FSTFieldValue.h"
-#import "Firestore/Source/Model/FSTPath.h"
#import "Firestore/Source/Util/FSTAssert.h"
#import "Firestore/Source/Util/FSTAsyncQueryListener.h"
#import "Firestore/Source/Util/FSTUsageValidation.h"
+#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+#include "Firestore/core/src/firebase/firestore/model/field_path.h"
+#include "Firestore/core/src/firebase/firestore/model/resource_path.h"
+#include "Firestore/core/src/firebase/firestore/util/string_apple.h"
+
+namespace util = firebase::firestore::util;
+using firebase::firestore::model::DocumentKey;
+using firebase::firestore::model::FieldPath;
+using firebase::firestore::model::ResourcePath;
+
NS_ASSUME_NONNULL_BEGIN
@interface FIRQueryListenOptions ()
@@ -107,7 +115,7 @@ NS_ASSUME_NONNULL_BEGIN
- (BOOL)isEqual:(nullable id)other {
if (other == self) return YES;
- if (!other || ![[other class] isEqual:[self class]]) return NO;
+ if (![[other class] isEqual:[self class]]) return NO;
return [self isEqualToQuery:other];
}
@@ -115,9 +123,8 @@ NS_ASSUME_NONNULL_BEGIN
- (BOOL)isEqualToQuery:(nullable FIRQuery *)query {
if (self == query) return YES;
if (query == nil) return NO;
- if (self.firestore != query.firestore && ![self.firestore isEqual:query.firestore]) return NO;
- if (self.query != query.query && ![self.query isEqual:query.query]) return NO;
- return YES;
+
+ return [self.firestore isEqual:query.firestore] && [self.query isEqual:query.query];
}
- (NSUInteger)hash {
@@ -256,6 +263,95 @@ addSnapshotListenerInternalWithOptions:(FSTListenOptions *)internalOptions
value:value];
}
+- (FIRQuery *)queryFilteredUsingComparisonPredicate:(NSPredicate *)predicate {
+ NSComparisonPredicate *comparison = (NSComparisonPredicate *)predicate;
+ if (comparison.comparisonPredicateModifier != NSDirectPredicateModifier) {
+ FSTThrowInvalidArgument(@"Invalid query. Predicate cannot have an aggregate modifier.");
+ }
+ NSString *path;
+ id value = nil;
+ if ([comparison.leftExpression expressionType] == NSKeyPathExpressionType &&
+ [comparison.rightExpression expressionType] == NSConstantValueExpressionType) {
+ path = comparison.leftExpression.keyPath;
+ value = comparison.rightExpression.constantValue;
+ switch (comparison.predicateOperatorType) {
+ case NSEqualToPredicateOperatorType:
+ return [self queryWhereField:path isEqualTo:value];
+ case NSLessThanPredicateOperatorType:
+ return [self queryWhereField:path isLessThan:value];
+ case NSLessThanOrEqualToPredicateOperatorType:
+ return [self queryWhereField:path isLessThanOrEqualTo:value];
+ case NSGreaterThanPredicateOperatorType:
+ return [self queryWhereField:path isGreaterThan:value];
+ case NSGreaterThanOrEqualToPredicateOperatorType:
+ return [self queryWhereField:path isGreaterThanOrEqualTo:value];
+ default:; // Fallback below to throw assertion.
+ }
+ } else if ([comparison.leftExpression expressionType] == NSConstantValueExpressionType &&
+ [comparison.rightExpression expressionType] == NSKeyPathExpressionType) {
+ path = comparison.rightExpression.keyPath;
+ value = comparison.leftExpression.constantValue;
+ switch (comparison.predicateOperatorType) {
+ case NSEqualToPredicateOperatorType:
+ return [self queryWhereField:path isEqualTo:value];
+ case NSLessThanPredicateOperatorType:
+ return [self queryWhereField:path isGreaterThan:value];
+ case NSLessThanOrEqualToPredicateOperatorType:
+ return [self queryWhereField:path isGreaterThanOrEqualTo:value];
+ case NSGreaterThanPredicateOperatorType:
+ return [self queryWhereField:path isLessThan:value];
+ case NSGreaterThanOrEqualToPredicateOperatorType:
+ return [self queryWhereField:path isLessThanOrEqualTo:value];
+ default:; // Fallback below to throw assertion.
+ }
+ } else {
+ FSTThrowInvalidArgument(
+ @"Invalid query. Predicate comparisons must include a key path and a constant.");
+ }
+ // Fallback cases of unsupported comparison operator.
+ switch (comparison.predicateOperatorType) {
+ case NSCustomSelectorPredicateOperatorType:
+ FSTThrowInvalidArgument(@"Invalid query. Custom predicate filters are not supported.");
+ break;
+ default:
+ FSTThrowInvalidArgument(@"Invalid query. Operator type %lu is not supported.",
+ (unsigned long)comparison.predicateOperatorType);
+ }
+}
+
+- (FIRQuery *)queryFilteredUsingCompoundPredicate:(NSPredicate *)predicate {
+ NSCompoundPredicate *compound = (NSCompoundPredicate *)predicate;
+ if (compound.compoundPredicateType != NSAndPredicateType || compound.subpredicates.count == 0) {
+ FSTThrowInvalidArgument(@"Invalid query. Only compound queries using AND are supported.");
+ }
+ FIRQuery *query = self;
+ for (NSPredicate *pred in compound.subpredicates) {
+ query = [query queryFilteredUsingPredicate:pred];
+ }
+ return query;
+}
+
+- (FIRQuery *)queryFilteredUsingPredicate:(NSPredicate *)predicate {
+ if ([predicate isKindOfClass:[NSComparisonPredicate class]]) {
+ return [self queryFilteredUsingComparisonPredicate:predicate];
+ } else if ([predicate isKindOfClass:[NSCompoundPredicate class]]) {
+ return [self queryFilteredUsingCompoundPredicate:predicate];
+ } else if ([predicate isKindOfClass:[[NSPredicate
+ predicateWithBlock:^BOOL(id obj, NSDictionary *bindings) {
+ return true;
+ }] class]]) {
+ FSTThrowInvalidArgument(
+ @"Invalid query. Block-based predicates are not "
+ "supported. Please use predicateWithFormat to "
+ "create predicates instead.");
+ } else {
+ FSTThrowInvalidArgument(
+ @"Invalid query. Expect comparison or compound of "
+ "comparison predicate. Please use "
+ "predicateWithFormat to create predicates.");
+ }
+}
+
- (FIRQuery *)queryOrderedByField:(NSString *)field {
return
[self queryOrderedByFieldPath:[FIRFieldPath pathWithDotSeparatedString:field] descending:NO];
@@ -356,10 +452,10 @@ addSnapshotListenerInternalWithOptions:(FSTListenOptions *)internalOptions
}
- (FIRQuery *)queryWithFilterOperator:(FSTRelationFilterOperator)filterOperator
- path:(FSTFieldPath *)fieldPath
+ path:(const FieldPath &)fieldPath
value:(id)value {
FSTFieldValue *fieldValue;
- if ([fieldPath isKeyFieldPath]) {
+ if (fieldPath.IsKeyFieldPath()) {
if ([value isKindOfClass:[NSString class]]) {
NSString *documentKey = (NSString *)value;
if ([documentKey containsString:@"/"]) {
@@ -372,9 +468,9 @@ addSnapshotListenerInternalWithOptions:(FSTListenOptions *)internalOptions
@"Invalid query. When querying by document ID you must provide "
"a valid document ID, but it was an empty string.");
}
- FSTResourcePath *path = [self.query.path pathByAppendingSegment:documentKey];
- fieldValue = [FSTReferenceValue referenceValue:[FSTDocumentKey keyWithPath:path]
- databaseID:self.firestore.databaseID];
+ ResourcePath path = self.query.path.Append([documentKey UTF8String]);
+ fieldValue =
+ [FSTReferenceValue referenceValue:DocumentKey{path} databaseID:self.firestore.databaseID];
} else if ([value isKindOfClass:[FIRDocumentReference class]]) {
FIRDocumentReference *ref = (FIRDocumentReference *)value;
fieldValue = [FSTReferenceValue referenceValue:ref.key databaseID:self.firestore.databaseID];
@@ -414,43 +510,44 @@ addSnapshotListenerInternalWithOptions:(FSTListenOptions *)internalOptions
- (void)validateNewRelationFilter:(FSTRelationFilter *)filter {
if ([filter isInequality]) {
- FSTFieldPath *existingField = [self.query inequalityFilterField];
- if (existingField && ![existingField isEqual:filter.field]) {
+ const FieldPath *existingField = [self.query inequalityFilterField];
+ if (existingField && *existingField != filter.field) {
FSTThrowInvalidUsage(
@"InvalidQueryException",
@"Invalid Query. All where filters with an inequality "
"(lessThan, lessThanOrEqual, greaterThan, or greaterThanOrEqual) must be on the same "
- "field. But you have inequality filters on '%@' and '%@'",
- existingField, filter.field);
+ "field. But you have inequality filters on '%s' and '%s'",
+ existingField->CanonicalString().c_str(), filter.field.CanonicalString().c_str());
}
- FSTFieldPath *firstOrderByField = [self.query firstSortOrderField];
+ const FieldPath *firstOrderByField = [self.query firstSortOrderField];
if (firstOrderByField) {
- [self validateOrderByField:firstOrderByField matchesInequalityField:filter.field];
+ [self validateOrderByField:*firstOrderByField matchesInequalityField:filter.field];
}
}
}
-- (void)validateNewOrderByPath:(FSTFieldPath *)fieldPath {
+- (void)validateNewOrderByPath:(const FieldPath &)fieldPath {
if (![self.query firstSortOrderField]) {
// This is the first order by. It must match any inequality.
- FSTFieldPath *inequalityField = [self.query inequalityFilterField];
+ const FieldPath *inequalityField = [self.query inequalityFilterField];
if (inequalityField) {
- [self validateOrderByField:fieldPath matchesInequalityField:inequalityField];
+ [self validateOrderByField:fieldPath matchesInequalityField:*inequalityField];
}
}
}
-- (void)validateOrderByField:(FSTFieldPath *)orderByField
- matchesInequalityField:(FSTFieldPath *)inequalityField {
- if (!([orderByField isEqual:inequalityField])) {
+- (void)validateOrderByField:(const FieldPath &)orderByField
+ matchesInequalityField:(const FieldPath &)inequalityField {
+ if (orderByField != inequalityField) {
FSTThrowInvalidUsage(
@"InvalidQueryException",
@"Invalid query. You have a where filter with an "
- "inequality (lessThan, lessThanOrEqual, greaterThan, or greaterThanOrEqual) on field '%@' "
- "and so you must also use '%@' as your first queryOrderedBy field, but your first "
- "queryOrderedBy is currently on field '%@' instead.",
- inequalityField, inequalityField, orderByField);
+ "inequality (lessThan, lessThanOrEqual, greaterThan, or greaterThanOrEqual) on field '%s' "
+ "and so you must also use '%s' as your first queryOrderedBy field, but your first "
+ "queryOrderedBy is currently on field '%s' instead.",
+ inequalityField.CanonicalString().c_str(), inequalityField.CanonicalString().c_str(),
+ orderByField.CanonicalString().c_str());
}
}
@@ -477,7 +574,7 @@ addSnapshotListenerInternalWithOptions:(FSTListenOptions *)internalOptions
// continues/ends exactly at the provided document. Without the key (by using the explicit sort
// orders), multiple documents could match the position, yielding duplicate results.
for (FSTSortOrder *sortOrder in self.query.sortOrders) {
- if ([sortOrder.field isEqual:[FSTFieldPath keyFieldPath]]) {
+ if (sortOrder.field == FieldPath::KeyFieldPath()) {
[components addObject:[FSTReferenceValue referenceValue:document.key
databaseID:self.firestore.databaseID]];
} else {
@@ -487,9 +584,9 @@ addSnapshotListenerInternalWithOptions:(FSTListenOptions *)internalOptions
} else {
FSTThrowInvalidUsage(@"InvalidQueryException",
@"Invalid query. You are trying to start or end a query using a "
- "document for which the field '%@' (used as the order by) "
+ "document for which the field '%s' (used as the order by) "
"does not exist.",
- sortOrder.field.canonicalString);
+ sortOrder.field.CanonicalString().c_str());
}
}
}
@@ -509,7 +606,7 @@ addSnapshotListenerInternalWithOptions:(FSTListenOptions *)internalOptions
NSMutableArray<FSTFieldValue *> *components = [NSMutableArray array];
[fieldValues enumerateObjectsUsingBlock:^(id rawValue, NSUInteger idx, BOOL *stop) {
FSTSortOrder *sortOrder = explicitSortOrders[idx];
- if ([sortOrder.field isEqual:[FSTFieldPath keyFieldPath]]) {
+ if (sortOrder.field == FieldPath::KeyFieldPath()) {
if (![rawValue isKindOfClass:[NSString class]]) {
FSTThrowInvalidUsage(@"InvalidQueryException",
@"Invalid query. Expected a string for the document ID.");
@@ -519,8 +616,7 @@ addSnapshotListenerInternalWithOptions:(FSTListenOptions *)internalOptions
FSTThrowInvalidUsage(@"InvalidQueryException",
@"Invalid query. Document ID '%@' contains a slash.", documentID);
}
- FSTDocumentKey *key =
- [FSTDocumentKey keyWithPath:[self.query.path pathByAppendingSegment:documentID]];
+ const DocumentKey key{self.query.path.Append([documentID UTF8String])};
[components
addObject:[FSTReferenceValue referenceValue:key databaseID:self.firestore.databaseID]];
} else {
diff --git a/Firestore/Source/API/FIRQuerySnapshot.m b/Firestore/Source/API/FIRQuerySnapshot.mm
index 6bc6761..abee84c 100644
--- a/Firestore/Source/API/FIRQuerySnapshot.m
+++ b/Firestore/Source/API/FIRQuerySnapshot.mm
@@ -16,6 +16,7 @@
#import "Firestore/Source/API/FIRQuerySnapshot+Internal.h"
+#import "FIRFirestore.h"
#import "FIRSnapshotMetadata.h"
#import "Firestore/Source/API/FIRDocumentChange+Internal.h"
#import "Firestore/Source/API/FIRDocumentSnapshot+Internal.h"
@@ -57,7 +58,7 @@ NS_ASSUME_NONNULL_BEGIN
@implementation FIRQuerySnapshot {
// Cached value of the documents property.
- NSArray<FIRDocumentSnapshot *> *_documents;
+ NSArray<FIRQueryDocumentSnapshot *> *_documents;
// Cached value of the documentChanges property.
NSArray<FIRDocumentChange *> *_documentChanges;
@@ -76,6 +77,31 @@ NS_ASSUME_NONNULL_BEGIN
return self;
}
+// NSObject Methods
+- (BOOL)isEqual:(nullable id)other {
+ if (other == self) return YES;
+ if (![[other class] isEqual:[self class]]) return NO;
+
+ return [self isEqualToSnapshot:other];
+}
+
+- (BOOL)isEqualToSnapshot:(nullable FIRQuerySnapshot *)snapshot {
+ if (self == snapshot) return YES;
+ if (snapshot == nil) return NO;
+
+ return [self.firestore isEqual:snapshot.firestore] &&
+ [self.originalQuery isEqual:snapshot.originalQuery] &&
+ [self.snapshot isEqual:snapshot.snapshot] && [self.metadata isEqual:snapshot.metadata];
+}
+
+- (NSUInteger)hash {
+ NSUInteger hash = [self.firestore hash];
+ hash = hash * 31u + [self.originalQuery hash];
+ hash = hash * 31u + [self.snapshot hash];
+ hash = hash * 31u + [self.metadata hash];
+ return hash;
+}
+
@dynamic empty;
- (FIRQuery *)query {
@@ -93,18 +119,18 @@ NS_ASSUME_NONNULL_BEGIN
return self.snapshot.documents.count;
}
-- (NSArray<FIRDocumentSnapshot *> *)documents {
+- (NSArray<FIRQueryDocumentSnapshot *> *)documents {
if (!_documents) {
FSTDocumentSet *documentSet = self.snapshot.documents;
FIRFirestore *firestore = self.firestore;
BOOL fromCache = self.metadata.fromCache;
- NSMutableArray<FIRDocumentSnapshot *> *result = [NSMutableArray array];
+ NSMutableArray<FIRQueryDocumentSnapshot *> *result = [NSMutableArray array];
for (FSTDocument *document in documentSet.documentEnumerator) {
- [result addObject:[FIRDocumentSnapshot snapshotWithFirestore:firestore
- documentKey:document.key
- document:document
- fromCache:fromCache]];
+ [result addObject:[FIRQueryDocumentSnapshot snapshotWithFirestore:firestore
+ documentKey:document.key
+ document:document
+ fromCache:fromCache]];
}
_documents = result;
diff --git a/Firestore/Source/API/FIRSetOptions.m b/Firestore/Source/API/FIRSetOptions.m
deleted file mode 100644
index 623deaa..0000000
--- a/Firestore/Source/API/FIRSetOptions.m
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright 2017 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 "Firestore/Source/API/FIRSetOptions+Internal.h"
-#import "Firestore/Source/Model/FSTMutation.h"
-
-NS_ASSUME_NONNULL_BEGIN
-
-@implementation FIRSetOptions
-
-- (instancetype)initWithMerge:(BOOL)merge {
- if (self = [super init]) {
- _merge = merge;
- }
- return self;
-}
-
-+ (instancetype)merge {
- return [[FIRSetOptions alloc] initWithMerge:YES];
-}
-
-- (BOOL)isEqual:(id)other {
- if (self == other) {
- return YES;
- } else if (![other isKindOfClass:[FIRSetOptions class]]) {
- return NO;
- }
-
- FIRSetOptions *otherOptions = (FIRSetOptions *)other;
-
- return otherOptions.merge != self.merge;
-}
-
-- (NSUInteger)hash {
- return self.merge ? 1231 : 1237;
-}
-@end
-
-@implementation FIRSetOptions (Internal)
-
-+ (instancetype)overwrite {
- static FIRSetOptions *overwriteInstance = nil;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- overwriteInstance = [[FIRSetOptions alloc] initWithMerge:NO];
- });
- return overwriteInstance;
-}
-
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/API/FIRSnapshotMetadata.m b/Firestore/Source/API/FIRSnapshotMetadata.mm
index 224015f..27747ce 100644
--- a/Firestore/Source/API/FIRSnapshotMetadata.m
+++ b/Firestore/Source/API/FIRSnapshotMetadata.mm
@@ -44,6 +44,27 @@ NS_ASSUME_NONNULL_BEGIN
return self;
}
+// NSObject Methods
+- (BOOL)isEqual:(nullable id)other {
+ if (other == self) return YES;
+ if (![[other class] isEqual:[self class]]) return NO;
+
+ return [self isEqualToMetadata:other];
+}
+
+- (BOOL)isEqualToMetadata:(nullable FIRSnapshotMetadata *)metadata {
+ if (self == metadata) return YES;
+ if (metadata == nil) return NO;
+
+ return self.pendingWrites == metadata.pendingWrites && self.fromCache == metadata.fromCache;
+}
+
+- (NSUInteger)hash {
+ NSUInteger hash = self.pendingWrites ? 1 : 0;
+ hash = hash * 31u + (self.fromCache ? 1 : 0);
+ return hash;
+}
+
@end
NS_ASSUME_NONNULL_END
diff --git a/Firestore/Example/Tests/Util/FSTTestDispatchQueue.h b/Firestore/Source/API/FIRSnapshotOptions+Internal.h
index 7ecbbaf..64e7dbc 100644
--- a/Firestore/Example/Tests/Util/FSTTestDispatchQueue.h
+++ b/Firestore/Source/API/FIRSnapshotOptions+Internal.h
@@ -14,25 +14,24 @@
* limitations under the License.
*/
-#import "Firestore/Source/Util/FSTDispatchQueue.h"
+#import "FIRDocumentSnapshot.h"
-@class XCTestExpectation;
+#import <Foundation/Foundation.h>
+
+#import "Firestore/Source/Model/FSTFieldValue.h"
NS_ASSUME_NONNULL_BEGIN
-/**
- * Dispatch queue used in the integration tests that caps delayed executions at 1.0 seconds.
- */
-@interface FSTTestDispatchQueue : FSTDispatchQueue
+@interface FIRSnapshotOptions (Internal)
-/** Creates and returns an FSTTestDispatchQueue wrapping the specified dispatch_queue_t. */
-+ (instancetype)queueWith:(dispatch_queue_t)dispatchQueue;
+/** Returns a default instance of FIRSnapshotOptions that specifies no options. */
++ (instancetype)defaultOptions;
-/**
- * Registers a test expectation that is fulfilled when the next delayed callback finished
- * executing.
- */
-- (void)fulfillOnExecution:(XCTestExpectation *)expectation;
+/* Initializes a new instance with the specified server timestamp behavior. */
+- (instancetype)initWithServerTimestampBehavior:(FSTServerTimestampBehavior)serverTimestampBehavior;
+
+/* Returns the server timestamp behavior. Returns -1 if no behavior is specified. */
+- (FSTServerTimestampBehavior)serverTimestampBehavior;
@end
diff --git a/Firestore/Source/API/FIRSnapshotOptions.mm b/Firestore/Source/API/FIRSnapshotOptions.mm
new file mode 100644
index 0000000..72ea3cc
--- /dev/null
+++ b/Firestore/Source/API/FIRSnapshotOptions.mm
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2017 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 "FIRDocumentSnapshot.h"
+
+#import "Firestore/Source/API/FIRSnapshotOptions+Internal.h"
+#import "Firestore/Source/Util/FSTAssert.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface FIRSnapshotOptions ()
+
+@property(nonatomic) FSTServerTimestampBehavior serverTimestampBehavior;
+
+@end
+
+@implementation FIRSnapshotOptions
+
+- (instancetype)initWithServerTimestampBehavior:
+ (FSTServerTimestampBehavior)serverTimestampBehavior {
+ self = [super init];
+
+ if (self) {
+ _serverTimestampBehavior = serverTimestampBehavior;
+ }
+
+ return self;
+}
+
++ (instancetype)defaultOptions {
+ static FIRSnapshotOptions *sharedInstance = nil;
+ static dispatch_once_t onceToken;
+
+ dispatch_once(&onceToken, ^{
+ sharedInstance =
+ [[FIRSnapshotOptions alloc] initWithServerTimestampBehavior:FSTServerTimestampBehaviorNone];
+ });
+
+ return sharedInstance;
+}
+
++ (instancetype)serverTimestampBehavior:(FIRServerTimestampBehavior)serverTimestampBehavior {
+ switch (serverTimestampBehavior) {
+ case FIRServerTimestampBehaviorEstimate:
+ return [[FIRSnapshotOptions alloc]
+ initWithServerTimestampBehavior:FSTServerTimestampBehaviorEstimate];
+ case FIRServerTimestampBehaviorPrevious:
+ return [[FIRSnapshotOptions alloc]
+ initWithServerTimestampBehavior:FSTServerTimestampBehaviorPrevious];
+ case FIRServerTimestampBehaviorNone:
+ return [FIRSnapshotOptions defaultOptions];
+ default:
+ FSTFail(@"Encountered unknown server timestamp behavior: %d", (int)serverTimestampBehavior);
+ }
+}
+
+@end
+
+NS_ASSUME_NONNULL_END \ No newline at end of file
diff --git a/Firestore/Source/API/FIRTimestamp+Internal.h b/Firestore/Source/API/FIRTimestamp+Internal.h
new file mode 100644
index 0000000..48e38b2
--- /dev/null
+++ b/Firestore/Source/API/FIRTimestamp+Internal.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2018 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 "FIRTimestamp.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+/** Internal FIRTimestamp API we don't want exposed in our public header files. */
+@interface FIRTimestamp (Internal)
+
+/**
+ * Converts the given date to an ISO 8601 timestamp string, useful for rendering in JSON.
+ *
+ * ISO 8601 dates times in UTC look like this: "1912-04-14T23:40:00.000000000Z".
+ *
+ * @see http://www.ecma-international.org/ecma-262/6.0/#sec-date-time-string-format
+ */
+- (NSString *)ISO8601String;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/Core/FSTTimestamp.m b/Firestore/Source/API/FIRTimestamp.m
index 6d9e314..e5a2cd3 100644
--- a/Firestore/Source/Core/FSTTimestamp.m
+++ b/Firestore/Source/API/FIRTimestamp.m
@@ -14,23 +14,36 @@
* limitations under the License.
*/
-#import "Firestore/Source/Core/FSTTimestamp.h"
-
-#import "Firestore/Source/Util/FSTAssert.h"
-#import "Firestore/Source/Util/FSTComparison.h"
+#import "Firestore/Source/API/FIRTimestamp+Internal.h"
NS_ASSUME_NONNULL_BEGIN
static const int kNanosPerSecond = 1000000000;
-@implementation FSTTimestamp
+@implementation FIRTimestamp (Internal)
-#pragma mark - Constructors
+#pragma mark - Internal public methods
-+ (instancetype)timestamp {
- return [FSTTimestamp timestampWithDate:[NSDate date]];
+- (NSString *)ISO8601String {
+ NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
+ formatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss";
+ formatter.timeZone = [NSTimeZone timeZoneWithName:@"UTC"];
+ NSDate *secondsDate = [NSDate dateWithTimeIntervalSince1970:self.seconds];
+ NSString *secondsString = [formatter stringFromDate:secondsDate];
+ if (secondsString.length != 19) {
+ [NSException raise:@"Invalid ISO string" format:@"Invalid ISO string: %@", secondsString];
+ }
+
+ NSString *nanosString = [NSString stringWithFormat:@"%09d", self.nanoseconds];
+ return [NSString stringWithFormat:@"%@.%@Z", secondsString, nanosString];
}
+@end
+
+@implementation FIRTimestamp
+
+#pragma mark - Constructors
+
+ (instancetype)timestampWithDate:(NSDate *)date {
double secondsDouble;
double fraction = modf(date.timeIntervalSince1970, &secondsDouble);
@@ -41,21 +54,41 @@ static const int kNanosPerSecond = 1000000000;
}
int64_t seconds = (int64_t)secondsDouble;
int32_t nanos = (int32_t)(fraction * kNanosPerSecond);
- return [[FSTTimestamp alloc] initWithSeconds:seconds nanos:nanos];
+ return [[FIRTimestamp alloc] initWithSeconds:seconds nanoseconds:nanos];
+}
+
++ (instancetype)timestampWithSeconds:(int64_t)seconds nanoseconds:(int32_t)nanoseconds {
+ return [[FIRTimestamp alloc] initWithSeconds:seconds nanoseconds:nanoseconds];
}
-- (instancetype)initWithSeconds:(int64_t)seconds nanos:(int32_t)nanos {
++ (instancetype)timestamp {
+ return [FIRTimestamp timestampWithDate:[NSDate date]];
+}
+
+- (instancetype)initWithSeconds:(int64_t)seconds nanoseconds:(int32_t)nanoseconds {
self = [super init];
if (self) {
- FSTAssert(nanos >= 0, @"timestamp nanoseconds out of range: %d", nanos);
- FSTAssert(nanos < 1e9, @"timestamp nanoseconds out of range: %d", nanos);
- // Midnight at the beginning of 1/1/1 is the earliest timestamp Firestore supports.
- FSTAssert(seconds >= -62135596800L, @"timestamp seconds out of range: %lld", seconds);
+ if (nanoseconds < 0) {
+ [NSException raise:@"Invalid timestamp"
+ format:@"Timestamp nanoseconds out of range: %d", nanoseconds];
+ }
+ if (nanoseconds >= 1e9) {
+ [NSException raise:@"Invalid timestamp"
+ format:@"Timestamp nanoseconds out of range: %d", nanoseconds];
+ }
+ // Midnight at the beginning of 1/1/1 is the earliest timestamp supported.
+ if (seconds < -62135596800L) {
+ [NSException raise:@"Invalid timestamp"
+ format:@"Timestamp seconds out of range: %lld", seconds];
+ }
// This will break in the year 10,000.
- FSTAssert(seconds < 253402300800L, @"timestamp seconds out of range: %lld", seconds);
+ if (seconds >= 253402300800L) {
+ [NSException raise:@"Invalid timestamp"
+ format:@"Timestamp seconds out of range: %lld", seconds];
+ }
_seconds = seconds;
- _nanos = nanos;
+ _nanoseconds = nanoseconds;
}
return self;
}
@@ -66,19 +99,19 @@ static const int kNanosPerSecond = 1000000000;
if (self == object) {
return YES;
}
- if (![object isKindOfClass:[FSTTimestamp class]]) {
+ if (![object isKindOfClass:[FIRTimestamp class]]) {
return NO;
}
- return [self isEqualToTimestamp:(FSTTimestamp *)object];
+ return [self isEqualToTimestamp:(FIRTimestamp *)object];
}
- (NSUInteger)hash {
- return (NSUInteger)((self.seconds >> 32) ^ self.seconds ^ self.nanos);
+ return (NSUInteger)((self.seconds >> 32) ^ self.seconds ^ self.nanoseconds);
}
- (NSString *)description {
- return [NSString
- stringWithFormat:@"<FSTTimestamp: seconds=%lld nanos=%d>", self.seconds, self.nanos];
+ return [NSString stringWithFormat:@"FIRTimestamp: seconds=%lld nanoseconds=%d>", self.seconds,
+ self.nanoseconds];
}
/** Implements NSCopying without actually copying because timestamps are immutable. */
@@ -88,33 +121,30 @@ static const int kNanosPerSecond = 1000000000;
#pragma mark - Public methods
-- (NSDate *)approximateDateValue {
- NSTimeInterval interval = (NSTimeInterval)self.seconds + ((NSTimeInterval)self.nanos) / 1e9;
+- (NSDate *)dateValue {
+ NSTimeInterval interval = (NSTimeInterval)self.seconds + ((NSTimeInterval)self.nanoseconds) / 1e9;
return [NSDate dateWithTimeIntervalSince1970:interval];
}
-- (BOOL)isEqualToTimestamp:(FSTTimestamp *)other {
- return [self compare:other] == NSOrderedSame;
-}
-
-- (NSString *)ISO8601String {
- NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
- formatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss";
- formatter.timeZone = [NSTimeZone timeZoneWithName:@"UTC"];
- NSDate *secondsDate = [NSDate dateWithTimeIntervalSince1970:self.seconds];
- NSString *secondsString = [formatter stringFromDate:secondsDate];
- FSTAssert(secondsString.length == 19, @"Invalid ISO string: %@", secondsString);
+- (NSComparisonResult)compare:(FIRTimestamp *)other {
+ if (self.seconds < other.seconds) {
+ return NSOrderedAscending;
+ } else if (self.seconds > other.seconds) {
+ return NSOrderedDescending;
+ }
- NSString *nanosString = [NSString stringWithFormat:@"%09d", self.nanos];
- return [NSString stringWithFormat:@"%@.%@Z", secondsString, nanosString];
+ if (self.nanoseconds < other.nanoseconds) {
+ return NSOrderedAscending;
+ } else if (self.nanoseconds > other.nanoseconds) {
+ return NSOrderedDescending;
+ }
+ return NSOrderedSame;
}
-- (NSComparisonResult)compare:(FSTTimestamp *)other {
- NSComparisonResult result = FSTCompareInt64s(self.seconds, other.seconds);
- if (result != NSOrderedSame) {
- return result;
- }
- return FSTCompareInt32s(self.nanos, other.nanos);
+#pragma mark - Private methods
+
+- (BOOL)isEqualToTimestamp:(FIRTimestamp *)other {
+ return [self compare:other] == NSOrderedSame;
}
@end
diff --git a/Firestore/Source/API/FIRTransaction.m b/Firestore/Source/API/FIRTransaction.mm
index 5edff19..668a359 100644
--- a/Firestore/Source/API/FIRTransaction.m
+++ b/Firestore/Source/API/FIRTransaction.mm
@@ -19,7 +19,6 @@
#import "Firestore/Source/API/FIRDocumentReference+Internal.h"
#import "Firestore/Source/API/FIRDocumentSnapshot+Internal.h"
#import "Firestore/Source/API/FIRFirestore+Internal.h"
-#import "Firestore/Source/API/FIRSetOptions+Internal.h"
#import "Firestore/Source/API/FSTUserDataConverter.h"
#import "Firestore/Source/Core/FSTTransaction.h"
#import "Firestore/Source/Model/FSTDocument.h"
@@ -62,15 +61,15 @@ NS_ASSUME_NONNULL_BEGIN
- (FIRTransaction *)setData:(NSDictionary<NSString *, id> *)data
forDocument:(FIRDocumentReference *)document {
- return [self setData:data forDocument:document options:[FIRSetOptions overwrite]];
+ return [self setData:data forDocument:document merge:NO];
}
- (FIRTransaction *)setData:(NSDictionary<NSString *, id> *)data
forDocument:(FIRDocumentReference *)document
- options:(FIRSetOptions *)options {
+ merge:(BOOL)merge {
[self validateReference:document];
- FSTParsedSetData *parsed = options.isMerge ? [self.firestore.dataConverter parsedMergeData:data]
- : [self.firestore.dataConverter parsedSetData:data];
+ FSTParsedSetData *parsed = merge ? [self.firestore.dataConverter parsedMergeData:data]
+ : [self.firestore.dataConverter parsedSetData:data];
[self.internalTransaction setData:parsed forDocument:document.key];
return self;
}
@@ -94,7 +93,7 @@ NS_ASSUME_NONNULL_BEGIN
NSError *_Nullable error))completion {
[self validateReference:document];
[self.internalTransaction
- lookupDocumentsForKeys:@[ document.key ]
+ lookupDocumentsForKeys:{document.key}
completion:^(NSArray<FSTMaybeDocument *> *_Nullable documents,
NSError *_Nullable error) {
if (error) {
diff --git a/Firestore/Source/API/FIRWriteBatch.m b/Firestore/Source/API/FIRWriteBatch.mm
index b918a9a..1185dae 100644
--- a/Firestore/Source/API/FIRWriteBatch.m
+++ b/Firestore/Source/API/FIRWriteBatch.mm
@@ -18,12 +18,15 @@
#import "Firestore/Source/API/FIRDocumentReference+Internal.h"
#import "Firestore/Source/API/FIRFirestore+Internal.h"
-#import "Firestore/Source/API/FIRSetOptions+Internal.h"
#import "Firestore/Source/API/FSTUserDataConverter.h"
#import "Firestore/Source/Core/FSTFirestoreClient.h"
#import "Firestore/Source/Model/FSTMutation.h"
#import "Firestore/Source/Util/FSTUsageValidation.h"
+#include "Firestore/core/src/firebase/firestore/model/precondition.h"
+
+using firebase::firestore::model::Precondition;
+
NS_ASSUME_NONNULL_BEGIN
#pragma mark - FIRWriteBatch
@@ -59,18 +62,18 @@ NS_ASSUME_NONNULL_BEGIN
- (FIRWriteBatch *)setData:(NSDictionary<NSString *, id> *)data
forDocument:(FIRDocumentReference *)document {
- return [self setData:data forDocument:document options:[FIRSetOptions overwrite]];
+ return [self setData:data forDocument:document merge:NO];
}
- (FIRWriteBatch *)setData:(NSDictionary<NSString *, id> *)data
forDocument:(FIRDocumentReference *)document
- options:(FIRSetOptions *)options {
+ merge:(BOOL)merge {
[self verifyNotCommitted];
[self validateReference:document];
- FSTParsedSetData *parsed = options.isMerge ? [self.firestore.dataConverter parsedMergeData:data]
- : [self.firestore.dataConverter parsedSetData:data];
- [self.mutations addObjectsFromArray:[parsed mutationsWithKey:document.key
- precondition:[FSTPrecondition none]]];
+ FSTParsedSetData *parsed = merge ? [self.firestore.dataConverter parsedMergeData:data]
+ : [self.firestore.dataConverter parsedSetData:data];
+ [self.mutations
+ addObjectsFromArray:[parsed mutationsWithKey:document.key precondition:Precondition::None()]];
return self;
}
@@ -79,9 +82,8 @@ NS_ASSUME_NONNULL_BEGIN
[self verifyNotCommitted];
[self validateReference:document];
FSTParsedUpdateData *parsed = [self.firestore.dataConverter parsedUpdateData:fields];
- [self.mutations
- addObjectsFromArray:[parsed mutationsWithKey:document.key
- precondition:[FSTPrecondition preconditionWithExists:YES]]];
+ [self.mutations addObjectsFromArray:[parsed mutationsWithKey:document.key
+ precondition:Precondition::Exists(true)]];
return self;
}
@@ -89,11 +91,15 @@ NS_ASSUME_NONNULL_BEGIN
[self verifyNotCommitted];
[self validateReference:document];
[self.mutations addObject:[[FSTDeleteMutation alloc] initWithKey:document.key
- precondition:[FSTPrecondition none]]];
+ precondition:Precondition::None()]];
return self;
}
-- (void)commitWithCompletion:(void (^)(NSError *_Nullable error))completion {
+- (void)commit {
+ [self commitWithCompletion:nil];
+}
+
+- (void)commitWithCompletion:(nullable void (^)(NSError *_Nullable error))completion {
[self verifyNotCommitted];
self.committed = TRUE;
[self.firestore.client writeMutations:self.mutations completion:completion];
diff --git a/Firestore/Source/API/FSTUserDataConverter.h b/Firestore/Source/API/FSTUserDataConverter.h
index 2c52ad8..98a65ae 100644
--- a/Firestore/Source/API/FSTUserDataConverter.h
+++ b/Firestore/Source/API/FSTUserDataConverter.h
@@ -16,15 +16,17 @@
#import <Foundation/Foundation.h>
-@class FIRSetOptions;
-@class FSTDatabaseID;
-@class FSTDocumentKey;
+#include <vector>
+
+#include "Firestore/core/src/firebase/firestore/model/database_id.h"
+#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+#include "Firestore/core/src/firebase/firestore/model/field_mask.h"
+#include "Firestore/core/src/firebase/firestore/model/field_transform.h"
+#include "Firestore/core/src/firebase/firestore/model/precondition.h"
+
@class FSTObjectValue;
-@class FSTFieldMask;
@class FSTFieldValue;
-@class FSTFieldTransform;
@class FSTMutation;
-@class FSTPrecondition;
@class FSTSnapshotVersion;
NS_ASSUME_NONNULL_BEGIN
@@ -35,20 +37,28 @@ NS_ASSUME_NONNULL_BEGIN
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithData:(FSTObjectValue *)data
- fieldMask:(nullable FSTFieldMask *)fieldMask
- fieldTransforms:(NSArray<FSTFieldTransform *> *)fieldTransforms
+ fieldTransforms:
+ (std::vector<firebase::firestore::model::FieldTransform>)fieldTransforms
+ NS_DESIGNATED_INITIALIZER;
+
+- (instancetype)initWithData:(FSTObjectValue *)data
+ fieldMask:(firebase::firestore::model::FieldMask)fieldMask
+ fieldTransforms:
+ (std::vector<firebase::firestore::model::FieldTransform>)fieldTransforms
NS_DESIGNATED_INITIALIZER;
+- (const std::vector<firebase::firestore::model::FieldTransform> &)fieldTransforms;
+
@property(nonatomic, strong, readonly) FSTObjectValue *data;
-@property(nonatomic, strong, readonly, nullable) FSTFieldMask *fieldMask;
-@property(nonatomic, strong, readonly) NSArray<FSTFieldTransform *> *fieldTransforms;
+@property(nonatomic, assign, readonly) BOOL isPatch;
/**
* Converts the parsed document data into 1 or 2 mutations (depending on whether there are any
* field transforms) using the specified document key and precondition.
*/
-- (NSArray<FSTMutation *> *)mutationsWithKey:(FSTDocumentKey *)key
- precondition:(FSTPrecondition *)precondition;
+- (NSArray<FSTMutation *> *)mutationsWithKey:(const firebase::firestore::model::DocumentKey &)key
+ precondition:
+ (const firebase::firestore::model::Precondition &)precondition;
@end
@@ -58,20 +68,23 @@ NS_ASSUME_NONNULL_BEGIN
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithData:(FSTObjectValue *)data
- fieldMask:(FSTFieldMask *)fieldMask
- fieldTransforms:(NSArray<FSTFieldTransform *> *)fieldTransforms
+ fieldMask:(firebase::firestore::model::FieldMask)fieldMask
+ fieldTransforms:
+ (std::vector<firebase::firestore::model::FieldTransform>)fieldTransforms
NS_DESIGNATED_INITIALIZER;
+- (const firebase::firestore::model::FieldMask &)fieldMask;
+- (const std::vector<firebase::firestore::model::FieldTransform> &)fieldTransforms;
+
@property(nonatomic, strong, readonly) FSTObjectValue *data;
-@property(nonatomic, strong, readonly) FSTFieldMask *fieldMask;
-@property(nonatomic, strong, readonly) NSArray<FSTFieldTransform *> *fieldTransforms;
/**
* Converts the parsed update data into 1 or 2 mutations (depending on whether there are any
* field transforms) using the specified document key and precondition.
*/
-- (NSArray<FSTMutation *> *)mutationsWithKey:(FSTDocumentKey *)key
- precondition:(FSTPrecondition *)precondition;
+- (NSArray<FSTMutation *> *)mutationsWithKey:(const firebase::firestore::model::DocumentKey &)key
+ precondition:
+ (const firebase::firestore::model::Precondition &)precondition;
@end
@@ -80,17 +93,20 @@ NS_ASSUME_NONNULL_BEGIN
* This is necessary because keys assume a database from context (usually the current one).
* FSTDocumentKeyReference binds a key to a specific databaseID.
*
- * TODO(b/64160088): Make FSTDocumentKey aware of the specific databaseID it is tied to.
+ * TODO(b/64160088): Make DocumentKey aware of the specific databaseID it is tied to.
*/
@interface FSTDocumentKeyReference : NSObject
- (instancetype)init NS_UNAVAILABLE;
-- (instancetype)initWithKey:(FSTDocumentKey *)key
- databaseID:(FSTDatabaseID *)databaseID NS_DESIGNATED_INITIALIZER;
+- (instancetype)initWithKey:(firebase::firestore::model::DocumentKey)key
+ databaseID:(const firebase::firestore::model::DatabaseId *)databaseID
+ NS_DESIGNATED_INITIALIZER;
+
+- (const firebase::firestore::model::DocumentKey &)key;
-@property(nonatomic, strong, readonly) FSTDocumentKey *key;
-@property(nonatomic, strong, readonly) FSTDatabaseID *databaseID;
+// Does not own the DatabaseId instance.
+@property(nonatomic, assign, readonly) const firebase::firestore::model::DatabaseId *databaseID;
@end
@@ -107,13 +123,13 @@ typedef id _Nullable (^FSTPreConverterBlock)(id _Nullable);
@interface FSTUserDataConverter : NSObject
- (instancetype)init NS_UNAVAILABLE;
-- (instancetype)initWithDatabaseID:(FSTDatabaseID *)databaseID
+- (instancetype)initWithDatabaseID:(const firebase::firestore::model::DatabaseId *)databaseID
preConverter:(FSTPreConverterBlock)preConverter NS_DESIGNATED_INITIALIZER;
/** Parse document data from a non-merge setData call.*/
- (FSTParsedSetData *)parsedSetData:(id)input;
-/** Parse document data from a setData call with '[FIRSetOptions merge]'. */
+/** Parse document data from a setData call with `merge:YES`. */
- (FSTParsedSetData *)parsedMergeData:(id)input;
/** Parse update data from an updateData call. */
diff --git a/Firestore/Source/API/FSTUserDataConverter.m b/Firestore/Source/API/FSTUserDataConverter.mm
index 414aadb..719f6f5 100644
--- a/Firestore/Source/API/FSTUserDataConverter.m
+++ b/Firestore/Source/API/FSTUserDataConverter.mm
@@ -16,46 +16,88 @@
#import "Firestore/Source/API/FSTUserDataConverter.h"
+#include <memory>
+#include <utility>
+#include <vector>
+
#import "FIRGeoPoint.h"
+#import "FIRTimestamp.h"
+
#import "Firestore/Source/API/FIRDocumentReference+Internal.h"
#import "Firestore/Source/API/FIRFieldPath+Internal.h"
#import "Firestore/Source/API/FIRFieldValue+Internal.h"
#import "Firestore/Source/API/FIRFirestore+Internal.h"
-#import "Firestore/Source/API/FIRSetOptions+Internal.h"
-#import "Firestore/Source/Core/FSTTimestamp.h"
-#import "Firestore/Source/Model/FSTDatabaseID.h"
-#import "Firestore/Source/Model/FSTDocumentKey.h"
#import "Firestore/Source/Model/FSTFieldValue.h"
#import "Firestore/Source/Model/FSTMutation.h"
-#import "Firestore/Source/Model/FSTPath.h"
#import "Firestore/Source/Util/FSTAssert.h"
#import "Firestore/Source/Util/FSTUsageValidation.h"
+#include "Firestore/core/src/firebase/firestore/model/database_id.h"
+#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+#include "Firestore/core/src/firebase/firestore/model/field_mask.h"
+#include "Firestore/core/src/firebase/firestore/model/field_path.h"
+#include "Firestore/core/src/firebase/firestore/model/field_transform.h"
+#include "Firestore/core/src/firebase/firestore/model/precondition.h"
+#include "Firestore/core/src/firebase/firestore/model/transform_operations.h"
+#include "Firestore/core/src/firebase/firestore/util/string_apple.h"
+#include "absl/memory/memory.h"
+
+namespace util = firebase::firestore::util;
+using firebase::firestore::model::ArrayTransform;
+using firebase::firestore::model::DatabaseId;
+using firebase::firestore::model::DocumentKey;
+using firebase::firestore::model::FieldMask;
+using firebase::firestore::model::FieldPath;
+using firebase::firestore::model::FieldTransform;
+using firebase::firestore::model::Precondition;
+using firebase::firestore::model::ServerTimestampTransform;
+using firebase::firestore::model::TransformOperation;
+
NS_ASSUME_NONNULL_BEGIN
static NSString *const RESERVED_FIELD_DESIGNATOR = @"__";
#pragma mark - FSTParsedSetData
-@implementation FSTParsedSetData
+@implementation FSTParsedSetData {
+ FieldMask _fieldMask;
+ std::vector<FieldTransform> _fieldTransforms;
+}
+
+- (instancetype)initWithData:(FSTObjectValue *)data
+ fieldTransforms:(std::vector<FieldTransform>)fieldTransforms {
+ self = [super init];
+ if (self) {
+ _data = data;
+ _fieldTransforms = std::move(fieldTransforms);
+ _isPatch = NO;
+ }
+ return self;
+}
+
- (instancetype)initWithData:(FSTObjectValue *)data
- fieldMask:(nullable FSTFieldMask *)fieldMask
- fieldTransforms:(NSArray<FSTFieldTransform *> *)fieldTransforms {
+ fieldMask:(FieldMask)fieldMask
+ fieldTransforms:(std::vector<FieldTransform>)fieldTransforms {
self = [super init];
if (self) {
_data = data;
- _fieldMask = fieldMask;
- _fieldTransforms = fieldTransforms;
+ _fieldMask = std::move(fieldMask);
+ _fieldTransforms = std::move(fieldTransforms);
+ _isPatch = YES;
}
return self;
}
-- (NSArray<FSTMutation *> *)mutationsWithKey:(FSTDocumentKey *)key
- precondition:(FSTPrecondition *)precondition {
+- (const std::vector<FieldTransform> &)fieldTransforms {
+ return _fieldTransforms;
+}
+
+- (NSArray<FSTMutation *> *)mutationsWithKey:(const DocumentKey &)key
+ precondition:(const Precondition &)precondition {
NSMutableArray<FSTMutation *> *mutations = [NSMutableArray array];
- if (self.fieldMask) {
+ if (self.isPatch) {
[mutations addObject:[[FSTPatchMutation alloc] initWithKey:key
- fieldMask:self.fieldMask
+ fieldMask:_fieldMask
value:self.data
precondition:precondition]];
} else {
@@ -63,7 +105,7 @@ static NSString *const RESERVED_FIELD_DESIGNATOR = @"__";
value:self.data
precondition:precondition]];
}
- if (self.fieldTransforms.count > 0) {
+ if (!self.fieldTransforms.empty()) {
[mutations addObject:[[FSTTransformMutation alloc] initWithKey:key
fieldTransforms:self.fieldTransforms]];
}
@@ -74,33 +116,45 @@ static NSString *const RESERVED_FIELD_DESIGNATOR = @"__";
#pragma mark - FSTParsedUpdateData
-@implementation FSTParsedUpdateData
+@implementation FSTParsedUpdateData {
+ FieldMask _fieldMask;
+ std::vector<FieldTransform> _fieldTransforms;
+}
+
- (instancetype)initWithData:(FSTObjectValue *)data
- fieldMask:(FSTFieldMask *)fieldMask
- fieldTransforms:(NSArray<FSTFieldTransform *> *)fieldTransforms {
+ fieldMask:(FieldMask)fieldMask
+ fieldTransforms:(std::vector<FieldTransform>)fieldTransforms {
self = [super init];
if (self) {
_data = data;
- _fieldMask = fieldMask;
- _fieldTransforms = fieldTransforms;
+ _fieldMask = std::move(fieldMask);
+ _fieldTransforms = std::move(fieldTransforms);
}
return self;
}
-- (NSArray<FSTMutation *> *)mutationsWithKey:(FSTDocumentKey *)key
- precondition:(FSTPrecondition *)precondition {
+- (NSArray<FSTMutation *> *)mutationsWithKey:(const DocumentKey &)key
+ precondition:(const Precondition &)precondition {
NSMutableArray<FSTMutation *> *mutations = [NSMutableArray array];
[mutations addObject:[[FSTPatchMutation alloc] initWithKey:key
fieldMask:self.fieldMask
value:self.data
precondition:precondition]];
- if (self.fieldTransforms.count > 0) {
+ if (!self.fieldTransforms.empty()) {
[mutations addObject:[[FSTTransformMutation alloc] initWithKey:key
fieldTransforms:self.fieldTransforms]];
}
return mutations;
}
+- (const firebase::firestore::model::FieldMask &)fieldMask {
+ return _fieldMask;
+}
+
+- (const std::vector<FieldTransform> &)fieldTransforms {
+ return _fieldTransforms;
+}
+
@end
/**
@@ -111,7 +165,11 @@ typedef NS_ENUM(NSInteger, FSTUserDataSource) {
FSTUserDataSourceSet,
FSTUserDataSourceMergeSet,
FSTUserDataSourceUpdate,
- FSTUserDataSourceQueryValue, // from a where clause or cursor bound.
+ /**
+ * Indicates the source is a where clause, cursor bound, arrayUnion() element, etc. In particular,
+ * this will result in [FSTParseContext isWrite] returning NO.
+ */
+ FSTUserDataSourceArgument,
};
#pragma mark - FSTParseContext
@@ -120,9 +178,6 @@ typedef NS_ENUM(NSInteger, FSTUserDataSource) {
* A "context" object passed around while parsing user data.
*/
@interface FSTParseContext : NSObject
-/** The current path being parsed. */
-// TODO(b/34871131): path should never be nil, but we don't support array paths right now.
-@property(nonatomic, strong, readonly, nullable) FSTFieldPath *path;
/** Whether or not this context corresponds to an element of an array. */
@property(nonatomic, assign, readonly, getter=isArrayElement) BOOL arrayElement;
@@ -132,8 +187,6 @@ typedef NS_ENUM(NSInteger, FSTUserDataSource) {
* conditions apply during parsing and providing better error messages.
*/
@property(nonatomic, assign) FSTUserDataSource dataSource;
-@property(nonatomic, strong, readonly) NSMutableArray<FSTFieldTransform *> *fieldTransforms;
-@property(nonatomic, strong, readonly) NSMutableArray<FSTFieldPath *> *fieldMask;
- (instancetype)init NS_UNAVAILABLE;
/**
@@ -144,71 +197,100 @@ typedef NS_ENUM(NSInteger, FSTUserDataSource) {
* the context represents the root of the data being parsed), or a nonempty path (indicating the
* context represents a nested location within the data).
*
- * TODO(b/34871131): We don't support array paths right now, so path can be nil to indicate
+ * TODO(b/34871131): We don't support array paths right now, so path can be nullptr to indicate
* the context represents any location within an array (in which case certain features will not work
* and errors will be somewhat compromised).
*/
- (instancetype)initWithSource:(FSTUserDataSource)dataSource
- path:(nullable FSTFieldPath *)path
+ path:(std::unique_ptr<FieldPath>)path
arrayElement:(BOOL)arrayElement
- fieldTransforms:(NSMutableArray<FSTFieldTransform *> *)fieldTransforms
- fieldMask:(NSMutableArray<FSTFieldPath *> *)fieldMask
+ fieldTransforms:(std::shared_ptr<std::vector<FieldTransform>>)fieldTransforms
+ fieldMask:(std::shared_ptr<std::vector<FieldPath>>)fieldMask
NS_DESIGNATED_INITIALIZER;
// Helpers to get a FSTParseContext for a child field.
- (instancetype)contextForField:(NSString *)fieldName;
-- (instancetype)contextForFieldPath:(FSTFieldPath *)fieldPath;
+- (instancetype)contextForFieldPath:(const FieldPath &)fieldPath;
- (instancetype)contextForArrayIndex:(NSUInteger)index;
/** Returns true for the non-query parse contexts (Set, MergeSet and Update) */
- (BOOL)isWrite;
+
+- (const FieldPath *)path;
+
+- (const std::vector<FieldPath> *)fieldMask;
+
+- (void)appendToFieldMaskWithFieldPath:(FieldPath)fieldPath;
+
+- (const std::vector<FieldTransform> *)fieldTransforms;
+
+- (void)appendToFieldTransformsWithFieldPath:(FieldPath)fieldPath
+ transformOperation:
+ (std::unique_ptr<TransformOperation>)transformOperation;
@end
-@implementation FSTParseContext
+@implementation FSTParseContext {
+ /** The current path being parsed. */
+ // TODO(b/34871131): path should never be nullptr, but we don't support array paths right now.
+ std::unique_ptr<FieldPath> _path;
+ // _fieldMask and _fieldTransforms are shared across all active context objects to accumulate the
+ // result. For example, the result of calling any of contextForField, contextForFieldPath and
+ // contextForArrayIndex shares the ownership of _fieldMask and _fieldTransforms.
+ std::shared_ptr<std::vector<FieldPath>> _fieldMask;
+ std::shared_ptr<std::vector<FieldTransform>> _fieldTransforms;
+}
-+ (instancetype)contextWithSource:(FSTUserDataSource)dataSource path:(nullable FSTFieldPath *)path {
- FSTParseContext *context = [[FSTParseContext alloc] initWithSource:dataSource
- path:path
- arrayElement:NO
- fieldTransforms:[NSMutableArray array]
- fieldMask:[NSMutableArray array]];
++ (instancetype)contextWithSource:(FSTUserDataSource)dataSource
+ path:(std::unique_ptr<FieldPath>)path {
+ FSTParseContext *context =
+ [[FSTParseContext alloc] initWithSource:dataSource
+ path:std::move(path)
+ arrayElement:NO
+ fieldTransforms:std::make_shared<std::vector<FieldTransform>>()
+ fieldMask:std::make_shared<std::vector<FieldPath>>()];
[context validatePath];
return context;
}
- (instancetype)initWithSource:(FSTUserDataSource)dataSource
- path:(nullable FSTFieldPath *)path
+ path:(std::unique_ptr<FieldPath>)path
arrayElement:(BOOL)arrayElement
- fieldTransforms:(NSMutableArray<FSTFieldTransform *> *)fieldTransforms
- fieldMask:(NSMutableArray<FSTFieldPath *> *)fieldMask {
+ fieldTransforms:(std::shared_ptr<std::vector<FieldTransform>>)fieldTransforms
+ fieldMask:(std::shared_ptr<std::vector<FieldPath>>)fieldMask {
if (self = [super init]) {
_dataSource = dataSource;
- _path = path;
+ _path = std::move(path);
_arrayElement = arrayElement;
- _fieldTransforms = fieldTransforms;
- _fieldMask = fieldMask;
+ _fieldTransforms = std::move(fieldTransforms);
+ _fieldMask = std::move(fieldMask);
}
return self;
}
- (instancetype)contextForField:(NSString *)fieldName {
- FSTParseContext *context =
- [[FSTParseContext alloc] initWithSource:self.dataSource
- path:[self.path pathByAppendingSegment:fieldName]
- arrayElement:NO
- fieldTransforms:self.fieldTransforms
- fieldMask:self.fieldMask];
+ std::unique_ptr<FieldPath> path{};
+ if (_path) {
+ path = absl::make_unique<FieldPath>(_path->Append(util::MakeString(fieldName)));
+ }
+ FSTParseContext *context = [[FSTParseContext alloc] initWithSource:self.dataSource
+ path:std::move(path)
+ arrayElement:NO
+ fieldTransforms:_fieldTransforms
+ fieldMask:_fieldMask];
[context validatePathSegment:fieldName];
return context;
}
-- (instancetype)contextForFieldPath:(FSTFieldPath *)fieldPath {
- FSTParseContext *context =
- [[FSTParseContext alloc] initWithSource:self.dataSource
- path:[self.path pathByAppendingPath:fieldPath]
- arrayElement:NO
- fieldTransforms:self.fieldTransforms
- fieldMask:self.fieldMask];
+- (instancetype)contextForFieldPath:(const FieldPath &)fieldPath {
+ std::unique_ptr<FieldPath> path{};
+ if (_path) {
+ path = absl::make_unique<FieldPath>(_path->Append(fieldPath));
+ }
+ FSTParseContext *context = [[FSTParseContext alloc] initWithSource:self.dataSource
+ path:std::move(path)
+ arrayElement:NO
+ fieldTransforms:_fieldTransforms
+ fieldMask:_fieldMask];
[context validatePath];
return context;
}
@@ -218,8 +300,8 @@ typedef NS_ENUM(NSInteger, FSTUserDataSource) {
return [[FSTParseContext alloc] initWithSource:self.dataSource
path:nil
arrayElement:YES
- fieldTransforms:self.fieldTransforms
- fieldMask:self.fieldMask];
+ fieldTransforms:_fieldTransforms
+ fieldMask:_fieldMask];
}
/**
@@ -227,10 +309,10 @@ typedef NS_ENUM(NSInteger, FSTUserDataSource) {
*/
- (NSString *)fieldDescription {
// TODO(b/34871131): Remove nil check once we have proper paths for fields within arrays.
- if (!self.path || self.path.empty) {
+ if (!_path || _path->empty()) {
return @"";
} else {
- return [NSString stringWithFormat:@" (found in field %@)", self.path];
+ return [NSString stringWithFormat:@" (found in field %s)", _path->CanonicalString().c_str()];
}
}
@@ -240,7 +322,7 @@ typedef NS_ENUM(NSInteger, FSTUserDataSource) {
case FSTUserDataSourceMergeSet: // Falls through.
case FSTUserDataSourceUpdate:
return YES;
- case FSTUserDataSourceQueryValue:
+ case FSTUserDataSourceArgument:
return NO;
default:
FSTThrowInvalidArgument(@"Unexpected case for FSTUserDataSource: %d", self.dataSource);
@@ -249,11 +331,11 @@ typedef NS_ENUM(NSInteger, FSTUserDataSource) {
- (void)validatePath {
// TODO(b/34871131): Remove nil check once we have proper paths for fields within arrays.
- if (self.path == nil) {
+ if (_path == nullptr) {
return;
}
- for (int i = 0; i < self.path.length; i++) {
- [self validatePathSegment:[self.path segmentAtIndex:i]];
+ for (const auto &segment : *_path) {
+ [self validatePathSegment:util::WrapNSStringNoCopy(segment)];
}
}
@@ -265,33 +347,62 @@ typedef NS_ENUM(NSInteger, FSTUserDataSource) {
}
}
+- (const FieldPath *)path {
+ return _path.get();
+}
+
+- (const std::vector<FieldPath> *)fieldMask {
+ return _fieldMask.get();
+}
+
+- (void)appendToFieldMaskWithFieldPath:(FieldPath)fieldPath {
+ _fieldMask->push_back(std::move(fieldPath));
+}
+
+- (const std::vector<FieldTransform> *)fieldTransforms {
+ return _fieldTransforms.get();
+}
+
+- (void)appendToFieldTransformsWithFieldPath:(FieldPath)fieldPath
+ transformOperation:
+ (std::unique_ptr<TransformOperation>)transformOperation {
+ _fieldTransforms->emplace_back(std::move(fieldPath), std::move(transformOperation));
+}
+
@end
#pragma mark - FSTDocumentKeyReference
-@implementation FSTDocumentKeyReference
+@implementation FSTDocumentKeyReference {
+ DocumentKey _key;
+}
-- (instancetype)initWithKey:(FSTDocumentKey *)key databaseID:(FSTDatabaseID *)databaseID {
+- (instancetype)initWithKey:(DocumentKey)key databaseID:(const DatabaseId *)databaseID {
self = [super init];
if (self) {
- _key = key;
+ _key = std::move(key);
_databaseID = databaseID;
}
return self;
}
+- (const firebase::firestore::model::DocumentKey &)key {
+ return _key;
+}
+
@end
#pragma mark - FSTUserDataConverter
@interface FSTUserDataConverter ()
-@property(strong, nonatomic, readonly) FSTDatabaseID *databaseID;
+// Does not own the DatabaseId instance.
+@property(assign, nonatomic, readonly) const DatabaseId *databaseID;
@property(strong, nonatomic, readonly) FSTPreConverterBlock preConverter;
@end
@implementation FSTUserDataConverter
-- (instancetype)initWithDatabaseID:(FSTDatabaseID *)databaseID
+- (instancetype)initWithDatabaseID:(const DatabaseId *)databaseID
preConverter:(FSTPreConverterBlock)preConverter {
self = [super init];
if (self) {
@@ -309,13 +420,13 @@ typedef NS_ENUM(NSInteger, FSTUserDataSource) {
}
FSTParseContext *context =
- [FSTParseContext contextWithSource:FSTUserDataSourceMergeSet path:[FSTFieldPath emptyPath]];
+ [FSTParseContext contextWithSource:FSTUserDataSourceMergeSet
+ path:absl::make_unique<FieldPath>(FieldPath::EmptyPath())];
FSTObjectValue *updateData = (FSTObjectValue *)[self parseData:input context:context];
- return
- [[FSTParsedSetData alloc] initWithData:updateData
- fieldMask:[[FSTFieldMask alloc] initWithFields:context.fieldMask]
- fieldTransforms:context.fieldTransforms];
+ return [[FSTParsedSetData alloc] initWithData:updateData
+ fieldMask:FieldMask{*context.fieldMask}
+ fieldTransforms:*context.fieldTransforms];
}
- (FSTParsedSetData *)parsedSetData:(id)input {
@@ -326,12 +437,12 @@ typedef NS_ENUM(NSInteger, FSTUserDataSource) {
}
FSTParseContext *context =
- [FSTParseContext contextWithSource:FSTUserDataSourceSet path:[FSTFieldPath emptyPath]];
+ [FSTParseContext contextWithSource:FSTUserDataSourceSet
+ path:absl::make_unique<FieldPath>(FieldPath::EmptyPath())];
FSTObjectValue *updateData = (FSTObjectValue *)[self parseData:input context:context];
- return [[FSTParsedSetData alloc] initWithData:updateData
- fieldMask:nil
- fieldTransforms:context.fieldTransforms];
+ return
+ [[FSTParsedSetData alloc] initWithData:updateData fieldTransforms:*context.fieldTransforms];
}
- (FSTParsedUpdateData *)parsedUpdateData:(id)input {
@@ -343,13 +454,14 @@ typedef NS_ENUM(NSInteger, FSTUserDataSource) {
NSDictionary *dict = input;
- NSMutableArray<FSTFieldPath *> *fieldMaskPaths = [NSMutableArray array];
+ __block std::vector<FieldPath> fieldMaskPaths{};
__block FSTObjectValue *updateData = [FSTObjectValue objectValue];
FSTParseContext *context =
- [FSTParseContext contextWithSource:FSTUserDataSourceUpdate path:[FSTFieldPath emptyPath]];
+ [FSTParseContext contextWithSource:FSTUserDataSourceUpdate
+ path:absl::make_unique<FieldPath>(FieldPath::EmptyPath())];
[dict enumerateKeysAndObjectsUsingBlock:^(id key, id value, BOOL *stop) {
- FSTFieldPath *path;
+ FieldPath path{};
if ([key isKindOfClass:[NSString class]]) {
path = [FIRFieldPath pathWithDotSeparatedString:key].internalValue;
@@ -363,29 +475,29 @@ typedef NS_ENUM(NSInteger, FSTUserDataSource) {
value = self.preConverter(value);
if ([value isKindOfClass:[FSTDeleteFieldValue class]]) {
// Add it to the field mask, but don't add anything to updateData.
- [fieldMaskPaths addObject:path];
+ fieldMaskPaths.push_back(path);
} else {
FSTFieldValue *_Nullable parsedValue =
[self parseData:value context:[context contextForFieldPath:path]];
if (parsedValue) {
- [fieldMaskPaths addObject:path];
+ fieldMaskPaths.push_back(path);
updateData = [updateData objectBySettingValue:parsedValue forPath:path];
}
}
}];
- FSTFieldMask *mask = [[FSTFieldMask alloc] initWithFields:fieldMaskPaths];
return [[FSTParsedUpdateData alloc] initWithData:updateData
- fieldMask:mask
- fieldTransforms:context.fieldTransforms];
+ fieldMask:FieldMask{fieldMaskPaths}
+ fieldTransforms:*context.fieldTransforms];
}
- (FSTFieldValue *)parsedQueryValue:(id)input {
FSTParseContext *context =
- [FSTParseContext contextWithSource:FSTUserDataSourceQueryValue path:[FSTFieldPath emptyPath]];
+ [FSTParseContext contextWithSource:FSTUserDataSourceArgument
+ path:absl::make_unique<FieldPath>(FieldPath::EmptyPath())];
FSTFieldValue *_Nullable parsed = [self parseData:input context:context];
FSTAssert(parsed, @"Parsed data should not be nil.");
- FSTAssert(context.fieldTransforms.count == 0, @"Field transforms should have been disallowed.");
+ FSTAssert(context.fieldTransforms->empty(), @"Field transforms should have been disallowed.");
return parsed;
}
@@ -401,64 +513,128 @@ typedef NS_ENUM(NSInteger, FSTUserDataSource) {
*/
- (nullable FSTFieldValue *)parseData:(id)input context:(FSTParseContext *)context {
input = self.preConverter(input);
- if ([input isKindOfClass:[NSArray class]]) {
- // TODO(b/34871131): Include the path containing the array in the error message.
- if (context.isArrayElement) {
- FSTThrowInvalidArgument(@"Nested arrays are not supported");
- }
- NSArray *array = input;
- NSMutableArray<FSTFieldValue *> *result = [NSMutableArray arrayWithCapacity:array.count];
- [array enumerateObjectsUsingBlock:^(id entry, NSUInteger idx, BOOL *stop) {
- FSTFieldValue *_Nullable parsedEntry =
- [self parseData:entry context:[context contextForArrayIndex:idx]];
- if (!parsedEntry) {
- // Just include nulls in the array for fields being replaced with a sentinel.
- parsedEntry = [FSTNullValue nullValue];
- }
- [result addObject:parsedEntry];
- }];
+ if ([input isKindOfClass:[NSDictionary class]]) {
+ return [self parseDictionary:(NSDictionary *)input context:context];
+ } else {
// If context.path is nil we are already inside an array and we don't support field mask paths
// more granular than the top-level array.
if (context.path) {
- [context.fieldMask addObject:context.path];
+ [context appendToFieldMaskWithFieldPath:*context.path];
}
- return [[FSTArrayValue alloc] initWithValueNoCopy:result];
- } else if ([input isKindOfClass:[NSDictionary class]]) {
- NSDictionary *dict = input;
- NSMutableDictionary<NSString *, FSTFieldValue *> *result =
- [NSMutableDictionary dictionaryWithCapacity:dict.count];
- [dict enumerateKeysAndObjectsUsingBlock:^(NSString *key, id value, BOOL *stop) {
- FSTFieldValue *_Nullable parsedValue =
- [self parseData:value context:[context contextForField:key]];
- if (parsedValue) {
- result[key] = parsedValue;
+ if ([input isKindOfClass:[NSArray class]]) {
+ // TODO(b/34871131): Include the path containing the array in the error message.
+ if (context.isArrayElement) {
+ FSTThrowInvalidArgument(@"Nested arrays are not supported");
}
- }];
- return [[FSTObjectValue alloc] initWithDictionary:result];
+ return [self parseArray:(NSArray *)input context:context];
+ } else if ([input isKindOfClass:[FIRFieldValue class]]) {
+ // parseSentinelFieldValue may add an FSTFieldTransform, but we return nil since nothing
+ // should be included in the actual parsed data.
+ [self parseSentinelFieldValue:(FIRFieldValue *)input context:context];
+ return nil;
+ } else {
+ return [self parseScalarValue:input context:context];
+ }
+ }
+}
- } else {
- // If context.path is null, we are inside an array and we should have already added the root of
- // the array to the field mask.
- if (context.path) {
- [context.fieldMask addObject:context.path];
+- (FSTFieldValue *)parseDictionary:(NSDictionary *)dict context:(FSTParseContext *)context {
+ NSMutableDictionary<NSString *, FSTFieldValue *> *result =
+ [NSMutableDictionary dictionaryWithCapacity:dict.count];
+ [dict enumerateKeysAndObjectsUsingBlock:^(NSString *key, id value, BOOL *stop) {
+ FSTFieldValue *_Nullable parsedValue =
+ [self parseData:value context:[context contextForField:key]];
+ if (parsedValue) {
+ result[key] = parsedValue;
+ }
+ }];
+ return [[FSTObjectValue alloc] initWithDictionary:result];
+}
+
+- (FSTFieldValue *)parseArray:(NSArray *)array context:(FSTParseContext *)context {
+ NSMutableArray<FSTFieldValue *> *result = [NSMutableArray arrayWithCapacity:array.count];
+ [array enumerateObjectsUsingBlock:^(id entry, NSUInteger idx, BOOL *stop) {
+ FSTFieldValue *_Nullable parsedEntry =
+ [self parseData:entry context:[context contextForArrayIndex:idx]];
+ if (!parsedEntry) {
+ // Just include nulls in the array for fields being replaced with a sentinel.
+ parsedEntry = [FSTNullValue nullValue];
}
- return [self parseScalarValue:input context:context];
+ [result addObject:parsedEntry];
+ }];
+ return [[FSTArrayValue alloc] initWithValueNoCopy:result];
+}
+
+/**
+ * "Parses" the provided FIRFieldValue, adding any necessary transforms to
+ * context.fieldTransforms.
+ */
+- (void)parseSentinelFieldValue:(FIRFieldValue *)fieldValue context:(FSTParseContext *)context {
+ // Sentinels are only supported with writes, and not within arrays.
+ if (![context isWrite]) {
+ FSTThrowInvalidArgument(@"%@ can only be used with updateData() and setData()%@",
+ fieldValue.methodName, [context fieldDescription]);
+ }
+ if (!context.path) {
+ FSTThrowInvalidArgument(@"%@ is not currently supported inside arrays", fieldValue.methodName);
+ }
+
+ if ([fieldValue isKindOfClass:[FSTDeleteFieldValue class]]) {
+ if (context.dataSource == FSTUserDataSourceMergeSet) {
+ // No transform to add for a delete, so we do nothing.
+ } else if (context.dataSource == FSTUserDataSourceUpdate) {
+ FSTAssert(context.path->size() > 0,
+ @"FieldValue.delete() at the top level should have already been handled.");
+ FSTThrowInvalidArgument(
+ @"FieldValue.delete() can only appear at the top level of your "
+ "update data%@",
+ [context fieldDescription]);
+ } else {
+ // We shouldn't encounter delete sentinels for queries or non-merge setData calls.
+ FSTThrowInvalidArgument(
+ @"FieldValue.delete() can only be used with updateData() and setData() with "
+ @"merge:true%@",
+ [context fieldDescription]);
+ }
+
+ } else if ([fieldValue isKindOfClass:[FSTServerTimestampFieldValue class]]) {
+ [context appendToFieldTransformsWithFieldPath:*context.path
+ transformOperation:absl::make_unique<ServerTimestampTransform>(
+ ServerTimestampTransform::Get())];
+
+ } else if ([fieldValue isKindOfClass:[FSTArrayUnionFieldValue class]]) {
+ std::vector<FSTFieldValue *> parsedElements =
+ [self parseArrayTransformElements:((FSTArrayUnionFieldValue *)fieldValue).elements];
+ auto array_union =
+ absl::make_unique<ArrayTransform>(TransformOperation::Type::ArrayUnion, parsedElements);
+ [context appendToFieldTransformsWithFieldPath:*context.path
+ transformOperation:std::move(array_union)];
+
+ } else if ([fieldValue isKindOfClass:[FSTArrayRemoveFieldValue class]]) {
+ std::vector<FSTFieldValue *> parsedElements =
+ [self parseArrayTransformElements:((FSTArrayRemoveFieldValue *)fieldValue).elements];
+ auto array_remove =
+ absl::make_unique<ArrayTransform>(TransformOperation::Type::ArrayRemove, parsedElements);
+ [context appendToFieldTransformsWithFieldPath:*context.path
+ transformOperation:std::move(array_remove)];
+
+ } else {
+ FSTFail(@"Unknown FIRFieldValue type: %@", NSStringFromClass([fieldValue class]));
}
}
/**
- * Helper to parse a scalar value (i.e. not an NSDictionary or NSArray).
+ * Helper to parse a scalar value (i.e. not an NSDictionary, NSArray, or FIRFieldValue).
*
* Note that it handles all NSNumber values that are encodable as int64_t or doubles
* (depending on the underlying type of the NSNumber). Unsigned integer values are handled though
* any value outside what is representable by int64_t (a signed 64-bit value) will throw an
* exception.
*
- * @return The parsed value, or nil if the value was a FieldValue sentinel that should not be
- * included in the resulting parsed data.
+ * @return The parsed value.
*/
-- (nullable FSTFieldValue *)parseScalarValue:(nullable id)input context:(FSTParseContext *)context {
+- (FSTFieldValue *)parseScalarValue:(nullable id)input context:(FSTParseContext *)context {
if (!input || [input isMemberOfClass:[NSNull class]]) {
return [FSTNullValue nullValue];
@@ -530,7 +706,14 @@ typedef NS_ENUM(NSInteger, FSTUserDataSource) {
return [FSTStringValue stringValue:input];
} else if ([input isKindOfClass:[NSDate class]]) {
- return [FSTTimestampValue timestampValue:[FSTTimestamp timestampWithDate:input]];
+ return [FSTTimestampValue timestampValue:[FIRTimestamp timestampWithDate:input]];
+
+ } else if ([input isKindOfClass:[FIRTimestamp class]]) {
+ FIRTimestamp *originalTimestamp = (FIRTimestamp *)input;
+ FIRTimestamp *truncatedTimestamp =
+ [FIRTimestamp timestampWithSeconds:originalTimestamp.seconds
+ nanoseconds:originalTimestamp.nanoseconds / 1000 * 1000];
+ return [FSTTimestampValue timestampValue:truncatedTimestamp];
} else if ([input isKindOfClass:[FIRGeoPoint class]]) {
return [FSTGeoPointValue geoPointValue:input];
@@ -540,11 +723,12 @@ typedef NS_ENUM(NSInteger, FSTUserDataSource) {
} else if ([input isKindOfClass:[FSTDocumentKeyReference class]]) {
FSTDocumentKeyReference *reference = input;
- if (![reference.databaseID isEqual:self.databaseID]) {
- FSTDatabaseID *other = reference.databaseID;
+ if (*reference.databaseID != *self.databaseID) {
+ const DatabaseId *other = reference.databaseID;
FSTThrowInvalidArgument(
- @"Document Reference is for database %@/%@ but should be for database %@/%@%@",
- other.projectID, other.databaseID, self.databaseID.projectID, self.databaseID.databaseID,
+ @"Document Reference is for database %s/%s but should be for database %s/%s%@",
+ other->project_id().c_str(), other->database_id().c_str(),
+ self.databaseID->project_id().c_str(), self.databaseID->database_id().c_str(),
[context fieldDescription]);
}
return [FSTReferenceValue referenceValue:reference.key databaseID:self.databaseID];
@@ -554,17 +738,16 @@ typedef NS_ENUM(NSInteger, FSTUserDataSource) {
if (context.dataSource == FSTUserDataSourceMergeSet) {
return nil;
} else if (context.dataSource == FSTUserDataSourceUpdate) {
- FSTAssert(context.path.length > 0,
+ FSTAssert(context.path->size() > 0,
@"FieldValue.delete() at the top level should have already been handled.");
FSTThrowInvalidArgument(
- @"FieldValue.delete() can only appear at the top level of your "
- "update data%@",
+ @"FieldValue.delete() can only appear at the top level of your update data%@",
[context fieldDescription]);
} else {
// We shouldn't encounter delete sentinels for queries or non-merge setData calls.
FSTThrowInvalidArgument(
@"FieldValue.delete() can only be used with updateData() and setData() with "
- @"SetOptions.merge().");
+ @"merge: true.");
}
} else if ([input isKindOfClass:[FSTServerTimestampFieldValue class]]) {
if (![context isWrite]) {
@@ -576,10 +759,9 @@ typedef NS_ENUM(NSInteger, FSTUserDataSource) {
@"FieldValue.serverTimestamp() is not currently supported inside arrays%@",
[context fieldDescription]);
}
- [context.fieldTransforms
- addObject:[[FSTFieldTransform alloc]
- initWithPath:context.path
- transform:[FSTServerTimestampTransform serverTimestampTransform]]];
+ [context appendToFieldTransformsWithFieldPath:*context.path
+ transformOperation:absl::make_unique<ServerTimestampTransform>(
+ ServerTimestampTransform::Get())];
// Return nil so this value is omitted from the parsed result.
return nil;
@@ -593,6 +775,22 @@ typedef NS_ENUM(NSInteger, FSTUserDataSource) {
}
}
+- (std::vector<FSTFieldValue *>)parseArrayTransformElements:(NSArray<id> *)elements {
+ std::vector<FSTFieldValue *> results;
+ for (id element in elements) {
+ // Although array transforms are used with writes, the actual elements being unioned or removed
+ // are not considered writes since they cannot contain any FieldValue sentinels, etc.
+ FSTParseContext *context =
+ [FSTParseContext contextWithSource:FSTUserDataSourceArgument
+ path:absl::make_unique<FieldPath>(FieldPath::EmptyPath())];
+ FSTFieldValue *parsedElement = [self parseData:element context:context];
+ FSTAssert(parsedElement && context.fieldTransforms->size() == 0,
+ @"Failed to properly parse array transform element: %@", element);
+ results.push_back(parsedElement);
+ }
+ return results;
+}
+
@end
NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/Auth/FSTCredentialsProvider.h b/Firestore/Source/Auth/FSTCredentialsProvider.h
deleted file mode 100644
index 92d5fdc..0000000
--- a/Firestore/Source/Auth/FSTCredentialsProvider.h
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright 2017 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/Foundation.h>
-
-NS_ASSUME_NONNULL_BEGIN
-
-@class FIRApp;
-@class FSTDispatchQueue;
-@class FSTUser;
-
-#pragma mark - FSTGetTokenResult
-
-/**
- * The current FSTUser and the authentication token provided by the underlying authentication
- * mechanism. This is the result of calling -[FSTCredentialsProvider getTokenForcingRefresh].
- *
- * ## Portability notes: no TokenType on iOS
- *
- * The TypeScript client supports 1st party Oauth tokens (for the Firebase Console to auth as the
- * developer) and OAuth2 tokens for the node.js sdk to auth with a service account. We don't have
- * plans to support either case on mobile so there's no TokenType here.
- */
-// TODO(mcg): Rename FSTToken, change parameter order to line up with the other platforms.
-@interface FSTGetTokenResult : NSObject
-
-- (instancetype)init NS_UNAVAILABLE;
-- (instancetype)initWithUser:(FSTUser *)user
- token:(NSString *_Nullable)token NS_DESIGNATED_INITIALIZER;
-
-/** The user with which the token is associated (used for persisting user state on disk, etc.). */
-@property(nonatomic, nonnull, readonly) FSTUser *user;
-
-/** The actual raw token. */
-@property(nonatomic, copy, nullable, readonly) NSString *token;
-
-@end
-
-#pragma mark - Typedefs
-
-/**
- * `FSTVoidTokenErrorBlock` is a block that gets a token or an error.
- *
- * @param token An auth token as a string.
- * @param error The error if one occurred, or else `nil`.
- */
-typedef void (^FSTVoidGetTokenResultBlock)(FSTGetTokenResult *_Nullable token,
- NSError *_Nullable error);
-
-/** Listener block notified with an FSTUser. */
-typedef void (^FSTVoidUserBlock)(FSTUser *user);
-
-#pragma mark - FSTCredentialsProvider
-
-/** Provides methods for getting the uid and token for the current user and listen for changes. */
-@protocol FSTCredentialsProvider <NSObject>
-
-/** Requests token for the current user, optionally forcing a refreshed token to be fetched. */
-- (void)getTokenForcingRefresh:(BOOL)forceRefresh completion:(FSTVoidGetTokenResultBlock)completion;
-
-/**
- * A listener to be notified of user changes (sign-in / sign-out). It is immediately called once
- * with the initial user.
- *
- * Note that this block will be called back on an arbitrary thread that is not the normal Firestore
- * worker thread.
- */
-@property(nonatomic, copy, nullable, readwrite) FSTVoidUserBlock userChangeListener;
-
-@end
-
-#pragma mark - FSTFirebaseCredentialsProvider
-
-/**
- * `FSTFirebaseCredentialsProvider` uses Firebase Auth via `FIRApp` to get an auth token.
- *
- * NOTE: To simplify the implementation, it requires that you set `userChangeListener` with a
- * non-`nil` value no more than once and don't call `getTokenForcingRefresh:` after setting
- * it to `nil`.
- *
- * This class must be implemented in a thread-safe manner since it is accessed from the thread
- * backing our internal worker queue and the callbacks from FIRAuth will be executed on an
- * arbitrary different thread.
- */
-@interface FSTFirebaseCredentialsProvider : NSObject <FSTCredentialsProvider>
-
-/**
- * Initializes a new FSTFirebaseCredentialsProvider.
- *
- * @param app The Firebase app from which to get credentials.
- *
- * @return A new instance of FSTFirebaseCredentialsProvider.
- */
-- (instancetype)initWithApp:(FIRApp *)app NS_DESIGNATED_INITIALIZER;
-
-- (id)init NS_UNAVAILABLE;
-
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/Auth/FSTCredentialsProvider.m b/Firestore/Source/Auth/FSTCredentialsProvider.m
deleted file mode 100644
index 653d7ff..0000000
--- a/Firestore/Source/Auth/FSTCredentialsProvider.m
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- * Copyright 2017 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 "Firestore/Source/Auth/FSTCredentialsProvider.h"
-
-#import <FirebaseCore/FIRApp.h>
-#import <FirebaseCore/FIRAppInternal.h>
-#import <GRPCClient/GRPCCall.h>
-
-#import "FIRFirestoreErrors.h"
-#import "Firestore/Source/Auth/FSTUser.h"
-#import "Firestore/Source/Util/FSTAssert.h"
-#import "Firestore/Source/Util/FSTClasses.h"
-#import "Firestore/Source/Util/FSTDispatchQueue.h"
-
-NS_ASSUME_NONNULL_BEGIN
-
-#pragma mark - FSTGetTokenResult
-
-@implementation FSTGetTokenResult
-- (instancetype)initWithUser:(FSTUser *)user token:(NSString *_Nullable)token {
- if (self = [super init]) {
- _user = user;
- _token = token;
- }
- return self;
-}
-@end
-
-#pragma mark - FSTFirebaseCredentialsProvider
-@interface FSTFirebaseCredentialsProvider ()
-
-@property(nonatomic, strong, readonly) FIRApp *app;
-
-/** Handle used to stop receiving auth changes once userChangeListener is removed. */
-@property(nonatomic, strong, nullable, readwrite) id<NSObject> authListenerHandle;
-
-/** The current user as reported to us via our AuthStateDidChangeListener. */
-@property(nonatomic, strong, nonnull, readwrite) FSTUser *currentUser;
-
-/**
- * Counter used to detect if the user changed while a -getTokenForcingRefresh: request was
- * outstanding.
- */
-@property(nonatomic, assign, readwrite) int userCounter;
-
-@end
-
-@implementation FSTFirebaseCredentialsProvider {
- FSTVoidUserBlock _userChangeListener;
-}
-
-- (instancetype)initWithApp:(FIRApp *)app {
- self = [super init];
- if (self) {
- _app = app;
- _currentUser = [[FSTUser alloc] initWithUID:[self.app getUID]];
- _userCounter = 0;
-
- // Register for user changes so that we can internally track the current user.
- FSTWeakify(self);
- _authListenerHandle = [[NSNotificationCenter defaultCenter]
- addObserverForName:FIRAuthStateDidChangeInternalNotification
- object:nil
- queue:nil
- usingBlock:^(NSNotification *notification) {
- FSTStrongify(self);
- if (self) {
- @synchronized(self) {
- NSDictionary *userInfo = notification.userInfo;
-
- // ensure we're only notifiying for the current app.
- FIRApp *notifiedApp =
- userInfo[FIRAuthStateDidChangeInternalNotificationAppKey];
- if (![self.app isEqual:notifiedApp]) {
- return;
- }
-
- NSString *userID = userInfo[FIRAuthStateDidChangeInternalNotificationUIDKey];
- FSTUser *newUser = [[FSTUser alloc] initWithUID:userID];
- if (![newUser isEqual:self.currentUser]) {
- self.currentUser = newUser;
- self.userCounter++;
- FSTVoidUserBlock listenerBlock = self.userChangeListener;
- if (listenerBlock) {
- listenerBlock(self.currentUser);
- }
- }
- }
- }
- }];
- }
- return self;
-}
-
-- (void)getTokenForcingRefresh:(BOOL)forceRefresh
- completion:(FSTVoidGetTokenResultBlock)completion {
- FSTAssert(self.authListenerHandle, @"getToken cannot be called after listener removed.");
-
- // Take note of the current value of the userCounter so that this method can fail (with a
- // FIRFirestoreErrorCodeAborted error) if there is a user change while the request is outstanding.
- int initialUserCounter = self.userCounter;
-
- void (^getTokenCallback)(NSString *, NSError *) =
- ^(NSString *_Nullable token, NSError *_Nullable error) {
- @synchronized(self) {
- if (initialUserCounter != self.userCounter) {
- // Cancel the request since the user changed while the request was outstanding so the
- // response is likely for a previous user (which user, we can't be sure).
- NSDictionary *errorInfo = @{@"details" : @"getToken aborted due to user change."};
- NSError *cancelError = [NSError errorWithDomain:FIRFirestoreErrorDomain
- code:FIRFirestoreErrorCodeAborted
- userInfo:errorInfo];
- completion(nil, cancelError);
- } else {
- FSTGetTokenResult *result =
- [[FSTGetTokenResult alloc] initWithUser:self.currentUser token:token];
- completion(result, error);
- }
- };
- };
-
- [self.app getTokenForcingRefresh:forceRefresh withCallback:getTokenCallback];
-}
-
-- (void)setUserChangeListener:(nullable FSTVoidUserBlock)block {
- @synchronized(self) {
- if (block) {
- FSTAssert(!_userChangeListener, @"UserChangeListener set twice!");
-
- // Fire initial event.
- block(self.currentUser);
- } else {
- FSTAssert(self.authListenerHandle, @"UserChangeListener removed twice!");
- FSTAssert(_userChangeListener, @"UserChangeListener removed without being set!");
- [[NSNotificationCenter defaultCenter] removeObserver:self.authListenerHandle];
- self.authListenerHandle = nil;
- }
- _userChangeListener = block;
- }
-}
-
-- (nullable FSTVoidUserBlock)userChangeListener {
- @synchronized(self) {
- return _userChangeListener;
- }
-}
-
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/Auth/FSTEmptyCredentialsProvider.m b/Firestore/Source/Auth/FSTEmptyCredentialsProvider.m
deleted file mode 100644
index e78452a..0000000
--- a/Firestore/Source/Auth/FSTEmptyCredentialsProvider.m
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright 2017 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 "Firestore/Source/Auth/FSTEmptyCredentialsProvider.h"
-
-#import "Firestore/Source/Auth/FSTUser.h"
-#import "Firestore/Source/Util/FSTAssert.h"
-#import "Firestore/Source/Util/FSTDispatchQueue.h"
-
-NS_ASSUME_NONNULL_BEGIN
-
-@implementation FSTEmptyCredentialsProvider
-
-- (void)getTokenForcingRefresh:(BOOL)forceRefresh
- completion:(FSTVoidGetTokenResultBlock)completion {
- completion(nil, nil);
-}
-
-- (void)setUserChangeListener:(nullable FSTVoidUserBlock)block {
- // Since the user never changes, we just need to fire the initial event and don't need to hang
- // onto the block.
- if (block) {
- block([FSTUser unauthenticatedUser]);
- }
-}
-
-- (nullable FSTVoidUserBlock)userChangeListener {
- // TODO(mikelehen): Implementation omitted for convenience since it's not actually required.
- FSTFail(@"Not implemented.");
-}
-
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/Auth/FSTUser.h b/Firestore/Source/Auth/FSTUser.h
deleted file mode 100644
index 68ecc4c..0000000
--- a/Firestore/Source/Auth/FSTUser.h
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright 2017 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/Foundation.h>
-
-NS_ASSUME_NONNULL_BEGIN
-
-/**
- * Simple wrapper around a nullable UID. Mostly exists to make code more readable and for use as
- * a key in dictionaries (since keys cannot be nil).
- */
-@interface FSTUser : NSObject <NSCopying>
-
-/** Returns an FSTUser with a nil UID. */
-+ (instancetype)unauthenticatedUser;
-
-// Porting note: no GOOGLE_CREDENTIALS or FIRST_PARTY equivalent on iOS, see FSTGetTokenResult for
-// more details.
-
-/** Initializes an FSTUser with the given UID. */
-- (instancetype)initWithUID:(NSString *_Nullable)UID NS_DESIGNATED_INITIALIZER;
-- (instancetype)init NS_UNAVAILABLE;
-
-@property(nonatomic, copy, nullable, readonly) NSString *UID;
-
-@property(nonatomic, assign, readonly, getter=isUnauthenticated) BOOL unauthenticated;
-
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/Auth/FSTUser.m b/Firestore/Source/Auth/FSTUser.m
deleted file mode 100644
index 605b4e6..0000000
--- a/Firestore/Source/Auth/FSTUser.m
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright 2017 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 "Firestore/Source/Auth/FSTUser.h"
-
-NS_ASSUME_NONNULL_BEGIN
-
-#pragma mark - FSTUser
-
-@implementation FSTUser
-
-@dynamic unauthenticated;
-
-+ (instancetype)unauthenticatedUser {
- return [[FSTUser alloc] initWithUID:nil];
-}
-
-- (instancetype)initWithUID:(NSString *_Nullable)UID {
- if (self = [super init]) {
- _UID = UID;
- }
- return self;
-}
-
-- (BOOL)isEqual:(id)object {
- if (self == object) {
- return YES;
- } else if (![object isKindOfClass:[FSTUser class]]) {
- return NO;
- } else {
- FSTUser *other = object;
- return (self.isUnauthenticated && other.isUnauthenticated) ||
- [self.UID isEqualToString:other.UID];
- }
-}
-
-- (NSUInteger)hash {
- return [self.UID hash];
-}
-
-- (id)copyWithZone:(nullable NSZone *)zone {
- return self; // since FSTUser objects are immutable
-}
-
-- (NSString *)description {
- return [NSString stringWithFormat:@"<FSTUser uid=%@>", self.UID];
-}
-
-- (BOOL)isUnauthenticated {
- return self.UID == nil;
-}
-
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/Core/FSTDatabaseInfo.h b/Firestore/Source/Core/FSTDatabaseInfo.h
deleted file mode 100644
index fae884f..0000000
--- a/Firestore/Source/Core/FSTDatabaseInfo.h
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright 2017 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/Foundation.h>
-
-@class FSTDatabaseID;
-
-NS_ASSUME_NONNULL_BEGIN
-
-/** FSTDatabaseInfo contains data about the database. */
-@interface FSTDatabaseInfo : NSObject
-
-/**
- * Creates and returns a new FSTDatabaseInfo.
- *
- * @param databaseID The project/database to use.
- * @param persistenceKey A unique identifier for this Firestore's local storage. Usually derived
- * from -[FIRApp appName].
- * @param host The hostname of the datastore backend.
- * @param sslEnabled Whether to use SSL when connecting.
- * @return A new instance of FSTDatabaseInfo.
- */
-+ (instancetype)databaseInfoWithDatabaseID:(FSTDatabaseID *)databaseID
- persistenceKey:(NSString *)persistenceKey
- host:(NSString *)host
- sslEnabled:(BOOL)sslEnabled;
-
-/** The database info. */
-@property(nonatomic, strong, readonly) FSTDatabaseID *databaseID;
-
-/** The application name, taken from FIRApp. */
-@property(nonatomic, copy, readonly) NSString *persistenceKey;
-
-/** The hostname of the backend. */
-@property(nonatomic, copy, readonly) NSString *host;
-
-/** Whether to use SSL when connecting. */
-@property(nonatomic, readonly, getter=isSSLEnabled) BOOL sslEnabled;
-
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/Core/FSTDatabaseInfo.m b/Firestore/Source/Core/FSTDatabaseInfo.m
deleted file mode 100644
index 2dbe61a..0000000
--- a/Firestore/Source/Core/FSTDatabaseInfo.m
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright 2017 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 "Firestore/Source/Core/FSTDatabaseInfo.h"
-
-#import "Firestore/Source/Model/FSTDatabaseID.h"
-
-NS_ASSUME_NONNULL_BEGIN
-
-#pragma mark - FSTDatabaseInfo
-
-@implementation FSTDatabaseInfo
-
-#pragma mark - Constructors
-
-+ (instancetype)databaseInfoWithDatabaseID:(FSTDatabaseID *)databaseID
- persistenceKey:(NSString *)persistenceKey
- host:(NSString *)host
- sslEnabled:(BOOL)sslEnabled {
- return [[FSTDatabaseInfo alloc] initWithDatabaseID:databaseID
- persistenceKey:persistenceKey
- host:host
- sslEnabled:sslEnabled];
-}
-
-/**
- * Designated initializer.
- *
- * @param databaseID The database in the datastore.
- * @param persistenceKey A unique identifier for this Firestore's local storage. Usually derived
- * from -[FIRApp appName].
- * @param host The Firestore server hostname.
- * @param sslEnabled Whether to use SSL when connecting.
- */
-- (instancetype)initWithDatabaseID:(FSTDatabaseID *)databaseID
- persistenceKey:(NSString *)persistenceKey
- host:(NSString *)host
- sslEnabled:(BOOL)sslEnabled {
- if (self = [super init]) {
- _databaseID = databaseID;
- _persistenceKey = [persistenceKey copy];
- _host = [host copy];
- _sslEnabled = sslEnabled;
- }
- return self;
-}
-
-#pragma mark - NSObject methods
-
-- (NSString *)description {
- return [NSString
- stringWithFormat:@"<FSTDatabaseInfo: databaseID:%@ host:%@>", self.databaseID, self.host];
-}
-
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/Core/FSTEventManager.h b/Firestore/Source/Core/FSTEventManager.h
index edd2a96..8eafd4b 100644
--- a/Firestore/Source/Core/FSTEventManager.h
+++ b/Firestore/Source/Core/FSTEventManager.h
@@ -62,7 +62,7 @@ NS_ASSUME_NONNULL_BEGIN
- (void)queryDidChangeViewSnapshot:(FSTViewSnapshot *)snapshot;
- (void)queryDidError:(NSError *)error;
-- (void)clientDidChangeOnlineState:(FSTOnlineState)onlineState;
+- (void)applyChangedOnlineState:(FSTOnlineState)onlineState;
@property(nonatomic, strong, readonly) FSTQuery *query;
diff --git a/Firestore/Source/Core/FSTEventManager.m b/Firestore/Source/Core/FSTEventManager.mm
index 3e1b99b..b02fc5f 100644
--- a/Firestore/Source/Core/FSTEventManager.m
+++ b/Firestore/Source/Core/FSTEventManager.mm
@@ -151,7 +151,7 @@ NS_ASSUME_NONNULL_BEGIN
self.viewSnapshotHandler(nil, error);
}
-- (void)clientDidChangeOnlineState:(FSTOnlineState)onlineState {
+- (void)applyChangedOnlineState:(FSTOnlineState)onlineState {
self.onlineState = onlineState;
if (self.snapshot && !self.raisedInitialEvent &&
[self shouldRaiseInitialEventForSnapshot:self.snapshot onlineState:onlineState]) {
@@ -169,9 +169,9 @@ NS_ASSUME_NONNULL_BEGIN
return YES;
}
- // NOTE: We consider OnlineState.Unknown as online (it should become Failed
- // or Online if we wait long enough).
- BOOL maybeOnline = onlineState != FSTOnlineStateFailed;
+ // NOTE: We consider OnlineState.Unknown as online (it should become Offline or Online if we
+ // wait long enough).
+ BOOL maybeOnline = onlineState != FSTOnlineStateOffline;
// Don't raise the event if we're online, aren't synced yet (checked
// above) and are waiting for a sync.
if (self.options.waitForSyncWhenOnline && maybeOnline) {
@@ -180,7 +180,7 @@ NS_ASSUME_NONNULL_BEGIN
}
// Raise data from cache if we have any documents or we are offline
- return !snapshot.documents.isEmpty || onlineState == FSTOnlineStateFailed;
+ return !snapshot.documents.isEmpty || onlineState == FSTOnlineStateOffline;
}
- (BOOL)shouldRaiseEventForSnapshot:(FSTViewSnapshot *)snapshot {
@@ -268,7 +268,7 @@ NS_ASSUME_NONNULL_BEGIN
}
[queryInfo.listeners addObject:listener];
- [listener clientDidChangeOnlineState:self.onlineState];
+ [listener applyChangedOnlineState:self.onlineState];
if (queryInfo.viewSnapshot) {
[listener queryDidChangeViewSnapshot:queryInfo.viewSnapshot];
@@ -321,11 +321,11 @@ NS_ASSUME_NONNULL_BEGIN
[self.queries removeObjectForKey:query];
}
-- (void)watchStreamDidChangeOnlineState:(FSTOnlineState)onlineState {
+- (void)applyChangedOnlineState:(FSTOnlineState)onlineState {
self.onlineState = onlineState;
for (FSTQueryListenersInfo *info in self.queries.objectEnumerator) {
for (FSTQueryListener *listener in info.listeners) {
- [listener clientDidChangeOnlineState:onlineState];
+ [listener applyChangedOnlineState:onlineState];
}
}
}
diff --git a/Firestore/Source/Core/FSTFirestoreClient.h b/Firestore/Source/Core/FSTFirestoreClient.h
index 6a1e11b..6da5ed3 100644
--- a/Firestore/Source/Core/FSTFirestoreClient.h
+++ b/Firestore/Source/Core/FSTFirestoreClient.h
@@ -20,8 +20,10 @@
#import "Firestore/Source/Core/FSTViewSnapshot.h"
#import "Firestore/Source/Remote/FSTRemoteStore.h"
-@class FSTDatabaseID;
-@class FSTDatabaseInfo;
+#include "Firestore/core/src/firebase/firestore/auth/credentials_provider.h"
+#include "Firestore/core/src/firebase/firestore/core/database_info.h"
+#include "Firestore/core/src/firebase/firestore/model/database_id.h"
+
@class FSTDispatchQueue;
@class FSTDocument;
@class FSTListenOptions;
@@ -29,7 +31,6 @@
@class FSTQuery;
@class FSTQueryListener;
@class FSTTransaction;
-@protocol FSTCredentialsProvider;
NS_ASSUME_NONNULL_BEGIN
@@ -38,16 +39,17 @@ NS_ASSUME_NONNULL_BEGIN
* SDK architecture. It is responsible for creating the worker queue that is shared by all of the
* other components in the system.
*/
-@interface FSTFirestoreClient : NSObject
+@interface FSTFirestoreClient : NSObject <FSTOnlineStateDelegate>
/**
* Creates and returns a FSTFirestoreClient with the given parameters.
*
* All callbacks and events will be triggered on the provided userDispatchQueue.
*/
-+ (instancetype)clientWithDatabaseInfo:(FSTDatabaseInfo *)databaseInfo
++ (instancetype)clientWithDatabaseInfo:(const firebase::firestore::core::DatabaseInfo &)databaseInfo
usePersistence:(BOOL)usePersistence
- credentialsProvider:(id<FSTCredentialsProvider>)credentialsProvider
+ credentialsProvider:(firebase::firestore::auth::CredentialsProvider *)
+ credentialsProvider // no passing ownership
userDispatchQueue:(FSTDispatchQueue *)userDispatchQueue
workerDispatchQueue:(FSTDispatchQueue *)workerDispatchQueue;
@@ -80,7 +82,8 @@ NS_ASSUME_NONNULL_BEGIN
completion:(FSTVoidIDErrorBlock)completion;
/** The database ID of the databaseInfo this client was initialized with. */
-@property(nonatomic, strong, readonly) FSTDatabaseID *databaseID;
+// Ownes a DatabaseInfo instance, which contains the id here.
+@property(nonatomic, assign, readonly) const firebase::firestore::model::DatabaseId *databaseID;
/**
* Dispatch queue for user callbacks / events. This will often be the "Main Dispatch Queue" of the
diff --git a/Firestore/Source/Core/FSTFirestoreClient.m b/Firestore/Source/Core/FSTFirestoreClient.mm
index 2e0e407..33d1903 100644
--- a/Firestore/Source/Core/FSTFirestoreClient.m
+++ b/Firestore/Source/Core/FSTFirestoreClient.mm
@@ -16,8 +16,9 @@
#import "Firestore/Source/Core/FSTFirestoreClient.h"
-#import "Firestore/Source/Auth/FSTCredentialsProvider.h"
-#import "Firestore/Source/Core/FSTDatabaseInfo.h"
+#include <future> // NOLINT(build/c++11)
+#include <memory>
+
#import "Firestore/Source/Core/FSTEventManager.h"
#import "Firestore/Source/Core/FSTSyncEngine.h"
#import "Firestore/Source/Core/FSTTransaction.h"
@@ -35,16 +36,31 @@
#import "Firestore/Source/Util/FSTDispatchQueue.h"
#import "Firestore/Source/Util/FSTLogger.h"
+#include "Firestore/core/src/firebase/firestore/auth/credentials_provider.h"
+#include "Firestore/core/src/firebase/firestore/core/database_info.h"
+#include "Firestore/core/src/firebase/firestore/model/database_id.h"
+#include "Firestore/core/src/firebase/firestore/util/string_apple.h"
+
+namespace util = firebase::firestore::util;
+using firebase::firestore::auth::CredentialsProvider;
+using firebase::firestore::auth::User;
+using firebase::firestore::core::DatabaseInfo;
+using firebase::firestore::model::DatabaseId;
+
NS_ASSUME_NONNULL_BEGIN
-@interface FSTFirestoreClient ()
-- (instancetype)initWithDatabaseInfo:(FSTDatabaseInfo *)databaseInfo
+@interface FSTFirestoreClient () {
+ DatabaseInfo _databaseInfo;
+}
+
+- (instancetype)initWithDatabaseInfo:(const DatabaseInfo &)databaseInfo
usePersistence:(BOOL)usePersistence
- credentialsProvider:(id<FSTCredentialsProvider>)credentialsProvider
+ credentialsProvider:
+ (CredentialsProvider *)credentialsProvider // no passing ownership
userDispatchQueue:(FSTDispatchQueue *)userDispatchQueue
workerDispatchQueue:(FSTDispatchQueue *)queue NS_DESIGNATED_INITIALIZER;
-@property(nonatomic, strong, readonly) FSTDatabaseInfo *databaseInfo;
+@property(nonatomic, assign, readonly) const DatabaseInfo *databaseInfo;
@property(nonatomic, strong, readonly) FSTEventManager *eventManager;
@property(nonatomic, strong, readonly) id<FSTPersistence> persistence;
@property(nonatomic, strong, readonly) FSTSyncEngine *syncEngine;
@@ -59,15 +75,17 @@ NS_ASSUME_NONNULL_BEGIN
*/
@property(nonatomic, strong, readonly) FSTDispatchQueue *workerDispatchQueue;
-@property(nonatomic, strong, readonly) id<FSTCredentialsProvider> credentialsProvider;
+// Does not own the CredentialsProvider instance.
+@property(nonatomic, assign, readonly) CredentialsProvider *credentialsProvider;
@end
@implementation FSTFirestoreClient
-+ (instancetype)clientWithDatabaseInfo:(FSTDatabaseInfo *)databaseInfo
++ (instancetype)clientWithDatabaseInfo:(const DatabaseInfo &)databaseInfo
usePersistence:(BOOL)usePersistence
- credentialsProvider:(id<FSTCredentialsProvider>)credentialsProvider
+ credentialsProvider:
+ (CredentialsProvider *)credentialsProvider // no passing ownership
userDispatchQueue:(FSTDispatchQueue *)userDispatchQueue
workerDispatchQueue:(FSTDispatchQueue *)workerDispatchQueue {
return [[FSTFirestoreClient alloc] initWithDatabaseInfo:databaseInfo
@@ -77,9 +95,10 @@ NS_ASSUME_NONNULL_BEGIN
workerDispatchQueue:workerDispatchQueue];
}
-- (instancetype)initWithDatabaseInfo:(FSTDatabaseInfo *)databaseInfo
+- (instancetype)initWithDatabaseInfo:(const DatabaseInfo &)databaseInfo
usePersistence:(BOOL)usePersistence
- credentialsProvider:(id<FSTCredentialsProvider>)credentialsProvider
+ credentialsProvider:
+ (CredentialsProvider *)credentialsProvider // no passing ownership
userDispatchQueue:(FSTDispatchQueue *)userDispatchQueue
workerDispatchQueue:(FSTDispatchQueue *)workerDispatchQueue {
if (self = [super init]) {
@@ -88,36 +107,38 @@ NS_ASSUME_NONNULL_BEGIN
_userDispatchQueue = userDispatchQueue;
_workerDispatchQueue = workerDispatchQueue;
- dispatch_semaphore_t initialUserAvailable = dispatch_semaphore_create(0);
- __block FSTUser *initialUser;
- FSTWeakify(self);
- _credentialsProvider.userChangeListener = ^(FSTUser *user) {
- FSTStrongify(self);
- if (self) {
- if (!initialUser) {
- initialUser = user;
- dispatch_semaphore_signal(initialUserAvailable);
- } else {
- [workerDispatchQueue dispatchAsync:^{
- [self userDidChange:user];
- }];
- }
+ auto userPromise = std::make_shared<std::promise<User>>();
+
+ __weak typeof(self) weakSelf = self;
+ auto userChangeListener = [initialized = false, userPromise, weakSelf,
+ workerDispatchQueue](User user) mutable {
+ typeof(self) strongSelf = weakSelf;
+ if (!strongSelf) return;
+
+ if (!initialized) {
+ initialized = true;
+ userPromise->set_value(user);
+ } else {
+ [workerDispatchQueue dispatchAsync:^{
+ [strongSelf userDidChange:user];
+ }];
}
};
+ _credentialsProvider->SetUserChangeListener(userChangeListener);
+
// Defer initialization until we get the current user from the userChangeListener. This is
// guaranteed to be synchronously dispatched onto our worker queue, so we will be initialized
// before any subsequently queued work runs.
[_workerDispatchQueue dispatchAsync:^{
- dispatch_semaphore_wait(initialUserAvailable, DISPATCH_TIME_FOREVER);
-
- [self initializeWithUser:initialUser usePersistence:usePersistence];
+ User user = userPromise->get_future().get();
+ [self initializeWithUser:user usePersistence:usePersistence];
}];
}
return self;
}
-- (void)initializeWithUser:(FSTUser *)user usePersistence:(BOOL)usePersistence {
+- (void)initializeWithUser:(const User &)user usePersistence:(BOOL)usePersistence {
// Do all of our initialization on our own dispatch queue.
[self.workerDispatchQueue verifyIsCurrentQueue];
@@ -130,11 +151,11 @@ NS_ASSUME_NONNULL_BEGIN
// enabled.
garbageCollector = [[FSTNoOpGarbageCollector alloc] init];
- NSString *dir = [FSTLevelDB storageDirectoryForDatabaseInfo:self.databaseInfo
+ NSString *dir = [FSTLevelDB storageDirectoryForDatabaseInfo:*self.databaseInfo
documentsDirectory:[FSTLevelDB documentsDirectory]];
FSTSerializerBeta *remoteSerializer =
- [[FSTSerializerBeta alloc] initWithDatabaseID:self.databaseInfo.databaseID];
+ [[FSTSerializerBeta alloc] initWithDatabaseID:&self.databaseInfo->database_id()];
FSTLocalSerializer *serializer =
[[FSTLocalSerializer alloc] initWithRemoteSerializer:remoteSerializer];
@@ -159,9 +180,11 @@ NS_ASSUME_NONNULL_BEGIN
FSTDatastore *datastore = [FSTDatastore datastoreWithDatabase:self.databaseInfo
workerDispatchQueue:self.workerDispatchQueue
- credentials:self.credentialsProvider];
+ credentials:_credentialsProvider];
- _remoteStore = [FSTRemoteStore remoteStoreWithLocalStore:_localStore datastore:datastore];
+ _remoteStore = [[FSTRemoteStore alloc] initWithLocalStore:_localStore
+ datastore:datastore
+ workerDispatchQueue:self.workerDispatchQueue];
_syncEngine = [[FSTSyncEngine alloc] initWithLocalStore:_localStore
remoteStore:_remoteStore
@@ -172,7 +195,7 @@ NS_ASSUME_NONNULL_BEGIN
// Setup wiring for remote store.
_remoteStore.syncEngine = _syncEngine;
- _remoteStore.onlineStateDelegate = _eventManager;
+ _remoteStore.onlineStateDelegate = self;
// NOTE: RemoteStore depends on LocalStore (for persisting stream tokens, refilling mutation
// queue, etc.) so must be started after LocalStore.
@@ -180,13 +203,18 @@ NS_ASSUME_NONNULL_BEGIN
[_remoteStore start];
}
-- (void)userDidChange:(FSTUser *)user {
+- (void)userDidChange:(const User &)user {
[self.workerDispatchQueue verifyIsCurrentQueue];
- FSTLog(@"User Changed: %@", user);
+ FSTLog(@"User Changed: %s", user.uid().c_str());
[self.syncEngine userDidChange:user];
}
+- (void)applyChangedOnlineState:(FSTOnlineState)onlineState {
+ [self.syncEngine applyChangedOnlineState:onlineState];
+ [self.eventManager applyChangedOnlineState:onlineState];
+}
+
- (void)disableNetworkWithCompletion:(nullable FSTVoidErrorBlock)completion {
[self.workerDispatchQueue dispatchAsync:^{
[self.remoteStore disableNetwork];
@@ -211,10 +239,9 @@ NS_ASSUME_NONNULL_BEGIN
- (void)shutdownWithCompletion:(nullable FSTVoidErrorBlock)completion {
[self.workerDispatchQueue dispatchAsync:^{
- self.credentialsProvider.userChangeListener = nil;
+ self->_credentialsProvider->SetUserChangeListener(nullptr);
[self.remoteStore shutdown];
- [self.localStore shutdown];
[self.persistence shutdown];
if (completion) {
[self.userDispatchQueue dispatchAsync:^{
@@ -248,9 +275,11 @@ NS_ASSUME_NONNULL_BEGIN
completion:(nullable FSTVoidErrorBlock)completion {
[self.workerDispatchQueue dispatchAsync:^{
if (mutations.count == 0) {
- [self.userDispatchQueue dispatchAsync:^{
- completion(nil);
- }];
+ if (completion) {
+ [self.userDispatchQueue dispatchAsync:^{
+ completion(nil);
+ }];
+ }
} else {
[self.syncEngine writeMutations:mutations
completion:^(NSError *error) {
@@ -280,12 +309,15 @@ NS_ASSUME_NONNULL_BEGIN
}];
}
}];
-
}];
}
-- (FSTDatabaseID *)databaseID {
- return self.databaseInfo.databaseID;
+- (const DatabaseInfo *)databaseInfo {
+ return &_databaseInfo;
+}
+
+- (const DatabaseId *)databaseID {
+ return &_databaseInfo.database_id();
}
@end
diff --git a/Firestore/Source/Core/FSTListenSequence.h b/Firestore/Source/Core/FSTListenSequence.h
new file mode 100644
index 0000000..56d0e78
--- /dev/null
+++ b/Firestore/Source/Core/FSTListenSequence.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2018 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/Foundation.h>
+
+#import "FSTTypes.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ * FSTListenSequence is a monotonic sequence. It is initialized with a minimum value to
+ * exceed. All subsequent calls to next will return increasing values.
+ */
+@interface FSTListenSequence : NSObject
+
+- (instancetype)initStartingAfter:(FSTListenSequenceNumber)after NS_DESIGNATED_INITIALIZER;
+
+- (id)init NS_UNAVAILABLE;
+
+- (FSTListenSequenceNumber)next;
+
+@end
+
+NS_ASSUME_NONNULL_END \ No newline at end of file
diff --git a/Firestore/Source/Core/FSTListenSequence.mm b/Firestore/Source/Core/FSTListenSequence.mm
new file mode 100644
index 0000000..6a9f9ae
--- /dev/null
+++ b/Firestore/Source/Core/FSTListenSequence.mm
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2018 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 "Firestore/Source/Core/FSTListenSequence.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+#pragma mark - FSTListenSequence
+
+@interface FSTListenSequence () {
+ FSTListenSequenceNumber _previousSequenceNumber;
+}
+
+@end
+
+@implementation FSTListenSequence
+
+#pragma mark - Constructors
+
+- (instancetype)initStartingAfter:(FSTListenSequenceNumber)after {
+ self = [super init];
+ if (self) {
+ _previousSequenceNumber = after;
+ }
+ return self;
+}
+
+#pragma mark - Public methods
+
+- (FSTListenSequenceNumber)next {
+ _previousSequenceNumber++;
+ return _previousSequenceNumber;
+}
+
+@end
+
+NS_ASSUME_NONNULL_END \ No newline at end of file
diff --git a/Firestore/Source/Core/FSTQuery.h b/Firestore/Source/Core/FSTQuery.h
index 0562ae4..3a67e7f 100644
--- a/Firestore/Source/Core/FSTQuery.h
+++ b/Firestore/Source/Core/FSTQuery.h
@@ -16,11 +16,11 @@
#import <Foundation/Foundation.h>
+#include "Firestore/core/src/firebase/firestore/model/field_path.h"
+#include "Firestore/core/src/firebase/firestore/model/resource_path.h"
+
@class FSTDocument;
-@class FSTDocumentKey;
-@class FSTFieldPath;
@class FSTFieldValue;
-@class FSTResourcePath;
NS_ASSUME_NONNULL_BEGIN
@@ -40,7 +40,7 @@ typedef NS_ENUM(NSInteger, FSTRelationFilterOperator) {
@protocol FSTFilter <NSObject>
/** Returns the field the Filter operates over. */
-- (FSTFieldPath *)field;
+- (const firebase::firestore::model::FieldPath &)field;
/** Returns true if a document matches the filter. */
- (BOOL)matchesDocument:(FSTDocument *)document;
@@ -64,7 +64,7 @@ typedef NS_ENUM(NSInteger, FSTRelationFilterOperator) {
* @param value A constant value to compare @a field to. The RHS of the expression.
* @return A new instance of FSTRelationFilter.
*/
-+ (instancetype)filterWithField:(FSTFieldPath *)field
++ (instancetype)filterWithField:(firebase::firestore::model::FieldPath)field
filterOperator:(FSTRelationFilterOperator)filterOperator
value:(FSTFieldValue *)value;
@@ -74,7 +74,7 @@ typedef NS_ENUM(NSInteger, FSTRelationFilterOperator) {
- (BOOL)isInequality;
/** The left hand side of the relation. A path into a document field. */
-@property(nonatomic, strong, readonly) FSTFieldPath *field;
+- (const firebase::firestore::model::FieldPath &)field;
/** The type of equality/inequality operator to use in the relation. */
@property(nonatomic, assign, readonly) FSTRelationFilterOperator filterOperator;
@@ -87,32 +87,35 @@ typedef NS_ENUM(NSInteger, FSTRelationFilterOperator) {
/** Filter that matches NULL values. */
@interface FSTNullFilter : NSObject <FSTFilter>
- (instancetype)init NS_UNAVAILABLE;
-- (instancetype)initWithField:(FSTFieldPath *)field NS_DESIGNATED_INITIALIZER;
+- (instancetype)initWithField:(firebase::firestore::model::FieldPath)field
+ NS_DESIGNATED_INITIALIZER;
@end
/** Filter that matches NAN values. */
@interface FSTNanFilter : NSObject <FSTFilter>
- (instancetype)init NS_UNAVAILABLE;
-- (instancetype)initWithField:(FSTFieldPath *)field NS_DESIGNATED_INITIALIZER;
+- (instancetype)initWithField:(firebase::firestore::model::FieldPath)field
+ NS_DESIGNATED_INITIALIZER;
@end
/** FSTSortOrder is a field and direction to order query results by. */
@interface FSTSortOrder : NSObject <NSCopying>
/** Creates a new sort order with the given field and direction. */
-+ (instancetype)sortOrderWithFieldPath:(FSTFieldPath *)fieldPath ascending:(BOOL)ascending;
++ (instancetype)sortOrderWithFieldPath:(firebase::firestore::model::FieldPath)fieldPath
+ ascending:(BOOL)ascending;
- (instancetype)init NS_UNAVAILABLE;
/** Compares two documents based on the field and direction of this sort order. */
- (NSComparisonResult)compareDocument:(FSTDocument *)document1 toDocument:(FSTDocument *)document2;
+/** The field to sort by. */
+- (const firebase::firestore::model::FieldPath &)field;
+
/** The direction of the sort. */
@property(nonatomic, assign, readonly, getter=isAscending) BOOL ascending;
-/** The field to sort by. */
-@property(nonatomic, strong, readonly) FSTFieldPath *field;
-
@end
/**
@@ -157,7 +160,7 @@ typedef NS_ENUM(NSInteger, FSTRelationFilterOperator) {
/**
* Initializes a query with all of its components directly.
*/
-- (instancetype)initWithPath:(FSTResourcePath *)path
+- (instancetype)initWithPath:(firebase::firestore::model::ResourcePath)path
filterBy:(NSArray<id<FSTFilter>> *)filters
orderBy:(NSArray<FSTSortOrder *> *)sortOrders
limit:(NSInteger)limit
@@ -170,7 +173,7 @@ typedef NS_ENUM(NSInteger, FSTRelationFilterOperator) {
* @param path The path to the collection to be queried over.
* @return A new instance of FSTQuery.
*/
-+ (instancetype)queryWithPath:(FSTResourcePath *)path;
++ (instancetype)queryWithPath:(firebase::firestore::model::ResourcePath)path;
/**
* Returns the list of ordering constraints that were explicitly requested on the query by the
@@ -237,14 +240,15 @@ typedef NS_ENUM(NSInteger, FSTRelationFilterOperator) {
/** Returns a comparator that will sort documents according to the receiver's sort order. */
- (NSComparator)comparator;
-/** Returns the field of the first filter on the receiver that's an inequality, or nil if none. */
-- (FSTFieldPath *_Nullable)inequalityFilterField;
+/** Returns the field of the first filter on the receiver that's an inequality, or nullptr if none.
+ */
+- (const firebase::firestore::model::FieldPath *)inequalityFilterField;
-/** Returns the first field in an order-by constraint, or nil if none. */
-- (FSTFieldPath *_Nullable)firstSortOrderField;
+/** Returns the first field in an order-by constraint, or nullptr if none. */
+- (const firebase::firestore::model::FieldPath *)firstSortOrderField;
/** The base path of the query. */
-@property(nonatomic, strong, readonly) FSTResourcePath *path;
+- (const firebase::firestore::model::ResourcePath &)path;
/** The filters on the documents returned by the query. */
@property(nonatomic, strong, readonly) NSArray<id<FSTFilter>> *filters;
diff --git a/Firestore/Source/Core/FSTQuery.m b/Firestore/Source/Core/FSTQuery.mm
index 0bfd917..8f49c26 100644
--- a/Firestore/Source/Core/FSTQuery.m
+++ b/Firestore/Source/Core/FSTQuery.mm
@@ -16,17 +16,36 @@
#import "Firestore/Source/Core/FSTQuery.h"
+#include <memory>
+#include <string>
+#include <utility>
+
#import "Firestore/Source/API/FIRFirestore+Internal.h"
#import "Firestore/Source/Model/FSTDocument.h"
-#import "Firestore/Source/Model/FSTDocumentKey.h"
#import "Firestore/Source/Model/FSTFieldValue.h"
-#import "Firestore/Source/Model/FSTPath.h"
#import "Firestore/Source/Util/FSTAssert.h"
+#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+#include "Firestore/core/src/firebase/firestore/model/field_path.h"
+#include "Firestore/core/src/firebase/firestore/model/resource_path.h"
+#include "Firestore/core/src/firebase/firestore/util/string_apple.h"
+
+namespace util = firebase::firestore::util;
+using firebase::firestore::model::DocumentKey;
+using firebase::firestore::model::FieldPath;
+using firebase::firestore::model::ResourcePath;
+
NS_ASSUME_NONNULL_BEGIN
#pragma mark - FSTRelationFilterOperator functions
+/**
+ * Returns the reverse order (i.e. Ascending => Descending) etc.
+ */
+static constexpr NSComparisonResult ReverseOrder(NSComparisonResult result) {
+ return static_cast<NSComparisonResult>(-static_cast<NSInteger>(result));
+}
+
NSString *FSTStringFromQueryRelationOperator(FSTRelationFilterOperator filterOperator) {
switch (filterOperator) {
case FSTRelationFilterOperatorLessThan:
@@ -46,7 +65,10 @@ NSString *FSTStringFromQueryRelationOperator(FSTRelationFilterOperator filterOpe
#pragma mark - FSTRelationFilter
-@interface FSTRelationFilter ()
+@interface FSTRelationFilter () {
+ /** The left hand side of the relation. A path into a document field. */
+ firebase::firestore::model::FieldPath _field;
+}
/**
* Initializes the receiver relation filter.
@@ -55,7 +77,7 @@ NSString *FSTStringFromQueryRelationOperator(FSTRelationFilterOperator filterOpe
* @param filterOperator The binary operator to apply.
* @param value A constant value to compare @a field to. The RHS of the expression.
*/
-- (instancetype)initWithField:(FSTFieldPath *)field
+- (instancetype)initWithField:(FieldPath)field
filterOperator:(FSTRelationFilterOperator)filterOperator
value:(FSTFieldValue *)value NS_DESIGNATED_INITIALIZER;
@@ -74,18 +96,20 @@ NSString *FSTStringFromQueryRelationOperator(FSTRelationFilterOperator filterOpe
#pragma mark - Constructor methods
-+ (instancetype)filterWithField:(FSTFieldPath *)field
++ (instancetype)filterWithField:(FieldPath)field
filterOperator:(FSTRelationFilterOperator)filterOperator
value:(FSTFieldValue *)value {
- return [[FSTRelationFilter alloc] initWithField:field filterOperator:filterOperator value:value];
+ return [[FSTRelationFilter alloc] initWithField:std::move(field)
+ filterOperator:filterOperator
+ value:value];
}
-- (instancetype)initWithField:(FSTFieldPath *)field
+- (instancetype)initWithField:(FieldPath)field
filterOperator:(FSTRelationFilterOperator)filterOperator
value:(FSTFieldValue *)value {
self = [super init];
if (self) {
- _field = field;
+ _field = std::move(field);
_filterOperator = filterOperator;
_value = value;
}
@@ -98,10 +122,14 @@ NSString *FSTStringFromQueryRelationOperator(FSTRelationFilterOperator filterOpe
return self.filterOperator != FSTRelationFilterOperatorEqual;
}
+- (const firebase::firestore::model::FieldPath &)field {
+ return _field;
+}
+
#pragma mark - NSObject methods
- (NSString *)description {
- return [NSString stringWithFormat:@"%@ %@ %@", [self.field canonicalString],
+ return [NSString stringWithFormat:@"%s %@ %@", _field.CanonicalString().c_str(),
FSTStringFromQueryRelationOperator(self.filterOperator),
self.value];
}
@@ -119,7 +147,7 @@ NSString *FSTStringFromQueryRelationOperator(FSTRelationFilterOperator filterOpe
#pragma mark - Private methods
- (BOOL)matchesDocument:(FSTDocument *)document {
- if ([self.field isKeyFieldPath]) {
+ if (_field.IsKeyFieldPath()) {
FSTAssert([self.value isKindOfClass:[FSTReferenceValue class]],
@"Comparing on key, but filter value not a FSTReferenceValue.");
FSTReferenceValue *refValue = (FSTReferenceValue *)self.value;
@@ -132,7 +160,7 @@ NSString *FSTStringFromQueryRelationOperator(FSTRelationFilterOperator filterOpe
- (NSString *)canonicalID {
// TODO(b/37283291): This should be collision robust and avoid relying on |description| methods.
- return [NSString stringWithFormat:@"%@%@%@", [self.field canonicalString],
+ return [NSString stringWithFormat:@"%s%@%@", _field.CanonicalString().c_str(),
FSTStringFromQueryRelationOperator(self.filterOperator),
[self.value value]];
}
@@ -141,7 +169,7 @@ NSString *FSTStringFromQueryRelationOperator(FSTRelationFilterOperator filterOpe
if (self.filterOperator != other.filterOperator) {
return NO;
}
- if (![self.field isEqual:other.field]) {
+ if (_field != other.field) {
return NO;
}
if (![self.value isEqual:other.value]) {
@@ -178,14 +206,15 @@ NSString *FSTStringFromQueryRelationOperator(FSTRelationFilterOperator filterOpe
#pragma mark - FSTNullFilter
-@interface FSTNullFilter ()
-@property(nonatomic, strong, readonly) FSTFieldPath *field;
+@interface FSTNullFilter () {
+ FieldPath _field;
+}
@end
@implementation FSTNullFilter
-- (instancetype)initWithField:(FSTFieldPath *)field {
+- (instancetype)initWithField:(FieldPath)field {
if (self = [super init]) {
- _field = field;
+ _field = std::move(field);
}
return self;
}
@@ -196,7 +225,11 @@ NSString *FSTStringFromQueryRelationOperator(FSTRelationFilterOperator filterOpe
}
- (NSString *)canonicalID {
- return [NSString stringWithFormat:@"%@ IS NULL", [self.field canonicalString]];
+ return [NSString stringWithFormat:@"%s IS NULL", _field.CanonicalString().c_str()];
+}
+
+- (const firebase::firestore::model::FieldPath &)field {
+ return _field;
}
- (NSString *)description {
@@ -205,28 +238,29 @@ NSString *FSTStringFromQueryRelationOperator(FSTRelationFilterOperator filterOpe
- (BOOL)isEqual:(id)other {
if (other == self) return YES;
- if (!other || ![[other class] isEqual:[self class]]) return NO;
+ if (![[other class] isEqual:[self class]]) return NO;
- return [self.field isEqual:((FSTNullFilter *)other).field];
+ return _field == ((FSTNullFilter *)other)->_field;
}
- (NSUInteger)hash {
- return [self.field hash];
+ return _field.Hash();
}
@end
#pragma mark - FSTNanFilter
-@interface FSTNanFilter ()
-@property(nonatomic, strong, readonly) FSTFieldPath *field;
+@interface FSTNanFilter () {
+ FieldPath _field;
+}
@end
@implementation FSTNanFilter
-- (instancetype)initWithField:(FSTFieldPath *)field {
+- (instancetype)initWithField:(FieldPath)field {
if (self = [super init]) {
- _field = field;
+ _field = std::move(field);
}
return self;
}
@@ -237,7 +271,11 @@ NSString *FSTStringFromQueryRelationOperator(FSTRelationFilterOperator filterOpe
}
- (NSString *)canonicalID {
- return [NSString stringWithFormat:@"%@ IS NaN", [self.field canonicalString]];
+ return [NSString stringWithFormat:@"%s IS NaN", _field.CanonicalString().c_str()];
+}
+
+- (const firebase::firestore::model::FieldPath &)field {
+ return _field;
}
- (NSString *)description {
@@ -246,22 +284,25 @@ NSString *FSTStringFromQueryRelationOperator(FSTRelationFilterOperator filterOpe
- (BOOL)isEqual:(id)other {
if (other == self) return YES;
- if (!other || ![[other class] isEqual:[self class]]) return NO;
+ if (![[other class] isEqual:[self class]]) return NO;
- return [self.field isEqual:((FSTNanFilter *)other).field];
+ return _field == ((FSTNanFilter *)other)->_field;
}
- (NSUInteger)hash {
- return [self.field hash];
+ return _field.Hash();
}
@end
#pragma mark - FSTSortOrder
-@interface FSTSortOrder ()
+@interface FSTSortOrder () {
+ /** The field to sort by. */
+ firebase::firestore::model::FieldPath _field;
+}
/** Creates a new sort order with the given field and direction. */
-- (instancetype)initWithFieldPath:(FSTFieldPath *)fieldPath ascending:(BOOL)ascending;
+- (instancetype)initWithFieldPath:(FieldPath)fieldPath ascending:(BOOL)ascending;
- (NSString *)canonicalID;
@@ -271,47 +312,56 @@ NSString *FSTStringFromQueryRelationOperator(FSTRelationFilterOperator filterOpe
#pragma mark - Constructor methods
-+ (instancetype)sortOrderWithFieldPath:(FSTFieldPath *)fieldPath ascending:(BOOL)ascending {
- return [[FSTSortOrder alloc] initWithFieldPath:fieldPath ascending:ascending];
++ (instancetype)sortOrderWithFieldPath:(FieldPath)fieldPath ascending:(BOOL)ascending {
+ return [[FSTSortOrder alloc] initWithFieldPath:std::move(fieldPath) ascending:ascending];
}
-- (instancetype)initWithFieldPath:(FSTFieldPath *)fieldPath ascending:(BOOL)ascending {
+- (instancetype)initWithFieldPath:(FieldPath)fieldPath ascending:(BOOL)ascending {
self = [super init];
if (self) {
- _field = fieldPath;
+ _field = std::move(fieldPath);
_ascending = ascending;
}
return self;
}
+- (const firebase::firestore::model::FieldPath &)field {
+ return _field;
+}
+
#pragma mark - Public methods
- (NSComparisonResult)compareDocument:(FSTDocument *)document1 toDocument:(FSTDocument *)document2 {
- int modifier = self.isAscending ? 1 : -1;
- if ([self.field isEqual:[FSTFieldPath keyFieldPath]]) {
- return (NSComparisonResult)(modifier * FSTDocumentKeyComparator(document1.key, document2.key));
+ NSComparisonResult result;
+ if (_field == FieldPath::KeyFieldPath()) {
+ result = FSTDocumentKeyComparator(document1.key, document2.key);
} else {
FSTFieldValue *value1 = [document1 fieldForPath:self.field];
FSTFieldValue *value2 = [document2 fieldForPath:self.field];
FSTAssert(value1 != nil && value2 != nil,
@"Trying to compare documents on fields that don't exist.");
- return modifier * [value1 compare:value2];
+ result = [value1 compare:value2];
+ }
+ if (!self.isAscending) {
+ result = ReverseOrder(result);
}
+ return result;
}
- (NSString *)canonicalID {
- return [NSString
- stringWithFormat:@"%@%@", self.field.canonicalString, self.isAscending ? @"asc" : @"desc"];
+ return [NSString stringWithFormat:@"%s%@", _field.CanonicalString().c_str(),
+ self.isAscending ? @"asc" : @"desc"];
}
- (BOOL)isEqualToSortOrder:(FSTSortOrder *)other {
- return [self.field isEqual:other.field] && self.isAscending == other.isAscending;
+ return _field == other->_field && self.isAscending == other.isAscending;
}
#pragma mark - NSObject methods
- (NSString *)description {
- return [NSString stringWithFormat:@"<FSTSortOrder: path:%@ dir:%@>", self.field,
+ return [NSString stringWithFormat:@"<FSTSortOrder: path:%s dir:%@>",
+ _field.CanonicalString().c_str(),
self.ascending ? @"asc" : @"desc"];
}
@@ -374,10 +424,11 @@ NSString *FSTStringFromQueryRelationOperator(FSTRelationFilterOperator filterOpe
BOOL *stop) {
FSTSortOrder *sortOrderComponent = sortOrder[idx];
NSComparisonResult comparison;
- if ([sortOrderComponent.field isEqual:[FSTFieldPath keyFieldPath]]) {
+ if (sortOrderComponent.field == FieldPath::KeyFieldPath()) {
FSTAssert([fieldValue isKindOfClass:[FSTReferenceValue class]],
@"FSTBound has a non-key value where the key path is being used %@", fieldValue);
- comparison = [fieldValue.value compare:document.key];
+ FSTReferenceValue *refValue = (FSTReferenceValue *)fieldValue;
+ comparison = [refValue.value compare:document.key];
} else {
FSTFieldValue *docValue = [document fieldForPath:sortOrderComponent.field];
FSTAssert(docValue != nil, @"Field should exist since document matched the orderBy already.");
@@ -385,7 +436,7 @@ NSString *FSTStringFromQueryRelationOperator(FSTRelationFilterOperator filterOpe
}
if (!sortOrderComponent.isAscending) {
- comparison = comparison * -1;
+ comparison = ReverseOrder(comparison);
}
if (comparison != 0) {
@@ -432,6 +483,8 @@ NSString *FSTStringFromQueryRelationOperator(FSTRelationFilterOperator filterOpe
@interface FSTQuery () {
// Cached value of the canonicalID property.
NSString *_canonicalID;
+ /** The base path of the query. */
+ ResourcePath _path;
}
/**
@@ -442,7 +495,7 @@ NSString *FSTStringFromQueryRelationOperator(FSTRelationFilterOperator filterOpe
* @param sortOrders The fields and directions to sort the results.
* @param limit If not NSNotFound, only this many results will be returned.
*/
-- (instancetype)initWithPath:(FSTResourcePath *)path
+- (instancetype)initWithPath:(ResourcePath)path
filterBy:(NSArray<id<FSTFilter>> *)filters
orderBy:(NSArray<FSTSortOrder *> *)sortOrders
limit:(NSInteger)limit
@@ -461,8 +514,8 @@ NSString *FSTStringFromQueryRelationOperator(FSTRelationFilterOperator filterOpe
#pragma mark - Constructors
-+ (instancetype)queryWithPath:(FSTResourcePath *)path {
- return [[FSTQuery alloc] initWithPath:path
++ (instancetype)queryWithPath:(ResourcePath)path {
+ return [[FSTQuery alloc] initWithPath:std::move(path)
filterBy:@[]
orderBy:@[]
limit:NSNotFound
@@ -470,14 +523,14 @@ NSString *FSTStringFromQueryRelationOperator(FSTRelationFilterOperator filterOpe
endAt:nil];
}
-- (instancetype)initWithPath:(FSTResourcePath *)path
+- (instancetype)initWithPath:(ResourcePath)path
filterBy:(NSArray<id<FSTFilter>> *)filters
orderBy:(NSArray<FSTSortOrder *> *)sortOrders
limit:(NSInteger)limit
startAt:(nullable FSTBound *)startAtBound
endAt:(nullable FSTBound *)endAtBound {
if (self = [super init]) {
- _path = path;
+ _path = std::move(path);
_filters = filters;
_explicitSortOrders = sortOrders;
_limit = limit;
@@ -515,32 +568,33 @@ NSString *FSTStringFromQueryRelationOperator(FSTRelationFilterOperator filterOpe
- (NSArray *)sortOrders {
if (self.memoizedSortOrders == nil) {
- FSTFieldPath *_Nullable inequalityField = [self inequalityFilterField];
- FSTFieldPath *_Nullable firstSortOrderField = [self firstSortOrderField];
+ const FieldPath *inequalityField = [self inequalityFilterField];
+ const FieldPath *firstSortOrderField = [self firstSortOrderField];
if (inequalityField && !firstSortOrderField) {
// In order to implicitly add key ordering, we must also add the inequality filter field for
// it to be a valid query. Note that the default inequality field and key ordering is
// ascending.
- if ([inequalityField isKeyFieldPath]) {
+ if (inequalityField->IsKeyFieldPath()) {
self.memoizedSortOrders =
- @[ [FSTSortOrder sortOrderWithFieldPath:[FSTFieldPath keyFieldPath] ascending:YES] ];
+ @[ [FSTSortOrder sortOrderWithFieldPath:FieldPath::KeyFieldPath() ascending:YES] ];
} else {
self.memoizedSortOrders = @[
- [FSTSortOrder sortOrderWithFieldPath:inequalityField ascending:YES],
- [FSTSortOrder sortOrderWithFieldPath:[FSTFieldPath keyFieldPath] ascending:YES]
+ [FSTSortOrder sortOrderWithFieldPath:*inequalityField ascending:YES],
+ [FSTSortOrder sortOrderWithFieldPath:FieldPath::KeyFieldPath() ascending:YES]
];
}
} else {
- FSTAssert(!inequalityField || [inequalityField isEqual:firstSortOrderField],
- @"First orderBy %@ should match inequality field %@.", firstSortOrderField,
- inequalityField);
+ FSTAssert(!inequalityField || *inequalityField == *firstSortOrderField,
+ @"First orderBy %s should match inequality field %s.",
+ firstSortOrderField->CanonicalString().c_str(),
+ inequalityField->CanonicalString().c_str());
__block BOOL foundKeyOrder = NO;
NSMutableArray *result = [NSMutableArray array];
for (FSTSortOrder *sortOrder in self.explicitSortOrders) {
[result addObject:sortOrder];
- if ([sortOrder.field isEqual:[FSTFieldPath keyFieldPath]]) {
+ if (sortOrder.field == FieldPath::KeyFieldPath()) {
foundKeyOrder = YES;
}
}
@@ -550,7 +604,7 @@ NSString *FSTStringFromQueryRelationOperator(FSTRelationFilterOperator filterOpe
// explicit sort order
BOOL lastIsAscending =
self.explicitSortOrders.count > 0 ? self.explicitSortOrders.lastObject.ascending : YES;
- [result addObject:[FSTSortOrder sortOrderWithFieldPath:[FSTFieldPath keyFieldPath]
+ [result addObject:[FSTSortOrder sortOrderWithFieldPath:FieldPath::KeyFieldPath()
ascending:lastIsAscending]];
}
@@ -561,17 +615,17 @@ NSString *FSTStringFromQueryRelationOperator(FSTRelationFilterOperator filterOpe
}
- (instancetype)queryByAddingFilter:(id<FSTFilter>)filter {
- FSTAssert(![FSTDocumentKey isDocumentKey:self.path], @"No filtering allowed for document query");
+ FSTAssert(!DocumentKey::IsDocumentKey(_path), @"No filtering allowed for document query");
- FSTFieldPath *_Nullable newInequalityField = nil;
+ const FieldPath *newInequalityField = nullptr;
if ([filter isKindOfClass:[FSTRelationFilter class]] &&
[((FSTRelationFilter *)filter)isInequality]) {
- newInequalityField = filter.field;
+ newInequalityField = &filter.field;
}
- FSTFieldPath *_Nullable queryInequalityField = [self inequalityFilterField];
- FSTAssert(!queryInequalityField || !newInequalityField ||
- [queryInequalityField isEqual:newInequalityField],
- @"Query must only have one inequality field.");
+ const FieldPath *queryInequalityField = [self inequalityFilterField];
+ FSTAssert(
+ !queryInequalityField || !newInequalityField || *queryInequalityField == *newInequalityField,
+ @"Query must only have one inequality field.");
return [[FSTQuery alloc] initWithPath:self.path
filterBy:[self.filters arrayByAddingObject:filter]
@@ -582,8 +636,7 @@ NSString *FSTStringFromQueryRelationOperator(FSTRelationFilterOperator filterOpe
}
- (instancetype)queryByAddingSortOrder:(FSTSortOrder *)sortOrder {
- FSTAssert(![FSTDocumentKey isDocumentKey:self.path],
- @"No ordering is allowed for a document query.");
+ FSTAssert(!DocumentKey::IsDocumentKey(_path), @"No ordering is allowed for a document query.");
// TODO(klimt): Validate that the same key isn't added twice.
return [[FSTQuery alloc] initWithPath:self.path
@@ -622,7 +675,7 @@ NSString *FSTStringFromQueryRelationOperator(FSTRelationFilterOperator filterOpe
}
- (BOOL)isDocumentQuery {
- return [FSTDocumentKey isDocumentKey:self.path] && self.filters.count == 0;
+ return DocumentKey::IsDocumentKey(_path) && self.filters.count == 0;
}
- (BOOL)matchesDocument:(FSTDocument *)document {
@@ -638,26 +691,33 @@ NSString *FSTStringFromQueryRelationOperator(FSTRelationFilterOperator filterOpe
if (comp != NSOrderedSame) {
return comp;
}
- didCompareOnKeyField =
- didCompareOnKeyField || [orderBy.field isEqual:[FSTFieldPath keyFieldPath]];
+ didCompareOnKeyField = didCompareOnKeyField || orderBy.field == FieldPath::KeyFieldPath();
}
FSTAssert(didCompareOnKeyField, @"sortOrder of query did not include key ordering");
return NSOrderedSame;
};
}
-- (FSTFieldPath *_Nullable)inequalityFilterField {
+- (const FieldPath *)inequalityFilterField {
for (id<FSTFilter> filter in self.filters) {
if ([filter isKindOfClass:[FSTRelationFilter class]] &&
((FSTRelationFilter *)filter).filterOperator != FSTRelationFilterOperatorEqual) {
- return filter.field;
+ return &filter.field;
}
}
- return nil;
+ return nullptr;
+}
+
+- (const FieldPath *)firstSortOrderField {
+ if (self.explicitSortOrders.count > 0) {
+ return &self.explicitSortOrders.firstObject.field;
+ }
+ return nullptr;
}
-- (FSTFieldPath *_Nullable)firstSortOrderField {
- return self.explicitSortOrders.firstObject.field;
+/** The base path of the query. */
+- (const firebase::firestore::model::ResourcePath &)path {
+ return _path;
}
#pragma mark - Private properties
@@ -667,7 +727,7 @@ NSString *FSTStringFromQueryRelationOperator(FSTRelationFilterOperator filterOpe
return _canonicalID;
}
- NSMutableString *canonicalID = [[self.path canonicalString] mutableCopy];
+ NSMutableString *canonicalID = [util::WrapNSStringNoCopy(_path.CanonicalString()) mutableCopy];
// Add filters.
[canonicalID appendString:@"|f:"];
@@ -701,7 +761,7 @@ NSString *FSTStringFromQueryRelationOperator(FSTRelationFilterOperator filterOpe
#pragma mark - Private methods
- (BOOL)isEqualToQuery:(FSTQuery *)other {
- return [self.path isEqual:other.path] && self.limit == other.limit &&
+ return self.path == other.path && self.limit == other.limit &&
[self.filters isEqual:other.filters] && [self.sortOrders isEqual:other.sortOrders] &&
(self.startAt == other.startAt || [self.startAt isEqual:other.startAt]) &&
(self.endAt == other.endAt || [self.endAt isEqual:other.endAt]);
@@ -709,13 +769,13 @@ NSString *FSTStringFromQueryRelationOperator(FSTRelationFilterOperator filterOpe
/* Returns YES if the document matches the path for the receiver. */
- (BOOL)pathMatchesDocument:(FSTDocument *)document {
- FSTResourcePath *documentPath = document.key.path;
- if ([FSTDocumentKey isDocumentKey:self.path]) {
+ const ResourcePath &documentPath = document.key.path();
+ if (DocumentKey::IsDocumentKey(_path)) {
// Exact match for document queries.
- return [self.path isEqual:documentPath];
+ return self.path == documentPath;
} else {
// Shallow ancestor queries by default.
- return [self.path isPrefixOfPath:documentPath] && self.path.length == documentPath.length - 1;
+ return self.path.IsPrefixOf(documentPath) && _path.size() == documentPath.size() - 1;
}
}
@@ -724,10 +784,9 @@ NSString *FSTStringFromQueryRelationOperator(FSTRelationFilterOperator filterOpe
*/
- (BOOL)orderByMatchesDocument:(FSTDocument *)document {
for (FSTSortOrder *orderBy in self.explicitSortOrders) {
- FSTFieldPath *fieldPath = orderBy.field;
+ const FieldPath &fieldPath = orderBy.field;
// order by key always matches
- if (![fieldPath isEqual:[FSTFieldPath keyFieldPath]] &&
- [document fieldForPath:fieldPath] == nil) {
+ if (fieldPath != FieldPath::KeyFieldPath() && [document fieldForPath:fieldPath] == nil) {
return NO;
}
}
diff --git a/Firestore/Source/Core/FSTSnapshotVersion.h b/Firestore/Source/Core/FSTSnapshotVersion.h
index b72e4a2..8649d40 100644
--- a/Firestore/Source/Core/FSTSnapshotVersion.h
+++ b/Firestore/Source/Core/FSTSnapshotVersion.h
@@ -18,7 +18,7 @@
NS_ASSUME_NONNULL_BEGIN
-@class FSTTimestamp;
+@class FIRTimestamp;
/**
* A version of a document in Firestore. This corresponds to the version timestamp, such as
@@ -30,13 +30,13 @@ NS_ASSUME_NONNULL_BEGIN
+ (instancetype)noVersion;
/** Creates a new version representing the given timestamp. */
-+ (instancetype)versionWithTimestamp:(FSTTimestamp *)timestamp;
++ (instancetype)versionWithTimestamp:(FIRTimestamp *)timestamp;
- (instancetype)init NS_UNAVAILABLE;
- (NSComparisonResult)compare:(FSTSnapshotVersion *)other;
-@property(nonatomic, strong, readonly) FSTTimestamp *timestamp;
+@property(nonatomic, strong, readonly) FIRTimestamp *timestamp;
@end
diff --git a/Firestore/Source/Core/FSTSnapshotVersion.m b/Firestore/Source/Core/FSTSnapshotVersion.mm
index 980ae52..58b2be4 100644
--- a/Firestore/Source/Core/FSTSnapshotVersion.m
+++ b/Firestore/Source/Core/FSTSnapshotVersion.mm
@@ -16,7 +16,7 @@
#import "Firestore/Source/Core/FSTSnapshotVersion.h"
-#import "Firestore/Source/Core/FSTTimestamp.h"
+#import "FIRTimestamp.h"
NS_ASSUME_NONNULL_BEGIN
@@ -26,17 +26,17 @@ NS_ASSUME_NONNULL_BEGIN
static FSTSnapshotVersion *min;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
- FSTTimestamp *timestamp = [[FSTTimestamp alloc] initWithSeconds:0 nanos:0];
+ FIRTimestamp *timestamp = [[FIRTimestamp alloc] initWithSeconds:0 nanoseconds:0];
min = [FSTSnapshotVersion versionWithTimestamp:timestamp];
});
return min;
}
-+ (instancetype)versionWithTimestamp:(FSTTimestamp *)timestamp {
++ (instancetype)versionWithTimestamp:(FIRTimestamp *)timestamp {
return [[FSTSnapshotVersion alloc] initWithTimestamp:timestamp];
}
-- (instancetype)initWithTimestamp:(FSTTimestamp *)timestamp {
+- (instancetype)initWithTimestamp:(FIRTimestamp *)timestamp {
self = [super init];
if (self) {
_timestamp = timestamp;
diff --git a/Firestore/Source/Core/FSTSyncEngine.h b/Firestore/Source/Core/FSTSyncEngine.h
index bb45196..3b28e36 100644
--- a/Firestore/Source/Core/FSTSyncEngine.h
+++ b/Firestore/Source/Core/FSTSyncEngine.h
@@ -19,13 +19,14 @@
#import "Firestore/Source/Core/FSTTypes.h"
#import "Firestore/Source/Remote/FSTRemoteStore.h"
+#include "Firestore/core/src/firebase/firestore/auth/user.h"
+
@class FSTDispatchQueue;
@class FSTLocalStore;
@class FSTMutation;
@class FSTQuery;
@class FSTRemoteEvent;
@class FSTRemoteStore;
-@class FSTUser;
@class FSTViewSnapshot;
NS_ASSUME_NONNULL_BEGIN
@@ -57,7 +58,8 @@ NS_ASSUME_NONNULL_BEGIN
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithLocalStore:(FSTLocalStore *)localStore
remoteStore:(FSTRemoteStore *)remoteStore
- initialUser:(FSTUser *)user NS_DESIGNATED_INITIALIZER;
+ initialUser:(const firebase::firestore::auth::User &)user
+ NS_DESIGNATED_INITIALIZER;
/**
* A delegate to be notified when queries being listened to produce new view snapshots or
@@ -98,7 +100,10 @@ NS_ASSUME_NONNULL_BEGIN
updateBlock:(FSTTransactionBlock)updateBlock
completion:(FSTVoidIDErrorBlock)completion;
-- (void)userDidChange:(FSTUser *)user;
+- (void)userDidChange:(const firebase::firestore::auth::User &)user;
+
+/** Applies an FSTOnlineState change to the sync engine and notifies any views of the change. */
+- (void)applyChangedOnlineState:(FSTOnlineState)onlineState;
@end
diff --git a/Firestore/Source/Core/FSTSyncEngine.m b/Firestore/Source/Core/FSTSyncEngine.mm
index 98658e4..0a4fc94 100644
--- a/Firestore/Source/Core/FSTSyncEngine.m
+++ b/Firestore/Source/Core/FSTSyncEngine.mm
@@ -18,11 +18,13 @@
#import <GRPCClient/GRPCCall.h>
+#include <map>
+#include <set>
+#include <unordered_map>
+
#import "FIRFirestoreErrors.h"
-#import "Firestore/Source/Auth/FSTUser.h"
#import "Firestore/Source/Core/FSTQuery.h"
#import "Firestore/Source/Core/FSTSnapshotVersion.h"
-#import "Firestore/Source/Core/FSTTargetIDGenerator.h"
#import "Firestore/Source/Core/FSTTransaction.h"
#import "Firestore/Source/Core/FSTView.h"
#import "Firestore/Source/Core/FSTViewSnapshot.h"
@@ -33,7 +35,6 @@
#import "Firestore/Source/Local/FSTQueryData.h"
#import "Firestore/Source/Local/FSTReferenceSet.h"
#import "Firestore/Source/Model/FSTDocument.h"
-#import "Firestore/Source/Model/FSTDocumentKey.h"
#import "Firestore/Source/Model/FSTDocumentSet.h"
#import "Firestore/Source/Model/FSTMutationBatch.h"
#import "Firestore/Source/Remote/FSTRemoteEvent.h"
@@ -41,8 +42,22 @@
#import "Firestore/Source/Util/FSTDispatchQueue.h"
#import "Firestore/Source/Util/FSTLogger.h"
+#include "Firestore/core/src/firebase/firestore/auth/user.h"
+#include "Firestore/core/src/firebase/firestore/core/target_id_generator.h"
+#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+
+using firebase::firestore::auth::HashUser;
+using firebase::firestore::auth::User;
+using firebase::firestore::core::TargetIdGenerator;
+using firebase::firestore::model::DocumentKey;
+using firebase::firestore::model::TargetId;
+
NS_ASSUME_NONNULL_BEGIN
+// Limbo documents don't use persistence, and are eagerly GC'd. So, listens for them don't need
+// real sequence numbers.
+static const FSTListenSequenceNumber kIrrelevantSequenceNumber = -1;
+
#pragma mark - FSTQueryView
/**
@@ -115,40 +130,37 @@ NS_ASSUME_NONNULL_BEGIN
@property(nonatomic, strong, readonly)
NSMutableDictionary<NSNumber *, FSTQueryView *> *queryViewsByTarget;
-/**
- * When a document is in limbo, we create a special listen to resolve it. This maps the
- * FSTDocumentKey of each limbo document to the FSTTargetID of the listen resolving it.
- */
-@property(nonatomic, strong, readonly)
- NSMutableDictionary<FSTDocumentKey *, FSTBoxedTargetID *> *limboTargetsByKey;
-
-/** The inverse of limboTargetsByKey, a map of FSTTargetID to the key of the limbo doc. */
-@property(nonatomic, strong, readonly)
- NSMutableDictionary<FSTBoxedTargetID *, FSTDocumentKey *> *limboKeysByTarget;
-
/** Used to track any documents that are currently in limbo. */
@property(nonatomic, strong, readonly) FSTReferenceSet *limboDocumentRefs;
/** The garbage collector used to collect documents that are no longer in limbo. */
@property(nonatomic, strong, readonly) FSTEagerGarbageCollector *limboCollector;
-/** Stores user completion blocks, indexed by user and FSTBatchID. */
-@property(nonatomic, strong)
- NSMutableDictionary<FSTUser *, NSMutableDictionary<NSNumber *, FSTVoidErrorBlock> *>
- *mutationCompletionBlocks;
+@end
-/** Used for creating the FSTTargetIDs for the listens used to resolve limbo documents. */
-@property(nonatomic, strong, readonly) FSTTargetIDGenerator *targetIdGenerator;
+@implementation FSTSyncEngine {
+ /** Used for creating the FSTTargetIDs for the listens used to resolve limbo documents. */
+ TargetIdGenerator _targetIdGenerator;
-@property(nonatomic, strong) FSTUser *currentUser;
+ /** Stores user completion blocks, indexed by user and FSTBatchID. */
+ std::unordered_map<User, NSMutableDictionary<NSNumber *, FSTVoidErrorBlock> *, HashUser>
+ _mutationCompletionBlocks;
-@end
+ /**
+ * When a document is in limbo, we create a special listen to resolve it. This maps the
+ * DocumentKey of each limbo document to the TargetId of the listen resolving it.
+ */
+ std::map<DocumentKey, TargetId> _limboTargetsByKey;
-@implementation FSTSyncEngine
+ /** The inverse of _limboTargetsByKey, a map of TargetId to the key of the limbo doc. */
+ std::map<TargetId, DocumentKey> _limboKeysByTarget;
+
+ User _currentUser;
+}
- (instancetype)initWithLocalStore:(FSTLocalStore *)localStore
remoteStore:(FSTRemoteStore *)remoteStore
- initialUser:(FSTUser *)initialUser {
+ initialUser:(const User &)initialUser {
if (self = [super init]) {
_localStore = localStore;
_remoteStore = remoteStore;
@@ -156,14 +168,11 @@ NS_ASSUME_NONNULL_BEGIN
_queryViewsByQuery = [NSMutableDictionary dictionary];
_queryViewsByTarget = [NSMutableDictionary dictionary];
- _limboTargetsByKey = [NSMutableDictionary dictionary];
- _limboKeysByTarget = [NSMutableDictionary dictionary];
_limboCollector = [[FSTEagerGarbageCollector alloc] init];
_limboDocumentRefs = [[FSTReferenceSet alloc] init];
[_limboCollector addGarbageSource:_limboDocumentRefs];
- _mutationCompletionBlocks = [NSMutableDictionary dictionary];
- _targetIdGenerator = [FSTTargetIDGenerator generatorForSyncEngineStartingAfterID:0];
+ _targetIdGenerator = TargetIdGenerator::SyncEngineTargetIdGenerator(0);
_currentUser = initialUser;
}
return self;
@@ -220,10 +229,10 @@ NS_ASSUME_NONNULL_BEGIN
- (void)addMutationCompletionBlock:(FSTVoidErrorBlock)completion batchID:(FSTBatchID)batchID {
NSMutableDictionary<NSNumber *, FSTVoidErrorBlock> *completionBlocks =
- self.mutationCompletionBlocks[self.currentUser];
+ _mutationCompletionBlocks[_currentUser];
if (!completionBlocks) {
completionBlocks = [NSMutableDictionary dictionary];
- self.mutationCompletionBlocks[self.currentUser] = completionBlocks;
+ _mutationCompletionBlocks[_currentUser] = completionBlocks;
}
[completionBlocks setObject:completion forKey:@(batchID)];
}
@@ -287,9 +296,13 @@ NS_ASSUME_NONNULL_BEGIN
[remoteEvent.targetChanges enumerateKeysAndObjectsUsingBlock:^(
FSTBoxedTargetID *_Nonnull targetID,
FSTTargetChange *_Nonnull targetChange, BOOL *_Nonnull stop) {
- FSTDocumentKey *limboKey = self.limboKeysByTarget[targetID];
- if (limboKey && targetChange.currentStatusUpdate == FSTCurrentStatusUpdateMarkCurrent &&
- remoteEvent.documentUpdates[limboKey] == nil) {
+ const auto iter = self->_limboKeysByTarget.find([targetID intValue]);
+ if (iter == self->_limboKeysByTarget.end()) {
+ return;
+ }
+ const DocumentKey &limboKey = iter->second;
+ if (targetChange.currentStatusUpdate == FSTCurrentStatusUpdateMarkCurrent &&
+ remoteEvent.documentUpdates.find(limboKey) == remoteEvent.documentUpdates.end()) {
// When listening to a query the server responds with a snapshot containing documents
// matching the query and a current marker telling us we're now in sync. It's possible for
// these to arrive as separate remote events or as a single remote event. For a document
@@ -318,15 +331,31 @@ NS_ASSUME_NONNULL_BEGIN
[self emitNewSnapshotsWithChanges:changes remoteEvent:remoteEvent];
}
-- (void)rejectListenWithTargetID:(FSTBoxedTargetID *)targetID error:(NSError *)error {
+- (void)applyChangedOnlineState:(FSTOnlineState)onlineState {
+ NSMutableArray<FSTViewSnapshot *> *newViewSnapshots = [NSMutableArray array];
+ [self.queryViewsByQuery
+ enumerateKeysAndObjectsUsingBlock:^(FSTQuery *query, FSTQueryView *queryView, BOOL *stop) {
+ FSTViewChange *viewChange = [queryView.view applyChangedOnlineState:onlineState];
+ FSTAssert(viewChange.limboChanges.count == 0,
+ @"OnlineState should not affect limbo documents.");
+ if (viewChange.snapshot) {
+ [newViewSnapshots addObject:viewChange.snapshot];
+ }
+ }];
+
+ [self.delegate handleViewSnapshots:newViewSnapshots];
+}
+
+- (void)rejectListenWithTargetID:(const TargetId)targetID error:(NSError *)error {
[self assertDelegateExistsForSelector:_cmd];
- FSTDocumentKey *limboKey = self.limboKeysByTarget[targetID];
- if (limboKey) {
+ const auto iter = _limboKeysByTarget.find(targetID);
+ if (iter != _limboKeysByTarget.end()) {
+ const DocumentKey limboKey = iter->second;
// Since this query failed, we won't want to manually unlisten to it.
// So go ahead and remove it from bookkeeping.
- [self.limboTargetsByKey removeObjectForKey:limboKey];
- [self.limboKeysByTarget removeObjectForKey:targetID];
+ _limboTargetsByKey.erase(limboKey);
+ _limboKeysByTarget.erase(targetID);
// TODO(dimond): Retry on transient errors?
@@ -337,15 +366,13 @@ NS_ASSUME_NONNULL_BEGIN
[NSMutableDictionary dictionary];
FSTDeletedDocument *doc =
[FSTDeletedDocument documentWithKey:limboKey version:[FSTSnapshotVersion noVersion]];
- NSMutableDictionary<FSTDocumentKey *, FSTMaybeDocument *> *docUpdate =
- [NSMutableDictionary dictionaryWithObject:doc forKey:limboKey];
FSTRemoteEvent *event = [FSTRemoteEvent eventWithSnapshotVersion:[FSTSnapshotVersion noVersion]
targetChanges:targetChanges
- documentUpdates:docUpdate];
+ documentUpdates:{{limboKey, doc}}];
[self applyRemoteEvent:event];
} else {
- FSTQueryView *queryView = self.queryViewsByTarget[targetID];
- FSTAssert(queryView, @"Unknown targetId: %@", targetID);
+ FSTQueryView *queryView = self.queryViewsByTarget[@(targetID)];
+ FSTAssert(queryView, @"Unknown targetId: %d", targetID);
[self.localStore releaseQuery:queryView.query];
[self removeAndCleanupQuery:queryView];
[self.delegate handleError:error forQuery:queryView.query];
@@ -378,7 +405,7 @@ NS_ASSUME_NONNULL_BEGIN
- (void)processUserCallbacksForBatchID:(FSTBatchID)batchID error:(NSError *_Nullable)error {
NSMutableDictionary<NSNumber *, FSTVoidErrorBlock> *completionBlocks =
- self.mutationCompletionBlocks[self.currentUser];
+ _mutationCompletionBlocks[_currentUser];
// NOTE: Mutations restored from persistence won't have completion blocks, so it's okay for
// this (or the completion below) to be nil.
@@ -455,7 +482,7 @@ NS_ASSUME_NONNULL_BEGIN
break;
case FSTLimboDocumentChangeTypeRemoved:
- FSTLog(@"Document no longer in limbo: %@", limboChange.key);
+ FSTLog(@"Document no longer in limbo: %s", limboChange.key.ToString().c_str());
[self.limboDocumentRefs removeReferenceToKey:limboChange.key forID:targetID];
break;
@@ -467,45 +494,46 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)trackLimboChange:(FSTLimboDocumentChange *)limboChange {
- FSTDocumentKey *key = limboChange.key;
+ DocumentKey key{limboChange.key};
- if (!self.limboTargetsByKey[key]) {
- FSTLog(@"New document in limbo: %@", key);
- FSTTargetID limboTargetID = [self.targetIdGenerator nextID];
- FSTQuery *query = [FSTQuery queryWithPath:key.path];
+ if (_limboTargetsByKey.find(key) == _limboTargetsByKey.end()) {
+ FSTLog(@"New document in limbo: %s", key.ToString().c_str());
+ TargetId limboTargetID = _targetIdGenerator.NextId();
+ FSTQuery *query = [FSTQuery queryWithPath:key.path()];
FSTQueryData *queryData = [[FSTQueryData alloc] initWithQuery:query
targetID:limboTargetID
+ listenSequenceNumber:kIrrelevantSequenceNumber
purpose:FSTQueryPurposeLimboResolution];
- self.limboKeysByTarget[@(limboTargetID)] = key;
+ _limboKeysByTarget[limboTargetID] = key;
[self.remoteStore listenToTargetWithQueryData:queryData];
- self.limboTargetsByKey[key] = @(limboTargetID);
+ _limboTargetsByKey[key] = limboTargetID;
}
}
/** Garbage collect the limbo documents that we no longer need to track. */
- (void)garbageCollectLimboDocuments {
- NSSet<FSTDocumentKey *> *garbage = [self.limboCollector collectGarbage];
- for (FSTDocumentKey *key in garbage) {
- FSTBoxedTargetID *limboTarget = self.limboTargetsByKey[key];
- if (!limboTarget) {
+ const std::set<DocumentKey> garbage = [self.limboCollector collectGarbage];
+ for (const DocumentKey &key : garbage) {
+ const auto iter = _limboTargetsByKey.find(key);
+ if (iter == _limboTargetsByKey.end()) {
// This target already got removed, because the query failed.
return;
}
- FSTTargetID limboTargetID = limboTarget.intValue;
+ TargetId limboTargetID = iter->second;
[self.remoteStore stopListeningToTargetID:limboTargetID];
- [self.limboTargetsByKey removeObjectForKey:key];
- [self.limboKeysByTarget removeObjectForKey:limboTarget];
+ _limboTargetsByKey.erase(key);
+ _limboKeysByTarget.erase(limboTargetID);
}
}
// Used for testing
-- (NSDictionary<FSTDocumentKey *, FSTBoxedTargetID *> *)currentLimboDocuments {
+- (std::map<DocumentKey, TargetId>)currentLimboDocuments {
// Return defensive copy
- return [self.limboTargetsByKey copy];
+ return _limboTargetsByKey;
}
-- (void)userDidChange:(FSTUser *)user {
- self.currentUser = user;
+- (void)userDidChange:(const User &)user {
+ _currentUser = user;
// Notify local store and emit any resulting events from swapping out the mutation queue.
FSTMaybeDocumentDictionary *changes = [self.localStore userDidChange:user];
diff --git a/Firestore/Source/Core/FSTTargetIDGenerator.h b/Firestore/Source/Core/FSTTargetIDGenerator.h
deleted file mode 100644
index 0b230ae..0000000
--- a/Firestore/Source/Core/FSTTargetIDGenerator.h
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright 2017 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/Foundation.h>
-
-#import "Firestore/Source/Core/FSTTypes.h"
-
-NS_ASSUME_NONNULL_BEGIN
-
-/**
- * FSTTargetIDGenerator generates monotonically increasing integer IDs. There are separate
- * generators for different scopes. While these generators will operate independently of each
- * other, they are scoped, such that no two generators will ever produce the same ID. This is
- * useful, because sometimes the backend may group IDs from separate parts of the client into the
- * same ID space.
- */
-@interface FSTTargetIDGenerator : NSObject
-
-/**
- * Creates and returns the FSTTargetIDGenerator for the local store.
- *
- * @param after An ID to start at. Every call to nextID will return an ID > @a after.
- * @return A shared instance of FSTTargetIDGenerator.
- */
-+ (instancetype)generatorForLocalStoreStartingAfterID:(FSTTargetID)after;
-
-/**
- * Creates and returns the FSTTargetIDGenerator for the sync engine.
- *
- * @param after An ID to start at. Every call to nextID will return an ID > @a after.
- * @return A shared instance of FSTTargetIDGenerator.
- */
-+ (instancetype)generatorForSyncEngineStartingAfterID:(FSTTargetID)after;
-
-- (id)init __attribute__((unavailable("Use a static constructor method.")));
-
-/** Returns the next ID in the sequence. */
-- (FSTTargetID)nextID;
-
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/Core/FSTTargetIDGenerator.m b/Firestore/Source/Core/FSTTargetIDGenerator.m
deleted file mode 100644
index 58092ec..0000000
--- a/Firestore/Source/Core/FSTTargetIDGenerator.m
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Copyright 2017 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 "Firestore/Source/Core/FSTTargetIDGenerator.h"
-
-#import <libkern/OSAtomic.h>
-
-NS_ASSUME_NONNULL_BEGIN
-
-#pragma mark - FSTTargetIDGenerator
-
-static const int kReservedBits = 1;
-
-/** FSTTargetIDGeneratorID is the set of all valid generators. */
-typedef NS_ENUM(NSInteger, FSTTargetIDGeneratorID) {
- FSTTargetIDGeneratorIDLocalStore = 0,
- FSTTargetIDGeneratorIDSyncEngine = 1
-};
-
-@interface FSTTargetIDGenerator () {
- // This is volatile so it can be used with OSAtomicAdd32.
- volatile FSTTargetID _previousID;
-}
-
-/**
- * Initializes the generator.
- *
- * @param generatorID A unique ID indicating which generator this is.
- * @param after Every call to nextID will return a number > @a after.
- */
-- (instancetype)initWithGeneratorID:(FSTTargetIDGeneratorID)generatorID
- startingAfterID:(FSTTargetID)after NS_DESIGNATED_INITIALIZER;
-
-// This is typed as FSTTargetID because we need to do bitwise operations with them together.
-@property(nonatomic, assign) FSTTargetID generatorID;
-@end
-
-@implementation FSTTargetIDGenerator
-
-#pragma mark - Constructors
-
-- (instancetype)initWithGeneratorID:(FSTTargetIDGeneratorID)generatorID
- startingAfterID:(FSTTargetID)after {
- self = [super init];
- if (self) {
- _generatorID = generatorID;
-
- // Replace the generator part of |after| with this generator's ID.
- FSTTargetID afterWithoutGenerator = (after >> kReservedBits) << kReservedBits;
- FSTTargetID afterGenerator = after - afterWithoutGenerator;
- if (afterGenerator >= _generatorID) {
- // For example, if:
- // self.generatorID = 0b0000
- // after = 0b1011
- // afterGenerator = 0b0001
- // Then:
- // previous = 0b1010
- // next = 0b1100
- _previousID = afterWithoutGenerator | self.generatorID;
- } else {
- // For example, if:
- // self.generatorID = 0b0001
- // after = 0b1010
- // afterGenerator = 0b0000
- // Then:
- // previous = 0b1001
- // next = 0b1011
- _previousID = (afterWithoutGenerator | self.generatorID) - (1 << kReservedBits);
- }
- }
- return self;
-}
-
-+ (instancetype)generatorForLocalStoreStartingAfterID:(FSTTargetID)after {
- return [[FSTTargetIDGenerator alloc] initWithGeneratorID:FSTTargetIDGeneratorIDLocalStore
- startingAfterID:after];
-}
-
-+ (instancetype)generatorForSyncEngineStartingAfterID:(FSTTargetID)after {
- return [[FSTTargetIDGenerator alloc] initWithGeneratorID:FSTTargetIDGeneratorIDSyncEngine
- startingAfterID:after];
-}
-
-#pragma mark - Public methods
-
-- (FSTTargetID)nextID {
- return OSAtomicAdd32(1 << kReservedBits, &_previousID);
-}
-
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/Core/FSTTransaction.h b/Firestore/Source/Core/FSTTransaction.h
index d4f3d8d..ab59bde 100644
--- a/Firestore/Source/Core/FSTTransaction.h
+++ b/Firestore/Source/Core/FSTTransaction.h
@@ -16,13 +16,13 @@
#import <Foundation/Foundation.h>
+#include <vector>
+
#import "Firestore/Source/Core/FSTTypes.h"
-@class FIRSetOptions;
+#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+
@class FSTDatastore;
-@class FSTDocumentKey;
-@class FSTFieldMask;
-@class FSTFieldTransform;
@class FSTMaybeDocument;
@class FSTObjectValue;
@class FSTParsedSetData;
@@ -42,25 +42,27 @@ NS_ASSUME_NONNULL_BEGIN
* Takes a set of keys and asynchronously attempts to fetch all the documents from the backend,
* ignoring any local changes.
*/
-- (void)lookupDocumentsForKeys:(NSArray<FSTDocumentKey *> *)keys
+- (void)lookupDocumentsForKeys:(const std::vector<firebase::firestore::model::DocumentKey> &)keys
completion:(FSTVoidMaybeDocumentArrayErrorBlock)completion;
/**
* Stores mutation for the given key and set data, to be committed when commitWithCompletion is
* called.
*/
-- (void)setData:(FSTParsedSetData *)data forDocument:(FSTDocumentKey *)key;
+- (void)setData:(FSTParsedSetData *)data
+ forDocument:(const firebase::firestore::model::DocumentKey &)key;
/**
* Stores mutations for the given key and update data, to be committed when commitWithCompletion
* is called.
*/
-- (void)updateData:(FSTParsedUpdateData *)data forDocument:(FSTDocumentKey *)key;
+- (void)updateData:(FSTParsedUpdateData *)data
+ forDocument:(const firebase::firestore::model::DocumentKey &)key;
/**
* Stores a delete mutation for the given key, to be committed when commitWithCompletion is called.
*/
-- (void)deleteDocument:(FSTDocumentKey *)key;
+- (void)deleteDocument:(const firebase::firestore::model::DocumentKey &)key;
/**
* Attempts to commit the mutations set on this transaction. Calls the given completion block when
diff --git a/Firestore/Source/Core/FSTTransaction.m b/Firestore/Source/Core/FSTTransaction.mm
index c4c5f27..4aabd5a 100644
--- a/Firestore/Source/Core/FSTTransaction.m
+++ b/Firestore/Source/Core/FSTTransaction.mm
@@ -18,26 +18,31 @@
#import <GRPCClient/GRPCCall.h>
+#include <map>
+#include <vector>
+
#import "FIRFirestoreErrors.h"
-#import "FIRSetOptions.h"
#import "Firestore/Source/API/FSTUserDataConverter.h"
#import "Firestore/Source/Core/FSTSnapshotVersion.h"
#import "Firestore/Source/Model/FSTDocument.h"
-#import "Firestore/Source/Model/FSTDocumentKey.h"
#import "Firestore/Source/Model/FSTDocumentKeySet.h"
#import "Firestore/Source/Model/FSTMutation.h"
#import "Firestore/Source/Remote/FSTDatastore.h"
#import "Firestore/Source/Util/FSTAssert.h"
#import "Firestore/Source/Util/FSTUsageValidation.h"
+#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+#include "Firestore/core/src/firebase/firestore/model/precondition.h"
+
+using firebase::firestore::model::DocumentKey;
+using firebase::firestore::model::Precondition;
+
NS_ASSUME_NONNULL_BEGIN
#pragma mark - FSTTransaction
@interface FSTTransaction ()
@property(nonatomic, strong, readonly) FSTDatastore *datastore;
-@property(nonatomic, strong, readonly)
- NSMutableDictionary<FSTDocumentKey *, FSTSnapshotVersion *> *readVersions;
@property(nonatomic, strong, readonly) NSMutableArray *mutations;
@property(nonatomic, assign) BOOL commitCalled;
/**
@@ -47,7 +52,9 @@ NS_ASSUME_NONNULL_BEGIN
@property(nonatomic, strong, nullable) NSError *lastWriteError;
@end
-@implementation FSTTransaction
+@implementation FSTTransaction {
+ std::map<DocumentKey, FSTSnapshotVersion *> _readVersions;
+}
+ (instancetype)transactionWithDatastore:(FSTDatastore *)datastore {
return [[FSTTransaction alloc] initWithDatastore:datastore];
@@ -57,7 +64,6 @@ NS_ASSUME_NONNULL_BEGIN
self = [super init];
if (self) {
_datastore = datastore;
- _readVersions = [NSMutableDictionary dictionary];
_mutations = [NSMutableArray array];
_commitCalled = NO;
}
@@ -79,8 +85,10 @@ NS_ASSUME_NONNULL_BEGIN
// when writing.
docVersion = [FSTSnapshotVersion noVersion];
}
- FSTSnapshotVersion *existingVersion = self.readVersions[doc.key];
- if (existingVersion) {
+ if (_readVersions.find(doc.key) == _readVersions.end()) {
+ _readVersions[doc.key] = docVersion;
+ return YES;
+ } else {
if (error) {
*error =
[NSError errorWithDomain:FIRFirestoreErrorDomain
@@ -91,35 +99,32 @@ NS_ASSUME_NONNULL_BEGIN
}];
}
return NO;
- } else {
- self.readVersions[doc.key] = docVersion;
- return YES;
}
}
-- (void)lookupDocumentsForKeys:(NSArray<FSTDocumentKey *> *)keys
+- (void)lookupDocumentsForKeys:(const std::vector<DocumentKey> &)keys
completion:(FSTVoidMaybeDocumentArrayErrorBlock)completion {
[self ensureCommitNotCalled];
if (self.mutations.count) {
FSTThrowInvalidUsage(@"FIRIllegalStateException",
@"All reads in a transaction must be done before any writes.");
}
- [self.datastore
- lookupDocuments:keys
- completion:^(NSArray<FSTDocument *> *_Nullable documents, NSError *_Nullable error) {
- if (error) {
- completion(nil, error);
- return;
- }
- for (FSTMaybeDocument *doc in documents) {
- NSError *recordError = nil;
- if (![self recordVersionForDocument:doc error:&recordError]) {
- completion(nil, recordError);
- return;
- }
- }
- completion(documents, nil);
- }];
+ [self.datastore lookupDocuments:keys
+ completion:^(NSArray<FSTMaybeDocument *> *_Nullable documents,
+ NSError *_Nullable error) {
+ if (error) {
+ completion(nil, error);
+ return;
+ }
+ for (FSTMaybeDocument *doc in documents) {
+ NSError *recordError = nil;
+ if (![self recordVersionForDocument:doc error:&recordError]) {
+ completion(nil, recordError);
+ return;
+ }
+ }
+ completion(documents, nil);
+ }];
}
/** Stores mutations to be written when commitWithCompletion is called. */
@@ -132,23 +137,30 @@ NS_ASSUME_NONNULL_BEGIN
* Returns version of this doc when it was read in this transaction as a precondition, or no
* precondition if it was not read.
*/
-- (FSTPrecondition *)preconditionForDocumentKey:(FSTDocumentKey *)key {
- FSTSnapshotVersion *_Nullable snapshotVersion = self.readVersions[key];
- if (snapshotVersion) {
- return [FSTPrecondition preconditionWithUpdateTime:snapshotVersion];
+- (Precondition)preconditionForDocumentKey:(const DocumentKey &)key {
+ const auto iter = _readVersions.find(key);
+ if (iter == _readVersions.end()) {
+ return Precondition::None();
} else {
- return [FSTPrecondition none];
+ return Precondition::UpdateTime(iter->second);
}
}
/**
* Returns the precondition for a document if the operation is an update, based on the provided
- * UpdateOptions. Will return nil if an error occurred, in which case it sets the error parameter.
+ * UpdateOptions. Will return none precondition if an error occurred, in which case it sets the
+ * error parameter.
*/
-- (nullable FSTPrecondition *)preconditionForUpdateWithDocumentKey:(FSTDocumentKey *)key
- error:(NSError **)error {
- FSTSnapshotVersion *_Nullable version = self.readVersions[key];
- if (version && [version isEqual:[FSTSnapshotVersion noVersion]]) {
+- (Precondition)preconditionForUpdateWithDocumentKey:(const DocumentKey &)key
+ error:(NSError **)error {
+ const auto iter = _readVersions.find(key);
+ if (iter == _readVersions.end()) {
+ // Document was not read, so we just use the preconditions for an update.
+ return Precondition::Exists(true);
+ }
+
+ FSTSnapshotVersion *version = iter->second;
+ if ([version isEqual:[FSTSnapshotVersion noVersion]]) {
// The document was read, but doesn't exist.
// Return an error because the precondition is impossible
if (error) {
@@ -159,40 +171,36 @@ NS_ASSUME_NONNULL_BEGIN
NSLocalizedDescriptionKey : @"Can't update a document that doesn't exist."
}];
}
- return nil;
- } else if (version) {
- // Document exists, just base precondition on document update time.
- return [FSTPrecondition preconditionWithUpdateTime:version];
+ return Precondition::None();
} else {
- // Document was not read, so we just use the preconditions for an update.
- return [FSTPrecondition preconditionWithExists:YES];
+ // Document exists, just base precondition on document update time.
+ return Precondition::UpdateTime(version);
}
}
-- (void)setData:(FSTParsedSetData *)data forDocument:(FSTDocumentKey *)key {
+- (void)setData:(FSTParsedSetData *)data forDocument:(const DocumentKey &)key {
[self writeMutations:[data mutationsWithKey:key
precondition:[self preconditionForDocumentKey:key]]];
}
-- (void)updateData:(FSTParsedUpdateData *)data forDocument:(FSTDocumentKey *)key {
+- (void)updateData:(FSTParsedUpdateData *)data forDocument:(const DocumentKey &)key {
NSError *error = nil;
- FSTPrecondition *_Nullable precondition =
- [self preconditionForUpdateWithDocumentKey:key error:&error];
- if (precondition) {
- [self writeMutations:[data mutationsWithKey:key precondition:precondition]];
- } else {
+ const Precondition precondition = [self preconditionForUpdateWithDocumentKey:key error:&error];
+ if (precondition.IsNone()) {
FSTAssert(error, @"Got nil precondition, but error was not set");
self.lastWriteError = error;
+ } else {
+ [self writeMutations:[data mutationsWithKey:key precondition:precondition]];
}
}
-- (void)deleteDocument:(FSTDocumentKey *)key {
+- (void)deleteDocument:(const DocumentKey &)key {
[self writeMutations:@[ [[FSTDeleteMutation alloc]
initWithKey:key
precondition:[self preconditionForDocumentKey:key]] ]];
// Since the delete will be applied before all following writes, we need to ensure that the
- // precondition for the next write will be exists: false.
- self.readVersions[key] = [FSTSnapshotVersion noVersion];
+ // precondition for the next write will be exists without timestamp.
+ _readVersions[key] = [FSTSnapshotVersion noVersion];
}
- (void)commitWithCompletion:(FSTVoidErrorBlock)completion {
@@ -207,11 +215,10 @@ NS_ASSUME_NONNULL_BEGIN
}
// Make a list of read documents that haven't been written.
- __block FSTDocumentKeySet *unwritten = [FSTDocumentKeySet keySet];
- [self.readVersions enumerateKeysAndObjectsUsingBlock:^(FSTDocumentKey *key,
- FSTSnapshotVersion *version, BOOL *stop) {
- unwritten = [unwritten setByAddingObject:key];
- }];
+ FSTDocumentKeySet *unwritten = [FSTDocumentKeySet keySet];
+ for (const auto &kv : _readVersions) {
+ unwritten = [unwritten setByAddingObject:kv.first];
+ };
// For each mutation, note that the doc was written.
for (FSTMutation *mutation in self.mutations) {
unwritten = [unwritten setByRemovingObject:mutation.key];
diff --git a/Firestore/Source/Core/FSTTypes.h b/Firestore/Source/Core/FSTTypes.h
index c10f1bf..f15f463 100644
--- a/Firestore/Source/Core/FSTTypes.h
+++ b/Firestore/Source/Core/FSTTypes.h
@@ -16,15 +16,19 @@
#import <Foundation/Foundation.h>
+#include "Firestore/core/src/firebase/firestore/model/types.h"
+
NS_ASSUME_NONNULL_BEGIN
@class FSTMaybeDocument;
@class FSTTransaction;
/** FSTBatchID is a locally assigned ID for a batch of mutations that have been applied. */
-typedef int32_t FSTBatchID;
+typedef firebase::firestore::model::BatchId FSTBatchID;
+
+typedef firebase::firestore::model::TargetId FSTTargetID;
-typedef int32_t FSTTargetID;
+typedef int64_t FSTListenSequenceNumber;
typedef NSNumber FSTBoxedTargetID;
@@ -63,12 +67,18 @@ typedef void (^FSTVoidMaybeDocumentArrayErrorBlock)(
typedef void (^FSTTransactionBlock)(FSTTransaction *transaction,
void (^completion)(id _Nullable, NSError *_Nullable));
-/** Describes the online state of the Firestore client */
+/**
+ * Describes the online state of the Firestore client. Note that this does not indicate whether
+ * or not the remote store is trying to connect or not. This is primarily used by the View /
+ * EventManager code to change their behavior while offline (e.g. get() calls shouldn't wait for
+ * data from the server and snapshot events should set metadata.isFromCache=true).
+ */
typedef NS_ENUM(NSUInteger, FSTOnlineState) {
/**
* The Firestore client is in an unknown online state. This means the client is either not
- * actively trying to establish a connection or it was previously in an unknown state and is
- * trying to establish a connection.
+ * actively trying to establish a connection or it is currently trying to establish a connection,
+ * but it has not succeeded or failed yet. Higher-level components should not operate in
+ * offline mode.
*/
FSTOnlineStateUnknown,
@@ -77,14 +87,14 @@ typedef NS_ENUM(NSUInteger, FSTOnlineState) {
* successful connection and there has been at least one successful message received from the
* backends.
*/
- FSTOnlineStateHealthy,
+ FSTOnlineStateOnline,
/**
- * The client has tried to establish a connection but has failed.
- * This state is reached after either a connection attempt failed or a healthy stream was closed
- * for unexpected reasons.
+ * The client is either trying to establish a connection but failing, or it has been explicitly
+ * marked offline via a call to `disableNetwork`. Higher-level components should operate in
+ * offline mode.
*/
- FSTOnlineStateFailed
+ FSTOnlineStateOffline
};
NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/Core/FSTView.h b/Firestore/Source/Core/FSTView.h
index ed230a3..38f57fc 100644
--- a/Firestore/Source/Core/FSTView.h
+++ b/Firestore/Source/Core/FSTView.h
@@ -16,10 +16,12 @@
#import <Foundation/Foundation.h>
+#import "Firestore/Source/Core/FSTTypes.h"
#import "Firestore/Source/Model/FSTDocumentDictionary.h"
#import "Firestore/Source/Model/FSTDocumentKeySet.h"
-@class FSTDocumentKey;
+#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+
@class FSTDocumentSet;
@class FSTDocumentViewChangeSet;
@class FSTMaybeDocument;
@@ -62,12 +64,14 @@ typedef NS_ENUM(NSInteger, FSTLimboDocumentChangeType) {
// A change to a particular document wrt to whether it is in "limbo".
@interface FSTLimboDocumentChange : NSObject
-+ (instancetype)changeWithType:(FSTLimboDocumentChangeType)type key:(FSTDocumentKey *)key;
++ (instancetype)changeWithType:(FSTLimboDocumentChangeType)type
+ key:(firebase::firestore::model::DocumentKey)key;
- (id)init __attribute__((unavailable("Use a static constructor method.")));
+- (const firebase::firestore::model::DocumentKey &)key;
+
@property(nonatomic, assign, readonly) FSTLimboDocumentChangeType type;
-@property(nonatomic, strong, readonly) FSTDocumentKey *key;
@end
#pragma mark - FSTViewChange
@@ -138,6 +142,12 @@ typedef NS_ENUM(NSInteger, FSTLimboDocumentChangeType) {
- (FSTViewChange *)applyChangesToDocuments:(FSTViewDocumentChanges *)docChanges
targetChange:(nullable FSTTargetChange *)targetChange;
+/**
+ * Applies an FSTOnlineState change to the view, potentially generating an FSTViewChange if the
+ * view's syncState changes as a result.
+ */
+- (FSTViewChange *)applyChangedOnlineState:(FSTOnlineState)onlineState;
+
@end
NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/Core/FSTView.m b/Firestore/Source/Core/FSTView.mm
index 9b44bf4..d87951a 100644
--- a/Firestore/Source/Core/FSTView.m
+++ b/Firestore/Source/Core/FSTView.mm
@@ -16,15 +16,20 @@
#import "Firestore/Source/Core/FSTView.h"
+#include <utility>
+
#import "Firestore/Source/Core/FSTQuery.h"
#import "Firestore/Source/Core/FSTViewSnapshot.h"
#import "Firestore/Source/Model/FSTDocument.h"
-#import "Firestore/Source/Model/FSTDocumentKey.h"
#import "Firestore/Source/Model/FSTDocumentSet.h"
#import "Firestore/Source/Model/FSTFieldValue.h"
#import "Firestore/Source/Remote/FSTRemoteEvent.h"
#import "Firestore/Source/Util/FSTAssert.h"
+#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+
+using firebase::firestore::model::DocumentKey;
+
NS_ASSUME_NONNULL_BEGIN
#pragma mark - FSTViewDocumentChanges
@@ -61,28 +66,34 @@ NS_ASSUME_NONNULL_BEGIN
@interface FSTLimboDocumentChange ()
-+ (instancetype)changeWithType:(FSTLimboDocumentChangeType)type key:(FSTDocumentKey *)key;
++ (instancetype)changeWithType:(FSTLimboDocumentChangeType)type key:(DocumentKey)key;
- (instancetype)initWithType:(FSTLimboDocumentChangeType)type
- key:(FSTDocumentKey *)key NS_DESIGNATED_INITIALIZER;
+ key:(DocumentKey)key NS_DESIGNATED_INITIALIZER;
@end
-@implementation FSTLimboDocumentChange
+@implementation FSTLimboDocumentChange {
+ DocumentKey _key;
+}
-+ (instancetype)changeWithType:(FSTLimboDocumentChangeType)type key:(FSTDocumentKey *)key {
- return [[FSTLimboDocumentChange alloc] initWithType:type key:key];
++ (instancetype)changeWithType:(FSTLimboDocumentChangeType)type key:(DocumentKey)key {
+ return [[FSTLimboDocumentChange alloc] initWithType:type key:std::move(key)];
}
-- (instancetype)initWithType:(FSTLimboDocumentChangeType)type key:(FSTDocumentKey *)key {
+- (instancetype)initWithType:(FSTLimboDocumentChangeType)type key:(DocumentKey)key {
self = [super init];
if (self) {
_type = type;
- _key = key;
+ _key = std::move(key);
}
return self;
}
+- (const DocumentKey &)key {
+ return _key;
+}
+
- (BOOL)isEqual:(id)other {
if (self == other) {
return YES;
@@ -94,6 +105,12 @@ NS_ASSUME_NONNULL_BEGIN
return self.type == otherChange.type && [self.key isEqual:otherChange.key];
}
+- (NSUInteger)hash {
+ NSUInteger hash = self.type;
+ hash = hash * 31u + [self.key hash];
+ return hash;
+}
+
@end
#pragma mark - FSTViewChange
@@ -210,8 +227,8 @@ static NSComparisonResult FSTCompareDocumentViewChangeTypes(FSTDocumentViewChang
newDoc = (FSTDocument *)maybeNewDoc;
}
if (newDoc) {
- FSTAssert([key isEqual:newDoc.key], @"Mismatching key in document changes: %@ != %@", key,
- newDoc.key);
+ FSTAssert([key isEqual:newDoc.key], @"Mismatching key in document changes: %@ != %s", key,
+ newDoc.key.ToString().c_str());
if (![self.query matchesDocument:newDoc]) {
newDoc = nil;
}
@@ -306,8 +323,8 @@ static NSComparisonResult FSTCompareDocumentViewChangeTypes(FSTDocumentViewChang
}
return self.query.comparator(c1.document, c2.document);
}];
-
- NSArray<FSTLimboDocumentChange *> *limboChanges = [self applyTargetChange:targetChange];
+ [self applyTargetChange:targetChange];
+ NSArray<FSTLimboDocumentChange *> *limboChanges = [self updateLimboDocuments];
BOOL synced = self.limboDocuments.count == 0 && self.isCurrent;
FSTSyncState newSyncState = synced ? FSTSyncStateSynced : FSTSyncStateLocal;
BOOL syncStateChanged = newSyncState != self.syncState;
@@ -330,10 +347,28 @@ static NSComparisonResult FSTCompareDocumentViewChangeTypes(FSTDocumentViewChang
}
}
+- (FSTViewChange *)applyChangedOnlineState:(FSTOnlineState)onlineState {
+ if (self.isCurrent && onlineState == FSTOnlineStateOffline) {
+ // If we're offline, set `current` to NO and then call applyChanges to refresh our syncState
+ // and generate an FSTViewChange as appropriate. We are guaranteed to get a new FSTTargetChange
+ // that sets `current` back to YES once the client is back online.
+ self.current = NO;
+ return
+ [self applyChangesToDocuments:[[FSTViewDocumentChanges alloc]
+ initWithDocumentSet:self.documentSet
+ changeSet:[FSTDocumentViewChangeSet changeSet]
+ needsRefill:NO
+ mutatedKeys:self.mutatedKeys]];
+ } else {
+ // No effect, just return a no-op FSTViewChange.
+ return [[FSTViewChange alloc] initWithSnapshot:nil limboChanges:@[]];
+ }
+}
+
#pragma mark - Private methods
/** Returns whether the doc for the given key should be in limbo. */
-- (BOOL)shouldBeLimboDocumentKey:(FSTDocumentKey *)key {
+- (BOOL)shouldBeLimboDocumentKey:(const DocumentKey &)key {
// If the remote end says it's part of this query, it's not in limbo.
if ([self.syncedDocuments containsObject:key]) {
return NO;
@@ -354,10 +389,9 @@ static NSComparisonResult FSTCompareDocumentViewChangeTypes(FSTDocumentViewChang
}
/**
- * Updates syncedDocuments, isAcked, and limbo docs based on the given change.
- * @return the list of changes to which docs are in limbo.
+ * Updates syncedDocuments and current based on the given change.
*/
-- (NSArray<FSTLimboDocumentChange *> *)applyTargetChange:(nullable FSTTargetChange *)targetChange {
+- (void)applyTargetChange:(nullable FSTTargetChange *)targetChange {
if (targetChange) {
FSTTargetMapping *targetMapping = targetChange.mapping;
if ([targetMapping isKindOfClass:[FSTResetMapping class]]) {
@@ -384,16 +418,21 @@ static NSComparisonResult FSTCompareDocumentViewChangeTypes(FSTDocumentViewChang
break;
}
}
+}
+
+/** Updates limboDocuments and returns any changes as FSTLimboDocumentChanges. */
+- (NSArray<FSTLimboDocumentChange *> *)updateLimboDocuments {
+ // We can only determine limbo documents when we're in-sync with the server.
+ if (!self.isCurrent) {
+ return @[];
+ }
- // Recompute the set of limbo docs.
// TODO(klimt): Do this incrementally so that it's not quadratic when updating many documents.
FSTDocumentKeySet *oldLimboDocuments = self.limboDocuments;
self.limboDocuments = [FSTDocumentKeySet keySet];
- if (self.isCurrent) {
- for (FSTDocument *doc in self.documentSet.documentEnumerator) {
- if ([self shouldBeLimboDocumentKey:doc.key]) {
- self.limboDocuments = [self.limboDocuments setByAddingObject:doc.key];
- }
+ for (FSTDocument *doc in self.documentSet.documentEnumerator) {
+ if ([self shouldBeLimboDocumentKey:doc.key]) {
+ self.limboDocuments = [self.limboDocuments setByAddingObject:doc.key];
}
}
diff --git a/Firestore/Source/Core/FSTViewSnapshot.m b/Firestore/Source/Core/FSTViewSnapshot.mm
index e60b785..ae366fb 100644
--- a/Firestore/Source/Core/FSTViewSnapshot.m
+++ b/Firestore/Source/Core/FSTViewSnapshot.mm
@@ -18,11 +18,14 @@
#import "Firestore/Source/Core/FSTQuery.h"
#import "Firestore/Source/Model/FSTDocument.h"
-#import "Firestore/Source/Model/FSTDocumentKey.h"
#import "Firestore/Source/Model/FSTDocumentSet.h"
#import "Firestore/Source/Util/FSTAssert.h"
#import "Firestore/third_party/Immutable/FSTImmutableSortedDictionary.h"
+#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+
+using firebase::firestore::model::DocumentKey;
+
NS_ASSUME_NONNULL_BEGIN
#pragma mark - FSTDocumentViewChange
@@ -98,7 +101,7 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)addChange:(FSTDocumentViewChange *)change {
- FSTDocumentKey *key = change.document.key;
+ const DocumentKey &key = change.document.key;
FSTDocumentViewChange *oldChange = [self.changeMap objectForKey:key];
if (!oldChange) {
self.changeMap = [self.changeMap dictionaryBySettingObject:change forKey:key];
diff --git a/Firestore/Source/Local/FSTDocumentReference.h b/Firestore/Source/Local/FSTDocumentReference.h
index eff60e4..e23905c 100644
--- a/Firestore/Source/Local/FSTDocumentReference.h
+++ b/Firestore/Source/Local/FSTDocumentReference.h
@@ -16,7 +16,7 @@
#import <Foundation/Foundation.h>
-@class FSTDocumentKey;
+#include "Firestore/core/src/firebase/firestore/model/document_key.h"
NS_ASSUME_NONNULL_BEGIN
@@ -32,18 +32,19 @@ NS_ASSUME_NONNULL_BEGIN
@interface FSTDocumentReference : NSObject <NSCopying>
/** Initializes the document reference with the given key and ID. */
-- (instancetype)initWithKey:(FSTDocumentKey *)key ID:(int)ID NS_DESIGNATED_INITIALIZER;
+- (instancetype)initWithKey:(firebase::firestore::model::DocumentKey)key
+ ID:(int32_t)ID NS_DESIGNATED_INITIALIZER;
- (instancetype)init NS_UNAVAILABLE;
/** The document key that's the target of this reference. */
-@property(nonatomic, strong, readonly) FSTDocumentKey *key;
+- (const firebase::firestore::model::DocumentKey &)key;
/**
* The targetID of a referring target or the batchID of a referring mutation batch. (Which this
* is depends upon which FSTReferenceSet this reference is a part of.)
*/
-@property(nonatomic, assign, readonly) int ID;
+@property(nonatomic, assign, readonly) int32_t ID;
@end
diff --git a/Firestore/Source/Local/FSTDocumentReference.m b/Firestore/Source/Local/FSTDocumentReference.mm
index 1631789..3e755bc 100644
--- a/Firestore/Source/Local/FSTDocumentReference.m
+++ b/Firestore/Source/Local/FSTDocumentReference.mm
@@ -16,17 +16,24 @@
#import "Firestore/Source/Local/FSTDocumentReference.h"
-#import "Firestore/Source/Model/FSTDocumentKey.h"
-#import "Firestore/Source/Util/FSTComparison.h"
+#include <utility>
+
+#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+#include "Firestore/core/src/firebase/firestore/util/comparison.h"
+
+using firebase::firestore::model::DocumentKey;
+using firebase::firestore::util::WrapCompare;
NS_ASSUME_NONNULL_BEGIN
-@implementation FSTDocumentReference
+@implementation FSTDocumentReference {
+ DocumentKey _key;
+}
-- (instancetype)initWithKey:(FSTDocumentKey *)key ID:(int)ID {
+- (instancetype)initWithKey:(DocumentKey)key ID:(int32_t)ID {
self = [super init];
if (self) {
- _key = key;
+ _key = std::move(key);
_ID = ID;
}
return self;
@@ -34,7 +41,7 @@ NS_ASSUME_NONNULL_BEGIN
- (BOOL)isEqual:(id)other {
if (other == self) return YES;
- if (!other || ![[other class] isEqual:[self class]]) return NO;
+ if (![[other class] isEqual:[self class]]) return NO;
FSTDocumentReference *reference = (FSTDocumentReference *)other;
@@ -48,7 +55,8 @@ NS_ASSUME_NONNULL_BEGIN
}
- (NSString *)description {
- return [NSString stringWithFormat:@"<FSTDocumentReference: key=%@, ID=%d>", self.key, self.ID];
+ return [NSString stringWithFormat:@"<FSTDocumentReference: key=%s, ID=%d>",
+ self.key.ToString().c_str(), self.ID];
}
- (id)copyWithZone:(nullable NSZone *)zone {
@@ -56,6 +64,10 @@ NS_ASSUME_NONNULL_BEGIN
return self;
}
+- (const firebase::firestore::model::DocumentKey &)key {
+ return _key;
+}
+
@end
#pragma mark Comparators
@@ -67,13 +79,13 @@ const NSComparator FSTDocumentReferenceComparatorByKey =
if (result != NSOrderedSame) {
return result;
}
- return FSTCompareInts(left.ID, right.ID);
+ return WrapCompare<int32_t>(left.ID, right.ID);
};
/** Sorts document references by ID then key. */
const NSComparator FSTDocumentReferenceComparatorByID =
^NSComparisonResult(FSTDocumentReference *left, FSTDocumentReference *right) {
- NSComparisonResult result = FSTCompareInts(left.ID, right.ID);
+ NSComparisonResult result = WrapCompare<int32_t>(left.ID, right.ID);
if (result != NSOrderedSame) {
return result;
}
diff --git a/Firestore/Source/Local/FSTEagerGarbageCollector.h b/Firestore/Source/Local/FSTEagerGarbageCollector.h
index 40815b7..72b7375 100644
--- a/Firestore/Source/Local/FSTEagerGarbageCollector.h
+++ b/Firestore/Source/Local/FSTEagerGarbageCollector.h
@@ -18,8 +18,6 @@
#import "Firestore/Source/Local/FSTGarbageCollector.h"
-@class FSTDocumentKey;
-
NS_ASSUME_NONNULL_BEGIN
/**
diff --git a/Firestore/Source/Local/FSTEagerGarbageCollector.m b/Firestore/Source/Local/FSTEagerGarbageCollector.mm
index 77a577e..f7c75dd 100644
--- a/Firestore/Source/Local/FSTEagerGarbageCollector.m
+++ b/Firestore/Source/Local/FSTEagerGarbageCollector.mm
@@ -16,7 +16,11 @@
#import "Firestore/Source/Local/FSTEagerGarbageCollector.h"
-#import "Firestore/Source/Model/FSTDocumentKey.h"
+#include <set>
+
+#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+
+using firebase::firestore::model::DocumentKey;
NS_ASSUME_NONNULL_BEGIN
@@ -27,18 +31,17 @@ NS_ASSUME_NONNULL_BEGIN
/** The garbage collectible sources to double-check during garbage collection. */
@property(nonatomic, strong, readonly) NSMutableArray<id<FSTGarbageSource>> *sources;
-/** A set of potentially garbage keys. */
-@property(nonatomic, strong, readonly) NSMutableSet<FSTDocumentKey *> *potentialGarbage;
-
@end
-@implementation FSTEagerGarbageCollector
+@implementation FSTEagerGarbageCollector {
+ /** A set of potentially garbage keys. */
+ std::set<DocumentKey> _potentialGarbage;
+}
- (instancetype)init {
self = [super init];
if (self) {
_sources = [NSMutableArray array];
- _potentialGarbage = [[NSMutableSet alloc] init];
}
return self;
}
@@ -57,15 +60,15 @@ NS_ASSUME_NONNULL_BEGIN
garbageSource.garbageCollector = nil;
}
-- (void)addPotentialGarbageKey:(FSTDocumentKey *)key {
- [self.potentialGarbage addObject:key];
+- (void)addPotentialGarbageKey:(const DocumentKey &)key {
+ _potentialGarbage.insert(key);
}
-- (NSMutableSet<FSTDocumentKey *> *)collectGarbage {
+- (std::set<DocumentKey>)collectGarbage {
NSMutableArray<id<FSTGarbageSource>> *sources = self.sources;
- NSMutableSet<FSTDocumentKey *> *actualGarbage = [NSMutableSet set];
- for (FSTDocumentKey *key in self.potentialGarbage) {
+ std::set<DocumentKey> actualGarbage;
+ for (const DocumentKey &key : _potentialGarbage) {
BOOL isGarbage = YES;
for (id<FSTGarbageSource> source in sources) {
if ([source containsKey:key]) {
@@ -75,12 +78,12 @@ NS_ASSUME_NONNULL_BEGIN
}
if (isGarbage) {
- [actualGarbage addObject:key];
+ actualGarbage.insert(key);
}
}
// Clear locally retained potential keys and returned confirmed garbage.
- [self.potentialGarbage removeAllObjects];
+ _potentialGarbage.clear();
return actualGarbage;
}
diff --git a/Firestore/Source/Local/FSTGarbageCollector.h b/Firestore/Source/Local/FSTGarbageCollector.h
index ff5116b..e69e2b2 100644
--- a/Firestore/Source/Local/FSTGarbageCollector.h
+++ b/Firestore/Source/Local/FSTGarbageCollector.h
@@ -16,9 +16,12 @@
#import <Foundation/Foundation.h>
+#include <set>
+
#import "Firestore/Source/Core/FSTTypes.h"
-@class FSTDocumentKey;
+#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+
@class FSTDocumentReference;
@protocol FSTGarbageCollector;
@@ -41,7 +44,7 @@ NS_ASSUME_NONNULL_BEGIN
* garbage collectors to double-check if a key exists in this collection when it was released
* elsewhere.
*/
-- (BOOL)containsKey:(FSTDocumentKey *)key;
+- (BOOL)containsKey:(const firebase::firestore::model::DocumentKey&)key;
@end
@@ -85,10 +88,10 @@ NS_ASSUME_NONNULL_BEGIN
* matches any active targets. This behavior allows the client to avoid re-showing an old document
* in the next latency-compensated view.
*/
-- (void)addPotentialGarbageKey:(FSTDocumentKey *)key;
+- (void)addPotentialGarbageKey:(const firebase::firestore::model::DocumentKey&)key;
/** Returns the contents of the garbage bin and clears it. */
-- (NSSet<FSTDocumentKey *> *)collectGarbage;
+- (std::set<firebase::firestore::model::DocumentKey>)collectGarbage;
@end
diff --git a/Firestore/Source/Local/FSTLevelDB.h b/Firestore/Source/Local/FSTLevelDB.h
index 762054b..95b80a6 100644
--- a/Firestore/Source/Local/FSTLevelDB.h
+++ b/Firestore/Source/Local/FSTLevelDB.h
@@ -16,25 +16,20 @@
#import <Foundation/Foundation.h>
-#import "Firestore/Source/Local/FSTPersistence.h"
-
-#ifdef __cplusplus
#include <memory>
-namespace leveldb {
-class DB;
-class Status;
-}
-#endif
+#import "Firestore/Source/Local/FSTPersistence.h"
+#include "Firestore/core/src/firebase/firestore/core/database_info.h"
+#include "Firestore/core/src/firebase/firestore/local/leveldb_transaction.h"
+#include "leveldb/db.h"
-@class FSTDatabaseInfo;
@class FSTLocalSerializer;
NS_ASSUME_NONNULL_BEGIN
/** A LevelDB-backed instance of FSTPersistence. */
// TODO(mikelehen): Rename to FSTLevelDBPersistence.
-@interface FSTLevelDB : NSObject <FSTPersistence>
+@interface FSTLevelDB : NSObject <FSTPersistence, FSTTransactional>
/**
* Initializes the LevelDB in the given directory. Note that all expensive startup work including
@@ -56,7 +51,8 @@ NS_ASSUME_NONNULL_BEGIN
* will be created. Usually just +[FSTLevelDB documentsDir].
* @return A storage directory unique to the instance identified by databaseInfo.
*/
-+ (NSString *)storageDirectoryForDatabaseInfo:(FSTDatabaseInfo *)databaseInfo
++ (NSString *)storageDirectoryForDatabaseInfo:
+ (const firebase::firestore::core::DatabaseInfo &)databaseInfo
documentsDirectory:(NSString *)documentsDirectory;
/**
@@ -68,8 +64,11 @@ NS_ASSUME_NONNULL_BEGIN
*/
- (BOOL)start:(NSError **)error;
-#ifdef __cplusplus
// What follows is the Objective-C++ extension to the API.
+/**
+ * @return A standard set of read options
+ */
++ (const leveldb::ReadOptions)standardReadOptions;
/**
* Creates an NSError based on the given status if the status is not ok.
@@ -98,7 +97,7 @@ NS_ASSUME_NONNULL_BEGIN
/** The native db pointer, allocated during start. */
@property(nonatomic, assign, readonly) std::shared_ptr<leveldb::DB> ptr;
-#endif
+@property(nonatomic, readonly) firebase::firestore::local::LevelDbTransaction *currentTransaction;
@end
diff --git a/Firestore/Source/Local/FSTLevelDB.mm b/Firestore/Source/Local/FSTLevelDB.mm
index fb1c81a..fae85e7 100644
--- a/Firestore/Source/Local/FSTLevelDB.mm
+++ b/Firestore/Source/Local/FSTLevelDB.mm
@@ -16,50 +16,77 @@
#import "Firestore/Source/Local/FSTLevelDB.h"
-#include <leveldb/db.h>
+#include <memory>
#import "FIRFirestoreErrors.h"
-#import "Firestore/Source/Core/FSTDatabaseInfo.h"
+#import "Firestore/Source/Local/FSTLevelDBMigrations.h"
#import "Firestore/Source/Local/FSTLevelDBMutationQueue.h"
#import "Firestore/Source/Local/FSTLevelDBQueryCache.h"
#import "Firestore/Source/Local/FSTLevelDBRemoteDocumentCache.h"
-#import "Firestore/Source/Local/FSTWriteGroup.h"
-#import "Firestore/Source/Local/FSTWriteGroupTracker.h"
-#import "Firestore/Source/Model/FSTDatabaseID.h"
#import "Firestore/Source/Remote/FSTSerializerBeta.h"
#import "Firestore/Source/Util/FSTAssert.h"
#import "Firestore/Source/Util/FSTLogger.h"
+#include "Firestore/core/src/firebase/firestore/auth/user.h"
+#include "Firestore/core/src/firebase/firestore/core/database_info.h"
+#include "Firestore/core/src/firebase/firestore/local/leveldb_transaction.h"
+#include "Firestore/core/src/firebase/firestore/model/database_id.h"
+#include "Firestore/core/src/firebase/firestore/util/string_apple.h"
+#include "absl/memory/memory.h"
+#include "leveldb/db.h"
+
+namespace util = firebase::firestore::util;
+using firebase::firestore::auth::User;
+using firebase::firestore::core::DatabaseInfo;
+using firebase::firestore::model::DatabaseId;
+
NS_ASSUME_NONNULL_BEGIN
static NSString *const kReservedPathComponent = @"firestore";
+using firebase::firestore::local::LevelDbTransaction;
using leveldb::DB;
using leveldb::Options;
+using leveldb::ReadOptions;
using leveldb::Status;
using leveldb::WriteOptions;
@interface FSTLevelDB ()
@property(nonatomic, copy) NSString *directory;
-@property(nonatomic, strong) FSTWriteGroupTracker *writeGroupTracker;
@property(nonatomic, assign, getter=isStarted) BOOL started;
@property(nonatomic, strong, readonly) FSTLocalSerializer *serializer;
@end
-@implementation FSTLevelDB
+@implementation FSTLevelDB {
+ std::unique_ptr<LevelDbTransaction> _transaction;
+ FSTTransactionRunner _transactionRunner;
+}
+
+/**
+ * For now this is paranoid, but perhaps disable that in production builds.
+ */
++ (const ReadOptions)standardReadOptions {
+ ReadOptions options;
+ options.verify_checksums = true;
+ return options;
+}
- (instancetype)initWithDirectory:(NSString *)directory
serializer:(FSTLocalSerializer *)serializer {
if (self = [super init]) {
_directory = [directory copy];
- _writeGroupTracker = [FSTWriteGroupTracker tracker];
_serializer = serializer;
+ _transactionRunner.SetBackingPersistence(self);
}
return self;
}
+- (const FSTTransactionRunner &)run {
+ return _transactionRunner;
+}
+
+ (NSString *)documentsDirectory {
#if TARGET_OS_IPHONE
NSArray<NSString *> *directories =
@@ -72,13 +99,13 @@ using leveldb::WriteOptions;
#else
#error "local storage on tvOS"
-// TODO(mcg): Writing to NSDocumentsDirectory on tvOS will fail; we need to write to Caches
-// https://developer.apple.com/library/content/documentation/General/Conceptual/AppleTV_PG/
+ // TODO(mcg): Writing to NSDocumentsDirectory on tvOS will fail; we need to write to Caches
+ // https://developer.apple.com/library/content/documentation/General/Conceptual/AppleTV_PG/
#endif
}
-+ (NSString *)storageDirectoryForDatabaseInfo:(FSTDatabaseInfo *)databaseInfo
++ (NSString *)storageDirectoryForDatabaseInfo:(const DatabaseInfo &)databaseInfo
documentsDirectory:(NSString *)documentsDirectory {
// Use two different path formats:
//
@@ -88,11 +115,13 @@ using leveldb::WriteOptions;
// projectIDs are DNS-compatible names and cannot contain dots so there's
// no danger of collisions.
NSString *directory = documentsDirectory;
- directory = [directory stringByAppendingPathComponent:databaseInfo.persistenceKey];
+ directory = [directory
+ stringByAppendingPathComponent:util::WrapNSStringNoCopy(databaseInfo.persistence_key())];
- NSString *segment = databaseInfo.databaseID.projectID;
- if (![databaseInfo.databaseID isDefaultDatabase]) {
- segment = [NSString stringWithFormat:@"%@.%@", segment, databaseInfo.databaseID.databaseID];
+ NSString *segment = util::WrapNSStringNoCopy(databaseInfo.database_id().project_id());
+ if (!databaseInfo.database_id().IsDefaultDatabase()) {
+ segment = [NSString
+ stringWithFormat:@"%@.%s", segment, databaseInfo.database_id().database_id().c_str()];
}
directory = [directory stringByAppendingPathComponent:segment];
@@ -115,8 +144,10 @@ using leveldb::WriteOptions;
if (!database) {
return NO;
}
-
_ptr.reset(database);
+ LevelDbTransaction transaction(_ptr.get(), "Start LevelDB");
+ [FSTLevelDBMigrations runMigrationsWithTransaction:&transaction];
+ transaction.Commit();
return YES;
}
@@ -176,35 +207,34 @@ using leveldb::WriteOptions;
return database;
}
+- (LevelDbTransaction *)currentTransaction {
+ FSTAssert(_transaction != nullptr, @"Attempting to access transaction before one has started");
+ return _transaction.get();
+}
+
#pragma mark - Persistence Factory methods
-- (id<FSTMutationQueue>)mutationQueueForUser:(FSTUser *)user {
- return [FSTLevelDBMutationQueue mutationQueueWithUser:user db:_ptr serializer:self.serializer];
+- (id<FSTMutationQueue>)mutationQueueForUser:(const User &)user {
+ return [FSTLevelDBMutationQueue mutationQueueWithUser:user db:self serializer:self.serializer];
}
- (id<FSTQueryCache>)queryCache {
- return [[FSTLevelDBQueryCache alloc] initWithDB:_ptr serializer:self.serializer];
+ return [[FSTLevelDBQueryCache alloc] initWithDB:self serializer:self.serializer];
}
- (id<FSTRemoteDocumentCache>)remoteDocumentCache {
- return [[FSTLevelDBRemoteDocumentCache alloc] initWithDB:_ptr serializer:self.serializer];
+ return [[FSTLevelDBRemoteDocumentCache alloc] initWithDB:self serializer:self.serializer];
}
-- (FSTWriteGroup *)startGroupWithAction:(NSString *)action {
- return [self.writeGroupTracker startGroupWithAction:action];
+- (void)startTransaction:(absl::string_view)label {
+ FSTAssert(_transaction == nullptr, @"Starting a transaction while one is already outstanding");
+ _transaction = absl::make_unique<LevelDbTransaction>(_ptr.get(), label);
}
-- (void)commitGroup:(FSTWriteGroup *)group {
- [self.writeGroupTracker endGroup:group];
-
- NSString *description = [group description];
- FSTLog(@"Committing %@", description);
-
- Status status = [group writeToDB:_ptr];
- if (!status.ok()) {
- FSTFail(@"%@ failed with status: %s, description: %@", group.action, status.ToString().c_str(),
- description);
- }
+- (void)commitTransaction {
+ FSTAssert(_transaction != nullptr, @"Committing a transaction before one is started");
+ _transaction->Commit();
+ _transaction.reset();
}
- (void)shutdown {
diff --git a/Firestore/Source/Local/FSTLevelDBKey.h b/Firestore/Source/Local/FSTLevelDBKey.h
index 2e9b9b2..5b234ec 100644
--- a/Firestore/Source/Local/FSTLevelDBKey.h
+++ b/Firestore/Source/Local/FSTLevelDBKey.h
@@ -14,17 +14,16 @@
* limitations under the License.
*/
-#ifndef __cplusplus
-#error "FSTLevelDBKey is Objective-C++ and can only be included from .mm files"
-#endif
-
#import <Foundation/Foundation.h>
+#include <string>
+
#import "Firestore/Source/Core/FSTTypes.h"
#import "Firestore/Source/Local/StringView.h"
+#include "Firestore/core/src/firebase/firestore/model/resource_path.h"
+
@class FSTDocumentKey;
-@class FSTResourcePath;
NS_ASSUME_NONNULL_BEGIN
@@ -38,7 +37,7 @@ NS_ASSUME_NONNULL_BEGIN
// document_mutations:
// - tableName: string = "document_mutation"
// - userID: string
-// - path: FSTResourcePath
+// - path: ResourcePath
// - batchID: FSTBatchID
//
// mutation_queues:
@@ -60,16 +59,16 @@ NS_ASSUME_NONNULL_BEGIN
// target_documents:
// - tableName: string = "target_document"
// - targetID: FSTTargetID
-// - path: FSTResourcePath
+// - path: ResourcePath
//
// document_targets:
// - tableName: string = "document_target"
-// - path: FSTResourcePath
+// - path: ResourcePath
// - targetID: FSTTargetID
//
// remote_documents:
// - tableName: string = "remote_document"
-// - path: FSTResourcePath
+// - path: ResourcePath
/** Helpers for any LevelDB key. */
@interface FSTLevelDBKey : NSObject
@@ -82,6 +81,14 @@ NS_ASSUME_NONNULL_BEGIN
@end
+/** A key to a singleton row storing the version of the schema. */
+@interface FSTLevelDBVersionKey : NSObject
+
+/** Returns the key pointing to the singleton row storing the schema version. */
++ (std::string)key;
+
+@end
+
/** A key in the mutations table. */
@interface FSTLevelDBMutationKey : NSObject
@@ -124,12 +131,12 @@ NS_ASSUME_NONNULL_BEGIN
/**
* Creates a key prefix that points just before the first key for the userID and resource path.
*
- * Note that this uses an FSTResourcePath rather than an FSTDocumentKey in order to allow prefix
+ * Note that this uses a ResourcePath rather than an FSTDocumentKey in order to allow prefix
* scans over a collection. However a naive scan over those results isn't useful since it would
* match both immediate children of the collection and any subcollections.
*/
+ (std::string)keyPrefixWithUserID:(Firestore::StringView)userID
- resourcePath:(FSTResourcePath *)resourcePath;
+ resourcePath:(const firebase::firestore::model::ResourcePath &)resourcePath;
/** Creates a complete key that points to a specific userID, document key, and batchID. */
+ (std::string)keyWithUserID:(Firestore::StringView)userID
@@ -287,7 +294,8 @@ NS_ASSUME_NONNULL_BEGIN
+ (std::string)keyPrefix;
/** Creates a key that points to the first document-target association for document. */
-+ (std::string)keyPrefixWithResourcePath:(FSTResourcePath *)resourcePath;
++ (std::string)keyPrefixWithResourcePath:
+ (const firebase::firestore::model::ResourcePath &)resourcePath;
/** Creates a key that points to a specific document-target entry. */
+ (std::string)keyWithDocumentKey:(FSTDocumentKey *)documentKey targetID:(FSTTargetID)targetID;
@@ -324,7 +332,8 @@ NS_ASSUME_NONNULL_BEGIN
* a document key prefix will match the document itself and any documents that exist in its
* subcollections.
*/
-+ (std::string)keyPrefixWithResourcePath:(FSTResourcePath *)resourcePath;
++ (std::string)keyPrefixWithResourcePath:
+ (const firebase::firestore::model::ResourcePath &)resourcePath;
/**
* Decodes the contents of a remote document key into properties on this instance. This can only
diff --git a/Firestore/Source/Local/FSTLevelDBKey.mm b/Firestore/Source/Local/FSTLevelDBKey.mm
index c6f51b9..e23f5b7 100644
--- a/Firestore/Source/Local/FSTLevelDBKey.mm
+++ b/Firestore/Source/Local/FSTLevelDBKey.mm
@@ -17,19 +17,28 @@
#import "Firestore/Source/Local/FSTLevelDBKey.h"
#include <string>
+#include <utility>
+#include <vector>
-#include "Firestore/Port/ordered_code.h"
-#include "Firestore/Port/string_util.h"
#import "Firestore/Source/Model/FSTDocumentKey.h"
-#import "Firestore/Source/Model/FSTPath.h"
+
+#include "Firestore/core/src/firebase/firestore/model/resource_path.h"
+#include "Firestore/core/src/firebase/firestore/util/ordered_code.h"
+#include "Firestore/core/src/firebase/firestore/util/string_apple.h"
+
+namespace util = firebase::firestore::util;
+
+namespace util = firebase::firestore::util;
+using firebase::firestore::model::ResourcePath;
NS_ASSUME_NONNULL_BEGIN
-using Firestore::OrderedCode;
-using Firestore::PrefixSuccessor;
+using firebase::firestore::model::ResourcePath;
+using firebase::firestore::util::OrderedCode;
using Firestore::StringView;
using leveldb::Slice;
+static const char *kVersionGlobalTable = "version";
static const char *kMutationsTable = "mutation";
static const char *kDocumentMutationsTable = "document_mutation";
static const char *kMutationQueuesTable = "mutation_queue";
@@ -111,11 +120,11 @@ void WriteComponentLabel(std::string *dest, FSTComponentLabel label) {
*/
BOOL ReadComponentLabel(leveldb::Slice *contents, FSTComponentLabel *label) {
int64_t rawResult = 0;
- Slice tmp = *contents;
+ absl::string_view tmp(contents->data(), contents->size());
if (OrderedCode::ReadSignedNumIncreasing(&tmp, &rawResult)) {
if (rawResult >= FSTComponentLabelTerminator && rawResult <= FSTComponentLabelUnknown) {
*label = static_cast<FSTComponentLabel>(rawResult);
- *contents = tmp;
+ *contents = leveldb::Slice(tmp.data(), tmp.size());
return YES;
}
}
@@ -130,9 +139,9 @@ BOOL ReadComponentLabel(leveldb::Slice *contents, FSTComponentLabel *label) {
*
* If the read is successful, returns YES and contents will be updated to the next unread byte.
*/
-BOOL ReadComponentLabelMatching(Slice *contents, FSTComponentLabel expectedLabel) {
+BOOL ReadComponentLabelMatching(absl::string_view *contents, FSTComponentLabel expectedLabel) {
int64_t rawResult = 0;
- Slice tmp = *contents;
+ absl::string_view tmp = *contents;
if (OrderedCode::ReadSignedNumIncreasing(&tmp, &rawResult)) {
if (rawResult == expectedLabel) {
*contents = tmp;
@@ -154,10 +163,10 @@ BOOL ReadComponentLabelMatching(Slice *contents, FSTComponentLabel expectedLabel
*/
BOOL ReadInt32(Slice *contents, int32_t *result) {
int64_t rawResult = 0;
- Slice tmp = *contents;
+ absl::string_view tmp(contents->data(), contents->size());
if (OrderedCode::ReadSignedNumIncreasing(&tmp, &rawResult)) {
if (rawResult >= INT32_MIN && rawResult <= INT32_MAX) {
- *contents = tmp;
+ *contents = leveldb::Slice(tmp.data(), tmp.size());
*result = static_cast<int32_t>(rawResult);
return YES;
}
@@ -182,10 +191,11 @@ void WriteLabeledInt32(std::string *dest, FSTComponentLabel label, int32_t value
* value will be set to the decoded integer value.
*/
BOOL ReadLabeledInt32(Slice *contents, FSTComponentLabel expectedLabel, int32_t *value) {
- Slice tmp = *contents;
+ absl::string_view tmp(contents->data(), contents->size());
if (ReadComponentLabelMatching(&tmp, expectedLabel)) {
- if (ReadInt32(&tmp, value)) {
- *contents = tmp;
+ Slice tmpSlice = leveldb::Slice(tmp.data(), tmp.size());
+ if (ReadInt32(&tmpSlice, value)) {
+ *contents = tmpSlice;
return YES;
}
}
@@ -209,10 +219,10 @@ void WriteLabeledString(std::string *dest, FSTComponentLabel label, StringView v
* value will be set to the decoded string value.
*/
BOOL ReadLabeledString(Slice *contents, FSTComponentLabel expectedLabel, std::string *value) {
- Slice tmp = *contents;
+ absl::string_view tmp(contents->data(), contents->size());
if (ReadComponentLabelMatching(&tmp, expectedLabel)) {
if (OrderedCode::ReadString(&tmp, value)) {
- *contents = tmp;
+ *contents = leveldb::Slice(tmp.data(), tmp.size());
return YES;
}
}
@@ -248,10 +258,10 @@ BOOL ReadLabeledStringMatching(Slice *contents,
* For each segment in the given resource path writes an FSTComponentLabelPathSegment component
* label and a string containing the path segment.
*/
-void WriteResourcePath(std::string *dest, FSTResourcePath *path) {
- for (int i = 0; i < path.length; i++) {
+void WriteResourcePath(std::string *dest, const ResourcePath &path) {
+ for (const auto &segment : path) {
WriteComponentLabel(dest, FSTComponentLabelPathSegment);
- OrderedCode::WriteString(dest, StringView([path segmentAtIndex:i]));
+ OrderedCode::WriteString(dest, segment);
}
}
@@ -270,11 +280,11 @@ BOOL ReadDocumentKey(Slice *contents, FSTDocumentKey *__strong *result) {
Slice completeSegments = *contents;
std::string segment;
- NSMutableArray<NSString *> *pathSegments = [NSMutableArray array];
+ std::vector<std::string> path_segments{};
for (;;) {
// Advance a temporary slice to avoid advancing contents into the next key component which may
// not be a path segment.
- Slice readPosition = completeSegments;
+ absl::string_view readPosition(completeSegments.data(), completeSegments.size());
if (!ReadComponentLabelMatching(&readPosition, FSTComponentLabelPathSegment)) {
break;
}
@@ -282,15 +292,14 @@ BOOL ReadDocumentKey(Slice *contents, FSTDocumentKey *__strong *result) {
return NO;
}
- NSString *pathSegment = [[NSString alloc] initWithUTF8String:segment.c_str()];
- [pathSegments addObject:pathSegment];
+ path_segments.push_back(std::move(segment));
segment.clear();
- completeSegments = readPosition;
+ completeSegments = leveldb::Slice(readPosition.data(), readPosition.size());
}
- FSTResourcePath *path = [FSTResourcePath pathWithSegments:pathSegments];
- if (path.length > 0 && [FSTDocumentKey isDocumentKey:path]) {
+ ResourcePath path{std::move(path_segments)};
+ if (path.size() > 0 && [FSTDocumentKey isDocumentKey:path]) {
*contents = completeSegments;
*result = [FSTDocumentKey keyWithPath:path];
return YES;
@@ -306,7 +315,10 @@ inline void WriteTerminator(std::string *dest) {
}
inline BOOL ReadTerminator(Slice *contents) {
- return ReadComponentLabelMatching(contents, FSTComponentLabelTerminator);
+ absl::string_view tmp(contents->data(), contents->size());
+ BOOL result = ReadComponentLabelMatching(&tmp, FSTComponentLabelTerminator);
+ *contents = leveldb::Slice(tmp.data(), tmp.size());
+ return result;
}
inline void WriteTableName(std::string *dest, const char *tableName) {
@@ -387,7 +399,7 @@ NSString *InvalidKey(const Slice &key) {
if (!ReadDocumentKey(&tmp, &documentKey)) {
break;
}
- [description appendFormat:@" key=%@", [documentKey.path description]];
+ [description appendFormat:@" key=%s", documentKey.path.CanonicalString().c_str()];
} else if (label == FSTComponentLabelTableName) {
std::string table;
@@ -445,6 +457,17 @@ NSString *InvalidKey(const Slice &key) {
@end
+@implementation FSTLevelDBVersionKey
+
++ (std::string)key {
+ std::string result;
+ WriteTableName(&result, kVersionGlobalTable);
+ WriteTerminator(&result);
+ return result;
+}
+
+@end
+
@implementation FSTLevelDBMutationKey {
std::string _userID;
}
@@ -502,7 +525,8 @@ NSString *InvalidKey(const Slice &key) {
return result;
}
-+ (std::string)keyPrefixWithUserID:(StringView)userID resourcePath:(FSTResourcePath *)resourcePath {
++ (std::string)keyPrefixWithUserID:(StringView)userID
+ resourcePath:(const ResourcePath &)resourcePath {
std::string result;
WriteTableName(&result, kDocumentMutationsTable);
WriteUserID(&result, userID);
@@ -694,7 +718,7 @@ NSString *InvalidKey(const Slice &key) {
return result;
}
-+ (std::string)keyPrefixWithResourcePath:(FSTResourcePath *)resourcePath {
++ (std::string)keyPrefixWithResourcePath:(const ResourcePath &)resourcePath {
std::string result;
WriteTableName(&result, kDocumentTargetsTable);
WriteResourcePath(&result, resourcePath);
@@ -729,7 +753,7 @@ NSString *InvalidKey(const Slice &key) {
return result;
}
-+ (std::string)keyPrefixWithResourcePath:(FSTResourcePath *)path {
++ (std::string)keyPrefixWithResourcePath:(const ResourcePath &)path {
std::string result;
WriteTableName(&result, kRemoteDocumentsTable);
WriteResourcePath(&result, path);
diff --git a/Firestore/Source/Local/FSTLevelDBMigrations.h b/Firestore/Source/Local/FSTLevelDBMigrations.h
new file mode 100644
index 0000000..1724edf
--- /dev/null
+++ b/Firestore/Source/Local/FSTLevelDBMigrations.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2018 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/Foundation.h>
+
+#include <memory>
+
+#include "Firestore/core/src/firebase/firestore/local/leveldb_transaction.h"
+#include "leveldb/db.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+typedef int32_t FSTLevelDBSchemaVersion;
+
+@interface FSTLevelDBMigrations : NSObject
+
+/**
+ * Returns the current version of the schema for the given database
+ */
++ (FSTLevelDBSchemaVersion)schemaVersionWithTransaction:
+ (firebase::firestore::local::LevelDbTransaction *)transaction;
+
+/**
+ * Runs any migrations needed to bring the given database up to the current schema version
+ */
++ (void)runMigrationsWithTransaction:(firebase::firestore::local::LevelDbTransaction *)transaction;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/Local/FSTLevelDBMigrations.mm b/Firestore/Source/Local/FSTLevelDBMigrations.mm
new file mode 100644
index 0000000..fefd0f7
--- /dev/null
+++ b/Firestore/Source/Local/FSTLevelDBMigrations.mm
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2018 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 "Firestore/Source/Local/FSTLevelDBMigrations.h"
+
+#include <string>
+
+#import "Firestore/Protos/objc/firestore/local/Target.pbobjc.h"
+#import "Firestore/Source/Local/FSTLevelDBKey.h"
+#import "Firestore/Source/Local/FSTLevelDBQueryCache.h"
+#import "Firestore/Source/Util/FSTAssert.h"
+
+#include "absl/strings/match.h"
+#include "leveldb/write_batch.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+// Current version of the schema defined in this file.
+static FSTLevelDBSchemaVersion kSchemaVersion = 2;
+
+using firebase::firestore::local::LevelDbTransaction;
+using leveldb::DB;
+using leveldb::Iterator;
+using leveldb::Status;
+using leveldb::Slice;
+using leveldb::WriteOptions;
+
+/**
+ * Ensures that the global singleton target metadata row exists in LevelDB.
+ */
+static void EnsureTargetGlobal(LevelDbTransaction *transaction) {
+ FSTPBTargetGlobal *targetGlobal =
+ [FSTLevelDBQueryCache readTargetMetadataWithTransaction:transaction];
+ if (!targetGlobal) {
+ transaction->Put([FSTLevelDBTargetGlobalKey key], [FSTPBTargetGlobal message]);
+ }
+}
+
+/**
+ * Save the given version number as the current version of the schema of the database.
+ * @param version The version to save
+ * @param transaction The transaction in which to save the new version number
+ */
+static void SaveVersion(FSTLevelDBSchemaVersion version, LevelDbTransaction *transaction) {
+ std::string key = [FSTLevelDBVersionKey key];
+ std::string version_string = std::to_string(version);
+ transaction->Put(key, version_string);
+}
+
+/**
+ * This function counts the number of targets that currently exist in the given db. It
+ * then reads the target global row, adds the count to the metadata from that row, and writes
+ * the metadata back.
+ *
+ * It assumes the metadata has already been written and is able to be read in this transaction.
+ */
+static void AddTargetCount(LevelDbTransaction *transaction) {
+ auto it = transaction->NewIterator();
+ std::string start_key = [FSTLevelDBTargetKey keyPrefix];
+ it->Seek(start_key);
+
+ int32_t count = 0;
+ while (it->Valid() && absl::StartsWith(it->key(), start_key)) {
+ count++;
+ it->Next();
+ }
+
+ FSTPBTargetGlobal *targetGlobal =
+ [FSTLevelDBQueryCache readTargetMetadataWithTransaction:transaction];
+ FSTCAssert(targetGlobal != nil,
+ @"We should have a metadata row as it was added in an earlier migration");
+ targetGlobal.targetCount = count;
+ transaction->Put([FSTLevelDBTargetGlobalKey key], targetGlobal);
+}
+
+@implementation FSTLevelDBMigrations
+
++ (FSTLevelDBSchemaVersion)schemaVersionWithTransaction:
+ (firebase::firestore::local::LevelDbTransaction *)transaction {
+ std::string key = [FSTLevelDBVersionKey key];
+ std::string version_string;
+ Status status = transaction->Get(key, &version_string);
+ if (status.IsNotFound()) {
+ return 0;
+ } else {
+ return stoi(version_string);
+ }
+}
+
++ (void)runMigrationsWithTransaction:(firebase::firestore::local::LevelDbTransaction *)transaction {
+ FSTLevelDBSchemaVersion currentVersion = [self schemaVersionWithTransaction:transaction];
+ // Each case in this switch statement intentionally falls through. This lets us
+ // start at the current schema version and apply any migrations that have not yet
+ // been applied, to bring us up to current, as defined by the kSchemaVersion constant.
+ switch (currentVersion) {
+ case 0:
+ EnsureTargetGlobal(transaction);
+ // Fallthrough
+ case 1:
+ // We're now guaranteed that the target global exists. We can safely add a count to it.
+ AddTargetCount(transaction);
+ // Fallthrough
+ default:
+ if (currentVersion < kSchemaVersion) {
+ SaveVersion(kSchemaVersion, transaction);
+ }
+ }
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/Local/FSTLevelDBMutationQueue.h b/Firestore/Source/Local/FSTLevelDBMutationQueue.h
index dd2ed4f..034738f 100644
--- a/Firestore/Source/Local/FSTLevelDBMutationQueue.h
+++ b/Firestore/Source/Local/FSTLevelDBMutationQueue.h
@@ -16,19 +16,15 @@
#import <Foundation/Foundation.h>
-#import "Firestore/Source/Local/FSTMutationQueue.h"
-
-#ifdef __cplusplus
#include <memory>
-namespace leveldb {
-class DB;
-}
-#endif
+#import "Firestore/Source/Local/FSTMutationQueue.h"
+
+#include "Firestore/core/src/firebase/firestore/auth/user.h"
+#include "leveldb/db.h"
@class FSTLevelDB;
@class FSTLocalSerializer;
-@class FSTUser;
@protocol FSTGarbageCollector;
NS_ASSUME_NONNULL_BEGIN
@@ -41,15 +37,14 @@ NS_ASSUME_NONNULL_BEGIN
/** The garbage collector to notify about potential garbage keys. */
@property(nonatomic, weak, readwrite, nullable) id<FSTGarbageCollector> garbageCollector;
-#ifdef __cplusplus
/**
* Creates a new mutation queue for the given user, in the given LevelDB.
*
* @param user The user for which to create a mutation queue.
* @param db The LevelDB in which to create the queue.
*/
-+ (instancetype)mutationQueueWithUser:(FSTUser *)user
- db:(std::shared_ptr<leveldb::DB>)db
++ (instancetype)mutationQueueWithUser:(const firebase::firestore::auth::User &)user
+ db:(FSTLevelDB *)db
serializer:(FSTLocalSerializer *)serializer;
/**
@@ -57,7 +52,6 @@ NS_ASSUME_NONNULL_BEGIN
* returns 0. Note that batch IDs are global.
*/
+ (FSTBatchID)loadNextBatchIDFromDB:(std::shared_ptr<leveldb::DB>)db;
-#endif
@end
diff --git a/Firestore/Source/Local/FSTLevelDBMutationQueue.mm b/Firestore/Source/Local/FSTLevelDBMutationQueue.mm
index 56a22a1..75c3cf6 100644
--- a/Firestore/Source/Local/FSTLevelDBMutationQueue.mm
+++ b/Firestore/Source/Local/FSTLevelDBMutationQueue.mm
@@ -16,31 +16,37 @@
#import "Firestore/Source/Local/FSTLevelDBMutationQueue.h"
-#include <leveldb/db.h>
-#include <leveldb/write_batch.h>
+#include <memory>
#include <set>
#include <string>
#import "Firestore/Protos/objc/firestore/local/Mutation.pbobjc.h"
-#import "Firestore/Source/Auth/FSTUser.h"
#import "Firestore/Source/Core/FSTQuery.h"
#import "Firestore/Source/Local/FSTLevelDB.h"
#import "Firestore/Source/Local/FSTLevelDBKey.h"
#import "Firestore/Source/Local/FSTLocalSerializer.h"
-#import "Firestore/Source/Local/FSTWriteGroup.h"
-#import "Firestore/Source/Model/FSTDocumentKey.h"
#import "Firestore/Source/Model/FSTMutation.h"
#import "Firestore/Source/Model/FSTMutationBatch.h"
-#import "Firestore/Source/Model/FSTPath.h"
#import "Firestore/Source/Util/FSTAssert.h"
-#include "Firestore/Port/ordered_code.h"
-#include "Firestore/Port/string_util.h"
+#include "Firestore/core/src/firebase/firestore/auth/user.h"
+#include "Firestore/core/src/firebase/firestore/local/leveldb_transaction.h"
+#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+#include "Firestore/core/src/firebase/firestore/model/resource_path.h"
+#include "Firestore/core/src/firebase/firestore/util/string_apple.h"
+#include "Firestore/core/src/firebase/firestore/util/string_util.h"
+#include "absl/strings/match.h"
+#include "leveldb/db.h"
+#include "leveldb/write_batch.h"
NS_ASSUME_NONNULL_BEGIN
-using Firestore::OrderedCode;
+namespace util = firebase::firestore::util;
+using firebase::firestore::local::LevelDbTransaction;
using Firestore::StringView;
+using firebase::firestore::auth::User;
+using firebase::firestore::model::DocumentKey;
+using firebase::firestore::model::ResourcePath;
using leveldb::DB;
using leveldb::Iterator;
using leveldb::ReadOptions;
@@ -52,7 +58,7 @@ using leveldb::WriteOptions;
@interface FSTLevelDBMutationQueue ()
- (instancetype)initWithUserID:(NSString *)userID
- db:(std::shared_ptr<DB>)db
+ db:(FSTLevelDB *)db
serializer:(FSTLocalSerializer *)serializer NS_DESIGNATED_INITIALIZER;
/** The normalized userID (e.g. nil UID => @"" userID) used in our LevelDB keys. */
@@ -74,44 +80,31 @@ using leveldb::WriteOptions;
@end
-/**
- * Returns a standard set of read options.
- *
- * For now this is paranoid, but perhaps disable that in production builds.
- */
-static ReadOptions StandardReadOptions() {
- ReadOptions options;
- options.verify_checksums = true;
- return options;
-}
-
@implementation FSTLevelDBMutationQueue {
- // The DB pointer is shared with all cooperating LevelDB-related objects.
- std::shared_ptr<DB> _db;
+ FSTLevelDB *_db;
}
-+ (instancetype)mutationQueueWithUser:(FSTUser *)user
- db:(std::shared_ptr<DB>)db
++ (instancetype)mutationQueueWithUser:(const User &)user
+ db:(FSTLevelDB *)db
serializer:(FSTLocalSerializer *)serializer {
- FSTAssert(![user.UID isEqual:@""], @"UserID must not be an empty string.");
- NSString *userID = user.isUnauthenticated ? @"" : user.UID;
+ NSString *userID = user.is_authenticated() ? util::WrapNSString(user.uid()) : @"";
return [[FSTLevelDBMutationQueue alloc] initWithUserID:userID db:db serializer:serializer];
}
- (instancetype)initWithUserID:(NSString *)userID
- db:(std::shared_ptr<DB>)db
+ db:(FSTLevelDB *)db
serializer:(FSTLocalSerializer *)serializer {
if (self = [super init]) {
- _userID = userID;
+ _userID = [userID copy];
_db = db;
_serializer = serializer;
}
return self;
}
-- (void)startWithGroup:(FSTWriteGroup *)group {
- FSTBatchID nextBatchID = [FSTLevelDBMutationQueue loadNextBatchIDFromDB:_db];
+- (void)start {
+ FSTBatchID nextBatchID = [FSTLevelDBMutationQueue loadNextBatchIDFromDB:_db.ptr];
// On restart, nextBatchId may end up lower than lastAcknowledgedBatchId since it's computed from
// the queue contents, and there may be no mutations in the queue. In this case, we need to reset
@@ -131,7 +124,7 @@ static ReadOptions StandardReadOptions() {
lastAcked = kFSTBatchIDUnknown;
metadata.lastAcknowledgedBatchId = lastAcked;
- [group setMessage:metadata forKey:[self keyForCurrentMutationQueue]];
+ _db.currentTransaction->Put([self keyForCurrentMutationQueue], metadata);
}
}
@@ -139,12 +132,10 @@ static ReadOptions StandardReadOptions() {
self.metadata = metadata;
}
-- (void)shutdown {
- _db.reset();
-}
-
+ (FSTBatchID)loadNextBatchIDFromDB:(std::shared_ptr<DB>)db {
- std::unique_ptr<Iterator> it(db->NewIterator(StandardReadOptions()));
+ // TODO(gsoltis): implement Prev() and SeekToLast() on LevelDbTransaction::Iterator, then port
+ // this to a transaction.
+ std::unique_ptr<Iterator> it(db->NewIterator(LevelDbTransaction::DefaultReadOptions()));
auto tableKey = [FSTLevelDBMutationKey keyPrefix];
@@ -164,7 +155,7 @@ static ReadOptions StandardReadOptions() {
while (moreUserIDs) {
// Compute the first key after the last mutation for nextUserID.
auto userEnd = [FSTLevelDBMutationKey keyPrefixWithUserID:nextUserID];
- userEnd = Firestore::PrefixSuccessor(userEnd);
+ userEnd = util::PrefixSuccessor(userEnd);
// Seek to that key with the intent of finding the boundary between nextUserID's mutations
// and the one after that (if any).
@@ -207,19 +198,14 @@ static ReadOptions StandardReadOptions() {
- (BOOL)isEmpty {
std::string userKey = [FSTLevelDBMutationKey keyPrefixWithUserID:self.userID];
- std::unique_ptr<Iterator> it(_db->NewIterator(StandardReadOptions()));
+ auto it = _db.currentTransaction->NewIterator();
it->Seek(userKey);
BOOL empty = YES;
- if (it->Valid() && it->key().starts_with(userKey)) {
+ if (it->Valid() && absl::StartsWith(it->key(), userKey)) {
empty = NO;
}
- Status status = it->status();
- if (!status.ok()) {
- FSTFail(@"isEmpty failed with status: %s", status.ToString().c_str());
- }
-
return empty;
}
@@ -227,9 +213,7 @@ static ReadOptions StandardReadOptions() {
return self.metadata.lastAcknowledgedBatchId;
}
-- (void)acknowledgeBatch:(FSTMutationBatch *)batch
- streamToken:(nullable NSData *)streamToken
- group:(FSTWriteGroup *)group {
+- (void)acknowledgeBatch:(FSTMutationBatch *)batch streamToken:(nullable NSData *)streamToken {
FSTBatchID batchID = batch.batchID;
FSTAssert(batchID > self.highestAcknowledgedBatchID,
@"Mutation batchIDs must be acknowledged in order");
@@ -238,18 +222,18 @@ static ReadOptions StandardReadOptions() {
metadata.lastAcknowledgedBatchId = batchID;
metadata.lastStreamToken = streamToken;
- [group setMessage:metadata forKey:[self keyForCurrentMutationQueue]];
+ _db.currentTransaction->Put([self keyForCurrentMutationQueue], metadata);
}
- (nullable NSData *)lastStreamToken {
return self.metadata.lastStreamToken;
}
-- (void)setLastStreamToken:(nullable NSData *)streamToken group:(FSTWriteGroup *)group {
+- (void)setLastStreamToken:(nullable NSData *)streamToken {
FSTPBMutationQueue *metadata = self.metadata;
metadata.lastStreamToken = streamToken;
- [group setMessage:metadata forKey:[self keyForCurrentMutationQueue]];
+ _db.currentTransaction->Put([self keyForCurrentMutationQueue], metadata);
}
- (std::string)keyForCurrentMutationQueue {
@@ -258,7 +242,7 @@ static ReadOptions StandardReadOptions() {
- (nullable FSTPBMutationQueue *)metadataForKey:(const std::string &)key {
std::string value;
- Status status = _db->Get(StandardReadOptions(), key, &value);
+ Status status = _db.currentTransaction->Get(key, &value);
if (status.ok()) {
return [self parsedMetadata:value];
} else if (status.IsNotFound()) {
@@ -269,9 +253,8 @@ static ReadOptions StandardReadOptions() {
}
}
-- (FSTMutationBatch *)addMutationBatchWithWriteTime:(FSTTimestamp *)localWriteTime
- mutations:(NSArray<FSTMutation *> *)mutations
- group:(FSTWriteGroup *)group {
+- (FSTMutationBatch *)addMutationBatchWithWriteTime:(FIRTimestamp *)localWriteTime
+ mutations:(NSArray<FSTMutation *> *)mutations {
FSTBatchID batchID = self.nextBatchID;
self.nextBatchID += 1;
@@ -279,7 +262,7 @@ static ReadOptions StandardReadOptions() {
localWriteTime:localWriteTime
mutations:mutations];
std::string key = [self mutationKeyForBatch:batch];
- [group setMessage:[self.serializer encodedMutationBatch:batch] forKey:key];
+ _db.currentTransaction->Put(key, [self.serializer encodedMutationBatch:batch]);
NSString *userID = self.userID;
@@ -292,7 +275,7 @@ static ReadOptions StandardReadOptions() {
key = [FSTLevelDBDocumentMutationKey keyWithUserID:userID
documentKey:mutation.key
batchID:batchID];
- [group setData:emptyBuffer forKey:key];
+ _db.currentTransaction->Put(key, emptyBuffer);
}
return batch;
@@ -302,7 +285,7 @@ static ReadOptions StandardReadOptions() {
std::string key = [self mutationKeyForBatchID:batchID];
std::string value;
- Status status = _db->Get(StandardReadOptions(), key, &value);
+ Status status = _db.currentTransaction->Get(key, &value);
if (!status.ok()) {
if (status.IsNotFound()) {
return nil;
@@ -315,15 +298,14 @@ static ReadOptions StandardReadOptions() {
}
- (nullable FSTMutationBatch *)nextMutationBatchAfterBatchID:(FSTBatchID)batchID {
- std::string key = [self mutationKeyForBatchID:batchID + 1];
- std::unique_ptr<Iterator> it(_db->NewIterator(StandardReadOptions()));
- it->Seek(key);
+ // All batches with batchID <= self.metadata.lastAcknowledgedBatchId have been acknowledged so
+ // the first unacknowledged batch after batchID will have a batchID larger than both of these
+ // values.
+ FSTBatchID nextBatchID = MAX(batchID, self.metadata.lastAcknowledgedBatchId) + 1;
- Status status = it->status();
- if (!status.ok()) {
- FSTFail(@"Seek to mutation batch (%@, %d) failed with status: %s", self.userID, batchID,
- status.ToString().c_str());
- }
+ std::string key = [self mutationKeyForBatchID:nextBatchID];
+ auto it = _db.currentTransaction->NewIterator();
+ it->Seek(key);
FSTLevelDBMutationKey *rowKey = [[FSTLevelDBMutationKey alloc] init];
if (!it->Valid() || ![rowKey decodeKey:it->key()]) {
@@ -336,7 +318,7 @@ static ReadOptions StandardReadOptions() {
return nil;
}
- FSTAssert(rowKey.batchID > batchID, @"Should have found mutation after %d", batchID);
+ FSTAssert(rowKey.batchID >= nextBatchID, @"Should have found mutation after %d", nextBatchID);
return [self decodedMutationBatch:it->value()];
}
@@ -344,7 +326,7 @@ static ReadOptions StandardReadOptions() {
std::string userKey = [FSTLevelDBMutationKey keyPrefixWithUserID:self.userID];
const char *userID = [self.userID UTF8String];
- std::unique_ptr<Iterator> it(_db->NewIterator(StandardReadOptions()));
+ auto it = _db.currentTransaction->NewIterator();
it->Seek(userKey);
NSMutableArray *result = [NSMutableArray array];
@@ -361,43 +343,36 @@ static ReadOptions StandardReadOptions() {
[result addObject:[self decodedMutationBatch:it->value()]];
}
- Status status = it->status();
- if (!status.ok()) {
- FSTFail(@"Find all mutations through mutation batch (%@, %d) failed with status: %s",
- self.userID, batchID, status.ToString().c_str());
- }
-
return result;
}
- (NSArray<FSTMutationBatch *> *)allMutationBatchesAffectingDocumentKey:
- (FSTDocumentKey *)documentKey {
+ (const DocumentKey &)documentKey {
NSString *userID = self.userID;
// Scan the document-mutation index starting with a prefix starting with the given documentKey.
- std::string indexPrefix =
- [FSTLevelDBDocumentMutationKey keyPrefixWithUserID:self.userID resourcePath:documentKey.path];
- std::unique_ptr<Iterator> indexIterator(_db->NewIterator(StandardReadOptions()));
+ std::string indexPrefix = [FSTLevelDBDocumentMutationKey keyPrefixWithUserID:self.userID
+ resourcePath:documentKey.path()];
+ auto indexIterator = _db.currentTransaction->NewIterator();
indexIterator->Seek(indexPrefix);
// Simultaneously scan the mutation queue. This works because each (key, batchID) pair is unique
// and ordered, so when scanning a table prefixed by exactly key, all the batchIDs encountered
// will be unique and in order.
std::string mutationsPrefix = [FSTLevelDBMutationKey keyPrefixWithUserID:userID];
- std::unique_ptr<Iterator> mutationIterator(_db->NewIterator(StandardReadOptions()));
+ auto mutationIterator = _db.currentTransaction->NewIterator();
NSMutableArray *result = [NSMutableArray array];
FSTLevelDBDocumentMutationKey *rowKey = [[FSTLevelDBDocumentMutationKey alloc] init];
for (; indexIterator->Valid(); indexIterator->Next()) {
- Slice indexKey = indexIterator->key();
-
// Only consider rows matching exactly the specific key of interest. Note that because we order
// by path first, and we order terminators before path separators, we'll encounter all the
// index rows for documentKey contiguously. In particular, all the rows for documentKey will
// occur before any rows for documents nested in a subcollection beneath documentKey so we can
// stop as soon as we hit any such row.
- if (!indexKey.starts_with(indexPrefix) || ![rowKey decodeKey:indexKey] ||
- ![rowKey.documentKey isEqualToKey:documentKey]) {
+ if (!absl::StartsWith(indexIterator->key(), indexPrefix) ||
+ ![rowKey decodeKey:indexIterator->key()] ||
+ DocumentKey{rowKey.documentKey} != documentKey) {
break;
}
@@ -413,8 +388,8 @@ static ReadOptions StandardReadOptions() {
FSTFail(
@"Dangling document-mutation reference found: "
@"%@ points to %@; seeking there found %@",
- [FSTLevelDBKey descriptionForKey:indexKey], [FSTLevelDBKey descriptionForKey:mutationKey],
- foundKeyDescription);
+ [FSTLevelDBKey descriptionForKey:indexIterator->key()],
+ [FSTLevelDBKey descriptionForKey:mutationKey], foundKeyDescription);
}
[result addObject:[self decodedMutationBatch:mutationIterator->value()]];
@@ -426,8 +401,8 @@ static ReadOptions StandardReadOptions() {
FSTAssert(![query isDocumentQuery], @"Document queries shouldn't go down this path");
NSString *userID = self.userID;
- FSTResourcePath *queryPath = query.path;
- int immediateChildrenPathLength = queryPath.length + 1;
+ const ResourcePath &queryPath = query.path;
+ size_t immediateChildrenPathLength = queryPath.size() + 1;
// TODO(mcg): Actually implement a single-collection query
//
@@ -444,7 +419,7 @@ static ReadOptions StandardReadOptions() {
// unique nor in order. This means an efficient simultaneous scan isn't possible.
std::string indexPrefix =
[FSTLevelDBDocumentMutationKey keyPrefixWithUserID:self.userID resourcePath:queryPath];
- std::unique_ptr<Iterator> indexIterator(_db->NewIterator(StandardReadOptions()));
+ auto indexIterator = _db.currentTransaction->NewIterator();
indexIterator->Seek(indexPrefix);
NSMutableArray *result = [NSMutableArray array];
@@ -458,16 +433,15 @@ static ReadOptions StandardReadOptions() {
// numbers of keys but > 30% faster for larger numbers of keys.
std::set<FSTBatchID> uniqueBatchIds;
for (; indexIterator->Valid(); indexIterator->Next()) {
- Slice indexKey = indexIterator->key();
-
- if (!indexKey.starts_with(indexPrefix) || ![rowKey decodeKey:indexKey]) {
+ if (!absl::StartsWith(indexIterator->key(), indexPrefix) ||
+ ![rowKey decodeKey:indexIterator->key()]) {
break;
}
// Rows with document keys more than one segment longer than the query path can't be matches.
// For example, a query on 'rooms' can't match the document /rooms/abc/messages/xyx.
// TODO(mcg): we'll need a different scanner when we implement ancestor queries.
- if (rowKey.documentKey.path.length != immediateChildrenPathLength) {
+ if (rowKey.documentKey.path.size() != immediateChildrenPathLength) {
continue;
}
@@ -476,7 +450,7 @@ static ReadOptions StandardReadOptions() {
// Given an ordered set of unique batchIDs perform a skipping scan over the main table to find
// the mutation batches.
- std::unique_ptr<Iterator> mutationIterator(_db->NewIterator(StandardReadOptions()));
+ auto mutationIterator = _db.currentTransaction->NewIterator();
for (FSTBatchID batchID : uniqueBatchIds) {
std::string mutationKey = [FSTLevelDBMutationKey keyWithUserID:userID batchID:batchID];
@@ -500,27 +474,22 @@ static ReadOptions StandardReadOptions() {
- (NSArray<FSTMutationBatch *> *)allMutationBatches {
std::string userKey = [FSTLevelDBMutationKey keyPrefixWithUserID:self.userID];
- std::unique_ptr<Iterator> it(_db->NewIterator(StandardReadOptions()));
+ auto it = _db.currentTransaction->NewIterator();
it->Seek(userKey);
NSMutableArray *result = [NSMutableArray array];
- for (; it->Valid() && it->key().starts_with(userKey); it->Next()) {
+ for (; it->Valid() && absl::StartsWith(it->key(), userKey); it->Next()) {
[result addObject:[self decodedMutationBatch:it->value()]];
}
- Status status = it->status();
- if (!status.ok()) {
- FSTFail(@"Find all mutation batches failed with status: %s", status.ToString().c_str());
- }
-
return result;
}
-- (void)removeMutationBatches:(NSArray<FSTMutationBatch *> *)batches group:(FSTWriteGroup *)group {
+- (void)removeMutationBatches:(NSArray<FSTMutationBatch *> *)batches {
NSString *userID = self.userID;
id<FSTGarbageCollector> garbageCollector = self.garbageCollector;
- std::unique_ptr<Iterator> checkIterator(_db->NewIterator(StandardReadOptions()));
+ auto checkIterator = _db.currentTransaction->NewIterator();
for (FSTMutationBatch *batch in batches) {
FSTBatchID batchID = batch.batchID;
@@ -535,13 +504,13 @@ static ReadOptions StandardReadOptions() {
[FSTLevelDBKey descriptionForKey:key],
[FSTLevelDBKey descriptionForKey:checkIterator->key()]);
- [group removeMessageForKey:key];
+ _db.currentTransaction->Delete(key);
for (FSTMutation *mutation in batch.mutations) {
key = [FSTLevelDBDocumentMutationKey keyWithUserID:userID
documentKey:mutation.key
batchID:batchID];
- [group removeMessageForKey:key];
+ _db.currentTransaction->Delete(key);
[garbageCollector addPotentialGarbageKey:mutation.key];
}
}
@@ -554,20 +523,18 @@ static ReadOptions StandardReadOptions() {
// Verify that there are no entries in the document-mutation index if the queue is empty.
std::string indexPrefix = [FSTLevelDBDocumentMutationKey keyPrefixWithUserID:self.userID];
- std::unique_ptr<Iterator> indexIterator(_db->NewIterator(StandardReadOptions()));
+ auto indexIterator = _db.currentTransaction->NewIterator();
indexIterator->Seek(indexPrefix);
NSMutableArray<NSString *> *danglingMutationReferences = [NSMutableArray array];
for (; indexIterator->Valid(); indexIterator->Next()) {
- Slice indexKey = indexIterator->key();
-
// Only consider rows matching this index prefix for the current user.
- if (!indexKey.starts_with(indexPrefix)) {
+ if (!absl::StartsWith(indexIterator->key(), indexPrefix)) {
break;
}
- [danglingMutationReferences addObject:[FSTLevelDBKey descriptionForKey:indexKey]];
+ [danglingMutationReferences addObject:[FSTLevelDBKey descriptionForKey:indexIterator->key()]];
}
FSTAssert(danglingMutationReferences.count == 0,
@@ -598,9 +565,10 @@ static ReadOptions StandardReadOptions() {
return proto;
}
-- (FSTMutationBatch *)decodedMutationBatch:(Slice)slice {
- NSData *data =
- [[NSData alloc] initWithBytesNoCopy:(void *)slice.data() length:slice.size() freeWhenDone:NO];
+- (FSTMutationBatch *)decodedMutationBatch:(absl::string_view)encoded {
+ NSData *data = [[NSData alloc] initWithBytesNoCopy:(void *)encoded.data()
+ length:encoded.size()
+ freeWhenDone:NO];
NSError *error;
FSTPBWriteBatch *proto = [FSTPBWriteBatch parseFromData:data error:&error];
@@ -613,20 +581,19 @@ static ReadOptions StandardReadOptions() {
#pragma mark - FSTGarbageSource implementation
-- (BOOL)containsKey:(FSTDocumentKey *)documentKey {
- std::string indexPrefix =
- [FSTLevelDBDocumentMutationKey keyPrefixWithUserID:self.userID resourcePath:documentKey.path];
- std::unique_ptr<Iterator> indexIterator(_db->NewIterator(StandardReadOptions()));
+- (BOOL)containsKey:(const DocumentKey &)documentKey {
+ std::string indexPrefix = [FSTLevelDBDocumentMutationKey keyPrefixWithUserID:self.userID
+ resourcePath:documentKey.path()];
+ auto indexIterator = _db.currentTransaction->NewIterator();
indexIterator->Seek(indexPrefix);
if (indexIterator->Valid()) {
FSTLevelDBDocumentMutationKey *rowKey = [[FSTLevelDBDocumentMutationKey alloc] init];
- Slice iteratorKey = indexIterator->key();
// Check both that the key prefix matches and that the decoded document key is exactly the key
// we're looking for.
- if (iteratorKey.starts_with(indexPrefix) && [rowKey decodeKey:iteratorKey] &&
- [rowKey.documentKey isEqualToKey:documentKey]) {
+ if (absl::StartsWith(indexIterator->key(), indexPrefix) &&
+ [rowKey decodeKey:indexIterator->key()] && DocumentKey{rowKey.documentKey} == documentKey) {
return YES;
}
}
diff --git a/Firestore/Source/Local/FSTLevelDBQueryCache.h b/Firestore/Source/Local/FSTLevelDBQueryCache.h
index 6d5cd60..2cd6758 100644
--- a/Firestore/Source/Local/FSTLevelDBQueryCache.h
+++ b/Firestore/Source/Local/FSTLevelDBQueryCache.h
@@ -16,17 +16,15 @@
#import <Foundation/Foundation.h>
-#import "Firestore/Source/Local/FSTQueryCache.h"
-
-#ifdef __cplusplus
#include <memory>
-namespace leveldb {
-class DB;
-}
-#endif
+#import "Firestore/Source/Local/FSTQueryCache.h"
+#include "Firestore/core/src/firebase/firestore/local/leveldb_transaction.h"
+#include "leveldb/db.h"
+@class FSTLevelDB;
@class FSTLocalSerializer;
+@class FSTPBTargetGlobal;
@protocol FSTGarbageCollector;
NS_ASSUME_NONNULL_BEGIN
@@ -34,20 +32,30 @@ NS_ASSUME_NONNULL_BEGIN
/** Cached Queries backed by LevelDB. */
@interface FSTLevelDBQueryCache : NSObject <FSTQueryCache>
+/**
+ * Retrieves the global singleton metadata row from the given database, if it exists.
+ * TODO(gsoltis): remove this method once fully ported to transactions.
+ */
++ (nullable FSTPBTargetGlobal *)readTargetMetadataFromDB:(std::shared_ptr<leveldb::DB>)db;
+
+/**
+ * Retrieves the global singleton metadata row using the given transaction, if it exists.
+ */
++ (nullable FSTPBTargetGlobal *)readTargetMetadataWithTransaction:
+ (firebase::firestore::local::LevelDbTransaction *)transaction;
+
- (instancetype)init NS_UNAVAILABLE;
/** The garbage collector to notify about potential garbage keys. */
@property(nonatomic, weak, readwrite, nullable) id<FSTGarbageCollector> garbageCollector;
-#ifdef __cplusplus
/**
* Creates a new query cache in the given LevelDB.
*
* @param db The LevelDB in which to create the cache.
*/
-- (instancetype)initWithDB:(std::shared_ptr<leveldb::DB>)db
+- (instancetype)initWithDB:(FSTLevelDB *)db
serializer:(FSTLocalSerializer *)serializer NS_DESIGNATED_INITIALIZER;
-#endif
@end
diff --git a/Firestore/Source/Local/FSTLevelDBQueryCache.mm b/Firestore/Source/Local/FSTLevelDBQueryCache.mm
index 13d15ee..5fde7d7 100644
--- a/Firestore/Source/Local/FSTLevelDBQueryCache.mm
+++ b/Firestore/Source/Local/FSTLevelDBQueryCache.mm
@@ -16,43 +16,28 @@
#import "Firestore/Source/Local/FSTLevelDBQueryCache.h"
-#include <leveldb/db.h>
-#include <leveldb/write_batch.h>
+#include <memory>
#include <string>
#import "Firestore/Protos/objc/firestore/local/Target.pbobjc.h"
#import "Firestore/Source/Core/FSTQuery.h"
+#import "Firestore/Source/Local/FSTLevelDB.h"
#import "Firestore/Source/Local/FSTLevelDBKey.h"
#import "Firestore/Source/Local/FSTLocalSerializer.h"
#import "Firestore/Source/Local/FSTQueryData.h"
-#import "Firestore/Source/Local/FSTWriteGroup.h"
-#import "Firestore/Source/Model/FSTDocumentKey.h"
#import "Firestore/Source/Util/FSTAssert.h"
-#include "Firestore/Port/ordered_code.h"
-#include "Firestore/Port/string_util.h"
+#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+#include "absl/strings/match.h"
NS_ASSUME_NONNULL_BEGIN
-using Firestore::OrderedCode;
+using firebase::firestore::local::LevelDbTransaction;
using Firestore::StringView;
+using firebase::firestore::model::DocumentKey;
using leveldb::DB;
-using leveldb::Iterator;
-using leveldb::ReadOptions;
using leveldb::Slice;
using leveldb::Status;
-using leveldb::WriteOptions;
-
-/**
- * Returns a standard set of read options.
- *
- * For now this is paranoid, but perhaps disable that in production builds.
- */
-static ReadOptions GetStandardReadOptions() {
- ReadOptions options;
- options.verify_checksums = true;
- return options;
-}
@interface FSTLevelDBQueryCache ()
@@ -64,8 +49,7 @@ static ReadOptions GetStandardReadOptions() {
@end
@implementation FSTLevelDBQueryCache {
- // The DB pointer is shared with all cooperating LevelDB-related objects.
- std::shared_ptr<DB> _db;
+ FSTLevelDB *_db;
/**
* The last received snapshot version. This is part of `metadata` but we store it separately to
@@ -74,7 +58,54 @@ static ReadOptions GetStandardReadOptions() {
FSTSnapshotVersion *_lastRemoteSnapshotVersion;
}
-- (instancetype)initWithDB:(std::shared_ptr<DB>)db serializer:(FSTLocalSerializer *)serializer {
++ (nullable FSTPBTargetGlobal *)readTargetMetadataWithTransaction:
+ (firebase::firestore::local::LevelDbTransaction *)transaction {
+ std::string key = [FSTLevelDBTargetGlobalKey key];
+ std::string value;
+ Status status = transaction->Get(key, &value);
+ if (status.IsNotFound()) {
+ return nil;
+ } else if (!status.ok()) {
+ FSTFail(@"metadataForKey: failed loading key %s with status: %s", key.c_str(),
+ status.ToString().c_str());
+ }
+
+ NSData *data =
+ [[NSData alloc] initWithBytesNoCopy:(void *)value.data() length:value.size() freeWhenDone:NO];
+
+ NSError *error;
+ FSTPBTargetGlobal *proto = [FSTPBTargetGlobal parseFromData:data error:&error];
+ if (!proto) {
+ FSTFail(@"FSTPBTargetGlobal failed to parse: %@", error);
+ }
+
+ return proto;
+}
+
++ (nullable FSTPBTargetGlobal *)readTargetMetadataFromDB:(std::shared_ptr<DB>)db {
+ std::string key = [FSTLevelDBTargetGlobalKey key];
+ std::string value;
+ Status status = db->Get([FSTLevelDB standardReadOptions], key, &value);
+ if (status.IsNotFound()) {
+ return nil;
+ } else if (!status.ok()) {
+ FSTFail(@"metadataForKey: failed loading key %s with status: %s", key.c_str(),
+ status.ToString().c_str());
+ }
+
+ NSData *data =
+ [[NSData alloc] initWithBytesNoCopy:(void *)value.data() length:value.size() freeWhenDone:NO];
+
+ NSError *error;
+ FSTPBTargetGlobal *proto = [FSTPBTargetGlobal parseFromData:data error:&error];
+ if (!proto) {
+ FSTFail(@"FSTPBTargetGlobal failed to parse: %@", error);
+ }
+
+ return proto;
+}
+
+- (instancetype)initWithDB:(FSTLevelDB *)db serializer:(FSTLocalSerializer *)serializer {
if (self = [super init]) {
FSTAssert(db, @"db must not be NULL");
_db = db;
@@ -84,11 +115,11 @@ static ReadOptions GetStandardReadOptions() {
}
- (void)start {
- std::string key = [FSTLevelDBTargetGlobalKey key];
- FSTPBTargetGlobal *metadata = [self metadataForKey:key];
- if (!metadata) {
- metadata = [FSTPBTargetGlobal message];
- }
+ // TODO(gsoltis): switch this usage of ptr to currentTransaction
+ FSTPBTargetGlobal *metadata = [FSTLevelDBQueryCache readTargetMetadataFromDB:_db.ptr];
+ FSTAssert(
+ metadata != nil,
+ @"Found nil metadata, expected schema to be at version 0 which ensures metadata existence");
_lastRemoteSnapshotVersion = [self.serializer decodedVersion:metadata.lastRemoteSnapshotVersion];
self.metadata = metadata;
@@ -100,88 +131,90 @@ static ReadOptions GetStandardReadOptions() {
return self.metadata.highestTargetId;
}
+- (FSTListenSequenceNumber)highestListenSequenceNumber {
+ return self.metadata.highestListenSequenceNumber;
+}
+
- (FSTSnapshotVersion *)lastRemoteSnapshotVersion {
return _lastRemoteSnapshotVersion;
}
-- (void)setLastRemoteSnapshotVersion:(FSTSnapshotVersion *)snapshotVersion
- group:(FSTWriteGroup *)group {
+- (void)setLastRemoteSnapshotVersion:(FSTSnapshotVersion *)snapshotVersion {
_lastRemoteSnapshotVersion = snapshotVersion;
self.metadata.lastRemoteSnapshotVersion = [self.serializer encodedVersion:snapshotVersion];
- [group setMessage:self.metadata forKey:[FSTLevelDBTargetGlobalKey key]];
-}
-
-- (void)shutdown {
- _db.reset();
+ _db.currentTransaction->Put([FSTLevelDBTargetGlobalKey key], self.metadata);
}
-- (void)addQueryData:(FSTQueryData *)queryData group:(FSTWriteGroup *)group {
- // TODO(mcg): actually populate listen sequence number
+- (void)saveQueryData:(FSTQueryData *)queryData {
FSTTargetID targetID = queryData.targetID;
std::string key = [FSTLevelDBTargetKey keyWithTargetID:targetID];
- [group setMessage:[self.serializer encodedQueryData:queryData] forKey:key];
+ _db.currentTransaction->Put(key, [self.serializer encodedQueryData:queryData]);
+}
+
+- (BOOL)updateMetadataForQueryData:(FSTQueryData *)queryData {
+ BOOL updatedMetadata = NO;
+
+ if (queryData.targetID > self.metadata.highestTargetId) {
+ self.metadata.highestTargetId = queryData.targetID;
+ updatedMetadata = YES;
+ }
+
+ if (queryData.sequenceNumber > self.metadata.highestListenSequenceNumber) {
+ self.metadata.highestListenSequenceNumber = queryData.sequenceNumber;
+ updatedMetadata = YES;
+ }
+ return updatedMetadata;
+}
+
+- (void)addQueryData:(FSTQueryData *)queryData {
+ [self saveQueryData:queryData];
NSString *canonicalID = queryData.query.canonicalID;
std::string indexKey =
- [FSTLevelDBQueryTargetKey keyWithCanonicalID:canonicalID targetID:targetID];
+ [FSTLevelDBQueryTargetKey keyWithCanonicalID:canonicalID targetID:queryData.targetID];
std::string emptyBuffer;
- [group setData:emptyBuffer forKey:indexKey];
+ _db.currentTransaction->Put(indexKey, emptyBuffer);
- FSTPBTargetGlobal *metadata = self.metadata;
- if (targetID > metadata.highestTargetId) {
- metadata.highestTargetId = targetID;
- [group setMessage:metadata forKey:[FSTLevelDBTargetGlobalKey key]];
+ self.metadata.targetCount += 1;
+ [self updateMetadataForQueryData:queryData];
+ _db.currentTransaction->Put([FSTLevelDBTargetGlobalKey key], self.metadata);
+}
+
+- (void)updateQueryData:(FSTQueryData *)queryData {
+ [self saveQueryData:queryData];
+
+ if ([self updateMetadataForQueryData:queryData]) {
+ _db.currentTransaction->Put([FSTLevelDBTargetGlobalKey key], self.metadata);
}
}
-- (void)removeQueryData:(FSTQueryData *)queryData group:(FSTWriteGroup *)group {
+- (void)removeQueryData:(FSTQueryData *)queryData {
FSTTargetID targetID = queryData.targetID;
- [self removeMatchingKeysForTargetID:targetID group:group];
+ [self removeMatchingKeysForTargetID:targetID];
std::string key = [FSTLevelDBTargetKey keyWithTargetID:targetID];
- [group removeMessageForKey:key];
+ _db.currentTransaction->Delete(key);
std::string indexKey =
[FSTLevelDBQueryTargetKey keyWithCanonicalID:queryData.query.canonicalID targetID:targetID];
- [group removeMessageForKey:indexKey];
+ _db.currentTransaction->Delete(indexKey);
+ self.metadata.targetCount -= 1;
+ _db.currentTransaction->Put([FSTLevelDBTargetGlobalKey key], self.metadata);
}
-/**
- * Looks up the query global metadata associated with the given key.
- *
- * @return the parsed protocol buffer message or nil if the row referenced by the given key does
- * not exist.
- */
-- (nullable FSTPBTargetGlobal *)metadataForKey:(const std::string &)key {
- std::string value;
- Status status = _db->Get(GetStandardReadOptions(), key, &value);
- if (status.IsNotFound()) {
- return nil;
- } else if (!status.ok()) {
- FSTFail(@"metadataForKey: failed loading key %s with status: %s", key.c_str(),
- status.ToString().c_str());
- }
-
- NSData *data =
- [[NSData alloc] initWithBytesNoCopy:(void *)value.data() length:value.size() freeWhenDone:NO];
-
- NSError *error;
- FSTPBTargetGlobal *proto = [FSTPBTargetGlobal parseFromData:data error:&error];
- if (!proto) {
- FSTFail(@"FSTPBTargetGlobal failed to parse: %@", error);
- }
-
- return proto;
+- (int32_t)count {
+ return self.metadata.targetCount;
}
/**
* Parses the given bytes as an FSTPBTarget protocol buffer and then converts to the equivalent
* query data.
*/
-- (FSTQueryData *)decodedTargetWithSlice:(Slice)slice {
- NSData *data =
- [[NSData alloc] initWithBytesNoCopy:(void *)slice.data() length:slice.size() freeWhenDone:NO];
+- (FSTQueryData *)decodeTarget:(absl::string_view)encoded {
+ NSData *data = [[NSData alloc] initWithBytesNoCopy:(void *)encoded.data()
+ length:encoded.size()
+ freeWhenDone:NO];
NSError *error;
FSTPBTarget *proto = [FSTPBTarget parseFromData:data error:&error];
@@ -197,7 +230,7 @@ static ReadOptions GetStandardReadOptions() {
// Note that this is a scan rather than a get because canonicalIDs are not required to be unique
// per target.
Slice canonicalID = StringView(query.canonicalID);
- std::unique_ptr<Iterator> indexItererator(_db->NewIterator(GetStandardReadOptions()));
+ auto indexItererator = _db.currentTransaction->NewIterator();
std::string indexPrefix = [FSTLevelDBQueryTargetKey keyPrefixWithCanonicalID:canonicalID];
indexItererator->Seek(indexPrefix);
@@ -205,15 +238,13 @@ static ReadOptions GetStandardReadOptions() {
// unique and ordered, so when scanning a table prefixed by exactly one canonicalID, all the
// targetIDs will be unique and in order.
std::string targetPrefix = [FSTLevelDBTargetKey keyPrefix];
- std::unique_ptr<Iterator> targetIterator(_db->NewIterator(GetStandardReadOptions()));
+ auto targetIterator = _db.currentTransaction->NewIterator();
FSTLevelDBQueryTargetKey *rowKey = [[FSTLevelDBQueryTargetKey alloc] init];
for (; indexItererator->Valid(); indexItererator->Next()) {
- Slice indexKey = indexItererator->key();
-
// Only consider rows matching exactly the specific canonicalID of interest.
- if (!indexKey.starts_with(indexPrefix) || ![rowKey decodeKey:indexKey] ||
- canonicalID != rowKey.canonicalID) {
+ if (!absl::StartsWith(indexItererator->key(), indexPrefix) ||
+ ![rowKey decodeKey:indexItererator->key()] || canonicalID != rowKey.canonicalID) {
// End of this canonicalID's possible targets.
break;
}
@@ -230,13 +261,13 @@ static ReadOptions GetStandardReadOptions() {
FSTFail(
@"Dangling query-target reference found: "
@"%@ points to %@; seeking there found %@",
- [FSTLevelDBKey descriptionForKey:indexKey], [FSTLevelDBKey descriptionForKey:targetKey],
- foundKeyDescription);
+ [FSTLevelDBKey descriptionForKey:indexItererator->key()],
+ [FSTLevelDBKey descriptionForKey:targetKey], foundKeyDescription);
}
// Finally after finding a potential match, check that the query is actually equal to the
// requested query.
- FSTQueryData *target = [self decodedTargetWithSlice:targetIterator->value()];
+ FSTQueryData *target = [self decodeTarget:targetIterator->value()];
if ([target.query isEqual:query]) {
return target;
}
@@ -247,66 +278,64 @@ static ReadOptions GetStandardReadOptions() {
#pragma mark Matching Key tracking
-- (void)addMatchingKeys:(FSTDocumentKeySet *)keys
- forTargetID:(FSTTargetID)targetID
- group:(FSTWriteGroup *)group {
+- (void)addMatchingKeys:(FSTDocumentKeySet *)keys forTargetID:(FSTTargetID)targetID {
// Store an empty value in the index which is equivalent to serializing a GPBEmpty message. In the
// future if we wanted to store some other kind of value here, we can parse these empty values as
// with some other protocol buffer (and the parser will see all default values).
std::string emptyBuffer;
[keys enumerateObjectsUsingBlock:^(FSTDocumentKey *documentKey, BOOL *stop) {
- [group setData:emptyBuffer
- forKey:[FSTLevelDBTargetDocumentKey keyWithTargetID:targetID documentKey:documentKey]];
- [group setData:emptyBuffer
- forKey:[FSTLevelDBDocumentTargetKey keyWithDocumentKey:documentKey targetID:targetID]];
+ self->_db.currentTransaction->Put(
+ [FSTLevelDBTargetDocumentKey keyWithTargetID:targetID documentKey:documentKey],
+ emptyBuffer);
+ self->_db.currentTransaction->Put(
+ [FSTLevelDBDocumentTargetKey keyWithDocumentKey:documentKey targetID:targetID],
+ emptyBuffer);
}];
}
-- (void)removeMatchingKeys:(FSTDocumentKeySet *)keys
- forTargetID:(FSTTargetID)targetID
- group:(FSTWriteGroup *)group {
+- (void)removeMatchingKeys:(FSTDocumentKeySet *)keys forTargetID:(FSTTargetID)targetID {
[keys enumerateObjectsUsingBlock:^(FSTDocumentKey *key, BOOL *stop) {
- [group
- removeMessageForKey:[FSTLevelDBTargetDocumentKey keyWithTargetID:targetID documentKey:key]];
- [group
- removeMessageForKey:[FSTLevelDBDocumentTargetKey keyWithDocumentKey:key targetID:targetID]];
+ self->_db.currentTransaction->Delete(
+ [FSTLevelDBTargetDocumentKey keyWithTargetID:targetID documentKey:key]);
+ self->_db.currentTransaction->Delete(
+ [FSTLevelDBDocumentTargetKey keyWithDocumentKey:key targetID:targetID]);
[self.garbageCollector addPotentialGarbageKey:key];
}];
}
-- (void)removeMatchingKeysForTargetID:(FSTTargetID)targetID group:(FSTWriteGroup *)group {
+- (void)removeMatchingKeysForTargetID:(FSTTargetID)targetID {
std::string indexPrefix = [FSTLevelDBTargetDocumentKey keyPrefixWithTargetID:targetID];
- std::unique_ptr<Iterator> indexIterator(_db->NewIterator(GetStandardReadOptions()));
+ auto indexIterator = _db.currentTransaction->NewIterator();
indexIterator->Seek(indexPrefix);
FSTLevelDBTargetDocumentKey *rowKey = [[FSTLevelDBTargetDocumentKey alloc] init];
for (; indexIterator->Valid(); indexIterator->Next()) {
- Slice indexKey = indexIterator->key();
+ absl::string_view indexKey = indexIterator->key();
// Only consider rows matching this specific targetID.
if (![rowKey decodeKey:indexKey] || rowKey.targetID != targetID) {
break;
}
- FSTDocumentKey *documentKey = rowKey.documentKey;
+ const DocumentKey &documentKey = rowKey.documentKey;
// Delete both index rows
- [group removeMessageForKey:indexKey];
- [group removeMessageForKey:[FSTLevelDBDocumentTargetKey keyWithDocumentKey:documentKey
- targetID:targetID]];
+ _db.currentTransaction->Delete(indexKey);
+ _db.currentTransaction->Delete(
+ [FSTLevelDBDocumentTargetKey keyWithDocumentKey:documentKey targetID:targetID]);
[self.garbageCollector addPotentialGarbageKey:documentKey];
}
}
- (FSTDocumentKeySet *)matchingKeysForTargetID:(FSTTargetID)targetID {
std::string indexPrefix = [FSTLevelDBTargetDocumentKey keyPrefixWithTargetID:targetID];
- std::unique_ptr<Iterator> indexIterator(_db->NewIterator(GetStandardReadOptions()));
+ auto indexIterator = _db.currentTransaction->NewIterator();
indexIterator->Seek(indexPrefix);
FSTDocumentKeySet *result = [FSTDocumentKeySet keySet];
FSTLevelDBTargetDocumentKey *rowKey = [[FSTLevelDBTargetDocumentKey alloc] init];
for (; indexIterator->Valid(); indexIterator->Next()) {
- Slice indexKey = indexIterator->key();
+ absl::string_view indexKey = indexIterator->key();
// Only consider rows matching this specific targetID.
if (![rowKey decodeKey:indexKey] || rowKey.targetID != targetID) {
@@ -321,14 +350,14 @@ static ReadOptions GetStandardReadOptions() {
#pragma mark - FSTGarbageSource implementation
-- (BOOL)containsKey:(FSTDocumentKey *)key {
- std::string indexPrefix = [FSTLevelDBDocumentTargetKey keyPrefixWithResourcePath:key.path];
- std::unique_ptr<Iterator> indexIterator(_db->NewIterator(GetStandardReadOptions()));
+- (BOOL)containsKey:(const DocumentKey &)key {
+ std::string indexPrefix = [FSTLevelDBDocumentTargetKey keyPrefixWithResourcePath:key.path()];
+ auto indexIterator = _db.currentTransaction->NewIterator();
indexIterator->Seek(indexPrefix);
if (indexIterator->Valid()) {
FSTLevelDBDocumentTargetKey *rowKey = [[FSTLevelDBDocumentTargetKey alloc] init];
- if ([rowKey decodeKey:indexIterator->key()] && [rowKey.documentKey isEqualToKey:key]) {
+ if ([rowKey decodeKey:indexIterator->key()] && DocumentKey{rowKey.documentKey} == key) {
return YES;
}
}
diff --git a/Firestore/Source/Local/FSTLevelDBRemoteDocumentCache.h b/Firestore/Source/Local/FSTLevelDBRemoteDocumentCache.h
index 1da3cca..381d308 100644
--- a/Firestore/Source/Local/FSTLevelDBRemoteDocumentCache.h
+++ b/Firestore/Source/Local/FSTLevelDBRemoteDocumentCache.h
@@ -16,16 +16,11 @@
#import <Foundation/Foundation.h>
-#import "Firestore/Source/Local/FSTRemoteDocumentCache.h"
-
-#ifdef __cplusplus
#include <memory>
-namespace leveldb {
-class DB;
-}
-#endif
+#import "Firestore/Source/Local/FSTRemoteDocumentCache.h"
+@class FSTLevelDB;
@class FSTLocalSerializer;
NS_ASSUME_NONNULL_BEGIN
@@ -35,15 +30,13 @@ NS_ASSUME_NONNULL_BEGIN
- (instancetype)init NS_UNAVAILABLE;
-#ifdef __cplusplus
/**
* Creates a new remote documents cache in the given leveldb.
*
* @param db The leveldb in which to create the cache.
*/
-- (instancetype)initWithDB:(std::shared_ptr<leveldb::DB>)db
+- (instancetype)initWithDB:(FSTLevelDB *)db
serializer:(FSTLocalSerializer *)serializer NS_DESIGNATED_INITIALIZER;
-#endif
@end
diff --git a/Firestore/Source/Local/FSTLevelDBRemoteDocumentCache.mm b/Firestore/Source/Local/FSTLevelDBRemoteDocumentCache.mm
index 02f9f3e..f655e3a 100644
--- a/Firestore/Source/Local/FSTLevelDBRemoteDocumentCache.mm
+++ b/Firestore/Source/Local/FSTLevelDBRemoteDocumentCache.mm
@@ -16,34 +16,29 @@
#import "Firestore/Source/Local/FSTLevelDBRemoteDocumentCache.h"
-#include <leveldb/db.h>
-#include <leveldb/write_batch.h>
#include <string>
#import "Firestore/Protos/objc/firestore/local/MaybeDocument.pbobjc.h"
#import "Firestore/Source/Core/FSTQuery.h"
+#import "Firestore/Source/Local/FSTLevelDB.h"
#import "Firestore/Source/Local/FSTLevelDBKey.h"
#import "Firestore/Source/Local/FSTLocalSerializer.h"
-#import "Firestore/Source/Local/FSTWriteGroup.h"
#import "Firestore/Source/Model/FSTDocument.h"
#import "Firestore/Source/Model/FSTDocumentDictionary.h"
-#import "Firestore/Source/Model/FSTDocumentKey.h"
#import "Firestore/Source/Model/FSTDocumentSet.h"
-#import "Firestore/Source/Model/FSTPath.h"
#import "Firestore/Source/Util/FSTAssert.h"
-#include "Firestore/Port/ordered_code.h"
-#include "Firestore/Port/string_util.h"
+#include "Firestore/core/src/firebase/firestore/local/leveldb_transaction.h"
+#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+#include "leveldb/db.h"
+#include "leveldb/write_batch.h"
NS_ASSUME_NONNULL_BEGIN
-using Firestore::OrderedCode;
+using firebase::firestore::local::LevelDbTransaction;
+using firebase::firestore::model::DocumentKey;
using leveldb::DB;
-using leveldb::Iterator;
-using leveldb::ReadOptions;
-using leveldb::Slice;
using leveldb::Status;
-using leveldb::WriteOptions;
@interface FSTLevelDBRemoteDocumentCache ()
@@ -51,23 +46,11 @@ using leveldb::WriteOptions;
@end
-/**
- * Returns a standard set of read options.
- *
- * For now this is paranoid, but perhaps disable that in production builds.
- */
-static ReadOptions StandardReadOptions() {
- ReadOptions options;
- options.verify_checksums = true;
- return options;
-}
-
@implementation FSTLevelDBRemoteDocumentCache {
- // The DB pointer is shared with all cooperating LevelDB-related objects.
- std::shared_ptr<DB> _db;
+ FSTLevelDB *_db;
}
-- (instancetype)initWithDB:(std::shared_ptr<DB>)db serializer:(FSTLocalSerializer *)serializer {
+- (instancetype)initWithDB:(FSTLevelDB *)db serializer:(FSTLocalSerializer *)serializer {
if (self = [super init]) {
_db = db;
_serializer = serializer;
@@ -75,30 +58,26 @@ static ReadOptions StandardReadOptions() {
return self;
}
-- (void)shutdown {
- _db.reset();
-}
-
-- (void)addEntry:(FSTMaybeDocument *)document group:(FSTWriteGroup *)group {
+- (void)addEntry:(FSTMaybeDocument *)document {
std::string key = [self remoteDocumentKey:document.key];
- [group setMessage:[self.serializer encodedMaybeDocument:document] forKey:key];
+ _db.currentTransaction->Put(key, [self.serializer encodedMaybeDocument:document]);
}
-- (void)removeEntryForKey:(FSTDocumentKey *)documentKey group:(FSTWriteGroup *)group {
+- (void)removeEntryForKey:(const DocumentKey &)documentKey {
std::string key = [self remoteDocumentKey:documentKey];
- [group removeMessageForKey:key];
+ _db.currentTransaction->Delete(key);
}
-- (nullable FSTMaybeDocument *)entryForKey:(FSTDocumentKey *)documentKey {
+- (nullable FSTMaybeDocument *)entryForKey:(const DocumentKey &)documentKey {
std::string key = [FSTLevelDBRemoteDocumentKey keyWithDocumentKey:documentKey];
std::string value;
- Status status = _db->Get(StandardReadOptions(), key, &value);
+ Status status = _db.currentTransaction->Get(key, &value);
if (status.IsNotFound()) {
return nil;
} else if (status.ok()) {
- return [self decodedMaybeDocument:value withKey:documentKey];
+ return [self decodeMaybeDocument:value withKey:documentKey];
} else {
- FSTFail(@"Fetch document for key (%@) failed with status: %s", documentKey,
+ FSTFail(@"Fetch document for key (%s) failed with status: %s", documentKey.ToString().c_str(),
status.ToString().c_str());
}
}
@@ -109,36 +88,32 @@ static ReadOptions StandardReadOptions() {
// Documents are ordered by key, so we can use a prefix scan to narrow down
// the documents we need to match the query against.
std::string startKey = [FSTLevelDBRemoteDocumentKey keyPrefixWithResourcePath:query.path];
- std::unique_ptr<Iterator> it(_db->NewIterator(StandardReadOptions()));
+ auto it = _db.currentTransaction->NewIterator();
it->Seek(startKey);
FSTLevelDBRemoteDocumentKey *currentKey = [[FSTLevelDBRemoteDocumentKey alloc] init];
for (; it->Valid() && [currentKey decodeKey:it->key()]; it->Next()) {
FSTMaybeDocument *maybeDoc =
- [self decodedMaybeDocument:it->value() withKey:currentKey.documentKey];
- if (![query.path isPrefixOfPath:maybeDoc.key.path]) {
+ [self decodeMaybeDocument:it->value() withKey:currentKey.documentKey];
+ if (!query.path.IsPrefixOf(maybeDoc.key.path())) {
break;
} else if ([maybeDoc isKindOfClass:[FSTDocument class]]) {
results = [results dictionaryBySettingObject:(FSTDocument *)maybeDoc forKey:maybeDoc.key];
}
}
- Status status = it->status();
- if (!status.ok()) {
- FSTFail(@"Find documents matching query (%@) failed with status: %s", query,
- status.ToString().c_str());
- }
-
return results;
}
-- (std::string)remoteDocumentKey:(FSTDocumentKey *)key {
+- (std::string)remoteDocumentKey:(const DocumentKey &)key {
return [FSTLevelDBRemoteDocumentKey keyWithDocumentKey:key];
}
-- (FSTMaybeDocument *)decodedMaybeDocument:(Slice)slice withKey:(FSTDocumentKey *)documentKey {
- NSData *data =
- [[NSData alloc] initWithBytesNoCopy:(void *)slice.data() length:slice.size() freeWhenDone:NO];
+- (FSTMaybeDocument *)decodeMaybeDocument:(absl::string_view)encoded
+ withKey:(const DocumentKey &)documentKey {
+ NSData *data = [[NSData alloc] initWithBytesNoCopy:(void *)encoded.data()
+ length:encoded.size()
+ freeWhenDone:NO];
NSError *error;
FSTPBMaybeDocument *proto = [FSTPBMaybeDocument parseFromData:data error:&error];
@@ -148,8 +123,8 @@ static ReadOptions StandardReadOptions() {
FSTMaybeDocument *maybeDocument = [self.serializer decodedMaybeDocument:proto];
FSTAssert([maybeDocument.key isEqualToKey:documentKey],
- @"Read document has key (%@) instead of expected key (%@).", maybeDocument.key,
- documentKey);
+ @"Read document has key (%s) instead of expected key (%s).",
+ maybeDocument.key.ToString().c_str(), documentKey.ToString().c_str());
return maybeDocument;
}
diff --git a/Firestore/Source/Local/FSTLocalDocumentsView.h b/Firestore/Source/Local/FSTLocalDocumentsView.h
index 46830e7..e75e0f3 100644
--- a/Firestore/Source/Local/FSTLocalDocumentsView.h
+++ b/Firestore/Source/Local/FSTLocalDocumentsView.h
@@ -19,7 +19,8 @@
#import "Firestore/Source/Model/FSTDocumentDictionary.h"
#import "Firestore/Source/Model/FSTDocumentKeySet.h"
-@class FSTDocumentKey;
+#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+
@class FSTMaybeDocument;
@class FSTQuery;
@protocol FSTMutationQueue;
@@ -44,7 +45,7 @@ NS_ASSUME_NONNULL_BEGIN
*
* @return Local view of the document or nil if we don't have any cached state for it.
*/
-- (nullable FSTMaybeDocument *)documentForKey:(FSTDocumentKey *)key;
+- (nullable FSTMaybeDocument *)documentForKey:(const firebase::firestore::model::DocumentKey &)key;
/**
* Gets the local view of the documents identified by `keys`.
diff --git a/Firestore/Source/Local/FSTLocalDocumentsView.m b/Firestore/Source/Local/FSTLocalDocumentsView.mm
index a6734c4..e9b9423 100644
--- a/Firestore/Source/Local/FSTLocalDocumentsView.m
+++ b/Firestore/Source/Local/FSTLocalDocumentsView.mm
@@ -22,11 +22,16 @@
#import "Firestore/Source/Local/FSTRemoteDocumentCache.h"
#import "Firestore/Source/Model/FSTDocument.h"
#import "Firestore/Source/Model/FSTDocumentDictionary.h"
-#import "Firestore/Source/Model/FSTDocumentKey.h"
#import "Firestore/Source/Model/FSTMutation.h"
#import "Firestore/Source/Model/FSTMutationBatch.h"
#import "Firestore/Source/Util/FSTAssert.h"
+#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+#include "Firestore/core/src/firebase/firestore/model/resource_path.h"
+
+using firebase::firestore::model::DocumentKey;
+using firebase::firestore::model::ResourcePath;
+
NS_ASSUME_NONNULL_BEGIN
@interface FSTLocalDocumentsView ()
@@ -54,7 +59,7 @@ NS_ASSUME_NONNULL_BEGIN
return self;
}
-- (nullable FSTMaybeDocument *)documentForKey:(FSTDocumentKey *)key {
+- (nullable FSTMaybeDocument *)documentForKey:(const DocumentKey &)key {
FSTMaybeDocument *_Nullable remoteDoc = [self.remoteDocumentCache entryForKey:key];
return [self localDocument:remoteDoc key:key];
}
@@ -74,17 +79,17 @@ NS_ASSUME_NONNULL_BEGIN
}
- (FSTDocumentDictionary *)documentsMatchingQuery:(FSTQuery *)query {
- if ([FSTDocumentKey isDocumentKey:query.path]) {
+ if (DocumentKey::IsDocumentKey(query.path)) {
return [self documentsMatchingDocumentQuery:query.path];
} else {
return [self documentsMatchingCollectionQuery:query];
}
}
-- (FSTDocumentDictionary *)documentsMatchingDocumentQuery:(FSTResourcePath *)docPath {
+- (FSTDocumentDictionary *)documentsMatchingDocumentQuery:(const ResourcePath &)docPath {
FSTDocumentDictionary *result = [FSTDocumentDictionary documentDictionary];
// Just do a simple document lookup.
- FSTMaybeDocument *doc = [self documentForKey:[FSTDocumentKey keyWithPath:docPath]];
+ FSTMaybeDocument *doc = [self documentForKey:DocumentKey{docPath}];
if ([doc isKindOfClass:[FSTDocument class]]) {
result = [result dictionaryBySettingObject:(FSTDocument *)doc forKey:doc.key];
}
@@ -144,7 +149,7 @@ NS_ASSUME_NONNULL_BEGIN
* @param documentKey The key of the document (necessary when remoteDocument is nil).
*/
- (nullable FSTMaybeDocument *)localDocument:(nullable FSTMaybeDocument *)document
- key:(FSTDocumentKey *)documentKey {
+ key:(const DocumentKey &)documentKey {
NSArray<FSTMutationBatch *> *batches =
[self.mutationQueue allMutationBatchesAffectingDocumentKey:documentKey];
for (FSTMutationBatch *batch in batches) {
@@ -167,9 +172,9 @@ NS_ASSUME_NONNULL_BEGIN
BOOL *stop) {
FSTMaybeDocument *mutatedDoc = [self localDocument:remoteDocument key:key];
if ([mutatedDoc isKindOfClass:[FSTDeletedDocument class]]) {
- result = [documents dictionaryByRemovingObjectForKey:key];
+ result = [result dictionaryByRemovingObjectForKey:key];
} else if ([mutatedDoc isKindOfClass:[FSTDocument class]]) {
- result = [documents dictionaryBySettingObject:(FSTDocument *)mutatedDoc forKey:key];
+ result = [result dictionaryBySettingObject:(FSTDocument *)mutatedDoc forKey:key];
} else {
FSTFail(@"Unknown document: %@", mutatedDoc);
}
diff --git a/Firestore/Source/Local/FSTLocalSerializer.m b/Firestore/Source/Local/FSTLocalSerializer.mm
index c71e9dd..61e173a 100644
--- a/Firestore/Source/Local/FSTLocalSerializer.m
+++ b/Firestore/Source/Local/FSTLocalSerializer.mm
@@ -16,6 +16,8 @@
#import "Firestore/Source/Local/FSTLocalSerializer.h"
+#include <cinttypes>
+
#import "Firestore/Protos/objc/firestore/local/MaybeDocument.pbobjc.h"
#import "Firestore/Protos/objc/firestore/local/Mutation.pbobjc.h"
#import "Firestore/Protos/objc/firestore/local/Target.pbobjc.h"
@@ -28,6 +30,10 @@
#import "Firestore/Source/Remote/FSTSerializerBeta.h"
#import "Firestore/Source/Util/FSTAssert.h"
+#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+
+using firebase::firestore::model::DocumentKey;
+
@interface FSTLocalSerializer ()
@property(nonatomic, strong, readonly) FSTSerializerBeta *remoteSerializer;
@@ -93,7 +99,7 @@
FSTSerializerBeta *remoteSerializer = self.remoteSerializer;
FSTObjectValue *data = [remoteSerializer decodedFields:document.fields];
- FSTDocumentKey *key = [remoteSerializer decodedDocumentKey:document.name];
+ const DocumentKey key = [remoteSerializer decodedDocumentKey:document.name];
FSTSnapshotVersion *version = [remoteSerializer decodedVersion:document.updateTime];
return [FSTDocument documentWithData:data key:key version:version hasLocalMutations:NO];
}
@@ -112,7 +118,7 @@
- (FSTDeletedDocument *)decodedDeletedDocument:(FSTPBNoDocument *)proto {
FSTSerializerBeta *remoteSerializer = self.remoteSerializer;
- FSTDocumentKey *key = [remoteSerializer decodedDocumentKey:proto.name];
+ const DocumentKey key = [remoteSerializer decodedDocumentKey:proto.name];
FSTSnapshotVersion *version = [remoteSerializer decodedVersion:proto.readTime];
return [FSTDeletedDocument documentWithKey:key version:version];
}
@@ -140,7 +146,7 @@
[mutations addObject:[remoteSerializer decodedMutation:write]];
}
- FSTTimestamp *localWriteTime = [remoteSerializer decodedTimestamp:batch.localWriteTime];
+ FIRTimestamp *localWriteTime = [remoteSerializer decodedTimestamp:batch.localWriteTime];
return [[FSTMutationBatch alloc] initWithBatchID:batchID
localWriteTime:localWriteTime
@@ -156,6 +162,7 @@
FSTPBTarget *proto = [FSTPBTarget message];
proto.targetId = queryData.targetID;
+ proto.lastListenSequenceNumber = queryData.sequenceNumber;
proto.snapshotVersion = [remoteSerializer encodedVersion:queryData.snapshotVersion];
proto.resumeToken = queryData.resumeToken;
@@ -173,6 +180,7 @@
FSTSerializerBeta *remoteSerializer = self.remoteSerializer;
FSTTargetID targetID = target.targetId;
+ FSTListenSequenceNumber sequenceNumber = target.lastListenSequenceNumber;
FSTSnapshotVersion *version = [remoteSerializer decodedVersion:target.snapshotVersion];
NSData *resumeToken = target.resumeToken;
@@ -192,6 +200,7 @@
return [[FSTQueryData alloc] initWithQuery:query
targetID:targetID
+ listenSequenceNumber:sequenceNumber
purpose:FSTQueryPurposeListen
snapshotVersion:version
resumeToken:resumeToken];
diff --git a/Firestore/Source/Local/FSTLocalStore.h b/Firestore/Source/Local/FSTLocalStore.h
index 19803ac..82402e4 100644
--- a/Firestore/Source/Local/FSTLocalStore.h
+++ b/Firestore/Source/Local/FSTLocalStore.h
@@ -21,6 +21,9 @@
#import "Firestore/Source/Model/FSTDocumentKeySet.h"
#import "Firestore/Source/Model/FSTDocumentVersionDictionary.h"
+#include "Firestore/core/src/firebase/firestore/auth/user.h"
+#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+
@class FSTLocalViewChanges;
@class FSTLocalWriteResult;
@class FSTMutation;
@@ -29,7 +32,6 @@
@class FSTQuery;
@class FSTQueryData;
@class FSTRemoteEvent;
-@class FSTUser;
@protocol FSTPersistence;
@protocol FSTGarbageCollector;
@@ -80,29 +82,27 @@ NS_ASSUME_NONNULL_BEGIN
/** Creates a new instance of the FSTLocalStore with its required dependencies as parameters. */
- (instancetype)initWithPersistence:(id<FSTPersistence>)persistence
garbageCollector:(id<FSTGarbageCollector>)garbageCollector
- initialUser:(FSTUser *)initialUser NS_DESIGNATED_INITIALIZER;
+ initialUser:(const firebase::firestore::auth::User &)initialUser
+ NS_DESIGNATED_INITIALIZER;
- (instancetype)init NS_UNAVAILABLE;
/** Performs any initial startup actions required by the local store. */
- (void)start;
-/** Releases any open resources. */
-- (void)shutdown;
-
/**
* Tells the FSTLocalStore that the currently authenticated user has changed.
*
* In response the local store switches the mutation queue to the new user and returns any
* resulting document changes.
*/
-- (FSTMaybeDocumentDictionary *)userDidChange:(FSTUser *)user;
+- (FSTMaybeDocumentDictionary *)userDidChange:(const firebase::firestore::auth::User &)user;
/** Accepts locally generated Mutations and commits them to storage. */
- (FSTLocalWriteResult *)locallyWriteMutations:(NSArray<FSTMutation *> *)mutations;
/** Returns the current value of a document with a given key, or nil if not found. */
-- (nullable FSTMaybeDocument *)readDocument:(FSTDocumentKey *)key;
+- (nullable FSTMaybeDocument *)readDocument:(const firebase::firestore::model::DocumentKey &)key;
/**
* Acknowledges the given batch.
diff --git a/Firestore/Source/Local/FSTLocalStore.m b/Firestore/Source/Local/FSTLocalStore.m
deleted file mode 100644
index cde7104..0000000
--- a/Firestore/Source/Local/FSTLocalStore.m
+++ /dev/null
@@ -1,546 +0,0 @@
-/*
- * Copyright 2017 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 "Firestore/Source/Local/FSTLocalStore.h"
-
-#import "Firestore/Source/Auth/FSTUser.h"
-#import "Firestore/Source/Core/FSTQuery.h"
-#import "Firestore/Source/Core/FSTSnapshotVersion.h"
-#import "Firestore/Source/Core/FSTTargetIDGenerator.h"
-#import "Firestore/Source/Core/FSTTimestamp.h"
-#import "Firestore/Source/Local/FSTGarbageCollector.h"
-#import "Firestore/Source/Local/FSTLocalDocumentsView.h"
-#import "Firestore/Source/Local/FSTLocalViewChanges.h"
-#import "Firestore/Source/Local/FSTLocalWriteResult.h"
-#import "Firestore/Source/Local/FSTMutationQueue.h"
-#import "Firestore/Source/Local/FSTPersistence.h"
-#import "Firestore/Source/Local/FSTQueryCache.h"
-#import "Firestore/Source/Local/FSTQueryData.h"
-#import "Firestore/Source/Local/FSTReferenceSet.h"
-#import "Firestore/Source/Local/FSTRemoteDocumentCache.h"
-#import "Firestore/Source/Local/FSTRemoteDocumentChangeBuffer.h"
-#import "Firestore/Source/Model/FSTDocument.h"
-#import "Firestore/Source/Model/FSTDocumentDictionary.h"
-#import "Firestore/Source/Model/FSTDocumentKey.h"
-#import "Firestore/Source/Model/FSTMutation.h"
-#import "Firestore/Source/Model/FSTMutationBatch.h"
-#import "Firestore/Source/Remote/FSTRemoteEvent.h"
-#import "Firestore/Source/Util/FSTAssert.h"
-#import "Firestore/Source/Util/FSTLogger.h"
-
-NS_ASSUME_NONNULL_BEGIN
-
-@interface FSTLocalStore ()
-
-/** Manages our in-memory or durable persistence. */
-@property(nonatomic, strong, readonly) id<FSTPersistence> persistence;
-
-/** The set of all mutations that have been sent but not yet been applied to the backend. */
-@property(nonatomic, strong) id<FSTMutationQueue> mutationQueue;
-
-/** The set of all cached remote documents. */
-@property(nonatomic, strong) id<FSTRemoteDocumentCache> remoteDocumentCache;
-
-/** The "local" view of all documents (layering mutationQueue on top of remoteDocumentCache). */
-@property(nonatomic, strong) FSTLocalDocumentsView *localDocuments;
-
-/** The set of document references maintained by any local views. */
-@property(nonatomic, strong) FSTReferenceSet *localViewReferences;
-
-/**
- * The garbage collector collects documents that should no longer be cached (e.g. if they are no
- * longer retained by the above reference sets and the garbage collector is performing eager
- * collection).
- */
-@property(nonatomic, strong) id<FSTGarbageCollector> garbageCollector;
-
-/** Maps a query to the data about that query. */
-@property(nonatomic, strong) id<FSTQueryCache> queryCache;
-
-/** Maps a targetID to data about its query. */
-@property(nonatomic, strong) NSMutableDictionary<NSNumber *, FSTQueryData *> *targetIDs;
-
-/** Used to generate targetIDs for queries tracked locally. */
-@property(nonatomic, strong) FSTTargetIDGenerator *targetIDGenerator;
-
-/**
- * A heldBatchResult is a mutation batch result (from a write acknowledgement) that arrived before
- * the watch stream got notified of a snapshot that includes the write.  So we "hold" it until
- * the watch stream catches up. It ensures that the local write remains visible (latency
- * compensation) and doesn't temporarily appear reverted because the watch stream is slower than
- * the write stream and so wasn't reflecting it.
- *
- * NOTE: Eventually we want to move this functionality into the remote store.
- */
-@property(nonatomic, strong) NSMutableArray<FSTMutationBatchResult *> *heldBatchResults;
-
-@end
-
-@implementation FSTLocalStore
-
-- (instancetype)initWithPersistence:(id<FSTPersistence>)persistence
- garbageCollector:(id<FSTGarbageCollector>)garbageCollector
- initialUser:(FSTUser *)initialUser {
- if (self = [super init]) {
- _persistence = persistence;
- _mutationQueue = [persistence mutationQueueForUser:initialUser];
- _remoteDocumentCache = [persistence remoteDocumentCache];
- _queryCache = [persistence queryCache];
- _localDocuments = [FSTLocalDocumentsView viewWithRemoteDocumentCache:_remoteDocumentCache
- mutationQueue:_mutationQueue];
- _localViewReferences = [[FSTReferenceSet alloc] init];
-
- _garbageCollector = garbageCollector;
- [_garbageCollector addGarbageSource:_queryCache];
- [_garbageCollector addGarbageSource:_localViewReferences];
- [_garbageCollector addGarbageSource:_mutationQueue];
-
- _targetIDs = [NSMutableDictionary dictionary];
- _heldBatchResults = [NSMutableArray array];
- }
- return self;
-}
-
-- (void)start {
- [self startMutationQueue];
- [self startQueryCache];
-}
-
-- (void)startMutationQueue {
- FSTWriteGroup *group = [self.persistence startGroupWithAction:@"Start MutationQueue"];
- [self.mutationQueue startWithGroup:group];
-
- // If we have any leftover mutation batch results from a prior run, just drop them.
- // TODO(http://b/33446471): We probably need to repopulate heldBatchResults or similar instead,
- // but that is not straightforward since we're not persisting the write ack versions.
- [self.heldBatchResults removeAllObjects];
-
- // TODO(mikelehen): This is the only usage of getAllMutationBatchesThroughBatchId:. Consider
- // removing it in favor of a getAcknowledgedBatches method.
- FSTBatchID highestAck = [self.mutationQueue highestAcknowledgedBatchID];
- if (highestAck != kFSTBatchIDUnknown) {
- NSArray<FSTMutationBatch *> *batches =
- [self.mutationQueue allMutationBatchesThroughBatchID:highestAck];
- if (batches.count > 0) {
- // NOTE: This could be more efficient if we had a removeBatchesThroughBatchID, but this set
- // should be very small and this code should go away eventually.
- [self.mutationQueue removeMutationBatches:batches group:group];
- }
- }
- [self.persistence commitGroup:group];
-}
-
-- (void)startQueryCache {
- [self.queryCache start];
-
- FSTTargetID targetID = [self.queryCache highestTargetID];
- self.targetIDGenerator = [FSTTargetIDGenerator generatorForLocalStoreStartingAfterID:targetID];
-}
-
-- (void)shutdown {
- [self.mutationQueue shutdown];
- [self.remoteDocumentCache shutdown];
- [self.queryCache shutdown];
-}
-
-- (FSTMaybeDocumentDictionary *)userDidChange:(FSTUser *)user {
- // Swap out the mutation queue, grabbing the pending mutation batches before and after.
- NSArray<FSTMutationBatch *> *oldBatches = [self.mutationQueue allMutationBatches];
-
- [self.mutationQueue shutdown];
- [self.garbageCollector removeGarbageSource:self.mutationQueue];
-
- self.mutationQueue = [self.persistence mutationQueueForUser:user];
- [self.garbageCollector addGarbageSource:self.mutationQueue];
-
- [self startMutationQueue];
-
- NSArray<FSTMutationBatch *> *newBatches = [self.mutationQueue allMutationBatches];
-
- // Recreate our LocalDocumentsView using the new MutationQueue.
- self.localDocuments = [FSTLocalDocumentsView viewWithRemoteDocumentCache:self.remoteDocumentCache
- mutationQueue:self.mutationQueue];
-
- // Union the old/new changed keys.
- FSTDocumentKeySet *changedKeys = [FSTDocumentKeySet keySet];
- for (NSArray<FSTMutationBatch *> *batches in @[ oldBatches, newBatches ]) {
- for (FSTMutationBatch *batch in batches) {
- for (FSTMutation *mutation in batch.mutations) {
- changedKeys = [changedKeys setByAddingObject:mutation.key];
- }
- }
- }
-
- // Return the set of all (potentially) changed documents as the result of the user change.
- return [self.localDocuments documentsForKeys:changedKeys];
-}
-
-- (FSTLocalWriteResult *)locallyWriteMutations:(NSArray<FSTMutation *> *)mutations {
- FSTWriteGroup *group = [self.persistence startGroupWithAction:@"Locally write mutations"];
- FSTTimestamp *localWriteTime = [FSTTimestamp timestamp];
- FSTMutationBatch *batch = [self.mutationQueue addMutationBatchWithWriteTime:localWriteTime
- mutations:mutations
- group:group];
- [self.persistence commitGroup:group];
-
- FSTDocumentKeySet *keys = [batch keys];
- FSTMaybeDocumentDictionary *changedDocuments = [self.localDocuments documentsForKeys:keys];
- return [FSTLocalWriteResult resultForBatchID:batch.batchID changes:changedDocuments];
-}
-
-- (FSTMaybeDocumentDictionary *)acknowledgeBatchWithResult:(FSTMutationBatchResult *)batchResult {
- FSTWriteGroup *group = [self.persistence startGroupWithAction:@"Acknowledge batch"];
- id<FSTMutationQueue> mutationQueue = self.mutationQueue;
-
- [mutationQueue acknowledgeBatch:batchResult.batch
- streamToken:batchResult.streamToken
- group:group];
-
- FSTDocumentKeySet *affected;
- if ([self shouldHoldBatchResultWithVersion:batchResult.commitVersion]) {
- [self.heldBatchResults addObject:batchResult];
- affected = [FSTDocumentKeySet keySet];
- } else {
- FSTRemoteDocumentChangeBuffer *remoteDocuments =
- [FSTRemoteDocumentChangeBuffer changeBufferWithCache:self.remoteDocumentCache];
-
- affected =
- [self releaseBatchResults:@[ batchResult ] group:group remoteDocuments:remoteDocuments];
-
- [remoteDocuments applyToWriteGroup:group];
- }
-
- [self.persistence commitGroup:group];
- [self.mutationQueue performConsistencyCheck];
-
- return [self.localDocuments documentsForKeys:affected];
-}
-
-- (FSTMaybeDocumentDictionary *)rejectBatchID:(FSTBatchID)batchID {
- FSTWriteGroup *group = [self.persistence startGroupWithAction:@"Reject batch"];
-
- FSTMutationBatch *toReject = [self.mutationQueue lookupMutationBatch:batchID];
- FSTAssert(toReject, @"Attempt to reject nonexistent batch!");
-
- FSTBatchID lastAcked = [self.mutationQueue highestAcknowledgedBatchID];
- FSTAssert(batchID > lastAcked, @"Acknowledged batches can't be rejected.");
-
- FSTDocumentKeySet *affected = [self removeMutationBatch:toReject group:group];
-
- [self.persistence commitGroup:group];
- [self.mutationQueue performConsistencyCheck];
-
- return [self.localDocuments documentsForKeys:affected];
-}
-
-- (nullable NSData *)lastStreamToken {
- return [self.mutationQueue lastStreamToken];
-}
-
-- (void)setLastStreamToken:(nullable NSData *)streamToken {
- FSTWriteGroup *group = [self.persistence startGroupWithAction:@"Set stream token"];
-
- [self.mutationQueue setLastStreamToken:streamToken group:group];
- [self.persistence commitGroup:group];
-}
-
-- (FSTSnapshotVersion *)lastRemoteSnapshotVersion {
- return [self.queryCache lastRemoteSnapshotVersion];
-}
-
-- (FSTMaybeDocumentDictionary *)applyRemoteEvent:(FSTRemoteEvent *)remoteEvent {
- id<FSTQueryCache> queryCache = self.queryCache;
-
- FSTWriteGroup *group = [self.persistence startGroupWithAction:@"Apply remote event"];
- FSTRemoteDocumentChangeBuffer *remoteDocuments =
- [FSTRemoteDocumentChangeBuffer changeBufferWithCache:self.remoteDocumentCache];
-
- [remoteEvent.targetChanges enumerateKeysAndObjectsUsingBlock:^(
- NSNumber *targetIDNumber, FSTTargetChange *change, BOOL *stop) {
- FSTTargetID targetID = targetIDNumber.intValue;
-
- // Do not ref/unref unassigned targetIDs - it may lead to leaks.
- FSTQueryData *queryData = self.targetIDs[targetIDNumber];
- if (!queryData) {
- return;
- }
-
- FSTTargetMapping *mapping = change.mapping;
- if (mapping) {
- // First make sure that all references are deleted.
- if ([mapping isKindOfClass:[FSTResetMapping class]]) {
- FSTResetMapping *reset = (FSTResetMapping *)mapping;
- [queryCache removeMatchingKeysForTargetID:targetID group:group];
- [queryCache addMatchingKeys:reset.documents forTargetID:targetID group:group];
-
- } else if ([mapping isKindOfClass:[FSTUpdateMapping class]]) {
- FSTUpdateMapping *update = (FSTUpdateMapping *)mapping;
- [queryCache removeMatchingKeys:update.removedDocuments forTargetID:targetID group:group];
- [queryCache addMatchingKeys:update.addedDocuments forTargetID:targetID group:group];
-
- } else {
- FSTFail(@"Unknown mapping type: %@", mapping);
- }
- }
-
- // Update the resume token if the change includes one. Don't clear any preexisting value.
- NSData *resumeToken = change.resumeToken;
- if (resumeToken.length > 0) {
- queryData = [queryData queryDataByReplacingSnapshotVersion:change.snapshotVersion
- resumeToken:resumeToken];
- self.targetIDs[targetIDNumber] = queryData;
- [self.queryCache addQueryData:queryData group:group];
- }
- }];
-
- // TODO(klimt): This could probably be an NSMutableDictionary.
- __block FSTDocumentKeySet *changedDocKeys = [FSTDocumentKeySet keySet];
- [remoteEvent.documentUpdates
- enumerateKeysAndObjectsUsingBlock:^(FSTDocumentKey *key, FSTMaybeDocument *doc, BOOL *stop) {
- changedDocKeys = [changedDocKeys setByAddingObject:key];
- FSTMaybeDocument *existingDoc = [remoteDocuments entryForKey:key];
- // Make sure we don't apply an old document version to the remote cache, though we
- // make an exception for [SnapshotVersion noVersion] which can happen for manufactured
- // events (e.g. in the case of a limbo document resolution failing).
- if (!existingDoc || [doc.version isEqual:[FSTSnapshotVersion noVersion]] ||
- [doc.version compare:existingDoc.version] != NSOrderedAscending) {
- [remoteDocuments addEntry:doc];
- } else {
- FSTLog(
- @"FSTLocalStore Ignoring outdated watch update for %@. "
- "Current version: %@ Watch version: %@",
- key, existingDoc.version, doc.version);
- }
-
- // The document might be garbage because it was unreferenced by everything.
- // Make sure to mark it as garbage if it is...
- [self.garbageCollector addPotentialGarbageKey:key];
- }];
-
- // HACK: The only reason we allow omitting snapshot version is so we can synthesize remote events
- // when we get permission denied errors while trying to resolve the state of a locally cached
- // document that is in limbo.
- FSTSnapshotVersion *lastRemoteVersion = [self.queryCache lastRemoteSnapshotVersion];
- FSTSnapshotVersion *remoteVersion = remoteEvent.snapshotVersion;
- if (![remoteVersion isEqual:[FSTSnapshotVersion noVersion]]) {
- FSTAssert([remoteVersion compare:lastRemoteVersion] != NSOrderedAscending,
- @"Watch stream reverted to previous snapshot?? (%@ < %@)", remoteVersion,
- lastRemoteVersion);
- [self.queryCache setLastRemoteSnapshotVersion:remoteVersion group:group];
- }
-
- FSTDocumentKeySet *releasedWriteKeys =
- [self releaseHeldBatchResultsWithGroup:group remoteDocuments:remoteDocuments];
-
- [remoteDocuments applyToWriteGroup:group];
-
- [self.persistence commitGroup:group];
-
- // Union the two key sets.
- __block FSTDocumentKeySet *keysToRecalc = changedDocKeys;
- [releasedWriteKeys enumerateObjectsUsingBlock:^(FSTDocumentKey *key, BOOL *stop) {
- keysToRecalc = [keysToRecalc setByAddingObject:key];
- }];
-
- return [self.localDocuments documentsForKeys:keysToRecalc];
-}
-
-- (void)notifyLocalViewChanges:(NSArray<FSTLocalViewChanges *> *)viewChanges {
- FSTReferenceSet *localViewReferences = self.localViewReferences;
- for (FSTLocalViewChanges *view in viewChanges) {
- FSTQueryData *queryData = [self.queryCache queryDataForQuery:view.query];
- FSTAssert(queryData, @"Local view changes contain unallocated query.");
- FSTTargetID targetID = queryData.targetID;
- [localViewReferences addReferencesToKeys:view.addedKeys forID:targetID];
- [localViewReferences removeReferencesToKeys:view.removedKeys forID:targetID];
- }
-}
-
-- (nullable FSTMutationBatch *)nextMutationBatchAfterBatchID:(FSTBatchID)batchID {
- return [self.mutationQueue nextMutationBatchAfterBatchID:batchID];
-}
-
-- (nullable FSTMaybeDocument *)readDocument:(FSTDocumentKey *)key {
- return [self.localDocuments documentForKey:key];
-}
-
-- (FSTQueryData *)allocateQuery:(FSTQuery *)query {
- FSTQueryData *cached = [self.queryCache queryDataForQuery:query];
- FSTTargetID targetID;
- if (cached) {
- // This query has been listened to previously, so reuse the previous targetID.
- // TODO(mcg): freshen last accessed date?
- targetID = cached.targetID;
- } else {
- FSTWriteGroup *group = [self.persistence startGroupWithAction:@"Allocate query"];
-
- targetID = [self.targetIDGenerator nextID];
- cached =
- [[FSTQueryData alloc] initWithQuery:query targetID:targetID purpose:FSTQueryPurposeListen];
- [self.queryCache addQueryData:cached group:group];
-
- [self.persistence commitGroup:group];
- }
-
- // Sanity check to ensure that even when resuming a query it's not currently active.
- FSTBoxedTargetID *boxedTargetID = @(targetID);
- FSTAssert(!self.targetIDs[boxedTargetID], @"Tried to allocate an already allocated query: %@",
- query);
- self.targetIDs[boxedTargetID] = cached;
- return cached;
-}
-
-- (void)releaseQuery:(FSTQuery *)query {
- FSTWriteGroup *group = [self.persistence startGroupWithAction:@"Release query"];
-
- FSTQueryData *queryData = [self.queryCache queryDataForQuery:query];
- FSTAssert(queryData, @"Tried to release nonexistent query: %@", query);
-
- [self.localViewReferences removeReferencesForID:queryData.targetID];
- if (self.garbageCollector.isEager) {
- [self.queryCache removeQueryData:queryData group:group];
- }
- [self.targetIDs removeObjectForKey:@(queryData.targetID)];
-
- // If this was the last watch target, then we won't get any more watch snapshots, so we should
- // release any held batch results.
- if ([self.targetIDs count] == 0) {
- FSTRemoteDocumentChangeBuffer *remoteDocuments =
- [FSTRemoteDocumentChangeBuffer changeBufferWithCache:self.remoteDocumentCache];
-
- [self releaseHeldBatchResultsWithGroup:group remoteDocuments:remoteDocuments];
-
- [remoteDocuments applyToWriteGroup:group];
- }
-
- [self.persistence commitGroup:group];
-}
-
-- (FSTDocumentDictionary *)executeQuery:(FSTQuery *)query {
- return [self.localDocuments documentsMatchingQuery:query];
-}
-
-- (FSTDocumentKeySet *)remoteDocumentKeysForTarget:(FSTTargetID)targetID {
- return [self.queryCache matchingKeysForTargetID:targetID];
-}
-
-- (void)collectGarbage {
- // Call collectGarbage regardless of whether isGCEnabled so the referenceSet doesn't continue to
- // accumulate the garbage keys.
- NSSet<FSTDocumentKey *> *garbage = [self.garbageCollector collectGarbage];
- if (garbage.count > 0) {
- FSTWriteGroup *group = [self.persistence startGroupWithAction:@"Garbage Collection"];
- for (FSTDocumentKey *key in garbage) {
- [self.remoteDocumentCache removeEntryForKey:key group:group];
- }
- [self.persistence commitGroup:group];
- }
-}
-
-/**
- * Releases all the held mutation batches up to the current remote version received, and
- * applies their mutations to the docs in the remote documents cache.
- *
- * @return the set of keys of docs that were modified by those writes.
- */
-- (FSTDocumentKeySet *)releaseHeldBatchResultsWithGroup:(FSTWriteGroup *)group
- remoteDocuments:
- (FSTRemoteDocumentChangeBuffer *)remoteDocuments {
- NSMutableArray<FSTMutationBatchResult *> *toRelease = [NSMutableArray array];
- for (FSTMutationBatchResult *batchResult in self.heldBatchResults) {
- if (![self isRemoteUpToVersion:batchResult.commitVersion]) {
- break;
- }
- [toRelease addObject:batchResult];
- }
-
- if (toRelease.count == 0) {
- return [FSTDocumentKeySet keySet];
- } else {
- [self.heldBatchResults removeObjectsInRange:NSMakeRange(0, toRelease.count)];
- return [self releaseBatchResults:toRelease group:group remoteDocuments:remoteDocuments];
- }
-}
-
-- (BOOL)isRemoteUpToVersion:(FSTSnapshotVersion *)version {
- // If there are no watch targets, then we won't get remote snapshots, and are always "up-to-date."
- return [version compare:self.queryCache.lastRemoteSnapshotVersion] != NSOrderedDescending ||
- self.targetIDs.count == 0;
-}
-
-- (BOOL)shouldHoldBatchResultWithVersion:(FSTSnapshotVersion *)version {
- // Check if watcher isn't up to date or prior results are already held.
- return ![self isRemoteUpToVersion:version] || self.heldBatchResults.count > 0;
-}
-
-- (FSTDocumentKeySet *)releaseBatchResults:(NSArray<FSTMutationBatchResult *> *)batchResults
- group:(FSTWriteGroup *)group
- remoteDocuments:(FSTRemoteDocumentChangeBuffer *)remoteDocuments {
- NSMutableArray<FSTMutationBatch *> *batches = [NSMutableArray array];
- for (FSTMutationBatchResult *batchResult in batchResults) {
- [self applyBatchResult:batchResult toRemoteDocuments:remoteDocuments];
- [batches addObject:batchResult.batch];
- }
-
- return [self removeMutationBatches:batches group:group];
-}
-
-- (FSTDocumentKeySet *)removeMutationBatch:(FSTMutationBatch *)batch group:(FSTWriteGroup *)group {
- return [self removeMutationBatches:@[ batch ] group:group];
-}
-
-/** Removes all the mutation batches named in the given array. */
-- (FSTDocumentKeySet *)removeMutationBatches:(NSArray<FSTMutationBatch *> *)batches
- group:(FSTWriteGroup *)group {
- // TODO(klimt): Could this be an NSMutableDictionary?
- __block FSTDocumentKeySet *affectedDocs = [FSTDocumentKeySet keySet];
-
- for (FSTMutationBatch *batch in batches) {
- for (FSTMutation *mutation in batch.mutations) {
- FSTDocumentKey *key = mutation.key;
- affectedDocs = [affectedDocs setByAddingObject:key];
- }
- }
-
- [self.mutationQueue removeMutationBatches:batches group:group];
-
- return affectedDocs;
-}
-
-- (void)applyBatchResult:(FSTMutationBatchResult *)batchResult
- toRemoteDocuments:(FSTRemoteDocumentChangeBuffer *)remoteDocuments {
- FSTMutationBatch *batch = batchResult.batch;
- FSTDocumentKeySet *docKeys = batch.keys;
- [docKeys enumerateObjectsUsingBlock:^(FSTDocumentKey *docKey, BOOL *stop) {
- FSTMaybeDocument *_Nullable remoteDoc = [remoteDocuments entryForKey:docKey];
- FSTMaybeDocument *_Nullable doc = remoteDoc;
- FSTSnapshotVersion *ackVersion = batchResult.docVersions[docKey];
- FSTAssert(ackVersion, @"docVersions should contain every doc in the write.");
- if (!doc || [doc.version compare:ackVersion] == NSOrderedAscending) {
- doc = [batch applyTo:doc documentKey:docKey mutationBatchResult:batchResult];
- if (!doc) {
- FSTAssert(!remoteDoc, @"Mutation batch %@ applied to document %@ resulted in nil.", batch,
- remoteDoc);
- } else {
- [remoteDocuments addEntry:doc];
- }
- }
- }];
-}
-
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/Local/FSTLocalStore.mm b/Firestore/Source/Local/FSTLocalStore.mm
new file mode 100644
index 0000000..b5dfeec
--- /dev/null
+++ b/Firestore/Source/Local/FSTLocalStore.mm
@@ -0,0 +1,533 @@
+/*
+ * Copyright 2017 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 "Firestore/Source/Local/FSTLocalStore.h"
+
+#include <set>
+
+#import "FIRTimestamp.h"
+#import "Firestore/Source/Core/FSTListenSequence.h"
+#import "Firestore/Source/Core/FSTQuery.h"
+#import "Firestore/Source/Core/FSTSnapshotVersion.h"
+#import "Firestore/Source/Local/FSTGarbageCollector.h"
+#import "Firestore/Source/Local/FSTLocalDocumentsView.h"
+#import "Firestore/Source/Local/FSTLocalViewChanges.h"
+#import "Firestore/Source/Local/FSTLocalWriteResult.h"
+#import "Firestore/Source/Local/FSTMutationQueue.h"
+#import "Firestore/Source/Local/FSTPersistence.h"
+#import "Firestore/Source/Local/FSTQueryCache.h"
+#import "Firestore/Source/Local/FSTQueryData.h"
+#import "Firestore/Source/Local/FSTReferenceSet.h"
+#import "Firestore/Source/Local/FSTRemoteDocumentCache.h"
+#import "Firestore/Source/Model/FSTDocument.h"
+#import "Firestore/Source/Model/FSTDocumentDictionary.h"
+#import "Firestore/Source/Model/FSTMutation.h"
+#import "Firestore/Source/Model/FSTMutationBatch.h"
+#import "Firestore/Source/Remote/FSTRemoteEvent.h"
+#import "Firestore/Source/Util/FSTAssert.h"
+#import "Firestore/Source/Util/FSTLogger.h"
+
+#include "Firestore/core/src/firebase/firestore/auth/user.h"
+#include "Firestore/core/src/firebase/firestore/core/target_id_generator.h"
+#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+
+using firebase::firestore::auth::User;
+using firebase::firestore::model::DocumentKey;
+using firebase::firestore::core::TargetIdGenerator;
+using firebase::firestore::model::DocumentKey;
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface FSTLocalStore ()
+
+/** Manages our in-memory or durable persistence. */
+@property(nonatomic, strong, readonly) id<FSTPersistence> persistence;
+
+/** The set of all mutations that have been sent but not yet been applied to the backend. */
+@property(nonatomic, strong) id<FSTMutationQueue> mutationQueue;
+
+/** The set of all cached remote documents. */
+@property(nonatomic, strong) id<FSTRemoteDocumentCache> remoteDocumentCache;
+
+/** The "local" view of all documents (layering mutationQueue on top of remoteDocumentCache). */
+@property(nonatomic, strong) FSTLocalDocumentsView *localDocuments;
+
+/** The set of document references maintained by any local views. */
+@property(nonatomic, strong) FSTReferenceSet *localViewReferences;
+
+/**
+ * The garbage collector collects documents that should no longer be cached (e.g. if they are no
+ * longer retained by the above reference sets and the garbage collector is performing eager
+ * collection).
+ */
+@property(nonatomic, strong) id<FSTGarbageCollector> garbageCollector;
+
+/** Maps a query to the data about that query. */
+@property(nonatomic, strong) id<FSTQueryCache> queryCache;
+
+/** Maps a targetID to data about its query. */
+@property(nonatomic, strong) NSMutableDictionary<NSNumber *, FSTQueryData *> *targetIDs;
+
+@property(nonatomic, strong) FSTListenSequence *listenSequence;
+
+/**
+ * A heldBatchResult is a mutation batch result (from a write acknowledgement) that arrived before
+ * the watch stream got notified of a snapshot that includes the write.  So we "hold" it until
+ * the watch stream catches up. It ensures that the local write remains visible (latency
+ * compensation) and doesn't temporarily appear reverted because the watch stream is slower than
+ * the write stream and so wasn't reflecting it.
+ *
+ * NOTE: Eventually we want to move this functionality into the remote store.
+ */
+@property(nonatomic, strong) NSMutableArray<FSTMutationBatchResult *> *heldBatchResults;
+
+@end
+
+@implementation FSTLocalStore {
+ /** Used to generate targetIDs for queries tracked locally. */
+ TargetIdGenerator _targetIDGenerator;
+}
+
+- (instancetype)initWithPersistence:(id<FSTPersistence>)persistence
+ garbageCollector:(id<FSTGarbageCollector>)garbageCollector
+ initialUser:(const User &)initialUser {
+ if (self = [super init]) {
+ _persistence = persistence;
+ _mutationQueue = [persistence mutationQueueForUser:initialUser];
+ _remoteDocumentCache = [persistence remoteDocumentCache];
+ _queryCache = [persistence queryCache];
+ _localDocuments = [FSTLocalDocumentsView viewWithRemoteDocumentCache:_remoteDocumentCache
+ mutationQueue:_mutationQueue];
+ _localViewReferences = [[FSTReferenceSet alloc] init];
+
+ _garbageCollector = garbageCollector;
+ [_garbageCollector addGarbageSource:_queryCache];
+ [_garbageCollector addGarbageSource:_localViewReferences];
+ [_garbageCollector addGarbageSource:_mutationQueue];
+
+ _targetIDs = [NSMutableDictionary dictionary];
+ _heldBatchResults = [NSMutableArray array];
+
+ _targetIDGenerator = TargetIdGenerator::LocalStoreTargetIdGenerator(0);
+ }
+ return self;
+}
+
+- (void)start {
+ [self startMutationQueue];
+ [self startQueryCache];
+}
+
+- (void)startMutationQueue {
+ self.persistence.run("Start MutationQueue", [&]() {
+ [self.mutationQueue start];
+
+ // If we have any leftover mutation batch results from a prior run, just drop them.
+ // TODO(http://b/33446471): We probably need to repopulate heldBatchResults or similar instead,
+ // but that is not straightforward since we're not persisting the write ack versions.
+ [self.heldBatchResults removeAllObjects];
+
+ // TODO(mikelehen): This is the only usage of getAllMutationBatchesThroughBatchId:. Consider
+ // removing it in favor of a getAcknowledgedBatches method.
+ FSTBatchID highestAck = [self.mutationQueue highestAcknowledgedBatchID];
+ if (highestAck != kFSTBatchIDUnknown) {
+ NSArray<FSTMutationBatch *> *batches =
+ [self.mutationQueue allMutationBatchesThroughBatchID:highestAck];
+ if (batches.count > 0) {
+ // NOTE: This could be more efficient if we had a removeBatchesThroughBatchID, but this set
+ // should be very small and this code should go away eventually.
+ [self.mutationQueue removeMutationBatches:batches];
+ }
+ }
+ });
+}
+
+- (void)startQueryCache {
+ [self.queryCache start];
+
+ FSTTargetID targetID = [self.queryCache highestTargetID];
+ _targetIDGenerator = TargetIdGenerator::LocalStoreTargetIdGenerator(targetID);
+ FSTListenSequenceNumber sequenceNumber = [self.queryCache highestListenSequenceNumber];
+ self.listenSequence = [[FSTListenSequence alloc] initStartingAfter:sequenceNumber];
+}
+
+- (FSTMaybeDocumentDictionary *)userDidChange:(const User &)user {
+ // Swap out the mutation queue, grabbing the pending mutation batches before and after.
+ NSArray<FSTMutationBatch *> *oldBatches = self.persistence.run(
+ "OldBatches",
+ [&]() -> NSArray<FSTMutationBatch *> * { return [self.mutationQueue allMutationBatches]; });
+
+ [self.garbageCollector removeGarbageSource:self.mutationQueue];
+
+ self.mutationQueue = [self.persistence mutationQueueForUser:user];
+ [self.garbageCollector addGarbageSource:self.mutationQueue];
+
+ [self startMutationQueue];
+
+ return self.persistence.run("NewBatches", [&]() -> FSTMaybeDocumentDictionary * {
+ NSArray<FSTMutationBatch *> *newBatches = [self.mutationQueue allMutationBatches];
+
+ // Recreate our LocalDocumentsView using the new MutationQueue.
+ self.localDocuments =
+ [FSTLocalDocumentsView viewWithRemoteDocumentCache:self.remoteDocumentCache
+ mutationQueue:self.mutationQueue];
+
+ // Union the old/new changed keys.
+ FSTDocumentKeySet *changedKeys = [FSTDocumentKeySet keySet];
+ for (NSArray<FSTMutationBatch *> *batches in @[ oldBatches, newBatches ]) {
+ for (FSTMutationBatch *batch in batches) {
+ for (FSTMutation *mutation in batch.mutations) {
+ changedKeys = [changedKeys setByAddingObject:mutation.key];
+ }
+ }
+ }
+
+ // Return the set of all (potentially) changed documents as the result of the user change.
+ return [self.localDocuments documentsForKeys:changedKeys];
+ });
+}
+
+- (FSTLocalWriteResult *)locallyWriteMutations:(NSArray<FSTMutation *> *)mutations {
+ return self.persistence.run("Locally write mutations", [&]() -> FSTLocalWriteResult * {
+ FIRTimestamp *localWriteTime = [FIRTimestamp timestamp];
+ FSTMutationBatch *batch =
+ [self.mutationQueue addMutationBatchWithWriteTime:localWriteTime mutations:mutations];
+ FSTDocumentKeySet *keys = [batch keys];
+ FSTMaybeDocumentDictionary *changedDocuments = [self.localDocuments documentsForKeys:keys];
+ return [FSTLocalWriteResult resultForBatchID:batch.batchID changes:changedDocuments];
+ });
+}
+
+- (FSTMaybeDocumentDictionary *)acknowledgeBatchWithResult:(FSTMutationBatchResult *)batchResult {
+ return self.persistence.run("Acknowledge batch", [&]() -> FSTMaybeDocumentDictionary * {
+ id<FSTMutationQueue> mutationQueue = self.mutationQueue;
+
+ [mutationQueue acknowledgeBatch:batchResult.batch streamToken:batchResult.streamToken];
+
+ FSTDocumentKeySet *affected;
+ if ([self shouldHoldBatchResultWithVersion:batchResult.commitVersion]) {
+ [self.heldBatchResults addObject:batchResult];
+ affected = [FSTDocumentKeySet keySet];
+ } else {
+ affected = [self releaseBatchResults:@[ batchResult ]];
+ }
+
+ [self.mutationQueue performConsistencyCheck];
+
+ return [self.localDocuments documentsForKeys:affected];
+ });
+}
+
+- (FSTMaybeDocumentDictionary *)rejectBatchID:(FSTBatchID)batchID {
+ return self.persistence.run("Reject batch", [&]() -> FSTMaybeDocumentDictionary * {
+ FSTMutationBatch *toReject = [self.mutationQueue lookupMutationBatch:batchID];
+ FSTAssert(toReject, @"Attempt to reject nonexistent batch!");
+
+ FSTBatchID lastAcked = [self.mutationQueue highestAcknowledgedBatchID];
+ FSTAssert(batchID > lastAcked, @"Acknowledged batches can't be rejected.");
+
+ FSTDocumentKeySet *affected = [self removeMutationBatch:toReject];
+
+ [self.mutationQueue performConsistencyCheck];
+
+ return [self.localDocuments documentsForKeys:affected];
+ });
+}
+
+- (nullable NSData *)lastStreamToken {
+ return [self.mutationQueue lastStreamToken];
+}
+
+- (void)setLastStreamToken:(nullable NSData *)streamToken {
+ self.persistence.run("Set stream token",
+ [&]() { [self.mutationQueue setLastStreamToken:streamToken]; });
+}
+
+- (FSTSnapshotVersion *)lastRemoteSnapshotVersion {
+ return [self.queryCache lastRemoteSnapshotVersion];
+}
+
+- (FSTMaybeDocumentDictionary *)applyRemoteEvent:(FSTRemoteEvent *)remoteEvent {
+ return self.persistence.run("Apply remote event", [&]() -> FSTMaybeDocumentDictionary * {
+ id<FSTQueryCache> queryCache = self.queryCache;
+
+ [remoteEvent.targetChanges enumerateKeysAndObjectsUsingBlock:^(
+ NSNumber *targetIDNumber, FSTTargetChange *change, BOOL *stop) {
+ FSTTargetID targetID = targetIDNumber.intValue;
+
+ // Do not ref/unref unassigned targetIDs - it may lead to leaks.
+ FSTQueryData *queryData = self.targetIDs[targetIDNumber];
+ if (!queryData) {
+ return;
+ }
+
+ FSTTargetMapping *mapping = change.mapping;
+ if (mapping) {
+ // First make sure that all references are deleted.
+ if ([mapping isKindOfClass:[FSTResetMapping class]]) {
+ FSTResetMapping *reset = (FSTResetMapping *)mapping;
+ [queryCache removeMatchingKeysForTargetID:targetID];
+ [queryCache addMatchingKeys:reset.documents forTargetID:targetID];
+
+ } else if ([mapping isKindOfClass:[FSTUpdateMapping class]]) {
+ FSTUpdateMapping *update = (FSTUpdateMapping *)mapping;
+ [queryCache removeMatchingKeys:update.removedDocuments forTargetID:targetID];
+ [queryCache addMatchingKeys:update.addedDocuments forTargetID:targetID];
+
+ } else {
+ FSTFail(@"Unknown mapping type: %@", mapping);
+ }
+ }
+
+ // Update the resume token if the change includes one. Don't clear any preexisting value.
+ NSData *resumeToken = change.resumeToken;
+ if (resumeToken.length > 0) {
+ queryData = [queryData queryDataByReplacingSnapshotVersion:change.snapshotVersion
+ resumeToken:resumeToken];
+ self.targetIDs[targetIDNumber] = queryData;
+ [self.queryCache updateQueryData:queryData];
+ }
+ }];
+
+ // TODO(klimt): This could probably be an NSMutableDictionary.
+ FSTDocumentKeySet *changedDocKeys = [FSTDocumentKeySet keySet];
+ for (const auto &kv : remoteEvent.documentUpdates) {
+ const DocumentKey &key = kv.first;
+ FSTMaybeDocument *doc = kv.second;
+ changedDocKeys = [changedDocKeys setByAddingObject:key];
+ FSTMaybeDocument *existingDoc = [self.remoteDocumentCache entryForKey:key];
+ // Make sure we don't apply an old document version to the remote cache, though we
+ // make an exception for [SnapshotVersion noVersion] which can happen for manufactured
+ // events (e.g. in the case of a limbo document resolution failing).
+ if (!existingDoc || [doc.version isEqual:[FSTSnapshotVersion noVersion]] ||
+ [doc.version compare:existingDoc.version] != NSOrderedAscending) {
+ [self.remoteDocumentCache addEntry:doc];
+ } else {
+ FSTLog(
+ @"FSTLocalStore Ignoring outdated watch update for %s. "
+ "Current version: %@ Watch version: %@",
+ key.ToString().c_str(), existingDoc.version, doc.version);
+ }
+
+ // The document might be garbage because it was unreferenced by everything.
+ // Make sure to mark it as garbage if it is...
+ [self.garbageCollector addPotentialGarbageKey:key];
+ }
+
+ // HACK: The only reason we allow omitting snapshot version is so we can synthesize remote
+ // events when we get permission denied errors while trying to resolve the state of a locally
+ // cached document that is in limbo.
+ FSTSnapshotVersion *lastRemoteVersion = [self.queryCache lastRemoteSnapshotVersion];
+ FSTSnapshotVersion *remoteVersion = remoteEvent.snapshotVersion;
+ if (![remoteVersion isEqual:[FSTSnapshotVersion noVersion]]) {
+ FSTAssert([remoteVersion compare:lastRemoteVersion] != NSOrderedAscending,
+ @"Watch stream reverted to previous snapshot?? (%@ < %@)", remoteVersion,
+ lastRemoteVersion);
+ [self.queryCache setLastRemoteSnapshotVersion:remoteVersion];
+ }
+
+ FSTDocumentKeySet *releasedWriteKeys = [self releaseHeldBatchResults];
+
+ // Union the two key sets.
+ __block FSTDocumentKeySet *keysToRecalc = changedDocKeys;
+ [releasedWriteKeys enumerateObjectsUsingBlock:^(FSTDocumentKey *key, BOOL *stop) {
+ keysToRecalc = [keysToRecalc setByAddingObject:key];
+ }];
+
+ return [self.localDocuments documentsForKeys:keysToRecalc];
+ });
+}
+
+- (void)notifyLocalViewChanges:(NSArray<FSTLocalViewChanges *> *)viewChanges {
+ self.persistence.run("NotifyLocalViewChanges", [&]() {
+ FSTReferenceSet *localViewReferences = self.localViewReferences;
+ for (FSTLocalViewChanges *view in viewChanges) {
+ FSTQueryData *queryData = [self.queryCache queryDataForQuery:view.query];
+ FSTAssert(queryData, @"Local view changes contain unallocated query.");
+ FSTTargetID targetID = queryData.targetID;
+ [localViewReferences addReferencesToKeys:view.addedKeys forID:targetID];
+ [localViewReferences removeReferencesToKeys:view.removedKeys forID:targetID];
+ }
+ });
+}
+
+- (nullable FSTMutationBatch *)nextMutationBatchAfterBatchID:(FSTBatchID)batchID {
+ FSTMutationBatch *result =
+ self.persistence.run("NextMutationBatchAfterBatchID", [&]() -> FSTMutationBatch * {
+ return [self.mutationQueue nextMutationBatchAfterBatchID:batchID];
+ });
+ return result;
+}
+
+- (nullable FSTMaybeDocument *)readDocument:(const DocumentKey &)key {
+ return self.persistence.run("ReadDocument", [&]() -> FSTMaybeDocument *_Nullable {
+ return [self.localDocuments documentForKey:key];
+ });
+}
+
+- (FSTQueryData *)allocateQuery:(FSTQuery *)query {
+ FSTQueryData *queryData = self.persistence.run("Allocate query", [&]() -> FSTQueryData * {
+ FSTQueryData *cached = [self.queryCache queryDataForQuery:query];
+ // TODO(mcg): freshen last accessed date if cached exists?
+ if (!cached) {
+ cached = [[FSTQueryData alloc] initWithQuery:query
+ targetID:_targetIDGenerator.NextId()
+ listenSequenceNumber:[self.listenSequence next]
+ purpose:FSTQueryPurposeListen];
+ [self.queryCache addQueryData:cached];
+ }
+ return cached;
+ });
+ // Sanity check to ensure that even when resuming a query it's not currently active.
+ FSTBoxedTargetID *boxedTargetID = @(queryData.targetID);
+ FSTAssert(!self.targetIDs[boxedTargetID], @"Tried to allocate an already allocated query: %@",
+ query);
+ self.targetIDs[boxedTargetID] = queryData;
+ return queryData;
+}
+
+- (void)releaseQuery:(FSTQuery *)query {
+ self.persistence.run("Release query", [&]() {
+ FSTQueryData *queryData = [self.queryCache queryDataForQuery:query];
+ FSTAssert(queryData, @"Tried to release nonexistent query: %@", query);
+
+ [self.localViewReferences removeReferencesForID:queryData.targetID];
+ if (self.garbageCollector.isEager) {
+ [self.queryCache removeQueryData:queryData];
+ }
+ [self.targetIDs removeObjectForKey:@(queryData.targetID)];
+
+ // If this was the last watch target, then we won't get any more watch snapshots, so we should
+ // release any held batch results.
+ if ([self.targetIDs count] == 0) {
+ [self releaseHeldBatchResults];
+ }
+ });
+}
+
+- (FSTDocumentDictionary *)executeQuery:(FSTQuery *)query {
+ return self.persistence.run("ExecuteQuery", [&]() -> FSTDocumentDictionary * {
+ return [self.localDocuments documentsMatchingQuery:query];
+ });
+}
+
+- (FSTDocumentKeySet *)remoteDocumentKeysForTarget:(FSTTargetID)targetID {
+ return self.persistence.run("RemoteDocumentKeysForTarget", [&]() -> FSTDocumentKeySet * {
+ return [self.queryCache matchingKeysForTargetID:targetID];
+ });
+}
+
+- (void)collectGarbage {
+ self.persistence.run("Garbage Collection", [&]() {
+ // Call collectGarbage regardless of whether isGCEnabled so the referenceSet doesn't continue to
+ // accumulate the garbage keys.
+ std::set<DocumentKey> garbage = [self.garbageCollector collectGarbage];
+ if (garbage.size() > 0) {
+ for (const DocumentKey &key : garbage) {
+ [self.remoteDocumentCache removeEntryForKey:key];
+ }
+ }
+ });
+}
+
+/**
+ * Releases all the held mutation batches up to the current remote version received, and
+ * applies their mutations to the docs in the remote documents cache.
+ *
+ * @return the set of keys of docs that were modified by those writes.
+ */
+- (FSTDocumentKeySet *)releaseHeldBatchResults {
+ NSMutableArray<FSTMutationBatchResult *> *toRelease = [NSMutableArray array];
+ for (FSTMutationBatchResult *batchResult in self.heldBatchResults) {
+ if (![self isRemoteUpToVersion:batchResult.commitVersion]) {
+ break;
+ }
+ [toRelease addObject:batchResult];
+ }
+
+ if (toRelease.count == 0) {
+ return [FSTDocumentKeySet keySet];
+ } else {
+ [self.heldBatchResults removeObjectsInRange:NSMakeRange(0, toRelease.count)];
+ return [self releaseBatchResults:toRelease];
+ }
+}
+
+- (BOOL)isRemoteUpToVersion:(FSTSnapshotVersion *)version {
+ // If there are no watch targets, then we won't get remote snapshots, and are always "up-to-date."
+ return [version compare:self.queryCache.lastRemoteSnapshotVersion] != NSOrderedDescending ||
+ self.targetIDs.count == 0;
+}
+
+- (BOOL)shouldHoldBatchResultWithVersion:(FSTSnapshotVersion *)version {
+ // Check if watcher isn't up to date or prior results are already held.
+ return ![self isRemoteUpToVersion:version] || self.heldBatchResults.count > 0;
+}
+
+- (FSTDocumentKeySet *)releaseBatchResults:(NSArray<FSTMutationBatchResult *> *)batchResults {
+ NSMutableArray<FSTMutationBatch *> *batches = [NSMutableArray array];
+ for (FSTMutationBatchResult *batchResult in batchResults) {
+ [self applyBatchResult:batchResult];
+ [batches addObject:batchResult.batch];
+ }
+
+ return [self removeMutationBatches:batches];
+}
+
+- (FSTDocumentKeySet *)removeMutationBatch:(FSTMutationBatch *)batch {
+ return [self removeMutationBatches:@[ batch ]];
+}
+
+/** Removes all the mutation batches named in the given array. */
+- (FSTDocumentKeySet *)removeMutationBatches:(NSArray<FSTMutationBatch *> *)batches {
+ // TODO(klimt): Could this be an NSMutableDictionary?
+ __block FSTDocumentKeySet *affectedDocs = [FSTDocumentKeySet keySet];
+
+ for (FSTMutationBatch *batch in batches) {
+ for (FSTMutation *mutation in batch.mutations) {
+ const DocumentKey &key = mutation.key;
+ affectedDocs = [affectedDocs setByAddingObject:key];
+ }
+ }
+
+ [self.mutationQueue removeMutationBatches:batches];
+
+ return affectedDocs;
+}
+
+- (void)applyBatchResult:(FSTMutationBatchResult *)batchResult {
+ FSTMutationBatch *batch = batchResult.batch;
+ FSTDocumentKeySet *docKeys = batch.keys;
+ [docKeys enumerateObjectsUsingBlock:^(FSTDocumentKey *docKey, BOOL *stop) {
+ FSTMaybeDocument *_Nullable remoteDoc = [self.remoteDocumentCache entryForKey:docKey];
+ FSTMaybeDocument *_Nullable doc = remoteDoc;
+ FSTSnapshotVersion *ackVersion = batchResult.docVersions[docKey];
+ FSTAssert(ackVersion, @"docVersions should contain every doc in the write.");
+ if (!doc || [doc.version compare:ackVersion] == NSOrderedAscending) {
+ doc = [batch applyTo:doc documentKey:docKey mutationBatchResult:batchResult];
+ if (!doc) {
+ FSTAssert(!remoteDoc, @"Mutation batch %@ applied to document %@ resulted in nil.", batch,
+ remoteDoc);
+ } else {
+ [self.remoteDocumentCache addEntry:doc];
+ }
+ }
+ }];
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/Local/FSTLocalViewChanges.h b/Firestore/Source/Local/FSTLocalViewChanges.h
index e391472..eb84642 100644
--- a/Firestore/Source/Local/FSTLocalViewChanges.h
+++ b/Firestore/Source/Local/FSTLocalViewChanges.h
@@ -18,7 +18,6 @@
#import "Firestore/Source/Model/FSTDocumentKeySet.h"
-@class FSTDocumentKey;
@class FSTDocumentSet;
@class FSTMutation;
@class FSTQuery;
diff --git a/Firestore/Source/Local/FSTLocalViewChanges.m b/Firestore/Source/Local/FSTLocalViewChanges.mm
index 9a7f445..9a7f445 100644
--- a/Firestore/Source/Local/FSTLocalViewChanges.m
+++ b/Firestore/Source/Local/FSTLocalViewChanges.mm
diff --git a/Firestore/Source/Local/FSTLocalWriteResult.m b/Firestore/Source/Local/FSTLocalWriteResult.mm
index c1753fe..c1753fe 100644
--- a/Firestore/Source/Local/FSTLocalWriteResult.m
+++ b/Firestore/Source/Local/FSTLocalWriteResult.mm
diff --git a/Firestore/Source/Local/FSTMemoryMutationQueue.m b/Firestore/Source/Local/FSTMemoryMutationQueue.mm
index b155264..8028bb3 100644
--- a/Firestore/Source/Local/FSTMemoryMutationQueue.m
+++ b/Firestore/Source/Local/FSTMemoryMutationQueue.mm
@@ -18,15 +18,22 @@
#import "Firestore/Source/Core/FSTQuery.h"
#import "Firestore/Source/Local/FSTDocumentReference.h"
-#import "Firestore/Source/Model/FSTDocumentKey.h"
#import "Firestore/Source/Model/FSTMutation.h"
#import "Firestore/Source/Model/FSTMutationBatch.h"
-#import "Firestore/Source/Model/FSTPath.h"
#import "Firestore/Source/Util/FSTAssert.h"
-#import "Firestore/Source/Util/FSTComparison.h"
+
+#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+#include "Firestore/core/src/firebase/firestore/model/resource_path.h"
+
+using firebase::firestore::model::DocumentKey;
+using firebase::firestore::model::ResourcePath;
NS_ASSUME_NONNULL_BEGIN
+static const NSComparator NumberComparator = ^NSComparisonResult(NSNumber *left, NSNumber *right) {
+ return [left compare:right];
+};
+
@interface FSTMemoryMutationQueue ()
/**
@@ -85,7 +92,7 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark - FSTMutationQueue implementation
-- (void)startWithGroup:(FSTWriteGroup *)group {
+- (void)start {
// Note: The queue may be shutdown / started multiple times, since we maintain the queue for the
// duration of the app session in case a user logs out / back in. To behave like the
// LevelDB-backed MutationQueue (and accommodate tests that expect as much), we reset nextBatchID
@@ -98,9 +105,6 @@ NS_ASSUME_NONNULL_BEGIN
@"highestAcknowledgedBatchID must be less than the nextBatchID");
}
-- (void)shutdown {
-}
-
- (BOOL)isEmpty {
// If the queue has any entries at all, the first entry must not be a tombstone (otherwise it
// would have been removed already).
@@ -111,9 +115,7 @@ NS_ASSUME_NONNULL_BEGIN
return _highestAcknowledgedBatchID;
}
-- (void)acknowledgeBatch:(FSTMutationBatch *)batch
- streamToken:(nullable NSData *)streamToken
- group:(__unused FSTWriteGroup *)group {
+- (void)acknowledgeBatch:(FSTMutationBatch *)batch streamToken:(nullable NSData *)streamToken {
NSMutableArray<FSTMutationBatch *> *queue = self.queue;
FSTBatchID batchID = batch.batchID;
@@ -132,13 +134,8 @@ NS_ASSUME_NONNULL_BEGIN
self.lastStreamToken = streamToken;
}
-- (void)setLastStreamToken:(nullable NSData *)streamToken group:(__unused FSTWriteGroup *)group {
- self.lastStreamToken = streamToken;
-}
-
-- (FSTMutationBatch *)addMutationBatchWithWriteTime:(FSTTimestamp *)localWriteTime
- mutations:(NSArray<FSTMutation *> *)mutations
- group:(FSTWriteGroup *)group {
+- (FSTMutationBatch *)addMutationBatchWithWriteTime:(FIRTimestamp *)localWriteTime
+ mutations:(NSArray<FSTMutation *> *)mutations {
FSTAssert(mutations.count > 0, @"Mutation batches should not be empty");
FSTBatchID batchID = self.nextBatchID;
@@ -185,10 +182,10 @@ NS_ASSUME_NONNULL_BEGIN
// All batches with batchID <= self.highestAcknowledgedBatchID have been acknowledged so the
// first unacknowledged batch after batchID will have a batchID larger than both of these values.
- batchID = MAX(batchID + 1, self.highestAcknowledgedBatchID);
+ FSTBatchID nextBatchID = MAX(batchID, self.highestAcknowledgedBatchID) + 1;
// The requested batchID may still be out of range so normalize it to the start of the queue.
- NSInteger rawIndex = [self indexOfBatchID:batchID];
+ NSInteger rawIndex = [self indexOfBatchID:nextBatchID];
NSUInteger index = rawIndex < 0 ? 0 : (NSUInteger)rawIndex;
// Finally return the first non-tombstone batch.
@@ -224,7 +221,7 @@ NS_ASSUME_NONNULL_BEGIN
}
- (NSArray<FSTMutationBatch *> *)allMutationBatchesAffectingDocumentKey:
- (FSTDocumentKey *)documentKey {
+ (const DocumentKey &)documentKey {
FSTDocumentReference *start = [[FSTDocumentReference alloc] initWithKey:documentKey ID:0];
NSMutableArray<FSTMutationBatch *> *result = [NSMutableArray array];
@@ -245,25 +242,25 @@ NS_ASSUME_NONNULL_BEGIN
- (NSArray<FSTMutationBatch *> *)allMutationBatchesAffectingQuery:(FSTQuery *)query {
// Use the query path as a prefix for testing if a document matches the query.
- FSTResourcePath *prefix = query.path;
- int immediateChildrenPathLength = prefix.length + 1;
+ const ResourcePath &prefix = query.path;
+ size_t immediateChildrenPathLength = prefix.size() + 1;
// Construct a document reference for actually scanning the index. Unlike the prefix, the document
// key in this reference must have an even number of segments. The empty segment can be used as
// a suffix of the query path because it precedes all other segments in an ordered traversal.
- FSTResourcePath *startPath = query.path;
- if (![FSTDocumentKey isDocumentKey:startPath]) {
- startPath = [startPath pathByAppendingSegment:@""];
+ ResourcePath startPath = query.path;
+ if (!DocumentKey::IsDocumentKey(startPath)) {
+ startPath = startPath.Append("");
}
FSTDocumentReference *start =
- [[FSTDocumentReference alloc] initWithKey:[FSTDocumentKey keyWithPath:startPath] ID:0];
+ [[FSTDocumentReference alloc] initWithKey:DocumentKey{startPath} ID:0];
// Find unique batchIDs referenced by all documents potentially matching the query.
__block FSTImmutableSortedSet<NSNumber *> *uniqueBatchIDs =
- [FSTImmutableSortedSet setWithComparator:FSTNumberComparator];
+ [FSTImmutableSortedSet setWithComparator:NumberComparator];
FSTDocumentReferenceBlock block = ^(FSTDocumentReference *reference, BOOL *stop) {
- FSTResourcePath *rowKeyPath = reference.key.path;
- if (![prefix isPrefixOfPath:rowKeyPath]) {
+ const ResourcePath &rowKeyPath = reference.key.path();
+ if (!prefix.IsPrefixOf(rowKeyPath)) {
*stop = YES;
return;
}
@@ -271,7 +268,7 @@ NS_ASSUME_NONNULL_BEGIN
// Rows with document keys more than one segment longer than the query path can't be matches.
// For example, a query on 'rooms' can't match the document /rooms/abc/messages/xyx.
// TODO(mcg): we'll need a different scanner when we implement ancestor queries.
- if (rowKeyPath.length != immediateChildrenPathLength) {
+ if (rowKeyPath.size() != immediateChildrenPathLength) {
return;
}
@@ -292,7 +289,7 @@ NS_ASSUME_NONNULL_BEGIN
return result;
}
-- (void)removeMutationBatches:(NSArray<FSTMutationBatch *> *)batches group:(FSTWriteGroup *)group {
+- (void)removeMutationBatches:(NSArray<FSTMutationBatch *> *)batches {
NSUInteger batchCount = batches.count;
FSTAssert(batchCount > 0, @"Should not remove mutations when none exist.");
@@ -348,7 +345,7 @@ NS_ASSUME_NONNULL_BEGIN
for (FSTMutationBatch *batch in batches) {
FSTBatchID batchID = batch.batchID;
for (FSTMutation *mutation in batch.mutations) {
- FSTDocumentKey *key = mutation.key;
+ const DocumentKey &key = mutation.key;
[garbageCollector addPotentialGarbageKey:key];
FSTDocumentReference *reference = [[FSTDocumentReference alloc] initWithKey:key ID:batchID];
@@ -367,15 +364,15 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark - FSTGarbageSource implementation
-- (BOOL)containsKey:(FSTDocumentKey *)key {
+- (BOOL)containsKey:(const DocumentKey &)key {
// Create a reference with a zero ID as the start position to find any document reference with
// this key.
FSTDocumentReference *reference = [[FSTDocumentReference alloc] initWithKey:key ID:0];
NSEnumerator<FSTDocumentReference *> *enumerator =
[self.batchesByDocumentKey objectEnumeratorFrom:reference];
- FSTDocumentKey *_Nullable firstKey = [enumerator nextObject].key;
- return [firstKey isEqual:key];
+ FSTDocumentReference *_Nullable firstReference = [enumerator nextObject];
+ return firstReference && firstReference.key == reference.key;
}
#pragma mark - Helpers
diff --git a/Firestore/Source/Local/FSTMemoryPersistence.m b/Firestore/Source/Local/FSTMemoryPersistence.mm
index e301820..8d74881 100644
--- a/Firestore/Source/Local/FSTMemoryPersistence.m
+++ b/Firestore/Source/Local/FSTMemoryPersistence.mm
@@ -16,20 +16,21 @@
#import "Firestore/Source/Local/FSTMemoryPersistence.h"
-#import "Firestore/Source/Auth/FSTUser.h"
+#include <unordered_map>
+
#import "Firestore/Source/Local/FSTMemoryMutationQueue.h"
#import "Firestore/Source/Local/FSTMemoryQueryCache.h"
#import "Firestore/Source/Local/FSTMemoryRemoteDocumentCache.h"
-#import "Firestore/Source/Local/FSTWriteGroup.h"
-#import "Firestore/Source/Local/FSTWriteGroupTracker.h"
#import "Firestore/Source/Util/FSTAssert.h"
+#include "Firestore/core/src/firebase/firestore/auth/user.h"
+
+using firebase::firestore::auth::HashUser;
+using firebase::firestore::auth::User;
+
NS_ASSUME_NONNULL_BEGIN
@interface FSTMemoryPersistence ()
-@property(nonatomic, strong, nonnull) FSTWriteGroupTracker *writeGroupTracker;
-@property(nonatomic, strong, nonnull)
- NSMutableDictionary<FSTUser *, id<FSTMutationQueue>> *mutationQueues;
@property(nonatomic, assign, getter=isStarted) BOOL started;
@end
@@ -46,6 +47,10 @@ NS_ASSUME_NONNULL_BEGIN
/** The FSTRemoteDocumentCache representing the persisted cache of remote documents. */
FSTMemoryRemoteDocumentCache *_remoteDocumentCache;
+
+ std::unordered_map<User, id<FSTMutationQueue>, HashUser> _mutationQueues;
+
+ FSTTransactionRunner _transactionRunner;
}
+ (instancetype)persistence {
@@ -54,10 +59,8 @@ NS_ASSUME_NONNULL_BEGIN
- (instancetype)init {
if (self = [super init]) {
- _writeGroupTracker = [FSTWriteGroupTracker tracker];
_queryCache = [[FSTMemoryQueryCache alloc] init];
_remoteDocumentCache = [[FSTMemoryRemoteDocumentCache alloc] init];
- _mutationQueues = [NSMutableDictionary dictionary];
}
return self;
}
@@ -75,11 +78,15 @@ NS_ASSUME_NONNULL_BEGIN
self.started = NO;
}
-- (id<FSTMutationQueue>)mutationQueueForUser:(FSTUser *)user {
- id<FSTMutationQueue> queue = self.mutationQueues[user];
+- (const FSTTransactionRunner &)run {
+ return _transactionRunner;
+}
+
+- (id<FSTMutationQueue>)mutationQueueForUser:(const User &)user {
+ id<FSTMutationQueue> queue = _mutationQueues[user];
if (!queue) {
queue = [FSTMemoryMutationQueue mutationQueue];
- self.mutationQueues[user] = queue;
+ _mutationQueues[user] = queue;
}
return queue;
}
@@ -92,16 +99,6 @@ NS_ASSUME_NONNULL_BEGIN
return _remoteDocumentCache;
}
-- (FSTWriteGroup *)startGroupWithAction:(NSString *)action {
- return [self.writeGroupTracker startGroupWithAction:action];
-}
-
-- (void)commitGroup:(FSTWriteGroup *)group {
- [self.writeGroupTracker endGroup:group];
-
- FSTAssert(group.isEmpty, @"Memory persistence shouldn't use write groups: %@", group.action);
-}
-
@end
NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/Local/FSTMemoryQueryCache.m b/Firestore/Source/Local/FSTMemoryQueryCache.mm
index 8d37bcb..18d14f2 100644
--- a/Firestore/Source/Local/FSTMemoryQueryCache.m
+++ b/Firestore/Source/Local/FSTMemoryQueryCache.mm
@@ -21,6 +21,8 @@
#import "Firestore/Source/Local/FSTQueryData.h"
#import "Firestore/Source/Local/FSTReferenceSet.h"
+#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+
NS_ASSUME_NONNULL_BEGIN
@interface FSTMemoryQueryCache ()
@@ -34,12 +36,14 @@ NS_ASSUME_NONNULL_BEGIN
/** The highest numbered target ID encountered. */
@property(nonatomic, assign) FSTTargetID highestTargetID;
+@property(nonatomic, assign) FSTListenSequenceNumber highestListenSequenceNumber;
+
+/** The last received snapshot version. */
+@property(nonatomic, strong) FSTSnapshotVersion *lastRemoteSnapshotVersion;
+
@end
-@implementation FSTMemoryQueryCache {
- /** The last received snapshot version. */
- FSTSnapshotVersion *_lastRemoteSnapshotVersion;
-}
+@implementation FSTMemoryQueryCache
- (instancetype)init {
if (self = [super init]) {
@@ -57,31 +61,48 @@ NS_ASSUME_NONNULL_BEGIN
// Nothing to do.
}
-- (void)shutdown {
- // No resources to release.
-}
-
- (FSTTargetID)highestTargetID {
return _highestTargetID;
}
-- (FSTSnapshotVersion *)lastRemoteSnapshotVersion {
+- (FSTListenSequenceNumber)highestListenSequenceNumber {
+ return _highestListenSequenceNumber;
+}
+
+/*- (FSTSnapshotVersion *)lastRemoteSnapshotVersion {
return _lastRemoteSnapshotVersion;
}
- (void)setLastRemoteSnapshotVersion:(FSTSnapshotVersion *)snapshotVersion
group:(FSTWriteGroup *)group {
_lastRemoteSnapshotVersion = snapshotVersion;
+}*/
+
+- (void)addQueryData:(FSTQueryData *)queryData {
+ self.queries[queryData.query] = queryData;
+ if (queryData.targetID > self.highestTargetID) {
+ self.highestTargetID = queryData.targetID;
+ }
+ if (queryData.sequenceNumber > self.highestListenSequenceNumber) {
+ self.highestListenSequenceNumber = queryData.sequenceNumber;
+ }
}
-- (void)addQueryData:(FSTQueryData *)queryData group:(__unused FSTWriteGroup *)group {
+- (void)updateQueryData:(FSTQueryData *)queryData {
self.queries[queryData.query] = queryData;
if (queryData.targetID > self.highestTargetID) {
self.highestTargetID = queryData.targetID;
}
+ if (queryData.sequenceNumber > self.highestListenSequenceNumber) {
+ self.highestListenSequenceNumber = queryData.sequenceNumber;
+ }
+}
+
+- (int32_t)count {
+ return (int32_t)[self.queries count];
}
-- (void)removeQueryData:(FSTQueryData *)queryData group:(__unused FSTWriteGroup *)group {
+- (void)removeQueryData:(FSTQueryData *)queryData {
[self.queries removeObjectForKey:queryData.query];
[self.references removeReferencesForID:queryData.targetID];
}
@@ -92,19 +113,15 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark Reference tracking
-- (void)addMatchingKeys:(FSTDocumentKeySet *)keys
- forTargetID:(FSTTargetID)targetID
- group:(__unused FSTWriteGroup *)group {
+- (void)addMatchingKeys:(FSTDocumentKeySet *)keys forTargetID:(FSTTargetID)targetID {
[self.references addReferencesToKeys:keys forID:targetID];
}
-- (void)removeMatchingKeys:(FSTDocumentKeySet *)keys
- forTargetID:(FSTTargetID)targetID
- group:(__unused FSTWriteGroup *)group {
+- (void)removeMatchingKeys:(FSTDocumentKeySet *)keys forTargetID:(FSTTargetID)targetID {
[self.references removeReferencesToKeys:keys forID:targetID];
}
-- (void)removeMatchingKeysForTargetID:(FSTTargetID)targetID group:(__unused FSTWriteGroup *)group {
+- (void)removeMatchingKeysForTargetID:(FSTTargetID)targetID {
[self.references removeReferencesForID:targetID];
}
@@ -122,7 +139,7 @@ NS_ASSUME_NONNULL_BEGIN
self.references.garbageCollector = garbageCollector;
}
-- (BOOL)containsKey:(FSTDocumentKey *)key {
+- (BOOL)containsKey:(const firebase::firestore::model::DocumentKey &)key {
return [self.references containsKey:key];
}
diff --git a/Firestore/Source/Local/FSTMemoryRemoteDocumentCache.m b/Firestore/Source/Local/FSTMemoryRemoteDocumentCache.mm
index 9bbc047..75bec8f 100644
--- a/Firestore/Source/Local/FSTMemoryRemoteDocumentCache.m
+++ b/Firestore/Source/Local/FSTMemoryRemoteDocumentCache.mm
@@ -19,8 +19,10 @@
#import "Firestore/Source/Core/FSTQuery.h"
#import "Firestore/Source/Model/FSTDocument.h"
#import "Firestore/Source/Model/FSTDocumentDictionary.h"
-#import "Firestore/Source/Model/FSTDocumentKey.h"
-#import "Firestore/Source/Model/FSTPath.h"
+
+#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+
+using firebase::firestore::model::DocumentKey;
NS_ASSUME_NONNULL_BEGIN
@@ -40,19 +42,16 @@ NS_ASSUME_NONNULL_BEGIN
return self;
}
-- (void)shutdown {
-}
-
-- (void)addEntry:(FSTMaybeDocument *)document group:(FSTWriteGroup *)group {
+- (void)addEntry:(FSTMaybeDocument *)document {
self.docs = [self.docs dictionaryBySettingObject:document forKey:document.key];
}
-- (void)removeEntryForKey:(FSTDocumentKey *)key group:(FSTWriteGroup *)group {
+- (void)removeEntryForKey:(const DocumentKey &)key {
self.docs = [self.docs dictionaryByRemovingObjectForKey:key];
}
-- (nullable FSTMaybeDocument *)entryForKey:(FSTDocumentKey *)key {
- return self.docs[key];
+- (nullable FSTMaybeDocument *)entryForKey:(const DocumentKey &)key {
+ return self.docs[static_cast<FSTDocumentKey *>(key)];
}
- (FSTDocumentDictionary *)documentsMatchingQuery:(FSTQuery *)query {
@@ -60,10 +59,10 @@ NS_ASSUME_NONNULL_BEGIN
// Documents are ordered by key, so we can use a prefix scan to narrow down the documents
// we need to match the query against.
- FSTDocumentKey *prefix = [FSTDocumentKey keyWithPath:[query.path pathByAppendingSegment:@""]];
+ FSTDocumentKey *prefix = [FSTDocumentKey keyWithPath:query.path.Append("")];
NSEnumerator<FSTDocumentKey *> *enumerator = [self.docs keyEnumeratorFrom:prefix];
for (FSTDocumentKey *key in enumerator) {
- if (![query.path isPrefixOfPath:key.path]) {
+ if (!query.path.IsPrefixOf(key.path)) {
break;
}
FSTMaybeDocument *maybeDoc = self.docs[key];
diff --git a/Firestore/Source/Local/FSTMutationQueue.h b/Firestore/Source/Local/FSTMutationQueue.h
index a1eddd4..89b1a52 100644
--- a/Firestore/Source/Local/FSTMutationQueue.h
+++ b/Firestore/Source/Local/FSTMutationQueue.h
@@ -19,12 +19,12 @@
#import "Firestore/Source/Core/FSTTypes.h"
#import "Firestore/Source/Local/FSTGarbageCollector.h"
-@class FSTDocumentKey;
+#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+
@class FSTMutation;
@class FSTMutationBatch;
@class FSTQuery;
-@class FSTTimestamp;
-@class FSTWriteGroup;
+@class FIRTimestamp;
NS_ASSUME_NONNULL_BEGIN
@@ -41,10 +41,7 @@ NS_ASSUME_NONNULL_BEGIN
* than nextBatchID. This prevents the local store from creating new batches that the mutation
* queue would consider erroneously acknowledged.
*/
-- (void)startWithGroup:(FSTWriteGroup *)group;
-
-/** Shuts this mutation queue down, closing open files, etc. */
-- (void)shutdown;
+- (void)start;
/** Returns YES if this queue contains no mutation batches. */
- (BOOL)isEmpty;
@@ -64,20 +61,17 @@ NS_ASSUME_NONNULL_BEGIN
- (FSTBatchID)highestAcknowledgedBatchID;
/** Acknowledges the given batch. */
-- (void)acknowledgeBatch:(FSTMutationBatch *)batch
- streamToken:(nullable NSData *)streamToken
- group:(FSTWriteGroup *)group;
+- (void)acknowledgeBatch:(FSTMutationBatch *)batch streamToken:(nullable NSData *)streamToken;
/** Returns the current stream token for this mutation queue. */
- (nullable NSData *)lastStreamToken;
/** Sets the stream token for this mutation queue. */
-- (void)setLastStreamToken:(nullable NSData *)streamToken group:(FSTWriteGroup *)group;
+- (void)setLastStreamToken:(nullable NSData *)streamToken;
/** Creates a new mutation batch and adds it to this mutation queue. */
-- (FSTMutationBatch *)addMutationBatchWithWriteTime:(FSTTimestamp *)localWriteTime
- mutations:(NSArray<FSTMutation *> *)mutations
- group:(FSTWriteGroup *)group;
+- (FSTMutationBatch *)addMutationBatchWithWriteTime:(FIRTimestamp *)localWriteTime
+ mutations:(NSArray<FSTMutation *> *)mutations;
/** Loads the mutation batch with the given batchID. */
- (nullable FSTMutationBatch *)lookupMutationBatch:(FSTBatchID)batchID;
@@ -123,7 +117,7 @@ NS_ASSUME_NONNULL_BEGIN
// TODO(mcg): This should really return an NSEnumerator
// also for b/32992024, all backing stores should really index by document key
- (NSArray<FSTMutationBatch *> *)allMutationBatchesAffectingDocumentKey:
- (FSTDocumentKey *)documentKey;
+ (const firebase::firestore::model::DocumentKey &)documentKey;
/**
* Finds all mutation batches that could affect the results for the given query. Not all
@@ -149,7 +143,7 @@ NS_ASSUME_NONNULL_BEGIN
* In both cases, the array of mutations to remove must be a contiguous range of batchIds. This is
* most easily accomplished by loading mutations with @a -allMutationBatchesThroughBatchID:.
*/
-- (void)removeMutationBatches:(NSArray<FSTMutationBatch *> *)batches group:(FSTWriteGroup *)group;
+- (void)removeMutationBatches:(NSArray<FSTMutationBatch *> *)batches;
/** Performs a consistency check, examining the mutation queue for any leaks, if possible. */
- (void)performConsistencyCheck;
diff --git a/Firestore/Source/Local/FSTNoOpGarbageCollector.h b/Firestore/Source/Local/FSTNoOpGarbageCollector.h
index c9b5862..f58c5a0 100644
--- a/Firestore/Source/Local/FSTNoOpGarbageCollector.h
+++ b/Firestore/Source/Local/FSTNoOpGarbageCollector.h
@@ -18,8 +18,6 @@
#import "Firestore/Source/Local/FSTGarbageCollector.h"
-@class FSTDocumentKey;
-
NS_ASSUME_NONNULL_BEGIN
/**
diff --git a/Firestore/Source/Local/FSTNoOpGarbageCollector.m b/Firestore/Source/Local/FSTNoOpGarbageCollector.mm
index e03b599..421d283 100644
--- a/Firestore/Source/Local/FSTNoOpGarbageCollector.m
+++ b/Firestore/Source/Local/FSTNoOpGarbageCollector.mm
@@ -16,6 +16,12 @@
#import "Firestore/Source/Local/FSTNoOpGarbageCollector.h"
+#include <set>
+
+#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+
+using firebase::firestore::model::DocumentKey;
+
NS_ASSUME_NONNULL_BEGIN
@implementation FSTNoOpGarbageCollector
@@ -32,12 +38,12 @@ NS_ASSUME_NONNULL_BEGIN
// Not tracking garbage so don't track sources.
}
-- (void)addPotentialGarbageKey:(FSTDocumentKey *)key {
+- (void)addPotentialGarbageKey:(const DocumentKey&)key {
// Not tracking garbage so ignore.
}
-- (NSSet<FSTDocumentKey *> *)collectGarbage {
- return [NSSet set];
+- (std::set<DocumentKey>)collectGarbage {
+ return {};
}
@end
diff --git a/Firestore/Source/Local/FSTPersistence.h b/Firestore/Source/Local/FSTPersistence.h
index cf07a9e..2294ef1 100644
--- a/Firestore/Source/Local/FSTPersistence.h
+++ b/Firestore/Source/Local/FSTPersistence.h
@@ -16,8 +16,9 @@
#import <Foundation/Foundation.h>
-@class FSTUser;
-@class FSTWriteGroup;
+#import "Firestore/Source/Util/FSTAssert.h"
+#include "Firestore/core/src/firebase/firestore/auth/user.h"
+
@protocol FSTMutationQueue;
@protocol FSTQueryCache;
@protocol FSTRemoteDocumentCache;
@@ -54,6 +55,7 @@ NS_ASSUME_NONNULL_BEGIN
* FSTPersistence. The cost is that the FSTLocalStore needs to be slightly careful about the order
* of its reads and writes in order to avoid relying on being able to read back uncommitted writes.
*/
+struct FSTTransactionRunner;
@protocol FSTPersistence <NSObject>
/**
@@ -75,7 +77,7 @@ NS_ASSUME_NONNULL_BEGIN
* implementation to the extent possible (e.g. in the case of uid switching from
* sally=>jack=>sally, sally's mutation queue will be preserved).
*/
-- (id<FSTMutationQueue>)mutationQueueForUser:(FSTUser *)user;
+- (id<FSTMutationQueue>)mutationQueueForUser:(const firebase::firestore::auth::User &)user;
/** Creates an FSTQueryCache representing the persisted cache of queries. */
- (id<FSTQueryCache>)queryCache;
@@ -83,21 +85,82 @@ NS_ASSUME_NONNULL_BEGIN
/** Creates an FSTRemoteDocumentCache representing the persisted cache of remote documents. */
- (id<FSTRemoteDocumentCache>)remoteDocumentCache;
-/**
- * Creates an FSTWriteGroup with the specified action description.
- *
- * @param action A description of the action performed by this group, used for logging.
- * @return The created group.
- */
-- (FSTWriteGroup *)startGroupWithAction:(NSString *)action;
+@property(nonatomic, readonly, assign) const FSTTransactionRunner &run;
-/**
- * Commits all accumulated changes in the given group. If there are no changes this is a no-op.
- *
- * @param group The group of changes to write as a unit.
- */
-- (void)commitGroup:(FSTWriteGroup *)group;
+@end
+
+@protocol FSTTransactional
+
+- (void)startTransaction:(absl::string_view)label;
+
+- (void)commitTransaction;
@end
+struct FSTTransactionRunner {
+// Intentionally disable nullability checking for this function. We cannot properly annotate
+// the function because this function can handle both pointer and non-pointer types. It is an error
+// to annotate non-pointer types with a nullability annotation.
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wnullability-completeness"
+
+ /**
+ * The following two functions handle accepting callables and optionally running them within a
+ * transaction. Persistence layers that conform to the FSTTransactional protocol can set
+ * themselves as the backing persistence for a transaction runner, in which case a transaction
+ * will be started before a block is run, and committed after the block has executed. If there is
+ * no backing instance of FSTTransactional, the block will be run directly.
+ *
+ * There are two instances of operator() to handle the case where the block returns void, rather
+ * than a type.
+ *
+ * The transaction runner keeps a weak reference to the backing persistence so as not to cause a
+ * retain cycle. The reference is upgraded to strong (with a fatal error if it has disappeared)
+ * for the duration of running a transaction.
+ */
+
+ template <typename F>
+ auto operator()(absl::string_view label, F block) const ->
+ typename std::enable_if<std::is_void<decltype(block())>::value, void>::type {
+ __strong id<FSTTransactional> strongDb = _db;
+ if (!strongDb && _expect_db) {
+ FSTCFail(@"Transaction runner accessed without underlying db when it expected one");
+ }
+ if (strongDb) {
+ [strongDb startTransaction:label];
+ }
+ block();
+ if (strongDb) {
+ [strongDb commitTransaction];
+ }
+ }
+
+ template <typename F>
+ auto operator()(absl::string_view label, F block) const ->
+ typename std::enable_if<!std::is_void<decltype(block())>::value, decltype(block())>::type {
+ using ReturnT = decltype(block());
+ __strong id<FSTTransactional> strongDb = _db;
+ if (!strongDb && _expect_db) {
+ FSTCFail(@"Transaction runner accessed without underlying db when it expected one");
+ }
+ if (strongDb) {
+ [strongDb startTransaction:label];
+ }
+ ReturnT result = block();
+ if (strongDb) {
+ [strongDb commitTransaction];
+ }
+ return result;
+ }
+#pragma clang diagnostic pop
+ void SetBackingPersistence(id<FSTTransactional> db) {
+ _db = db;
+ _expect_db = true;
+ }
+
+ private:
+ __weak id<FSTTransactional> _db;
+ bool _expect_db = false;
+};
+
NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/Local/FSTQueryCache.h b/Firestore/Source/Local/FSTQueryCache.h
index e0cf4c8..d797d49 100644
--- a/Firestore/Source/Local/FSTQueryCache.h
+++ b/Firestore/Source/Local/FSTQueryCache.h
@@ -20,12 +20,10 @@
#import "Firestore/Source/Local/FSTGarbageCollector.h"
#import "Firestore/Source/Model/FSTDocumentKeySet.h"
-@class FSTDocumentKey;
@class FSTDocumentSet;
@class FSTMaybeDocument;
@class FSTQuery;
@class FSTQueryData;
-@class FSTWriteGroup;
@class FSTSnapshotVersion;
NS_ASSUME_NONNULL_BEGIN
@@ -42,9 +40,6 @@ NS_ASSUME_NONNULL_BEGIN
/** Starts the query cache up. */
- (void)start;
-/** Shuts this cache down, closing open files, etc. */
-- (void)shutdown;
-
/**
* Returns the highest target ID of any query in the cache. Typically called during startup to
* seed a target ID generator and avoid collisions with existing queries. If there are no queries
@@ -53,6 +48,11 @@ NS_ASSUME_NONNULL_BEGIN
- (FSTTargetID)highestTargetID;
/**
+ * Returns the highest listen sequence number of any query seen by the cache.
+ */
+- (FSTListenSequenceNumber)highestListenSequenceNumber;
+
+/**
* A global snapshot version representing the last consistent snapshot we received from the
* backend. This is monotonically increasing and any snapshots received from the backend prior to
* this version (e.g. for targets resumed with a resume_token) should be suppressed (buffered)
@@ -69,21 +69,31 @@ NS_ASSUME_NONNULL_BEGIN
*
* @param snapshotVersion The new snapshot version.
*/
-- (void)setLastRemoteSnapshotVersion:(FSTSnapshotVersion *)snapshotVersion
- group:(FSTWriteGroup *)group;
+- (void)setLastRemoteSnapshotVersion:(FSTSnapshotVersion *)snapshotVersion;
/**
- * Adds or replaces an entry in the cache.
+ * Adds an entry in the cache.
*
- * The cache key is extracted from `queryData.query`. If there is already a cache entry for the
- * key, it will be replaced.
+ * The cache key is extracted from `queryData.query`. The key must not already exist in the cache.
*
- * @param queryData An FSTQueryData instance to put in the cache.
+ * @param queryData A new FSTQueryData instance to put in the cache.
*/
-- (void)addQueryData:(FSTQueryData *)queryData group:(FSTWriteGroup *)group;
+- (void)addQueryData:(FSTQueryData *)queryData;
+
+/**
+ * Updates an entry in the cache.
+ *
+ * The cache key is extracted from `queryData.query`. The entry must already exist in the cache,
+ * and it will be replaced.
+ * @param queryData An FSTQueryData instance to replace an existing entry in the cache
+ */
+- (void)updateQueryData:(FSTQueryData *)queryData;
/** Removes the cached entry for the given query data (no-op if no entry exists). */
-- (void)removeQueryData:(FSTQueryData *)queryData group:(FSTWriteGroup *)group;
+- (void)removeQueryData:(FSTQueryData *)queryData;
+
+/** Returns the number of targets cached. */
+- (int32_t)count;
/**
* Looks up an FSTQueryData entry in the cache.
@@ -94,17 +104,13 @@ NS_ASSUME_NONNULL_BEGIN
- (nullable FSTQueryData *)queryDataForQuery:(FSTQuery *)query;
/** Adds the given document keys to cached query results of the given target ID. */
-- (void)addMatchingKeys:(FSTDocumentKeySet *)keys
- forTargetID:(FSTTargetID)targetID
- group:(FSTWriteGroup *)group;
+- (void)addMatchingKeys:(FSTDocumentKeySet *)keys forTargetID:(FSTTargetID)targetID;
/** Removes the given document keys from the cached query results of the given target ID. */
-- (void)removeMatchingKeys:(FSTDocumentKeySet *)keys
- forTargetID:(FSTTargetID)targetID
- group:(FSTWriteGroup *)group;
+- (void)removeMatchingKeys:(FSTDocumentKeySet *)keys forTargetID:(FSTTargetID)targetID;
/** Removes all the keys in the query results of the given target ID. */
-- (void)removeMatchingKeysForTargetID:(FSTTargetID)targetID group:(FSTWriteGroup *)group;
+- (void)removeMatchingKeysForTargetID:(FSTTargetID)targetID;
- (FSTDocumentKeySet *)matchingKeysForTargetID:(FSTTargetID)targetID;
diff --git a/Firestore/Source/Local/FSTQueryData.h b/Firestore/Source/Local/FSTQueryData.h
index 048bfad..5db2de6 100644
--- a/Firestore/Source/Local/FSTQueryData.h
+++ b/Firestore/Source/Local/FSTQueryData.h
@@ -40,6 +40,7 @@ typedef NS_ENUM(NSInteger, FSTQueryPurpose) {
- (instancetype)initWithQuery:(FSTQuery *)query
targetID:(FSTTargetID)targetID
+ listenSequenceNumber:(FSTListenSequenceNumber)sequenceNumber
purpose:(FSTQueryPurpose)purpose
snapshotVersion:(FSTSnapshotVersion *)snapshotVersion
resumeToken:(NSData *)resumeToken NS_DESIGNATED_INITIALIZER;
@@ -47,6 +48,7 @@ typedef NS_ENUM(NSInteger, FSTQueryPurpose) {
/** Convenience initializer for use when creating an FSTQueryData for the first time. */
- (instancetype)initWithQuery:(FSTQuery *)query
targetID:(FSTTargetID)targetID
+ listenSequenceNumber:(FSTListenSequenceNumber)sequenceNumber
purpose:(FSTQueryPurpose)purpose;
- (instancetype)init NS_UNAVAILABLE;
@@ -64,6 +66,8 @@ typedef NS_ENUM(NSInteger, FSTQueryPurpose) {
*/
@property(nonatomic, assign, readonly) FSTTargetID targetID;
+@property(nonatomic, assign, readonly) FSTListenSequenceNumber sequenceNumber;
+
/** The purpose of the query. */
@property(nonatomic, assign, readonly) FSTQueryPurpose purpose;
diff --git a/Firestore/Source/Local/FSTQueryData.m b/Firestore/Source/Local/FSTQueryData.mm
index 080f136..6bb716a 100644
--- a/Firestore/Source/Local/FSTQueryData.m
+++ b/Firestore/Source/Local/FSTQueryData.mm
@@ -25,6 +25,7 @@ NS_ASSUME_NONNULL_BEGIN
- (instancetype)initWithQuery:(FSTQuery *)query
targetID:(FSTTargetID)targetID
+ listenSequenceNumber:(FSTListenSequenceNumber)sequenceNumber
purpose:(FSTQueryPurpose)purpose
snapshotVersion:(FSTSnapshotVersion *)snapshotVersion
resumeToken:(NSData *)resumeToken {
@@ -32,6 +33,7 @@ NS_ASSUME_NONNULL_BEGIN
if (self) {
_query = query;
_targetID = targetID;
+ _sequenceNumber = sequenceNumber;
_purpose = purpose;
_snapshotVersion = snapshotVersion;
_resumeToken = [resumeToken copy];
@@ -41,9 +43,11 @@ NS_ASSUME_NONNULL_BEGIN
- (instancetype)initWithQuery:(FSTQuery *)query
targetID:(FSTTargetID)targetID
+ listenSequenceNumber:(FSTListenSequenceNumber)sequenceNumber
purpose:(FSTQueryPurpose)purpose {
return [self initWithQuery:query
targetID:targetID
+ listenSequenceNumber:sequenceNumber
purpose:purpose
snapshotVersion:[FSTSnapshotVersion noVersion]
resumeToken:[NSData data]];
@@ -83,6 +87,7 @@ NS_ASSUME_NONNULL_BEGIN
resumeToken:(NSData *)resumeToken {
return [[FSTQueryData alloc] initWithQuery:self.query
targetID:self.targetID
+ listenSequenceNumber:self.sequenceNumber
purpose:self.purpose
snapshotVersion:snapshotVersion
resumeToken:resumeToken];
diff --git a/Firestore/Source/Local/FSTReferenceSet.h b/Firestore/Source/Local/FSTReferenceSet.h
index 66285d9..9d842cb 100644
--- a/Firestore/Source/Local/FSTReferenceSet.h
+++ b/Firestore/Source/Local/FSTReferenceSet.h
@@ -20,8 +20,6 @@
#import "Firestore/Source/Local/FSTGarbageCollector.h"
#import "Firestore/Source/Model/FSTDocumentKeySet.h"
-@class FSTDocumentKey;
-
NS_ASSUME_NONNULL_BEGIN
/**
@@ -46,13 +44,13 @@ NS_ASSUME_NONNULL_BEGIN
- (BOOL)isEmpty;
/** Adds a reference to the given document key for the given ID. */
-- (void)addReferenceToKey:(FSTDocumentKey *)key forID:(int)ID;
+- (void)addReferenceToKey:(const firebase::firestore::model::DocumentKey &)key forID:(int)ID;
/** Add references to the given document keys for the given ID. */
- (void)addReferencesToKeys:(FSTDocumentKeySet *)keys forID:(int)ID;
/** Removes a reference to the given document key for the given ID. */
-- (void)removeReferenceToKey:(FSTDocumentKey *)key forID:(int)ID;
+- (void)removeReferenceToKey:(const firebase::firestore::model::DocumentKey &)key forID:(int)ID;
/** Removes references to the given document keys for the given ID. */
- (void)removeReferencesToKeys:(FSTDocumentKeySet *)keys forID:(int)ID;
diff --git a/Firestore/Source/Local/FSTReferenceSet.m b/Firestore/Source/Local/FSTReferenceSet.mm
index 2acd64b..14f5d47 100644
--- a/Firestore/Source/Local/FSTReferenceSet.m
+++ b/Firestore/Source/Local/FSTReferenceSet.mm
@@ -17,7 +17,10 @@
#import "Firestore/Source/Local/FSTReferenceSet.h"
#import "Firestore/Source/Local/FSTDocumentReference.h"
-#import "Firestore/Source/Model/FSTDocumentKey.h"
+
+#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+
+using firebase::firestore::model::DocumentKey;
NS_ASSUME_NONNULL_BEGIN
@@ -59,7 +62,7 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark - Public methods
-- (void)addReferenceToKey:(FSTDocumentKey *)key forID:(int)ID {
+- (void)addReferenceToKey:(const DocumentKey &)key forID:(int)ID {
FSTDocumentReference *reference = [[FSTDocumentReference alloc] initWithKey:key ID:ID];
self.referencesByKey = [self.referencesByKey setByAddingObject:reference];
self.referencesByID = [self.referencesByID setByAddingObject:reference];
@@ -71,7 +74,7 @@ NS_ASSUME_NONNULL_BEGIN
}];
}
-- (void)removeReferenceToKey:(FSTDocumentKey *)key forID:(int)ID {
+- (void)removeReferenceToKey:(const DocumentKey &)key forID:(int)ID {
[self removeReference:[[FSTDocumentReference alloc] initWithKey:key ID:ID]];
}
@@ -82,9 +85,10 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)removeReferencesForID:(int)ID {
- FSTDocumentKey *emptyKey = [FSTDocumentKey keyWithSegments:@[]];
- FSTDocumentReference *start = [[FSTDocumentReference alloc] initWithKey:emptyKey ID:ID];
- FSTDocumentReference *end = [[FSTDocumentReference alloc] initWithKey:emptyKey ID:(ID + 1)];
+ FSTDocumentReference *start =
+ [[FSTDocumentReference alloc] initWithKey:DocumentKey::Empty() ID:ID];
+ FSTDocumentReference *end =
+ [[FSTDocumentReference alloc] initWithKey:DocumentKey::Empty() ID:(ID + 1)];
[self.referencesByID enumerateObjectsFrom:start
to:end
@@ -106,9 +110,10 @@ NS_ASSUME_NONNULL_BEGIN
}
- (FSTDocumentKeySet *)referencedKeysForID:(int)ID {
- FSTDocumentKey *emptyKey = [FSTDocumentKey keyWithSegments:@[]];
- FSTDocumentReference *start = [[FSTDocumentReference alloc] initWithKey:emptyKey ID:ID];
- FSTDocumentReference *end = [[FSTDocumentReference alloc] initWithKey:emptyKey ID:(ID + 1)];
+ FSTDocumentReference *start =
+ [[FSTDocumentReference alloc] initWithKey:DocumentKey::Empty() ID:ID];
+ FSTDocumentReference *end =
+ [[FSTDocumentReference alloc] initWithKey:DocumentKey::Empty() ID:(ID + 1)];
__block FSTDocumentKeySet *keys = [FSTDocumentKeySet keySet];
[self.referencesByID enumerateObjectsFrom:start
@@ -119,15 +124,15 @@ NS_ASSUME_NONNULL_BEGIN
return keys;
}
-- (BOOL)containsKey:(FSTDocumentKey *)key {
+- (BOOL)containsKey:(const DocumentKey &)key {
// Create a reference with a zero ID as the start position to find any document reference with
// this key.
FSTDocumentReference *reference = [[FSTDocumentReference alloc] initWithKey:key ID:0];
NSEnumerator<FSTDocumentReference *> *enumerator =
[self.referencesByKey objectEnumeratorFrom:reference];
- FSTDocumentKey *_Nullable firstKey = [enumerator nextObject].key;
- return [firstKey isEqual:reference.key];
+ FSTDocumentReference *_Nullable firstReference = [enumerator nextObject];
+ return firstReference && firstReference.key == reference.key;
}
@end
diff --git a/Firestore/Source/Local/FSTRemoteDocumentCache.h b/Firestore/Source/Local/FSTRemoteDocumentCache.h
index fa42ce5..2c6d872 100644
--- a/Firestore/Source/Local/FSTRemoteDocumentCache.h
+++ b/Firestore/Source/Local/FSTRemoteDocumentCache.h
@@ -18,10 +18,10 @@
#import "Firestore/Source/Model/FSTDocumentDictionary.h"
-@class FSTDocumentKey;
+#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+
@class FSTMaybeDocument;
@class FSTQuery;
-@class FSTWriteGroup;
NS_ASSUME_NONNULL_BEGIN
@@ -34,9 +34,6 @@ NS_ASSUME_NONNULL_BEGIN
*/
@protocol FSTRemoteDocumentCache <NSObject>
-/** Shuts this cache down, closing open files, etc. */
-- (void)shutdown;
-
/**
* Adds or replaces an entry in the cache.
*
@@ -45,10 +42,10 @@ NS_ASSUME_NONNULL_BEGIN
*
* @param maybeDocument A FSTDocument or FSTDeletedDocument to put in the cache.
*/
-- (void)addEntry:(FSTMaybeDocument *)maybeDocument group:(FSTWriteGroup *)group;
+- (void)addEntry:(FSTMaybeDocument *)maybeDocument;
/** Removes the cached entry for the given key (no-op if no entry exists). */
-- (void)removeEntryForKey:(FSTDocumentKey *)documentKey group:(FSTWriteGroup *)group;
+- (void)removeEntryForKey:(const firebase::firestore::model::DocumentKey &)documentKey;
/**
* Looks up an entry in the cache.
@@ -56,7 +53,8 @@ NS_ASSUME_NONNULL_BEGIN
* @param documentKey The key of the entry to look up.
* @return The cached FSTDocument or FSTDeletedDocument entry, or nil if we have nothing cached.
*/
-- (nullable FSTMaybeDocument *)entryForKey:(FSTDocumentKey *)documentKey;
+- (nullable FSTMaybeDocument *)entryForKey:
+ (const firebase::firestore::model::DocumentKey &)documentKey;
/**
* Executes a query against the cached FSTDocument entries
diff --git a/Firestore/Source/Local/FSTRemoteDocumentChangeBuffer.h b/Firestore/Source/Local/FSTRemoteDocumentChangeBuffer.h
deleted file mode 100644
index be0d609..0000000
--- a/Firestore/Source/Local/FSTRemoteDocumentChangeBuffer.h
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright 2017 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/Foundation.h>
-
-NS_ASSUME_NONNULL_BEGIN
-
-@protocol FSTRemoteDocumentCache;
-@class FSTMaybeDocument;
-@class FSTDocumentKey;
-@class FSTWriteGroup;
-
-/**
- * An in-memory buffer of entries to be written to an FSTRemoteDocumentCache. It can be used to
- * batch up a set of changes to be written to the cache, but additionally supports reading entries
- * back with the `entryForKey:` method, falling back to the underlying FSTRemoteDocumentCache if
- * no entry is buffered. In the absence of LevelDB transactions (that would allow reading back
- * uncommitted writes), this greatly simplifies the implementation of complex operations that
- * may want to freely read/write entries to the FSTRemoteDocumentCache while still ensuring that
- * the final writing of the buffered entries is atomic.
- *
- * For doing blind writes that don't depend on the current state of the FSTRemoteDocumentCache
- * or for plain reads, you can/should still just use the FSTRemoteDocumentCache directly.
- */
-@interface FSTRemoteDocumentChangeBuffer : NSObject
-
-+ (instancetype)changeBufferWithCache:(id<FSTRemoteDocumentCache>)cache;
-
-- (instancetype)init __attribute__((unavailable("Use a static constructor instead")));
-
-/** Buffers an `FSTRemoteDocumentCache addEntry:group:` call. */
-- (void)addEntry:(FSTMaybeDocument *)maybeDocument;
-
-// NOTE: removeEntryForKey: is not presently necessary and so is omitted.
-
-/**
- * Looks up an entry in the cache. The buffered changes will first be checked, and if no
- * buffered change applies, this will forward to `FSTRemoteDocumentCache entryForKey:`.
- *
- * @param documentKey The key of the entry to look up.
- * @return The cached FSTDocument or FSTDeletedDocument entry, or nil if we have nothing cached.
- */
-- (nullable FSTMaybeDocument *)entryForKey:(FSTDocumentKey *)documentKey;
-
-/**
- * Applies buffered changes to the underlying FSTRemoteDocumentCache, using the provided
- * FSTWriteGroup.
- */
-- (void)applyToWriteGroup:(FSTWriteGroup *)group;
-
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/Local/FSTRemoteDocumentChangeBuffer.m b/Firestore/Source/Local/FSTRemoteDocumentChangeBuffer.m
deleted file mode 100644
index bca587a..0000000
--- a/Firestore/Source/Local/FSTRemoteDocumentChangeBuffer.m
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright 2017 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 "Firestore/Source/Local/FSTRemoteDocumentChangeBuffer.h"
-
-#import "Firestore/Source/Local/FSTRemoteDocumentCache.h"
-#import "Firestore/Source/Model/FSTDocument.h"
-#import "Firestore/Source/Model/FSTDocumentKey.h"
-#import "Firestore/Source/Util/FSTAssert.h"
-
-NS_ASSUME_NONNULL_BEGIN
-
-@interface FSTRemoteDocumentChangeBuffer ()
-
-- (instancetype)initWithCache:(id<FSTRemoteDocumentCache>)cache;
-
-/** The underlying cache we're buffering changes for. */
-@property(nonatomic, strong, nonnull) id<FSTRemoteDocumentCache> remoteDocumentCache;
-
-/** The buffered changes, stored as a dictionary for easy lookups. */
-@property(nonatomic, strong, nullable)
- NSMutableDictionary<FSTDocumentKey *, FSTMaybeDocument *> *changes;
-
-@end
-
-@implementation FSTRemoteDocumentChangeBuffer
-
-+ (instancetype)changeBufferWithCache:(id<FSTRemoteDocumentCache>)cache {
- return [[FSTRemoteDocumentChangeBuffer alloc] initWithCache:cache];
-}
-
-- (instancetype)initWithCache:(id<FSTRemoteDocumentCache>)cache {
- if (self = [super init]) {
- _remoteDocumentCache = cache;
- _changes = [NSMutableDictionary dictionary];
- }
- return self;
-}
-
-- (void)addEntry:(FSTMaybeDocument *)maybeDocument {
- [self assertValid];
-
- self.changes[maybeDocument.key] = maybeDocument;
-}
-
-- (nullable FSTMaybeDocument *)entryForKey:(FSTDocumentKey *)documentKey {
- [self assertValid];
-
- FSTMaybeDocument *bufferedEntry = self.changes[documentKey];
- if (bufferedEntry) {
- return bufferedEntry;
- } else {
- return [self.remoteDocumentCache entryForKey:documentKey];
- }
-}
-
-- (void)applyToWriteGroup:(FSTWriteGroup *)group {
- [self assertValid];
-
- [self.changes enumerateKeysAndObjectsUsingBlock:^(FSTDocumentKey *key, FSTMaybeDocument *value,
- BOOL *stop) {
- [self.remoteDocumentCache addEntry:value group:group];
- }];
-
- // We should not be used to buffer any more changes.
- self.changes = nil;
-}
-
-- (void)assertValid {
- FSTAssert(self.changes, @"Changes have already been applied.");
-}
-
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/Local/FSTWriteGroup.h b/Firestore/Source/Local/FSTWriteGroup.h
deleted file mode 100644
index 5ea0387..0000000
--- a/Firestore/Source/Local/FSTWriteGroup.h
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright 2017 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/Foundation.h>
-
-#ifdef __cplusplus
-#include <memory>
-
-#include "Firestore/Source/Local/StringView.h"
-
-namespace leveldb {
-class DB;
-class Status;
-}
-
-#endif
-
-NS_ASSUME_NONNULL_BEGIN
-
-@class GPBMessage;
-
-/**
- * A group of writes that will be applied together atomically to persistent storage.
- *
- * This class is usable by both Objective-C and Objective-C++ clients. Objective-C clients are able
- * to create a new group and commit it. Objective-C++ clients can additionally add to the group
- * using deleteKey: and putKey:value:.
- *
- * Note that this is a write "group" even though the underlying LevelDB concept is a write "batch"
- * because Firestore already has a concept of mutation batches, which are user-specified groups of
- * changes. This means that an FSTWriteGroup may contain the application of multiple user-specified
- * mutation batches.
- */
-@interface FSTWriteGroup : NSObject
-
-/**
- * Creates a new, empty write group.
- *
- * @param action A description of the action performed by this group, used for logging.
- */
-+ (instancetype)groupWithAction:(NSString *)action;
-
-- (instancetype)init __attribute__((unavailable("Use a static constructor instead")));
-
-/** The action description assigned to this write group. */
-@property(nonatomic, copy, readonly) NSString *action;
-
-/** Returns YES if the write group has no messages in it. */
-- (BOOL)isEmpty;
-
-#ifdef __cplusplus
-
-/**
- * Marks the given key for deletion.
- *
- * @param key The LevelDB key of the row to delete
- */
-- (void)removeMessageForKey:(Firestore::StringView)key;
-
-/**
- * Sets the row identified by the given key to the value of the given protocol buffer message.
- *
- * @param key The LevelDB Key of the row to set.
- * @param message The protocol buffer message whose serialized contents should be used for the
- * value associated with the key.
- */
-- (void)setMessage:(GPBMessage *)message forKey:(Firestore::StringView)key;
-
-/**
- * Sets the row identified by the given key to the value of the given data bytes.
- *
- * @param key The LevelDB Key of the row to set.
- * @param data The exact value to be associated with the key.
- */
-- (void)setData:(Firestore::StringView)data forKey:(Firestore::StringView)key;
-
-/** Writes the contents to the given LevelDB. */
-- (leveldb::Status)writeToDB:(std::shared_ptr<leveldb::DB>)db;
-
-#endif
-
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/Local/FSTWriteGroup.mm b/Firestore/Source/Local/FSTWriteGroup.mm
deleted file mode 100644
index 6859d53..0000000
--- a/Firestore/Source/Local/FSTWriteGroup.mm
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- * Copyright 2017 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 "Firestore/Source/Local/FSTWriteGroup.h"
-
-#import <Protobuf/GPBProtocolBuffers.h>
-#include <leveldb/db.h>
-#include <leveldb/write_batch.h>
-
-#import "Firestore/Source/Local/FSTLevelDBKey.h"
-#import "Firestore/Source/Util/FSTAssert.h"
-
-#include "Firestore/Port/ordered_code.h"
-
-using Firestore::OrderedCode;
-using Firestore::StringView;
-using leveldb::DB;
-using leveldb::Slice;
-using leveldb::Status;
-using leveldb::WriteBatch;
-using leveldb::WriteOptions;
-
-NS_ASSUME_NONNULL_BEGIN
-
-namespace Firestore {
-
-/**
- * A WriteBatch::Handler implementation that extracts batch details from a leveldb::WriteBatch.
- * This is used for describing a write batch primarily in log messages after a failure.
- */
-class BatchDescription : public WriteBatch::Handler {
- public:
- BatchDescription() : ops_(0), size_(0), message_([NSMutableString string]) {
- }
- virtual ~BatchDescription();
- virtual void Put(const Slice &key, const Slice &value);
- virtual void Delete(const Slice &key);
-
- // Converts the batch to a printable string description of it
- NSString *ToString() const {
- return [NSString
- stringWithFormat:@"%d changes (%lu bytes):%@", ops_, (unsigned long)size_, message_];
- }
-
- // Disallow copies and moves
- BatchDescription(const BatchDescription &) = delete;
- BatchDescription &operator=(const BatchDescription &) = delete;
- BatchDescription(BatchDescription &&) = delete;
- BatchDescription &operator=(BatchDescription &&) = delete;
-
- private:
- int ops_;
- size_t size_;
- NSMutableString *message_;
-};
-
-BatchDescription::~BatchDescription() {
-}
-
-void BatchDescription::Put(const Slice &key, const Slice &value) {
- ops_ += 1;
- size_ += value.size();
-
- [message_ appendFormat:@"\n - Put %@ (%lu bytes)", [FSTLevelDBKey descriptionForKey:key],
- (unsigned long)value.size()];
-}
-
-void BatchDescription::Delete(const Slice &key) {
- ops_ += 1;
-
- [message_ appendFormat:@"\n - Delete %@", [FSTLevelDBKey descriptionForKey:key]];
-}
-
-} // namespace Firestore
-
-@interface FSTWriteGroup ()
-- (instancetype)initWithAction:(NSString *)action NS_DESIGNATED_INITIALIZER;
-@end
-
-@implementation FSTWriteGroup {
- int _changes;
- WriteBatch _contents;
-}
-
-+ (instancetype)groupWithAction:(NSString *)action {
- return [[FSTWriteGroup alloc] initWithAction:action];
-}
-
-- (instancetype)initWithAction:(NSString *)action {
- if (self = [super init]) {
- _action = action;
- }
- return self;
-}
-
-- (NSString *)description {
- Firestore::BatchDescription description;
- Status status = _contents.Iterate(&description);
- if (!status.ok()) {
- FSTFail(@"Iterate over write batch should not fail");
- }
- return [NSString
- stringWithFormat:@"<FSTWriteGroup for %@: %@>", self.action, description.ToString()];
-}
-
-- (void)removeMessageForKey:(StringView)key {
- _contents.Delete(key);
- _changes += 1;
-}
-
-- (void)setMessage:(GPBMessage *)message forKey:(StringView)key {
- NSData *data = [message data];
- Slice value((const char *)data.bytes, data.length);
-
- _contents.Put(key, value);
- _changes += 1;
-}
-
-- (void)setData:(StringView)data forKey:(StringView)key {
- _contents.Put(key, data);
- _changes += 1;
-}
-
-- (leveldb::Status)writeToDB:(std::shared_ptr<leveldb::DB>)db {
- return db->Write(leveldb::WriteOptions(), &_contents);
-}
-
-- (BOOL)isEmpty {
- return _changes == 0;
-}
-
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/Local/FSTWriteGroupTracker.h b/Firestore/Source/Local/FSTWriteGroupTracker.h
deleted file mode 100644
index bd26a46..0000000
--- a/Firestore/Source/Local/FSTWriteGroupTracker.h
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright 2017 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/Foundation.h>
-
-@class FSTWriteGroup;
-
-NS_ASSUME_NONNULL_BEGIN
-
-/**
- * Helper class for FSTPersistence implementations to create WriteGroups and verify internal
- * contracts are maintained:
- * 1. Can't create a group when an uncommitted group exists (no nesting).
- * 2. Can't commit a group that differs from the last created one.
- */
-@interface FSTWriteGroupTracker : NSObject
-
-/** Creates and returns an FSTWriteGroupTracker instance. */
-+ (instancetype)tracker;
-
-/**
- * Verifies there's no active group already and then creates a new group and stores it for later
- * validation with `endGroup`.
- */
-- (FSTWriteGroup *)startGroupWithAction:(NSString *)action;
-
-/** Ends a group previously started with `startGroupWithAction`. */
-- (void)endGroup:(FSTWriteGroup *)group;
-
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/Local/FSTWriteGroupTracker.m b/Firestore/Source/Local/FSTWriteGroupTracker.m
deleted file mode 100644
index 7e3bf60..0000000
--- a/Firestore/Source/Local/FSTWriteGroupTracker.m
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright 2017 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 "Firestore/Source/Local/FSTWriteGroupTracker.h"
-
-#import "Firestore/Source/Local/FSTWriteGroup.h"
-#import "Firestore/Source/Util/FSTAssert.h"
-
-NS_ASSUME_NONNULL_BEGIN
-
-@interface FSTWriteGroupTracker ()
-@property(nonatomic, strong, nullable) FSTWriteGroup *activeGroup;
-@end
-
-@implementation FSTWriteGroupTracker
-
-+ (instancetype)tracker {
- return [[FSTWriteGroupTracker alloc] init];
-}
-
-- (FSTWriteGroup *)startGroupWithAction:(NSString *)action {
- // NOTE: We can relax this to allow nesting if/when we find we need it.
- FSTAssert(!self.activeGroup,
- @"Attempt to create write group (%@) while existing write group (%@) still active.",
- action, self.activeGroup.action);
- self.activeGroup = [FSTWriteGroup groupWithAction:action];
- return self.activeGroup;
-}
-
-- (void)endGroup:(FSTWriteGroup *)group {
- FSTAssert(self.activeGroup == group,
- @"Attempted to end write group (%@) which is different from active group (%@)",
- group.action, self.activeGroup.action);
- self.activeGroup = nil;
-}
-
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/Local/StringView.h b/Firestore/Source/Local/StringView.h
index b81b7b5..85afcaa 100644
--- a/Firestore/Source/Local/StringView.h
+++ b/Firestore/Source/Local/StringView.h
@@ -17,15 +17,13 @@
#ifndef IPHONE_FIRESTORE_SOURCE_LOCAL_STRING_VIEW_H_
#define IPHONE_FIRESTORE_SOURCE_LOCAL_STRING_VIEW_H_
-#ifndef __cplusplus
-#error "StringView is Objective-C++ and can only be included from .mm files"
-#endif
-
#import <Foundation/Foundation.h>
-#include <leveldb/slice.h>
#include <string>
+#include "absl/strings/string_view.h"
+#include "leveldb/slice.h"
+
namespace Firestore {
// A simple wrapper for the character data of any string-like type to which
@@ -46,7 +44,7 @@ class StringView {
// Creates a StringView from an NSString. When StringView is an argument type
// into which an NSString* is passed, the caller should ensure that the
// NSString is retained.
- StringView(NSString *str)
+ StringView(NSString *str) // NOLINT(runtime/explicit)
: data_([str UTF8String]), size_([str lengthOfBytesUsingEncoding:NSUTF8StringEncoding]) {
}
@@ -57,16 +55,24 @@ class StringView {
// Creates a StringView from the given char* pointer but computes the size
// with strlen. This is really only suitable for passing C string literals.
- StringView(const char *data) : data_(data), size_(strlen(data)) {
+ StringView(const char *data) // NOLINT(runtime/explicit)
+ : data_(data), size_(strlen(data)) {
}
// Creates a StringView from the given slice.
- StringView(leveldb::Slice slice) : data_(slice.data()), size_(slice.size()) {
+ StringView(leveldb::Slice slice) // NOLINT(runtime/explicit)
+ : data_(slice.data()), size_(slice.size()) {
+ }
+
+ // Creates a StringView from the absl::string_view.
+ StringView(absl::string_view s) // NOLINT(runtime/explicit)
+ : data_(s.data()), size_(s.size()) {
}
// Creates a StringView from the given std::string. The string must be an
// lvalue for the lifetime requirements to be satisfied.
- StringView(const std::string &str) : data_(str.data()), size_(str.size()) {
+ StringView(const std::string &str) // NOLINT(runtime/explicit)
+ : data_(str.data()), size_(str.size()) {
}
// Converts this StringView to a Slice, which is an equivalent (and more
@@ -76,6 +82,13 @@ class StringView {
return leveldb::Slice(data_, size_);
}
+ // Converts this StringView to a absl::string_view, which is an equivalent (and more
+ // functional) type. The returned string_view has the same lifetime as this
+ // StringView.
+ operator absl::string_view() {
+ return absl::string_view(data_, size_);
+ }
+
private:
const char *data_;
const size_t size_;
diff --git a/Firestore/Source/Model/FSTDatabaseID.h b/Firestore/Source/Model/FSTDatabaseID.h
deleted file mode 100644
index 442e764..0000000
--- a/Firestore/Source/Model/FSTDatabaseID.h
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright 2017 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/Foundation.h>
-
-NS_ASSUME_NONNULL_BEGIN
-
-/** FSTDatabaseID represents a particular database in the datastore. */
-@interface FSTDatabaseID : NSObject
-
-/**
- * Creates and returns a new FSTDatabaseID.
- * @param projectID The project for the database.
- * @param databaseID The database in the project to use.
- * @return A new instance of FSTDatabaseID.
- */
-+ (instancetype)databaseIDWithProject:(NSString *)projectID database:(NSString *)databaseID;
-
-/** The project. */
-@property(nonatomic, copy, readonly) NSString *projectID;
-
-/** The database. */
-@property(nonatomic, copy, readonly) NSString *databaseID;
-
-/** Whether this is the default database of the project. */
-- (BOOL)isDefaultDatabase;
-
-- (NSComparisonResult)compare:(FSTDatabaseID *)other;
-- (BOOL)isEqualToDatabaseId:(FSTDatabaseID *)databaseID;
-
-@end
-
-extern NSString *const kDefaultDatabaseID;
-
-NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/Model/FSTDatabaseID.m b/Firestore/Source/Model/FSTDatabaseID.m
deleted file mode 100644
index 4d0448a..0000000
--- a/Firestore/Source/Model/FSTDatabaseID.m
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright 2017 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 "Firestore/Source/Model/FSTDatabaseID.h"
-
-#import "Firestore/Source/Util/FSTAssert.h"
-
-NS_ASSUME_NONNULL_BEGIN
-
-/** The default name for "unset" database ID in resource names. */
-NSString *const kDefaultDatabaseID = @"(default)";
-
-#pragma mark - FSTDatabaseID
-
-@implementation FSTDatabaseID
-
-+ (instancetype)databaseIDWithProject:(NSString *)projectID database:(NSString *)databaseID {
- return [[FSTDatabaseID alloc] initWithProject:projectID database:databaseID];
-}
-
-/**
- * Designated initializer.
- *
- * @param projectID The project for the database.
- * @param databaseID The database in the datastore.
- */
-- (instancetype)initWithProject:(NSString *)projectID database:(NSString *)databaseID {
- if (self = [super init]) {
- FSTAssert(databaseID, @"databaseID cannot be nil");
- _projectID = [projectID copy];
- _databaseID = [databaseID copy];
- }
- return self;
-}
-
-- (BOOL)isEqual:(id)other {
- if (other == self) return YES;
- if (!other || ![[other class] isEqual:[self class]]) return NO;
-
- return [self isEqualToDatabaseId:other];
-}
-
-- (NSUInteger)hash {
- NSUInteger hash = [self.projectID hash];
- hash = hash * 31u + [self.databaseID hash];
- return hash;
-}
-
-- (NSString *)description {
- return [NSString
- stringWithFormat:@"<FSTDatabaseID: project:%@ database:%@>", self.projectID, self.databaseID];
-}
-
-- (NSComparisonResult)compare:(FSTDatabaseID *)other {
- NSComparisonResult cmp = [self.projectID compare:other.projectID];
- return cmp == NSOrderedSame ? [self.databaseID compare:other.databaseID] : cmp;
-}
-
-- (BOOL)isDefaultDatabase {
- return [self.databaseID isEqualToString:kDefaultDatabaseID];
-}
-
-- (BOOL)isEqualToDatabaseId:(FSTDatabaseID *)databaseID {
- if (self == databaseID) return YES;
- if (databaseID == nil) return NO;
- if (self.projectID != databaseID.projectID &&
- ![self.projectID isEqualToString:databaseID.projectID])
- return NO;
- if (self.databaseID != databaseID.databaseID &&
- ![self.databaseID isEqualToString:databaseID.databaseID])
- return NO;
- return YES;
-}
-
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/Model/FSTDocument.h b/Firestore/Source/Model/FSTDocument.h
index 100f553..47e4d28 100644
--- a/Firestore/Source/Model/FSTDocument.h
+++ b/Firestore/Source/Model/FSTDocument.h
@@ -16,8 +16,9 @@
#import <Foundation/Foundation.h>
-@class FSTDocumentKey;
-@class FSTFieldPath;
+#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+#include "Firestore/core/src/firebase/firestore/model/field_path.h"
+
@class FSTFieldValue;
@class FSTObjectValue;
@class FSTSnapshotVersion;
@@ -30,18 +31,18 @@ NS_ASSUME_NONNULL_BEGIN
*/
@interface FSTMaybeDocument : NSObject <NSCopying>
- (id)init __attribute__((unavailable("Abstract base class")));
+- (const firebase::firestore::model::DocumentKey &)key;
-@property(nonatomic, strong, readonly) FSTDocumentKey *key;
@property(nonatomic, readonly) FSTSnapshotVersion *version;
@end
@interface FSTDocument : FSTMaybeDocument
+ (instancetype)documentWithData:(FSTObjectValue *)data
- key:(FSTDocumentKey *)key
+ key:(firebase::firestore::model::DocumentKey)key
version:(FSTSnapshotVersion *)version
hasLocalMutations:(BOOL)mutations;
-- (nullable FSTFieldValue *)fieldForPath:(FSTFieldPath *)path;
+- (nullable FSTFieldValue *)fieldForPath:(const firebase::firestore::model::FieldPath &)path;
@property(nonatomic, strong, readonly) FSTObjectValue *data;
@property(nonatomic, readonly, getter=hasLocalMutations) BOOL localMutations;
@@ -49,7 +50,8 @@ NS_ASSUME_NONNULL_BEGIN
@end
@interface FSTDeletedDocument : FSTMaybeDocument
-+ (instancetype)documentWithKey:(FSTDocumentKey *)key version:(FSTSnapshotVersion *)version;
++ (instancetype)documentWithKey:(firebase::firestore::model::DocumentKey)key
+ version:(FSTSnapshotVersion *)version;
@end
/** An NSComparator suitable for comparing docs using only their keys. */
diff --git a/Firestore/Source/Model/FSTDocument.m b/Firestore/Source/Model/FSTDocument.mm
index bf416e7..9898c2a 100644
--- a/Firestore/Source/Model/FSTDocument.m
+++ b/Firestore/Source/Model/FSTDocument.mm
@@ -16,28 +16,38 @@
#import "Firestore/Source/Model/FSTDocument.h"
+#include <utility>
+
#import "Firestore/Source/Core/FSTSnapshotVersion.h"
-#import "Firestore/Source/Model/FSTDocumentKey.h"
#import "Firestore/Source/Model/FSTFieldValue.h"
-#import "Firestore/Source/Model/FSTPath.h"
#import "Firestore/Source/Util/FSTAssert.h"
+#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+#include "Firestore/core/src/firebase/firestore/model/field_path.h"
+#include "Firestore/core/src/firebase/firestore/util/string_apple.h"
+
+namespace util = firebase::firestore::util;
+using firebase::firestore::model::DocumentKey;
+using firebase::firestore::model::FieldPath;
+
NS_ASSUME_NONNULL_BEGIN
@interface FSTMaybeDocument ()
-- (instancetype)initWithKey:(FSTDocumentKey *)key
+- (instancetype)initWithKey:(DocumentKey)key
version:(FSTSnapshotVersion *)version NS_DESIGNATED_INITIALIZER;
@end
-@implementation FSTMaybeDocument
+@implementation FSTMaybeDocument {
+ DocumentKey _key;
+}
-- (instancetype)initWithKey:(FSTDocumentKey *)key version:(FSTSnapshotVersion *)version {
+- (instancetype)initWithKey:(DocumentKey)key version:(FSTSnapshotVersion *)version {
FSTAssert(!!version, @"Version must not be nil.");
self = [super init];
if (self) {
- _key = key;
+ _key = std::move(key);
_version = version;
}
return self;
@@ -48,23 +58,29 @@ NS_ASSUME_NONNULL_BEGIN
return self;
}
+- (const DocumentKey &)key {
+ return _key;
+}
+
@end
@implementation FSTDocument
+ (instancetype)documentWithData:(FSTObjectValue *)data
- key:(FSTDocumentKey *)key
+ key:(DocumentKey)key
version:(FSTSnapshotVersion *)version
hasLocalMutations:(BOOL)mutations {
- return
- [[FSTDocument alloc] initWithData:data key:key version:version hasLocalMutations:mutations];
+ return [[FSTDocument alloc] initWithData:data
+ key:std::move(key)
+ version:version
+ hasLocalMutations:mutations];
}
- (instancetype)initWithData:(FSTObjectValue *)data
- key:(FSTDocumentKey *)key
+ key:(DocumentKey)key
version:(FSTSnapshotVersion *)version
hasLocalMutations:(BOOL)mutations {
- self = [super initWithKey:key version:version];
+ self = [super initWithKey:std::move(key) version:version];
if (self) {
_data = data;
_localMutations = mutations;
@@ -94,12 +110,12 @@ NS_ASSUME_NONNULL_BEGIN
}
- (NSString *)description {
- return [NSString stringWithFormat:@"<FSTDocument: key:%@ version:%@ localMutations:%@ data:%@>",
- self.key.path, self.version,
+ return [NSString stringWithFormat:@"<FSTDocument: key:%s version:%@ localMutations:%@ data:%@>",
+ self.key.ToString().c_str(), self.version,
self.localMutations ? @"YES" : @"NO", self.data];
}
-- (nullable FSTFieldValue *)fieldForPath:(FSTFieldPath *)path {
+- (nullable FSTFieldValue *)fieldForPath:(const FieldPath &)path {
return [_data valueForPath:path];
}
@@ -107,8 +123,8 @@ NS_ASSUME_NONNULL_BEGIN
@implementation FSTDeletedDocument
-+ (instancetype)documentWithKey:(FSTDocumentKey *)key version:(FSTSnapshotVersion *)version {
- return [[FSTDeletedDocument alloc] initWithKey:key version:version];
++ (instancetype)documentWithKey:(DocumentKey)key version:(FSTSnapshotVersion *)version {
+ return [[FSTDeletedDocument alloc] initWithKey:std::move(key) version:version];
}
- (BOOL)isEqual:(id)other {
diff --git a/Firestore/Source/Model/FSTDocumentDictionary.m b/Firestore/Source/Model/FSTDocumentDictionary.mm
index 362af54..362af54 100644
--- a/Firestore/Source/Model/FSTDocumentDictionary.m
+++ b/Firestore/Source/Model/FSTDocumentDictionary.mm
diff --git a/Firestore/Source/Model/FSTDocumentKey.h b/Firestore/Source/Model/FSTDocumentKey.h
index 2af1c9a..a403117 100644
--- a/Firestore/Source/Model/FSTDocumentKey.h
+++ b/Firestore/Source/Model/FSTDocumentKey.h
@@ -16,7 +16,10 @@
#import <Foundation/Foundation.h>
-@class FSTResourcePath;
+#include <initializer_list>
+#include <string>
+
+#include "Firestore/core/src/firebase/firestore/model/resource_path.h"
NS_ASSUME_NONNULL_BEGIN
@@ -29,16 +32,14 @@ NS_ASSUME_NONNULL_BEGIN
* @param path The path to the document.
* @return A new instance of FSTDocumentKey.
*/
-+ (instancetype)keyWithPath:(FSTResourcePath *)path;
-
++ (instancetype)keyWithPath:(firebase::firestore::model::ResourcePath)path;
/**
* Creates and returns a new document key with a path with the given segments.
*
* @param segments The segments of the path to the document.
* @return A new instance of FSTDocumentKey.
*/
-+ (instancetype)keyWithSegments:(NSArray<NSString *> *)segments;
-
++ (instancetype)keyWithSegments:(std::initializer_list<std::string>)segments;
/**
* Creates and returns a new document key from the given resource path string.
*
@@ -48,13 +49,12 @@ NS_ASSUME_NONNULL_BEGIN
+ (instancetype)keyWithPathString:(NSString *)resourcePath;
/** Returns true iff the given path is a path to a document. */
-+ (BOOL)isDocumentKey:(FSTResourcePath *)path;
-
++ (BOOL)isDocumentKey:(const firebase::firestore::model::ResourcePath &)path;
- (BOOL)isEqualToKey:(FSTDocumentKey *)other;
- (NSComparisonResult)compare:(FSTDocumentKey *)other;
/** The path to the document. */
-@property(strong, nonatomic, readonly) FSTResourcePath *path;
+- (const firebase::firestore::model::ResourcePath &)path;
@end
diff --git a/Firestore/Source/Model/FSTDocumentKey.m b/Firestore/Source/Model/FSTDocumentKey.mm
index a382a55..679d7a6 100644
--- a/Firestore/Source/Model/FSTDocumentKey.m
+++ b/Firestore/Source/Model/FSTDocumentKey.mm
@@ -16,35 +16,44 @@
#import "Firestore/Source/Model/FSTDocumentKey.h"
+#include <string>
+#include <utility>
+
#import "Firestore/Source/Core/FSTFirestoreClient.h"
-#import "Firestore/Source/Model/FSTPath.h"
#import "Firestore/Source/Util/FSTAssert.h"
+#include "Firestore/core/src/firebase/firestore/model/resource_path.h"
+#include "Firestore/core/src/firebase/firestore/util/string_apple.h"
+
+namespace util = firebase::firestore::util;
+using firebase::firestore::model::ResourcePath;
+
NS_ASSUME_NONNULL_BEGIN
-@interface FSTDocumentKey ()
-/** The path to the document. */
-@property(strong, nonatomic, readwrite) FSTResourcePath *path;
+@interface FSTDocumentKey () {
+ /** The path to the document. */
+ ResourcePath _path;
+}
@end
@implementation FSTDocumentKey
-+ (instancetype)keyWithPath:(FSTResourcePath *)path {
- return [[FSTDocumentKey alloc] initWithPath:path];
++ (instancetype)keyWithPath:(ResourcePath)path {
+ return [[FSTDocumentKey alloc] initWithPath:std::move(path)];
}
-+ (instancetype)keyWithSegments:(NSArray<NSString *> *)segments {
- return [FSTDocumentKey keyWithPath:[FSTResourcePath pathWithSegments:segments]];
++ (instancetype)keyWithSegments:(std::initializer_list<std::string>)segments {
+ return [FSTDocumentKey keyWithPath:ResourcePath(segments)];
}
+ (instancetype)keyWithPathString:(NSString *)resourcePath {
- NSArray<NSString *> *segments = [resourcePath componentsSeparatedByString:@"/"];
- return [FSTDocumentKey keyWithSegments:segments];
+ return [FSTDocumentKey keyWithPath:ResourcePath::FromString(util::MakeStringView(resourcePath))];
}
/** Designated initializer. */
-- (instancetype)initWithPath:(FSTResourcePath *)path {
- FSTAssert([FSTDocumentKey isDocumentKey:path], @"invalid document key path: %@", path);
+- (instancetype)initWithPath:(ResourcePath)path {
+ FSTAssert([FSTDocumentKey isDocumentKey:path], @"invalid document key path: %s",
+ path.CanonicalString().c_str());
if (self = [super init]) {
_path = path;
@@ -63,11 +72,11 @@ NS_ASSUME_NONNULL_BEGIN
}
- (NSUInteger)hash {
- return self.path.hash;
+ return _path.Hash();
}
- (NSString *)description {
- return [NSString stringWithFormat:@"<FSTDocumentKey: %@>", self.path];
+ return [NSString stringWithFormat:@"<FSTDocumentKey: %s>", _path.CanonicalString().c_str()];
}
/** Implements NSCopying without actually copying because FSTDocumentKeys are immutable. */
@@ -89,15 +98,25 @@ NS_ASSUME_NONNULL_BEGIN
};
}
-+ (BOOL)isDocumentKey:(FSTResourcePath *)path {
- return path.length % 2 == 0;
++ (BOOL)isDocumentKey:(const ResourcePath &)path {
+ return path.size() % 2 == 0;
+}
+
+- (const ResourcePath &)path {
+ return _path;
}
@end
const NSComparator FSTDocumentKeyComparator =
^NSComparisonResult(FSTDocumentKey *key1, FSTDocumentKey *key2) {
- return [key1.path compare:key2.path];
+ if (key1.path < key2.path) {
+ return NSOrderedAscending;
+ } else if (key1.path > key2.path) {
+ return NSOrderedDescending;
+ } else {
+ return NSOrderedSame;
+ }
};
NSString *const kDocumentKeyPath = @"__name__";
diff --git a/Firestore/Source/Model/FSTDocumentKeySet.m b/Firestore/Source/Model/FSTDocumentKeySet.mm
index f07b785..f07b785 100644
--- a/Firestore/Source/Model/FSTDocumentKeySet.m
+++ b/Firestore/Source/Model/FSTDocumentKeySet.mm
diff --git a/Firestore/Source/Model/FSTDocumentSet.h b/Firestore/Source/Model/FSTDocumentSet.h
index a7f8c4a..b5521e7 100644
--- a/Firestore/Source/Model/FSTDocumentSet.h
+++ b/Firestore/Source/Model/FSTDocumentSet.h
@@ -18,8 +18,9 @@
#import "Firestore/Source/Model/FSTDocumentDictionary.h"
+#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+
@class FSTDocument;
-@class FSTDocumentKey;
NS_ASSUME_NONNULL_BEGIN
@@ -41,10 +42,10 @@ NS_ASSUME_NONNULL_BEGIN
- (BOOL)isEmpty;
/** Returns YES if this set contains a document with the given key. */
-- (BOOL)containsKey:(FSTDocumentKey *)key;
+- (BOOL)containsKey:(const firebase::firestore::model::DocumentKey &)key;
/** Returns the document from this set with the given key if it exists or nil if it doesn't. */
-- (FSTDocument *_Nullable)documentForKey:(FSTDocumentKey *)key;
+- (FSTDocument *_Nullable)documentForKey:(const firebase::firestore::model::DocumentKey &)key;
/**
* Returns the first document in the set according to its built in ordering, or nil if the set
@@ -59,20 +60,10 @@ NS_ASSUME_NONNULL_BEGIN
- (FSTDocument *_Nullable)lastDocument;
/**
- * Returns the document previous to the document associated with the given key in the set according
- * to its built in ordering. Returns nil if the document associated with the given key is the
- * first document.
- *
- * @param key A key that must be present in the DocumentSet.
- * @throws NSInvalidArgumentException if key is not present.
- */
-- (FSTDocument *_Nullable)predecessorDocumentForKey:(FSTDocumentKey *)key;
-
-/**
* Returns the index of the document with the provided key in the document set. Returns NSNotFound
* if the key is not present.
*/
-- (NSUInteger)indexOfKey:(FSTDocumentKey *)key;
+- (NSUInteger)indexOfKey:(const firebase::firestore::model::DocumentKey &)key;
- (NSEnumerator<FSTDocument *> *)documentEnumerator;
@@ -89,7 +80,7 @@ NS_ASSUME_NONNULL_BEGIN
- (instancetype)documentSetByAddingDocument:(FSTDocument *_Nullable)document;
/** Returns a new FSTDocumentSet that excludes any document associated with the given key. */
-- (instancetype)documentSetByRemovingKey:(FSTDocumentKey *)key;
+- (instancetype)documentSetByRemovingKey:(const firebase::firestore::model::DocumentKey &)key;
@end
NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/Model/FSTDocumentSet.m b/Firestore/Source/Model/FSTDocumentSet.mm
index c4c0f49..2f0b42b 100644
--- a/Firestore/Source/Model/FSTDocumentSet.m
+++ b/Firestore/Source/Model/FSTDocumentSet.mm
@@ -20,6 +20,10 @@
#import "Firestore/Source/Model/FSTDocumentKey.h"
#import "Firestore/third_party/Immutable/FSTImmutableSortedSet.h"
+#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+
+using firebase::firestore::model::DocumentKey;
+
NS_ASSUME_NONNULL_BEGIN
/**
@@ -119,12 +123,12 @@ typedef FSTImmutableSortedSet<FSTDocument *> SetType;
return [self.index isEmpty];
}
-- (BOOL)containsKey:(FSTDocumentKey *)key {
- return [self.index objectForKey:key] != nil;
+- (BOOL)containsKey:(const DocumentKey &)key {
+ return [self.index objectForKey:(FSTDocumentKey *)key] != nil;
}
-- (FSTDocument *_Nullable)documentForKey:(FSTDocumentKey *)key {
- return [self.index objectForKey:key];
+- (FSTDocument *_Nullable)documentForKey:(const DocumentKey &)key {
+ return [self.index objectForKey:(FSTDocumentKey *)key];
}
- (FSTDocument *_Nullable)firstDocument {
@@ -135,18 +139,8 @@ typedef FSTImmutableSortedSet<FSTDocument *> SetType;
return [self.sortedSet lastObject];
}
-- (FSTDocument *_Nullable)predecessorDocumentForKey:(FSTDocumentKey *)key {
- FSTDocument *doc = [self.index objectForKey:key];
- if (!doc) {
- @throw [NSException exceptionWithName:NSInvalidArgumentException
- reason:[NSString stringWithFormat:@"Key %@ does not exist", key]
- userInfo:nil];
- }
- return [self.sortedSet predecessorObject:doc];
-}
-
-- (NSUInteger)indexOfKey:(FSTDocumentKey *)key {
- FSTDocument *doc = [self.index objectForKey:key];
+- (NSUInteger)indexOfKey:(const DocumentKey &)key {
+ FSTDocument *doc = [self.index objectForKey:(FSTDocumentKey *)key];
return doc ? [self.sortedSet indexOfObject:doc] : NSNotFound;
}
@@ -181,8 +175,8 @@ typedef FSTImmutableSortedSet<FSTDocument *> SetType;
return [[FSTDocumentSet alloc] initWithIndex:index set:set];
}
-- (instancetype)documentSetByRemovingKey:(FSTDocumentKey *)key {
- FSTDocument *doc = [self.index objectForKey:key];
+- (instancetype)documentSetByRemovingKey:(const DocumentKey &)key {
+ FSTDocument *doc = [self.index objectForKey:(FSTDocumentKey *)key];
if (!doc) {
return self;
}
diff --git a/Firestore/Source/Model/FSTDocumentVersionDictionary.m b/Firestore/Source/Model/FSTDocumentVersionDictionary.mm
index 870e082..870e082 100644
--- a/Firestore/Source/Model/FSTDocumentVersionDictionary.m
+++ b/Firestore/Source/Model/FSTDocumentVersionDictionary.mm
diff --git a/Firestore/Source/Model/FSTFieldValue.h b/Firestore/Source/Model/FSTFieldValue.h
index 6de9793..6914f4d 100644
--- a/Firestore/Source/Model/FSTFieldValue.h
+++ b/Firestore/Source/Model/FSTFieldValue.h
@@ -18,11 +18,14 @@
#import "Firestore/third_party/Immutable/FSTImmutableSortedDictionary.h"
-@class FSTDatabaseID;
+#include "Firestore/core/src/firebase/firestore/model/database_id.h"
+#include "Firestore/core/src/firebase/firestore/model/field_path.h"
+
@class FSTDocumentKey;
-@class FSTFieldPath;
-@class FSTTimestamp;
+@class FIRTimestamp;
+@class FSTFieldValueOptions;
@class FIRGeoPoint;
+@class FIRSnapshotOptions;
NS_ASSUME_NONNULL_BEGIN
@@ -40,6 +43,36 @@ typedef NS_ENUM(NSInteger, FSTTypeOrder) {
FSTTypeOrderObject,
};
+/** Defines the return value for pending server timestamps. */
+typedef NS_ENUM(NSInteger, FSTServerTimestampBehavior) {
+ FSTServerTimestampBehaviorNone,
+ FSTServerTimestampBehaviorEstimate,
+ FSTServerTimestampBehaviorPrevious
+};
+
+/** Holds properties that define field value deserialization options. */
+@interface FSTFieldValueOptions : NSObject
+
+@property(nonatomic, readonly, assign) FSTServerTimestampBehavior serverTimestampBehavior;
+
+@property(nonatomic) BOOL timestampsInSnapshotsEnabled;
+
+- (instancetype)init NS_UNAVAILABLE;
+
+/**
+ * Creates an FSTFieldValueOptions instance that specifies deserialization behavior for pending
+ * server timestamps.
+ */
+- (instancetype)initWithServerTimestampBehavior:(FSTServerTimestampBehavior)serverTimestampBehavior
+ timestampsInSnapshotsEnabled:(BOOL)timestampsInSnapshotsEnabled
+ NS_DESIGNATED_INITIALIZER;
+
+/** Creates an FSTFieldValueOptions instance from FIRSnapshotOptions. */
++ (instancetype)optionsForSnapshotOptions:(FIRSnapshotOptions *)value
+ timestampsInSnapshotsEnabled:(BOOL)timestampsInSnapshotsEnabled;
+
+@end
+
/**
* Abstract base class representing an immutable data value as stored in Firestore. FSTFieldValue
* represents all the different kinds of values that can be stored in fields in a document.
@@ -58,7 +91,7 @@ typedef NS_ENUM(NSInteger, FSTTypeOrder) {
* - Array
* - Object
*/
-@interface FSTFieldValue : NSObject
+@interface FSTFieldValue <__covariant T> : NSObject
/** Returns the FSTTypeOrder for this value. */
- (FSTTypeOrder)typeOrder;
@@ -69,7 +102,15 @@ typedef NS_ENUM(NSInteger, FSTTypeOrder) {
* TODO(mikelehen): This conversion should probably happen at the API level and right now `value` is
* used inappropriately in the serializer implementation, etc. We need to do some reworking.
*/
-- (id)value;
+- (T)value;
+
+/**
+ * Converts an FSTFieldValue into the value that users will see in document snapshots.
+ *
+ * Options can be provided to configure the deserialization of some field values (such as server
+ * timestamps).
+ */
+- (T)valueWithOptions:(FSTFieldValueOptions *)options;
/** Compares against another FSTFieldValue. */
- (NSComparisonResult)compare:(FSTFieldValue *)other;
@@ -79,26 +120,24 @@ typedef NS_ENUM(NSInteger, FSTTypeOrder) {
/**
* A null value stored in Firestore. The |value| of a FSTNullValue is [NSNull null].
*/
-@interface FSTNullValue : FSTFieldValue
+@interface FSTNullValue : FSTFieldValue <NSNull *>
+ (instancetype)nullValue;
-- (id)value;
@end
/**
* A boolean value stored in Firestore.
*/
-@interface FSTBooleanValue : FSTFieldValue
+@interface FSTBooleanValue : FSTFieldValue <NSNumber *>
+ (instancetype)trueValue;
+ (instancetype)falseValue;
+ (instancetype)booleanValue:(BOOL)value;
-- (NSNumber *)value;
@end
/**
* Base class inherited from by FSTIntegerValue and FSTDoubleValue. It implements proper number
* comparisons between the two types.
*/
-@interface FSTNumberValue : FSTFieldValue
+@interface FSTNumberValue : FSTFieldValue <NSNumber *>
@end
/**
@@ -106,7 +145,6 @@ typedef NS_ENUM(NSInteger, FSTTypeOrder) {
*/
@interface FSTIntegerValue : FSTNumberValue
+ (instancetype)integerValue:(int64_t)value;
-- (NSNumber *)value;
- (int64_t)internalValue;
@end
@@ -116,25 +154,21 @@ typedef NS_ENUM(NSInteger, FSTTypeOrder) {
@interface FSTDoubleValue : FSTNumberValue
+ (instancetype)doubleValue:(double)value;
+ (instancetype)nanValue;
-- (NSNumber *)value;
- (double)internalValue;
@end
/**
* A string stored in Firestore.
*/
-@interface FSTStringValue : FSTFieldValue
+@interface FSTStringValue : FSTFieldValue <NSString *>
+ (instancetype)stringValue:(NSString *)value;
-- (NSString *)value;
@end
/**
* A timestamp value stored in Firestore.
*/
-@interface FSTTimestampValue : FSTFieldValue
-+ (instancetype)timestampValue:(FSTTimestamp *)value;
-- (NSDate *)value;
-- (FSTTimestamp *)internalValue;
+@interface FSTTimestampValue : FSTFieldValue <FIRTimestamp *>
++ (instancetype)timestampValue:(FIRTimestamp *)value;
@end
/**
@@ -144,46 +178,53 @@ typedef NS_ENUM(NSInteger, FSTTypeOrder) {
* - FSTServerTimestampValue instances are created as the result of applying an FSTTransformMutation
* (see [FSTTransformMutation applyTo]). They can only exist in the local view of a document.
* Therefore they do not need to be parsed or serialized.
- * - When evaluated locally (e.g. via FSTDocumentSnapshot data), they evaluate to NSNull (at least
- * for now, see b/62064202).
+ * - When evaluated locally (e.g. via FSTDocumentSnapshot data), they by default evaluate to NSNull.
+ * This behavior can be configured by passing custom FSTFieldValueOptions to `valueWithOptions:`.
* - They sort after all FSTTimestampValues. With respect to other FSTServerTimestampValues, they
* sort by their localWriteTime.
*/
-@interface FSTServerTimestampValue : FSTFieldValue
-+ (instancetype)serverTimestampValueWithLocalWriteTime:(FSTTimestamp *)localWriteTime;
-- (NSNull *)value;
-@property(nonatomic, strong, readonly) FSTTimestamp *localWriteTime;
+@interface FSTServerTimestampValue : FSTFieldValue <id>
++ (instancetype)serverTimestampValueWithLocalWriteTime:(FIRTimestamp *)localWriteTime
+ previousValue:(nullable FSTFieldValue *)previousValue;
+
+@property(nonatomic, strong, readonly) FIRTimestamp *localWriteTime;
+@property(nonatomic, strong, readonly, nullable) FSTFieldValue *previousValue;
+
@end
/**
* A geo point value stored in Firestore.
*/
-@interface FSTGeoPointValue : FSTFieldValue
+@interface FSTGeoPointValue : FSTFieldValue <FIRGeoPoint *>
+ (instancetype)geoPointValue:(FIRGeoPoint *)value;
-- (FIRGeoPoint *)value;
@end
/**
* A blob value stored in Firestore.
*/
-@interface FSTBlobValue : FSTFieldValue
+@interface FSTBlobValue : FSTFieldValue <NSData *>
+ (instancetype)blobValue:(NSData *)value;
-- (NSData *)value;
@end
/**
* A reference value stored in Firestore.
*/
-@interface FSTReferenceValue : FSTFieldValue
-+ (instancetype)referenceValue:(FSTDocumentKey *)value databaseID:(FSTDatabaseID *)databaseID;
-- (FSTDocumentKey *)value;
-@property(nonatomic, strong, readonly) FSTDatabaseID *databaseID;
+@interface FSTReferenceValue : FSTFieldValue <FSTDocumentKey *>
++ (instancetype)referenceValue:(FSTDocumentKey *)value
+ databaseID:(const firebase::firestore::model::DatabaseId *)databaseID;
+// Does not own this DatabaseId.
+@property(nonatomic, assign, readonly) const firebase::firestore::model::DatabaseId *databaseID;
@end
/**
* A structured object value stored in Firestore.
*/
-@interface FSTObjectValue : FSTFieldValue
+// clang-format off
+@interface FSTObjectValue : FSTFieldValue < NSDictionary<NSString *, id> * >
+
+- (instancetype)init NS_UNAVAILABLE;
+// clang-format on
+
/** Returns an empty FSTObjectValue. */
+ (instancetype)objectValue;
@@ -198,43 +239,42 @@ typedef NS_ENUM(NSInteger, FSTTypeOrder) {
- (instancetype)initWithImmutableDictionary:
(FSTImmutableSortedDictionary<NSString *, FSTFieldValue *> *)value NS_DESIGNATED_INITIALIZER;
-- (instancetype)init NS_UNAVAILABLE;
-
-- (NSDictionary<NSString *, id> *)value;
- (FSTImmutableSortedDictionary<NSString *, FSTFieldValue *> *)internalValue;
/** Returns the value at the given path if it exists. Returns nil otherwise. */
-- (nullable FSTFieldValue *)valueForPath:(FSTFieldPath *)fieldPath;
+- (nullable FSTFieldValue *)valueForPath:(const firebase::firestore::model::FieldPath &)fieldPath;
/**
* Returns a new object where the field at the named path has its value set to the given value.
* This object remains unmodified.
*/
-- (FSTObjectValue *)objectBySettingValue:(FSTFieldValue *)value forPath:(FSTFieldPath *)fieldPath;
+- (FSTObjectValue *)objectBySettingValue:(FSTFieldValue *)value
+ forPath:(const firebase::firestore::model::FieldPath &)fieldPath;
/**
* Returns a new object where the field at the named path has been removed. If any segment of the
* path does not exist within this object's structure, no change is performed.
*/
-- (FSTObjectValue *)objectByDeletingPath:(FSTFieldPath *)fieldPath;
+- (FSTObjectValue *)objectByDeletingPath:(const firebase::firestore::model::FieldPath &)fieldPath;
@end
/**
* An array value stored in Firestore.
*/
-@interface FSTArrayValue : FSTFieldValue
+// clang-format off
+@interface FSTArrayValue : FSTFieldValue < NSArray <id> * >
+
+- (instancetype)init NS_UNAVAILABLE;
+// clang-format on
/**
* Initializes this instance with the given array of wrapped values.
*
* @param value An immutable array of FSTFieldValue objects. Caller is responsible for copying the
- * value or releasing all references.
+ * value or releasing all references.
*/
- (instancetype)initWithValueNoCopy:(NSArray<FSTFieldValue *> *)value NS_DESIGNATED_INITIALIZER;
-- (instancetype)init NS_UNAVAILABLE;
-
-- (NSArray<id> *)value;
- (NSArray<FSTFieldValue *> *)internalValue;
@end
diff --git a/Firestore/Source/Model/FSTFieldValue.m b/Firestore/Source/Model/FSTFieldValue.mm
index 95ad306..0d7c649 100644
--- a/Firestore/Source/Model/FSTFieldValue.m
+++ b/Firestore/Source/Model/FSTFieldValue.mm
@@ -16,17 +16,71 @@
#import "Firestore/Source/Model/FSTFieldValue.h"
+#import "FIRTimestamp.h"
+
#import "Firestore/Source/API/FIRGeoPoint+Internal.h"
-#import "Firestore/Source/Core/FSTTimestamp.h"
-#import "Firestore/Source/Model/FSTDatabaseID.h"
+#import "Firestore/Source/API/FIRSnapshotOptions+Internal.h"
#import "Firestore/Source/Model/FSTDocumentKey.h"
-#import "Firestore/Source/Model/FSTPath.h"
#import "Firestore/Source/Util/FSTAssert.h"
#import "Firestore/Source/Util/FSTClasses.h"
-#import "Firestore/Source/Util/FSTComparison.h"
+
+#include "Firestore/core/src/firebase/firestore/model/database_id.h"
+#include "Firestore/core/src/firebase/firestore/model/field_path.h"
+#include "Firestore/core/src/firebase/firestore/util/comparison.h"
+#include "Firestore/core/src/firebase/firestore/util/string_apple.h"
+
+namespace util = firebase::firestore::util;
+using firebase::firestore::model::DatabaseId;
+using firebase::firestore::model::FieldPath;
+using firebase::firestore::util::Comparator;
+using firebase::firestore::util::CompareMixedNumber;
+using firebase::firestore::util::DoubleBitwiseEquals;
+using firebase::firestore::util::DoubleBitwiseHash;
+using firebase::firestore::util::MakeStringView;
+using firebase::firestore::util::ReverseOrder;
+using firebase::firestore::util::WrapCompare;
NS_ASSUME_NONNULL_BEGIN
+#pragma mark - FSTFieldValueOptions
+
+@implementation FSTFieldValueOptions
+
++ (instancetype)optionsForSnapshotOptions:(FIRSnapshotOptions *)options
+ timestampsInSnapshotsEnabled:(BOOL)timestampsInSnapshotsEnabled {
+ FSTServerTimestampBehavior convertedServerTimestampBehavior = FSTServerTimestampBehaviorNone;
+ switch (options.serverTimestampBehavior) {
+ case FIRServerTimestampBehaviorNone:
+ convertedServerTimestampBehavior = FSTServerTimestampBehaviorNone;
+ break;
+ case FIRServerTimestampBehaviorEstimate:
+ convertedServerTimestampBehavior = FSTServerTimestampBehaviorEstimate;
+ break;
+ case FIRServerTimestampBehaviorPrevious:
+ convertedServerTimestampBehavior = FSTServerTimestampBehaviorPrevious;
+ break;
+ default:
+ FSTFail(@"Unexpected server timestamp option: %ld", (long)options.serverTimestampBehavior);
+ }
+
+ return
+ [[FSTFieldValueOptions alloc] initWithServerTimestampBehavior:convertedServerTimestampBehavior
+ timestampsInSnapshotsEnabled:timestampsInSnapshotsEnabled];
+}
+
+- (instancetype)initWithServerTimestampBehavior:(FSTServerTimestampBehavior)serverTimestampBehavior
+ timestampsInSnapshotsEnabled:(BOOL)timestampsInSnapshotsEnabled {
+ self = [super init];
+
+ if (self) {
+ _serverTimestampBehavior = serverTimestampBehavior;
+ _timestampsInSnapshotsEnabled = timestampsInSnapshotsEnabled;
+ }
+ return self;
+}
+
+@end
+
#pragma mark - FSTFieldValue
@interface FSTFieldValue ()
@@ -43,6 +97,10 @@ NS_ASSUME_NONNULL_BEGIN
@throw FSTAbstractMethodException(); // NOLINT
}
+- (id)valueWithOptions:(FSTFieldValueOptions *)options {
+ return [self value];
+}
+
- (BOOL)isEqual:(id)other {
@throw FSTAbstractMethodException(); // NOLINT
}
@@ -170,7 +228,7 @@ NS_ASSUME_NONNULL_BEGIN
- (NSComparisonResult)compare:(FSTFieldValue *)other {
if ([other isKindOfClass:[FSTBooleanValue class]]) {
- return FSTCompareBools(self.internalValue, ((FSTBooleanValue *)other).internalValue);
+ return WrapCompare<bool>(self.internalValue, ((FSTBooleanValue *)other).internalValue);
} else {
return [self defaultCompare:other];
}
@@ -193,19 +251,22 @@ NS_ASSUME_NONNULL_BEGIN
if ([self isKindOfClass:[FSTDoubleValue class]]) {
double thisDouble = ((FSTDoubleValue *)self).internalValue;
if ([other isKindOfClass:[FSTDoubleValue class]]) {
- return FSTCompareDoubles(thisDouble, ((FSTDoubleValue *)other).internalValue);
+ return WrapCompare(thisDouble, ((FSTDoubleValue *)other).internalValue);
} else {
FSTAssert([other isKindOfClass:[FSTIntegerValue class]], @"Unknown number value: %@",
other);
- return FSTCompareMixed(thisDouble, ((FSTIntegerValue *)other).internalValue);
+ auto result = CompareMixedNumber(thisDouble, ((FSTIntegerValue *)other).internalValue);
+ return static_cast<NSComparisonResult>(result);
}
} else {
int64_t thisInt = ((FSTIntegerValue *)self).internalValue;
if ([other isKindOfClass:[FSTIntegerValue class]]) {
- return FSTCompareInt64s(thisInt, ((FSTIntegerValue *)other).internalValue);
+ return WrapCompare(thisInt, ((FSTIntegerValue *)other).internalValue);
} else {
FSTAssert([other isKindOfClass:[FSTDoubleValue class]], @"Unknown number value: %@", other);
- return -1 * FSTCompareMixed(((FSTDoubleValue *)other).internalValue, thisInt);
+ double otherDouble = ((FSTDoubleValue *)other).internalValue;
+ auto result = ReverseOrder(CompareMixedNumber(otherDouble, thisInt));
+ return static_cast<NSComparisonResult>(result);
}
}
}
@@ -296,11 +357,11 @@ NS_ASSUME_NONNULL_BEGIN
// NOTE: isEqual: should compare NaN equal to itself and -0.0 not equal to 0.0.
return [other isKindOfClass:[FSTDoubleValue class]] &&
- FSTDoubleBitwiseEquals(self.internalValue, ((FSTDoubleValue *)other).internalValue);
+ DoubleBitwiseEquals(self.internalValue, ((FSTDoubleValue *)other).internalValue);
}
- (NSUInteger)hash {
- return FSTDoubleBitwiseHash(self.internalValue);
+ return DoubleBitwiseHash(self.internalValue);
}
// NOTE: compare: is implemented in NumberValue.
@@ -309,6 +370,17 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark - FSTStringValue
+/**
+ * Specialization of Comparator for NSStrings.
+ */
+template <>
+struct Comparator<NSString *> {
+ bool operator()(NSString *left, NSString *right) const {
+ Comparator<absl::string_view> lessThan;
+ return lessThan(MakeStringView(left), MakeStringView(right));
+ }
+};
+
@interface FSTStringValue ()
@property(nonatomic, copy, readonly) NSString *internalValue;
@end
@@ -347,7 +419,7 @@ NS_ASSUME_NONNULL_BEGIN
- (NSComparisonResult)compare:(FSTFieldValue *)other {
if ([other isKindOfClass:[FSTStringValue class]]) {
- return FSTCompareStrings(self.internalValue, ((FSTStringValue *)other).internalValue);
+ return WrapCompare(self.internalValue, ((FSTStringValue *)other).internalValue);
} else {
return [self defaultCompare:other];
}
@@ -358,19 +430,19 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark - FSTTimestampValue
@interface FSTTimestampValue ()
-@property(nonatomic, strong, readonly) FSTTimestamp *internalValue;
+@property(nonatomic, strong, readonly) FIRTimestamp *internalValue;
@end
@implementation FSTTimestampValue
-+ (instancetype)timestampValue:(FSTTimestamp *)value {
++ (instancetype)timestampValue:(FIRTimestamp *)value {
return [[FSTTimestampValue alloc] initWithValue:value];
}
-- (id)initWithValue:(FSTTimestamp *)value {
+- (id)initWithValue:(FIRTimestamp *)value {
self = [super init];
if (self) {
- _internalValue = value; // FSTTimestamp is immutable.
+ _internalValue = value; // FIRTimestamp is immutable.
}
return self;
}
@@ -380,8 +452,15 @@ NS_ASSUME_NONNULL_BEGIN
}
- (id)value {
- // For developers, we expose Timestamps as Dates.
- return self.internalValue.approximateDateValue;
+ return self.internalValue;
+}
+
+- (id)valueWithOptions:(FSTFieldValueOptions *)options {
+ if (options.timestampsInSnapshotsEnabled) {
+ return self.value;
+ } else {
+ return [self.value dateValue];
+ }
}
- (BOOL)isEqual:(id)other {
@@ -405,19 +484,22 @@ NS_ASSUME_NONNULL_BEGIN
}
@end
-
#pragma mark - FSTServerTimestampValue
@implementation FSTServerTimestampValue
-+ (instancetype)serverTimestampValueWithLocalWriteTime:(FSTTimestamp *)localWriteTime {
- return [[FSTServerTimestampValue alloc] initWithLocalWriteTime:localWriteTime];
++ (instancetype)serverTimestampValueWithLocalWriteTime:(FIRTimestamp *)localWriteTime
+ previousValue:(nullable FSTFieldValue *)previousValue {
+ return [[FSTServerTimestampValue alloc] initWithLocalWriteTime:localWriteTime
+ previousValue:previousValue];
}
-- (id)initWithLocalWriteTime:(FSTTimestamp *)localWriteTime {
+- (id)initWithLocalWriteTime:(FIRTimestamp *)localWriteTime
+ previousValue:(nullable FSTFieldValue *)previousValue {
self = [super init];
if (self) {
_localWriteTime = localWriteTime;
+ _previousValue = previousValue;
}
return self;
}
@@ -426,11 +508,23 @@ NS_ASSUME_NONNULL_BEGIN
return FSTTypeOrderTimestamp;
}
-- (NSNull *)value {
- // For developers, server timestamps always evaluate to NSNull (for now, at least; b/62064202).
+- (id)value {
return [NSNull null];
}
+- (id)valueWithOptions:(FSTFieldValueOptions *)options {
+ switch (options.serverTimestampBehavior) {
+ case FSTServerTimestampBehaviorNone:
+ return [NSNull null];
+ case FSTServerTimestampBehaviorEstimate:
+ return [[FSTTimestampValue timestampValue:self.localWriteTime] valueWithOptions:options];
+ case FSTServerTimestampBehaviorPrevious:
+ return self.previousValue ? [self.previousValue valueWithOptions:options] : [NSNull null];
+ default:
+ FSTFail(@"Unexpected server timestamp option: %ld", (long)options.serverTimestampBehavior);
+ }
+}
+
- (BOOL)isEqual:(id)other {
return [other isKindOfClass:[FSTServerTimestampValue class]] &&
[self.localWriteTime isEqual:((FSTServerTimestampValue *)other).localWriteTime];
@@ -503,9 +597,24 @@ NS_ASSUME_NONNULL_BEGIN
}
@end
-
#pragma mark - FSTBlobValue
+static NSComparisonResult CompareBytes(NSData *left, NSData *right) {
+ NSUInteger minLength = MIN(left.length, right.length);
+ int result = memcmp(left.bytes, right.bytes, minLength);
+ if (result < 0) {
+ return NSOrderedAscending;
+ } else if (result > 0) {
+ return NSOrderedDescending;
+ } else if (left.length < right.length) {
+ return NSOrderedAscending;
+ } else if (left.length > right.length) {
+ return NSOrderedDescending;
+ } else {
+ return NSOrderedSame;
+ }
+}
+
@interface FSTBlobValue ()
@property(nonatomic, copy, readonly) NSData *internalValue;
@end
@@ -544,7 +653,7 @@ NS_ASSUME_NONNULL_BEGIN
- (NSComparisonResult)compare:(FSTFieldValue *)other {
if ([other isKindOfClass:[FSTBlobValue class]]) {
- return FSTCompareBytes(self.internalValue, ((FSTBlobValue *)other).internalValue);
+ return CompareBytes(self.internalValue, ((FSTBlobValue *)other).internalValue);
} else {
return [self defaultCompare:other];
}
@@ -560,11 +669,11 @@ NS_ASSUME_NONNULL_BEGIN
@implementation FSTReferenceValue
-+ (instancetype)referenceValue:(FSTDocumentKey *)value databaseID:(FSTDatabaseID *)databaseID {
++ (instancetype)referenceValue:(FSTDocumentKey *)value databaseID:(const DatabaseId *)databaseID {
return [[FSTReferenceValue alloc] initWithValue:value databaseID:databaseID];
}
-- (id)initWithValue:(FSTDocumentKey *)value databaseID:(FSTDatabaseID *)databaseID {
+- (id)initWithValue:(FSTDocumentKey *)value databaseID:(const DatabaseId *)databaseID {
self = [super init];
if (self) {
_key = value;
@@ -590,12 +699,11 @@ NS_ASSUME_NONNULL_BEGIN
}
FSTReferenceValue *otherRef = (FSTReferenceValue *)other;
- return [self.key isEqualToKey:otherRef.key] &&
- [self.databaseID isEqualToDatabaseId:otherRef.databaseID];
+ return [self.key isEqualToKey:otherRef.key] && *self.databaseID == *otherRef.databaseID;
}
- (NSUInteger)hash {
- NSUInteger result = [self.databaseID hash];
+ NSUInteger result = self.databaseID->Hash();
result = 31 * result + [self.key hash];
return result;
}
@@ -603,7 +711,13 @@ NS_ASSUME_NONNULL_BEGIN
- (NSComparisonResult)compare:(FSTFieldValue *)other {
if ([other isKindOfClass:[FSTReferenceValue class]]) {
FSTReferenceValue *ref = (FSTReferenceValue *)other;
- NSComparisonResult cmp = [self.databaseID compare:ref.databaseID];
+ NSComparisonResult cmp = [util::WrapNSStringNoCopy(self.databaseID->project_id())
+ compare:util::WrapNSStringNoCopy(ref.databaseID->project_id())];
+ if (cmp != NSOrderedSame) {
+ return cmp;
+ }
+ cmp = [util::WrapNSStringNoCopy(self.databaseID->database_id())
+ compare:util::WrapNSStringNoCopy(ref.databaseID->database_id())];
return cmp != NSOrderedSame ? cmp : [self.key compare:ref.key];
} else {
return [self defaultCompare:other];
@@ -614,6 +728,10 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark - FSTObjectValue
+static const NSComparator StringComparator = ^NSComparisonResult(NSString *left, NSString *right) {
+ return WrapCompare(left, right);
+};
+
@interface FSTObjectValue ()
@property(nonatomic, strong, readonly)
FSTImmutableSortedDictionary<NSString *, FSTFieldValue *> *internalValue;
@@ -627,7 +745,7 @@ NS_ASSUME_NONNULL_BEGIN
dispatch_once(&onceToken, ^{
FSTImmutableSortedDictionary<NSString *, FSTFieldValue *> *empty =
- [FSTImmutableSortedDictionary dictionaryWithComparator:FSTStringComparator];
+ [FSTImmutableSortedDictionary dictionaryWithComparator:StringComparator];
sharedEmptyInstance = [[FSTObjectValue alloc] initWithImmutableDictionary:empty];
});
return sharedEmptyInstance;
@@ -644,7 +762,7 @@ NS_ASSUME_NONNULL_BEGIN
- (id)initWithDictionary:(NSDictionary<NSString *, FSTFieldValue *> *)value {
FSTImmutableSortedDictionary<NSString *, FSTFieldValue *> *dictionary =
- [FSTImmutableSortedDictionary dictionaryWithDictionary:value comparator:FSTStringComparator];
+ [FSTImmutableSortedDictionary dictionaryWithDictionary:value comparator:StringComparator];
return [self initWithImmutableDictionary:dictionary];
}
@@ -657,6 +775,15 @@ NS_ASSUME_NONNULL_BEGIN
return result;
}
+- (id)valueWithOptions:(FSTFieldValueOptions *)options {
+ NSMutableDictionary *result = [NSMutableDictionary dictionary];
+ [self.internalValue
+ enumerateKeysAndObjectsUsingBlock:^(NSString *key, FSTFieldValue *obj, BOOL *stop) {
+ result[key] = [obj valueWithOptions:options];
+ }];
+ return result;
+}
+
- (FSTTypeOrder)typeOrder {
return FSTTypeOrderObject;
}
@@ -698,36 +825,37 @@ NS_ASSUME_NONNULL_BEGIN
key2 = [enumerator2 nextObject];
}
// Only equal if both enumerators are exhausted.
- return FSTCompareBools(key1 != nil, key2 != nil);
+ return WrapCompare(key1 != nil, key2 != nil);
} else {
return [self defaultCompare:other];
}
}
-- (nullable FSTFieldValue *)valueForPath:(FSTFieldPath *)fieldPath {
+- (nullable FSTFieldValue *)valueForPath:(const FieldPath &)fieldPath {
FSTFieldValue *value = self;
- for (int i = 0, max = fieldPath.length; value && i < max; i++) {
+ for (size_t i = 0, max = fieldPath.size(); value && i < max; i++) {
if (![value isMemberOfClass:[FSTObjectValue class]]) {
return nil;
}
- NSString *fieldName = fieldPath[i];
+ NSString *fieldName = util::WrapNSStringNoCopy(fieldPath[i]);
value = ((FSTObjectValue *)value).internalValue[fieldName];
}
return value;
}
-- (FSTObjectValue *)objectBySettingValue:(FSTFieldValue *)value forPath:(FSTFieldPath *)fieldPath {
- FSTAssert([fieldPath length] > 0, @"Cannot set value with an empty path");
+- (FSTObjectValue *)objectBySettingValue:(FSTFieldValue *)value
+ forPath:(const FieldPath &)fieldPath {
+ FSTAssert(fieldPath.size() > 0, @"Cannot set value with an empty path");
- NSString *childName = [fieldPath firstSegment];
- if ([fieldPath length] == 1) {
+ NSString *childName = util::WrapNSString(fieldPath.first_segment());
+ if (fieldPath.size() == 1) {
// Recursive base case:
return [self objectBySettingValue:value forField:childName];
} else {
- // Nested path. Recursively generate a new sub-object and then wrap a new FSTObjectValue around
- // the result.
+ // Nested path. Recursively generate a new sub-object and then wrap a new FSTObjectValue
+ // around the result.
FSTFieldValue *child = [_internalValue objectForKey:childName];
FSTObjectValue *childObject;
if ([child isKindOfClass:[FSTObjectValue class]]) {
@@ -737,23 +865,22 @@ NS_ASSUME_NONNULL_BEGIN
// there.
childObject = [FSTObjectValue objectValue];
}
- FSTFieldValue *newChild =
- [childObject objectBySettingValue:value forPath:[fieldPath pathByRemovingFirstSegment]];
+ FSTFieldValue *newChild = [childObject objectBySettingValue:value forPath:fieldPath.PopFirst()];
return [self objectBySettingValue:newChild forField:childName];
}
}
-- (FSTObjectValue *)objectByDeletingPath:(FSTFieldPath *)fieldPath {
- FSTAssert([fieldPath length] > 0, @"Cannot delete an empty path");
- NSString *childName = [fieldPath firstSegment];
- if ([fieldPath length] == 1) {
+- (FSTObjectValue *)objectByDeletingPath:(const FieldPath &)fieldPath {
+ FSTAssert(fieldPath.size() > 0, @"Cannot delete an empty path");
+ NSString *childName = util::WrapNSString(fieldPath.first_segment());
+ if (fieldPath.size() == 1) {
return [[FSTObjectValue alloc]
initWithImmutableDictionary:[_internalValue dictionaryByRemovingObjectForKey:childName]];
} else {
FSTFieldValue *child = _internalValue[childName];
if ([child isKindOfClass:[FSTObjectValue class]]) {
FSTObjectValue *newChild =
- [((FSTObjectValue *)child) objectByDeletingPath:[fieldPath pathByRemovingFirstSegment]];
+ [((FSTObjectValue *)child) objectByDeletingPath:fieldPath.PopFirst()];
return [self objectBySettingValue:newChild forField:childName];
} else {
// If the child is not found or is a primitive type, make no modifications
@@ -811,6 +938,14 @@ NS_ASSUME_NONNULL_BEGIN
return result;
}
+- (id)valueWithOptions:(FSTFieldValueOptions *)options {
+ NSMutableArray *result = [NSMutableArray arrayWithCapacity:_internalValue.count];
+ [self.internalValue enumerateObjectsUsingBlock:^(FSTFieldValue *obj, NSUInteger idx, BOOL *stop) {
+ [result addObject:[obj valueWithOptions:options]];
+ }];
+ return result;
+}
+
- (FSTTypeOrder)typeOrder {
return FSTTypeOrderArray;
}
@@ -826,7 +961,7 @@ NS_ASSUME_NONNULL_BEGIN
return cmp;
}
}
- return FSTCompareUIntegers(selfArray.count, otherArray.count);
+ return WrapCompare<int64_t>(selfArray.count, otherArray.count);
} else {
return [self defaultCompare:other];
}
diff --git a/Firestore/Source/Model/FSTMutation.h b/Firestore/Source/Model/FSTMutation.h
index ef7f1c8..7261f30 100644
--- a/Firestore/Source/Model/FSTMutation.h
+++ b/Firestore/Source/Model/FSTMutation.h
@@ -16,104 +16,25 @@
#import <Foundation/Foundation.h>
+#include <memory>
+#include <vector>
+
+#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+#include "Firestore/core/src/firebase/firestore/model/field_mask.h"
+#include "Firestore/core/src/firebase/firestore/model/field_path.h"
+#include "Firestore/core/src/firebase/firestore/model/field_transform.h"
+#include "Firestore/core/src/firebase/firestore/model/precondition.h"
+#include "Firestore/core/src/firebase/firestore/model/transform_operations.h"
+
@class FSTDocument;
-@class FSTDocumentKey;
-@class FSTFieldPath;
@class FSTFieldValue;
@class FSTMaybeDocument;
@class FSTObjectValue;
@class FSTSnapshotVersion;
-@class FSTTimestamp;
+@class FIRTimestamp;
NS_ASSUME_NONNULL_BEGIN
-#pragma mark - FSTFieldMask
-
-/**
- * Provides a set of fields that can be used to partially patch a document. FieldMask is used in
- * conjunction with ObjectValue.
- *
- * Examples:
- * foo - Overwrites foo entirely with the provided value. If foo is not present in the companion
- * ObjectValue, the field is deleted.
- * foo.bar - Overwrites only the field bar of the object foo. If foo is not an object, foo is
- * replaced with an object containing bar.
- */
-@interface FSTFieldMask : NSObject
-- (id)init __attribute__((unavailable("Use initWithFields:")));
-
-/**
- * Initializes the field mask with the given field paths. Caller is expected to either copy or
- * or release the array of fields.
- */
-- (instancetype)initWithFields:(NSArray<FSTFieldPath *> *)fields NS_DESIGNATED_INITIALIZER;
-
-@property(nonatomic, strong, readonly) NSArray<FSTFieldPath *> *fields;
-@end
-
-#pragma mark - FSTFieldTransform
-
-/** Represents a transform within a TransformMutation. */
-@protocol FSTTransformOperation <NSObject>
-@end
-
-/** Transforms a value into a server-generated timestamp. */
-@interface FSTServerTimestampTransform : NSObject <FSTTransformOperation>
-+ (instancetype)serverTimestampTransform;
-@end
-
-/** A field path and the FSTTransformOperation to perform upon it. */
-@interface FSTFieldTransform : NSObject
-- (instancetype)init NS_UNAVAILABLE;
-- (instancetype)initWithPath:(FSTFieldPath *)path
- transform:(id<FSTTransformOperation>)transform NS_DESIGNATED_INITIALIZER;
-@property(nonatomic, strong, readonly) FSTFieldPath *path;
-@property(nonatomic, strong, readonly) id<FSTTransformOperation> transform;
-@end
-
-#pragma mark - FSTPrecondition
-
-typedef NS_ENUM(NSUInteger, FSTPreconditionExists) {
- FSTPreconditionExistsNotSet,
- FSTPreconditionExistsYes,
- FSTPreconditionExistsNo,
-};
-
-/**
- * Encodes a precondition for a mutation. This follows the model that the backend accepts with the
- * special case of an explicit "empty" precondition (meaning no precondition).
- */
-@interface FSTPrecondition : NSObject
-
-/** Creates a new FSTPrecondition with an exists flag. */
-+ (FSTPrecondition *)preconditionWithExists:(BOOL)exists;
-
-/** Creates a new FSTPrecondition based on a time the document exists at. */
-+ (FSTPrecondition *)preconditionWithUpdateTime:(FSTSnapshotVersion *)updateTime;
-
-/** Returns a precondition representing no precondition. */
-+ (FSTPrecondition *)none;
-
-/**
- * Returns true if the preconditions is valid for the given document (or null if no document is
- * available).
- */
-- (BOOL)isValidForDocument:(FSTMaybeDocument *_Nullable)maybeDoc;
-
-/** Returns whether this Precondition represents no precondition. */
-- (BOOL)isNone;
-
-/** If set, preconditions a mutation based on the last updateTime. */
-@property(nonatomic, strong, readonly, nullable) FSTSnapshotVersion *updateTime;
-
-/**
- * If set, preconditions a mutation based on whether the document exists.
- * Uses FSTPreconditionExistsNotSet to mark as unset.
- */
-@property(nonatomic, assign, readonly) FSTPreconditionExists exists;
-
-@end
-
#pragma mark - FSTMutationResult
@interface FSTMutationResult : NSObject
@@ -128,7 +49,7 @@ typedef NS_ENUM(NSUInteger, FSTPreconditionExists) {
/**
* The resulting fields returned from the backend after a FSTTransformMutation has been committed.
- * Contains one FieldValue for each FSTFieldTransform that was in the mutation.
+ * Contains one FieldValue for each FieldTransform that was in the mutation.
*
* Will be nil if the mutation was not a FSTTransformMutation.
*/
@@ -151,15 +72,18 @@ typedef NS_ENUM(NSUInteger, FSTPreconditionExists) {
- (id)init NS_UNAVAILABLE;
-- (instancetype)initWithKey:(FSTDocumentKey *)key
- precondition:(FSTPrecondition *)precondition NS_DESIGNATED_INITIALIZER;
+- (instancetype)initWithKey:(firebase::firestore::model::DocumentKey)key
+ precondition:(firebase::firestore::model::Precondition)precondition
+ NS_DESIGNATED_INITIALIZER;
/**
* Applies this mutation to the given FSTDocument, FSTDeletedDocument or nil, if we don't have
* information about this document. Both the input and returned documents can be nil.
*
- * @param maybeDoc The document to mutate. The input document should nil if it does not currently
- * exist.
+ * @param maybeDoc The current state of the document to mutate. The input document should be nil if
+ * it does not currently exist.
+ * @param baseDoc The state of the document prior to this mutation batch. The input document should
+ * be nil if it the document did not exist.
* @param localWriteTime A timestamp indicating the local write time of the batch this mutation is
* a part of.
* @param mutationResult Optional result info from the backend. If omitted, it's assumed that
@@ -196,21 +120,22 @@ typedef NS_ENUM(NSUInteger, FSTPreconditionExists) {
* apply the transform if the prior mutation resulted in an FSTDocument (always true for an
* FSTSetMutation, but not necessarily for an FSTPatchMutation).
*/
-- (FSTMaybeDocument *_Nullable)applyTo:(FSTMaybeDocument *_Nullable)maybeDoc
- localWriteTime:(FSTTimestamp *)localWriteTime
- mutationResult:(FSTMutationResult *_Nullable)mutationResult;
+- (nullable FSTMaybeDocument *)applyTo:(nullable FSTMaybeDocument *)maybeDoc
+ baseDocument:(nullable FSTMaybeDocument *)baseDoc
+ localWriteTime:(FIRTimestamp *)localWriteTime
+ mutationResult:(nullable FSTMutationResult *)mutationResult;
/**
* A helper version of applyTo for applying mutations locally (without a mutation result from the
* backend).
*/
-- (FSTMaybeDocument *_Nullable)applyTo:(FSTMaybeDocument *_Nullable)maybeDoc
- localWriteTime:(FSTTimestamp *)localWriteTime;
+- (nullable FSTMaybeDocument *)applyTo:(nullable FSTMaybeDocument *)maybeDoc
+ baseDocument:(nullable FSTMaybeDocument *)baseDoc
+ localWriteTime:(nullable FIRTimestamp *)localWriteTime;
-@property(nonatomic, strong, readonly) FSTDocumentKey *key;
+- (const firebase::firestore::model::DocumentKey &)key;
-/** The precondition for this mutation. */
-@property(nonatomic, strong, readonly) FSTPrecondition *precondition;
+- (const firebase::firestore::model::Precondition &)precondition;
@end
@@ -222,8 +147,8 @@ typedef NS_ENUM(NSUInteger, FSTPreconditionExists) {
*/
@interface FSTSetMutation : FSTMutation
-- (instancetype)initWithKey:(FSTDocumentKey *)key
- precondition:(FSTPrecondition *)precondition NS_UNAVAILABLE;
+- (instancetype)initWithKey:(firebase::firestore::model::DocumentKey)key
+ precondition:(firebase::firestore::model::Precondition)precondition NS_UNAVAILABLE;
/**
* Initializes the set mutation.
@@ -233,9 +158,10 @@ typedef NS_ENUM(NSUInteger, FSTPreconditionExists) {
* key.
* @param precondition The precondition for this mutation.
*/
-- (instancetype)initWithKey:(FSTDocumentKey *)key
+- (instancetype)initWithKey:(firebase::firestore::model::DocumentKey)key
value:(FSTObjectValue *)value
- precondition:(FSTPrecondition *)precondition NS_DESIGNATED_INITIALIZER;
+ precondition:(firebase::firestore::model::Precondition)precondition
+ NS_DESIGNATED_INITIALIZER;
/** The object value to use when setting the document. */
@property(nonatomic, strong, readonly) FSTObjectValue *value;
@@ -254,12 +180,12 @@ typedef NS_ENUM(NSUInteger, FSTPreconditionExists) {
*/
@interface FSTPatchMutation : FSTMutation
-/** Returns the precondition for the given FSTPrecondition. */
-- (instancetype)initWithKey:(FSTDocumentKey *)key
- precondition:(FSTPrecondition *)precondition NS_UNAVAILABLE;
+/** Returns the precondition for the given Precondition. */
+- (instancetype)initWithKey:(firebase::firestore::model::DocumentKey)key
+ precondition:(firebase::firestore::model::Precondition)precondition NS_UNAVAILABLE;
/**
- * Initializes a new patch mutation with an explicit FSTFieldMask and FSTObjectValue representing
+ * Initializes a new patch mutation with an explicit FieldMask and FSTObjectValue representing
* the updates to perform
*
* @param key Identifies the location of the document to mutate.
@@ -269,19 +195,20 @@ typedef NS_ENUM(NSUInteger, FSTPreconditionExists) {
* to determine the locations at which it should be applied).
* @param precondition The precondition for this mutation.
*/
-- (instancetype)initWithKey:(FSTDocumentKey *)key
- fieldMask:(FSTFieldMask *)fieldMask
+- (instancetype)initWithKey:(firebase::firestore::model::DocumentKey)key
+ fieldMask:(firebase::firestore::model::FieldMask)fieldMask
value:(FSTObjectValue *)value
- precondition:(FSTPrecondition *)precondition NS_DESIGNATED_INITIALIZER;
-
-/** The fields and associated values to use when patching the document. */
-@property(nonatomic, strong, readonly) FSTObjectValue *value;
+ precondition:(firebase::firestore::model::Precondition)precondition
+ NS_DESIGNATED_INITIALIZER;
/**
* A mask to apply to |value|, where only fields that are in both the fieldMask and the value
* will be updated.
*/
-@property(nonatomic, strong, readonly) FSTFieldMask *fieldMask;
+- (const firebase::firestore::model::FieldMask &)fieldMask;
+
+/** The fields and associated values to use when patching the document. */
+@property(nonatomic, strong, readonly) FSTObjectValue *value;
@end
@@ -298,21 +225,21 @@ typedef NS_ENUM(NSUInteger, FSTPreconditionExists) {
*/
@interface FSTTransformMutation : FSTMutation
-- (instancetype)initWithKey:(FSTDocumentKey *)key
- precondition:(FSTPrecondition *)precondition NS_UNAVAILABLE;
+- (instancetype)initWithKey:(firebase::firestore::model::DocumentKey)key
+ precondition:(firebase::firestore::model::Precondition)precondition NS_UNAVAILABLE;
/**
* Initializes a new transform mutation with the specified field transforms.
*
* @param key Identifies the location of the document to mutate.
- * @param fieldTransforms A list of FSTFieldTransform objects to perform to the document.
+ * @param fieldTransforms A list of FieldTransform objects to perform to the document.
*/
-- (instancetype)initWithKey:(FSTDocumentKey *)key
- fieldTransforms:(NSArray<FSTFieldTransform *> *)fieldTransforms
+- (instancetype)initWithKey:(firebase::firestore::model::DocumentKey)key
+ fieldTransforms:(std::vector<firebase::firestore::model::FieldTransform>)fieldTransforms
NS_DESIGNATED_INITIALIZER;
/** The field transforms to use when transforming the document. */
-@property(nonatomic, strong, readonly) NSArray<FSTFieldTransform *> *fieldTransforms;
+- (const std::vector<firebase::firestore::model::FieldTransform> &)fieldTransforms;
@end
diff --git a/Firestore/Source/Model/FSTMutation.m b/Firestore/Source/Model/FSTMutation.m
deleted file mode 100644
index 5b47280..0000000
--- a/Firestore/Source/Model/FSTMutation.m
+++ /dev/null
@@ -1,575 +0,0 @@
-/*
- * Copyright 2017 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 "Firestore/Source/Model/FSTMutation.h"
-
-#import "Firestore/Source/Core/FSTSnapshotVersion.h"
-#import "Firestore/Source/Core/FSTTimestamp.h"
-#import "Firestore/Source/Model/FSTDocument.h"
-#import "Firestore/Source/Model/FSTDocumentKey.h"
-#import "Firestore/Source/Model/FSTFieldValue.h"
-#import "Firestore/Source/Model/FSTPath.h"
-#import "Firestore/Source/Util/FSTAssert.h"
-#import "Firestore/Source/Util/FSTClasses.h"
-
-NS_ASSUME_NONNULL_BEGIN
-
-#pragma mark - FSTFieldMask
-
-@implementation FSTFieldMask
-
-- (instancetype)initWithFields:(NSArray<FSTFieldPath *> *)fields {
- if (self = [super init]) {
- _fields = fields;
- }
- return self;
-}
-
-- (BOOL)isEqual:(id)other {
- if (other == self) {
- return YES;
- }
- if (![other isKindOfClass:[FSTFieldMask class]]) {
- return NO;
- }
-
- FSTFieldMask *otherMask = (FSTFieldMask *)other;
- return [self.fields isEqual:otherMask.fields];
-}
-
-- (NSUInteger)hash {
- return self.fields.hash;
-}
-@end
-
-#pragma mark - FSTServerTimestampTransform
-
-@implementation FSTServerTimestampTransform
-
-+ (instancetype)serverTimestampTransform {
- static FSTServerTimestampTransform *sharedInstance = nil;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- sharedInstance = [[FSTServerTimestampTransform alloc] init];
- });
- return sharedInstance;
-}
-
-- (BOOL)isEqual:(id)other {
- if (other == self) {
- return YES;
- }
- return [other isKindOfClass:[FSTServerTimestampTransform class]];
-}
-
-- (NSUInteger)hash {
- // arbitrary number since all instances are equal.
- return 37;
-}
-
-@end
-
-#pragma mark - FSTFieldTransform
-
-@implementation FSTFieldTransform
-
-- (instancetype)initWithPath:(FSTFieldPath *)path transform:(id<FSTTransformOperation>)transform {
- self = [super init];
- if (self) {
- _path = path;
- _transform = transform;
- }
- return self;
-}
-
-- (BOOL)isEqual:(id)other {
- if (other == self) return YES;
- if (!other || ![[other class] isEqual:[self class]]) return NO;
- FSTFieldTransform *otherFieldTransform = other;
- return [self.path isEqual:otherFieldTransform.path] &&
- [self.transform isEqual:otherFieldTransform.transform];
-}
-
-- (NSUInteger)hash {
- NSUInteger hash = [self.path hash];
- hash = hash * 31 + [self.transform hash];
- return hash;
-}
-
-@end
-
-#pragma mark - FSTPrecondition
-
-@implementation FSTPrecondition
-
-+ (FSTPrecondition *)preconditionWithExists:(BOOL)exists {
- FSTPreconditionExists existsEnum = exists ? FSTPreconditionExistsYes : FSTPreconditionExistsNo;
- return [[FSTPrecondition alloc] initWithUpdateTime:nil exists:existsEnum];
-}
-
-+ (FSTPrecondition *)preconditionWithUpdateTime:(FSTSnapshotVersion *)updateTime {
- return [[FSTPrecondition alloc] initWithUpdateTime:updateTime exists:FSTPreconditionExistsNotSet];
-}
-
-+ (FSTPrecondition *)none {
- static dispatch_once_t onceToken;
- static FSTPrecondition *noPrecondition;
- dispatch_once(&onceToken, ^{
- noPrecondition =
- [[FSTPrecondition alloc] initWithUpdateTime:nil exists:FSTPreconditionExistsNotSet];
- });
- return noPrecondition;
-}
-
-- (instancetype)initWithUpdateTime:(FSTSnapshotVersion *_Nullable)updateTime
- exists:(FSTPreconditionExists)exists {
- if (self = [super init]) {
- _updateTime = updateTime;
- _exists = exists;
- }
- return self;
-}
-
-- (BOOL)isValidForDocument:(FSTMaybeDocument *_Nullable)maybeDoc {
- if (self.updateTime) {
- return
- [maybeDoc isKindOfClass:[FSTDocument class]] && [maybeDoc.version isEqual:self.updateTime];
- } else if (self.exists != FSTPreconditionExistsNotSet) {
- if (self.exists == FSTPreconditionExistsYes) {
- return [maybeDoc isKindOfClass:[FSTDocument class]];
- } else {
- FSTAssert(self.exists == FSTPreconditionExistsNo, @"Invalid precondition");
- return maybeDoc == nil || [maybeDoc isKindOfClass:[FSTDeletedDocument class]];
- }
- } else {
- FSTAssert(self.isNone, @"Precondition should be empty");
- return YES;
- }
-}
-
-- (BOOL)isNone {
- return self.updateTime == nil && self.exists == FSTPreconditionExistsNotSet;
-}
-
-- (BOOL)isEqual:(id)other {
- if (self == other) {
- return YES;
- }
-
- if (![other isKindOfClass:[FSTPrecondition class]]) {
- return NO;
- }
-
- FSTPrecondition *otherPrecondition = (FSTPrecondition *)other;
- // Compare references to cover nil equality
- return (self.updateTime == otherPrecondition.updateTime ||
- [self.updateTime isEqual:otherPrecondition.updateTime]) &&
- self.exists == otherPrecondition.exists;
-}
-
-- (NSUInteger)hash {
- NSUInteger hash = [self.updateTime hash];
- hash = hash * 31 + self.exists;
- return hash;
-}
-
-- (NSString *)description {
- if (self.isNone) {
- return @"<FSTPrecondition <none>>";
- } else {
- NSString *existsString;
- switch (self.exists) {
- case FSTPreconditionExistsYes:
- existsString = @"yes";
- break;
- case FSTPreconditionExistsNo:
- existsString = @"no";
- break;
- default:
- existsString = @"<not-set>";
- break;
- }
- return [NSString stringWithFormat:@"<FSTPrecondition updateTime=%@ exists=%@>", self.updateTime,
- existsString];
- }
-}
-
-@end
-
-#pragma mark - FSTMutationResult
-
-@implementation FSTMutationResult
-
-- (instancetype)initWithVersion:(FSTSnapshotVersion *_Nullable)version
- transformResults:(NSArray<FSTFieldValue *> *_Nullable)transformResults {
- if (self = [super init]) {
- _version = version;
- _transformResults = transformResults;
- }
- return self;
-}
-
-@end
-
-#pragma mark - FSTMutation
-
-@implementation FSTMutation
-
-- (instancetype)initWithKey:(FSTDocumentKey *)key precondition:(FSTPrecondition *)precondition {
- if (self = [super init]) {
- _key = key;
- _precondition = precondition;
- }
- return self;
-}
-
-- (FSTMaybeDocument *_Nullable)applyTo:(FSTMaybeDocument *_Nullable)maybeDoc
- localWriteTime:(FSTTimestamp *)localWriteTime
- mutationResult:(FSTMutationResult *_Nullable)mutationResult {
- @throw FSTAbstractMethodException(); // NOLINT
-}
-
-- (FSTMaybeDocument *_Nullable)applyTo:(FSTMaybeDocument *_Nullable)maybeDoc
- localWriteTime:(FSTTimestamp *)localWriteTime {
- return [self applyTo:maybeDoc localWriteTime:localWriteTime mutationResult:nil];
-}
-
-@end
-
-#pragma mark - FSTSetMutation
-
-@implementation FSTSetMutation
-
-- (instancetype)initWithKey:(FSTDocumentKey *)key
- value:(FSTObjectValue *)value
- precondition:(FSTPrecondition *)precondition {
- if (self = [super initWithKey:key precondition:precondition]) {
- _value = value;
- }
- return self;
-}
-
-- (NSString *)description {
- return [NSString stringWithFormat:@"<FSTSetMutation key=%@ value=%@ precondition=%@>", self.key,
- self.value, self.precondition];
-}
-
-- (BOOL)isEqual:(id)other {
- if (other == self) {
- return YES;
- }
- if (![other isKindOfClass:[FSTSetMutation class]]) {
- return NO;
- }
-
- FSTSetMutation *otherMutation = (FSTSetMutation *)other;
- return [self.key isEqual:otherMutation.key] && [self.value isEqual:otherMutation.value] &&
- [self.precondition isEqual:otherMutation.precondition];
-}
-
-- (NSUInteger)hash {
- NSUInteger result = [self.key hash];
- result = 31 * result + [self.precondition hash];
- result = 31 * result + [self.value hash];
- return result;
-}
-
-- (FSTMaybeDocument *_Nullable)applyTo:(FSTMaybeDocument *_Nullable)maybeDoc
- localWriteTime:(FSTTimestamp *)localWriteTime
- mutationResult:(FSTMutationResult *_Nullable)mutationResult {
- if (mutationResult) {
- FSTAssert(!mutationResult.transformResults, @"Transform results received by FSTSetMutation.");
- }
-
- if (![self.precondition isValidForDocument:maybeDoc]) {
- return maybeDoc;
- }
-
- BOOL hasLocalMutations = (mutationResult == nil);
- if (!maybeDoc || [maybeDoc isMemberOfClass:[FSTDeletedDocument class]]) {
- // If the document didn't exist before, create it.
- return [FSTDocument documentWithData:self.value
- key:self.key
- version:[FSTSnapshotVersion noVersion]
- hasLocalMutations:hasLocalMutations];
- }
-
- FSTAssert([maybeDoc isMemberOfClass:[FSTDocument class]], @"Unknown MaybeDocument type %@",
- [maybeDoc class]);
- FSTDocument *doc = (FSTDocument *)maybeDoc;
-
- FSTAssert([doc.key isEqual:self.key], @"Can only set a document with the same key");
- return [FSTDocument documentWithData:self.value
- key:doc.key
- version:doc.version
- hasLocalMutations:hasLocalMutations];
-}
-@end
-
-#pragma mark - FSTPatchMutation
-
-@implementation FSTPatchMutation
-
-- (instancetype)initWithKey:(FSTDocumentKey *)key
- fieldMask:(FSTFieldMask *)fieldMask
- value:(FSTObjectValue *)value
- precondition:(FSTPrecondition *)precondition {
- self = [super initWithKey:key precondition:precondition];
- if (self) {
- _fieldMask = fieldMask;
- _value = value;
- }
- return self;
-}
-
-- (BOOL)isEqual:(id)other {
- if (other == self) {
- return YES;
- }
- if (![other isKindOfClass:[FSTPatchMutation class]]) {
- return NO;
- }
-
- FSTPatchMutation *otherMutation = (FSTPatchMutation *)other;
- return [self.key isEqual:otherMutation.key] && [self.fieldMask isEqual:otherMutation.fieldMask] &&
- [self.value isEqual:otherMutation.value] &&
- [self.precondition isEqual:otherMutation.precondition];
-}
-
-- (NSUInteger)hash {
- NSUInteger result = [self.key hash];
- result = 31 * result + [self.precondition hash];
- result = 31 * result + [self.fieldMask hash];
- result = 31 * result + [self.value hash];
- return result;
-}
-
-- (NSString *)description {
- return [NSString stringWithFormat:@"<FSTPatchMutation key=%@ mask=%@ value=%@ precondition=%@>",
- self.key, self.fieldMask, self.value, self.precondition];
-}
-
-- (FSTMaybeDocument *_Nullable)applyTo:(FSTMaybeDocument *_Nullable)maybeDoc
- localWriteTime:(FSTTimestamp *)localWriteTime
- mutationResult:(FSTMutationResult *_Nullable)mutationResult {
- if (mutationResult) {
- FSTAssert(!mutationResult.transformResults, @"Transform results received by FSTPatchMutation.");
- }
-
- if (![self.precondition isValidForDocument:maybeDoc]) {
- return maybeDoc;
- }
-
- BOOL hasLocalMutations = (mutationResult == nil);
- if (!maybeDoc || [maybeDoc isMemberOfClass:[FSTDeletedDocument class]]) {
- // Precondition applied, so create the document if necessary
- FSTDocumentKey *key = maybeDoc ? maybeDoc.key : self.key;
- FSTSnapshotVersion *version = maybeDoc ? maybeDoc.version : [FSTSnapshotVersion noVersion];
- maybeDoc = [FSTDocument documentWithData:[FSTObjectValue objectValue]
- key:key
- version:version
- hasLocalMutations:hasLocalMutations];
- }
-
- FSTAssert([maybeDoc isMemberOfClass:[FSTDocument class]], @"Unknown MaybeDocument type %@",
- [maybeDoc class]);
- FSTDocument *doc = (FSTDocument *)maybeDoc;
-
- FSTAssert([doc.key isEqual:self.key], @"Can only patch a document with the same key");
-
- FSTObjectValue *newData = [self patchObjectValue:doc.data];
- return [FSTDocument documentWithData:newData
- key:doc.key
- version:doc.version
- hasLocalMutations:hasLocalMutations];
-}
-
-- (FSTObjectValue *)patchObjectValue:(FSTObjectValue *)objectValue {
- FSTObjectValue *result = objectValue;
- for (FSTFieldPath *fieldPath in self.fieldMask.fields) {
- FSTFieldValue *newValue = [self.value valueForPath:fieldPath];
- if (newValue) {
- result = [result objectBySettingValue:newValue forPath:fieldPath];
- } else {
- result = [result objectByDeletingPath:fieldPath];
- }
- }
- return result;
-}
-
-@end
-
-@implementation FSTTransformMutation
-
-- (instancetype)initWithKey:(FSTDocumentKey *)key
- fieldTransforms:(NSArray<FSTFieldTransform *> *)fieldTransforms {
- // NOTE: We set a precondition of exists: true as a safety-check, since we always combine
- // FSTTransformMutations with a FSTSetMutation or FSTPatchMutation which (if successful) should
- // end up with an existing document.
- if (self = [super initWithKey:key precondition:[FSTPrecondition preconditionWithExists:YES]]) {
- _fieldTransforms = fieldTransforms;
- }
- return self;
-}
-
-- (BOOL)isEqual:(id)other {
- if (other == self) {
- return YES;
- }
- if (![other isKindOfClass:[FSTTransformMutation class]]) {
- return NO;
- }
-
- FSTTransformMutation *otherMutation = (FSTTransformMutation *)other;
- return [self.key isEqual:otherMutation.key] &&
- [self.fieldTransforms isEqual:otherMutation.fieldTransforms] &&
- [self.precondition isEqual:otherMutation.precondition];
-}
-
-- (NSUInteger)hash {
- NSUInteger result = [self.key hash];
- result = 31 * result + [self.precondition hash];
- result = 31 * result + [self.fieldTransforms hash];
- return result;
-}
-
-- (NSString *)description {
- return [NSString stringWithFormat:@"<FSTTransformMutation key=%@ transforms=%@ precondition=%@>",
- self.key, self.fieldTransforms, self.precondition];
-}
-
-- (FSTMaybeDocument *_Nullable)applyTo:(FSTMaybeDocument *_Nullable)maybeDoc
- localWriteTime:(FSTTimestamp *)localWriteTime
- mutationResult:(FSTMutationResult *_Nullable)mutationResult {
- if (mutationResult) {
- FSTAssert(mutationResult.transformResults,
- @"Transform results missing for FSTTransformMutation.");
- }
-
- if (![self.precondition isValidForDocument:maybeDoc]) {
- return maybeDoc;
- }
-
- // We only support transforms with precondition exists, so we can only apply it to an existing
- // document
- FSTAssert([maybeDoc isMemberOfClass:[FSTDocument class]], @"Unknown MaybeDocument type %@",
- [maybeDoc class]);
- FSTDocument *doc = (FSTDocument *)maybeDoc;
-
- FSTAssert([doc.key isEqual:self.key], @"Can only patch a document with the same key");
-
- BOOL hasLocalMutations = (mutationResult == nil);
- NSArray<FSTFieldValue *> *transformResults =
- mutationResult ? mutationResult.transformResults
- : [self localTransformResultsWithWriteTime:localWriteTime];
- FSTObjectValue *newData = [self transformObject:doc.data transformResults:transformResults];
- return [FSTDocument documentWithData:newData
- key:doc.key
- version:doc.version
- hasLocalMutations:hasLocalMutations];
-}
-
-/**
- * Creates an array of "transform results" (a transform result is a field value representing the
- * result of applying a transform) for use when applying an FSTTransformMutation locally.
- *
- * @param localWriteTime The local time of the transform mutation (used to generate
- * FSTServerTimestampValues).
- * @return The transform results array.
- */
-- (NSArray<FSTFieldValue *> *)localTransformResultsWithWriteTime:(FSTTimestamp *)localWriteTime {
- NSMutableArray<FSTFieldValue *> *transformResults = [NSMutableArray array];
- for (FSTFieldTransform *fieldTransform in self.fieldTransforms) {
- if ([fieldTransform.transform isKindOfClass:[FSTServerTimestampTransform class]]) {
- [transformResults addObject:[FSTServerTimestampValue
- serverTimestampValueWithLocalWriteTime:localWriteTime]];
- } else {
- FSTFail(@"Encountered unknown transform: %@", fieldTransform);
- }
- }
- return transformResults;
-}
-
-- (FSTObjectValue *)transformObject:(FSTObjectValue *)objectValue
- transformResults:(NSArray<FSTFieldValue *> *)transformResults {
- FSTAssert(transformResults.count == self.fieldTransforms.count,
- @"Transform results length mismatch.");
-
- for (NSUInteger i = 0; i < self.fieldTransforms.count; i++) {
- FSTFieldTransform *fieldTransform = self.fieldTransforms[i];
- id<FSTTransformOperation> transform = fieldTransform.transform;
- FSTFieldPath *fieldPath = fieldTransform.path;
- if ([transform isKindOfClass:[FSTServerTimestampTransform class]]) {
- objectValue = [objectValue objectBySettingValue:transformResults[i] forPath:fieldPath];
- } else {
- FSTFail(@"Encountered unknown transform: %@", transform);
- }
- }
- return objectValue;
-}
-
-@end
-
-#pragma mark - FSTDeleteMutation
-
-@implementation FSTDeleteMutation
-
-- (BOOL)isEqual:(id)other {
- if (other == self) {
- return YES;
- }
- if (![other isKindOfClass:[FSTDeleteMutation class]]) {
- return NO;
- }
-
- FSTDeleteMutation *otherMutation = (FSTDeleteMutation *)other;
- return [self.key isEqual:otherMutation.key] &&
- [self.precondition isEqual:otherMutation.precondition];
-}
-
-- (NSUInteger)hash {
- NSUInteger result = [self.key hash];
- result = 31 * result + [self.precondition hash];
- return result;
-}
-
-- (NSString *)description {
- return [NSString
- stringWithFormat:@"<FSTDeleteMutation key=%@ precondition=%@>", self.key, self.precondition];
-}
-
-- (FSTMaybeDocument *_Nullable)applyTo:(FSTMaybeDocument *_Nullable)maybeDoc
- localWriteTime:(FSTTimestamp *)localWriteTime
- mutationResult:(FSTMutationResult *_Nullable)mutationResult {
- if (mutationResult) {
- FSTAssert(!mutationResult.transformResults,
- @"Transform results received by FSTDeleteMutation.");
- }
-
- if (![self.precondition isValidForDocument:maybeDoc]) {
- return maybeDoc;
- }
-
- if (maybeDoc) {
- FSTAssert([maybeDoc.key isEqual:self.key], @"Can only delete a document with the same key");
- }
-
- return [FSTDeletedDocument documentWithKey:self.key version:[FSTSnapshotVersion noVersion]];
-}
-
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/Model/FSTMutation.mm b/Firestore/Source/Model/FSTMutation.mm
new file mode 100644
index 0000000..99d2e51
--- /dev/null
+++ b/Firestore/Source/Model/FSTMutation.mm
@@ -0,0 +1,462 @@
+/*
+ * Copyright 2017 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 "Firestore/Source/Model/FSTMutation.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#import "FIRTimestamp.h"
+
+#import "Firestore/Source/Core/FSTSnapshotVersion.h"
+#import "Firestore/Source/Model/FSTDocument.h"
+#import "Firestore/Source/Model/FSTFieldValue.h"
+#import "Firestore/Source/Util/FSTAssert.h"
+#import "Firestore/Source/Util/FSTClasses.h"
+
+#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+#include "Firestore/core/src/firebase/firestore/model/field_mask.h"
+#include "Firestore/core/src/firebase/firestore/model/field_path.h"
+#include "Firestore/core/src/firebase/firestore/model/field_transform.h"
+#include "Firestore/core/src/firebase/firestore/model/precondition.h"
+#include "Firestore/core/src/firebase/firestore/model/transform_operations.h"
+
+using firebase::firestore::model::DocumentKey;
+using firebase::firestore::model::FieldMask;
+using firebase::firestore::model::FieldPath;
+using firebase::firestore::model::FieldTransform;
+using firebase::firestore::model::Precondition;
+using firebase::firestore::model::ServerTimestampTransform;
+using firebase::firestore::model::TransformOperation;
+
+NS_ASSUME_NONNULL_BEGIN
+
+#pragma mark - FSTMutationResult
+
+@implementation FSTMutationResult
+
+- (instancetype)initWithVersion:(FSTSnapshotVersion *_Nullable)version
+ transformResults:(NSArray<FSTFieldValue *> *_Nullable)transformResults {
+ if (self = [super init]) {
+ _version = version;
+ _transformResults = transformResults;
+ }
+ return self;
+}
+
+@end
+
+#pragma mark - FSTMutation
+
+@implementation FSTMutation {
+ DocumentKey _key;
+ Precondition _precondition;
+}
+
+- (instancetype)initWithKey:(DocumentKey)key precondition:(Precondition)precondition {
+ if (self = [super init]) {
+ _key = std::move(key);
+ _precondition = std::move(precondition);
+ }
+ return self;
+}
+
+- (nullable FSTMaybeDocument *)applyTo:(nullable FSTMaybeDocument *)maybeDoc
+ baseDocument:(nullable FSTMaybeDocument *)baseDoc
+ localWriteTime:(FIRTimestamp *)localWriteTime
+ mutationResult:(nullable FSTMutationResult *)mutationResult {
+ @throw FSTAbstractMethodException(); // NOLINT
+}
+
+- (nullable FSTMaybeDocument *)applyTo:(nullable FSTMaybeDocument *)maybeDoc
+ baseDocument:(nullable FSTMaybeDocument *)baseDoc
+ localWriteTime:(nullable FIRTimestamp *)localWriteTime {
+ return
+ [self applyTo:maybeDoc baseDocument:baseDoc localWriteTime:localWriteTime mutationResult:nil];
+}
+
+- (const DocumentKey &)key {
+ return _key;
+}
+
+- (const firebase::firestore::model::Precondition &)precondition {
+ return _precondition;
+}
+
+@end
+
+#pragma mark - FSTSetMutation
+
+@implementation FSTSetMutation
+
+- (instancetype)initWithKey:(DocumentKey)key
+ value:(FSTObjectValue *)value
+ precondition:(Precondition)precondition {
+ if (self = [super initWithKey:std::move(key) precondition:std::move(precondition)]) {
+ _value = value;
+ }
+ return self;
+}
+
+- (NSString *)description {
+ return [NSString stringWithFormat:@"<FSTSetMutation key=%s value=%@ precondition=%@>",
+ self.key.ToString().c_str(), self.value,
+ self.precondition.description()];
+}
+
+- (BOOL)isEqual:(id)other {
+ if (other == self) {
+ return YES;
+ }
+ if (![other isKindOfClass:[FSTSetMutation class]]) {
+ return NO;
+ }
+
+ FSTSetMutation *otherMutation = (FSTSetMutation *)other;
+ return [self.key isEqual:otherMutation.key] && [self.value isEqual:otherMutation.value] &&
+ self.precondition == otherMutation.precondition;
+}
+
+- (NSUInteger)hash {
+ NSUInteger result = [self.key hash];
+ result = 31 * result + self.precondition.Hash();
+ result = 31 * result + [self.value hash];
+ return result;
+}
+
+- (nullable FSTMaybeDocument *)applyTo:(nullable FSTMaybeDocument *)maybeDoc
+ baseDocument:(nullable FSTMaybeDocument *)baseDoc
+ localWriteTime:(FIRTimestamp *)localWriteTime
+ mutationResult:(nullable FSTMutationResult *)mutationResult {
+ if (mutationResult) {
+ FSTAssert(!mutationResult.transformResults, @"Transform results received by FSTSetMutation.");
+ }
+
+ if (!self.precondition.IsValidFor(maybeDoc)) {
+ return maybeDoc;
+ }
+
+ BOOL hasLocalMutations = (mutationResult == nil);
+ if (!maybeDoc || [maybeDoc isMemberOfClass:[FSTDeletedDocument class]]) {
+ // If the document didn't exist before, create it.
+ return [FSTDocument documentWithData:self.value
+ key:self.key
+ version:[FSTSnapshotVersion noVersion]
+ hasLocalMutations:hasLocalMutations];
+ }
+
+ FSTAssert([maybeDoc isMemberOfClass:[FSTDocument class]], @"Unknown MaybeDocument type %@",
+ [maybeDoc class]);
+ FSTDocument *doc = (FSTDocument *)maybeDoc;
+
+ FSTAssert([doc.key isEqual:self.key], @"Can only set a document with the same key");
+ return [FSTDocument documentWithData:self.value
+ key:doc.key
+ version:doc.version
+ hasLocalMutations:hasLocalMutations];
+}
+@end
+
+#pragma mark - FSTPatchMutation
+
+@implementation FSTPatchMutation {
+ FieldMask _fieldMask;
+}
+
+- (instancetype)initWithKey:(DocumentKey)key
+ fieldMask:(FieldMask)fieldMask
+ value:(FSTObjectValue *)value
+ precondition:(Precondition)precondition {
+ self = [super initWithKey:std::move(key) precondition:std::move(precondition)];
+ if (self) {
+ _fieldMask = std::move(fieldMask);
+ _value = value;
+ }
+ return self;
+}
+
+- (const firebase::firestore::model::FieldMask &)fieldMask {
+ return _fieldMask;
+}
+
+- (BOOL)isEqual:(id)other {
+ if (other == self) {
+ return YES;
+ }
+ if (![other isKindOfClass:[FSTPatchMutation class]]) {
+ return NO;
+ }
+
+ FSTPatchMutation *otherMutation = (FSTPatchMutation *)other;
+ return [self.key isEqual:otherMutation.key] && self.fieldMask == otherMutation.fieldMask &&
+ [self.value isEqual:otherMutation.value] &&
+ self.precondition == otherMutation.precondition;
+}
+
+- (NSUInteger)hash {
+ NSUInteger result = [self.key hash];
+ result = 31 * result + self.precondition.Hash();
+ result = 31 * result + self.fieldMask.Hash();
+ result = 31 * result + [self.value hash];
+ return result;
+}
+
+- (NSString *)description {
+ return [NSString stringWithFormat:@"<FSTPatchMutation key=%s mask=%s value=%@ precondition=%@>",
+ self.key.ToString().c_str(), self.fieldMask.ToString().c_str(),
+ self.value, self.precondition.description()];
+}
+
+- (nullable FSTMaybeDocument *)applyTo:(nullable FSTMaybeDocument *)maybeDoc
+ baseDocument:(nullable FSTMaybeDocument *)baseDoc
+ localWriteTime:(FIRTimestamp *)localWriteTime
+ mutationResult:(nullable FSTMutationResult *)mutationResult {
+ if (mutationResult) {
+ FSTAssert(!mutationResult.transformResults, @"Transform results received by FSTPatchMutation.");
+ }
+
+ if (!self.precondition.IsValidFor(maybeDoc)) {
+ return maybeDoc;
+ }
+
+ BOOL hasLocalMutations = (mutationResult == nil);
+ if (!maybeDoc || [maybeDoc isMemberOfClass:[FSTDeletedDocument class]]) {
+ // Precondition applied, so create the document if necessary
+ const DocumentKey &key = maybeDoc ? maybeDoc.key : self.key;
+ FSTSnapshotVersion *version = maybeDoc ? maybeDoc.version : [FSTSnapshotVersion noVersion];
+ maybeDoc = [FSTDocument documentWithData:[FSTObjectValue objectValue]
+ key:key
+ version:version
+ hasLocalMutations:hasLocalMutations];
+ }
+
+ FSTAssert([maybeDoc isMemberOfClass:[FSTDocument class]], @"Unknown MaybeDocument type %@",
+ [maybeDoc class]);
+ FSTDocument *doc = (FSTDocument *)maybeDoc;
+
+ FSTAssert([doc.key isEqual:self.key], @"Can only patch a document with the same key");
+
+ FSTObjectValue *newData = [self patchObjectValue:doc.data];
+ return [FSTDocument documentWithData:newData
+ key:doc.key
+ version:doc.version
+ hasLocalMutations:hasLocalMutations];
+}
+
+- (FSTObjectValue *)patchObjectValue:(FSTObjectValue *)objectValue {
+ FSTObjectValue *result = objectValue;
+ for (const FieldPath &fieldPath : self.fieldMask) {
+ FSTFieldValue *newValue = [self.value valueForPath:fieldPath];
+ if (newValue) {
+ result = [result objectBySettingValue:newValue forPath:fieldPath];
+ } else {
+ result = [result objectByDeletingPath:fieldPath];
+ }
+ }
+ return result;
+}
+
+@end
+
+@implementation FSTTransformMutation {
+ /** The field transforms to use when transforming the document. */
+ std::vector<FieldTransform> _fieldTransforms;
+}
+
+- (instancetype)initWithKey:(DocumentKey)key
+ fieldTransforms:(std::vector<FieldTransform>)fieldTransforms {
+ // NOTE: We set a precondition of exists: true as a safety-check, since we always combine
+ // FSTTransformMutations with a FSTSetMutation or FSTPatchMutation which (if successful) should
+ // end up with an existing document.
+ if (self = [super initWithKey:std::move(key) precondition:Precondition::Exists(true)]) {
+ _fieldTransforms = std::move(fieldTransforms);
+ }
+ return self;
+}
+
+- (const std::vector<FieldTransform> &)fieldTransforms {
+ return _fieldTransforms;
+}
+
+- (BOOL)isEqual:(id)other {
+ if (other == self) {
+ return YES;
+ }
+ if (![other isKindOfClass:[FSTTransformMutation class]]) {
+ return NO;
+ }
+
+ FSTTransformMutation *otherMutation = (FSTTransformMutation *)other;
+ return [self.key isEqual:otherMutation.key] &&
+ self.fieldTransforms == otherMutation.fieldTransforms &&
+ self.precondition == otherMutation.precondition;
+}
+
+- (NSUInteger)hash {
+ NSUInteger result = [self.key hash];
+ result = 31 * result + self.precondition.Hash();
+ for (const auto &transform : self.fieldTransforms) {
+ result = 31 * result + transform.Hash();
+ }
+ return result;
+}
+
+- (NSString *)description {
+ std::string fieldTransforms;
+ for (const auto &transform : self.fieldTransforms) {
+ fieldTransforms += " " + transform.path().CanonicalString();
+ }
+ return [NSString stringWithFormat:@"<FSTTransformMutation key=%s transforms=%s precondition=%@>",
+ self.key.ToString().c_str(), fieldTransforms.c_str(),
+ self.precondition.description()];
+}
+
+- (nullable FSTMaybeDocument *)applyTo:(nullable FSTMaybeDocument *)maybeDoc
+ baseDocument:(nullable FSTMaybeDocument *)baseDoc
+ localWriteTime:(FIRTimestamp *)localWriteTime
+ mutationResult:(nullable FSTMutationResult *)mutationResult {
+ if (mutationResult) {
+ FSTAssert(mutationResult.transformResults,
+ @"Transform results missing for FSTTransformMutation.");
+ }
+
+ if (!self.precondition.IsValidFor(maybeDoc)) {
+ return maybeDoc;
+ }
+
+ // We only support transforms with precondition exists, so we can only apply it to an existing
+ // document
+ FSTAssert([maybeDoc isMemberOfClass:[FSTDocument class]], @"Unknown MaybeDocument type %@",
+ [maybeDoc class]);
+ FSTDocument *doc = (FSTDocument *)maybeDoc;
+
+ FSTAssert([doc.key isEqual:self.key], @"Can only patch a document with the same key");
+
+ BOOL hasLocalMutations = (mutationResult == nil);
+ NSArray<FSTFieldValue *> *transformResults =
+ mutationResult
+ ? mutationResult.transformResults
+ : [self localTransformResultsWithBaseDocument:baseDoc writeTime:localWriteTime];
+ FSTObjectValue *newData = [self transformObject:doc.data transformResults:transformResults];
+ return [FSTDocument documentWithData:newData
+ key:doc.key
+ version:doc.version
+ hasLocalMutations:hasLocalMutations];
+}
+
+/**
+ * Creates an array of "transform results" (a transform result is a field value representing the
+ * result of applying a transform) for use when applying an FSTTransformMutation locally.
+ *
+ * @param baseDocument The document prior to applying this mutation batch.
+ * @param localWriteTime The local time of the transform mutation (used to generate
+ * FSTServerTimestampValues).
+ * @return The transform results array.
+ */
+- (NSArray<FSTFieldValue *> *)localTransformResultsWithBaseDocument:
+ (FSTMaybeDocument *_Nullable)baseDocument
+ writeTime:(FIRTimestamp *)localWriteTime {
+ NSMutableArray<FSTFieldValue *> *transformResults = [NSMutableArray array];
+ for (const FieldTransform &fieldTransform : self.fieldTransforms) {
+ if (fieldTransform.transformation().type() == TransformOperation::Type::ServerTimestamp) {
+ FSTFieldValue *previousValue = nil;
+
+ if ([baseDocument isMemberOfClass:[FSTDocument class]]) {
+ previousValue = [((FSTDocument *)baseDocument) fieldForPath:fieldTransform.path()];
+ }
+
+ [transformResults
+ addObject:[FSTServerTimestampValue serverTimestampValueWithLocalWriteTime:localWriteTime
+ previousValue:previousValue]];
+ } else {
+ FSTFail(@"Encountered unknown transform: %d type", fieldTransform.transformation().type());
+ }
+ }
+ return transformResults;
+}
+
+- (FSTObjectValue *)transformObject:(FSTObjectValue *)objectValue
+ transformResults:(NSArray<FSTFieldValue *> *)transformResults {
+ FSTAssert(transformResults.count == self.fieldTransforms.size(),
+ @"Transform results length mismatch.");
+
+ for (size_t i = 0; i < self.fieldTransforms.size(); i++) {
+ const FieldTransform &fieldTransform = self.fieldTransforms[i];
+ const TransformOperation &transform = fieldTransform.transformation();
+ const FieldPath &fieldPath = fieldTransform.path();
+ if (transform.type() == TransformOperation::Type::ServerTimestamp) {
+ objectValue = [objectValue objectBySettingValue:transformResults[i] forPath:fieldPath];
+ } else {
+ FSTFail(@"Encountered unknown transform: %d type", transform.type());
+ }
+ }
+ return objectValue;
+}
+
+@end
+
+#pragma mark - FSTDeleteMutation
+
+@implementation FSTDeleteMutation
+
+- (BOOL)isEqual:(id)other {
+ if (other == self) {
+ return YES;
+ }
+ if (![other isKindOfClass:[FSTDeleteMutation class]]) {
+ return NO;
+ }
+
+ FSTDeleteMutation *otherMutation = (FSTDeleteMutation *)other;
+ return [self.key isEqual:otherMutation.key] && self.precondition == otherMutation.precondition;
+}
+
+- (NSUInteger)hash {
+ NSUInteger result = [self.key hash];
+ result = 31 * result + self.precondition.Hash();
+ return result;
+}
+
+- (NSString *)description {
+ return [NSString stringWithFormat:@"<FSTDeleteMutation key=%s precondition=%@>",
+ self.key.ToString().c_str(), self.precondition.description()];
+}
+
+- (nullable FSTMaybeDocument *)applyTo:(nullable FSTMaybeDocument *)maybeDoc
+ baseDocument:(nullable FSTMaybeDocument *)baseDoc
+ localWriteTime:(FIRTimestamp *)localWriteTime
+ mutationResult:(nullable FSTMutationResult *)mutationResult {
+ if (mutationResult) {
+ FSTAssert(!mutationResult.transformResults,
+ @"Transform results received by FSTDeleteMutation.");
+ }
+
+ if (!self.precondition.IsValidFor(maybeDoc)) {
+ return maybeDoc;
+ }
+
+ if (maybeDoc) {
+ FSTAssert([maybeDoc.key isEqual:self.key], @"Can only delete a document with the same key");
+ }
+
+ return [FSTDeletedDocument documentWithKey:self.key version:[FSTSnapshotVersion noVersion]];
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/Model/FSTMutationBatch.h b/Firestore/Source/Model/FSTMutationBatch.h
index 145adfa..3c82338 100644
--- a/Firestore/Source/Model/FSTMutationBatch.h
+++ b/Firestore/Source/Model/FSTMutationBatch.h
@@ -20,8 +20,10 @@
#import "Firestore/Source/Model/FSTDocumentKeySet.h"
#import "Firestore/Source/Model/FSTDocumentVersionDictionary.h"
+#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+
@class FSTMutation;
-@class FSTTimestamp;
+@class FIRTimestamp;
@class FSTMutationResult;
@class FSTMutationBatchResult;
@class FSTSnapshotVersion;
@@ -45,7 +47,7 @@ extern const FSTBatchID kFSTBatchIDUnknown;
/** Initializes a mutation batch with the given batchID, localWriteTime, and mutations. */
- (instancetype)initWithBatchID:(FSTBatchID)batchID
- localWriteTime:(FSTTimestamp *)localWriteTime
+ localWriteTime:(FIRTimestamp *)localWriteTime
mutations:(NSArray<FSTMutation *> *)mutations NS_DESIGNATED_INITIALIZER;
- (id)init NS_UNAVAILABLE;
@@ -60,7 +62,7 @@ extern const FSTBatchID kFSTBatchIDUnknown;
* their hasLocalMutations flag set.
*/
- (FSTMaybeDocument *_Nullable)applyTo:(FSTMaybeDocument *_Nullable)maybeDoc
- documentKey:(FSTDocumentKey *)documentKey
+ documentKey:(const firebase::firestore::model::DocumentKey &)documentKey
mutationBatchResult:(FSTMutationBatchResult *_Nullable)mutationBatchResult;
/**
@@ -68,7 +70,7 @@ extern const FSTBatchID kFSTBatchIDUnknown;
* the backend).
*/
- (FSTMaybeDocument *_Nullable)applyTo:(FSTMaybeDocument *_Nullable)maybeDoc
- documentKey:(FSTDocumentKey *)documentKey;
+ documentKey:(const firebase::firestore::model::DocumentKey &)documentKey;
/**
* Returns YES if this mutation batch has already been removed from the mutation queue.
@@ -86,7 +88,7 @@ extern const FSTBatchID kFSTBatchIDUnknown;
- (FSTDocumentKeySet *)keys;
@property(nonatomic, assign, readonly) FSTBatchID batchID;
-@property(nonatomic, strong, readonly) FSTTimestamp *localWriteTime;
+@property(nonatomic, strong, readonly) FIRTimestamp *localWriteTime;
@property(nonatomic, strong, readonly) NSArray<FSTMutation *> *mutations;
@end
diff --git a/Firestore/Source/Model/FSTMutationBatch.m b/Firestore/Source/Model/FSTMutationBatch.mm
index 3677908..e62a72c 100644
--- a/Firestore/Source/Model/FSTMutationBatch.m
+++ b/Firestore/Source/Model/FSTMutationBatch.mm
@@ -16,13 +16,17 @@
#import "Firestore/Source/Model/FSTMutationBatch.h"
+#import "FIRTimestamp.h"
+
#import "Firestore/Source/Core/FSTSnapshotVersion.h"
-#import "Firestore/Source/Core/FSTTimestamp.h"
#import "Firestore/Source/Model/FSTDocument.h"
-#import "Firestore/Source/Model/FSTDocumentKey.h"
#import "Firestore/Source/Model/FSTMutation.h"
#import "Firestore/Source/Util/FSTAssert.h"
+#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+
+using firebase::firestore::model::DocumentKey;
+
NS_ASSUME_NONNULL_BEGIN
const FSTBatchID kFSTBatchIDUnknown = -1;
@@ -30,7 +34,7 @@ const FSTBatchID kFSTBatchIDUnknown = -1;
@implementation FSTMutationBatch
- (instancetype)initWithBatchID:(FSTBatchID)batchID
- localWriteTime:(FSTTimestamp *)localWriteTime
+ localWriteTime:(FIRTimestamp *)localWriteTime
mutations:(NSArray<FSTMutation *> *)mutations {
self = [super init];
if (self) {
@@ -67,10 +71,12 @@ const FSTBatchID kFSTBatchIDUnknown = -1;
}
- (FSTMaybeDocument *_Nullable)applyTo:(FSTMaybeDocument *_Nullable)maybeDoc
- documentKey:(FSTDocumentKey *)documentKey
+ documentKey:(const DocumentKey &)documentKey
mutationBatchResult:(FSTMutationBatchResult *_Nullable)mutationBatchResult {
FSTAssert(!maybeDoc || [maybeDoc.key isEqualToKey:documentKey],
- @"applyTo: key %@ doesn't match maybeDoc key %@", documentKey, maybeDoc.key);
+ @"applyTo: key %s doesn't match maybeDoc key %s", documentKey.ToString().c_str(),
+ maybeDoc.key.ToString().c_str());
+ FSTMaybeDocument *baseDoc = maybeDoc;
if (mutationBatchResult) {
FSTAssert(mutationBatchResult.mutationResults.count == self.mutations.count,
@"Mismatch between mutations length (%lu) and results length (%lu)",
@@ -83,6 +89,7 @@ const FSTBatchID kFSTBatchIDUnknown = -1;
FSTMutationResult *_Nullable mutationResult = mutationBatchResult.mutationResults[i];
if ([mutation.key isEqualToKey:documentKey]) {
maybeDoc = [mutation applyTo:maybeDoc
+ baseDocument:baseDoc
localWriteTime:self.localWriteTime
mutationResult:mutationResult];
}
@@ -91,7 +98,7 @@ const FSTBatchID kFSTBatchIDUnknown = -1;
}
- (FSTMaybeDocument *_Nullable)applyTo:(FSTMaybeDocument *_Nullable)maybeDoc
- documentKey:(FSTDocumentKey *)documentKey {
+ documentKey:(const DocumentKey &)documentKey {
return [self applyTo:maybeDoc documentKey:documentKey mutationBatchResult:nil];
}
diff --git a/Firestore/Source/Model/FSTPath.h b/Firestore/Source/Model/FSTPath.h
deleted file mode 100644
index 1f63f17..0000000
--- a/Firestore/Source/Model/FSTPath.h
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * Copyright 2017 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/Foundation.h>
-
-NS_ASSUME_NONNULL_BEGIN
-
-/**
- * FSTPath represents a path sequence in the Firestore database. It is composed of an ordered
- * sequence of string segments.
- *
- * ## Subclassing Notes
- *
- * FSTPath itself is an abstract class that must be specialized by subclasses. Subclasses should
- * implement constructors for common string-based representations of the path and also override
- * -canonicalString which converts back to the canonical string-based representation of the path.
- */
-@interface FSTPath <SelfType> : NSObject
-
-/** Returns the path segment of the given index. */
-- (NSString *)segmentAtIndex:(int)index;
-- (id)objectAtIndexedSubscript:(int)index;
-
-- (BOOL)isEqual:(id)path;
-- (NSComparisonResult)compare:(SelfType)other;
-
-/**
- * Returns a new path whose segments are the current path's plus one more.
- *
- * @param segment The new segment to concatenate to the path.
- * @return A new path with this path's segment plus the new one.
- */
-- (instancetype)pathByAppendingSegment:(NSString *)segment;
-
-/**
- * Returns a new path whose segments are the current path's plus another's.
- *
- * @param path The new path whose segments should be concatenated to the path.
- * @return A new path with this path's segment plus the new ones.
- */
-- (instancetype)pathByAppendingPath:(SelfType)path;
-
-/** Returns a new path whose segments are the same as this one's minus the first one. */
-- (instancetype)pathByRemovingFirstSegment;
-
-/** Returns a new path whose segments are the same as this one's minus the first `count`. */
-- (instancetype)pathByRemovingFirstSegments:(int)count;
-
-/** Returns a new path whose segments are the same as this one's minus the last one. */
-- (instancetype)pathByRemovingLastSegment;
-
-/** Convenience method for getting the first segment of this path. */
-- (NSString *)firstSegment;
-
-/** Convenience method for getting the last segment of this path. */
-- (NSString *)lastSegment;
-
-/** Returns true if this path is a prefix of the given path. */
-- (BOOL)isPrefixOfPath:(SelfType)other;
-
-/** Returns a standardized string representation of this path. */
-- (NSString *)canonicalString;
-
-/** The number of segments in the path. */
-@property(nonatomic, readonly) int length;
-
-/** True if the path is empty. */
-@property(nonatomic, readonly, getter=isEmpty) BOOL empty;
-
-@end
-
-/** A dot-separated path for navigating sub-objects within a document. */
-@class FSTFieldPath;
-
-@interface FSTFieldPath : FSTPath <FSTFieldPath *>
-
-/**
- * Creates and returns a new path with the given segments. The array of segments is not copied, so
- * one should not mutate the array once it is passed in here.
- *
- * @param segments The underlying array of segments for the path.
- * @return A new instance of FSTPath.
- */
-+ (instancetype)pathWithSegments:(NSArray<NSString *> *)segments;
-
-/**
- * Creates and returns a new path from the server formatted field-path string, where path segments
- * are separated by a dot "." and optionally encoded using backticks.
- *
- * @param fieldPath A dot-separated string representing the path.
- */
-+ (instancetype)pathWithServerFormat:(NSString *)fieldPath;
-
-/** Returns a field path that represents a document key. */
-+ (instancetype)keyFieldPath;
-
-/** Returns a field path that represents an empty path. */
-+ (instancetype)emptyPath;
-
-/** Returns YES if this is the `FSTFieldPath.keyFieldPath` field path. */
-- (BOOL)isKeyFieldPath;
-
-@end
-
-/** A slash-separated path for navigating resources (documents and collections) within Firestore. */
-@class FSTResourcePath;
-
-@interface FSTResourcePath : FSTPath <FSTResourcePath *>
-
-/**
- * Creates and returns a new path with the given segments. The array of segments is not copied, so
- * one should not mutate the array once it is passed in here.
- *
- * @param segments The underlying array of segments for the path.
- * @return A new instance of FSTPath.
- */
-+ (instancetype)pathWithSegments:(NSArray<NSString *> *)segments;
-
-/**
- * Creates and returns a new path from the given resource-path string, where the path segments are
- * separated by a slash "/".
- *
- * @param resourcePath A slash-separated string representing the path.
- */
-+ (instancetype)pathWithString:(NSString *)resourcePath;
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/Model/FSTPath.m b/Firestore/Source/Model/FSTPath.m
deleted file mode 100644
index b236107..0000000
--- a/Firestore/Source/Model/FSTPath.m
+++ /dev/null
@@ -1,356 +0,0 @@
-/*
- * Copyright 2017 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 "Firestore/Source/Model/FSTPath.h"
-
-#import "Firestore/Source/Model/FSTDocumentKey.h"
-#import "Firestore/Source/Util/FSTAssert.h"
-#import "Firestore/Source/Util/FSTClasses.h"
-#import "Firestore/Source/Util/FSTUsageValidation.h"
-
-NS_ASSUME_NONNULL_BEGIN
-
-@interface FSTPath ()
-/** An underlying array of which a subset of elements are the segments of the path. */
-@property(strong, nonatomic) NSArray<NSString *> *segments;
-/** The index into the segments array of the first segment in this path. */
-@property int offset;
-@end
-
-@implementation FSTPath
-
-/**
- * Designated initializer.
- *
- * @param segments The underlying array of segments for the path.
- * @param offset The starting index in the underlying array for the subarray to use.
- * @param length The length of the subarray to use.
- */
-- (instancetype)initWithSegments:(NSArray<NSString *> *)segments
- offset:(int)offset
- length:(int)length {
- FSTAssert(offset <= segments.count, @"offset %d out of range %d", offset, (int)segments.count);
- FSTAssert(length <= segments.count - offset, @"offset %d out of range %d", offset,
- (int)segments.count - offset);
-
- if (self = [super init]) {
- _segments = segments;
- _offset = offset;
- _length = length;
- }
- return self;
-}
-
-- (BOOL)isEqual:(id)object {
- if (self == object) {
- return YES;
- }
- if (![object isKindOfClass:[FSTPath class]]) {
- return NO;
- }
- FSTPath *path = object;
- return [self compare:path] == NSOrderedSame;
-}
-
-- (NSUInteger)hash {
- NSUInteger hash = 0;
- for (int i = 0; i < self.length; ++i) {
- hash += [self segmentAtIndex:i].hash;
- }
- return hash;
-}
-
-- (NSString *)description {
- return [self canonicalString];
-}
-
-- (id)objectAtIndexedSubscript:(int)index {
- return [self segmentAtIndex:index];
-}
-
-- (NSString *)segmentAtIndex:(int)index {
- FSTAssert(index < self.length, @"index %d out of range", index);
- return self.segments[self.offset + index];
-}
-
-- (NSString *)firstSegment {
- FSTAssert(!self.isEmpty, @"Cannot call firstSegment on empty path");
- return [self segmentAtIndex:0];
-}
-
-- (NSString *)lastSegment {
- FSTAssert(!self.isEmpty, @"Cannot call lastSegment on empty path");
- return [self segmentAtIndex:self.length - 1];
-}
-
-- (NSComparisonResult)compare:(FSTPath *)other {
- int length = MIN(self.length, other.length);
- for (int i = 0; i < length; ++i) {
- NSString *left = [self segmentAtIndex:i];
- NSString *right = [other segmentAtIndex:i];
- NSComparisonResult result = [left compare:right];
- if (result != NSOrderedSame) {
- return result;
- }
- }
- if (self.length < other.length) {
- return NSOrderedAscending;
- }
- if (self.length > other.length) {
- return NSOrderedDescending;
- }
- return NSOrderedSame;
-}
-
-- (instancetype)pathWithSegments:(NSArray<NSString *> *)segments
- offset:(int)offset
- length:(int)length {
- return [[[self class] alloc] initWithSegments:segments offset:offset length:length];
-}
-
-- (instancetype)pathByAppendingSegment:(NSString *)segment {
- int newLength = self.length + 1;
- NSMutableArray<NSString *> *segments = [NSMutableArray arrayWithCapacity:newLength];
- for (int i = 0; i < self.length; ++i) {
- [segments addObject:self[i]];
- }
- [segments addObject:segment];
- return [self pathWithSegments:segments offset:0 length:newLength];
-}
-
-- (instancetype)pathByAppendingPath:(FSTPath *)path {
- int newLength = self.length + path.length;
- NSMutableArray<NSString *> *segments = [NSMutableArray arrayWithCapacity:newLength];
- for (int i = 0; i < self.length; ++i) {
- [segments addObject:self[i]];
- }
- for (int i = 0; i < path.length; ++i) {
- [segments addObject:path[i]];
- }
- return [self pathWithSegments:segments offset:0 length:newLength];
-}
-
-- (BOOL)isEmpty {
- return self.length == 0;
-}
-
-- (instancetype)pathByRemovingFirstSegment {
- FSTAssert(!self.isEmpty, @"Cannot call pathByRemovingFirstSegment on empty path");
- return [self pathWithSegments:self.segments offset:self.offset + 1 length:self.length - 1];
-}
-
-- (instancetype)pathByRemovingFirstSegments:(int)count {
- FSTAssert(self.length >= count, @"pathByRemovingFirstSegments:%d on path of length %d", count,
- self.length);
- return
- [self pathWithSegments:self.segments offset:self.offset + count length:self.length - count];
-}
-
-- (instancetype)pathByRemovingLastSegment {
- FSTAssert(!self.isEmpty, @"Cannot call pathByRemovingLastSegment on empty path");
- return [self pathWithSegments:self.segments offset:self.offset length:self.length - 1];
-}
-
-- (BOOL)isPrefixOfPath:(FSTPath *)other {
- if (other.length < self.length) {
- return NO;
- }
- for (int i = 0; i < self.length; ++i) {
- if (![self[i] isEqual:other[i]]) {
- return NO;
- }
- }
- return YES;
-}
-
-/** Returns a standardized string representation of this path. */
-- (NSString *)canonicalString {
- @throw FSTAbstractMethodException(); // NOLINT
-}
-@end
-
-@implementation FSTFieldPath
-+ (instancetype)pathWithSegments:(NSArray<NSString *> *)segments {
- return [[FSTFieldPath alloc] initWithSegments:segments offset:0 length:(int)segments.count];
-}
-
-+ (instancetype)pathWithServerFormat:(NSString *)fieldPath {
- NSMutableArray<NSString *> *segments = [NSMutableArray array];
-
- // TODO(b/37244157): Once we move to v1beta1, we should make this more strict. Right now, it
- // allows non-identifier path components, even if they aren't escaped. Technically, this will
- // mangle paths with backticks in them used in v1alpha1, but that's fine.
-
- const char *source = [fieldPath UTF8String];
- char *segment = (char *)malloc(strlen(source) + 1);
- char *segmentEnd = segment;
-
- // If we're inside '`' backticks, then we should ignore '.' dots.
- BOOL inBackticks = NO;
-
- char c;
- do {
- // Examine current character. This is legit even on zero-length strings because there's always
- // a null terminator.
- c = *source++;
- switch (c) {
- case '\0': // Falls through
- case '.':
- if (!inBackticks) {
- // Segment is complete
- *segmentEnd = '\0';
- if (segment == segmentEnd) {
- FSTThrowInvalidArgument(
- @"Invalid field path (%@). Paths must not be empty, begin with "
- @"'.', end with '.', or contain '..'",
- fieldPath);
- }
-
- [segments addObject:[NSString stringWithUTF8String:segment]];
- segmentEnd = segment;
- } else {
- // copy into the current segment
- *segmentEnd++ = c;
- }
- break;
-
- case '`':
- if (inBackticks) {
- inBackticks = NO;
- } else {
- inBackticks = YES;
- }
- break;
-
- case '\\':
- // advance to escaped character
- c = *source++;
- // TODO(b/37244157): Make this a user-facing exception once we finalize field escaping.
- FSTAssert(c != '\0', @"Trailing escape characters not allowed in %@", fieldPath);
- // Fall through
-
- default:
- // copy into the current segment
- *segmentEnd++ = c;
- break;
- }
- } while (c);
-
- FSTAssert(!inBackticks, @"Unterminated ` in path %@", fieldPath);
-
- free(segment);
- return [FSTFieldPath pathWithSegments:segments];
-}
-
-+ (instancetype)keyFieldPath {
- static FSTFieldPath *keyFieldPath;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- keyFieldPath = [FSTFieldPath pathWithSegments:@[ kDocumentKeyPath ]];
- });
- return keyFieldPath;
-}
-
-+ (instancetype)emptyPath {
- static FSTFieldPath *emptyPath;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- emptyPath = [FSTFieldPath pathWithSegments:@[]];
- });
- return emptyPath;
-}
-
-/** Return YES if the string could be used as a segment in a field path without escaping. */
-+ (BOOL)isValidIdentifier:(NSString *)segment {
- if (segment.length == 0) {
- return NO;
- }
- unichar first = [segment characterAtIndex:0];
- if (first != '_' && (first < 'a' || first > 'z') && (first < 'A' || first > 'Z')) {
- return NO;
- }
- for (int i = 1; i < segment.length; i++) {
- unichar c = [segment characterAtIndex:i];
- if (c != '_' && (c < 'a' || c > 'z') && (c < 'A' || c > 'Z') && (c < '0' || c > '9')) {
- return NO;
- }
- }
- return YES;
-}
-
-- (BOOL)isKeyFieldPath {
- return [self isEqual:FSTFieldPath.keyFieldPath];
-}
-
-- (NSString *)canonicalString {
- NSMutableString *result = [NSMutableString string];
- for (int i = 0; i < self.length; i++) {
- if (i > 0) {
- [result appendString:@"."];
- }
-
- NSString *escaped = [self[i] stringByReplacingOccurrencesOfString:@"\\" withString:@"\\\\"];
- escaped = [escaped stringByReplacingOccurrencesOfString:@"`" withString:@"\\`"];
- if (![FSTFieldPath isValidIdentifier:escaped]) {
- escaped = [NSString stringWithFormat:@"`%@`", escaped];
- }
-
- [result appendString:escaped];
- }
- return result;
-}
-
-@end
-
-@implementation FSTResourcePath
-+ (instancetype)pathWithSegments:(NSArray<NSString *> *)segments {
- return [[FSTResourcePath alloc] initWithSegments:segments offset:0 length:(int)segments.count];
-}
-
-+ (instancetype)pathWithString:(NSString *)resourcePath {
- // NOTE: The client is ignorant of any path segments containing escape sequences (e.g. __id123__)
- // and just passes them through raw (they exist for legacy reasons and should not be used
- // frequently).
-
- if ([resourcePath rangeOfString:@"//"].location != NSNotFound) {
- FSTThrowInvalidArgument(@"Invalid path (%@). Paths must not contain // in them.", resourcePath);
- }
-
- NSMutableArray *segments = [[resourcePath componentsSeparatedByString:@"/"] mutableCopy];
- // We may still have an empty segment at the beginning or end if they had a leading or trailing
- // slash (which we allow).
- [segments removeObject:@""];
-
- return [self pathWithSegments:segments];
-}
-
-- (NSString *)canonicalString {
- // NOTE: The client is ignorant of any path segments containing escape sequences (e.g. __id123__)
- // and just passes them through raw (they exist for legacy reasons and should not be used
- // frequently).
-
- NSMutableString *result = [NSMutableString string];
- for (int i = 0; i < self.length; i++) {
- if (i > 0) {
- [result appendString:@"/"];
- }
- [result appendString:self[i]];
- }
- return result;
-}
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/Public/FIRDocumentChange.h b/Firestore/Source/Public/FIRDocumentChange.h
index 022c81b..4717067 100644
--- a/Firestore/Source/Public/FIRDocumentChange.h
+++ b/Firestore/Source/Public/FIRDocumentChange.h
@@ -18,7 +18,7 @@
NS_ASSUME_NONNULL_BEGIN
-@class FIRDocumentSnapshot;
+@class FIRQueryDocumentSnapshot;
/** An enumeration of document change types. */
typedef NS_ENUM(NSInteger, FIRDocumentChangeType) {
@@ -47,7 +47,7 @@ NS_SWIFT_NAME(DocumentChange)
@property(nonatomic, readonly) FIRDocumentChangeType type;
/** The document affected by this change. */
-@property(nonatomic, strong, readonly) FIRDocumentSnapshot *document;
+@property(nonatomic, strong, readonly) FIRQueryDocumentSnapshot *document;
/**
* The index of the changed document in the result set immediately prior to this FIRDocumentChange
diff --git a/Firestore/Source/Public/FIRDocumentReference.h b/Firestore/Source/Public/FIRDocumentReference.h
index 439e727..e7ba6eb 100644
--- a/Firestore/Source/Public/FIRDocumentReference.h
+++ b/Firestore/Source/Public/FIRDocumentReference.h
@@ -18,38 +18,12 @@
#import "FIRListenerRegistration.h"
-@class FIRFirestore;
@class FIRCollectionReference;
@class FIRDocumentSnapshot;
-@class FIRSetOptions;
+@class FIRFirestore;
NS_ASSUME_NONNULL_BEGIN
-/**
- * Options for use with `[FIRDocumentReference addSnapshotListener]` to control the behavior of the
- * snapshot listener.
- */
-NS_SWIFT_NAME(DocumentListenOptions)
-@interface FIRDocumentListenOptions : NSObject
-
-+ (instancetype)options NS_SWIFT_UNAVAILABLE("Use initializer");
-
-- (instancetype)init;
-
-@property(nonatomic, assign, readonly) BOOL includeMetadataChanges;
-
-/**
- * Sets the includeMetadataChanges option which controls whether metadata-only changes (i.e. only
- * `FIRDocumentSnapshot.metadata` changed) should trigger snapshot events. Default is NO.
- *
- * @param includeMetadataChanges Whether to raise events for metadata-only changes.
- * @return The receiver is returned for optional method chaining.
- */
-- (instancetype)includeMetadataChanges:(BOOL)includeMetadataChanges
- NS_SWIFT_NAME(includeMetadataChanges(_:));
-
-@end
-
typedef void (^FIRDocumentSnapshotBlock)(FIRDocumentSnapshot *_Nullable snapshot,
NSError *_Nullable error);
@@ -107,14 +81,14 @@ NS_SWIFT_NAME(DocumentReference)
/**
* Writes to the document referred to by this DocumentReference. If the document does not yet
- * exist, it will be created. If you pass `FIRSetOptions`, the provided data will be merged into
- * an existing document.
+ * exist, it will be created. If you pass `merge:YES`, the provided data will be merged into
+ * any existing document.
*
* @param documentData An `NSDictionary` that contains the fields and data to write to the
* document.
- * @param options A `FIRSetOptions` used to configure the set behavior.
+ * @param merge Whether to merge the provided data into any existing document.
*/
-- (void)setData:(NSDictionary<NSString *, id> *)documentData options:(FIRSetOptions *)options;
+- (void)setData:(NSDictionary<NSString *, id> *)documentData merge:(BOOL)merge;
/**
* Overwrites the document referred to by this `FIRDocumentReference`. If no document exists, it
@@ -131,18 +105,18 @@ NS_SWIFT_NAME(DocumentReference)
/**
* Writes to the document referred to by this DocumentReference. If the document does not yet
- * exist, it will be created. If you pass `FIRSetOptions`, the provided data will be merged into
- * an existing document.
+ * exist, it will be created. If you pass `merge:YES`, the provided data will be merged into
+ * any existing document.
*
* @param documentData An `NSDictionary` containing the fields that make up the document
* to be written.
- * @param options A `FIRSetOptions` used to configure the set behavior.
+ * @param merge Whether to merge the provided data into any existing document.
* @param completion A block to execute once the document has been successfully written to the
* server. This block will not be called while the client is offline, though local
* changes will be visible immediately.
*/
- (void)setData:(NSDictionary<NSString *, id> *)documentData
- options:(FIRSetOptions *)options
+ merge:(BOOL)merge
completion:(nullable void (^)(NSError *_Nullable error))completion;
/**
@@ -210,16 +184,17 @@ NS_SWIFT_NAME(DocumentReference)
/**
* Attaches a listener for DocumentSnapshot events.
*
- * @param options Options controlling the listener behavior.
+ * @param includeMetadataChanges Whether metadata-only changes (i.e. only
+ * `FIRDocumentSnapshot.metadata` changed) should trigger snapshot events.
* @param listener The listener to attach.
*
* @return A FIRListenerRegistration that can be used to remove this listener.
*/
// clang-format off
-- (id<FIRListenerRegistration>)addSnapshotListenerWithOptions:
- (nullable FIRDocumentListenOptions *)options
- listener:(FIRDocumentSnapshotBlock)listener
- NS_SWIFT_NAME(addSnapshotListener(options:listener:));
+- (id<FIRListenerRegistration>)
+addSnapshotListenerWithIncludeMetadataChanges:(BOOL)includeMetadataChanges
+ listener:(FIRDocumentSnapshotBlock)listener
+ NS_SWIFT_NAME(addSnapshotListener(includeMetadataChanges:listener:));
// clang-format on
@end
diff --git a/Firestore/Source/Public/FIRDocumentSnapshot.h b/Firestore/Source/Public/FIRDocumentSnapshot.h
index 3e67c25..6e79a7f 100644
--- a/Firestore/Source/Public/FIRDocumentSnapshot.h
+++ b/Firestore/Source/Public/FIRDocumentSnapshot.h
@@ -22,9 +22,61 @@
NS_ASSUME_NONNULL_BEGIN
/**
+ * Controls the return value for server timestamps that have not yet been set to
+ * their final value.
+ */
+typedef NS_ENUM(NSInteger, FIRServerTimestampBehavior) {
+ /**
+ * Return `NSNull` for `FieldValue.serverTimestamp()` fields that have not yet
+ * been set to their final value.
+ */
+ FIRServerTimestampBehaviorNone,
+
+ /**
+ * Return a local estimates for `FieldValue.serverTimestamp()`
+ * fields that have not yet been set to their final value. This estimate will
+ * likely differ from the final value and may cause these pending values to
+ * change once the server result becomes available.
+ */
+ FIRServerTimestampBehaviorEstimate,
+
+ /**
+ * Return the previous value for `FieldValue.serverTimestamp()` fields that
+ * have not yet been set to their final value.
+ */
+ FIRServerTimestampBehaviorPrevious
+} NS_SWIFT_NAME(ServerTimestampBehavior);
+
+/**
+ * Options that configure how data is retrieved from a `DocumentSnapshot`
+ * (e.g. the desired behavior for server timestamps that have not yet been set
+ * to their final value).
+ */
+NS_SWIFT_NAME(SnapshotOptions)
+@interface FIRSnapshotOptions : NSObject
+
+/** */
+- (instancetype)init __attribute__((unavailable("FIRSnapshotOptions cannot be created directly.")));
+
+/**
+ * If set, controls the return value for `FieldValue.serverTimestamp()`
+ * fields that have not yet been set to their final value.
+ *
+ * If omitted, `NSNull` will be returned by default.
+ *
+ * @return The created `FIRSnapshotOptions` object.
+ */
++ (instancetype)serverTimestampBehavior:(FIRServerTimestampBehavior)serverTimestampBehavior;
+
+@end
+
+/**
* A `FIRDocumentSnapshot` contains data read from a document in your Firestore database. The data
* can be extracted with the `data` property or by using subscript syntax to access a specific
* field.
+ *
+ * For a `FIRDocumentSnapshot` that points to a non-existing document, any data access will return
+ * `nil`. You can use the `exists` property to explicitly verify a documents existence.
*/
NS_SWIFT_NAME(DocumentSnapshot)
@interface FIRDocumentSnapshot : NSObject
@@ -46,21 +98,104 @@ NS_SWIFT_NAME(DocumentSnapshot)
@property(nonatomic, strong, readonly) FIRSnapshotMetadata *metadata;
/**
- * Retrieves all fields in the document as an `NSDictionary`.
+ * Retrieves all fields in the document as an `NSDictionary`. Returns `nil` if the document doesn't
+ * exist.
*
- * @return An `NSDictionary` containing all fields in the document.
+ * Server-provided timestamps that have not yet been set to their final value will be returned as
+ * `NSNull`. You can use `dataWithOptions()` to configure this behavior.
+ *
+ * @return An `NSDictionary` containing all fields in the document or `nil` if the document doesn't
+ * exist.
*/
-- (NSDictionary<NSString *, id> *)data;
+- (nullable NSDictionary<NSString *, id> *)data;
+
+/**
+ * Retrieves all fields in the document as a `Dictionary`. Returns `nil` if the document doesn't
+ * exist.
+ *
+ * @param options `SnapshotOptions` to configure how data is returned from the snapshot (e.g. the
+ * desired behavior for server timestamps that have not yet been set to their final value).
+ * @return A `Dictionary` containing all fields in the document or `nil` if the document doesn't
+ * exist.
+ */
+- (nullable NSDictionary<NSString *, id> *)dataWithOptions:(FIRSnapshotOptions *)options;
+
+/**
+ * Retrieves a specific field from the document. Returns `nil` if the document or the field doesn't
+ * exist.
+ *
+ * The timestamps that have not yet been set to their final value will be returned as `NSNull`. The
+ * can use `get(_:options:)` to configure this behavior.
+ *
+ * @param field The field to retrieve.
+ * @return The value contained in the field or `nil` if the document or field doesn't exist.
+ */
+- (nullable id)valueForField:(id)field NS_SWIFT_NAME(get(_:));
+
+/**
+ * Retrieves a specific field from the document. Returns `nil` if the document or the field doesn't
+ * exist.
+ *
+ * The timestamps that have not yet been set to their final value will be returned as `NSNull`. The
+ * can use `get(_:options:)` to configure this behavior.
+ *
+ * @param field The field to retrieve.
+ * @param options `SnapshotOptions` to configure how data is returned from the snapshot (e.g. the
+ * desired behavior for server timestamps that have not yet been set to their final value).
+ * @return The value contained in the field or `nil` if the document or field doesn't exist.
+ */
+// clang-format off
+- (nullable id)valueForField:(id)field
+ options:(FIRSnapshotOptions *)options
+ NS_SWIFT_NAME(get(_:options:));
+// clang-format on
/**
* Retrieves a specific field from the document.
*
* @param key The field to retrieve.
*
- * @return The value contained in the field or `nil` if the field doesn't exist.
+ * @return The value contained in the field or `nil` if the document or field doesn't exist.
*/
- (nullable id)objectForKeyedSubscript:(id)key;
@end
+/**
+ * A `FIRQueryDocumentSnapshot` contains data read from a document in your Firestore database as
+ * part of a query. The document is guaranteed to exist and its data can be extracted with the
+ * `data` property or by using subscript syntax to access a specific field.
+ *
+ * A `FIRQueryDocumentSnapshot` offers the same API surface as a `FIRDocumentSnapshot`. As
+ * deleted documents are not returned from queries, its `exists` property will always be true and
+ * `data:` will never return `nil`.
+ */
+NS_SWIFT_NAME(QueryDocumentSnapshot)
+@interface FIRQueryDocumentSnapshot : FIRDocumentSnapshot
+
+/** */
+- (instancetype)init
+ __attribute__((unavailable("FIRQueryDocumentSnapshot cannot be created directly.")));
+
+/**
+ * Retrieves all fields in the document as an `NSDictionary`.
+ *
+ * Server-provided timestamps that have not yet been set to their final value will be returned as
+ * `NSNull`. You can use `dataWithOptions()` to configure this behavior.
+ *
+ * @return An `NSDictionary` containing all fields in the document.
+ */
+- (NSDictionary<NSString *, id> *)data;
+
+/**
+ * Retrieves all fields in the document as a `Dictionary`.
+ *
+ * @param options `SnapshotOptions` to configure how data is returned from the snapshot (e.g. the
+ * desired behavior for server timestamps that have not yet been set to their final value).
+ * @return A `Dictionary` containing all fields in the document.
+ */
+- (NSDictionary<NSString *, id> *)dataWithOptions:(FIRSnapshotOptions *)options;
+
+@end
+
NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/Public/FIRFieldValue.h b/Firestore/Source/Public/FIRFieldValue.h
index 11a0da0..d896587 100644
--- a/Firestore/Source/Public/FIRFieldValue.h
+++ b/Firestore/Source/Public/FIRFieldValue.h
@@ -38,6 +38,29 @@ NS_SWIFT_NAME(FieldValue)
*/
+ (instancetype)fieldValueForServerTimestamp NS_SWIFT_NAME(serverTimestamp());
+/**
+ * Returns a special value that can be used with setData() or updateData() that tells the server to
+ * union the given elements with any array value that already exists on the server. Each
+ * specified element that doesn't already exist in the array will be added to the end. If the
+ * field being modified is not already an array it will be overwritten with an array containing
+ * exactly the specified elements.
+ *
+ * @param elements The elements to union into the array.
+ * @return The FieldValue sentinel for use in a call to setData() or updateData().
+ */
++ (instancetype)fieldValueForArrayUnion:(NSArray<id> *)elements NS_SWIFT_NAME(arrayUnion(_:));
+
+/**
+ * Returns a special value that can be used with setData() or updateData() that tells the server to
+ * remove the given elements from any array value that already exists on the server. All
+ * instances of each element specified will be removed from the array. If the field being
+ * modified is not already an array it will be overwritten with an empty array.
+ *
+ * @param elements The elements to remove from the array.
+ * @return The FieldValue sentinel for use in a call to setData() or updateData().
+ */
++ (instancetype)fieldValueForArrayRemove:(NSArray<id> *)elements NS_SWIFT_NAME(arrayRemove(_:));
+
@end
NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/Public/FIRFirestore.h b/Firestore/Source/Public/FIRFirestore.h
index 91a96a5..5e2b40f 100644
--- a/Firestore/Source/Public/FIRFirestore.h
+++ b/Firestore/Source/Public/FIRFirestore.h
@@ -136,8 +136,26 @@ NS_SWIFT_NAME(Firestore)
#pragma mark - Logging
/** Enables or disables logging from the Firestore client. */
-+ (void)enableLogging:(BOOL)logging
- DEPRECATED_MSG_ATTRIBUTE("Use FIRSetLoggerLevel(FIRLoggerLevelDebug) to enable logging");
++ (void)enableLogging:(BOOL)logging DEPRECATED_MSG_ATTRIBUTE(
+ "Use FirebaseConfiguration.shared.setLoggerLevel(.debug) to enable "
+ "logging.");
+
+#pragma mark - Network
+
+/**
+ * Re-enables usage of the network by this Firestore instance after a prior call to
+ * `disableNetworkWithCompletion`. Completion block, if provided, will be called once network uasge
+ * has been enabled.
+ */
+- (void)enableNetworkWithCompletion:(nullable void (^)(NSError *_Nullable error))completion;
+
+/**
+ * Disables usage of the network by this Firestore instance. It can be re-enabled by via
+ * `enableNetworkWithCompletion`. While the network is disabled, any snapshot listeners or get calls
+ * will return results from cache and any write operations will be queued until the network is
+ * restored. The completion block, if provided, will be called once network usage has been disabled.
+ */
+- (void)disableNetworkWithCompletion:(nullable void (^)(NSError *_Nullable error))completion;
@end
diff --git a/Firestore/Source/Public/FIRFirestoreSettings.h b/Firestore/Source/Public/FIRFirestoreSettings.h
index 7a1f2a3..cd3f91c 100644
--- a/Firestore/Source/Public/FIRFirestoreSettings.h
+++ b/Firestore/Source/Public/FIRFirestoreSettings.h
@@ -44,6 +44,23 @@ NS_SWIFT_NAME(FirestoreSettings)
/** Set to false to disable local persistent storage. */
@property(nonatomic, getter=isPersistenceEnabled) BOOL persistenceEnabled;
+/**
+ * Enables the use of FIRTimestamps for timestamp fields in FIRDocumentSnapshots.
+ *
+ * Currently, Firestore returns timestamp fields as an NSDate but NSDate is implemented as a double
+ * which loses precision and causes unexpected behavior when using a timestamp from a snapshot as
+ * a part of a subsequent query.
+ *
+ * Setting timestampsInSnapshotsEnabled to true will cause Firestore to return FIRTimestamp values
+ * instead of NSDate, avoiding this kind of problem. To make this work you must also change any code
+ * that uses NSDate to use FIRTimestamp instead.
+ *
+ * NOTE: in the future timestampsInSnapshotsEnabled = true will become the default and this option
+ * will be removed so you should change your code to use FIRTimestamp now and opt-in to this new
+ * behavior as soon as you can.
+ */
+@property(nonatomic, getter=areTimestampsInSnapshotsEnabled) BOOL timestampsInSnapshotsEnabled;
+
@end
NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/Public/FIRQuery.h b/Firestore/Source/Public/FIRQuery.h
index 0f3aeed..ff15ac6 100644
--- a/Firestore/Source/Public/FIRQuery.h
+++ b/Firestore/Source/Public/FIRQuery.h
@@ -256,6 +256,19 @@ NS_SWIFT_NAME(Query)
isGreaterThanOrEqualTo:(id)value NS_SWIFT_NAME(whereField(_:isGreaterThanOrEqualTo:));
// clang-format on
+/**
+ * Creates and returns a new `FIRQuery` with the additional filter that documents must
+ * satisfy the specified predicate.
+ *
+ * @param predicate The predicate the document must satisfy. Can be either comparison
+ * or compound of comparison. In particular, block-based predicate is not supported.
+ *
+ * @return The created `FIRQuery`.
+ */
+// clang-format off
+- (FIRQuery *)queryFilteredUsingPredicate:(NSPredicate *)predicate NS_SWIFT_NAME(filter(using:));
+// clang-format on
+
#pragma mark - Sorting Data
/**
* Creates and returns a new `FIRQuery` that's additionally sorted by the specified field.
diff --git a/Firestore/Source/Public/FIRQuerySnapshot.h b/Firestore/Source/Public/FIRQuerySnapshot.h
index c49a07a..1266832 100644
--- a/Firestore/Source/Public/FIRQuerySnapshot.h
+++ b/Firestore/Source/Public/FIRQuerySnapshot.h
@@ -19,8 +19,8 @@
NS_ASSUME_NONNULL_BEGIN
@class FIRDocumentChange;
-@class FIRDocumentSnapshot;
@class FIRQuery;
+@class FIRQueryDocumentSnapshot;
@class FIRSnapshotMetadata;
/**
@@ -50,7 +50,7 @@ NS_SWIFT_NAME(QuerySnapshot)
@property(nonatomic, readonly) NSInteger count;
/** An Array of the `FIRDocumentSnapshots` that make up this document set. */
-@property(nonatomic, strong, readonly) NSArray<FIRDocumentSnapshot *> *documents;
+@property(nonatomic, strong, readonly) NSArray<FIRQueryDocumentSnapshot *> *documents;
/**
* An array of the documents that changed since the last snapshot. If this is the first snapshot,
diff --git a/Firestore/Source/Public/FIRSetOptions.h b/Firestore/Source/Public/FIRSetOptions.h
deleted file mode 100644
index c865e06..0000000
--- a/Firestore/Source/Public/FIRSetOptions.h
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright 2017 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/Foundation.h>
-
-NS_ASSUME_NONNULL_BEGIN
-
-/**
- * An options object that configures the behavior of setData() calls. By providing the
- * `FIRSetOptions` objects returned by `merge:`, the setData() methods in `FIRDocumentReference`,
- * `FIRWriteBatch` and `FIRTransaction` can be configured to perform granular merges instead
- * of overwriting the target documents in their entirety.
- */
-NS_SWIFT_NAME(SetOptions)
-@interface FIRSetOptions : NSObject
-
-/** */
-- (id)init NS_UNAVAILABLE;
-/**
- * Changes the behavior of setData() calls to only replace the values specified in its data
- * argument. Fields with no corresponding values in the data passed to setData() will remain
- * untouched.
- *
- * @return The created `FIRSetOptions` object
- */
-+ (instancetype)merge;
-
-/** Whether setData() should merge existing data instead of performing an overwrite. */
-@property(nonatomic, readonly, getter=isMerge) BOOL merge;
-
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/Public/FIRSnapshotMetadata.h b/Firestore/Source/Public/FIRSnapshotMetadata.h
index 4c7ff98..f47f383 100644
--- a/Firestore/Source/Public/FIRSnapshotMetadata.h
+++ b/Firestore/Source/Public/FIRSnapshotMetadata.h
@@ -27,16 +27,16 @@ NS_SWIFT_NAME(SnapshotMetadata)
/**
* Returns YES if the snapshot contains the result of local writes (e.g. set() or update() calls)
* that have not yet been committed to the backend. If your listener has opted into metadata updates
- * (via `FIRDocumentListenOptions` or `FIRQueryListenOptions`) you will receive another snapshot
- * with `hasPendingWrites` equal to NO once the writes have been committed to the backend.
+ * (via `includeMetadataChanges:YES`) you will receive another snapshot with `hasPendingWrites`
+ * equal to NO once the writes have been committed to the backend.
*/
@property(nonatomic, assign, readonly, getter=hasPendingWrites) BOOL pendingWrites;
/**
* Returns YES if the snapshot was created from cached data rather than guaranteed up-to-date server
- * data. If your listener has opted into metadata updates (via `FIRDocumentListenOptions` or
- * `FIRQueryListenOptions`) you will receive another snapshot with `isFromCache` equal to NO once
- * the client has received up-to-date data from the backend.
+ * data. If your listener has opted into metadata updates (via `includeMetadataChanges:YES`) you
+ * will receive another snapshot with `isFromCache` equal to NO once the client has received
+ * up-to-date data from the backend.
*/
@property(nonatomic, assign, readonly, getter=isFromCache) BOOL fromCache;
diff --git a/Firestore/Source/Core/FSTTimestamp.h b/Firestore/Source/Public/FIRTimestamp.h
index f86779d..bf4aff4 100644
--- a/Firestore/Source/Core/FSTTimestamp.h
+++ b/Firestore/Source/Public/FIRTimestamp.h
@@ -1,5 +1,5 @@
/*
- * Copyright 2017 Google
+ * Copyright 2018 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,40 +19,50 @@
NS_ASSUME_NONNULL_BEGIN
/**
- * An FSTTimestamp represents an absolute time from the backend at up to nanosecond precision.
- * An FSTTimestamp is represented in terms of UTC and does not have an associated timezone.
+ * A Timestamp represents a point in time independent of any time zone or calendar, represented as
+ * seconds and fractions of seconds at nanosecond resolution in UTC Epoch time. It is encoded using
+ * the Proleptic Gregorian Calendar which extends the Gregorian calendar backwards to year one. It
+ * is encoded assuming all minutes are 60 seconds long, i.e. leap seconds are "smeared" so that no
+ * leap second table is needed for interpretation. Range is from 0001-01-01T00:00:00Z to
+ * 9999-12-31T23:59:59.999999999Z. By restricting to that range, we ensure that we can convert to
+ * and from RFC 3339 date strings.
+ *
+ * @see https://github.com/google/protobuf/blob/master/src/google/protobuf/timestamp.proto for the
+ * reference timestamp definition.
*/
-@interface FSTTimestamp : NSObject <NSCopying>
+NS_SWIFT_NAME(Timestamp)
+@interface FIRTimestamp : NSObject <NSCopying>
+/** */
- (instancetype)init NS_UNAVAILABLE;
/**
* Creates a new timestamp.
*
* @param seconds the number of seconds since epoch.
- * @param nanos the number of nanoseconds after the seconds.
+ * @param nanoseconds the number of nanoseconds after the seconds.
*/
-- (instancetype)initWithSeconds:(int64_t)seconds nanos:(int32_t)nanos NS_DESIGNATED_INITIALIZER;
+- (instancetype)initWithSeconds:(int64_t)seconds
+ nanoseconds:(int32_t)nanoseconds NS_DESIGNATED_INITIALIZER;
-/** Creates a new timestamp with the current date / time. */
-+ (instancetype)timestamp;
+/**
+ * Creates a new timestamp.
+ *
+ * @param seconds the number of seconds since epoch.
+ * @param nanoseconds the number of nanoseconds after the seconds.
+ */
++ (instancetype)timestampWithSeconds:(int64_t)seconds nanoseconds:(int32_t)nanoseconds;
/** Creates a new timestamp from the given date. */
+ (instancetype)timestampWithDate:(NSDate *)date;
-/** Returns a new NSDate corresponding to this timestamp. This may lose precision. */
-- (NSDate *)approximateDateValue;
+/** Creates a new timestamp with the current date / time. */
++ (instancetype)timestamp;
-/**
- * Converts the given date to a an ISO 8601 timestamp string, useful for rendering in JSON.
- *
- * ISO 8601 dates times in UTC look like this: "1912-04-14T23:40:00.000000000Z".
- *
- * @see http://www.ecma-international.org/ecma-262/6.0/#sec-date-time-string-format
- */
-- (NSString *)ISO8601String;
+/** Returns a new NSDate corresponding to this timestamp. This may lose precision. */
+- (NSDate *)dateValue;
-- (NSComparisonResult)compare:(FSTTimestamp *)other;
+- (NSComparisonResult)compare:(FIRTimestamp *)other;
/**
* Represents seconds of UTC time since Unix epoch 1970-01-01T00:00:00Z.
@@ -65,7 +75,7 @@ NS_ASSUME_NONNULL_BEGIN
* fractions must still have non-negative nanos values that count forward in time.
* Must be from 0 to 999,999,999 inclusive.
*/
-@property(nonatomic, assign, readonly) int32_t nanos;
+@property(nonatomic, assign, readonly) int32_t nanoseconds;
@end
diff --git a/Firestore/Source/Public/FIRTransaction.h b/Firestore/Source/Public/FIRTransaction.h
index 51a6e90..2fa4430 100644
--- a/Firestore/Source/Public/FIRTransaction.h
+++ b/Firestore/Source/Public/FIRTransaction.h
@@ -20,7 +20,6 @@ NS_ASSUME_NONNULL_BEGIN
@class FIRDocumentReference;
@class FIRDocumentSnapshot;
-@class FIRSetOptions;
/**
* `FIRTransaction` provides methods to read and write data within a transaction.
@@ -50,19 +49,19 @@ NS_SWIFT_NAME(Transaction)
/**
* Writes to the document referred to by `document`. If the document doesn't yet exist,
- * this method creates it and then sets the data. If you pass `FIRSetOptions`, the provided data
- * will be merged into an existing document.
+ * this method creates it and then sets the data. If you pass `merge:YES`, the provided data will be
+ * merged into any existing document.
*
* @param data An `NSDictionary` that contains the fields and data to write to the document.
* @param document A reference to the document whose data should be overwritten.
- * @param options A `FIRSetOptions` used to configure the set behavior.
+ * @param merge Whether to merge the provided data into any existing document.
* @return This `FIRTransaction` instance. Used for chaining method calls.
*/
// clang-format off
- (FIRTransaction *)setData:(NSDictionary<NSString *, id> *)data
forDocument:(FIRDocumentReference *)document
- options:(FIRSetOptions *)options
- NS_SWIFT_NAME(setData(_:forDocument:options:));
+ merge:(BOOL)merge
+ NS_SWIFT_NAME(setData(_:forDocument:merge:));
// clang-format on
/**
diff --git a/Firestore/Source/Public/FIRWriteBatch.h b/Firestore/Source/Public/FIRWriteBatch.h
index 5f0034c..1568723 100644
--- a/Firestore/Source/Public/FIRWriteBatch.h
+++ b/Firestore/Source/Public/FIRWriteBatch.h
@@ -19,7 +19,6 @@
NS_ASSUME_NONNULL_BEGIN
@class FIRDocumentReference;
-@class FIRSetOptions;
/**
* A write batch is used to perform multiple writes as a single atomic unit.
@@ -53,19 +52,19 @@ NS_SWIFT_NAME(WriteBatch)
/**
* Writes to the document referred to by `document`. If the document doesn't yet exist,
- * this method creates it and then sets the data. If you pass `FIRSetOptions`, the provided data
- * will be merged into an existing document.
+ * this method creates it and then sets the data. If you pass `merge:YES`, the provided data will be
+ * merged into any existing document.
*
* @param data An `NSDictionary` that contains the fields and data to write to the document.
* @param document A reference to the document whose data should be overwritten.
- * @param options A `FIRSetOptions` used to configure the set behavior.
+ * @param merge Whether to merge the provided data into any existing document.
* @return This `FIRWriteBatch` instance. Used for chaining method calls.
*/
// clang-format off
- (FIRWriteBatch *)setData:(NSDictionary<NSString *, id> *)data
forDocument:(FIRDocumentReference *)document
- options:(FIRSetOptions *)options
- NS_SWIFT_NAME(setData(_:forDocument:options:));
+ merge:(BOOL)merge
+ NS_SWIFT_NAME(setData(_:forDocument:merge:));
// clang-format on
/**
@@ -94,6 +93,11 @@ NS_SWIFT_NAME(WriteBatch)
/**
* Commits all of the writes in this write batch as a single atomic unit.
+ */
+- (void)commit;
+
+/**
+ * Commits all of the writes in this write batch as a single atomic unit.
*
* @param completion A block to be called once all of the writes in the batch have been
* successfully written to the backend as an atomic unit. This block will only execute
@@ -101,7 +105,7 @@ NS_SWIFT_NAME(WriteBatch)
* completion handler will not be called when the device is offline, though local
* changes will be visible immediately.
*/
-- (void)commitWithCompletion:(void (^)(NSError *_Nullable error))completion;
+- (void)commitWithCompletion:(nullable void (^)(NSError *_Nullable error))completion;
@end
diff --git a/Firestore/Source/Public/FirebaseFirestore.h b/Firestore/Source/Public/FirebaseFirestore.h
index ff110fd..127c935 100644
--- a/Firestore/Source/Public/FirebaseFirestore.h
+++ b/Firestore/Source/Public/FirebaseFirestore.h
@@ -27,7 +27,7 @@
#import "FIRListenerRegistration.h"
#import "FIRQuery.h"
#import "FIRQuerySnapshot.h"
-#import "FIRSetOptions.h"
#import "FIRSnapshotMetadata.h"
+#import "FIRTimestamp.h"
#import "FIRTransaction.h"
#import "FIRWriteBatch.h"
diff --git a/Firestore/Source/Remote/FSTBufferedWriter.m b/Firestore/Source/Remote/FSTBufferedWriter.mm
index 47dbb21..47dbb21 100644
--- a/Firestore/Source/Remote/FSTBufferedWriter.m
+++ b/Firestore/Source/Remote/FSTBufferedWriter.mm
diff --git a/Firestore/Source/Remote/FSTDatastore.h b/Firestore/Source/Remote/FSTDatastore.h
index 13d9eda..b3ba46c 100644
--- a/Firestore/Source/Remote/FSTDatastore.h
+++ b/Firestore/Source/Remote/FSTDatastore.h
@@ -16,10 +16,16 @@
#import <Foundation/Foundation.h>
+#include <vector>
+
#import "Firestore/Source/Core/FSTTypes.h"
-@class FSTDatabaseInfo;
-@class FSTDocumentKey;
+#include "Firestore/core/src/firebase/firestore/auth/credentials_provider.h"
+#include "Firestore/core/src/firebase/firestore/core/database_info.h"
+#include "Firestore/core/src/firebase/firestore/model/database_id.h"
+#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+#include "absl/strings/string_view.h"
+
@class FSTDispatchQueue;
@class FSTMutation;
@class FSTMutationResult;
@@ -32,9 +38,6 @@
@class GRPCCall;
@class GRXWriter;
-@protocol FSTCredentialsProvider;
-@class FSTDatabaseID;
-
NS_ASSUME_NONNULL_BEGIN
/**
@@ -52,15 +55,17 @@ NS_ASSUME_NONNULL_BEGIN
@interface FSTDatastore : NSObject
/** Creates a new Datastore instance with the given database info. */
-+ (instancetype)datastoreWithDatabase:(FSTDatabaseInfo *)database
++ (instancetype)datastoreWithDatabase:(const firebase::firestore::core::DatabaseInfo *)databaseInfo
workerDispatchQueue:(FSTDispatchQueue *)workerDispatchQueue
- credentials:(id<FSTCredentialsProvider>)credentials;
+ credentials:(firebase::firestore::auth::CredentialsProvider *)
+ credentials; // no passing ownership
- (instancetype)init __attribute__((unavailable("Use a static constructor method.")));
-- (instancetype)initWithDatabaseInfo:(FSTDatabaseInfo *)databaseInfo
+- (instancetype)initWithDatabaseInfo:(const firebase::firestore::core::DatabaseInfo *)databaseInfo
workerDispatchQueue:(FSTDispatchQueue *)workerDispatchQueue
- credentials:(id<FSTCredentialsProvider>)credentials
+ credentials:(firebase::firestore::auth::CredentialsProvider *)
+ credentials // no passing ownership
NS_DESIGNATED_INITIALIZER;
/**
@@ -81,11 +86,11 @@ NS_ASSUME_NONNULL_BEGIN
/** Adds headers to the RPC including any OAuth access token if provided .*/
+ (void)prepareHeadersForRPC:(GRPCCall *)rpc
- databaseID:(FSTDatabaseID *)databaseID
- token:(nullable NSString *)token;
+ databaseID:(const firebase::firestore::model::DatabaseId *)databaseID
+ token:(const absl::string_view)token;
/** Looks up a list of documents in datastore. */
-- (void)lookupDocuments:(NSArray<FSTDocumentKey *> *)keys
+- (void)lookupDocuments:(const std::vector<firebase::firestore::model::DocumentKey> &)keys
completion:(FSTVoidMaybeDocumentArrayErrorBlock)completion;
/** Commits data to datastore. */
@@ -99,7 +104,8 @@ NS_ASSUME_NONNULL_BEGIN
- (FSTWriteStream *)createWriteStream;
/** The name of the database and the backend. */
-@property(nonatomic, strong, readonly) FSTDatabaseInfo *databaseInfo;
+// Does not own this DatabaseInfo.
+@property(nonatomic, assign, readonly) const firebase::firestore::core::DatabaseInfo *databaseInfo;
@end
diff --git a/Firestore/Source/Remote/FSTDatastore.m b/Firestore/Source/Remote/FSTDatastore.mm
index 02d868c..c7ee30f 100644
--- a/Firestore/Source/Remote/FSTDatastore.m
+++ b/Firestore/Source/Remote/FSTDatastore.mm
@@ -19,15 +19,15 @@
#import <GRPCClient/GRPCCall+OAuth2.h>
#import <ProtoRPC/ProtoRPC.h>
+#include <map>
+#include <memory>
+#include <vector>
+
#import "FIRFirestoreErrors.h"
#import "Firestore/Source/API/FIRFirestore+Internal.h"
#import "Firestore/Source/API/FIRFirestoreVersion.h"
-#import "Firestore/Source/Auth/FSTCredentialsProvider.h"
-#import "Firestore/Source/Core/FSTDatabaseInfo.h"
#import "Firestore/Source/Local/FSTLocalStore.h"
-#import "Firestore/Source/Model/FSTDatabaseID.h"
#import "Firestore/Source/Model/FSTDocument.h"
-#import "Firestore/Source/Model/FSTDocumentKey.h"
#import "Firestore/Source/Model/FSTMutation.h"
#import "Firestore/Source/Remote/FSTSerializerBeta.h"
#import "Firestore/Source/Remote/FSTStream.h"
@@ -37,6 +37,21 @@
#import "Firestore/Protos/objc/google/firestore/v1beta1/Firestore.pbrpc.h"
+#include "Firestore/core/src/firebase/firestore/auth/credentials_provider.h"
+#include "Firestore/core/src/firebase/firestore/auth/token.h"
+#include "Firestore/core/src/firebase/firestore/core/database_info.h"
+#include "Firestore/core/src/firebase/firestore/model/database_id.h"
+#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+#include "Firestore/core/src/firebase/firestore/util/error_apple.h"
+#include "Firestore/core/src/firebase/firestore/util/string_apple.h"
+
+namespace util = firebase::firestore::util;
+using firebase::firestore::auth::CredentialsProvider;
+using firebase::firestore::auth::Token;
+using firebase::firestore::core::DatabaseInfo;
+using firebase::firestore::model::DatabaseId;
+using firebase::firestore::model::DocumentKey;
+
NS_ASSUME_NONNULL_BEGIN
// GRPC does not publicly declare a means of disabling SSL, which we need for testing. Firestore
@@ -62,8 +77,11 @@ typedef GRPCProtoCall * (^RPCFactory)(void);
@property(nonatomic, strong, readonly) FSTDispatchQueue *workerDispatchQueue;
-/** An object for getting an auth token before each request. */
-@property(nonatomic, strong, readonly) id<FSTCredentialsProvider> credentials;
+/**
+ * An object for getting an auth token before each request. Does not own the CredentialsProvider
+ * instance.
+ */
+@property(nonatomic, assign, readonly) CredentialsProvider *credentials;
@property(nonatomic, strong, readonly) FSTSerializerBeta *serializer;
@@ -71,33 +89,36 @@ typedef GRPCProtoCall * (^RPCFactory)(void);
@implementation FSTDatastore
-+ (instancetype)datastoreWithDatabase:(FSTDatabaseInfo *)databaseInfo
++ (instancetype)datastoreWithDatabase:(const DatabaseInfo *)databaseInfo
workerDispatchQueue:(FSTDispatchQueue *)workerDispatchQueue
- credentials:(id<FSTCredentialsProvider>)credentials {
+ credentials:(CredentialsProvider *)credentials {
return [[FSTDatastore alloc] initWithDatabaseInfo:databaseInfo
workerDispatchQueue:workerDispatchQueue
credentials:credentials];
}
-- (instancetype)initWithDatabaseInfo:(FSTDatabaseInfo *)databaseInfo
+- (instancetype)initWithDatabaseInfo:(const DatabaseInfo *)databaseInfo
workerDispatchQueue:(FSTDispatchQueue *)workerDispatchQueue
- credentials:(id<FSTCredentialsProvider>)credentials {
+ credentials:(CredentialsProvider *)credentials {
if (self = [super init]) {
_databaseInfo = databaseInfo;
- if (!databaseInfo.isSSLEnabled) {
- GRPCHost *hostConfig = [GRPCHost hostWithAddress:databaseInfo.host];
+ NSString *host = util::WrapNSString(databaseInfo->host());
+ if (!databaseInfo->ssl_enabled()) {
+ GRPCHost *hostConfig = [GRPCHost hostWithAddress:host];
hostConfig.secure = NO;
}
- _service = [GCFSFirestore serviceWithHost:databaseInfo.host];
+ _service = [GCFSFirestore serviceWithHost:host];
_workerDispatchQueue = workerDispatchQueue;
_credentials = credentials;
- _serializer = [[FSTSerializerBeta alloc] initWithDatabaseID:databaseInfo.databaseID];
+ _serializer = [[FSTSerializerBeta alloc] initWithDatabaseID:&databaseInfo->database_id()];
}
return self;
}
- (NSString *)description {
- return [NSString stringWithFormat:@"<FSTDatastore: %@>", self.databaseInfo];
+ return [NSString stringWithFormat:@"<FSTDatastore: <DatabaseInfo: database_id:%s host:%s>>",
+ self.databaseInfo->database_id().database_id().c_str(),
+ self.databaseInfo->host().c_str()];
}
/**
@@ -168,9 +189,9 @@ typedef GRPCProtoCall * (^RPCFactory)(void);
}
/** Returns the string to be used as google-cloud-resource-prefix header value. */
-+ (NSString *)googleCloudResourcePrefixForDatabaseID:(FSTDatabaseID *)databaseID {
- return [NSString
- stringWithFormat:@"projects/%@/databases/%@", databaseID.projectID, databaseID.databaseID];
++ (NSString *)googleCloudResourcePrefixForDatabaseID:(const DatabaseId *)databaseID {
+ return [NSString stringWithFormat:@"projects/%s/databases/%s", databaseID->project_id().c_str(),
+ databaseID->database_id().c_str()];
}
/**
* Takes a dictionary of (HTTP) response headers and returns the set of whitelisted headers
@@ -229,17 +250,19 @@ typedef GRPCProtoCall * (^RPCFactory)(void);
[self invokeRPCWithFactory:rpcFactory errorHandler:completion];
}
-- (void)lookupDocuments:(NSArray<FSTDocumentKey *> *)keys
+- (void)lookupDocuments:(const std::vector<DocumentKey> &)keys
completion:(FSTVoidMaybeDocumentArrayErrorBlock)completion {
GCFSBatchGetDocumentsRequest *request = [GCFSBatchGetDocumentsRequest message];
request.database = [self.serializer encodedDatabaseID];
- for (FSTDocumentKey *key in keys) {
+ for (const DocumentKey &key : keys) {
[request.documentsArray addObject:[self.serializer encodedDocumentKey:key]];
}
- __block FSTMaybeDocumentDictionary *results =
- [FSTMaybeDocumentDictionary maybeDocumentDictionary];
+ struct Closure {
+ std::map<DocumentKey, FSTMaybeDocument *> results;
+ };
+ __block std::shared_ptr<Closure> closure = std::make_shared<Closure>(Closure{});
RPCFactory rpcFactory = ^GRPCProtoCall * {
__block GRPCProtoCall *rpc = [self.service
RPCToBatchGetDocumentsWithRequest:request
@@ -259,16 +282,17 @@ typedef GRPCProtoCall * (^RPCFactory)(void);
// Streaming response, accumulate result
FSTMaybeDocument *doc =
[self.serializer decodedMaybeDocumentFromBatch:response];
- results = [results dictionaryBySettingObject:doc forKey:doc.key];
+ closure->results.insert({doc.key, doc});
} else {
// Streaming response is done, call completion
FSTLog(@"RPC BatchGetDocuments completed successfully.");
[FSTDatastore logHeadersForRPC:rpc RPCName:@"BatchGetDocuments"];
FSTAssert(!response, @"Got response after done.");
NSMutableArray<FSTMaybeDocument *> *docs =
- [NSMutableArray arrayWithCapacity:keys.count];
- for (FSTDocumentKey *key in keys) {
- [docs addObject:results[key]];
+ [NSMutableArray arrayWithCapacity:closure->results.size()];
+ for (auto &&entry : closure->results) {
+ FSTMaybeDocument *doc = entry.second;
+ [docs addObject:doc];
}
completion(docs, nil);
}
@@ -288,22 +312,23 @@ typedef GRPCProtoCall * (^RPCFactory)(void);
errorHandler:(FSTVoidErrorBlock)errorHandler {
// TODO(mikelehen): We should force a refresh if the previous RPC failed due to an expired token,
// but I'm not sure how to detect that right now. http://b/32762461
- [self.credentials
- getTokenForcingRefresh:NO
- completion:^(FSTGetTokenResult *_Nullable result, NSError *_Nullable error) {
- error = [FSTDatastore firestoreErrorForError:error];
- [self.workerDispatchQueue dispatchAsyncAllowingSameQueue:^{
- if (error) {
- errorHandler(error);
- } else {
- GRPCProtoCall *rpc = rpcFactory();
- [FSTDatastore prepareHeadersForRPC:rpc
- databaseID:self.databaseInfo.databaseID
- token:result.token];
- [rpc start];
- }
- }];
- }];
+ _credentials->GetToken(
+ /*force_refresh=*/false, [self, rpcFactory, errorHandler](util::StatusOr<Token> result) {
+ [self.workerDispatchQueue dispatchAsyncAllowingSameQueue:^{
+ if (!result.ok()) {
+ errorHandler(util::MakeNSError(result.status()));
+ } else {
+ GRPCProtoCall *rpc = rpcFactory();
+ const Token &token = result.ValueOrDie();
+ [FSTDatastore
+ prepareHeadersForRPC:rpc
+ databaseID:&self.databaseInfo->database_id()
+ token:(token.user().is_authenticated() ? token.token()
+ : absl::string_view())];
+ [rpc start];
+ }
+ }];
+ });
}
- (FSTWatchStream *)createWatchStream {
@@ -322,9 +347,9 @@ typedef GRPCProtoCall * (^RPCFactory)(void);
/** Adds headers to the RPC including any OAuth access token if provided .*/
+ (void)prepareHeadersForRPC:(GRPCCall *)rpc
- databaseID:(FSTDatabaseID *)databaseID
- token:(nullable NSString *)token {
- rpc.oauth2AccessToken = token;
+ databaseID:(const DatabaseId *)databaseID
+ token:(const absl::string_view)token {
+ rpc.oauth2AccessToken = token.data() == nullptr ? nil : util::WrapNSString(token);
rpc.requestHeaders[kXGoogAPIClientHeader] = [FSTDatastore googAPIClientHeaderValue];
// This header is used to improve routing and project isolation by the backend.
rpc.requestHeaders[kGoogleCloudResourcePrefix] =
diff --git a/Firestore/Source/Remote/FSTExistenceFilter.m b/Firestore/Source/Remote/FSTExistenceFilter.mm
index d5ec7b3..d5ec7b3 100644
--- a/Firestore/Source/Remote/FSTExistenceFilter.m
+++ b/Firestore/Source/Remote/FSTExistenceFilter.mm
diff --git a/Firestore/Source/Remote/FSTExponentialBackoff.h b/Firestore/Source/Remote/FSTExponentialBackoff.h
index 674b1ac..3beafbf 100644
--- a/Firestore/Source/Remote/FSTExponentialBackoff.h
+++ b/Firestore/Source/Remote/FSTExponentialBackoff.h
@@ -16,7 +16,7 @@
#import <Foundation/Foundation.h>
-@class FSTDispatchQueue;
+#import "Firestore/Source/Util/FSTDispatchQueue.h"
NS_ASSUME_NONNULL_BEGIN
@@ -29,7 +29,7 @@ NS_ASSUME_NONNULL_BEGIN
@interface FSTExponentialBackoff : NSObject
/**
- * Creates and returns a helper for running delayed tasks following an exponential backoff curve
+ * Initializes a helper for running delayed tasks following an exponential backoff curve
* between attempts.
*
* Each delay is made up of a "base" delay which follows the exponential backoff curve, and a
@@ -37,6 +37,7 @@ NS_ASSUME_NONNULL_BEGIN
* accidentally synchronizing their delays causing spikes of load to the backend.
*
* @param dispatchQueue The dispatch queue to run tasks on.
+ * @param timerID The ID to use when scheduling backoff operations on the FSTDispatchQueue.
* @param initialDelay The initial delay (used as the base delay on the first retry attempt).
* Note that jitter will still be applied, so the actual delay could be as little as
* 0.5*initialDelay.
@@ -45,13 +46,13 @@ NS_ASSUME_NONNULL_BEGIN
* @param maxDelay The maximum base delay after which no further backoff is performed. Note that
* jitter will still be applied, so the actual delay could be as much as 1.5*maxDelay.
*/
-+ (instancetype)exponentialBackoffWithDispatchQueue:(FSTDispatchQueue *)dispatchQueue
- initialDelay:(NSTimeInterval)initialDelay
- backoffFactor:(double)backoffFactor
- maxDelay:(NSTimeInterval)maxDelay;
+- (instancetype)initWithDispatchQueue:(FSTDispatchQueue *)dispatchQueue
+ timerID:(FSTTimerID)timerID
+ initialDelay:(NSTimeInterval)initialDelay
+ backoffFactor:(double)backoffFactor
+ maxDelay:(NSTimeInterval)maxDelay NS_DESIGNATED_INITIALIZER;
-- (instancetype)init
- __attribute__((unavailable("Use exponentialBackoffWithDispatchQueue constructor method.")));
+- (instancetype)init NS_UNAVAILABLE;
/**
* Resets the backoff delay.
@@ -68,12 +69,16 @@ NS_ASSUME_NONNULL_BEGIN
- (void)resetToMax;
/**
- * Waits for currentDelay seconds, increases the delay and runs the specified block.
+ * Waits for currentDelay seconds, increases the delay and runs the specified block. If there was
+ * a pending block waiting to be run already, it will be canceled.
*
* @param block The block to run.
*/
- (void)backoffAndRunBlock:(void (^)(void))block;
+/** Cancels any pending backoff block scheduled via backoffAndRunBlock:. */
+- (void)cancel;
+
@end
NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/Remote/FSTExponentialBackoff.mm b/Firestore/Source/Remote/FSTExponentialBackoff.mm
index 8077357..20b50a5 100644
--- a/Firestore/Source/Remote/FSTExponentialBackoff.mm
+++ b/Firestore/Source/Remote/FSTExponentialBackoff.mm
@@ -26,16 +26,14 @@
using firebase::firestore::util::SecureRandom;
@interface FSTExponentialBackoff ()
-- (instancetype)initWithDispatchQueue:(FSTDispatchQueue *)dispatchQueue
- initialDelay:(NSTimeInterval)initialDelay
- backoffFactor:(double)backoffFactor
- maxDelay:(NSTimeInterval)maxDelay NS_DESIGNATED_INITIALIZER;
@property(nonatomic, strong) FSTDispatchQueue *dispatchQueue;
+@property(nonatomic, assign, readonly) FSTTimerID timerID;
@property(nonatomic) double backoffFactor;
@property(nonatomic) NSTimeInterval initialDelay;
@property(nonatomic) NSTimeInterval maxDelay;
@property(nonatomic) NSTimeInterval currentBase;
+@property(nonatomic, strong, nullable) FSTDelayedCallback *timerCallback;
@end
@implementation FSTExponentialBackoff {
@@ -43,11 +41,13 @@ using firebase::firestore::util::SecureRandom;
}
- (instancetype)initWithDispatchQueue:(FSTDispatchQueue *)dispatchQueue
+ timerID:(FSTTimerID)timerID
initialDelay:(NSTimeInterval)initialDelay
backoffFactor:(double)backoffFactor
maxDelay:(NSTimeInterval)maxDelay {
if (self = [super init]) {
_dispatchQueue = dispatchQueue;
+ _timerID = timerID;
_initialDelay = initialDelay;
_backoffFactor = backoffFactor;
_maxDelay = maxDelay;
@@ -57,16 +57,6 @@ using firebase::firestore::util::SecureRandom;
return self;
}
-+ (instancetype)exponentialBackoffWithDispatchQueue:(FSTDispatchQueue *)dispatchQueue
- initialDelay:(NSTimeInterval)initialDelay
- backoffFactor:(double)backoffFactor
- maxDelay:(NSTimeInterval)maxDelay {
- return [[FSTExponentialBackoff alloc] initWithDispatchQueue:dispatchQueue
- initialDelay:initialDelay
- backoffFactor:backoffFactor
- maxDelay:maxDelay];
-}
-
- (void)reset {
_currentBase = 0;
}
@@ -76,6 +66,8 @@ using firebase::firestore::util::SecureRandom;
}
- (void)backoffAndRunBlock:(void (^)(void))block {
+ [self cancel];
+
// First schedule the block using the current base (which may be 0 and should be honored as such).
NSTimeInterval delayWithJitter = _currentBase + [self jitterDelay];
if (_currentBase > 0) {
@@ -83,7 +75,8 @@ using firebase::firestore::util::SecureRandom;
_currentBase);
}
- [self.dispatchQueue dispatchAfterDelay:delayWithJitter block:block];
+ self.timerCallback =
+ [self.dispatchQueue dispatchAfterDelay:delayWithJitter timerID:self.timerID block:block];
// Apply backoff factor to determine next delay and ensure it is within bounds.
_currentBase *= _backoffFactor;
@@ -95,6 +88,13 @@ using firebase::firestore::util::SecureRandom;
}
}
+- (void)cancel {
+ if (self.timerCallback) {
+ [self.timerCallback cancel];
+ self.timerCallback = nil;
+ }
+}
+
/** Returns a random value in the range [-currentBase/2, currentBase/2] */
- (NSTimeInterval)jitterDelay {
std::uniform_real_distribution<double> dist;
diff --git a/Firestore/Source/Remote/FSTOnlineStateTracker.h b/Firestore/Source/Remote/FSTOnlineStateTracker.h
new file mode 100644
index 0000000..2521c84
--- /dev/null
+++ b/Firestore/Source/Remote/FSTOnlineStateTracker.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2018 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/Foundation.h>
+
+#import "Firestore/Source/Core/FSTTypes.h"
+
+@class FSTDispatchQueue;
+@protocol FSTOnlineStateDelegate;
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ * A component used by the FSTRemoteStore to track the FSTOnlineState (that is, whether or not the
+ * client as a whole should be considered to be online or offline), implementing the appropriate
+ * heuristics.
+ *
+ * In particular, when the client is trying to connect to the backend, we allow up to
+ * kMaxWatchStreamFailures within kOnlineStateTimeout for a connection to succeed. If we have too
+ * many failures or the timeout elapses, then we set the FSTOnlineState to Offline, and
+ * the client will behave as if it is offline (getDocument() calls will return cached data, etc.).
+ */
+@interface FSTOnlineStateTracker : NSObject
+
+- (instancetype)initWithWorkerDispatchQueue:(FSTDispatchQueue *)queue;
+
+- (instancetype)init NS_UNAVAILABLE;
+
+/** A delegate to be notified on FSTOnlineState changes. */
+@property(nonatomic, weak) id<FSTOnlineStateDelegate> onlineStateDelegate;
+
+/**
+ * Called by FSTRemoteStore when a watch stream is started (including on each backoff attempt).
+ *
+ * If this is the first attempt, it sets the FSTOnlineState to Unknown and starts the
+ * onlineStateTimer.
+ */
+- (void)handleWatchStreamStart;
+
+/**
+ * Called by FSTRemoteStore when a watch stream fails.
+ *
+ * Updates our FSTOnlineState as appropriate. The first failure moves us to FSTOnlineStateUnknown.
+ * We then may allow multiple failures (based on kMaxWatchStreamFailures) before we actually
+ * transition to FSTOnlineStateOffline.
+ */
+- (void)handleWatchStreamFailure;
+
+/**
+ * Explicitly sets the FSTOnlineState to the specified state.
+ *
+ * Note that this resets the timers / failure counters, etc. used by our Offline heuristics, so
+ * it must not be used in place of handleWatchStreamStart and handleWatchStreamFailure.
+ */
+- (void)updateState:(FSTOnlineState)newState;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/Remote/FSTOnlineStateTracker.mm b/Firestore/Source/Remote/FSTOnlineStateTracker.mm
new file mode 100644
index 0000000..e782397
--- /dev/null
+++ b/Firestore/Source/Remote/FSTOnlineStateTracker.mm
@@ -0,0 +1,155 @@
+/*
+ * Copyright 2018 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 "Firestore/Source/Remote/FSTOnlineStateTracker.h"
+#import "Firestore/Source/Remote/FSTRemoteStore.h"
+#import "Firestore/Source/Util/FSTAssert.h"
+#import "Firestore/Source/Util/FSTDispatchQueue.h"
+#import "Firestore/Source/Util/FSTLogger.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+// To deal with transient failures, we allow multiple stream attempts before giving up and
+// transitioning from FSTOnlineState Unknown to Offline.
+static const int kMaxWatchStreamFailures = 2;
+
+// To deal with stream attempts that don't succeed or fail in a timely manner, we have a
+// timeout for FSTOnlineState to reach Online or Offline. If the timeout is reached, we transition
+// to Offline rather than waiting indefinitely.
+static const NSTimeInterval kOnlineStateTimeout = 10;
+
+@interface FSTOnlineStateTracker ()
+
+/** The current FSTOnlineState. */
+@property(nonatomic, assign) FSTOnlineState state;
+
+/**
+ * A count of consecutive failures to open the stream. If it reaches the maximum defined by
+ * kMaxWatchStreamFailures, we'll revert to FSTOnlineStateOffline.
+ */
+@property(nonatomic, assign) int watchStreamFailures;
+
+/**
+ * A timer that elapses after kOnlineStateTimeout, at which point we transition from FSTOnlineState
+ * Unknown to Offline without waiting for the stream to actually fail (kMaxWatchStreamFailures
+ * times).
+ */
+@property(nonatomic, strong, nullable) FSTDelayedCallback *onlineStateTimer;
+
+/**
+ * Whether the client should log a warning message if it fails to connect to the backend
+ * (initially YES, cleared after a successful stream, or if we've logged the message already).
+ */
+@property(nonatomic, assign) BOOL shouldWarnClientIsOffline;
+
+/** The FSTDispatchQueue to use for running timers (and to call onlineStateDelegate). */
+@property(nonatomic, strong, readonly) FSTDispatchQueue *queue;
+
+@end
+
+@implementation FSTOnlineStateTracker
+- (instancetype)initWithWorkerDispatchQueue:(FSTDispatchQueue *)queue {
+ if (self = [super init]) {
+ _queue = queue;
+ _state = FSTOnlineStateUnknown;
+ _shouldWarnClientIsOffline = YES;
+ }
+ return self;
+}
+
+- (void)handleWatchStreamStart {
+ if (self.watchStreamFailures == 0) {
+ [self setAndBroadcastState:FSTOnlineStateUnknown];
+
+ FSTAssert(!self.onlineStateTimer, @"onlineStateTimer shouldn't be started yet");
+ self.onlineStateTimer = [self.queue
+ dispatchAfterDelay:kOnlineStateTimeout
+ timerID:FSTTimerIDOnlineStateTimeout
+ block:^{
+ self.onlineStateTimer = nil;
+ FSTAssert(
+ self.state == FSTOnlineStateUnknown,
+ @"Timer should be canceled if we transitioned to a different state.");
+ FSTLog(
+ @"Watch stream didn't reach Online or Offline within %f seconds. "
+ @"Considering "
+ "client offline.",
+ kOnlineStateTimeout);
+ [self logClientOfflineWarningIfNecessary];
+ [self setAndBroadcastState:FSTOnlineStateOffline];
+
+ // NOTE: handleWatchStreamFailure will continue to increment
+ // watchStreamFailures even though we are already marked Offline but this is
+ // non-harmful.
+ }];
+ }
+}
+
+- (void)handleWatchStreamFailure {
+ if (self.state == FSTOnlineStateOnline) {
+ [self setAndBroadcastState:FSTOnlineStateUnknown];
+
+ // To get to FSTOnlineStateOnline, updateState: must have been called which would have reset
+ // our heuristics.
+ FSTAssert(self.watchStreamFailures == 0, @"watchStreamFailures must be 0");
+ FSTAssert(!self.onlineStateTimer, @"onlineStateTimer must be nil");
+ } else {
+ self.watchStreamFailures++;
+ if (self.watchStreamFailures >= kMaxWatchStreamFailures) {
+ [self clearOnlineStateTimer];
+ [self logClientOfflineWarningIfNecessary];
+ [self setAndBroadcastState:FSTOnlineStateOffline];
+ }
+ }
+}
+
+- (void)updateState:(FSTOnlineState)newState {
+ [self clearOnlineStateTimer];
+ self.watchStreamFailures = 0;
+
+ if (newState == FSTOnlineStateOnline) {
+ // We've connected to watch at least once. Don't warn the developer about being offline going
+ // forward.
+ self.shouldWarnClientIsOffline = NO;
+ }
+
+ [self setAndBroadcastState:newState];
+}
+
+- (void)setAndBroadcastState:(FSTOnlineState)newState {
+ if (newState != self.state) {
+ self.state = newState;
+ [self.onlineStateDelegate applyChangedOnlineState:newState];
+ }
+}
+
+- (void)logClientOfflineWarningIfNecessary {
+ if (self.shouldWarnClientIsOffline) {
+ FSTWarn(@"Could not reach Firestore backend.");
+ self.shouldWarnClientIsOffline = NO;
+ }
+}
+
+- (void)clearOnlineStateTimer {
+ if (self.onlineStateTimer) {
+ [self.onlineStateTimer cancel];
+ self.onlineStateTimer = nil;
+ }
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/Remote/FSTRemoteEvent.h b/Firestore/Source/Remote/FSTRemoteEvent.h
index 72f71a5..dd45117 100644
--- a/Firestore/Source/Remote/FSTRemoteEvent.h
+++ b/Firestore/Source/Remote/FSTRemoteEvent.h
@@ -16,12 +16,15 @@
#import <Foundation/Foundation.h>
+#include <map>
+
#import "Firestore/Source/Core/FSTTypes.h"
#import "Firestore/Source/Model/FSTDocumentDictionary.h"
#import "Firestore/Source/Model/FSTDocumentKeySet.h"
+#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+
@class FSTDocument;
-@class FSTDocumentKey;
@class FSTExistenceFilter;
@class FSTMaybeDocument;
@class FSTSnapshotVersion;
@@ -148,7 +151,7 @@ typedef NS_ENUM(NSUInteger, FSTCurrentStatusUpdate) {
eventWithSnapshotVersion:(FSTSnapshotVersion *)snapshotVersion
targetChanges:(NSMutableDictionary<NSNumber *, FSTTargetChange *> *)targetChanges
documentUpdates:
- (NSMutableDictionary<FSTDocumentKey *, FSTMaybeDocument *> *)documentUpdates;
+ (std::map<firebase::firestore::model::DocumentKey, FSTMaybeDocument *>)documentUpdates;
/** The snapshot version this event brings us up to. */
@property(nonatomic, strong, readonly) FSTSnapshotVersion *snapshotVersion;
@@ -161,8 +164,7 @@ eventWithSnapshotVersion:(FSTSnapshotVersion *)snapshotVersion
* A set of which documents have changed or been deleted, along with the doc's new values
* (if not deleted).
*/
-@property(nonatomic, strong, readonly)
- NSDictionary<FSTDocumentKey *, FSTMaybeDocument *> *documentUpdates;
+- (const std::map<firebase::firestore::model::DocumentKey, FSTMaybeDocument *> &)documentUpdates;
/** Adds a document update to this remote event */
- (void)addDocumentUpdate:(FSTMaybeDocument *)document;
diff --git a/Firestore/Source/Remote/FSTRemoteEvent.m b/Firestore/Source/Remote/FSTRemoteEvent.mm
index a97eb86..a3c85df 100644
--- a/Firestore/Source/Remote/FSTRemoteEvent.m
+++ b/Firestore/Source/Remote/FSTRemoteEvent.mm
@@ -16,14 +16,20 @@
#import "Firestore/Source/Remote/FSTRemoteEvent.h"
+#include <map>
+#include <utility>
+
#import "Firestore/Source/Core/FSTSnapshotVersion.h"
#import "Firestore/Source/Model/FSTDocument.h"
-#import "Firestore/Source/Model/FSTDocumentKey.h"
#import "Firestore/Source/Remote/FSTWatchChange.h"
#import "Firestore/Source/Util/FSTAssert.h"
#import "Firestore/Source/Util/FSTClasses.h"
#import "Firestore/Source/Util/FSTLogger.h"
+#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+
+using firebase::firestore::model::DocumentKey;
+
NS_ASSUME_NONNULL_BEGIN
#pragma mark - FSTTargetMapping
@@ -31,20 +37,20 @@ NS_ASSUME_NONNULL_BEGIN
@interface FSTTargetMapping ()
/** Private mutator method to add a document key to the mapping */
-- (void)addDocumentKey:(FSTDocumentKey *)documentKey;
+- (void)addDocumentKey:(const DocumentKey &)documentKey;
/** Private mutator method to remove a document key from the mapping */
-- (void)removeDocumentKey:(FSTDocumentKey *)documentKey;
+- (void)removeDocumentKey:(const DocumentKey &)documentKey;
@end
@implementation FSTTargetMapping
-- (void)addDocumentKey:(FSTDocumentKey *)documentKey {
+- (void)addDocumentKey:(const DocumentKey &)documentKey {
@throw FSTAbstractMethodException(); // NOLINT
}
-- (void)removeDocumentKey:(FSTDocumentKey *)documentKey {
+- (void)removeDocumentKey:(const DocumentKey &)documentKey {
@throw FSTAbstractMethodException(); // NOLINT
}
@@ -90,11 +96,11 @@ NS_ASSUME_NONNULL_BEGIN
return self.documents.hash;
}
-- (void)addDocumentKey:(FSTDocumentKey *)documentKey {
+- (void)addDocumentKey:(const DocumentKey &)documentKey {
self.documents = [self.documents setByAddingObject:documentKey];
}
-- (void)removeDocumentKey:(FSTDocumentKey *)documentKey {
+- (void)removeDocumentKey:(const DocumentKey &)documentKey {
self.documents = [self.documents setByRemovingObject:documentKey];
}
@@ -158,12 +164,12 @@ NS_ASSUME_NONNULL_BEGIN
return result;
}
-- (void)addDocumentKey:(FSTDocumentKey *)documentKey {
+- (void)addDocumentKey:(const DocumentKey &)documentKey {
self.addedDocuments = [self.addedDocuments setByAddingObject:documentKey];
self.removedDocuments = [self.removedDocuments setByRemovingObject:documentKey];
}
-- (void)removeDocumentKey:(FSTDocumentKey *)documentKey {
+- (void)removeDocumentKey:(const DocumentKey &)documentKey {
self.addedDocuments = [self.addedDocuments setByRemovingObject:documentKey];
self.removedDocuments = [self.removedDocuments setByAddingObject:documentKey];
}
@@ -238,46 +244,51 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark - FSTRemoteEvent
@interface FSTRemoteEvent () {
- NSMutableDictionary<FSTDocumentKey *, FSTMaybeDocument *> *_documentUpdates;
NSMutableDictionary<FSTBoxedTargetID *, FSTTargetChange *> *_targetChanges;
}
- (instancetype)
initWithSnapshotVersion:(FSTSnapshotVersion *)snapshotVersion
targetChanges:(NSMutableDictionary<FSTBoxedTargetID *, FSTTargetChange *> *)targetChanges
- documentUpdates:
- (NSMutableDictionary<FSTDocumentKey *, FSTMaybeDocument *> *)documentUpdates;
+ documentUpdates:(std::map<DocumentKey, FSTMaybeDocument *>)documentUpdates;
@property(nonatomic, strong) FSTSnapshotVersion *snapshotVersion;
@end
-@implementation FSTRemoteEvent
-
+@implementation FSTRemoteEvent {
+ std::map<DocumentKey, FSTMaybeDocument *> _documentUpdates;
+}
+ (instancetype)
eventWithSnapshotVersion:(FSTSnapshotVersion *)snapshotVersion
targetChanges:(NSMutableDictionary<NSNumber *, FSTTargetChange *> *)targetChanges
- documentUpdates:
- (NSMutableDictionary<FSTDocumentKey *, FSTMaybeDocument *> *)documentUpdates {
+ documentUpdates:(std::map<DocumentKey, FSTMaybeDocument *>)documentUpdates {
return [[FSTRemoteEvent alloc] initWithSnapshotVersion:snapshotVersion
targetChanges:targetChanges
- documentUpdates:documentUpdates];
+ documentUpdates:std::move(documentUpdates)];
}
-- (instancetype)
-initWithSnapshotVersion:(FSTSnapshotVersion *)snapshotVersion
- targetChanges:(NSMutableDictionary<NSNumber *, FSTTargetChange *> *)targetChanges
- documentUpdates:
- (NSMutableDictionary<FSTDocumentKey *, FSTMaybeDocument *> *)documentUpdates {
+- (instancetype)initWithSnapshotVersion:(FSTSnapshotVersion *)snapshotVersion
+ targetChanges:
+ (NSMutableDictionary<NSNumber *, FSTTargetChange *> *)targetChanges
+ documentUpdates:(std::map<DocumentKey, FSTMaybeDocument *>)documentUpdates {
self = [super init];
if (self) {
_snapshotVersion = snapshotVersion;
_targetChanges = targetChanges;
- _documentUpdates = documentUpdates;
+ _documentUpdates = std::move(documentUpdates);
}
return self;
}
+- (NSDictionary<FSTBoxedTargetID *, FSTTargetChange *> *)targetChanges {
+ return static_cast<NSDictionary<FSTBoxedTargetID *, FSTTargetChange *> *>(_targetChanges);
+}
+
+- (const std::map<DocumentKey, FSTMaybeDocument *> &)documentUpdates {
+ return _documentUpdates;
+}
+
/** Adds a document update to this remote event */
- (void)addDocumentUpdate:(FSTMaybeDocument *)document {
_documentUpdates[document.key] = document;
@@ -316,10 +327,6 @@ initWithSnapshotVersion:(FSTSnapshotVersion *)snapshotVersion
@property(nonatomic, strong, readonly)
NSMutableDictionary<FSTBoxedTargetID *, FSTTargetChange *> *targetChanges;
-/** Keeps track of document to update */
-@property(nonatomic, strong, readonly)
- NSMutableDictionary<FSTDocumentKey *, FSTMaybeDocument *> *documentUpdates;
-
/** The set of open listens on the client */
@property(nonatomic, strong, readonly)
NSDictionary<FSTBoxedTargetID *, FSTQueryData *> *listenTargets;
@@ -331,6 +338,8 @@ initWithSnapshotVersion:(FSTSnapshotVersion *)snapshotVersion
@implementation FSTWatchChangeAggregator {
NSMutableDictionary<FSTBoxedTargetID *, FSTExistenceFilter *> *_existenceFilters;
+ /** Keeps track of document to update */
+ std::map<DocumentKey, FSTMaybeDocument *> _documentUpdates;
}
- (instancetype)
@@ -347,11 +356,14 @@ initWithSnapshotVersion:(FSTSnapshotVersion *)snapshotVersion
_pendingTargetResponses = [NSMutableDictionary dictionaryWithDictionary:pendingTargetResponses];
_existenceFilters = [NSMutableDictionary dictionary];
- _documentUpdates = [NSMutableDictionary dictionary];
}
return self;
}
+- (NSDictionary<FSTBoxedTargetID *, FSTExistenceFilter *> *)existenceFilters {
+ return static_cast<NSDictionary<FSTBoxedTargetID *, FSTExistenceFilter *> *>(_existenceFilters);
+}
+
- (FSTTargetChange *)targetChangeForTargetID:(FSTBoxedTargetID *)targetID {
FSTTargetChange *change = self.targetChanges[targetID];
if (!change) {
@@ -404,7 +416,7 @@ initWithSnapshotVersion:(FSTSnapshotVersion *)snapshotVersion
// Only update the document if there is a new document to replace, this might be just a target
// update instead.
if (docChange.document && relevant) {
- self.documentUpdates[docChange.documentKey] = docChange.document;
+ _documentUpdates[docChange.documentKey] = docChange.document;
}
}
@@ -508,7 +520,7 @@ initWithSnapshotVersion:(FSTSnapshotVersion *)snapshotVersion
self.frozen = YES;
return [FSTRemoteEvent eventWithSnapshotVersion:self.snapshotVersion
targetChanges:targetChanges
- documentUpdates:self.documentUpdates];
+ documentUpdates:_documentUpdates];
}
@end
diff --git a/Firestore/Source/Remote/FSTRemoteStore.h b/Firestore/Source/Remote/FSTRemoteStore.h
index 313ddb7..09e1d32 100644
--- a/Firestore/Source/Remote/FSTRemoteStore.h
+++ b/Firestore/Source/Remote/FSTRemoteStore.h
@@ -19,9 +19,9 @@
#import "Firestore/Source/Core/FSTTypes.h"
#import "Firestore/Source/Model/FSTDocumentVersionDictionary.h"
-@class FSTDatabaseInfo;
+#include "Firestore/core/src/firebase/firestore/auth/user.h"
+
@class FSTDatastore;
-@class FSTDocumentKey;
@class FSTLocalStore;
@class FSTMutationBatch;
@class FSTMutationBatchResult;
@@ -29,7 +29,7 @@
@class FSTQueryData;
@class FSTRemoteEvent;
@class FSTTransaction;
-@class FSTUser;
+@class FSTDispatchQueue;
NS_ASSUME_NONNULL_BEGIN
@@ -58,7 +58,8 @@ NS_ASSUME_NONNULL_BEGIN
* will be an indication that the user is no longer authorized to see the data matching the
* target.
*/
-- (void)rejectListenWithTargetID:(FSTBoxedTargetID *)targetID error:(NSError *)error;
+- (void)rejectListenWithTargetID:(const firebase::firestore::model::TargetId)targetID
+ error:(NSError *)error;
/**
* Applies the result of a successful write of a mutation batch to the sync engine, emitting
@@ -83,7 +84,7 @@ NS_ASSUME_NONNULL_BEGIN
@protocol FSTOnlineStateDelegate <NSObject>
/** Called whenever the online state of the watch stream changes */
-- (void)watchStreamDidChangeOnlineState:(FSTOnlineState)onlineState;
+- (void)applyChangedOnlineState:(FSTOnlineState)onlineState;
@end
@@ -95,10 +96,11 @@ NS_ASSUME_NONNULL_BEGIN
*/
@interface FSTRemoteStore : NSObject
-+ (instancetype)remoteStoreWithLocalStore:(FSTLocalStore *)localStore
- datastore:(FSTDatastore *)datastore;
+- (instancetype)initWithLocalStore:(FSTLocalStore *)localStore
+ datastore:(FSTDatastore *)datastore
+ workerDispatchQueue:(FSTDispatchQueue *)queue;
-- (instancetype)init __attribute__((unavailable("Use static constructor method.")));
+- (instancetype)init NS_UNAVAILABLE;
@property(nonatomic, weak) id<FSTRemoteSyncer> syncEngine;
@@ -122,7 +124,7 @@ NS_ASSUME_NONNULL_BEGIN
* In response the remote store tears down streams and clears up any tracked operations that should
* not persist across users. Restarts the streams if appropriate.
*/
-- (void)userDidChange:(FSTUser *)user;
+- (void)userDidChange:(const firebase::firestore::auth::User &)user;
/** Listens to the target identified by the given FSTQueryData. */
- (void)listenToTargetWithQueryData:(FSTQueryData *)queryData;
diff --git a/Firestore/Source/Remote/FSTRemoteStore.m b/Firestore/Source/Remote/FSTRemoteStore.mm
index 063e487..39d285a 100644
--- a/Firestore/Source/Remote/FSTRemoteStore.m
+++ b/Firestore/Source/Remote/FSTRemoteStore.mm
@@ -16,23 +16,33 @@
#import "Firestore/Source/Remote/FSTRemoteStore.h"
+#include <cinttypes>
+
#import "Firestore/Source/Core/FSTQuery.h"
#import "Firestore/Source/Core/FSTSnapshotVersion.h"
#import "Firestore/Source/Core/FSTTransaction.h"
#import "Firestore/Source/Local/FSTLocalStore.h"
#import "Firestore/Source/Local/FSTQueryData.h"
#import "Firestore/Source/Model/FSTDocument.h"
-#import "Firestore/Source/Model/FSTDocumentKey.h"
#import "Firestore/Source/Model/FSTMutation.h"
#import "Firestore/Source/Model/FSTMutationBatch.h"
#import "Firestore/Source/Remote/FSTDatastore.h"
#import "Firestore/Source/Remote/FSTExistenceFilter.h"
+#import "Firestore/Source/Remote/FSTOnlineStateTracker.h"
#import "Firestore/Source/Remote/FSTRemoteEvent.h"
#import "Firestore/Source/Remote/FSTStream.h"
#import "Firestore/Source/Remote/FSTWatchChange.h"
#import "Firestore/Source/Util/FSTAssert.h"
#import "Firestore/Source/Util/FSTLogger.h"
+#include "Firestore/core/src/firebase/firestore/auth/user.h"
+#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+#include "Firestore/core/src/firebase/firestore/util/string_apple.h"
+
+namespace util = firebase::firestore::util;
+using firebase::firestore::auth::User;
+using firebase::firestore::model::DocumentKey;
+
NS_ASSUME_NONNULL_BEGIN
/**
@@ -41,21 +51,10 @@ NS_ASSUME_NONNULL_BEGIN
*/
static const int kMaxPendingWrites = 10;
-/**
- * The FSTRemoteStore notifies an onlineStateDelegate with FSTOnlineStateFailed if we fail to
- * connect to the backend. This subsequently triggers get() requests to fail or use cached data,
- * etc. Unfortunately, our connections have historically been subject to various transient failures.
- * So we wait for multiple failures before notifying the onlineStateDelegate.
- */
-static const int kOnlineAttemptsBeforeFailure = 2;
-
#pragma mark - FSTRemoteStore
@interface FSTRemoteStore () <FSTWatchStreamDelegate, FSTWriteStreamDelegate>
-- (instancetype)initWithLocalStore:(FSTLocalStore *)localStore
- datastore:(FSTDatastore *)datastore NS_DESIGNATED_INITIALIZER;
-
/**
* The local store, used to fill the write pipeline with outbound mutations and resolve existence
* filter mismatches. Immutable after initialization.
@@ -101,17 +100,7 @@ static const int kOnlineAttemptsBeforeFailure = 2;
@property(nonatomic, strong) NSMutableArray<FSTWatchChange *> *accumulatedChanges;
@property(nonatomic, assign) FSTBatchID lastBatchSeen;
-/**
- * The online state of the watch stream. The state is set to healthy if and only if there are
- * messages received by the backend.
- */
-@property(nonatomic, assign) FSTOnlineState watchStreamOnlineState;
-
-/** A count of consecutive failures to open the stream. */
-@property(nonatomic, assign) int watchStreamFailures;
-
-/** Whether the client should fire offline warning. */
-@property(nonatomic, assign) BOOL shouldWarnOffline;
+@property(nonatomic, strong, readonly) FSTOnlineStateTracker *onlineStateTracker;
#pragma mark Write Stream
// The writeStream is null when the network is disabled. The non-null check is performed by
@@ -119,12 +108,6 @@ static const int kOnlineAttemptsBeforeFailure = 2;
@property(nonatomic, strong, nullable) FSTWriteStream *writeStream;
/**
- * The approximate time the StreamingWrite stream was opened. Used to estimate if stream was
- * closed due to an auth expiration (a recoverable error) or some other more permanent error.
- */
-@property(nonatomic, strong, nullable) NSDate *writeStreamOpenTime;
-
-/**
* A FIFO queue of in-flight writes. This is in-flight from the point of view of the caller of
* writeMutations, not from the point of view from the Datastore itself. In particular, these
* requests may not have been sent to the Datastore server if the write stream is not yet running.
@@ -134,12 +117,9 @@ static const int kOnlineAttemptsBeforeFailure = 2;
@implementation FSTRemoteStore
-+ (instancetype)remoteStoreWithLocalStore:(FSTLocalStore *)localStore
- datastore:(FSTDatastore *)datastore {
- return [[FSTRemoteStore alloc] initWithLocalStore:localStore datastore:datastore];
-}
-
-- (instancetype)initWithLocalStore:(FSTLocalStore *)localStore datastore:(FSTDatastore *)datastore {
+- (instancetype)initWithLocalStore:(FSTLocalStore *)localStore
+ datastore:(FSTDatastore *)datastore
+ workerDispatchQueue:(FSTDispatchQueue *)queue {
if (self = [super init]) {
_localStore = localStore;
_datastore = datastore;
@@ -148,9 +128,8 @@ static const int kOnlineAttemptsBeforeFailure = 2;
_accumulatedChanges = [NSMutableArray array];
_lastBatchSeen = kFSTBatchIDUnknown;
- _watchStreamOnlineState = FSTOnlineStateUnknown;
- _shouldWarnOffline = YES;
_pendingWrites = [NSMutableArray array];
+ _onlineStateTracker = [[FSTOnlineStateTracker alloc] initWithWorkerDispatchQueue:queue];
}
return self;
}
@@ -160,45 +139,14 @@ static const int kOnlineAttemptsBeforeFailure = 2;
[self enableNetwork];
}
-- (void)setOnlineStateToHealthy {
- self.shouldWarnOffline = NO;
- [self updateAndNotifyAboutOnlineState:FSTOnlineStateHealthy];
-}
-
-- (void)setOnlineStateToUnknown {
- // The state is set to unknown when a healthy stream is closed (e.g. due to a token timeout) or
- // when we have no active listens and therefore there's no need to start the stream. Assuming
- // there is (possibly in the future) an active listen, then we will eventually move to state
- // Online or Failed, but we always want to make at least kOnlineAttemptsBeforeFailure attempts
- // before failing, so we reset the count here.
- self.watchStreamFailures = 0;
- [self updateAndNotifyAboutOnlineState:FSTOnlineStateUnknown];
-}
+@dynamic onlineStateDelegate;
-- (void)updateOnlineStateAfterFailure {
- // The first failure after we are successfully connected moves us to the 'Unknown' state. We
- // then may make multiple attempts (based on kOnlineAttemptsBeforeFailure) before we actually
- // report failure.
- if (self.watchStreamOnlineState == FSTOnlineStateHealthy) {
- [self setOnlineStateToUnknown];
- } else {
- self.watchStreamFailures++;
- if (self.watchStreamFailures >= kOnlineAttemptsBeforeFailure) {
- if (self.shouldWarnOffline) {
- FSTWarn(@"Could not reach Firestore backend.");
- self.shouldWarnOffline = NO;
- }
- [self updateAndNotifyAboutOnlineState:FSTOnlineStateFailed];
- }
- }
+- (nullable id<FSTOnlineStateDelegate>)onlineStateDelegate {
+ return self.onlineStateTracker.onlineStateDelegate;
}
-- (void)updateAndNotifyAboutOnlineState:(FSTOnlineState)watchStreamOnlineState {
- BOOL didChange = (watchStreamOnlineState != self.watchStreamOnlineState);
- self.watchStreamOnlineState = watchStreamOnlineState;
- if (didChange) {
- [self.onlineStateDelegate watchStreamDidChangeOnlineState:watchStreamOnlineState];
- }
+- (void)setOnlineStateDelegate:(nullable id<FSTOnlineStateDelegate>)delegate {
+ self.onlineStateTracker.onlineStateDelegate = delegate;
}
#pragma mark Online/Offline state
@@ -210,8 +158,9 @@ static const int kOnlineAttemptsBeforeFailure = 2;
}
- (void)enableNetwork {
- FSTAssert(self.watchStream == nil, @"enableNetwork: called with non-null watchStream.");
- FSTAssert(self.writeStream == nil, @"enableNetwork: called with non-null writeStream.");
+ if ([self isNetworkEnabled]) {
+ return;
+ }
// Create new streams (but note they're not started yet).
self.watchStream = [self.datastore createWatchStream];
@@ -222,60 +171,64 @@ static const int kOnlineAttemptsBeforeFailure = 2;
if ([self shouldStartWatchStream]) {
[self startWatchStream];
+ } else {
+ [self.onlineStateTracker updateState:FSTOnlineStateUnknown];
}
[self fillWritePipeline]; // This may start the writeStream.
-
- // We move back to the unknown state because we might not want to re-open the stream
- [self setOnlineStateToUnknown];
}
- (void)disableNetwork {
- [self updateAndNotifyAboutOnlineState:FSTOnlineStateFailed];
+ [self disableNetworkInternal];
+ // Set the FSTOnlineState to Offline so get()s return from cache, etc.
+ [self.onlineStateTracker updateState:FSTOnlineStateOffline];
+}
- // NOTE: We're guaranteed not to get any further events from these streams (not even a close
- // event).
- [self.watchStream stop];
- [self.writeStream stop];
+/** Disables the network, setting the FSTOnlineState to the specified targetOnlineState. */
+- (void)disableNetworkInternal {
+ if ([self isNetworkEnabled]) {
+ // NOTE: We're guaranteed not to get any further events from these streams (not even a close
+ // event).
+ [self.watchStream stop];
+ [self.writeStream stop];
- [self cleanUpWatchStreamState];
- [self cleanUpWriteStreamState];
+ [self cleanUpWatchStreamState];
+ [self cleanUpWriteStreamState];
- self.writeStream = nil;
- self.watchStream = nil;
+ self.writeStream = nil;
+ self.watchStream = nil;
+ }
}
#pragma mark Shutdown
- (void)shutdown {
FSTLog(@"FSTRemoteStore %p shutting down", (__bridge void *)self);
+ [self disableNetworkInternal];
+ // Set the FSTOnlineState to Unknown (rather than Offline) to avoid potentially triggering
+ // spurious listener events with cached data, etc.
+ [self.onlineStateTracker updateState:FSTOnlineStateUnknown];
+}
- // Don't fire initial listener callbacks on shutdown.
- self.onlineStateDelegate = nil;
-
- // For now, all shutdown logic is handled by disableNetwork(). We might expand on this in the
- // future.
+- (void)userDidChange:(const User &)user {
+ FSTLog(@"FSTRemoteStore %p changing users: %s", (__bridge void *)self, user.uid().c_str());
if ([self isNetworkEnabled]) {
- [self disableNetwork];
+ // Tear down and re-create our network streams. This will ensure we get a fresh auth token
+ // for the new user and re-fill the write pipeline with new mutations from the LocalStore
+ // (since mutations are per-user).
+ [self disableNetworkInternal];
+ [self.onlineStateTracker updateState:FSTOnlineStateUnknown];
+ [self enableNetwork];
}
}
-- (void)userDidChange:(FSTUser *)user {
- FSTLog(@"FSTRemoteStore %p changing users: %@", (__bridge void *)self, user);
-
- // Tear down and re-create our network streams. This will ensure we get a fresh auth token
- // for the new user and re-fill the write pipeline with new mutations from the LocalStore
- // (since mutations are per-user).
- [self disableNetwork];
- [self enableNetwork];
-}
-
#pragma mark Watch Stream
- (void)startWatchStream {
FSTAssert([self shouldStartWatchStream],
@"startWatchStream: called when shouldStartWatchStream: is false.");
[self.watchStream startWithDelegate:self];
+ [self.onlineStateTracker handleWatchStreamStart];
}
- (void)listenToTargetWithQueryData:(FSTQueryData *)queryData {
@@ -347,8 +300,8 @@ static const int kOnlineAttemptsBeforeFailure = 2;
- (void)watchStreamDidChange:(FSTWatchChange *)change
snapshotVersion:(FSTSnapshotVersion *)snapshotVersion {
- // Mark the connection as healthy because we got a message from the server.
- [self setOnlineStateToHealthy];
+ // Mark the connection as Online because we got a message from the server.
+ [self.onlineStateTracker updateState:FSTOnlineStateOnline];
FSTWatchTargetChange *watchTargetChange =
[change isKindOfClass:[FSTWatchTargetChange class]] ? (FSTWatchTargetChange *)change : nil;
@@ -379,19 +332,20 @@ static const int kOnlineAttemptsBeforeFailure = 2;
- (void)watchStreamWasInterruptedWithError:(nullable NSError *)error {
FSTAssert([self isNetworkEnabled],
- @"watchStreamDidClose should only be called when the network is enabled");
+ @"watchStreamWasInterruptedWithError: should only be called when the network is "
+ "enabled");
[self cleanUpWatchStreamState];
+ [self.onlineStateTracker handleWatchStreamFailure];
// If the watch stream closed due to an error, retry the connection if there are any active
// watch targets.
if ([self shouldStartWatchStream]) {
- [self updateOnlineStateAfterFailure];
[self startWatchStream];
} else {
// We don't need to restart the watch stream because there are no active targets. The online
// state is set to unknown because there is no active attempt at establishing a connection.
- [self setOnlineStateToUnknown];
+ [self.onlineStateTracker updateState:FSTOnlineStateUnknown];
}
}
@@ -429,7 +383,7 @@ static const int kOnlineAttemptsBeforeFailure = 2;
// updates. Without applying a deleted document there might be another query that will
// raise this document as part of a snapshot until it is resolved, essentially exposing
// inconsistency between queries
- FSTDocumentKey *key = [FSTDocumentKey keyWithPath:query.path];
+ const DocumentKey key{query.path};
FSTDeletedDocument *deletedDoc =
[FSTDeletedDocument documentWithKey:key version:snapshotVersion];
[remoteEvent addDocumentUpdate:deletedDoc];
@@ -460,8 +414,10 @@ static const int kOnlineAttemptsBeforeFailure = 2;
[remoteEvent handleExistenceFilterMismatchForTargetID:target];
// Clear the resume token for the query, since we're in a known mismatch state.
- queryData =
- [[FSTQueryData alloc] initWithQuery:query targetID:targetID purpose:queryData.purpose];
+ queryData = [[FSTQueryData alloc] initWithQuery:query
+ targetID:targetID
+ listenSequenceNumber:queryData.sequenceNumber
+ purpose:queryData.purpose];
self.listenTargets[target] = queryData;
// Cause a hard reset by unwatching and rewatching immediately, but deliberately don't
@@ -475,6 +431,7 @@ static const int kOnlineAttemptsBeforeFailure = 2;
FSTQueryData *requestQueryData =
[[FSTQueryData alloc] initWithQuery:query
targetID:targetID
+ listenSequenceNumber:queryData.sequenceNumber
purpose:FSTQueryPurposeExistenceFilterMismatch];
[self sendWatchRequestWithQueryData:requestQueryData];
}
@@ -487,10 +444,10 @@ static const int kOnlineAttemptsBeforeFailure = 2;
FSTBoxedTargetID *target, FSTTargetChange *change, BOOL *stop) {
NSData *resumeToken = change.resumeToken;
if (resumeToken.length > 0) {
- FSTQueryData *queryData = _listenTargets[target];
+ FSTQueryData *queryData = self->_listenTargets[target];
// A watched target might have been removed already.
if (queryData) {
- _listenTargets[target] =
+ self->_listenTargets[target] =
[queryData queryDataByReplacingSnapshotVersion:change.snapshotVersion
resumeToken:resumeToken];
}
@@ -508,7 +465,7 @@ static const int kOnlineAttemptsBeforeFailure = 2;
for (FSTBoxedTargetID *targetID in change.targetIDs) {
if (self.listenTargets[targetID]) {
[self.listenTargets removeObjectForKey:targetID];
- [self.syncEngine rejectListenWithTargetID:targetID error:change.cause];
+ [self.syncEngine rejectListenWithTargetID:[targetID intValue] error:change.cause];
}
}
}
@@ -532,6 +489,8 @@ static const int kOnlineAttemptsBeforeFailure = 2;
- (void)cleanUpWriteStreamState {
self.lastBatchSeen = kFSTBatchIDUnknown;
+ FSTLog(@"Stopping write stream with %lu pending writes",
+ (unsigned long)[self.pendingWrites count]);
[self.pendingWrites removeAllObjects];
}
@@ -580,8 +539,6 @@ static const int kOnlineAttemptsBeforeFailure = 2;
}
- (void)writeStreamDidOpen {
- self.writeStreamOpenTime = [NSDate date];
-
[self.writeStream writeHandshake];
}
diff --git a/Firestore/Source/Remote/FSTSerializerBeta.h b/Firestore/Source/Remote/FSTSerializerBeta.h
index 973f866..d96dbeb 100644
--- a/Firestore/Source/Remote/FSTSerializerBeta.h
+++ b/Firestore/Source/Remote/FSTSerializerBeta.h
@@ -16,8 +16,9 @@
#import <Foundation/Foundation.h>
-@class FSTDatabaseID;
-@class FSTDocumentKey;
+#include "Firestore/core/src/firebase/firestore/model/database_id.h"
+#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+
@class FSTFieldValue;
@class FSTMaybeDocument;
@class FSTMutation;
@@ -27,7 +28,7 @@
@class FSTQuery;
@class FSTQueryData;
@class FSTSnapshotVersion;
-@class FSTTimestamp;
+@class FIRTimestamp;
@class FSTWatchChange;
@class GCFSBatchGetDocumentsResponse;
@@ -57,10 +58,11 @@ NS_ASSUME_NONNULL_BEGIN
- (instancetype)init NS_UNAVAILABLE;
-- (instancetype)initWithDatabaseID:(FSTDatabaseID *)databaseID NS_DESIGNATED_INITIALIZER;
+- (instancetype)initWithDatabaseID:(const firebase::firestore::model::DatabaseId *)databaseID
+ NS_DESIGNATED_INITIALIZER;
-- (GPBTimestamp *)encodedTimestamp:(FSTTimestamp *)timestamp;
-- (FSTTimestamp *)decodedTimestamp:(GPBTimestamp *)timestamp;
+- (GPBTimestamp *)encodedTimestamp:(FIRTimestamp *)timestamp;
+- (FIRTimestamp *)decodedTimestamp:(GPBTimestamp *)timestamp;
- (GPBTimestamp *)encodedVersion:(FSTSnapshotVersion *)version;
- (FSTSnapshotVersion *)decodedVersion:(GPBTimestamp *)version;
@@ -68,8 +70,8 @@ NS_ASSUME_NONNULL_BEGIN
/** Returns the database ID, such as `projects/{project id}/databases/{database_id}`. */
- (NSString *)encodedDatabaseID;
-- (NSString *)encodedDocumentKey:(FSTDocumentKey *)key;
-- (FSTDocumentKey *)decodedDocumentKey:(NSString *)key;
+- (NSString *)encodedDocumentKey:(const firebase::firestore::model::DocumentKey &)key;
+- (firebase::firestore::model::DocumentKey)decodedDocumentKey:(NSString *)key;
- (GCFSValue *)encodedFieldValue:(FSTFieldValue *)fieldValue;
- (FSTFieldValue *)decodedFieldValue:(GCFSValue *)valueProto;
@@ -93,7 +95,8 @@ NS_ASSUME_NONNULL_BEGIN
- (FSTWatchChange *)decodedWatchChange:(GCFSListenResponse *)watchChange;
- (FSTSnapshotVersion *)versionFromListenResponse:(GCFSListenResponse *)watchChange;
-- (GCFSDocument *)encodedDocumentWithFields:(FSTObjectValue *)objectValue key:(FSTDocumentKey *)key;
+- (GCFSDocument *)encodedDocumentWithFields:(FSTObjectValue *)objectValue
+ key:(const firebase::firestore::model::DocumentKey &)key;
/**
* Encodes an FSTObjectValue into a dictionary.
diff --git a/Firestore/Source/Remote/FSTSerializerBeta.m b/Firestore/Source/Remote/FSTSerializerBeta.mm
index 04785c2..c8b0fa4 100644
--- a/Firestore/Source/Remote/FSTSerializerBeta.m
+++ b/Firestore/Source/Remote/FSTSerializerBeta.mm
@@ -18,6 +18,11 @@
#import <GRPCClient/GRPCCall.h>
+#include <cinttypes>
+#include <string>
+#include <utility>
+#include <vector>
+
#import "Firestore/Protos/objc/google/firestore/v1beta1/Common.pbobjc.h"
#import "Firestore/Protos/objc/google/firestore/v1beta1/Document.pbobjc.h"
#import "Firestore/Protos/objc/google/firestore/v1beta1/Firestore.pbobjc.h"
@@ -28,30 +33,51 @@
#import "FIRFirestoreErrors.h"
#import "FIRGeoPoint.h"
+#import "FIRTimestamp.h"
#import "Firestore/Source/Core/FSTQuery.h"
#import "Firestore/Source/Core/FSTSnapshotVersion.h"
-#import "Firestore/Source/Core/FSTTimestamp.h"
#import "Firestore/Source/Local/FSTQueryData.h"
-#import "Firestore/Source/Model/FSTDatabaseID.h"
#import "Firestore/Source/Model/FSTDocument.h"
-#import "Firestore/Source/Model/FSTDocumentKey.h"
#import "Firestore/Source/Model/FSTFieldValue.h"
#import "Firestore/Source/Model/FSTMutation.h"
#import "Firestore/Source/Model/FSTMutationBatch.h"
-#import "Firestore/Source/Model/FSTPath.h"
#import "Firestore/Source/Remote/FSTExistenceFilter.h"
#import "Firestore/Source/Remote/FSTWatchChange.h"
#import "Firestore/Source/Util/FSTAssert.h"
+#include "Firestore/core/src/firebase/firestore/model/database_id.h"
+#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+#include "Firestore/core/src/firebase/firestore/model/field_mask.h"
+#include "Firestore/core/src/firebase/firestore/model/field_path.h"
+#include "Firestore/core/src/firebase/firestore/model/field_transform.h"
+#include "Firestore/core/src/firebase/firestore/model/precondition.h"
+#include "Firestore/core/src/firebase/firestore/model/resource_path.h"
+#include "Firestore/core/src/firebase/firestore/model/transform_operations.h"
+#include "Firestore/core/src/firebase/firestore/util/string_apple.h"
+#include "absl/memory/memory.h"
+
+namespace util = firebase::firestore::util;
+using firebase::firestore::model::DatabaseId;
+using firebase::firestore::model::DocumentKey;
+using firebase::firestore::model::FieldMask;
+using firebase::firestore::model::FieldPath;
+using firebase::firestore::model::FieldTransform;
+using firebase::firestore::model::Precondition;
+using firebase::firestore::model::ResourcePath;
+using firebase::firestore::model::ServerTimestampTransform;
+using firebase::firestore::model::SnapshotVersion;
+using firebase::firestore::model::TransformOperation;
+
NS_ASSUME_NONNULL_BEGIN
@interface FSTSerializerBeta ()
-@property(nonatomic, strong, readonly) FSTDatabaseID *databaseID;
+// Does not own this DatabaseId.
+@property(nonatomic, assign, readonly) const DatabaseId *databaseID;
@end
@implementation FSTSerializerBeta
-- (instancetype)initWithDatabaseID:(FSTDatabaseID *)databaseID {
+- (instancetype)initWithDatabaseID:(const DatabaseId *)databaseID {
self = [super init];
if (self) {
_databaseID = databaseID;
@@ -61,15 +87,15 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark - FSTSnapshotVersion <=> GPBTimestamp
-- (GPBTimestamp *)encodedTimestamp:(FSTTimestamp *)timestamp {
+- (GPBTimestamp *)encodedTimestamp:(FIRTimestamp *)timestamp {
GPBTimestamp *result = [GPBTimestamp message];
result.seconds = timestamp.seconds;
- result.nanos = timestamp.nanos;
+ result.nanos = timestamp.nanoseconds;
return result;
}
-- (FSTTimestamp *)decodedTimestamp:(GPBTimestamp *)timestamp {
- return [[FSTTimestamp alloc] initWithSeconds:timestamp.seconds nanos:timestamp.nanos];
+- (FIRTimestamp *)decodedTimestamp:(GPBTimestamp *)timestamp {
+ return [[FIRTimestamp alloc] initWithSeconds:timestamp.seconds nanoseconds:timestamp.nanos];
}
- (GPBTimestamp *)encodedVersion:(FSTSnapshotVersion *)version {
@@ -93,109 +119,110 @@ NS_ASSUME_NONNULL_BEGIN
return [[FIRGeoPoint alloc] initWithLatitude:latLng.latitude longitude:latLng.longitude];
}
-#pragma mark - FSTDocumentKey <=> Key proto
+#pragma mark - DocumentKey <=> Key proto
-- (NSString *)encodedDocumentKey:(FSTDocumentKey *)key {
- return [self encodedResourcePathForDatabaseID:self.databaseID path:key.path];
+- (NSString *)encodedDocumentKey:(const DocumentKey &)key {
+ return [self encodedResourcePathForDatabaseID:self.databaseID path:key.path()];
}
-- (FSTDocumentKey *)decodedDocumentKey:(NSString *)name {
- FSTResourcePath *path = [self decodedResourcePathWithDatabaseID:name];
- FSTAssert([[path segmentAtIndex:1] isEqualToString:self.databaseID.projectID],
+- (DocumentKey)decodedDocumentKey:(NSString *)name {
+ const ResourcePath path = [self decodedResourcePathWithDatabaseID:name];
+ FSTAssert(path[1] == self.databaseID->project_id(),
@"Tried to deserialize key from different project.");
- FSTAssert([[path segmentAtIndex:3] isEqualToString:self.databaseID.databaseID],
+ FSTAssert(path[3] == self.databaseID->database_id(),
@"Tried to deserialize key from different datbase.");
- return [FSTDocumentKey keyWithPath:[self localResourcePathForQualifiedResourcePath:path]];
+ return DocumentKey{[self localResourcePathForQualifiedResourcePath:path]};
}
-- (NSString *)encodedResourcePathForDatabaseID:(FSTDatabaseID *)databaseID
- path:(FSTResourcePath *)path {
- return [[[[self encodedResourcePathForDatabaseID:databaseID] pathByAppendingSegment:@"documents"]
- pathByAppendingPath:path] canonicalString];
+- (NSString *)encodedResourcePathForDatabaseID:(const DatabaseId *)databaseID
+ path:(const ResourcePath &)path {
+ return util::WrapNSString([self encodedResourcePathForDatabaseID:databaseID]
+ .Append("documents")
+ .Append(path)
+ .CanonicalString());
}
-- (FSTResourcePath *)decodedResourcePathWithDatabaseID:(NSString *)name {
- FSTResourcePath *path = [FSTResourcePath pathWithString:name];
- FSTAssert([self validQualifiedResourcePath:path], @"Tried to deserialize invalid key %@", path);
+- (ResourcePath)decodedResourcePathWithDatabaseID:(NSString *)name {
+ const ResourcePath path = ResourcePath::FromString(util::MakeStringView(name));
+ FSTAssert([self validQualifiedResourcePath:path], @"Tried to deserialize invalid key %s",
+ path.CanonicalString().c_str());
return path;
}
-- (NSString *)encodedQueryPath:(FSTResourcePath *)path {
- if (path.length == 0) {
+- (NSString *)encodedQueryPath:(const ResourcePath &)path {
+ if (path.size() == 0) {
// If the path is empty, the backend requires we leave off the /documents at the end.
return [self encodedDatabaseID];
}
return [self encodedResourcePathForDatabaseID:self.databaseID path:path];
}
-- (FSTResourcePath *)decodedQueryPath:(NSString *)name {
- FSTResourcePath *resource = [self decodedResourcePathWithDatabaseID:name];
- if (resource.length == 4) {
- return [FSTResourcePath pathWithSegments:@[]];
+- (ResourcePath)decodedQueryPath:(NSString *)name {
+ const ResourcePath resource = [self decodedResourcePathWithDatabaseID:name];
+ if (resource.size() == 4) {
+ return ResourcePath{};
} else {
return [self localResourcePathForQualifiedResourcePath:resource];
}
}
-- (FSTResourcePath *)encodedResourcePathForDatabaseID:(FSTDatabaseID *)databaseID {
- return [FSTResourcePath
- pathWithSegments:@[ @"projects", databaseID.projectID, @"databases", databaseID.databaseID ]];
+- (ResourcePath)encodedResourcePathForDatabaseID:(const DatabaseId *)databaseID {
+ return ResourcePath{"projects", databaseID->project_id(), "databases", databaseID->database_id()};
}
-- (FSTResourcePath *)localResourcePathForQualifiedResourcePath:(FSTResourcePath *)resourceName {
- FSTAssert(
- resourceName.length > 4 && [[resourceName segmentAtIndex:4] isEqualToString:@"documents"],
- @"Tried to deserialize invalid key %@", resourceName);
- return [resourceName pathByRemovingFirstSegments:5];
+- (ResourcePath)localResourcePathForQualifiedResourcePath:(const ResourcePath &)resourceName {
+ FSTAssert(resourceName.size() > 4 && resourceName[4] == "documents",
+ @"Tried to deserialize invalid key %s", resourceName.CanonicalString().c_str());
+ return resourceName.PopFirst(5);
}
-- (BOOL)validQualifiedResourcePath:(FSTResourcePath *)path {
- return path.length >= 4 && [[path segmentAtIndex:0] isEqualToString:@"projects"] &&
- [[path segmentAtIndex:2] isEqualToString:@"databases"];
+- (BOOL)validQualifiedResourcePath:(const ResourcePath &)path {
+ return path.size() >= 4 && path[0] == "projects" && path[2] == "databases";
}
- (NSString *)encodedDatabaseID {
- return [[self encodedResourcePathForDatabaseID:self.databaseID] canonicalString];
+ return util::WrapNSString(
+ [self encodedResourcePathForDatabaseID:self.databaseID].CanonicalString());
}
#pragma mark - FSTFieldValue <=> Value proto
- (GCFSValue *)encodedFieldValue:(FSTFieldValue *)fieldValue {
- Class class = [fieldValue class];
- if (class == [FSTNullValue class]) {
+ Class fieldClass = [fieldValue class];
+ if (fieldClass == [FSTNullValue class]) {
return [self encodedNull];
- } else if (class == [FSTBooleanValue class]) {
+ } else if (fieldClass == [FSTBooleanValue class]) {
return [self encodedBool:[[fieldValue value] boolValue]];
- } else if (class == [FSTIntegerValue class]) {
+ } else if (fieldClass == [FSTIntegerValue class]) {
return [self encodedInteger:[[fieldValue value] longLongValue]];
- } else if (class == [FSTDoubleValue class]) {
+ } else if (fieldClass == [FSTDoubleValue class]) {
return [self encodedDouble:[[fieldValue value] doubleValue]];
- } else if (class == [FSTStringValue class]) {
+ } else if (fieldClass == [FSTStringValue class]) {
return [self encodedString:[fieldValue value]];
- } else if (class == [FSTTimestampValue class]) {
- return [self encodedTimestampValue:((FSTTimestampValue *)fieldValue).internalValue];
+ } else if (fieldClass == [FSTTimestampValue class]) {
+ return [self encodedTimestampValue:[fieldValue value]];
- } else if (class == [FSTGeoPointValue class]) {
+ } else if (fieldClass == [FSTGeoPointValue class]) {
return [self encodedGeoPointValue:[fieldValue value]];
- } else if (class == [FSTBlobValue class]) {
+ } else if (fieldClass == [FSTBlobValue class]) {
return [self encodedBlobValue:[fieldValue value]];
- } else if (class == [FSTReferenceValue class]) {
+ } else if (fieldClass == [FSTReferenceValue class]) {
FSTReferenceValue *ref = (FSTReferenceValue *)fieldValue;
return [self encodedReferenceValueForDatabaseID:[ref databaseID] key:[ref value]];
- } else if (class == [FSTObjectValue class]) {
+ } else if (fieldClass == [FSTObjectValue class]) {
GCFSValue *result = [GCFSValue message];
result.mapValue = [self encodedMapValue:(FSTObjectValue *)fieldValue];
return result;
- } else if (class == [FSTArrayValue class]) {
+ } else if (fieldClass == [FSTArrayValue class]) {
GCFSValue *result = [GCFSValue message];
result.arrayValue = [self encodedArrayValue:(FSTArrayValue *)fieldValue];
return result;
@@ -275,7 +302,7 @@ NS_ASSUME_NONNULL_BEGIN
return result;
}
-- (GCFSValue *)encodedTimestampValue:(FSTTimestamp *)value {
+- (GCFSValue *)encodedTimestampValue:(FIRTimestamp *)value {
GCFSValue *result = [GCFSValue message];
result.timestampValue = [self encodedTimestamp:value];
return result;
@@ -293,21 +320,27 @@ NS_ASSUME_NONNULL_BEGIN
return result;
}
-- (GCFSValue *)encodedReferenceValueForDatabaseID:(FSTDatabaseID *)databaseID
- key:(FSTDocumentKey *)key {
+- (GCFSValue *)encodedReferenceValueForDatabaseID:(const DatabaseId *)databaseID
+ key:(const DocumentKey &)key {
+ FSTAssert(*databaseID == *self.databaseID, @"Database %s:%s cannot encode reference from %s:%s",
+ self.databaseID->project_id().c_str(), self.databaseID->database_id().c_str(),
+ databaseID->project_id().c_str(), databaseID->database_id().c_str());
GCFSValue *result = [GCFSValue message];
- result.referenceValue = [self encodedResourcePathForDatabaseID:databaseID path:key.path];
+ result.referenceValue = [self encodedResourcePathForDatabaseID:databaseID path:key.path()];
return result;
}
- (FSTReferenceValue *)decodedReferenceValue:(NSString *)resourceName {
- FSTResourcePath *path = [self decodedResourcePathWithDatabaseID:resourceName];
- NSString *project = [path segmentAtIndex:1];
- NSString *database = [path segmentAtIndex:3];
- FSTDatabaseID *databaseID = [FSTDatabaseID databaseIDWithProject:project database:database];
- FSTDocumentKey *key =
- [FSTDocumentKey keyWithPath:[self localResourcePathForQualifiedResourcePath:path]];
- return [FSTReferenceValue referenceValue:key databaseID:databaseID];
+ const ResourcePath path = [self decodedResourcePathWithDatabaseID:resourceName];
+ const std::string &project = path[1];
+ const std::string &database = path[3];
+ const DocumentKey key{[self localResourcePathForQualifiedResourcePath:path]};
+
+ const DatabaseId database_id(project, database);
+ FSTAssert(database_id == *self.databaseID, @"Database %s:%s cannot encode reference from %s:%s",
+ self.databaseID->project_id().c_str(), self.databaseID->database_id().c_str(),
+ database_id.project_id().c_str(), database_id.database_id().c_str());
+ return [FSTReferenceValue referenceValue:key databaseID:self.databaseID];
}
- (GCFSArrayValue *)encodedArrayValue:(FSTArrayValue *)arrayValue {
@@ -361,7 +394,7 @@ NS_ASSUME_NONNULL_BEGIN
__block FSTObjectValue *result = [FSTObjectValue objectValue];
[fields enumerateKeysAndObjectsUsingBlock:^(NSString *_Nonnull key, GCFSValue *_Nonnull obj,
BOOL *_Nonnull stop) {
- FSTFieldPath *path = [FSTFieldPath pathWithSegments:@[ key ]];
+ FieldPath path{util::MakeString(key)};
FSTFieldValue *value = [self decodedFieldValue:obj];
result = [result objectBySettingValue:value forPath:path];
}];
@@ -371,7 +404,7 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark - FSTObjectValue <=> Document proto
- (GCFSDocument *)encodedDocumentWithFields:(FSTObjectValue *)objectValue
- key:(FSTDocumentKey *)key {
+ key:(const DocumentKey &)key {
GCFSDocument *proto = [GCFSDocument message];
proto.name = [self encodedDocumentKey:key];
proto.fields = [self encodedFields:objectValue];
@@ -393,7 +426,7 @@ NS_ASSUME_NONNULL_BEGIN
- (FSTDocument *)decodedFoundDocument:(GCFSBatchGetDocumentsResponse *)response {
FSTAssert(!!response.found, @"Tried to deserialize a found document from a deleted document.");
- FSTDocumentKey *key = [self decodedDocumentKey:response.found.name];
+ const DocumentKey key = [self decodedDocumentKey:response.found.name];
FSTObjectValue *value = [self decodedFields:response.found.fields];
FSTSnapshotVersion *version = [self decodedVersion:response.found.updateTime];
FSTAssert(![version isEqual:[FSTSnapshotVersion noVersion]],
@@ -404,7 +437,7 @@ NS_ASSUME_NONNULL_BEGIN
- (FSTDeletedDocument *)decodedDeletedDocument:(GCFSBatchGetDocumentsResponse *)response {
FSTAssert(!!response.missing, @"Tried to deserialize a deleted document from a found document.");
- FSTDocumentKey *key = [self decodedDocumentKey:response.missing];
+ const DocumentKey key = [self decodedDocumentKey:response.missing];
FSTSnapshotVersion *version = [self decodedVersion:response.readTime];
FSTAssert(![version isEqual:[FSTSnapshotVersion noVersion]],
@"Got a no document response with no snapshot version");
@@ -438,14 +471,14 @@ NS_ASSUME_NONNULL_BEGIN
proto.currentDocument.exists = YES;
} else if (mutationClass == [FSTDeleteMutation class]) {
- FSTDeleteMutation *delete = (FSTDeleteMutation *)mutation;
- proto.delete_p = [self encodedDocumentKey:delete.key];
+ FSTDeleteMutation *deleteMutation = (FSTDeleteMutation *)mutation;
+ proto.delete_p = [self encodedDocumentKey:deleteMutation.key];
} else {
FSTFail(@"Unknown mutation type %@", NSStringFromClass(mutationClass));
}
- if (!mutation.precondition.isNone) {
+ if (!mutation.precondition.IsNone()) {
proto.currentDocument = [self encodedPrecondition:mutation.precondition];
}
@@ -453,9 +486,9 @@ NS_ASSUME_NONNULL_BEGIN
}
- (FSTMutation *)decodedMutation:(GCFSWrite *)mutation {
- FSTPrecondition *precondition = [mutation hasCurrentDocument]
- ? [self decodedPrecondition:mutation.currentDocument]
- : [FSTPrecondition none];
+ Precondition precondition = [mutation hasCurrentDocument]
+ ? [self decodedPrecondition:mutation.currentDocument]
+ : Precondition::None();
switch (mutation.operationOneOfCase) {
case GCFSWrite_Operation_OneOfCase_Update:
@@ -475,8 +508,7 @@ NS_ASSUME_NONNULL_BEGIN
precondition:precondition];
case GCFSWrite_Operation_OneOfCase_Transform: {
- FSTPreconditionExists exists = precondition.exists;
- FSTAssert(exists == FSTPreconditionExistsYes,
+ FSTAssert(precondition == Precondition::Exists(true),
@"Transforms must have precondition \"exists == true\"");
return [[FSTTransformMutation alloc]
@@ -490,78 +522,77 @@ NS_ASSUME_NONNULL_BEGIN
}
}
-- (GCFSPrecondition *)encodedPrecondition:(FSTPrecondition *)precondition {
- FSTAssert(!precondition.isNone, @"Can't serialize an empty precondition");
+- (GCFSPrecondition *)encodedPrecondition:(const Precondition &)precondition {
+ FSTAssert(!precondition.IsNone(), @"Can't serialize an empty precondition");
GCFSPrecondition *message = [GCFSPrecondition message];
- if (precondition.updateTime) {
- message.updateTime = [self encodedVersion:precondition.updateTime];
- } else if (precondition.exists != FSTPreconditionExistsNotSet) {
- message.exists = precondition.exists == FSTPreconditionExistsYes;
+ if (precondition.type() == Precondition::Type::UpdateTime) {
+ message.updateTime = [self encodedVersion:precondition.update_time()];
+ } else if (precondition.type() == Precondition::Type::Exists) {
+ message.exists = precondition == Precondition::Exists(true);
} else {
- FSTFail(@"Unknown precondition: %@", precondition);
+ FSTFail(@"Unknown precondition: %@", precondition.description());
}
return message;
}
-- (FSTPrecondition *)decodedPrecondition:(GCFSPrecondition *)precondition {
+- (Precondition)decodedPrecondition:(GCFSPrecondition *)precondition {
switch (precondition.conditionTypeOneOfCase) {
case GCFSPrecondition_ConditionType_OneOfCase_GPBUnsetOneOfCase:
- return [FSTPrecondition none];
+ return Precondition::None();
case GCFSPrecondition_ConditionType_OneOfCase_Exists:
- return [FSTPrecondition preconditionWithExists:precondition.exists];
+ return Precondition::Exists(precondition.exists);
case GCFSPrecondition_ConditionType_OneOfCase_UpdateTime:
- return [FSTPrecondition
- preconditionWithUpdateTime:[self decodedVersion:precondition.updateTime]];
+ return Precondition::UpdateTime([self decodedVersion:precondition.updateTime]);
default:
FSTFail(@"Unrecognized Precondition one-of case %@", precondition);
}
}
-- (GCFSDocumentMask *)encodedFieldMask:(FSTFieldMask *)fieldMask {
+- (GCFSDocumentMask *)encodedFieldMask:(const FieldMask &)fieldMask {
GCFSDocumentMask *mask = [GCFSDocumentMask message];
- for (FSTFieldPath *field in fieldMask.fields) {
- [mask.fieldPathsArray addObject:field.canonicalString];
+ for (const FieldPath &field : fieldMask) {
+ [mask.fieldPathsArray addObject:util::WrapNSString(field.CanonicalString())];
}
return mask;
}
-- (FSTFieldMask *)decodedFieldMask:(GCFSDocumentMask *)fieldMask {
- NSMutableArray<FSTFieldPath *> *fields =
- [NSMutableArray arrayWithCapacity:fieldMask.fieldPathsArray_Count];
+- (FieldMask)decodedFieldMask:(GCFSDocumentMask *)fieldMask {
+ std::vector<FieldPath> fields;
+ fields.reserve(fieldMask.fieldPathsArray_Count);
for (NSString *path in fieldMask.fieldPathsArray) {
- [fields addObject:[FSTFieldPath pathWithServerFormat:path]];
+ fields.push_back(FieldPath::FromServerFormat(util::MakeStringView(path)));
}
- return [[FSTFieldMask alloc] initWithFields:fields];
+ return FieldMask(std::move(fields));
}
- (NSMutableArray<GCFSDocumentTransform_FieldTransform *> *)encodedFieldTransforms:
- (NSArray<FSTFieldTransform *> *)fieldTransforms {
+ (const std::vector<FieldTransform> &)fieldTransforms {
NSMutableArray *protos = [NSMutableArray array];
- for (FSTFieldTransform *fieldTransform in fieldTransforms) {
- FSTAssert([fieldTransform.transform isKindOfClass:[FSTServerTimestampTransform class]],
- @"Unknown transform: %@", fieldTransform.transform);
+ for (const FieldTransform &fieldTransform : fieldTransforms) {
+ FSTAssert(fieldTransform.transformation().type() == TransformOperation::Type::ServerTimestamp,
+ @"Unknown transform: %d type", fieldTransform.transformation().type());
GCFSDocumentTransform_FieldTransform *proto = [GCFSDocumentTransform_FieldTransform message];
- proto.fieldPath = fieldTransform.path.canonicalString;
+ proto.fieldPath = util::WrapNSString(fieldTransform.path().CanonicalString());
proto.setToServerValue = GCFSDocumentTransform_FieldTransform_ServerValue_RequestTime;
[protos addObject:proto];
}
return protos;
}
-- (NSArray<FSTFieldTransform *> *)decodedFieldTransforms:
+- (std::vector<FieldTransform>)decodedFieldTransforms:
(NSArray<GCFSDocumentTransform_FieldTransform *> *)protos {
- NSMutableArray<FSTFieldTransform *> *fieldTransforms = [NSMutableArray array];
+ std::vector<FieldTransform> fieldTransforms;
+ fieldTransforms.reserve(protos.count);
for (GCFSDocumentTransform_FieldTransform *proto in protos) {
FSTAssert(
proto.setToServerValue == GCFSDocumentTransform_FieldTransform_ServerValue_RequestTime,
@"Unknown transform setToServerValue: %d", proto.setToServerValue);
- [fieldTransforms
- addObject:[[FSTFieldTransform alloc]
- initWithPath:[FSTFieldPath pathWithServerFormat:proto.fieldPath]
- transform:[FSTServerTimestampTransform serverTimestampTransform]]];
+ fieldTransforms.emplace_back(
+ FieldPath::FromServerFormat(util::MakeStringView(proto.fieldPath)),
+ absl::make_unique<ServerTimestampTransform>(ServerTimestampTransform::Get()));
}
return fieldTransforms;
}
@@ -647,14 +678,14 @@ NS_ASSUME_NONNULL_BEGIN
- (GCFSTarget_QueryTarget *)encodedQueryTarget:(FSTQuery *)query {
// Dissect the path into parent, collectionId, and optional key filter.
GCFSTarget_QueryTarget *queryTarget = [GCFSTarget_QueryTarget message];
- if (query.path.length == 0) {
+ if (query.path.size() == 0) {
queryTarget.parent = [self encodedQueryPath:query.path];
} else {
- FSTResourcePath *path = query.path;
- FSTAssert(path.length % 2 != 0, @"Document queries with filters are not supported.");
- queryTarget.parent = [self encodedQueryPath:[path pathByRemovingLastSegment]];
+ const ResourcePath &path = query.path;
+ FSTAssert(path.size() % 2 != 0, @"Document queries with filters are not supported.");
+ queryTarget.parent = [self encodedQueryPath:path.PopLast()];
GCFSStructuredQuery_CollectionSelector *from = [GCFSStructuredQuery_CollectionSelector message];
- from.collectionId = path.lastSegment;
+ from.collectionId = util::WrapNSString(path.last_segment());
[queryTarget.structuredQuery.fromArray addObject:from];
}
@@ -685,7 +716,7 @@ NS_ASSUME_NONNULL_BEGIN
}
- (FSTQuery *)decodedQueryFromQueryTarget:(GCFSTarget_QueryTarget *)target {
- FSTResourcePath *path = [self decodedQueryPath:target.parent];
+ ResourcePath path = [self decodedQueryPath:target.parent];
GCFSStructuredQuery *query = target.structuredQuery;
NSUInteger fromCount = query.fromArray_Count;
@@ -694,7 +725,7 @@ NS_ASSUME_NONNULL_BEGIN
@"StructuredQuery.from with more than one collection is not supported.");
GCFSStructuredQuery_CollectionSelector *from = query.fromArray[0];
- path = [path pathByAppendingSegment:from.collectionId];
+ path = path.Append(util::MakeString(from.collectionId));
}
NSArray<id<FSTFilter>> *filterBy;
@@ -802,7 +833,7 @@ NS_ASSUME_NONNULL_BEGIN
}
- (FSTRelationFilter *)decodedRelationFilter:(GCFSStructuredQuery_FieldFilter *)proto {
- FSTFieldPath *fieldPath = [FSTFieldPath pathWithServerFormat:proto.field.fieldPath];
+ FieldPath fieldPath = FieldPath::FromServerFormat(util::MakeString(proto.field.fieldPath));
FSTRelationFilterOperator filterOperator = [self decodedRelationFilterOperator:proto.op];
FSTFieldValue *value = [self decodedFieldValue:proto.value];
return [FSTRelationFilter filterWithField:fieldPath filterOperator:filterOperator value:value];
@@ -822,7 +853,7 @@ NS_ASSUME_NONNULL_BEGIN
}
- (id<FSTFilter>)decodedUnaryFilter:(GCFSStructuredQuery_UnaryFilter *)proto {
- FSTFieldPath *field = [FSTFieldPath pathWithServerFormat:proto.field.fieldPath];
+ FieldPath field = FieldPath::FromServerFormat(util::MakeString(proto.field.fieldPath));
switch (proto.op) {
case GCFSStructuredQuery_UnaryFilter_Operator_IsNan:
return [[FSTNanFilter alloc] initWithField:field];
@@ -835,9 +866,9 @@ NS_ASSUME_NONNULL_BEGIN
}
}
-- (GCFSStructuredQuery_FieldReference *)encodedFieldPath:(FSTFieldPath *)fieldPath {
+- (GCFSStructuredQuery_FieldReference *)encodedFieldPath:(const FieldPath &)fieldPath {
GCFSStructuredQuery_FieldReference *ref = [GCFSStructuredQuery_FieldReference message];
- ref.fieldPath = fieldPath.canonicalString;
+ ref.fieldPath = util::WrapNSString(fieldPath.CanonicalString());
return ref;
}
@@ -907,7 +938,7 @@ NS_ASSUME_NONNULL_BEGIN
}
- (FSTSortOrder *)decodedSortOrder:(GCFSStructuredQuery_Order *)proto {
- FSTFieldPath *fieldPath = [FSTFieldPath pathWithServerFormat:proto.field.fieldPath];
+ FieldPath fieldPath = FieldPath::FromServerFormat(util::MakeString(proto.field.fieldPath));
BOOL ascending;
switch (proto.direction) {
case GCFSStructuredQuery_Direction_Ascending:
@@ -1032,7 +1063,7 @@ NS_ASSUME_NONNULL_BEGIN
- (FSTDocumentWatchChange *)decodedDocumentChange:(GCFSDocumentChange *)change {
FSTObjectValue *value = [self decodedFields:change.document.fields];
- FSTDocumentKey *key = [self decodedDocumentKey:change.document.name];
+ const DocumentKey key = [self decodedDocumentKey:change.document.name];
FSTSnapshotVersion *version = [self decodedVersion:change.document.updateTime];
FSTAssert(![version isEqual:[FSTSnapshotVersion noVersion]],
@"Got a document change with no snapshot version");
@@ -1049,7 +1080,7 @@ NS_ASSUME_NONNULL_BEGIN
}
- (FSTDocumentWatchChange *)decodedDocumentDelete:(GCFSDocumentDelete *)change {
- FSTDocumentKey *key = [self decodedDocumentKey:change.document];
+ const DocumentKey key = [self decodedDocumentKey:change.document];
// Note that version might be unset in which case we use [FSTSnapshotVersion noVersion]
FSTSnapshotVersion *version = [self decodedVersion:change.readTime];
FSTMaybeDocument *document = [FSTDeletedDocument documentWithKey:key version:version];
@@ -1063,7 +1094,7 @@ NS_ASSUME_NONNULL_BEGIN
}
- (FSTDocumentWatchChange *)decodedDocumentRemove:(GCFSDocumentRemove *)change {
- FSTDocumentKey *key = [self decodedDocumentKey:change.document];
+ const DocumentKey key = [self decodedDocumentKey:change.document];
NSArray<NSNumber *> *removedTargetIds = [self decodedIntegerArray:change.removedTargetIdsArray];
return [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[]
diff --git a/Firestore/Source/Remote/FSTStream.h b/Firestore/Source/Remote/FSTStream.h
index 546aa3d..fba79d2 100644
--- a/Firestore/Source/Remote/FSTStream.h
+++ b/Firestore/Source/Remote/FSTStream.h
@@ -17,9 +17,11 @@
#import <Foundation/Foundation.h>
#import "Firestore/Source/Core/FSTTypes.h"
+#import "Firestore/Source/Util/FSTDispatchQueue.h"
+
+#include "Firestore/core/src/firebase/firestore/auth/credentials_provider.h"
+#include "Firestore/core/src/firebase/firestore/core/database_info.h"
-@class FSTDatabaseInfo;
-@class FSTDocumentKey;
@class FSTDispatchQueue;
@class FSTMutation;
@class FSTMutationResult;
@@ -32,7 +34,6 @@
@class GRPCCall;
@class GRXWriter;
-@protocol FSTCredentialsProvider;
@protocol FSTWatchStreamDelegate;
@protocol FSTWriteStreamDelegate;
@@ -45,7 +46,7 @@ NS_ASSUME_NONNULL_BEGIN
*
* - Restarting a stream is allowed (after failure)
* - Exponential backoff on failure (independent of the underlying channel)
- * - Authentication via FSTCredentialsProvider
+ * - Authentication via CredentialsProvider
* - Dispatching all callbacks into the shared worker queue
*
* Subclasses of FSTStream implement serialization of models to and from bytes (via protocol
@@ -88,9 +89,11 @@ NS_ASSUME_NONNULL_BEGIN
*/
@interface FSTStream <__covariant FSTStreamDelegate> : NSObject
-- (instancetype)initWithDatabase:(FSTDatabaseInfo *)database
+- (instancetype)initWithDatabase:(const firebase::firestore::core::DatabaseInfo *)database
workerDispatchQueue:(FSTDispatchQueue *)workerDispatchQueue
- credentials:(id<FSTCredentialsProvider>)credentials
+ connectionTimerID:(FSTTimerID)connectionTimerID
+ idleTimerID:(FSTTimerID)idleTimerID
+ credentials:(firebase::firestore::auth::CredentialsProvider *)credentials // no passing ownership
responseMessageClass:(Class)responseMessageClass NS_DESIGNATED_INITIALIZER;
- (instancetype)init NS_UNAVAILABLE;
@@ -141,8 +144,13 @@ NS_ASSUME_NONNULL_BEGIN
- (void)stop;
/**
- * Initializes the idle timer. If no write takes place within one minute, the GRPC stream will be
- * closed.
+ * Marks this stream as idle. If no further actions are performed on the stream for one minute, the
+ * stream will automatically close itself and notify the stream's close handler. The stream will
+ * then be in a non-started state, requiring the caller to start the stream again before further
+ * use.
+ *
+ * Only streams that are in state 'Open' can be marked idle, as all other states imply pending
+ * network operations.
*/
- (void)markIdle;
@@ -197,14 +205,18 @@ NS_ASSUME_NONNULL_BEGIN
/**
* Initializes the watch stream with its dependencies.
*/
-- (instancetype)initWithDatabase:(FSTDatabaseInfo *)database
+- (instancetype)initWithDatabase:(const firebase::firestore::core::DatabaseInfo *)database
workerDispatchQueue:(FSTDispatchQueue *)workerDispatchQueue
- credentials:(id<FSTCredentialsProvider>)credentials
+ credentials:(firebase::firestore::auth::CredentialsProvider *)
+ credentials // no passsing ownership
serializer:(FSTSerializerBeta *)serializer NS_DESIGNATED_INITIALIZER;
-- (instancetype)initWithDatabase:(FSTDatabaseInfo *)database
+- (instancetype)initWithDatabase:(const firebase::firestore::core::DatabaseInfo *)database
workerDispatchQueue:(FSTDispatchQueue *)workerDispatchQueue
- credentials:(id<FSTCredentialsProvider>)credentials
+ connectionTimerID:(FSTTimerID)connectionTimerID
+ idleTimerID:(FSTTimerID)idleTimerID
+ credentials:(firebase::firestore::auth::CredentialsProvider *)
+ credentials // no passing ownership
responseMessageClass:(Class)responseMessageClass NS_UNAVAILABLE;
- (instancetype)init NS_UNAVAILABLE;
@@ -271,14 +283,18 @@ NS_ASSUME_NONNULL_BEGIN
/**
* Initializes the write stream with its dependencies.
*/
-- (instancetype)initWithDatabase:(FSTDatabaseInfo *)database
+- (instancetype)initWithDatabase:(const firebase::firestore::core::DatabaseInfo *)database
workerDispatchQueue:(FSTDispatchQueue *)workerDispatchQueue
- credentials:(id<FSTCredentialsProvider>)credentials
+ credentials:(firebase::firestore::auth::CredentialsProvider *)
+ credentials // no passing ownership
serializer:(FSTSerializerBeta *)serializer;
-- (instancetype)initWithDatabase:(FSTDatabaseInfo *)database
+- (instancetype)initWithDatabase:(const firebase::firestore::core::DatabaseInfo *)database
workerDispatchQueue:(FSTDispatchQueue *)workerDispatchQueue
- credentials:(id<FSTCredentialsProvider>)credentials
+ connectionTimerID:(FSTTimerID)connectionTimerID
+ idleTimerID:(FSTTimerID)idleTimerID
+ credentials:(firebase::firestore::auth::CredentialsProvider *)
+ credentials // no passing ownership
responseMessageClass:(Class)responseMessageClass NS_UNAVAILABLE;
- (instancetype)init NS_UNAVAILABLE;
diff --git a/Firestore/Source/Remote/FSTStream.m b/Firestore/Source/Remote/FSTStream.mm
index 2c039be..a96feae 100644
--- a/Firestore/Source/Remote/FSTStream.m
+++ b/Firestore/Source/Remote/FSTStream.mm
@@ -14,19 +14,15 @@
* limitations under the License.
*/
-#import "Firestore/Source/Remote/FSTDatastore.h"
-
#import <GRPCClient/GRPCCall+OAuth2.h>
#import <GRPCClient/GRPCCall.h>
#import "FIRFirestoreErrors.h"
#import "Firestore/Source/API/FIRFirestore+Internal.h"
-#import "Firestore/Source/Auth/FSTCredentialsProvider.h"
-#import "Firestore/Source/Core/FSTDatabaseInfo.h"
#import "Firestore/Source/Local/FSTQueryData.h"
-#import "Firestore/Source/Model/FSTDatabaseID.h"
#import "Firestore/Source/Model/FSTMutation.h"
#import "Firestore/Source/Remote/FSTBufferedWriter.h"
+#import "Firestore/Source/Remote/FSTDatastore.h"
#import "Firestore/Source/Remote/FSTExponentialBackoff.h"
#import "Firestore/Source/Remote/FSTSerializerBeta.h"
#import "Firestore/Source/Remote/FSTStream.h"
@@ -37,6 +33,18 @@
#import "Firestore/Protos/objc/google/firestore/v1beta1/Firestore.pbrpc.h"
+#include "Firestore/core/src/firebase/firestore/auth/token.h"
+#include "Firestore/core/src/firebase/firestore/core/database_info.h"
+#include "Firestore/core/src/firebase/firestore/model/database_id.h"
+#include "Firestore/core/src/firebase/firestore/util/error_apple.h"
+#include "Firestore/core/src/firebase/firestore/util/string_apple.h"
+
+namespace util = firebase::firestore::util;
+using firebase::firestore::auth::CredentialsProvider;
+using firebase::firestore::auth::Token;
+using firebase::firestore::core::DatabaseInfo;
+using firebase::firestore::model::DatabaseId;
+
/**
* Initial backoff time in seconds after an error.
* Set to 1s according to https://cloud.google.com/apis/design/errors.
@@ -93,30 +101,32 @@ typedef NS_ENUM(NSInteger, FSTStreamState) {
/**
* Initializes the watch stream with its dependencies.
*/
-- (instancetype)initWithDatabase:(FSTDatabaseInfo *)database
+- (instancetype)initWithDatabase:(const DatabaseInfo *)database
workerDispatchQueue:(FSTDispatchQueue *)workerDispatchQueue
- credentials:(id<FSTCredentialsProvider>)credentials
+ credentials:(CredentialsProvider *)credentials
serializer:(FSTSerializerBeta *)serializer NS_DESIGNATED_INITIALIZER;
-- (instancetype)initWithDatabase:(FSTDatabaseInfo *)database
+- (instancetype)initWithDatabase:(const DatabaseInfo *)database
workerDispatchQueue:(FSTDispatchQueue *)workerDispatchQueue
- credentials:(id<FSTCredentialsProvider>)credentials
+ credentials:(CredentialsProvider *)credentials
responseMessageClass:(Class)responseMessageClass NS_UNAVAILABLE;
@end
@interface FSTStream ()
-@property(nonatomic, getter=isIdle) BOOL idle;
+@property(nonatomic, assign, readonly) FSTTimerID idleTimerID;
+@property(nonatomic, strong, nullable) FSTDelayedCallback *idleTimerCallback;
@property(nonatomic, weak, readwrite, nullable) id delegate;
@end
@interface FSTStream () <GRXWriteable>
-@property(nonatomic, strong, readonly) FSTDatabaseInfo *databaseInfo;
+// Does not own this DatabaseInfo.
+@property(nonatomic, assign, readonly) const DatabaseInfo *databaseInfo;
@property(nonatomic, strong, readonly) FSTDispatchQueue *workerDispatchQueue;
-@property(nonatomic, strong, readonly) id<FSTCredentialsProvider> credentials;
+@property(nonatomic, assign, readonly) CredentialsProvider *credentials;
@property(nonatomic, unsafe_unretained, readonly) Class responseMessageClass;
@property(nonatomic, strong, readonly) FSTExponentialBackoff *backoff;
@@ -142,7 +152,12 @@ typedef NS_ENUM(NSInteger, FSTStreamState) {
#pragma mark - FSTCallbackFilter
-/** Filter class that allows disabling of GRPC callbacks. */
+/**
+ * Implements callbacks from gRPC via the GRXWriteable protocol. This is separate from the main
+ * FSTStream to allow the stream to be stopped externally (either by the user or via idle timer)
+ * and be able to completely prevent any subsequent events from gRPC from calling back into the
+ * FSTSTream.
+ */
@interface FSTCallbackFilter : NSObject <GRXWriteable>
- (instancetype)initWithStream:(FSTStream *)stream NS_DESIGNATED_INITIALIZER;
@@ -194,20 +209,24 @@ typedef NS_ENUM(NSInteger, FSTStreamState) {
/** The time a stream stays open after it is marked idle. */
static const NSTimeInterval kIdleTimeout = 60.0;
-- (instancetype)initWithDatabase:(FSTDatabaseInfo *)database
+- (instancetype)initWithDatabase:(const DatabaseInfo *)database
workerDispatchQueue:(FSTDispatchQueue *)workerDispatchQueue
- credentials:(id<FSTCredentialsProvider>)credentials
+ connectionTimerID:(FSTTimerID)connectionTimerID
+ idleTimerID:(FSTTimerID)idleTimerID
+ credentials:(CredentialsProvider *)credentials
responseMessageClass:(Class)responseMessageClass {
if (self = [super init]) {
_databaseInfo = database;
_workerDispatchQueue = workerDispatchQueue;
+ _idleTimerID = idleTimerID;
_credentials = credentials;
_responseMessageClass = responseMessageClass;
- _backoff = [FSTExponentialBackoff exponentialBackoffWithDispatchQueue:workerDispatchQueue
- initialDelay:kBackoffInitialDelay
- backoffFactor:kBackoffFactor
- maxDelay:kBackoffMaxDelay];
+ _backoff = [[FSTExponentialBackoff alloc] initWithDispatchQueue:workerDispatchQueue
+ timerID:connectionTimerID
+ initialDelay:kBackoffInitialDelay
+ backoffFactor:kBackoffFactor
+ maxDelay:kBackoffMaxDelay];
_state = FSTStreamStateInitial;
}
return self;
@@ -244,40 +263,42 @@ static const NSTimeInterval kIdleTimeout = 60.0;
FSTAssert(_delegate == nil, @"Delegate must be nil");
_delegate = delegate;
- [self.credentials
- getTokenForcingRefresh:NO
- completion:^(FSTGetTokenResult *_Nullable result, NSError *_Nullable error) {
- error = [FSTDatastore firestoreErrorForError:error];
- [self.workerDispatchQueue dispatchAsyncAllowingSameQueue:^{
- [self resumeStartWithToken:result error:error];
- }];
- }];
+ _credentials->GetToken(
+ /*force_refresh=*/false, [self](util::StatusOr<Token> result) {
+ [self.workerDispatchQueue dispatchAsyncAllowingSameQueue:^{
+ [self resumeStartWithToken:result];
+ }];
+ });
}
/** Add an access token to our RPC, after obtaining one from the credentials provider. */
-- (void)resumeStartWithToken:(FSTGetTokenResult *)token error:(NSError *)error {
+- (void)resumeStartWithToken:(const util::StatusOr<Token> &)result {
+ [self.workerDispatchQueue verifyIsCurrentQueue];
+
if (self.state == FSTStreamStateStopped) {
// Streams can be stopped while waiting for authorization.
return;
}
-
- [self.workerDispatchQueue verifyIsCurrentQueue];
FSTAssert(self.state == FSTStreamStateAuth, @"State should still be auth (was %ld)",
(long)self.state);
// TODO(mikelehen): We should force a refresh if the previous RPC failed due to an expired token,
// but I'm not sure how to detect that right now. http://b/32762461
- if (error) {
+ if (!result.ok()) {
// RPC has not been started yet, so just invoke higher-level close handler.
- [self handleStreamClose:error];
+ [self handleStreamClose:util::MakeNSError(result.status())];
return;
}
self.requestsWriter = [[FSTBufferedWriter alloc] init];
_rpc = [self createRPCWithRequestsWriter:self.requestsWriter];
- [FSTDatastore prepareHeadersForRPC:_rpc
- databaseID:self.databaseInfo.databaseID
- token:token.token];
+ [_rpc setResponseDispatchQueue:self.workerDispatchQueue.queue];
+
+ const Token &token = result.ValueOrDie();
+ [FSTDatastore
+ prepareHeadersForRPC:_rpc
+ databaseID:&self.databaseInfo->database_id()
+ token:(token.user().is_authenticated() ? token.token() : absl::string_view())];
FSTAssert(_callbackFilter == nil, @"GRX Filter must be nil");
_callbackFilter = [[FSTCallbackFilter alloc] initWithStream:self];
[_rpc startWithWriteable:_callbackFilter];
@@ -304,7 +325,8 @@ static const NSTimeInterval kIdleTimeout = 60.0;
/** Resumes stream start after backing off. */
- (void)resumeStartFromBackoffWithDelegate:(id)delegate {
if (self.state == FSTStreamStateStopped) {
- // Streams can be stopped while waiting for backoff to complete.
+ // We should have canceled the backoff timer when the stream was closed, but just in case we
+ // make this a no-op.
return;
}
@@ -343,13 +365,16 @@ static const NSTimeInterval kIdleTimeout = 60.0;
- (void)closeWithFinalState:(FSTStreamState)finalState error:(nullable NSError *)error {
FSTAssert(finalState == FSTStreamStateError || error == nil,
@"Can't provide an error when not in an error state.");
- FSTAssert(self.delegate,
- @"closeWithFinalState should only be called for a started stream that has an active "
- @"delegate.");
[self.workerDispatchQueue verifyIsCurrentQueue];
+
+ // The stream will be closed so we don't need our idle close timer anymore.
[self cancelIdleCheck];
+ // Ensure we don't leave a pending backoff operation queued (in case close()
+ // was called while we were waiting to reconnect).
+ [self.backoff cancel];
+
if (finalState != FSTStreamStateError) {
// If this is an intentional close ensure we don't delay our next connection attempt.
[self.backoff reset];
@@ -359,7 +384,10 @@ static const NSTimeInterval kIdleTimeout = 60.0;
[self.backoff resetToMax];
}
- [self tearDown];
+ if (finalState != FSTStreamStateError) {
+ FSTLog(@"%@ %p Performing stream teardown", [self class], (__bridge void *)self);
+ [self tearDown];
+ }
if (self.requestsWriter) {
// Clean up the underlying RPC. If this close: is in response to an error, don't attempt to
@@ -390,8 +418,9 @@ static const NSTimeInterval kIdleTimeout = 60.0;
[self notifyStreamInterruptedWithError:error];
}
- // Clear the delegates to avoid any possible bleed through of events from GRPC.
- _delegate = nil;
+ // PORTING NOTE: notifyStreamInterruptedWithError may have restarted the stream with a new
+ // delegate so we do /not/ want to clear the delegate here. And since we've already suppressed
+ // callbacks via our callbackFilter, there is no worry about bleed through of events from GRPC.
}
- (void)stop {
@@ -414,7 +443,7 @@ static const NSTimeInterval kIdleTimeout = 60.0;
/** Called by the idle timer when the stream should close due to inactivity. */
- (void)handleIdleCloseTimer {
[self.workerDispatchQueue verifyIsCurrentQueue];
- if (self.state == FSTStreamStateOpen && [self isIdle]) {
+ if ([self isOpen]) {
// When timing out an idle stream there's no reason to force the stream into backoff when
// it restarts so set the stream state to Initial instead of Error.
[self closeWithFinalState:FSTStreamStateInitial error:nil];
@@ -423,18 +452,23 @@ static const NSTimeInterval kIdleTimeout = 60.0;
- (void)markIdle {
[self.workerDispatchQueue verifyIsCurrentQueue];
- if (self.state == FSTStreamStateOpen) {
- self.idle = YES;
- [self.workerDispatchQueue dispatchAfterDelay:kIdleTimeout
- block:^() {
- [self handleIdleCloseTimer];
- }];
+ // Starts the idle timer if we are in state 'Open' and are not yet already running a timer (in
+ // which case the previous idle timeout still applies).
+ if ([self isOpen] && !self.idleTimerCallback) {
+ self.idleTimerCallback = [self.workerDispatchQueue dispatchAfterDelay:kIdleTimeout
+ timerID:self.idleTimerID
+ block:^() {
+ [self handleIdleCloseTimer];
+ }];
}
}
- (void)cancelIdleCheck {
[self.workerDispatchQueue verifyIsCurrentQueue];
- self.idle = NO;
+ if (self.idleTimerCallback) {
+ [self.idleTimerCallback cancel];
+ self.idleTimerCallback = nil;
+ }
}
/**
@@ -515,11 +549,7 @@ static const NSTimeInterval kIdleTimeout = 60.0;
*/
- (void)handleStreamClose:(nullable NSError *)error {
FSTLog(@"%@ %p close: %@", NSStringFromClass([self class]), (__bridge void *)self, error);
-
- if (![self isStarted]) { // The stream could have already been closed by the idle close timer.
- FSTLog(@"%@ Ignoring server close for already closed stream.", NSStringFromClass([self class]));
- return;
- }
+ FSTAssert([self isStarted], @"handleStreamClose: called for non-started stream.");
// In theory the stream could close cleanly, however, in our current model we never expect this
// to happen because if we stop a stream ourselves, this callback will never be called. To
@@ -532,19 +562,15 @@ static const NSTimeInterval kIdleTimeout = 60.0;
// The GRXWriteable implementation defines the receive side of the RPC stream.
/**
- * Called by GRPC when it publishes a value. It is called from GRPC's own queue so we immediately
- * redispatch back onto our own worker queue.
+ * Called by GRPC when it publishes a value.
+ *
+ * GRPC must be configured to use our worker queue by calling
+ * `[call setResponseDispatchQueue:self.workerDispatchQueue.queue]` on the GRPCCall before starting
+ * the RPC.
*/
-- (void)writeValue:(id)value __used {
- // TODO(mcg): remove the double-dispatch once GRPCCall at head is released.
- // Once released we can set the responseDispatchQueue property on the GRPCCall and then this
- // method can call handleStreamMessage directly.
- FSTWeakify(self);
- [self.workerDispatchQueue dispatchAsync:^{
- FSTStrongify(self);
- if (!self || ![self isStarted]) {
- FSTLog(@"%@ Ignoring stream message from inactive stream.", NSStringFromClass([self class]));
- }
+- (void)writeValue:(id)value {
+ [self.workerDispatchQueue enterCheckedOperation:^{
+ FSTAssert([self isStarted], @"writeValue: called for stopped stream.");
if (!self.messageReceived) {
self.messageReceived = YES;
@@ -559,7 +585,7 @@ static const NSTimeInterval kIdleTimeout = 60.0;
if (proto) {
[self handleStreamMessage:proto];
} else {
- [_rpc finishWithError:error];
+ [self.rpc finishWithError:error];
}
}];
}
@@ -568,17 +594,18 @@ static const NSTimeInterval kIdleTimeout = 60.0;
* Called by GRPC when it closed the stream with an error representing the final state of the
* stream.
*
- * Do not call directly, since it dispatches via the worker queue. Call handleStreamClose to
- * directly inform stream-specific logic, or call stop to tear down the stream.
+ * GRPC must be configured to use our worker queue by calling
+ * `[call setResponseDispatchQueue:self.workerDispatchQueue.queue]` on the GRPCCall before starting
+ * the RPC.
+ *
+ * Do not call directly. Call handleStreamClose to directly inform stream-specific logic, or call
+ * stop to tear down the stream.
*/
- (void)writesFinishedWithError:(nullable NSError *)error __used {
error = [FSTDatastore firestoreErrorForError:error];
- FSTWeakify(self);
- [self.workerDispatchQueue dispatchAsync:^{
- FSTStrongify(self);
- if (!self || self.state == FSTStreamStateStopped) {
- return;
- }
+ [self.workerDispatchQueue enterCheckedOperation:^{
+ FSTAssert([self isStarted], @"writesFinishedWithError: called for stopped stream.");
+
[self handleStreamClose:error];
}];
}
@@ -595,12 +622,14 @@ static const NSTimeInterval kIdleTimeout = 60.0;
@implementation FSTWatchStream
-- (instancetype)initWithDatabase:(FSTDatabaseInfo *)database
+- (instancetype)initWithDatabase:(const DatabaseInfo *)database
workerDispatchQueue:(FSTDispatchQueue *)workerDispatchQueue
- credentials:(id<FSTCredentialsProvider>)credentials
+ credentials:(CredentialsProvider *)credentials
serializer:(FSTSerializerBeta *)serializer {
self = [super initWithDatabase:database
workerDispatchQueue:workerDispatchQueue
+ connectionTimerID:FSTTimerIDListenStreamConnectionBackoff
+ idleTimerID:FSTTimerIDListenStreamIdle
credentials:credentials
responseMessageClass:[GCFSListenResponse class]];
if (self) {
@@ -610,7 +639,7 @@ static const NSTimeInterval kIdleTimeout = 60.0;
}
- (GRPCCall *)createRPCWithRequestsWriter:(GRXWriter *)requestsWriter {
- return [[GRPCCall alloc] initWithHost:self.databaseInfo.host
+ return [[GRPCCall alloc] initWithHost:util::WrapNSString(self.databaseInfo->host())
path:@"/google.firestore.v1beta1.Firestore/Listen"
requestsWriter:requestsWriter];
}
@@ -678,12 +707,14 @@ static const NSTimeInterval kIdleTimeout = 60.0;
@implementation FSTWriteStream
-- (instancetype)initWithDatabase:(FSTDatabaseInfo *)database
+- (instancetype)initWithDatabase:(const DatabaseInfo *)database
workerDispatchQueue:(FSTDispatchQueue *)workerDispatchQueue
- credentials:(id<FSTCredentialsProvider>)credentials
+ credentials:(CredentialsProvider *)credentials
serializer:(FSTSerializerBeta *)serializer {
self = [super initWithDatabase:database
workerDispatchQueue:workerDispatchQueue
+ connectionTimerID:FSTTimerIDWriteStreamConnectionBackoff
+ idleTimerID:FSTTimerIDWriteStreamIdle
credentials:credentials
responseMessageClass:[GCFSWriteResponse class]];
if (self) {
@@ -693,7 +724,7 @@ static const NSTimeInterval kIdleTimeout = 60.0;
}
- (GRPCCall *)createRPCWithRequestsWriter:(GRXWriter *)requestsWriter {
- return [[GRPCCall alloc] initWithHost:self.databaseInfo.host
+ return [[GRPCCall alloc] initWithHost:util::WrapNSString(self.databaseInfo->host())
path:@"/google.firestore.v1beta1.Firestore/Write"
requestsWriter:requestsWriter];
}
diff --git a/Firestore/Source/Remote/FSTWatchChange.h b/Firestore/Source/Remote/FSTWatchChange.h
index 8ce24fa..8f730de 100644
--- a/Firestore/Source/Remote/FSTWatchChange.h
+++ b/Firestore/Source/Remote/FSTWatchChange.h
@@ -18,7 +18,8 @@
#import "Firestore/Source/Core/FSTTypes.h"
-@class FSTDocumentKey;
+#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+
@class FSTExistenceFilter;
@class FSTMaybeDocument;
@class FSTSnapshotVersion;
@@ -41,21 +42,21 @@ NS_ASSUME_NONNULL_BEGIN
- (instancetype)initWithUpdatedTargetIDs:(NSArray<NSNumber *> *)updatedTargetIDs
removedTargetIDs:(NSArray<NSNumber *> *)removedTargetIDs
- documentKey:(FSTDocumentKey *)documentKey
+ documentKey:(firebase::firestore::model::DocumentKey)documentKey
document:(nullable FSTMaybeDocument *)document
NS_DESIGNATED_INITIALIZER;
- (instancetype)init NS_UNAVAILABLE;
+/** The key of the document for this change. */
+- (const firebase::firestore::model::DocumentKey &)documentKey;
+
/** The new document applies to all of these targets. */
@property(nonatomic, strong, readonly) NSArray<NSNumber *> *updatedTargetIDs;
/** The new document is removed from all of these targets. */
@property(nonatomic, strong, readonly) NSArray<NSNumber *> *removedTargetIDs;
-/** The key of the document for this change. */
-@property(nonatomic, strong, readonly) FSTDocumentKey *documentKey;
-
/**
* The new document or DeletedDocument if it was deleted. Is null if the document went out of
* view without the server sending a new document.
diff --git a/Firestore/Source/Remote/FSTWatchChange.m b/Firestore/Source/Remote/FSTWatchChange.mm
index 926d027..284e980 100644
--- a/Firestore/Source/Remote/FSTWatchChange.m
+++ b/Firestore/Source/Remote/FSTWatchChange.mm
@@ -16,31 +16,42 @@
#import "Firestore/Source/Remote/FSTWatchChange.h"
+#include <utility>
+
#import "Firestore/Source/Model/FSTDocument.h"
-#import "Firestore/Source/Model/FSTDocumentKey.h"
#import "Firestore/Source/Remote/FSTExistenceFilter.h"
+#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+
+using firebase::firestore::model::DocumentKey;
+
NS_ASSUME_NONNULL_BEGIN
@implementation FSTWatchChange
@end
-@implementation FSTDocumentWatchChange
+@implementation FSTDocumentWatchChange {
+ DocumentKey _documentKey;
+}
- (instancetype)initWithUpdatedTargetIDs:(NSArray<NSNumber *> *)updatedTargetIDs
removedTargetIDs:(NSArray<NSNumber *> *)removedTargetIDs
- documentKey:(FSTDocumentKey *)documentKey
+ documentKey:(DocumentKey)documentKey
document:(nullable FSTMaybeDocument *)document {
self = [super init];
if (self) {
_updatedTargetIDs = updatedTargetIDs;
_removedTargetIDs = removedTargetIDs;
- _documentKey = documentKey;
+ _documentKey = std::move(documentKey);
_document = document;
}
return self;
}
+- (const firebase::firestore::model::DocumentKey &)documentKey {
+ return _documentKey;
+}
+
- (BOOL)isEqual:(id)other {
if (other == self) {
return YES;
@@ -59,7 +70,7 @@ NS_ASSUME_NONNULL_BEGIN
- (NSUInteger)hash {
NSUInteger hash = self.updatedTargetIDs.hash;
hash = hash * 31 + self.removedTargetIDs.hash;
- hash = hash * 31 + self.documentKey.hash;
+ hash = hash * 31 + self.documentKey.Hash();
hash = hash * 31 + self.document.hash;
return hash;
}
diff --git a/Firestore/Source/Util/FSTAssert.h b/Firestore/Source/Util/FSTAssert.h
index 77bbb1d..610d306 100644
--- a/Firestore/Source/Util/FSTAssert.h
+++ b/Firestore/Source/Util/FSTAssert.h
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#include <Foundation/Foundation.h>
+#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
diff --git a/Firestore/Source/Util/FSTAsyncQueryListener.m b/Firestore/Source/Util/FSTAsyncQueryListener.mm
index d98e2dd..b72ac57 100644
--- a/Firestore/Source/Util/FSTAsyncQueryListener.m
+++ b/Firestore/Source/Util/FSTAsyncQueryListener.mm
@@ -34,10 +34,15 @@
}
- (FSTViewSnapshotHandler)asyncSnapshotHandler {
+ // Retain `self` strongly in resulting snapshot handler so that even if the
+ // user releases the `FSTAsyncQueryListener` we'll continue to deliver
+ // events. This is done specifically to facilitate the common case where
+ // users just want to turn on notifications "forever" and don't want to have
+ // to keep track of our handle to keep them going.
return ^(FSTViewSnapshot *_Nullable snapshot, NSError *_Nullable error) {
- [_dispatchQueue dispatchAsync:^{
- if (!_muted) {
- _snapshotHandler(snapshot, error);
+ [self->_dispatchQueue dispatchAsync:^{
+ if (!self->_muted) {
+ self->_snapshotHandler(snapshot, error);
}
}];
};
diff --git a/Firestore/Source/Util/FSTComparison.h b/Firestore/Source/Util/FSTComparison.h
deleted file mode 100644
index e6e57e6..0000000
--- a/Firestore/Source/Util/FSTComparison.h
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright 2017 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/Foundation.h>
-
-NS_ASSUME_NONNULL_BEGIN
-
-/** Compares two NSStrings. */
-NSComparisonResult FSTCompareStrings(NSString *left, NSString *right);
-
-/** Compares two BOOLs. */
-NSComparisonResult FSTCompareBools(BOOL left, BOOL right);
-
-/** Compares two integers. */
-NSComparisonResult FSTCompareInts(int left, int right);
-
-/** Compares two int32_t. */
-NSComparisonResult FSTCompareInt32s(int32_t left, int32_t right);
-
-/** Compares two int64_t. */
-NSComparisonResult FSTCompareInt64s(int64_t left, int64_t right);
-
-/** Compares two NSUIntegers. */
-NSComparisonResult FSTCompareUIntegers(NSUInteger left, NSUInteger right);
-
-/** Compares two doubles (using Firestore semantics for NaN). */
-NSComparisonResult FSTCompareDoubles(double left, double right);
-
-/** Compares a double and an int64_t. */
-NSComparisonResult FSTCompareMixed(double doubleValue, int64_t longValue);
-
-/** Compare two NSData byte sequences. */
-NSComparisonResult FSTCompareBytes(NSData *left, NSData *right);
-
-/** A simple NSComparator for comparing NSNumber instances. */
-extern const NSComparator FSTNumberComparator;
-
-/** A simple NSComparator for comparing NSString instances. */
-extern const NSComparator FSTStringComparator;
-
-/**
- * Compares the bitwise representation of two doubles, but normalizes NaN values. This is
- * similar to what the backend and android clients do, including comparing -0.0 as not equal to 0.0.
- */
-BOOL FSTDoubleBitwiseEquals(double left, double right);
-
-/**
- * Computes a bitwise hash of a double, but normalizes NaN values, suitable for use when using
- * FSTDoublesAreBitwiseEqual for equality.
- */
-NSUInteger FSTDoubleBitwiseHash(double d);
-
-NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/Util/FSTComparison.m b/Firestore/Source/Util/FSTComparison.m
deleted file mode 100644
index 9c5c3eb..0000000
--- a/Firestore/Source/Util/FSTComparison.m
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * Copyright 2017 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 "Firestore/Source/Util/FSTComparison.h"
-
-NS_ASSUME_NONNULL_BEGIN
-
-union DoubleBits {
- double d;
- uint64_t bits;
-};
-
-const NSComparator FSTNumberComparator = ^NSComparisonResult(NSNumber *left, NSNumber *right) {
- return [left compare:right];
-};
-
-const NSComparator FSTStringComparator = ^NSComparisonResult(NSString *left, NSString *right) {
- return FSTCompareStrings(left, right);
-};
-
-NSComparisonResult FSTCompareStrings(NSString *left, NSString *right) {
- // NOTE: NSLiteralSearch is necessary to compare the raw character codes. By default,
- // precomposed characters are considered equivalent to their decomposed equivalents.
- return [left compare:right options:NSLiteralSearch];
-}
-
-NSComparisonResult FSTCompareBools(BOOL left, BOOL right) {
- if (!left) {
- return right ? NSOrderedAscending : NSOrderedSame;
- } else {
- return right ? NSOrderedSame : NSOrderedDescending;
- }
-}
-
-NSComparisonResult FSTCompareInts(int left, int right) {
- if (left > right) {
- return NSOrderedDescending;
- }
- if (right > left) {
- return NSOrderedAscending;
- }
- return NSOrderedSame;
-}
-
-NSComparisonResult FSTCompareInt32s(int32_t left, int32_t right) {
- if (left > right) {
- return NSOrderedDescending;
- }
- if (right > left) {
- return NSOrderedAscending;
- }
- return NSOrderedSame;
-}
-
-NSComparisonResult FSTCompareInt64s(int64_t left, int64_t right) {
- if (left > right) {
- return NSOrderedDescending;
- }
- if (right > left) {
- return NSOrderedAscending;
- }
- return NSOrderedSame;
-}
-
-NSComparisonResult FSTCompareUIntegers(NSUInteger left, NSUInteger right) {
- if (left > right) {
- return NSOrderedDescending;
- }
- if (right > left) {
- return NSOrderedAscending;
- }
- return NSOrderedSame;
-}
-
-NSComparisonResult FSTCompareDoubles(double left, double right) {
- // NaN sorts equal to itself and before any other number.
- if (left < right) {
- return NSOrderedAscending;
- } else if (left > right) {
- return NSOrderedDescending;
- } else if (left == right) {
- return NSOrderedSame;
- } else {
- // One or both left and right is NaN.
- if (isnan(left)) {
- return isnan(right) ? NSOrderedSame : NSOrderedAscending;
- } else {
- return NSOrderedDescending;
- }
- }
-}
-
-static const double LONG_MIN_VALUE_AS_DOUBLE = (double)LLONG_MIN;
-static const double LONG_MAX_VALUE_AS_DOUBLE = (double)LLONG_MAX;
-
-NSComparisonResult FSTCompareMixed(double doubleValue, int64_t longValue) {
- // LLONG_MIN has an exact representation as double, so to check for a value outside the range
- // representable by long, we have to check for strictly less than LLONG_MIN. Note that this also
- // handles negative infinity.
- if (doubleValue < LONG_MIN_VALUE_AS_DOUBLE) {
- return NSOrderedAscending;
- }
-
- // LLONG_MAX has no exact representation as double (casting as we've done makes 2^63, which is
- // larger than LLONG_MAX), so consider any value greater than or equal to the threshold to be out
- // of range. This also handles positive infinity.
- if (doubleValue >= LONG_MAX_VALUE_AS_DOUBLE) {
- return NSOrderedDescending;
- }
-
- // In Firestore NaN is defined to compare before all other numbers.
- if (isnan(doubleValue)) {
- return NSOrderedAscending;
- }
-
- int64_t doubleAsLong = (int64_t)doubleValue;
- NSComparisonResult cmp = FSTCompareInt64s(doubleAsLong, longValue);
- if (cmp != NSOrderedSame) {
- return cmp;
- }
-
- // At this point the long representations are equal but this could be due to rounding.
- double longAsDouble = (double)longValue;
- return FSTCompareDoubles(doubleValue, longAsDouble);
-}
-
-NSComparisonResult FSTCompareBytes(NSData *left, NSData *right) {
- NSUInteger minLength = MIN(left.length, right.length);
- int result = memcmp(left.bytes, right.bytes, minLength);
- if (result < 0) {
- return NSOrderedAscending;
- } else if (result > 0) {
- return NSOrderedDescending;
- } else if (left.length < right.length) {
- return NSOrderedAscending;
- } else if (left.length > right.length) {
- return NSOrderedDescending;
- } else {
- return NSOrderedSame;
- }
-}
-
-/** Helper to normalize a double and then return the raw bits as a uint64_t. */
-uint64_t FSTDoubleBits(double d) {
- if (isnan(d)) {
- d = NAN;
- }
- union DoubleBits converter = {.d = d};
- return converter.bits;
-}
-
-BOOL FSTDoubleBitwiseEquals(double left, double right) {
- return FSTDoubleBits(left) == FSTDoubleBits(right);
-}
-
-NSUInteger FSTDoubleBitwiseHash(double d) {
- uint64_t bits = FSTDoubleBits(d);
- // Note that x ^ (x >> 32) works fine for both 32 and 64 bit definitions of NSUInteger
- return (((NSUInteger)bits) ^ (NSUInteger)(bits >> 32));
-}
-
-NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/Util/FSTDispatchQueue.h b/Firestore/Source/Util/FSTDispatchQueue.h
index fe87887..8e9273c 100644
--- a/Firestore/Source/Util/FSTDispatchQueue.h
+++ b/Firestore/Source/Util/FSTDispatchQueue.h
@@ -18,6 +18,47 @@
NS_ASSUME_NONNULL_BEGIN
+/**
+ * Well-known "timer" IDs used when scheduling delayed callbacks on the FSTDispatchQueue. These IDs
+ * can then be used from tests to check for the presence of callbacks or to run them early.
+ */
+typedef NS_ENUM(NSInteger, FSTTimerID) {
+ /** All can be used with runDelayedCallbacksUntil: to run all timers. */
+ FSTTimerIDAll,
+
+ /**
+ * The following 4 timers are used in FSTStream for the listen and write streams. The "Idle" timer
+ * is used to close the stream due to inactivity. The "ConnectionBackoff" timer is used to
+ * restart a stream once the appropriate backoff delay has elapsed.
+ */
+ FSTTimerIDListenStreamIdle,
+ FSTTimerIDListenStreamConnectionBackoff,
+ FSTTimerIDWriteStreamIdle,
+ FSTTimerIDWriteStreamConnectionBackoff,
+
+ /**
+ * A timer used in FSTOnlineStateTracker to transition from FSTOnlineState Unknown to Offline
+ * after a set timeout, rather than waiting indefinitely for success or failure.
+ */
+ FSTTimerIDOnlineStateTimeout
+};
+
+/**
+ * Handle to a callback scheduled via [FSTDispatchQueue dispatchAfterDelay:]. Supports cancellation
+ * via the cancel method.
+ */
+@interface FSTDelayedCallback : NSObject
+
+/**
+ * Cancels the callback if it hasn't already been executed or canceled.
+ *
+ * As long as the callback has not yet been run, calling cancel() (from a callback already running
+ * on the dispatch queue) provides a guarantee that the operation will not be run.
+ */
+- (void)cancel;
+
+@end
+
@interface FSTDispatchQueue : NSObject
/** Creates and returns an FSTDispatchQueue wrapping the specified dispatch_queue_t. */
@@ -34,6 +75,14 @@ NS_ASSUME_NONNULL_BEGIN
- (void)verifyIsCurrentQueue;
/**
+ * Declares that we are already executing on the correct dispatch_queue_t and would like to
+ * officially execute code on behalf of this FSTDispatchQueue. To be used only when called back
+ * by some other API directly onto our queue. This allows us to safely dispatch directly onto the
+ * worker queue without destroying the invariants this class helps us maintain.
+ */
+- (void)enterCheckedOperation:(void (^)(void))block;
+
+/**
* Same as dispatch_async() except it asserts that we're not already on the queue, since this
* generally indicates a bug (and can lead to re-ordering of operations, etc).
*
@@ -53,15 +102,42 @@ NS_ASSUME_NONNULL_BEGIN
- (void)dispatchAsyncAllowingSameQueue:(void (^)(void))block;
/**
+ * Wrapper for dispatch_sync(). Mostly meant for use in tests.
+ *
+ * @param block The block to run.
+ */
+- (void)dispatchSync:(void (^)(void))block;
+
+/**
* Schedules a callback after the specified delay.
*
* Unlike dispatchAsync: this method does not require you to dispatch to a different queue than
- * the current one (thus it is equivalent to a raw dispatch_after()).
+ * the current one.
+ *
+ * The returned FSTDelayedCallback handle can be used to cancel the callback prior to its running.
*
* @param block The block to run.
* @param delay The delay (in seconds) after which to run the block.
+ * @param timerID An FSTTimerID that can be used from tests to check for the presence of this
+ * callback or to schedule it to run early.
+ * @return A FSTDelayedCallback instance that can be used for cancellation.
+ */
+- (FSTDelayedCallback *)dispatchAfterDelay:(NSTimeInterval)delay
+ timerID:(FSTTimerID)timerID
+ block:(void (^)(void))block;
+
+/**
+ * For Tests: Determine if a delayed callback with a particular FSTTimerID exists.
+ */
+- (BOOL)containsDelayedCallbackWithTimerID:(FSTTimerID)timerID;
+
+/**
+ * For Tests: Runs delayed callbacks early, blocking until completion.
+ *
+ * @param lastTimerID Only delayed callbacks up to and including one that was scheduled using this
+ * FSTTimerID will be run. Method throws if no matching callback exists.
*/
-- (void)dispatchAfterDelay:(NSTimeInterval)delay block:(void (^)(void))block;
+- (void)runDelayedCallbacksUntil:(FSTTimerID)lastTimerID;
/** The underlying wrapped dispatch_queue_t */
@property(nonatomic, strong, readonly) dispatch_queue_t queue;
diff --git a/Firestore/Source/Util/FSTDispatchQueue.m b/Firestore/Source/Util/FSTDispatchQueue.m
deleted file mode 100644
index 6ce5d74..0000000
--- a/Firestore/Source/Util/FSTDispatchQueue.m
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright 2017 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/Foundation.h>
-
-#import "Firestore/Source/Util/FSTAssert.h"
-#import "Firestore/Source/Util/FSTDispatchQueue.h"
-
-NS_ASSUME_NONNULL_BEGIN
-
-@interface FSTDispatchQueue ()
-- (instancetype)initWithQueue:(dispatch_queue_t)queue NS_DESIGNATED_INITIALIZER;
-@end
-
-@implementation FSTDispatchQueue
-
-+ (instancetype)queueWith:(dispatch_queue_t)dispatchQueue {
- return [[FSTDispatchQueue alloc] initWithQueue:dispatchQueue];
-}
-
-- (instancetype)initWithQueue:(dispatch_queue_t)queue {
- if (self = [super init]) {
- _queue = queue;
- }
- return self;
-}
-
-- (void)verifyIsCurrentQueue {
- FSTAssert([self onTargetQueue],
- @"We are running on the wrong dispatch queue. Expected '%@' Actual: '%@'",
- [self targetQueueLabel], [self currentQueueLabel]);
-}
-
-- (void)dispatchAsync:(void (^)(void))block {
- FSTAssert(![self onTargetQueue],
- @"dispatchAsync called when we are already running on target dispatch queue '%@'",
- [self targetQueueLabel]);
-
- dispatch_async(self.queue, block);
-}
-
-- (void)dispatchAsyncAllowingSameQueue:(void (^)(void))block {
- dispatch_async(self.queue, block);
-}
-
-- (void)dispatchAfterDelay:(NSTimeInterval)delay block:(void (^)(void))block {
- dispatch_time_t delayNs = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC));
- dispatch_after(delayNs, self.queue, block);
-}
-
-#pragma mark - Private Methods
-
-- (NSString *)currentQueueLabel {
- return [NSString stringWithUTF8String:dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL)];
-}
-
-- (NSString *)targetQueueLabel {
- return [NSString stringWithUTF8String:dispatch_queue_get_label(self.queue)];
-}
-
-- (BOOL)onTargetQueue {
- return [[self currentQueueLabel] isEqualToString:[self targetQueueLabel]];
-}
-
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/Util/FSTDispatchQueue.mm b/Firestore/Source/Util/FSTDispatchQueue.mm
new file mode 100644
index 0000000..0974359
--- /dev/null
+++ b/Firestore/Source/Util/FSTDispatchQueue.mm
@@ -0,0 +1,314 @@
+/*
+ * Copyright 2017 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/Foundation.h>
+
+#include <atomic>
+
+#import "Firestore/Source/Util/FSTAssert.h"
+#import "Firestore/Source/Util/FSTDispatchQueue.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ * removeDelayedCallback is used by FSTDelayedCallback and so we pre-declare it before the rest of
+ * the FSTDispatchQueue private interface.
+ */
+@interface FSTDispatchQueue ()
+- (void)removeDelayedCallback:(FSTDelayedCallback *)callback;
+@end
+
+#pragma mark - FSTDelayedCallback
+
+/**
+ * Represents a callback scheduled to be run in the future on an FSTDispatchQueue.
+ *
+ * It is created via [FSTDelayedCallback createAndScheduleWithQueue].
+ *
+ * Supports cancellation (via cancel) and early execution (via skipDelay).
+ */
+@interface FSTDelayedCallback ()
+
+@property(nonatomic, strong, readonly) FSTDispatchQueue *queue;
+@property(nonatomic, assign, readonly) FSTTimerID timerID;
+@property(nonatomic, assign, readonly) NSTimeInterval targetTime;
+@property(nonatomic, copy) void (^callback)();
+/** YES if the callback has been run or canceled. */
+@property(nonatomic, getter=isDone) BOOL done;
+
+/**
+ * Creates and returns an FSTDelayedCallback that has been scheduled on the provided queue with the
+ * provided delay.
+ *
+ * @param queue The FSTDispatchQueue to run the callback on.
+ * @param timerID A FSTTimerID identifying the type of the delayed callback.
+ * @param delay The delay before the callback should be scheduled.
+ * @param callback The callback block to run.
+ * @return The created FSTDelayedCallback instance.
+ */
++ (instancetype)createAndScheduleWithQueue:(FSTDispatchQueue *)queue
+ timerID:(FSTTimerID)timerID
+ delay:(NSTimeInterval)delay
+ callback:(void (^)(void))callback;
+
+/**
+ * Queues the callback to run immediately (if it hasn't already been run or canceled).
+ */
+- (void)skipDelay;
+
+@end
+
+@implementation FSTDelayedCallback
+
+- (instancetype)initWithQueue:(FSTDispatchQueue *)queue
+ timerID:(FSTTimerID)timerID
+ targetTime:(NSTimeInterval)targetTime
+ callback:(void (^)(void))callback {
+ if (self = [super init]) {
+ _queue = queue;
+ _timerID = timerID;
+ _targetTime = targetTime;
+ _callback = callback;
+ _done = NO;
+ }
+ return self;
+}
+
++ (instancetype)createAndScheduleWithQueue:(FSTDispatchQueue *)queue
+ timerID:(FSTTimerID)timerID
+ delay:(NSTimeInterval)delay
+ callback:(void (^)(void))callback {
+ NSTimeInterval targetTime = [[NSDate date] timeIntervalSince1970] + delay;
+ FSTDelayedCallback *delayedCallback = [[FSTDelayedCallback alloc] initWithQueue:queue
+ timerID:timerID
+ targetTime:targetTime
+ callback:callback];
+ [delayedCallback startWithDelay:delay];
+ return delayedCallback;
+}
+
+/**
+ * Starts the timer. This is called immediately after construction by createAndScheduleWithQueue.
+ */
+- (void)startWithDelay:(NSTimeInterval)delay {
+ dispatch_time_t delayNs = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC));
+ dispatch_after(delayNs, self.queue.queue, ^{
+ [self.queue enterCheckedOperation:^{
+ [self delayDidElapse];
+ }];
+ });
+}
+
+- (void)skipDelay {
+ [self.queue dispatchAsyncAllowingSameQueue:^{
+ [self delayDidElapse];
+ }];
+}
+
+- (void)cancel {
+ [self.queue verifyIsCurrentQueue];
+ if (!self.isDone) {
+ // PORTING NOTE: There's no way to actually cancel the dispatched callback, but it'll be a no-op
+ // since we set done to YES.
+ [self markDone];
+ }
+}
+
+- (void)delayDidElapse {
+ [self.queue verifyIsCurrentQueue];
+ if (!self.isDone) {
+ [self markDone];
+ self.callback();
+ }
+}
+
+/**
+ * Marks this delayed callback as done, and notifies the FSTDispatchQueue that it should be removed.
+ */
+- (void)markDone {
+ self.done = YES;
+ [self.queue removeDelayedCallback:self];
+}
+
+@end
+
+#pragma mark - FSTDispatchQueue
+
+@interface FSTDispatchQueue ()
+/**
+ * Callbacks scheduled to be queued in the future. Callbacks are automatically removed after they
+ * are run or canceled.
+ */
+@property(nonatomic, strong, readonly) NSMutableArray<FSTDelayedCallback *> *delayedCallbacks;
+
+- (instancetype)initWithQueue:(dispatch_queue_t)queue NS_DESIGNATED_INITIALIZER;
+
+@end
+
+@implementation FSTDispatchQueue {
+ /**
+ * Flag set while an FSTDispatchQueue operation is currently executing. Used for assertion
+ * sanity-checks.
+ */
+ std::atomic<bool> _operationInProgress;
+}
+
++ (instancetype)queueWith:(dispatch_queue_t)dispatchQueue {
+ return [[FSTDispatchQueue alloc] initWithQueue:dispatchQueue];
+}
+
+- (instancetype)initWithQueue:(dispatch_queue_t)queue {
+ if (self = [super init]) {
+ _operationInProgress = false;
+ _queue = queue;
+ _delayedCallbacks = [NSMutableArray array];
+ }
+ return self;
+}
+
+- (void)verifyIsCurrentQueue {
+ FSTAssert([self onTargetQueue],
+ @"We are running on the wrong dispatch queue. Expected '%@' Actual: '%@'",
+ [self targetQueueLabel], [self currentQueueLabel]);
+ FSTAssert(_operationInProgress,
+ @"verifyIsCurrentQueue called outside enterCheckedOperation on queue '%@'",
+ [self currentQueueLabel]);
+}
+
+- (void)enterCheckedOperation:(void (^)(void))block {
+ FSTAssert(!_operationInProgress,
+ @"enterCheckedOperation may not be called when an operation is in progress");
+ @try {
+ _operationInProgress = true;
+ [self verifyIsCurrentQueue];
+ block();
+ } @finally {
+ _operationInProgress = false;
+ }
+}
+
+- (void)dispatchAsync:(void (^)(void))block {
+ FSTAssert(![self onTargetQueue] || !_operationInProgress,
+ @"dispatchAsync called when we are already running on target dispatch queue '%@'",
+ [self targetQueueLabel]);
+
+ dispatch_async(self.queue, ^{
+ [self enterCheckedOperation:block];
+ });
+}
+
+- (void)dispatchAsyncAllowingSameQueue:(void (^)(void))block {
+ dispatch_async(self.queue, ^{
+ [self enterCheckedOperation:block];
+ });
+}
+
+- (void)dispatchSync:(void (^)(void))block {
+ FSTAssert(![self onTargetQueue] || !_operationInProgress,
+ @"dispatchSync called when we are already running on target dispatch queue '%@'",
+ [self targetQueueLabel]);
+
+ dispatch_sync(self.queue, ^{
+ [self enterCheckedOperation:block];
+ });
+}
+
+- (FSTDelayedCallback *)dispatchAfterDelay:(NSTimeInterval)delay
+ timerID:(FSTTimerID)timerID
+ block:(void (^)(void))block {
+ // While not necessarily harmful, we currently don't expect to have multiple callbacks with the
+ // same timerID in the queue, so defensively reject them.
+ FSTAssert(![self containsDelayedCallbackWithTimerID:timerID],
+ @"Attempted to schedule multiple callbacks with id %ld", (unsigned long)timerID);
+ FSTDelayedCallback *delayedCallback = [FSTDelayedCallback createAndScheduleWithQueue:self
+ timerID:timerID
+ delay:delay
+ callback:block];
+ [self.delayedCallbacks addObject:delayedCallback];
+ return delayedCallback;
+}
+
+- (BOOL)containsDelayedCallbackWithTimerID:(FSTTimerID)timerID {
+ NSUInteger matchIndex = [self.delayedCallbacks
+ indexOfObjectPassingTest:^BOOL(FSTDelayedCallback *obj, NSUInteger idx, BOOL *stop) {
+ return obj.timerID == timerID;
+ }];
+ return matchIndex != NSNotFound;
+}
+
+- (void)runDelayedCallbacksUntil:(FSTTimerID)lastTimerID {
+ dispatch_semaphore_t doneSemaphore = dispatch_semaphore_create(0);
+
+ [self dispatchAsync:^{
+ FSTAssert(lastTimerID == FSTTimerIDAll || [self containsDelayedCallbackWithTimerID:lastTimerID],
+ @"Attempted to run callbacks until missing timer ID: %ld",
+ (unsigned long)lastTimerID);
+
+ [self sortDelayedCallbacks];
+ for (FSTDelayedCallback *callback in self.delayedCallbacks) {
+ [callback skipDelay];
+ if (lastTimerID != FSTTimerIDAll && callback.timerID == lastTimerID) {
+ break;
+ }
+ }
+
+ // Now that the callbacks are queued, we want to enqueue an additional item to release the
+ // 'done' semaphore.
+ [self dispatchAsyncAllowingSameQueue:^{
+ dispatch_semaphore_signal(doneSemaphore);
+ }];
+ }];
+
+ dispatch_semaphore_wait(doneSemaphore, DISPATCH_TIME_FOREVER);
+}
+
+// NOTE: For performance we could store the callbacks sorted (e.g. using std::priority_queue),
+// but this sort only happens in tests (if runDelayedCallbacksUntil: is called), and the size
+// is guaranteed to be small since we don't allow duplicate TimerIds (of which there are only 4).
+- (void)sortDelayedCallbacks {
+ // We want to run callbacks in the same order they'd run if they ran naturally.
+ [self.delayedCallbacks
+ sortUsingComparator:^NSComparisonResult(FSTDelayedCallback *a, FSTDelayedCallback *b) {
+ return a.targetTime < b.targetTime
+ ? NSOrderedAscending
+ : a.targetTime > b.targetTime ? NSOrderedDescending : NSOrderedSame;
+ }];
+}
+
+/** Called by FSTDelayedCallback when a callback is run or canceled. */
+- (void)removeDelayedCallback:(FSTDelayedCallback *)callback {
+ NSUInteger index = [self.delayedCallbacks indexOfObject:callback];
+ FSTAssert(index != NSNotFound, @"Delayed callback not found.");
+ [self.delayedCallbacks removeObjectAtIndex:index];
+}
+
+#pragma mark - Private Methods
+
+- (NSString *)currentQueueLabel {
+ return [NSString stringWithUTF8String:dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL)];
+}
+
+- (NSString *)targetQueueLabel {
+ return [NSString stringWithUTF8String:dispatch_queue_get_label(self.queue)];
+}
+
+- (BOOL)onTargetQueue {
+ return [[self currentQueueLabel] isEqualToString:[self targetQueueLabel]];
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/Util/FSTLogger.h b/Firestore/Source/Util/FSTLogger.h
index 699570a..c4e2b85 100644
--- a/Firestore/Source/Util/FSTLogger.h
+++ b/Firestore/Source/Util/FSTLogger.h
@@ -18,17 +18,9 @@
NS_ASSUME_NONNULL_BEGIN
-#ifdef __cplusplus
-extern "C" {
-#endif
-
/** Logs to NSLog if [FIRFirestore isLoggingEnabled] is YES. */
void FSTLog(NSString *format, ...) NS_FORMAT_FUNCTION(1, 2);
void FSTWarn(NSString *format, ...) NS_FORMAT_FUNCTION(1, 2);
-#ifdef __cplusplus
-} // extern "C"
-#endif
-
NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/Util/FSTLogger.m b/Firestore/Source/Util/FSTLogger.mm
index f0081e0..f0081e0 100644
--- a/Firestore/Source/Util/FSTLogger.m
+++ b/Firestore/Source/Util/FSTLogger.mm
diff --git a/Firestore/Source/Util/FSTUsageValidation.h b/Firestore/Source/Util/FSTUsageValidation.h
index 34a3d64..05933ea 100644
--- a/Firestore/Source/Util/FSTUsageValidation.h
+++ b/Firestore/Source/Util/FSTUsageValidation.h
@@ -14,14 +14,10 @@
* limitations under the License.
*/
-#include <Foundation/Foundation.h>
+#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
-#if __cplusplus
-extern "C" {
-#endif
-
/** Helper for creating a general exception for invalid usage of an API. */
NSException *FSTInvalidUsage(NSString *exceptionName, NSString *format, ...);
@@ -46,8 +42,4 @@ NSException *FSTInvalidUsage(NSString *exceptionName, NSString *format, ...);
@throw FSTInvalidUsage(@"FIRInvalidArgumentException", format, ##__VA_ARGS__); \
} while (0)
-#if __cplusplus
-} // extern "C"
-#endif
-
NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/Util/FSTUsageValidation.m b/Firestore/Source/Util/FSTUsageValidation.mm
index 82128f4..93abf87 100644
--- a/Firestore/Source/Util/FSTUsageValidation.m
+++ b/Firestore/Source/Util/FSTUsageValidation.mm
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#include <Foundation/Foundation.h>
+#import "Firestore/Source/Util/FSTUsageValidation.h"
NS_ASSUME_NONNULL_BEGIN
diff --git a/Firestore/Swift/Source/Codable/CodableGeoPoint.swift b/Firestore/Swift/Source/Codable/CodableGeoPoint.swift
new file mode 100644
index 0000000..fa56340
--- /dev/null
+++ b/Firestore/Swift/Source/Codable/CodableGeoPoint.swift
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2018 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 FirebaseFirestore
+
+/**
+ * A protocol describing the encodable properties of a GeoPoint.
+ *
+ * Note: this protocol exists as a workaround for the Swift compiler: if the GeoPoint class was
+ * extended directly to conform to Codable, the methods implementing the protcol would be need to be
+ * marked required but that can't be done in an extension. Declaring the extension on the protocol
+ * sidesteps this issue.
+ */
+fileprivate protocol CodableGeoPoint: Codable {
+ var latitude: Double { get }
+ var longitude: Double { get }
+
+ init(latitude: Double, longitude: Double)
+}
+
+/** The keys in a GeoPoint. Must match the properties of CodableGeoPoint. */
+fileprivate enum GeoPointKeys: String, CodingKey {
+ case latitude
+ case longitude
+}
+
+/**
+ * An extension of GeoPoint that implements the behavior of the Codable protocol.
+ *
+ * Note: this is implemented manually here because the Swift compiler can't synthesize these methods
+ * when declaring an extension to conform to Codable.
+ */
+extension CodableGeoPoint {
+ 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)
+ }
+}
+
+/** Extends GeoPoint to conform to Codable. */
+extension GeoPoint: CodableGeoPoint {}
diff --git a/Firestore/Swift/Tests/Codable/CodableGeoPointTests.swift b/Firestore/Swift/Tests/Codable/CodableGeoPointTests.swift
new file mode 100644
index 0000000..6b1dce4
--- /dev/null
+++ b/Firestore/Swift/Tests/Codable/CodableGeoPointTests.swift
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2018 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 FirebaseFirestore
+import FirebaseFirestoreSwift
+import Foundation
+import XCTest
+
+class CodableGeoPointTests: XCTestCase {
+ func testGeoPointEncodes() {
+ let geoPoint = GeoPoint(latitude: 37.77493, longitude: -122.41942)
+
+ let jsonData = try! JSONEncoder().encode(geoPoint)
+ let json = String(data: jsonData, encoding: .utf8)!
+
+ // The ordering of attributes in the JSON output is not guaranteed, nor is the rounding of
+ // the values so just verify that each required property is present and that the value
+ // starts as expected.
+ XCTAssert(json.contains("\"latitude\":37."))
+ XCTAssert(json.contains("\"longitude\":-122."))
+ }
+
+ func testGeoPointDecodes() {
+ let json = """
+ {
+ "latitude": 37.77493,
+ "longitude": -122.41942
+ }
+ """
+ let jsonData: Data = json.data(using: .utf8)!
+
+ let geoPoint = try! JSONDecoder().decode(GeoPoint.self, from: jsonData)
+ XCTAssertEqual(37.77493, geoPoint.latitude, accuracy: 0.0001)
+ XCTAssertEqual(-122.41942, geoPoint.longitude, accuracy: 0.0001)
+ }
+}
diff --git a/Firestore/Swift/Tests/Info.plist b/Firestore/Swift/Tests/Info.plist
new file mode 100644
index 0000000..6c40a6c
--- /dev/null
+++ b/Firestore/Swift/Tests/Info.plist
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>$(DEVELOPMENT_LANGUAGE)</string>
+ <key>CFBundleExecutable</key>
+ <string>$(EXECUTABLE_NAME)</string>
+ <key>CFBundleIdentifier</key>
+ <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>$(PRODUCT_NAME)</string>
+ <key>CFBundlePackageType</key>
+ <string>BNDL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>1.0</string>
+ <key>CFBundleVersion</key>
+ <string>1</string>
+</dict>
+</plist>
diff --git a/Firestore/core/.clang-tidy b/Firestore/core/.clang-tidy
new file mode 100644
index 0000000..a071105
--- /dev/null
+++ b/Firestore/core/.clang-tidy
@@ -0,0 +1,34 @@
+---
+# cert-*
+# -cert-dcl50-cpp
+# We use variadic functions
+# -cert-err58-cpp
+# GoogleTest creates instances in static scope in a way that trips this
+# warning for every test.
+# readability-*
+# -readability-else-after-return
+# -readability-implicit-bool-conversion
+# These checks generate a bunch of noise that we're just not religious
+# about.
+# modernize-*
+# -modernize-use-equals-default
+# VS 2015 and Xcode <= 8.2 don't fully support this so we don't use
+# `= default`.
+# -modernize-use-equals-delete
+# GoogleTest generates test classes that use the old idiom of making
+# default constructors and operator= private.
+Checks: 'bugprone-*,cert-*,-cert-dcl50-cpp,-cert-err58-cpp,google-*,objc-*,readability-*,-readability-else-after-return,-readability-implicit-bool-conversion,misc-*,modernize-*,-modernize-use-equals-default,-modernize-use-equals-delete,clang-diagnostic-*,clang-analyzer-*'
+WarningsAsErrors: ''
+HeaderFilterRegex: ''
+CheckOptions:
+ - key: readability-braces-around-statements.ShortStatementLines
+ value: '1'
+ - key: google-readability-braces-around-statements.ShortStatementLines
+ value: '1'
+ - key: google-readability-function-size.StatementThreshold
+ value: '800'
+ - key: google-readability-namespace-comments.ShortNamespaceLines
+ value: '10'
+ - key: google-readability-namespace-comments.SpacesBeforeComments
+ value: '2'
+...
diff --git a/Firestore/core/CMakeLists.txt b/Firestore/core/CMakeLists.txt
index c49b6db..1a0c936 100644
--- a/Firestore/core/CMakeLists.txt
+++ b/Firestore/core/CMakeLists.txt
@@ -12,5 +12,23 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+add_subdirectory(include/firebase/firestore)
+
+add_subdirectory(src/firebase/firestore)
+add_subdirectory(src/firebase/firestore/auth)
+add_subdirectory(src/firebase/firestore/core)
+add_subdirectory(src/firebase/firestore/immutable)
+add_subdirectory(src/firebase/firestore/local)
+add_subdirectory(src/firebase/firestore/model)
+add_subdirectory(src/firebase/firestore/remote)
add_subdirectory(src/firebase/firestore/util)
+
+add_subdirectory(test/firebase/firestore/testutil)
+add_subdirectory(test/firebase/firestore)
+add_subdirectory(test/firebase/firestore/auth)
+add_subdirectory(test/firebase/firestore/core)
+add_subdirectory(test/firebase/firestore/immutable)
+add_subdirectory(test/firebase/firestore/local)
+add_subdirectory(test/firebase/firestore/model)
+add_subdirectory(test/firebase/firestore/remote)
add_subdirectory(test/firebase/firestore/util)
diff --git a/Firestore/core/include/firebase/firestore/CMakeLists.txt b/Firestore/core/include/firebase/firestore/CMakeLists.txt
new file mode 100644
index 0000000..e4e7acd
--- /dev/null
+++ b/Firestore/core/include/firebase/firestore/CMakeLists.txt
@@ -0,0 +1,25 @@
+# Copyright 2018 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.
+
+# Hack to make the headers show up in IDEs
+# (see https://stackoverflow.com/questions/27039019/ and open issue on CMake
+# issue tracker: https://gitlab.kitware.com/cmake/cmake/issues/15234)
+add_custom_target(firebase_firestore_types_ide SOURCES
+ document_reference.h
+ event_listener.h
+ firestore.h
+ firestore_errors.h
+ geo_point.h
+ timestamp.h
+)
diff --git a/Firestore/core/include/firebase/firestore/document_reference.h b/Firestore/core/include/firebase/firestore/document_reference.h
new file mode 100644
index 0000000..d295188
--- /dev/null
+++ b/Firestore/core/include/firebase/firestore/document_reference.h
@@ -0,0 +1,375 @@
+/*
+ * Copyright 2018 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.
+ */
+
+// TODO(rsgowman): This file isn't intended to be used just yet. It's just an
+// outline of what the API might eventually look like. Most of this was
+// shamelessly stolen and modified from RTDB's header file, melded with the
+// (java) firestore api.
+
+#ifndef FIRESTORE_CORE_INCLUDE_FIREBASE_FIRESTORE_DOCUMENT_REFERENCE_H_
+#define FIRESTORE_CORE_INCLUDE_FIREBASE_FIRESTORE_DOCUMENT_REFERENCE_H_
+
+#include <string>
+#include <unordered_map>
+
+#if defined(FIREBASE_USE_STD_FUNCTION)
+#include <functional>
+#endif
+
+// TODO(rsgowman): Note that RTDB uses:
+// #if defined(FIREBASE_USE_MOVE_OPERATORS) || defined(DOXYGEN
+// to protect move operators from older compilers. But all our supported
+// compilers support this, so we've skipped the #if guard. This TODO comment is
+// here so we don't forget to mention this during the API review, and should be
+// removed once this note has migrated to the API review doc.
+
+// TODO(rsgowman): replace these forward decls with appropriate includes (once
+// they exist)
+namespace firebase {
+class App;
+template <typename T>
+class Future;
+} // namespace firebase
+
+namespace firebase {
+namespace firestore {
+
+// TODO(rsgowman): replace these forward decls with appropriate includes (once
+// they exist)
+class FieldValue;
+class DocumentSnapshot;
+class Firestore;
+class Error;
+template <typename T>
+class EventListener;
+class ListenerRegistration;
+class CollectionReference;
+class DocumentListenOptions;
+// TODO(rsgowman): not quite a forward decl, but required to make the default
+// parameter to Set() "compile".
+class SetOptions {
+ public:
+ SetOptions();
+};
+
+// TODO(rsgowman): move this into the FieldValue header
+#ifdef STLPORT
+using MapFieldValue = std::tr1::unordered_map<std::string, FieldValue>;
+#else
+using MapFieldValue = std::unordered_map<std::string, FieldValue>;
+#endif
+
+/**
+ * A DocumentReference refers to a document location in a Firestore database and
+ * can be used to write, read, or listen to the location. There may or may not
+ * exist a document at the referenced location. A DocumentReference can also be
+ * used to create a CollectionReference to a subcollection.
+ *
+ * Create a DocumentReference via Firebase::Document(const string& path).
+ *
+ * Subclassing Note: Firestore classes are not meant to be subclassed except for
+ * use in test mocks. Subclassing is not supported in production code and new
+ * SDK releases may break code that does so.
+ */
+class DocumentReference {
+ public:
+ /**
+ * @brief Default constructor. This creates an invalid DocumentReference.
+ * Attempting to perform any operations on this reference will fail (and cause
+ * a crash) unless a valid DocumentReference has been assigned to it.
+ */
+ DocumentReference();
+
+ /**
+ * @brief Copy constructor. It's totally okay (and efficient) to copy
+ * DocumentReference instances, as they simply point to the same location in
+ * the database.
+ *
+ * @param[in] reference DocumentReference to copy from.
+ */
+ DocumentReference(const DocumentReference& reference);
+
+ /**
+ * @brief Move constructor. Moving is an efficient operation for
+ * DocumentReference instances.
+ *
+ * @param[in] reference DocumentReference to move data from.
+ */
+ DocumentReference(DocumentReference&& reference);
+
+ virtual ~DocumentReference();
+
+ /**
+ * @brief Copy assignment operator. It's totally okay (and efficient) to copy
+ * DocumentReference instances, as they simply point to the same location in
+ * the database.
+ *
+ * @param[in] reference DocumentReference to copy from.
+ *
+ * @returns Reference to the destination DocumentReference.
+ */
+ DocumentReference& operator=(const DocumentReference& reference);
+
+ /**
+ * @brief Move assignment operator. Moving is an efficient operation for
+ * DocumentReference instances.
+ *
+ * @param[in] reference DocumentReference to move data from.
+ *
+ * @returns Reference to the destination DocumentReference.
+ */
+ DocumentReference& operator=(DocumentReference&& reference);
+
+ /**
+ * @brief Returns the Firestore instance associated with this document
+ * reference.
+ *
+ * The pointer will remain valid indefinitely.
+ *
+ * @returns Firebase Firestore instance that this DocumentReference refers to.
+ */
+ virtual const Firestore* firestore() const;
+
+ /**
+ * @brief Returns the Firestore instance associated with this document
+ * reference.
+ *
+ * The pointer will remain valid indefinitely.
+ *
+ * @returns Firebase Firestore instance that this DocumentReference refers to.
+ */
+ virtual Firestore* firestore();
+
+ /**
+ * @brief Returns the string id of this document location.
+ *
+ * The pointer is only valid while the DocumentReference remains in memory.
+ *
+ * @returns String id of this document location, which will remain valid in
+ * memory until the DocumentReference itself goes away.
+ */
+ virtual const char* id() const;
+
+ /**
+ * @brief Returns the string id of this document location.
+ *
+ * @returns String id of this document location.
+ */
+ virtual std::string id_string() const;
+
+ /**
+ * @brief Returns the path of this document (relative to the root of the
+ * database) as a slash-separated string.
+ *
+ * The pointer is only valid while the DocumentReference remains in memory.
+ *
+ * @returns String path of this document location, which will remain valid in
+ * memory until the DocumentReference itself goes away.
+ */
+ virtual const char* path() const;
+
+ /**
+ * @brief Returns the path of this document (relative to the root of the
+ * database) as a slash-separated string.
+ *
+ * @returns String path of this document location.
+ */
+ virtual std::string path_string() const;
+
+ /**
+ * @brief Returns a CollectionReference to the collection that contains this
+ * document.
+ */
+ virtual CollectionReference get_parent() const;
+
+ /**
+ * @brief Returns a CollectionReference instance that refers to the
+ * subcollection at the specified path relative to this document.
+ *
+ * @param[in] collection_path A slash-separated relative path to a
+ * subcollection. The pointer only needs to be valid during this call.
+ *
+ * @return The CollectionReference instance.
+ */
+ virtual CollectionReference Collection(const char* collection_path) const;
+
+ /**
+ * @brief Returns a CollectionReference instance that refers to the
+ * subcollection at the specified path relative to this document.
+ *
+ * @param[in] collection_path A slash-separated relative path to a
+ * subcollection.
+ *
+ * @return The CollectionReference instance.
+ */
+ virtual CollectionReference Collection(
+ const std::string& collection_path) const;
+
+ /**
+ * @brief Reads the document referenced by this DocumentReference.
+ *
+ * @return A Future that will be resolved with the contents of the Document at
+ * this DocumentReference.
+ */
+ virtual Future<DocumentSnapshot> Get() const;
+
+ /**
+ * @brief Writes to the document referred to by this DocumentReference.
+ *
+ * If the document does not yet exist, it will be created. If you pass
+ * SetOptions, the provided data can be merged into an existing document.
+ *
+ * @param[in] data A map of the fields and values for the document.
+ * @param[in] options An object to configure the set behavior.
+ *
+ * @return A Future that will be resolved when the write finishes.
+ */
+ virtual Future<void> Set(const MapFieldValue& data,
+ const SetOptions& options = SetOptions());
+
+ /**
+ * @brief Updates fields in the document referred to by this
+ * DocumentReference.
+ *
+ * If no document exists yet, the update will fail.
+ *
+ * @param[in] data A map of field / value pairs to update. Fields can contain
+ * dots to reference nested fields within the document.
+ *
+ * @return A Future that will be resolved when the write finishes.
+ */
+ virtual Future<void> Update(const MapFieldValue& data);
+
+ /**
+ * @brief Removes the document referred to by this DocumentReference.
+ *
+ * @return A Task that will be resolved when the delete completes.
+ */
+ virtual Future<void> Delete();
+
+ /**
+ * @brief Starts listening to the document referenced by this
+ * DocumentReference.
+ *
+ * @param[in] listener The event listener that will be called with the
+ * snapshots, which must remain in memory until you remove the listener from
+ * this DocumentReference. (Ownership is not transferred; you are responsible
+ * for making sure that listener is valid as long as this DocumentReference is
+ * valid and the listener is registered.)
+ *
+ * @return A registration object that can be used to remove the listener.
+ */
+ virtual ListenerRegistration AddSnapshotListener(
+ EventListener<DocumentSnapshot>* listener);
+
+ /**
+ * @brief Starts listening to the document referenced by this
+ * DocumentReference.
+ *
+ * @param[in] options The options to use for this listen.
+ * @param[in] listener The event listener that will be called with the
+ * snapshots, which must remain in memory until you remove the listener from
+ * this DocumentReference. (Ownership is not transferred; you are responsible
+ * for making sure that listener is valid as long as this DocumentReference is
+ * valid and the listener is registered.)
+ *
+ * @return A registration object that can be used to remove the listener.
+ */
+ virtual ListenerRegistration AddSnapshotListener(
+ const DocumentListenOptions& options,
+ EventListener<DocumentSnapshot>* listener);
+
+#if defined(FIREBASE_USE_STD_FUNCTION) || defined(DOXYGEN)
+ /**
+ * @brief Starts listening to the document referenced by this
+ * DocumentReference.
+ *
+ * @param[in] callback function or lambda to call. When this function is
+ * called, exactly one of the parameters will be non-null.
+ *
+ * @return A registration object that can be used to remove the listener.
+ *
+ * @note This method is not available when using STLPort on Android, as
+ * std::function is not supported on STLPort.
+ */
+ virtual ListenerRegistration AddSnapshotListener(
+ std::function<void(const DocumentSnapshot*, const Error*)> callback);
+
+ /**
+ * @brief Starts listening to the document referenced by this
+ * DocumentReference.
+ *
+ * @param[in] options The options to use for this listen.
+ * @param[in] callback function or lambda to call. When this function is
+ * called, exactly one of the parameters will be non-null.
+ *
+ * @return A registration object that can be used to remove the listener.
+ *
+ * @note This method is not available when using STLPort on Android, as
+ * std::function is not supported on STLPort.
+ */
+ virtual ListenerRegistration AddSnapshotListener(
+ const DocumentListenOptions& options,
+ std::function<void(const DocumentSnapshot*, const Error*)> callback);
+#endif // defined(FIREBASE_USE_STD_FUNCTION) || defined(DOXYGEN)
+};
+
+// TODO(rsgowman): probably define and inline here.
+bool operator==(const DocumentReference& lhs, const DocumentReference& rhs);
+
+inline bool operator!=(const DocumentReference& lhs,
+ const DocumentReference& rhs) {
+ return !(lhs == rhs);
+}
+
+// TODO(rsgowman): probably define and inline here.
+bool operator<(const DocumentReference& lhs, const DocumentReference& rhs);
+
+inline bool operator>(const DocumentReference& lhs,
+ const DocumentReference& rhs) {
+ return rhs < lhs;
+}
+
+inline bool operator<=(const DocumentReference& lhs,
+ const DocumentReference& rhs) {
+ return !(lhs > rhs);
+}
+
+inline bool operator>=(const DocumentReference& lhs,
+ const DocumentReference& rhs) {
+ return !(lhs < rhs);
+}
+
+} // namespace firestore
+} // namespace firebase
+
+namespace std {
+// TODO(rsgowman): NB that specialization of std::hash deviates from the Google
+// C++ style guide. But we think this is probably ok in this case since:
+// a) It's the standard way of doing this outside of Google (as the style guide
+// itself points out), and
+// b) This has a straightforward hash function anyway (just hash the path) so I
+// don't think the concerns in the style guide are going to bite us.
+//
+// Raise this concern during the API review.
+template <>
+struct hash<firebase::firestore::DocumentReference> {
+ std::size_t operator()(
+ const firebase::firestore::DocumentReference& doc_ref) const;
+};
+} // namespace std
+
+#endif // FIRESTORE_CORE_INCLUDE_FIREBASE_FIRESTORE_DOCUMENT_REFERENCE_H_
diff --git a/Firestore/core/include/firebase/firestore/event_listener.h b/Firestore/core/include/firebase/firestore/event_listener.h
new file mode 100644
index 0000000..6c94428
--- /dev/null
+++ b/Firestore/core/include/firebase/firestore/event_listener.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2018 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.
+ */
+
+// TODO(rsgowman): This file isn't intended to be used just yet. It's just an
+// outline of what the API might eventually look like. Most of this was
+// shamelessly stolen and modified from rtdb's header file, melded with the
+// (java) firestore api.
+
+#ifndef FIRESTORE_CORE_INCLUDE_FIREBASE_FIRESTORE_EVENT_LISTENER_H_
+#define FIRESTORE_CORE_INCLUDE_FIREBASE_FIRESTORE_EVENT_LISTENER_H_
+
+namespace firebase {
+namespace firestore {
+
+// TODO(rsgowman): replace these forward decl's with appropriate includes (once
+// they exist)
+class Error;
+
+/**
+ * @brief An interface for event listeners.
+ */
+template <typename T>
+class EventListener {
+ public:
+ /**
+ * @brief OnEvent will be called with the new value or the error if an error
+ * occurred.
+ *
+ * It's guaranteed that exactly one of value or error will be non-null.
+ *
+ * @param value The value of the event. null if there was an error.
+ * @param error The error if there was error. null otherwise.
+ */
+ void OnEvent(const T* value, const Error* error);
+};
+
+} // namespace firestore
+} // namespace firebase
+
+#endif // FIRESTORE_CORE_INCLUDE_FIREBASE_FIRESTORE_EVENT_LISTENER_H_
diff --git a/Firestore/core/include/firebase/firestore/firestore.h b/Firestore/core/include/firebase/firestore/firestore.h
new file mode 100644
index 0000000..793fdd0
--- /dev/null
+++ b/Firestore/core/include/firebase/firestore/firestore.h
@@ -0,0 +1,160 @@
+/*
+ * Copyright 2018 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.
+ */
+
+// TODO(rsgowman): This file isn't intended to be used just yet. It's just an
+// outline of what the API might eventually look like. Most of this was
+// shamelessly stolen and modified from rtdb's header file, melded with the
+// firestore api.
+
+#ifndef FIRESTORE_CORE_INCLUDE_FIREBASE_FIRESTORE_FIRESTORE_H_
+#define FIRESTORE_CORE_INCLUDE_FIREBASE_FIRESTORE_FIRESTORE_H_
+
+#include <string>
+
+// TODO(rsgowman): replace these forward decl's with appropriate includes (once
+// they exist)
+namespace firebase {
+class App;
+class InitResult;
+} // namespace firebase
+
+namespace firebase {
+namespace firestore {
+
+// TODO(rsgowman): replace these forward decl's with appropriate includes (once
+// they exist)
+class DocumentReference;
+class CollectionReference;
+class Settings;
+
+/**
+ * @brief Entry point for the Firebase Firestore C++ SDK.
+ *
+ * To use the SDK, call firebase::firestore::Firestore::GetInstance() to obtain
+ * an instance of Firestore, then use Collection() or Document() to obtain
+ * references to child paths within the database. From there you can set data
+ * via CollectionReference::Add() and DocumentReference::Set(), or get data via
+ * CollectionReference::Get() and DocumentReference::Get(), attach listeners,
+ * and more.
+ *
+ * Subclassing Note: Firestore classes are not meant to be subclassed except for
+ * use in test mocks. Subclassing is not supported in production code and new
+ * SDK releases may break code that does so.
+ */
+class Firestore {
+ public:
+ /**
+ * @brief Returns an instance of Firestore corresponding to the given App.
+ *
+ * Firebase Firestore uses firebase::App to communicate with Firebase
+ * Authentication to authenticate users to the Firestore server backend.
+ *
+ * If you call GetInstance() multiple times with the same App, you will get
+ * the same instance of App.
+ *
+ * @param[in] app Your instance of firebase::App. Firebase Firestore will use
+ * this to communicate with Firebase Authentication.
+ * @param[out] init_result_out Optional: If provided, write the init result
+ * here. Will be set to kInitResultSuccess if initialization succeeded, or
+ * kInitResultFailedMissingDependency on Android if Google Play services is
+ * not available on the current device.
+ *
+ * @returns An instance of Firestore corresponding to the given App.
+ */
+ static Firestore* GetInstance(::firebase::App* app,
+ InitResult* init_result_out = nullptr);
+
+ static Firestore* GetInstance(InitResult* init_result_out = nullptr);
+
+ /**
+ * @brief Destructor for the Firestore object.
+ *
+ * When deleted, this instance will be removed from the cache of Firestore
+ * objects. If you call GetInstance() in the future with the same App, a new
+ * Firestore instance will be created.
+ */
+ virtual ~Firestore();
+
+ /**
+ * @brief Returns the firebase::App that this Firestore was created with.
+ *
+ * @returns The firebase::App this Firestore was created with.
+ */
+ virtual const App* app() const;
+
+ /**
+ * @brief Returns the firebase::App that this Firestore was created with.
+ *
+ * @returns The firebase::App this Firestore was created with.
+ */
+ virtual App* app();
+
+ /**
+ * @brief Returns a CollectionReference instance that refers to the
+ * collection at the specified path within the database.
+ *
+ * @param[in] collection_path A slash-separated path to a collection.
+ *
+ * @return The CollectionReference instance.
+ */
+ virtual CollectionReference Collection(const char* collection_path) const;
+
+ /**
+ * @brief Returns a CollectionReference instance that refers to the
+ * collection at the specified path within the database.
+ *
+ * @param[in] collection_path A slash-separated path to a collection.
+ *
+ * @return The CollectionReference instance.
+ */
+ virtual CollectionReference Collection(
+ const std::string& collection_path) const;
+
+ /**
+ * @brief Returns a DocumentReference instance that refers to the document at
+ * the specified path within the database.
+ *
+ * @param[in] document_path A slash-separated path to a document.
+ * @return The DocumentReference instance.
+ */
+ virtual DocumentReference Document(const char* document_path) const;
+
+ /**
+ * @brief Returns a DocumentReference instance that refers to the document at
+ * the specified path within the database.
+ *
+ * @param[in] document_path A slash-separated path to a document.
+ *
+ * @return The DocumentReference instance.
+ */
+ virtual DocumentReference Document(const std::string& document_path) const;
+
+ /** Returns the settings used by this Firestore object. */
+ virtual Settings settings() const;
+
+ /** Sets any custom settings used to configure this Firestore object. */
+ virtual void set_settings(const Settings& settings);
+
+ // TODO(rsgowman): batch(), runTransaction()
+
+ /** Globally enables / disables Firestore logging for the SDK. */
+ static void set_logging_enabled(bool logging_enabled);
+};
+
+} // namespace firestore
+} // namespace firebase
+
+#endif // FIRESTORE_CORE_INCLUDE_FIREBASE_FIRESTORE_FIRESTORE_H_
diff --git a/Firestore/core/include/firebase/firestore/firestore_errors.h b/Firestore/core/include/firebase/firestore/firestore_errors.h
new file mode 100644
index 0000000..7a0ff7c
--- /dev/null
+++ b/Firestore/core/include/firebase/firestore/firestore_errors.h
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#ifndef FIRESTORE_CORE_INCLUDE_FIREBASE_FIRESTORE_FIRESTORE_ERRORS_H_
+#define FIRESTORE_CORE_INCLUDE_FIREBASE_FIRESTORE_FIRESTORE_ERRORS_H_
+
+namespace firebase {
+namespace firestore {
+
+/**
+ * Error codes used by Cloud Firestore.
+ *
+ * The codes are in sync with the Firestore iOS client SDK header file
+ * FIRFirestoreErrors.h.
+ */
+enum FirestoreErrorCode {
+ /**
+ * The operation completed successfully. NSError objects will never have a
+ * code with this value.
+ */
+ Ok = 0,
+
+ /** The operation was cancelled (typically by the caller). */
+ Cancelled = 1,
+
+ /** Unknown error or an error from a different error domain. */
+ Unknown = 2,
+
+ /**
+ * Client specified an invalid argument. Note that this differs from
+ * FailedPrecondition. InvalidArgument indicates arguments that are
+ * problematic regardless of the state of the system (e.g., an invalid field
+ * name).
+ */
+ InvalidArgument = 3,
+
+ /**
+ * Deadline expired before operation could complete. For operations that
+ * change the state of the system, this error may be returned even if the
+ * operation has completed successfully. For example, a successful response
+ * from a server could have been delayed long enough for the deadline to
+ * expire.
+ */
+ DeadlineExceeded = 4,
+
+ /** Some requested document was not found. */
+ NotFound = 5,
+
+ /** Some document that we attempted to create already exists. */
+ AlreadyExists = 6,
+
+ /** The caller does not have permission to execute the specified operation. */
+ PermissionDenied = 7,
+
+ /**
+ * Some resource has been exhausted, perhaps a per-user quota, or perhaps the
+ * entire file system is out of space.
+ */
+ ResourceExhausted = 8,
+
+ /**
+ * Operation was rejected because the system is not in a state required for
+ * the operation's execution.
+ */
+ FailedPrecondition = 9,
+
+ /**
+ * The operation was aborted, typically due to a concurrency issue like
+ * transaction aborts, etc.
+ */
+ Aborted = 10,
+
+ /** Operation was attempted past the valid range. */
+ OutOfRange = 11,
+
+ /** Operation is not implemented or not supported/enabled. */
+ Unimplemented = 12,
+
+ /**
+ * Internal errors. Means some invariants expected by underlying system has
+ * been broken. If you see one of these errors, something is very broken.
+ */
+ Internal = 13,
+
+ /**
+ * The service is currently unavailable. This is a most likely a transient
+ * condition and may be corrected by retrying with a backoff.
+ */
+ Unavailable = 14,
+
+ /** Unrecoverable data loss or corruption. */
+ DataLoss = 15,
+
+ /** The request does not have valid authentication credentials for the
+ operation. */
+ Unauthenticated = 16
+};
+
+} // namespace firestore
+} // namespace firebase
+
+#endif // FIRESTORE_CORE_INCLUDE_FIREBASE_FIRESTORE_FIRESTORE_ERRORS_H_
diff --git a/Firestore/core/include/firebase/firestore/geo_point.h b/Firestore/core/include/firebase/firestore/geo_point.h
new file mode 100644
index 0000000..cc366be
--- /dev/null
+++ b/Firestore/core/include/firebase/firestore/geo_point.h
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#ifndef FIRESTORE_CORE_INCLUDE_FIREBASE_FIRESTORE_GEO_POINT_H_
+#define FIRESTORE_CORE_INCLUDE_FIREBASE_FIRESTORE_GEO_POINT_H_
+
+namespace firebase {
+namespace firestore {
+
+/**
+ * An immutable object representing a geographical point in Firestore. The point
+ * is represented as a latitude/longitude pair.
+ *
+ * Latitude values are in the range of [-90, 90].
+ * Longitude values are in the range of [-180, 180].
+ */
+class GeoPoint {
+ public:
+ /**
+ * Creates a `GeoPoint` with both latitude and longitude being 0.
+ */
+ GeoPoint();
+
+ /**
+ * Creates a `GeoPoint` from the provided latitude and longitude degrees.
+ *
+ * @param latitude The latitude as number between -90 and 90.
+ * @param longitude The longitude as number between -180 and 180.
+ */
+ GeoPoint(double latitude, double longitude);
+
+ GeoPoint(const GeoPoint& other) = default;
+ GeoPoint(GeoPoint&& other) = default;
+ GeoPoint& operator=(const GeoPoint& other) = default;
+ GeoPoint& operator=(GeoPoint&& other) = default;
+
+ double latitude() const {
+ return latitude_;
+ }
+
+ double longitude() const {
+ return longitude_;
+ }
+
+ private:
+ double latitude_;
+ double longitude_;
+};
+
+/** Compares against another GeoPoint. */
+bool operator<(const GeoPoint& lhs, const GeoPoint& rhs);
+
+inline bool operator>(const GeoPoint& lhs, const GeoPoint& rhs) {
+ return rhs < lhs;
+}
+
+inline bool operator>=(const GeoPoint& lhs, const GeoPoint& rhs) {
+ return !(lhs < rhs);
+}
+
+inline bool operator<=(const GeoPoint& lhs, const GeoPoint& rhs) {
+ return !(lhs > rhs);
+}
+
+inline bool operator!=(const GeoPoint& lhs, const GeoPoint& rhs) {
+ return lhs < rhs || lhs > rhs;
+}
+
+inline bool operator==(const GeoPoint& lhs, const GeoPoint& rhs) {
+ return !(lhs != rhs);
+}
+
+} // namespace firestore
+} // namespace firebase
+
+#endif // FIRESTORE_CORE_INCLUDE_FIREBASE_FIRESTORE_GEO_POINT_H_
diff --git a/Firestore/core/include/firebase/firestore/timestamp.h b/Firestore/core/include/firebase/firestore/timestamp.h
new file mode 100644
index 0000000..1736981
--- /dev/null
+++ b/Firestore/core/include/firebase/firestore/timestamp.h
@@ -0,0 +1,221 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#ifndef FIRESTORE_CORE_INCLUDE_FIREBASE_FIRESTORE_TIMESTAMP_H_
+#define FIRESTORE_CORE_INCLUDE_FIREBASE_FIRESTORE_TIMESTAMP_H_
+
+#include <cstdint>
+#include <ctime>
+#include <string>
+
+#if !defined(_STLPORT_VERSION)
+#include <chrono> // NOLINT(build/c++11)
+#endif // !defined(_STLPORT_VERSION)
+
+namespace firebase {
+
+/**
+ * A Timestamp represents a point in time independent of any time zone or
+ * calendar, represented as seconds and fractions of seconds at nanosecond
+ * resolution in UTC Epoch time. It is encoded using the Proleptic Gregorian
+ * Calendar which extends the Gregorian calendar backwards to year one. It is
+ * encoded assuming all minutes are 60 seconds long, i.e. leap seconds are
+ * "smeared" so that no leap second table is needed for interpretation. Range is
+ * from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z.
+ *
+ * @see
+ * https://github.com/google/protobuf/blob/master/src/google/protobuf/timestamp.proto
+ */
+class Timestamp {
+ public:
+ /**
+ * Creates a new timestamp representing the epoch (with seconds and
+ * nanoseconds set to 0).
+ */
+ Timestamp();
+
+ /**
+ * Creates a new timestamp.
+ *
+ * @param seconds The number of seconds of UTC time since Unix epoch
+ * 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to
+ * 9999-12-31T23:59:59Z inclusive; otherwise, assertion failure will be
+ * triggered.
+ * @param nanoseconds The non-negative fractions of a second at nanosecond
+ * resolution. Negative second values with fractions must still have
+ * non-negative nanoseconds values that count forward in time. Must be
+ * from 0 to 999,999,999 inclusive; otherwise, assertion failure will be
+ * triggered.
+ */
+ Timestamp(int64_t seconds, int32_t nanoseconds);
+
+ /**
+ * Creates a new timestamp with the current date.
+ *
+ * The precision is up to nanoseconds, depending on the system clock.
+ *
+ * @return a new timestamp representing the current date.
+ */
+ static Timestamp Now();
+
+ /**
+ * The number of seconds of UTC time since Unix epoch 1970-01-01T00:00:00Z.
+ */
+ int64_t seconds() const {
+ return seconds_;
+ }
+
+ /**
+ * The non-negative fractions of a second at nanosecond resolution. Negative
+ * second values with fractions still have non-negative nanoseconds values
+ * that count forward in time.
+ */
+ int32_t nanoseconds() const {
+ return nanoseconds_;
+ }
+
+ /**
+ * Converts `time_t` to a `Timestamp`.
+ *
+ * @param seconds_since_unix_epoch
+ * @parblock
+ * The number of seconds of UTC time since Unix epoch
+ * 1970-01-01T00:00:00Z. Can be negative to represent dates before the
+ * epoch. Must be from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59Z
+ * inclusive; otherwise, assertion failure will be triggered.
+ *
+ * Note that while the epoch of `time_t` is unspecified, it's usually Unix
+ * epoch. If this assumption is broken, this function will produce
+ * incorrect results.
+ * @endparblock
+ *
+ * @return a new timestamp with the given number of seconds and zero
+ * nanoseconds.
+ */
+ static Timestamp FromTimeT(time_t seconds_since_unix_epoch);
+
+#if !defined(_STLPORT_VERSION)
+ /**
+ * Converts `std::chrono::time_point` to a `Timestamp`.
+ *
+ * @param time_point
+ * @parblock
+ * The time point with system clock's epoch, which is
+ * presumed to be Unix epoch 1970-01-01T00:00:00Z. Can be negative to
+ * represent dates before the epoch. Must be from 0001-01-01T00:00:00Z to
+ * 9999-12-31T23:59:59Z inclusive; otherwise, assertion failure will be
+ * triggered.
+ *
+ * Note that while the epoch of `std::chrono::system_clock` is
+ * unspecified, it's usually Unix epoch. If this assumption is broken,
+ * this constructor will produce incorrect results.
+ * @endparblock
+ */
+ static Timestamp FromTimePoint(
+ std::chrono::time_point<std::chrono::system_clock> time_point);
+
+ /**
+ * Converts this `Timestamp` to a `time_point`.
+ *
+ * Important: if overflow would occur, the returned value will be the maximum
+ * or minimum value that `Duration` can hold. Note in particular that `long
+ * long` is insufficient to hold the full range of `Timestamp` values with
+ * nanosecond precision (which is why `Duration` defaults to `microseconds`).
+ */
+ template <typename Clock = std::chrono::system_clock,
+ typename Duration = std::chrono::microseconds>
+ std::chrono::time_point<Clock, Duration> ToTimePoint() const;
+#endif // !defined(_STLPORT_VERSION)
+
+ /**
+ * Returns a string representation of this `Timestamp` for logging/debugging
+ * purposes.
+ *
+ * Note: the exact string representation is unspecified and subject to change;
+ * don't rely on the format of the string.
+ */
+ std::string ToString() const;
+
+ private:
+ // Checks that the number of seconds is within the supported date range, and
+ // that nanoseconds satisfy 0 <= ns <= 1second.
+ void ValidateBounds() const;
+
+ int64_t seconds_ = 0;
+ int32_t nanoseconds_ = 0;
+};
+
+inline bool operator<(const Timestamp& lhs, const Timestamp& rhs) {
+ return lhs.seconds() < rhs.seconds() ||
+ (lhs.seconds() == rhs.seconds() &&
+ lhs.nanoseconds() < rhs.nanoseconds());
+}
+
+inline bool operator>(const Timestamp& lhs, const Timestamp& rhs) {
+ return rhs < lhs;
+}
+
+inline bool operator>=(const Timestamp& lhs, const Timestamp& rhs) {
+ return !(lhs < rhs);
+}
+
+inline bool operator<=(const Timestamp& lhs, const Timestamp& rhs) {
+ return !(lhs > rhs);
+}
+
+inline bool operator!=(const Timestamp& lhs, const Timestamp& rhs) {
+ return lhs < rhs || lhs > rhs;
+}
+
+inline bool operator==(const Timestamp& lhs, const Timestamp& rhs) {
+ return !(lhs != rhs);
+}
+
+#if !defined(_STLPORT_VERSION)
+template <typename Clock, typename Duration>
+std::chrono::time_point<Clock, Duration> Timestamp::ToTimePoint() const {
+ namespace chr = std::chrono;
+ using TimePoint = chr::time_point<Clock, Duration>;
+
+ // Saturate on overflow
+ const auto max_seconds = chr::duration_cast<chr::seconds>(Duration::max());
+ if (seconds_ > 0 && max_seconds.count() <= seconds_) {
+ return TimePoint{Duration::max()};
+ }
+ const auto min_seconds = chr::duration_cast<chr::seconds>(Duration::min());
+ if (seconds_ < 0 && min_seconds.count() >= seconds_) {
+ return TimePoint{Duration::min()};
+ }
+
+ const auto seconds = chr::duration_cast<Duration>(chr::seconds(seconds_));
+ const auto nanoseconds =
+ chr::duration_cast<Duration>(chr::nanoseconds(nanoseconds_));
+ return TimePoint{seconds + nanoseconds};
+}
+#endif // !defined(_STLPORT_VERSION)
+
+} // namespace firebase
+
+namespace std {
+template <>
+struct hash<firebase::Timestamp> {
+ // Note: specialization of `std::hash` is provided for convenience only. The
+ // implementation is subject to change.
+ size_t operator()(const firebase::Timestamp& timestamp) const;
+};
+} // namespace std
+
+#endif // FIRESTORE_CORE_INCLUDE_FIREBASE_FIRESTORE_TIMESTAMP_H_
diff --git a/Firestore/core/src/firebase/firestore/CMakeLists.txt b/Firestore/core/src/firebase/firestore/CMakeLists.txt
new file mode 100644
index 0000000..aad2ebb
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/CMakeLists.txt
@@ -0,0 +1,30 @@
+# Copyright 2018 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.
+
+# Public types to be used both internally and externally.
+cc_library(
+ firebase_firestore_types
+ SOURCES
+ geo_point.cc
+ timestamp.cc
+ DEPENDS
+ firebase_firestore_util
+)
+
+# Include the folder with public headers.
+target_include_directories(
+ firebase_firestore_types
+ PUBLIC
+ ${PROJECT_SOURCE_DIR}/core/include
+)
diff --git a/Firestore/core/src/firebase/firestore/auth/CMakeLists.txt b/Firestore/core/src/firebase/firestore/auth/CMakeLists.txt
new file mode 100644
index 0000000..2241fae
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/auth/CMakeLists.txt
@@ -0,0 +1,52 @@
+# Copyright 2018 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.
+
+cc_library(
+ firebase_firestore_auth_base
+ SOURCES
+ credentials_provider.cc
+ credentials_provider.h
+ token.cc
+ token.h
+ user.cc
+ user.h
+ DEPENDS
+ absl_strings
+ firebase_firestore_util
+)
+
+cc_library(
+ firebase_firestore_auth_apple
+ SOURCES
+ firebase_credentials_provider_apple.h
+ firebase_credentials_provider_apple.mm
+ DEPENDS
+ FirebaseCore
+ firebase_firestore_auth_base
+ EXCLUDE_FROM_ALL
+)
+
+if(APPLE)
+ list(APPEND AUTH_DEPENDS firebase_firestore_auth_apple)
+endif()
+
+cc_library(
+ firebase_firestore_auth
+ SOURCES
+ empty_credentials_provider.cc
+ empty_credentials_provider.h
+ DEPENDS
+ ${AUTH_DEPENDS}
+ firebase_firestore_auth_base
+)
diff --git a/Firestore/core/src/firebase/firestore/auth/credentials_provider.cc b/Firestore/core/src/firebase/firestore/auth/credentials_provider.cc
new file mode 100644
index 0000000..0301944
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/auth/credentials_provider.cc
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#include "Firestore/core/src/firebase/firestore/auth/credentials_provider.h"
+
+namespace firebase {
+namespace firestore {
+namespace auth {
+
+CredentialsProvider::CredentialsProvider() : user_change_listener_(nullptr) {
+}
+
+CredentialsProvider::~CredentialsProvider() {
+}
+
+} // namespace auth
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/src/firebase/firestore/auth/credentials_provider.h b/Firestore/core/src/firebase/firestore/auth/credentials_provider.h
new file mode 100644
index 0000000..0a1930a
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/auth/credentials_provider.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_AUTH_CREDENTIALS_PROVIDER_H_
+#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_AUTH_CREDENTIALS_PROVIDER_H_
+
+#include <functional>
+#include <string>
+
+#include "Firestore/core/include/firebase/firestore/firestore_errors.h"
+#include "Firestore/core/src/firebase/firestore/auth/token.h"
+#include "Firestore/core/src/firebase/firestore/auth/user.h"
+#include "Firestore/core/src/firebase/firestore/util/statusor.h"
+#include "absl/strings/string_view.h"
+
+namespace firebase {
+namespace firestore {
+namespace auth {
+
+// `TokenErrorListener` is a listener that gets a token or an error.
+using TokenListener = std::function<void(util::StatusOr<Token>)>;
+
+// Listener notified with a User change.
+using UserChangeListener = std::function<void(User user)>;
+
+/**
+ * Provides methods for getting the uid and token for the current user and
+ * listen for changes.
+ */
+class CredentialsProvider {
+ public:
+ CredentialsProvider();
+
+ virtual ~CredentialsProvider();
+
+ /**
+ * Requests token for the current user, optionally forcing a refreshed token
+ * to be fetched.
+ */
+ virtual void GetToken(bool force_refresh, TokenListener completion) = 0;
+
+ /**
+ * Sets the listener to be notified of user changes (sign-in / sign-out). It
+ * is immediately called once with the initial user.
+ *
+ * Call with nullptr to remove previous listener.
+ */
+ virtual void SetUserChangeListener(UserChangeListener listener) = 0;
+
+ protected:
+ /**
+ * A listener to be notified of user changes (sign-in / sign-out). It is
+ * immediately called once with the initial user.
+ *
+ * Note that this block will be called back on an arbitrary thread that is not
+ * the normal Firestore worker thread.
+ */
+ UserChangeListener user_change_listener_;
+};
+
+} // namespace auth
+} // namespace firestore
+} // namespace firebase
+
+#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_AUTH_CREDENTIALS_PROVIDER_H_
diff --git a/Firestore/core/src/firebase/firestore/auth/empty_credentials_provider.cc b/Firestore/core/src/firebase/firestore/auth/empty_credentials_provider.cc
new file mode 100644
index 0000000..da4198d
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/auth/empty_credentials_provider.cc
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#define UNUSED(x) (void)(x)
+
+#include "Firestore/core/src/firebase/firestore/auth/empty_credentials_provider.h"
+
+namespace firebase {
+namespace firestore {
+namespace auth {
+
+void EmptyCredentialsProvider::GetToken(bool force_refresh,
+ TokenListener completion) {
+ UNUSED(force_refresh);
+ if (completion) {
+ // Unauthenticated token will force the GRPC fallback to use default
+ // settings.
+ completion(Token::Unauthenticated());
+ }
+}
+
+void EmptyCredentialsProvider::SetUserChangeListener(
+ UserChangeListener listener) {
+ if (listener) {
+ listener(User::Unauthenticated());
+ }
+}
+
+} // namespace auth
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/src/firebase/firestore/auth/empty_credentials_provider.h b/Firestore/core/src/firebase/firestore/auth/empty_credentials_provider.h
new file mode 100644
index 0000000..55b3cc6
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/auth/empty_credentials_provider.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_AUTH_EMPTY_CREDENTIALS_PROVIDER_H_
+#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_AUTH_EMPTY_CREDENTIALS_PROVIDER_H_
+
+#include "Firestore/core/src/firebase/firestore/auth/credentials_provider.h"
+
+namespace firebase {
+namespace firestore {
+namespace auth {
+
+/** `EmptyCredentialsProvider` always yields an empty token. */
+class EmptyCredentialsProvider : public CredentialsProvider {
+ public:
+ void GetToken(bool force_refresh, TokenListener completion) override;
+ void SetUserChangeListener(UserChangeListener listener) override;
+};
+
+} // namespace auth
+} // namespace firestore
+} // namespace firebase
+
+#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_AUTH_EMPTY_CREDENTIALS_PROVIDER_H_
diff --git a/Firestore/core/src/firebase/firestore/auth/firebase_credentials_provider_apple.h b/Firestore/core/src/firebase/firestore/auth/firebase_credentials_provider_apple.h
new file mode 100644
index 0000000..0e1da31
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/auth/firebase_credentials_provider_apple.h
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2018 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.
+ */
+
+// Right now, FirebaseCredentialsProvider only support APPLE build.
+#if !defined(__OBJC__)
+#error "This header only supports Objective-C++."
+#endif // !defined(__OBJC__)
+
+#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_AUTH_FIREBASE_CREDENTIALS_PROVIDER_APPLE_H_
+#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_AUTH_FIREBASE_CREDENTIALS_PROVIDER_APPLE_H_
+
+#import <Foundation/Foundation.h>
+
+#include <memory>
+#include <mutex> // NOLINT(build/c++11)
+#include <utility>
+
+#include "Firestore/core/src/firebase/firestore/auth/credentials_provider.h"
+#include "Firestore/core/src/firebase/firestore/auth/user.h"
+#include "absl/strings/string_view.h"
+
+@class FIRApp;
+
+namespace firebase {
+namespace firestore {
+namespace auth {
+
+/**
+ * `FirebaseCredentialsProvider` uses Firebase Auth via `FIRApp` to get an auth
+ * token.
+ *
+ * NOTE: To simplify the implementation, it requires that you set
+ * `userChangeListener` with a non-`nil` value no more than once and don't call
+ * `getTokenForcingRefresh:` after setting it to `nil`.
+ *
+ * This class must be implemented in a thread-safe manner since it is accessed
+ * from the thread backing our internal worker queue and the callbacks from
+ * FIRAuth will be executed on an arbitrary different thread.
+ *
+ * For non-Apple desktop build, this is right now just a stub.
+ */
+class FirebaseCredentialsProvider : public CredentialsProvider {
+ public:
+ // TODO(zxu123): Provide a ctor to accept the C++ Firebase Games App, which
+ // deals all platforms. Right now, only works for FIRApp*.
+ /**
+ * Initializes a new FirebaseCredentialsProvider.
+ *
+ * @param app The Firebase app from which to get credentials.
+ */
+ explicit FirebaseCredentialsProvider(FIRApp* app);
+
+ ~FirebaseCredentialsProvider() override;
+
+ void GetToken(bool force_refresh, TokenListener completion) override;
+
+ void SetUserChangeListener(UserChangeListener listener) override;
+
+ private:
+ /**
+ * Most contents of the FirebaseCredentialProvider are kept in this
+ * Contents object and pointed to with a shared pointer. Callbacks
+ * registered with FirebaseAuth use weak pointers to the Contents to
+ * avoid races between notifications arriving and C++ object destruction.
+ */
+ struct Contents {
+ Contents(FIRApp* app, User&& user)
+ : app(app), current_user(std::move(user)) {
+ }
+
+ const FIRApp* app;
+
+ /**
+ * The current user as reported to us via our AuthStateDidChangeListener.
+ */
+ User current_user;
+
+ /**
+ * Counter used to detect if the user changed while a
+ * -getTokenForcingRefresh: request was outstanding.
+ */
+ int user_counter = 0;
+
+ std::mutex mutex;
+ };
+
+ /**
+ * Handle used to stop receiving auth changes once userChangeListener is
+ * removed.
+ */
+ id<NSObject> auth_listener_handle_;
+
+ std::shared_ptr<Contents> contents_;
+};
+
+} // namespace auth
+} // namespace firestore
+} // namespace firebase
+
+#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_AUTH_FIREBASE_CREDENTIALS_PROVIDER_APPLE_H_
diff --git a/Firestore/core/src/firebase/firestore/auth/firebase_credentials_provider_apple.mm b/Firestore/core/src/firebase/firestore/auth/firebase_credentials_provider_apple.mm
new file mode 100644
index 0000000..ff2d5f7
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/auth/firebase_credentials_provider_apple.mm
@@ -0,0 +1,149 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#include "Firestore/core/src/firebase/firestore/auth/firebase_credentials_provider_apple.h"
+
+#import <FirebaseCore/FIRApp.h>
+#import <FirebaseCore/FIRAppInternal.h>
+#import <FirebaseCore/FIROptionsInternal.h>
+
+#include "Firestore/core/src/firebase/firestore/util/firebase_assert.h"
+#include "Firestore/core/src/firebase/firestore/util/string_apple.h"
+
+// NB: This is also defined in Firestore/Source/Public/FIRFirestoreErrors.h
+// NOLINTNEXTLINE: public constant
+NSString* const FIRFirestoreErrorDomain = @"FIRFirestoreErrorDomain";
+
+namespace firebase {
+namespace firestore {
+namespace auth {
+
+FirebaseCredentialsProvider::FirebaseCredentialsProvider(FIRApp* app)
+ : contents_(std::make_shared<Contents>(app, User::FromUid([app getUID]))) {
+ std::weak_ptr<Contents> weak_contents = contents_;
+
+ auth_listener_handle_ = [[NSNotificationCenter defaultCenter]
+ addObserverForName:FIRAuthStateDidChangeInternalNotification
+ object:nil
+ queue:nil
+ usingBlock:^(NSNotification* notification) {
+ std::shared_ptr<Contents> contents = weak_contents.lock();
+ if (!contents) {
+ return;
+ }
+
+ std::unique_lock<std::mutex> lock(contents->mutex);
+ NSDictionary<NSString*, id>* user_info = notification.userInfo;
+
+ // ensure we're only notifying for the current app.
+ FIRApp* notified_app =
+ user_info[FIRAuthStateDidChangeInternalNotificationAppKey];
+ if (![contents->app isEqual:notified_app]) {
+ return;
+ }
+
+ NSString* user_id =
+ user_info[FIRAuthStateDidChangeInternalNotificationUIDKey];
+ User new_user = User::FromUid(user_id);
+ if (new_user != contents->current_user) {
+ contents->current_user = new_user;
+ contents->user_counter++;
+ UserChangeListener listener = user_change_listener_;
+ if (listener) {
+ listener(contents->current_user);
+ }
+ }
+ }];
+}
+
+FirebaseCredentialsProvider::~FirebaseCredentialsProvider() {
+ if (auth_listener_handle_) {
+ // Even though iOS 9 (and later) and macOS 10.11 (and later) keep a weak
+ // reference to the observer so we could avoid this removeObserver call, we
+ // still support iOS 8 which requires it.
+ [[NSNotificationCenter defaultCenter] removeObserver:auth_listener_handle_];
+ }
+}
+
+void FirebaseCredentialsProvider::GetToken(bool force_refresh,
+ TokenListener completion) {
+ FIREBASE_ASSERT_MESSAGE(auth_listener_handle_,
+ "GetToken cannot be called after listener removed.");
+
+ // Take note of the current value of the userCounter so that this method can
+ // fail if there is a user change while the request is outstanding.
+ int initial_user_counter = contents_->user_counter;
+
+ std::weak_ptr<Contents> weak_contents = contents_;
+ void (^get_token_callback)(NSString*, NSError*) =
+ ^(NSString* _Nullable token, NSError* _Nullable error) {
+ std::shared_ptr<Contents> contents = weak_contents.lock();
+ if (!contents) {
+ return;
+ }
+
+ std::unique_lock<std::mutex> lock(contents->mutex);
+ if (initial_user_counter != contents->user_counter) {
+ // Cancel the request since the user changed while the request was
+ // outstanding so the response is likely for a previous user (which
+ // user, we can't be sure).
+ completion(util::Status(FirestoreErrorCode::Aborted,
+ "getToken aborted due to user change."));
+ } else {
+ if (error == nil) {
+ if (token != nil) {
+ completion(
+ Token{util::MakeStringView(token), contents->current_user});
+ } else {
+ completion(Token::Unauthenticated());
+ }
+ } else {
+ FirestoreErrorCode error_code = FirestoreErrorCode::Unknown;
+ if (error.domain == FIRFirestoreErrorDomain) {
+ error_code = static_cast<FirestoreErrorCode>(error.code);
+ }
+ completion(util::Status(
+ error_code, util::MakeStringView(error.localizedDescription)));
+ }
+ }
+ };
+
+ [contents_->app getTokenForcingRefresh:force_refresh
+ withCallback:get_token_callback];
+}
+
+void FirebaseCredentialsProvider::SetUserChangeListener(
+ UserChangeListener listener) {
+ std::unique_lock<std::mutex> lock(contents_->mutex);
+ if (listener) {
+ FIREBASE_ASSERT_MESSAGE(!user_change_listener_,
+ "set user_change_listener twice!");
+ // Fire initial event.
+ listener(contents_->current_user);
+ } else {
+ FIREBASE_ASSERT_MESSAGE(auth_listener_handle_,
+ "removed user_change_listener twice!");
+ FIREBASE_ASSERT_MESSAGE(user_change_listener_,
+ "user_change_listener removed without being set!");
+ [[NSNotificationCenter defaultCenter] removeObserver:auth_listener_handle_];
+ auth_listener_handle_ = nil;
+ }
+ user_change_listener_ = listener;
+}
+
+} // namespace auth
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/src/firebase/firestore/auth/token.cc b/Firestore/core/src/firebase/firestore/auth/token.cc
new file mode 100644
index 0000000..6fe5fc4
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/auth/token.cc
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#include "Firestore/core/src/firebase/firestore/auth/token.h"
+
+namespace firebase {
+namespace firestore {
+namespace auth {
+
+Token::Token(const absl::string_view token, const User& user)
+ : token_(token), user_(user) {
+}
+
+const Token& Token::Unauthenticated() {
+ static const Token kUnauthenticatedToken(absl::string_view(),
+ User::Unauthenticated());
+ return kUnauthenticatedToken;
+}
+
+} // namespace auth
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/src/firebase/firestore/auth/token.h b/Firestore/core/src/firebase/firestore/auth/token.h
new file mode 100644
index 0000000..56084f8
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/auth/token.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_AUTH_TOKEN_H_
+#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_AUTH_TOKEN_H_
+
+#include <string>
+
+#include "Firestore/core/src/firebase/firestore/auth/user.h"
+#include "Firestore/core/src/firebase/firestore/util/firebase_assert.h"
+#include "absl/strings/string_view.h"
+
+namespace firebase {
+namespace firestore {
+namespace auth {
+
+/**
+ * The current User and the authentication token provided by the underlying
+ * authentication mechanism. This is the result of calling
+ * CredentialsProvider::GetToken().
+ *
+ * ## Portability notes: no TokenType on iOS
+ *
+ * The TypeScript client supports 1st party Oauth tokens (for the Firebase
+ * Console to auth as the developer) and OAuth2 tokens for the node.js sdk to
+ * auth with a service account. We don't have plans to support either case on
+ * mobile so there's no TokenType here.
+ */
+// TODO(zxu123): Make this support token-type for desktop workflow.
+class Token {
+ public:
+ Token(absl::string_view token, const User& user);
+
+ /** The actual raw token. */
+ const std::string& token() const {
+ FIREBASE_ASSERT(user_.is_authenticated());
+ return token_;
+ }
+
+ /**
+ * The user with which the token is associated (used for persisting user
+ * state on disk, etc.).
+ */
+ const User& user() const {
+ return user_;
+ }
+
+ /**
+ * Returns a token for an unauthenticated user.
+ *
+ * ## Portability notes: An unauthenticated token is the equivalent of
+ * nil/null in the iOS/TypeScript token implementation. We use a reference
+ * instead of a pointer for Token instances in the C++ migration.
+ */
+ static const Token& Unauthenticated();
+
+ private:
+ const std::string token_;
+ const User user_;
+};
+
+} // namespace auth
+} // namespace firestore
+} // namespace firebase
+
+#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_AUTH_TOKEN_H_
diff --git a/Firestore/core/src/firebase/firestore/auth/user.cc b/Firestore/core/src/firebase/firestore/auth/user.cc
new file mode 100644
index 0000000..4793fed
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/auth/user.cc
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#include "Firestore/core/src/firebase/firestore/auth/user.h"
+
+#include "Firestore/core/src/firebase/firestore/util/firebase_assert.h"
+
+namespace firebase {
+namespace firestore {
+namespace auth {
+
+User::User() : is_authenticated_(false) {
+}
+
+User::User(const absl::string_view uid) : uid_(uid), is_authenticated_(true) {
+ FIREBASE_ASSERT(!uid.empty());
+}
+
+const User& User::Unauthenticated() {
+ static const User kUnauthenticated;
+ return kUnauthenticated;
+}
+
+} // namespace auth
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/src/firebase/firestore/auth/user.h b/Firestore/core/src/firebase/firestore/auth/user.h
new file mode 100644
index 0000000..cc030cc
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/auth/user.h
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_AUTH_USER_H_
+#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_AUTH_USER_H_
+
+#if defined(__OBJC__)
+#import <Foundation/Foundation.h>
+#include "Firestore/core/src/firebase/firestore/util/string_apple.h"
+#endif // defined(__OBJC__)
+
+#include <string>
+
+#include "absl/strings/string_view.h"
+
+namespace firebase {
+namespace firestore {
+namespace auth {
+
+/**
+ * Simple wrapper around a nullable UID. Mostly exists to make code more
+ * readable and for compatibility with other clients where map keys cannot be
+ * null.
+ */
+class User {
+ public:
+ /** Construct an unauthenticated user. */
+ User();
+
+ /** Construct an authenticated user with the given UID. */
+ explicit User(absl::string_view uid);
+
+ const std::string& uid() const {
+ return uid_;
+ }
+
+ // PORTING NOTE: Here use more clear naming is_authenticated() instead of
+ // is_unauthenticated().
+ bool is_authenticated() const {
+ return is_authenticated_;
+ }
+
+ /** Returns an unauthenticated instance. */
+ static const User& Unauthenticated();
+
+#if defined(__OBJC__)
+ /**
+ * Returns an authenticated user if uid is non-nil, otherwise an
+ * unauthenticated user.
+ */
+ static User FromUid(NSString* _Nullable uid) {
+ if (uid == nil) {
+ return Unauthenticated();
+ } else {
+ return User(util::MakeStringView(uid));
+ }
+ }
+#endif // defined(__OBJC__)
+
+ User& operator=(const User& other) = default;
+
+ friend bool operator==(const User& lhs, const User& rhs);
+
+ private:
+ std::string uid_;
+ bool is_authenticated_;
+};
+
+inline bool operator==(const User& lhs, const User& rhs) {
+ return lhs.is_authenticated_ == rhs.is_authenticated_ &&
+ (!lhs.is_authenticated_ || lhs.uid_ == rhs.uid_);
+}
+
+inline bool operator!=(const User& lhs, const User& rhs) {
+ return !(lhs == rhs);
+}
+
+// Specializations of std::hash is prohibited. We define a hash function to be
+// passed through manually.
+struct HashUser {
+ inline int64_t operator()(const User& user) const {
+ return std::hash<std::string>{}(user.uid());
+ }
+};
+
+} // namespace auth
+} // namespace firestore
+} // namespace firebase
+
+#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_AUTH_USER_H_
diff --git a/Firestore/core/src/firebase/firestore/core/CMakeLists.txt b/Firestore/core/src/firebase/firestore/core/CMakeLists.txt
new file mode 100644
index 0000000..cf3cafe
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/core/CMakeLists.txt
@@ -0,0 +1,25 @@
+# Copyright 2018 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.
+
+cc_library(
+ firebase_firestore_core
+ SOURCES
+ database_info.cc
+ database_info.h
+ target_id_generator.cc
+ target_id_generator.h
+ DEPENDS
+ absl_strings
+ firebase_firestore_model
+)
diff --git a/Firestore/core/src/firebase/firestore/core/database_info.cc b/Firestore/core/src/firebase/firestore/core/database_info.cc
new file mode 100644
index 0000000..b83da37
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/core/database_info.cc
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#include "Firestore/core/src/firebase/firestore/core/database_info.h"
+
+namespace firebase {
+namespace firestore {
+namespace core {
+
+DatabaseInfo::DatabaseInfo(
+ const firebase::firestore::model::DatabaseId& database_id,
+ const absl::string_view persistence_key,
+ const absl::string_view host,
+ bool ssl_enabled)
+ : database_id_(database_id),
+ persistence_key_(persistence_key),
+ host_(host),
+ ssl_enabled_(ssl_enabled) {
+}
+
+} // namespace core
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/src/firebase/firestore/core/database_info.h b/Firestore/core/src/firebase/firestore/core/database_info.h
new file mode 100644
index 0000000..0f97b1f
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/core/database_info.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_CORE_DATABASE_INFO_H_
+#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_CORE_DATABASE_INFO_H_
+
+#include <string>
+
+#include "Firestore/core/src/firebase/firestore/model/database_id.h"
+#include "absl/strings/string_view.h"
+
+namespace firebase {
+namespace firestore {
+namespace core {
+
+/** DatabaseInfo contains data about the database. */
+class DatabaseInfo {
+ public:
+#if defined(__OBJC__)
+ // For objective-c++ initialization; to be removed after migration.
+ // Do NOT use in C++ code.
+ DatabaseInfo() = default;
+#endif // defined(__OBJC__)
+
+ /**
+ * Creates a new DatabaseInfo.
+ *
+ * @param database_id The project/database to use.
+ * @param persistence_key A unique identifier for this Firestore's local
+ * storage. Usually derived from -[FIRApp appName].
+ * @param host The hostname of the Firestore backend.
+ * @param ssl_enabled Whether to use SSL when connecting.
+ */
+ DatabaseInfo(const firebase::firestore::model::DatabaseId& database_id,
+ absl::string_view persistence_key,
+ absl::string_view host,
+ bool ssl_enabled);
+
+ const firebase::firestore::model::DatabaseId& database_id() const {
+ return database_id_;
+ }
+
+ const std::string& persistence_key() const {
+ return persistence_key_;
+ }
+
+ const std::string& host() const {
+ return host_;
+ }
+
+ bool ssl_enabled() const {
+ return ssl_enabled_;
+ }
+
+ private:
+ firebase::firestore::model::DatabaseId database_id_;
+ std::string persistence_key_;
+ std::string host_;
+ bool ssl_enabled_;
+};
+
+} // namespace core
+} // namespace firestore
+} // namespace firebase
+
+#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_CORE_DATABASE_INFO_H_
diff --git a/Firestore/core/src/firebase/firestore/core/target_id_generator.cc b/Firestore/core/src/firebase/firestore/core/target_id_generator.cc
new file mode 100644
index 0000000..473d083
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/core/target_id_generator.cc
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#include "Firestore/core/src/firebase/firestore/core/target_id_generator.h"
+
+using firebase::firestore::model::TargetId;
+
+namespace firebase {
+namespace firestore {
+namespace core {
+
+TargetIdGenerator::TargetIdGenerator(const TargetIdGenerator& value)
+ : generator_id_(value.generator_id_), previous_id_(value.previous_id_) {
+}
+
+TargetIdGenerator::TargetIdGenerator(TargetIdGeneratorId generator_id,
+ TargetId after)
+ : generator_id_(generator_id) {
+ const TargetId after_without_generator = (after >> kReservedBits)
+ << kReservedBits;
+ const TargetId after_generator = after - after_without_generator;
+ const TargetId generator = static_cast<TargetId>(generator_id);
+ if (after_generator >= generator) {
+ // For example, if:
+ // self.generatorID = 0b0000
+ // after = 0b1011
+ // afterGenerator = 0b0001
+ // Then:
+ // previous = 0b1010
+ // next = 0b1100
+ previous_id_ = after_without_generator | generator;
+ } else {
+ // For example, if:
+ // self.generatorID = 0b0001
+ // after = 0b1010
+ // afterGenerator = 0b0000
+ // Then:
+ // previous = 0b1001
+ // next = 0b1011
+ previous_id_ = (after_without_generator | generator) - (1 << kReservedBits);
+ }
+}
+
+TargetId TargetIdGenerator::NextId() {
+ previous_id_ += 1 << kReservedBits;
+ return previous_id_;
+}
+
+} // namespace core
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/src/firebase/firestore/core/target_id_generator.h b/Firestore/core/src/firebase/firestore/core/target_id_generator.h
new file mode 100644
index 0000000..870e731
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/core/target_id_generator.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_CORE_TARGET_ID_GENERATOR_H_
+#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_CORE_TARGET_ID_GENERATOR_H_
+
+#include "Firestore/core/src/firebase/firestore/model/types.h"
+
+namespace firebase {
+namespace firestore {
+namespace core {
+
+/** The set of all valid generators. */
+enum class TargetIdGeneratorId { LocalStore = 0, SyncEngine = 1 };
+
+/**
+ * Generates monotonically increasing integer IDs. There are separate generators
+ * for different scopes. While these generators will operate independently of
+ * each other, they are scoped, such that no two generators will ever produce
+ * the same ID. This is useful, because sometimes the backend may group IDs from
+ * separate parts of the client into the same ID space.
+ *
+ * Not thread-safe.
+ */
+class TargetIdGenerator {
+ public:
+ // Makes Objective-C++ code happy to provide a default ctor.
+ TargetIdGenerator() = default;
+
+ TargetIdGenerator(const TargetIdGenerator& value);
+
+ /**
+ * Creates and returns the TargetIdGenerator for the local store.
+ *
+ * @param after An ID to start at. Every call to NextId returns a larger id.
+ * @return An instance of TargetIdGenerator.
+ */
+ static TargetIdGenerator LocalStoreTargetIdGenerator(model::TargetId after) {
+ return TargetIdGenerator(TargetIdGeneratorId::LocalStore, after);
+ }
+
+ /**
+ * Creates and returns the TargetIdGenerator for the sync engine.
+ *
+ * @param after An ID to start at. Every call to NextId returns a larger id.
+ * @return An instance of TargetIdGenerator.
+ */
+ static TargetIdGenerator SyncEngineTargetIdGenerator(model::TargetId after) {
+ return TargetIdGenerator(TargetIdGeneratorId::SyncEngine, after);
+ }
+
+ TargetIdGeneratorId generator_id() {
+ return generator_id_;
+ }
+
+ model::TargetId NextId();
+
+ private:
+ TargetIdGenerator(TargetIdGeneratorId generator_id, model::TargetId after);
+ TargetIdGeneratorId generator_id_;
+ model::TargetId previous_id_;
+
+ static const int kReservedBits = 1;
+};
+
+} // namespace core
+} // namespace firestore
+} // namespace firebase
+
+#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_CORE_TARGET_ID_GENERATOR_H_
diff --git a/Firestore/core/src/firebase/firestore/geo_point.cc b/Firestore/core/src/firebase/firestore/geo_point.cc
new file mode 100644
index 0000000..1ed5126
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/geo_point.cc
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#include "Firestore/core/include/firebase/firestore/geo_point.h"
+
+#include <cmath>
+
+#include "Firestore/core/src/firebase/firestore/util/firebase_assert.h"
+
+using std::isnan;
+
+namespace firebase {
+namespace firestore {
+
+GeoPoint::GeoPoint() : GeoPoint(0, 0) {
+}
+
+GeoPoint::GeoPoint(double latitude, double longitude)
+ : latitude_(latitude), longitude_(longitude) {
+ FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION(
+ !isnan(latitude) && -90 <= latitude && latitude <= 90,
+ -90 <= latitude && latitude <= 90,
+ "Latitude must be in the range of [-90, 90]");
+ FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION(
+ !isnan(longitude) && -180 <= longitude && longitude <= 180,
+ -180 <= longitude && longitude <= 180,
+ "Latitude must be in the range of [-180, 180]");
+}
+
+bool operator<(const GeoPoint& lhs, const GeoPoint& rhs) {
+ if (lhs.latitude() == rhs.latitude()) {
+ return lhs.longitude() < rhs.longitude();
+ } else {
+ return lhs.latitude() < rhs.latitude();
+ }
+}
+
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/src/firebase/firestore/immutable/CMakeLists.txt b/Firestore/core/src/firebase/firestore/immutable/CMakeLists.txt
new file mode 100644
index 0000000..543f912
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/immutable/CMakeLists.txt
@@ -0,0 +1,27 @@
+# Copyright 2018 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.
+
+cc_library(
+ firebase_firestore_immutable
+ SOURCES
+ array_sorted_map.h
+ llrb_node.h
+ map_entry.h
+ sorted_map.h
+ sorted_map_base.h
+ sorted_map_base.cc
+ tree_sorted_map.h
+ DEPENDS
+ firebase_firestore_util
+)
diff --git a/Firestore/core/src/firebase/firestore/immutable/array_sorted_map.h b/Firestore/core/src/firebase/firestore/immutable/array_sorted_map.h
new file mode 100644
index 0000000..92fd823
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/immutable/array_sorted_map.h
@@ -0,0 +1,286 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_IMMUTABLE_ARRAY_SORTED_MAP_H_
+#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_IMMUTABLE_ARRAY_SORTED_MAP_H_
+
+#include <algorithm>
+#include <array>
+#include <cassert>
+#include <functional>
+#include <memory>
+#include <utility>
+
+#include "Firestore/core/src/firebase/firestore/immutable/map_entry.h"
+#include "Firestore/core/src/firebase/firestore/immutable/sorted_map_base.h"
+#include "Firestore/core/src/firebase/firestore/util/firebase_assert.h"
+
+namespace firebase {
+namespace firestore {
+namespace immutable {
+namespace impl {
+
+/**
+ * A bounded-size array that allocates its contents directly in itself. This
+ * saves a heap allocation when compared with std::vector (though std::vector
+ * can resize itself while FixedArray cannot).
+ *
+ * Unlike std::array, FixedArray keeps track of its size and grows up to the
+ * fixed_size limit. Inserting more elements than fixed_size will trigger an
+ * assertion failure.
+ *
+ * ArraySortedMap does not actually contain its array: it contains a shared_ptr
+ * to a FixedArray.
+ *
+ * @tparam T The type of an element in the array.
+ * @tparam fixed_size the fixed size to use in creating the FixedArray.
+ */
+template <typename T, SortedMapBase::size_type fixed_size>
+class FixedArray {
+ public:
+ using size_type = SortedMapBase::size_type;
+ using array_type = std::array<T, fixed_size>;
+ using iterator = typename array_type::iterator;
+ using const_iterator = typename array_type::const_iterator;
+
+ FixedArray() {
+ }
+
+ template <typename SourceIterator>
+ FixedArray(SourceIterator src_begin, SourceIterator src_end) {
+ append(src_begin, src_end);
+ }
+
+ /**
+ * Appends to this array, copying from the given src_begin up to but not
+ * including the src_end.
+ */
+ template <typename SourceIterator>
+ void append(SourceIterator src_begin, SourceIterator src_end) {
+ auto appending = static_cast<size_type>(src_end - src_begin);
+ auto new_size = size_ + appending;
+ FIREBASE_ASSERT(new_size <= fixed_size);
+
+ std::copy(src_begin, src_end, end());
+ size_ = new_size;
+ }
+
+ /**
+ * Appends a single value to the array.
+ */
+ void append(T&& value) {
+ size_type new_size = size_ + 1;
+ FIREBASE_ASSERT(new_size <= fixed_size);
+
+ *end() = std::move(value);
+ size_ = new_size;
+ }
+
+ const_iterator begin() const {
+ return contents_.begin();
+ }
+
+ const_iterator end() const {
+ return begin() + size_;
+ }
+
+ size_type size() const {
+ return size_;
+ }
+
+ private:
+ iterator begin() {
+ return contents_.begin();
+ }
+
+ iterator end() {
+ return begin() + size_;
+ }
+
+ array_type contents_;
+ size_type size_ = 0;
+};
+
+/**
+ * ArraySortedMap is a value type containing a map. It is immutable, but has
+ * methods to efficiently create new maps that are mutations of it.
+ */
+template <typename K, typename V, typename C = std::less<K>>
+class ArraySortedMap : public SortedMapBase {
+ public:
+ using key_comparator_type = KeyComparator<K, V, C>;
+
+ /**
+ * The type of the entries stored in the map.
+ */
+ using value_type = std::pair<K, V>;
+
+ /**
+ * The type of the fixed-size array containing entries of value_type.
+ */
+ using array_type = FixedArray<value_type, kFixedSize>;
+ using const_iterator = typename array_type::const_iterator;
+
+ using array_pointer = std::shared_ptr<const array_type>;
+
+ /**
+ * Creates an empty ArraySortedMap.
+ */
+ explicit ArraySortedMap(const C& comparator = C())
+ : array_{EmptyArray()}, key_comparator_{comparator} {
+ }
+
+ /**
+ * Creates an ArraySortedMap containing the given entries.
+ */
+ ArraySortedMap(std::initializer_list<value_type> entries,
+ const C& comparator = C())
+ : array_{std::make_shared<array_type>(entries.begin(), entries.end())},
+ key_comparator_{comparator} {
+ }
+
+ /**
+ * Creates a new map identical to this one, but with a key-value pair added or
+ * updated.
+ *
+ * @param key The key to insert/update.
+ * @param value The value to associate with the key.
+ * @return A new dictionary with the added/updated value.
+ */
+ ArraySortedMap insert(const K& key, const V& value) const {
+ const_iterator current_end = end();
+ const_iterator pos = LowerBound(key);
+ bool replacing_entry = false;
+
+ if (pos != current_end) {
+ // LowerBound found an entry where pos->first >= pair.first. Reversing the
+ // argument order here tests pair.first < pos->first.
+ replacing_entry = !key_comparator_(key, *pos);
+ if (replacing_entry && value == pos->second) {
+ return *this;
+ }
+ }
+
+ // Copy the segment before the found position. If not found, this is
+ // everything.
+ auto copy = std::make_shared<array_type>(begin(), pos);
+
+ // Copy the value to be inserted.
+ copy->append({key, value});
+
+ if (replacing_entry) {
+ // Skip the thing at pos because it compares the same as the pair above.
+ copy->append(pos + 1, current_end);
+ } else {
+ copy->append(pos, current_end);
+ }
+ return wrap(copy);
+ }
+
+ /**
+ * Creates a new map identical to this one, but with a key removed from it.
+ *
+ * @param key The key to remove.
+ * @return A new dictionary without that value.
+ */
+ ArraySortedMap erase(const K& key) const {
+ const_iterator current_end = end();
+ const_iterator pos = find(key);
+ if (pos == current_end) {
+ return *this;
+ } else if (size() <= 1) {
+ // If the key was found and it's the last entry, removing it would make
+ // the result empty.
+ return wrap(EmptyArray());
+ } else {
+ auto copy = std::make_shared<array_type>(begin(), pos);
+ copy->append(pos + 1, current_end);
+ return wrap(copy);
+ }
+ }
+
+ /**
+ * Finds a value in the map.
+ *
+ * @param key The key to look up.
+ * @return An iterator pointing to the entry containing the key, or end() if
+ * not found.
+ */
+ const_iterator find(const K& key) const {
+ const_iterator not_found = end();
+ const_iterator lower_bound = LowerBound(key);
+ if (lower_bound != not_found && !key_comparator_(key, *lower_bound)) {
+ return lower_bound;
+ } else {
+ return not_found;
+ }
+ }
+
+ /** Returns true if the map contains no elements. */
+ bool empty() const {
+ return size() == 0;
+ }
+
+ /** Returns the number of items in this map. */
+ size_type size() const {
+ return array_->size();
+ }
+
+ /**
+ * Returns an iterator pointing to the first entry in the map. If there are
+ * no entries in the map, begin() == end().
+ */
+ const_iterator begin() const {
+ return array_->begin();
+ }
+
+ /**
+ * Returns an iterator pointing past the last entry in the map.
+ */
+ const_iterator end() const {
+ return array_->end();
+ }
+
+ private:
+ static array_pointer EmptyArray() {
+ static const array_pointer kEmptyArray =
+ std::make_shared<const array_type>();
+ return kEmptyArray;
+ }
+
+ ArraySortedMap(const array_pointer& array,
+ const key_comparator_type& key_comparator) noexcept
+ : array_{array}, key_comparator_{key_comparator} {
+ }
+
+ ArraySortedMap wrap(const array_pointer& array) const noexcept {
+ return ArraySortedMap{array, key_comparator_};
+ }
+
+ const_iterator LowerBound(const K& key) const {
+ return std::lower_bound(begin(), end(), key, key_comparator_);
+ }
+
+ array_pointer array_;
+ key_comparator_type key_comparator_;
+};
+
+} // namespace impl
+} // namespace immutable
+} // namespace firestore
+} // namespace firebase
+
+#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_IMMUTABLE_ARRAY_SORTED_MAP_H_
diff --git a/Firestore/core/src/firebase/firestore/immutable/llrb_node.h b/Firestore/core/src/firebase/firestore/immutable/llrb_node.h
new file mode 100644
index 0000000..89fd8cf
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/immutable/llrb_node.h
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_IMMUTABLE_LLRB_NODE_H_
+#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_IMMUTABLE_LLRB_NODE_H_
+
+#include <memory>
+#include <utility>
+
+#include "Firestore/core/src/firebase/firestore/immutable/sorted_map_base.h"
+
+namespace firebase {
+namespace firestore {
+namespace immutable {
+namespace impl {
+
+/**
+ * A Color of a tree node in a red-black tree.
+ */
+enum Color : unsigned int {
+ Black,
+ Red,
+};
+
+/**
+ * LlrbNode is a node in a TreeSortedMap.
+ */
+template <typename K, typename V>
+class LlrbNode : public SortedMapBase {
+ public:
+ using first_type = K;
+ using second_type = V;
+
+ /**
+ * The type of the entries stored in the map.
+ */
+ using value_type = std::pair<K, V>;
+
+ /**
+ * Constructs an empty node.
+ */
+ // TODO(wilhuff): move this into NodeData if that structure is to live on.
+ LlrbNode()
+ : LlrbNode{NodeData{{},
+ Color::Black,
+ /*size=*/0u,
+ LlrbNode{nullptr},
+ LlrbNode{nullptr}}} {
+ }
+
+ /**
+ * Returns a shared Empty node, to cut down on allocations in the base case.
+ */
+ static const LlrbNode& Empty() {
+ static const LlrbNode empty_node{};
+ return empty_node;
+ }
+
+ /** Returns the number of elements at this node or beneath it in the tree. */
+ size_type size() const {
+ return data_->size_;
+ }
+
+ /** Returns true if this is an empty node--a leaf node in the tree. */
+ bool empty() const {
+ return size() == 0;
+ }
+
+ /** Returns true if this node is red (as opposed to black). */
+ bool red() const {
+ return static_cast<bool>(data_->red_);
+ }
+
+ const value_type& entry() const {
+ return data_->contents_;
+ }
+ const K& key() const {
+ return entry().first;
+ }
+ const V& value() const {
+ return entry().second;
+ }
+ Color color() const {
+ return data_->red_ ? Color::Red : Color::Black;
+ }
+ const LlrbNode& left() const {
+ return data_->left_;
+ }
+ const LlrbNode& right() const {
+ return data_->right_;
+ }
+
+ private:
+ struct NodeData {
+ value_type contents_;
+
+ // Store the color in the high bit of the size to save memory.
+ size_type red_ : 1;
+ size_type size_ : 31;
+
+ LlrbNode left_;
+ LlrbNode right_;
+ };
+
+ explicit LlrbNode(NodeData&& data)
+ : data_{std::make_shared<NodeData>(std::move(data))} {
+ }
+
+ /**
+ * Constructs a dummy node that's a child of the empty node. This exists so
+ * that every node can have non-optional left and right children, despite the
+ * fact that these don't actually get visited.
+ *
+ * This should only be called when constructing the empty node.
+ */
+ explicit LlrbNode(std::nullptr_t) : data_{nullptr} {
+ }
+
+ std::shared_ptr<NodeData> data_;
+};
+
+} // namespace impl
+} // namespace immutable
+} // namespace firestore
+} // namespace firebase
+
+#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_IMMUTABLE_LLRB_NODE_H_
diff --git a/Firestore/core/src/firebase/firestore/immutable/map_entry.h b/Firestore/core/src/firebase/firestore/immutable/map_entry.h
new file mode 100644
index 0000000..1022b06
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/immutable/map_entry.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_IMMUTABLE_MAP_ENTRY_H_
+#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_IMMUTABLE_MAP_ENTRY_H_
+
+#include <functional>
+#include <utility>
+
+namespace firebase {
+namespace firestore {
+namespace immutable {
+
+/**
+ * Compares two keys out of a map entry.
+ *
+ * @tparam K The type of the first value in the pair.
+ * @tparam V The type of the second value in the pair.
+ * @tparam C The comparator for use for values of type K
+ */
+template <typename K, typename V, typename C = std::less<K>>
+struct KeyComparator {
+ using pair_type = std::pair<K, V>;
+
+ explicit KeyComparator(const C& comparator = C())
+ : key_comparator_(comparator) {
+ }
+
+ bool operator()(const K& lhs, const pair_type& rhs) const noexcept {
+ return key_comparator_(lhs, rhs.first);
+ }
+
+ bool operator()(const pair_type& lhs, const K& rhs) const noexcept {
+ return key_comparator_(lhs.first, rhs);
+ }
+
+ bool operator()(const pair_type& lhs, const pair_type& rhs) const noexcept {
+ return key_comparator_(lhs.first, rhs.first);
+ }
+
+ private:
+ C key_comparator_;
+};
+
+} // namespace immutable
+} // namespace firestore
+} // namespace firebase
+
+#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_IMMUTABLE_MAP_ENTRY_H_
diff --git a/Firestore/core/src/firebase/firestore/immutable/sorted_map.h b/Firestore/core/src/firebase/firestore/immutable/sorted_map.h
new file mode 100644
index 0000000..ef6f54e
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/immutable/sorted_map.h
@@ -0,0 +1,213 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_IMMUTABLE_SORTED_MAP_H_
+#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_IMMUTABLE_SORTED_MAP_H_
+
+#include <utility>
+
+#include "Firestore/core/src/firebase/firestore/immutable/array_sorted_map.h"
+#include "Firestore/core/src/firebase/firestore/immutable/sorted_map_base.h"
+#include "Firestore/core/src/firebase/firestore/immutable/tree_sorted_map.h"
+#include "Firestore/core/src/firebase/firestore/util/comparison.h"
+
+namespace firebase {
+namespace firestore {
+namespace immutable {
+
+/**
+ * SortedMap is a value type containing a map. It is immutable, but
+ * has methods to efficiently create new maps that are mutations of it.
+ */
+template <typename K, typename V, typename C = util::Comparator<K>>
+class SortedMap : public impl::SortedMapBase {
+ public:
+ /** The type of the entries stored in the map. */
+ using value_type = std::pair<K, V>;
+ using array_type = impl::ArraySortedMap<K, V, C>;
+ using tree_type = impl::TreeSortedMap<K, V, C>;
+
+ /**
+ * Creates an empty SortedMap.
+ */
+ explicit SortedMap(const C& comparator = {})
+ : SortedMap{array_type{comparator}} {
+ }
+
+ /**
+ * Creates an SortedMap containing the given entries.
+ */
+ SortedMap(std::initializer_list<value_type> entries,
+ const C& comparator = {}) {
+ if (entries.size() <= kFixedSize) {
+ tag_ = Tag::Array;
+ new (&array_) array_type{entries, comparator};
+ } else {
+ // TODO(wilhuff): implement tree initialization
+ abort();
+ }
+ }
+
+ SortedMap(const SortedMap& other) : tag_{other.tag_} {
+ switch (tag_) {
+ case Tag::Array:
+ new (&array_) array_type{other.array_};
+ break;
+ case Tag::Tree:
+ new (&tree_) tree_type{other.tree_};
+ break;
+ }
+ }
+
+ SortedMap(SortedMap&& other) : tag_{other.tag_} {
+ switch (tag_) {
+ case Tag::Array:
+ new (&array_) array_type{std::move(other.array_)};
+ break;
+ case Tag::Tree:
+ new (&tree_) tree_type{std::move(other.tree_)};
+ break;
+ }
+ }
+
+ ~SortedMap() {
+ switch (tag_) {
+ case Tag::Array:
+ array_.~ArraySortedMap();
+ break;
+ case Tag::Tree:
+ tree_.~TreeSortedMap();
+ break;
+ }
+ }
+
+ SortedMap& operator=(const SortedMap& other) {
+ if (tag_ == other.tag_) {
+ switch (tag_) {
+ case Tag::Array:
+ array_ = other.array_;
+ break;
+ case Tag::Tree:
+ tree_ = other.tree_;
+ break;
+ }
+ } else {
+ this->~SortedMap();
+ new (this) SortedMap{other};
+ }
+ return *this;
+ }
+
+ SortedMap& operator=(SortedMap&& other) {
+ if (tag_ == other.tag_) {
+ switch (tag_) {
+ case Tag::Array:
+ array_ = std::move(other.array_);
+ break;
+ case Tag::Tree:
+ tree_ = std::move(other.tree_);
+ break;
+ }
+ } else {
+ this->~SortedMap();
+ new (this) SortedMap{std::move(other)};
+ }
+ return *this;
+ }
+
+ /**
+ * Creates a new map identical to this one, but with a key-value pair added or
+ * updated.
+ *
+ * @param key The key to insert/update.
+ * @param value The value to associate with the key.
+ * @return A new dictionary with the added/updated value.
+ */
+ SortedMap insert(const K& key, const V& value) const {
+ switch (tag_) {
+ case Tag::Array:
+ // TODO(wilhuff): convert to TreeSortedMap
+ return SortedMap{array_.insert(key, value)};
+ case Tag::Tree:
+ return SortedMap{tree_.insert(key, value)};
+ }
+ FIREBASE_UNREACHABLE();
+ }
+
+ /**
+ * Creates a new map identical to this one, but with a key removed from it.
+ *
+ * @param key The key to remove.
+ * @return A new map without that value.
+ */
+ SortedMap erase(const K& key) const {
+ switch (tag_) {
+ case Tag::Array:
+ return SortedMap{array_.erase(key)};
+ case Tag::Tree:
+ return SortedMap{tree_.erase(key)};
+ }
+ FIREBASE_UNREACHABLE();
+ }
+
+ /** Returns true if the map contains no elements. */
+ bool empty() const {
+ switch (tag_) {
+ case Tag::Array:
+ return array_.empty();
+ case Tag::Tree:
+ return tree_.empty();
+ }
+ FIREBASE_UNREACHABLE();
+ }
+
+ /** Returns the number of items in this map. */
+ size_type size() const {
+ switch (tag_) {
+ case Tag::Array:
+ return array_.size();
+ case Tag::Tree:
+ return tree_.size();
+ }
+ FIREBASE_UNREACHABLE();
+ }
+
+ private:
+ explicit SortedMap(array_type&& array)
+ : tag_{Tag::Array}, array_{std::move(array)} {
+ }
+
+ explicit SortedMap(tree_type&& tree)
+ : tag_{Tag::Tree}, tree_{std::move(tree)} {
+ }
+
+ enum class Tag {
+ Array,
+ Tree,
+ };
+
+ Tag tag_;
+ union {
+ array_type array_;
+ tree_type tree_;
+ };
+};
+
+} // namespace immutable
+} // namespace firestore
+} // namespace firebase
+
+#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_IMMUTABLE_SORTED_MAP_H_
diff --git a/Firestore/core/src/firebase/firestore/immutable/sorted_map_base.cc b/Firestore/core/src/firebase/firestore/immutable/sorted_map_base.cc
new file mode 100644
index 0000000..f2faa33
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/immutable/sorted_map_base.cc
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#include "Firestore/core/src/firebase/firestore/immutable/sorted_map_base.h"
+
+namespace firebase {
+namespace firestore {
+namespace immutable {
+namespace impl {
+
+// Define external storage for constants:
+constexpr SortedMapBase::size_type SortedMapBase::kFixedSize;
+
+} // namespace impl
+} // namespace immutable
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/src/firebase/firestore/immutable/sorted_map_base.h b/Firestore/core/src/firebase/firestore/immutable/sorted_map_base.h
new file mode 100644
index 0000000..cfb19c1
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/immutable/sorted_map_base.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_IMMUTABLE_SORTED_MAP_BASE_H_
+#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_IMMUTABLE_SORTED_MAP_BASE_H_
+
+#include <cstdint>
+
+namespace firebase {
+namespace firestore {
+namespace immutable {
+namespace impl {
+
+/**
+ * A base class for implementing sorted maps, containing types and constants
+ * that don't depend upon the template parameters to the main class.
+ *
+ * Note that this exists as a base class rather than as just a namespace in
+ * order to make it possible for users of the SortedMap classes to avoid needing
+ * to declare storage for each instantiation of the template.
+ */
+class SortedMapBase {
+ public:
+ /**
+ * The type of size() methods on immutable collections. Note:
+ * * This is not size_t specifically to save space in the TreeSortedMap
+ * implementation.
+ * * This remains unsigned for straightforward casting to size_t.
+ */
+ using size_type = uint32_t;
+
+ /**
+ * The maximum size of an ArraySortedMap.
+ *
+ * This is the size threshold where we use a tree backed sorted map instead of
+ * an array backed sorted map. This is a more or less arbitrary chosen value,
+ * that was chosen to be large enough to fit most of object kind of Firebase
+ * data, but small enough to not notice degradation in performance for
+ * inserting and lookups. Feel free to empirically determine this constant,
+ * but don't expect much gain in real world performance.
+ */
+ // TODO(wilhuff): actually use this for switching implementations.
+ static constexpr size_type kFixedSize = 25;
+};
+
+} // namespace impl
+} // namespace immutable
+} // namespace firestore
+} // namespace firebase
+
+#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_IMMUTABLE_SORTED_MAP_BASE_H_
diff --git a/Firestore/core/src/firebase/firestore/immutable/tree_sorted_map.h b/Firestore/core/src/firebase/firestore/immutable/tree_sorted_map.h
new file mode 100644
index 0000000..dfe270d
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/immutable/tree_sorted_map.h
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_IMMUTABLE_TREE_SORTED_MAP_H_
+#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_IMMUTABLE_TREE_SORTED_MAP_H_
+
+#include <algorithm>
+#include <cassert>
+#include <functional>
+#include <memory>
+#include <utility>
+
+#include "Firestore/core/src/firebase/firestore/immutable/llrb_node.h"
+#include "Firestore/core/src/firebase/firestore/immutable/map_entry.h"
+#include "Firestore/core/src/firebase/firestore/immutable/sorted_map_base.h"
+#include "Firestore/core/src/firebase/firestore/util/comparator_holder.h"
+#include "Firestore/core/src/firebase/firestore/util/comparison.h"
+#include "Firestore/core/src/firebase/firestore/util/iterator_adaptors.h"
+
+namespace firebase {
+namespace firestore {
+namespace immutable {
+namespace impl {
+
+/**
+ * TreeSortedMap is a value type containing a map. It is immutable, but has
+ * methods to efficiently create new maps that are mutations of it.
+ */
+template <typename K, typename V, typename C = util::Comparator<K>>
+class TreeSortedMap : public SortedMapBase, private util::ComparatorHolder<C> {
+ public:
+ /**
+ * The type of the entries stored in the map.
+ */
+ using value_type = std::pair<K, V>;
+
+ /**
+ * The type of the node containing entries of value_type.
+ */
+ using node_type = LlrbNode<K, V>;
+
+ /**
+ * Creates an empty TreeSortedMap.
+ */
+ explicit TreeSortedMap(const C& comparator = {})
+ : util::ComparatorHolder<C>{comparator}, root_{node_type::Empty()} {
+ }
+
+ /**
+ * Creates a new map identical to this one, but with a key-value pair added or
+ * updated.
+ *
+ * @param key The key to insert/update.
+ * @param value The value to associate with the key.
+ * @return A new dictionary with the added/updated value.
+ */
+ TreeSortedMap insert(const K& key, const V& value) const {
+ // TODO(wilhuff): Actually implement insert
+ (void)key;
+ (void)value;
+ return *this;
+ }
+
+ /**
+ * Creates a new map identical to this one, but with a key removed from it.
+ *
+ * @param key The key to remove.
+ * @return A new map without that value.
+ */
+ TreeSortedMap erase(const K& key) const {
+ // TODO(wilhuff): Actually implement erase
+ (void)key;
+ return *this;
+ }
+
+ /** Returns true if the map contains no elements. */
+ bool empty() const {
+ return root_.empty();
+ }
+
+ /** Returns the number of items in this map. */
+ size_type size() const {
+ return root_.size();
+ }
+
+ const node_type& root() const {
+ return root_;
+ }
+
+ private:
+ TreeSortedMap(node_type&& root, const C& comparator) noexcept
+ : util::ComparatorHolder<C>{comparator}, root_{std::move(root)} {
+ }
+
+ TreeSortedMap Wrap(node_type&& root) noexcept {
+ return TreeSortedMap{std::move(root), this->comparator()};
+ }
+
+ node_type root_;
+};
+
+} // namespace impl
+} // namespace immutable
+} // namespace firestore
+} // namespace firebase
+
+#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_IMMUTABLE_TREE_SORTED_MAP_H_
diff --git a/Firestore/core/src/firebase/firestore/local/CMakeLists.txt b/Firestore/core/src/firebase/firestore/local/CMakeLists.txt
new file mode 100644
index 0000000..089d03c
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/local/CMakeLists.txt
@@ -0,0 +1,25 @@
+# Copyright 2018 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.
+
+cc_library(
+ firebase_firestore_local
+ SOURCES
+ leveldb_key.h
+ leveldb_key.cc
+ DEPENDS
+ LevelDB::LevelDB
+ absl_strings
+ firebase_firestore_model
+ firebase_firestore_util
+)
diff --git a/Firestore/core/src/firebase/firestore/local/leveldb_key.cc b/Firestore/core/src/firebase/firestore/local/leveldb_key.cc
new file mode 100644
index 0000000..7febe71
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/local/leveldb_key.cc
@@ -0,0 +1,817 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#include "Firestore/core/src/firebase/firestore/local/leveldb_key.h"
+
+#include <utility>
+#include <vector>
+
+#include "Firestore/core/src/firebase/firestore/local/leveldb_util.h"
+#include "Firestore/core/src/firebase/firestore/util/ordered_code.h"
+#include "absl/base/attributes.h"
+#include "absl/strings/escaping.h"
+#include "absl/strings/str_cat.h"
+
+using firebase::firestore::model::DocumentKey;
+using firebase::firestore::model::ResourcePath;
+using firebase::firestore::util::OrderedCode;
+
+namespace firebase {
+namespace firestore {
+namespace local {
+
+namespace {
+
+const char *kVersionGlobalTable = "version";
+const char *kMutationsTable = "mutation";
+const char *kDocumentMutationsTable = "document_mutation";
+const char *kMutationQueuesTable = "mutation_queue";
+const char *kTargetGlobalTable = "target_global";
+const char *kTargetsTable = "target";
+const char *kQueryTargetsTable = "query_target";
+const char *kTargetDocumentsTable = "target_document";
+const char *kDocumentTargetsTable = "document_target";
+const char *kRemoteDocumentsTable = "remote_document";
+
+/**
+ * Labels for the components of keys. These serve to make keys self-describing.
+ *
+ * These are intended to sort similarly to keys in the server storage format.
+ *
+ * Note that the server writes component labels using the equivalent to
+ * OrderedCode::WriteSignedNumDecreasing. This means that despite the higher
+ * numeric value, a terminator sorts before a path segment. In order to avoid
+ * needing the WriteSignedNumDecreasing code just for these values, this enum's
+ * values are in the reverse order to the server side.
+ *
+ * Most server-side values don't apply here. For example, the server embeds
+ * projects, databases, namespaces and similar values in its entity keys where
+ * the clients just open a different leveldb. Similarly, many of these values
+ * don't apply to the server since the server is backed by spanner which
+ * natively has concepts of tables and indexes. Where there's overlap, a comment
+ * denotes the server value from the storage_format_internal.proto.
+ */
+enum ComponentLabel {
+ /**
+ * A terminator is the final component of a key. All complete keys have a
+ * terminator and a key is known to be a key prefix if it doesn't have a
+ * terminator.
+ */
+ Terminator = 0, // TERMINATOR_COMPONENT = 63, server-side
+
+ /**
+ * A table name component names the logical table to which the key belongs.
+ */
+ TableName = 5,
+
+ /** A component containing the batch Id of a mutation. */
+ BatchId = 10,
+
+ /** A component containing the canonical Id of a query. */
+ CanonicalId = 11,
+
+ /** A component containing the target Id of a query. */
+ TargetId = 12,
+
+ /** A component containing a user Id. */
+ UserId = 13,
+
+ /**
+ * A path segment describes just a single segment in a resource path. Path
+ * segments that occur sequentially in a key represent successive segments in
+ * a single path.
+ *
+ * This value must be greater than ComponentLabel::Terminator to ensure that
+ * longer paths sort after paths that are prefixes of them.
+ *
+ * This value must also be larger than other separators so that path suffixes
+ * sort after other key components.
+ */
+ PathSegment = 62, // PATH = 60, server-side
+
+ /**
+ * The maximum value that can be encoded by WriteSignedNumIncreasing in a
+ * single byte.
+ */
+ Unknown = 63,
+};
+
+/**
+ * A helper for reading through the string form of a LevelDB key, as written
+ * by Writer.
+ */
+class Reader {
+ public:
+ explicit Reader(leveldb::Slice src) : src_(src), ok_(true) {
+ }
+
+ /** Returns true if the Reader has encountered no errors. */
+ bool ok() const {
+ return ok_;
+ }
+
+ /** Returns true if the Reader has no more bytes to read. */
+ bool empty() const {
+ return !ok_ || src_.empty();
+ }
+
+ /**
+ * Parses the components of the key and returns a string description of them.
+ */
+ std::string Describe();
+
+ void ReadTableNameMatching(const char *expected_table_name) {
+ if (!ReadLabeledStringMatching(ComponentLabel::TableName,
+ expected_table_name)) {
+ Fail();
+ }
+ }
+
+ model::BatchId ReadBatchId() {
+ return ReadLabeledInt32(ComponentLabel::BatchId);
+ }
+
+ std::string ReadCanonicalId() {
+ return ReadLabeledString(ComponentLabel::CanonicalId);
+ }
+
+ model::TargetId ReadTargetId() {
+ return ReadLabeledInt32(ComponentLabel::TargetId);
+ }
+
+ std::string ReadUserId() {
+ return ReadLabeledString(ComponentLabel::UserId);
+ }
+
+ /**
+ * Reads component labels and strings from the key until it finds a component
+ * label other than ComponentLabel::PathSegment (or the key is exhausted).
+ * All matched path segments are assembled into a ResourcePath and wrapped in
+ * a DocumentKey.
+ *
+ * If the read is unsuccessful or the document key is invalid, returns a
+ * default DocumentKey and fails the Reader.
+ *
+ * Otherwise returns the decoded DocumentKey and the Reader advances to the
+ * next unread byte.
+ */
+ DocumentKey ReadDocumentKey();
+
+ /**
+ * Reads a terminator component from the key.
+ *
+ * If the read is unsuccessful or the component wasn't a Terminator, fails
+ * the Reader.
+ *
+ * Otherwise the Reader advances to the next unread byte (which for valid
+ * keys should make the Reader empty).
+ */
+ void ReadTerminator() {
+ if (!ReadComponentLabelMatching(ComponentLabel::Terminator)) {
+ Fail();
+ }
+ }
+
+ private:
+ /** OrderedCode::ReadSignedNumIncreasing adapted to leveldb::Slice. */
+ int64_t ReadSignedNumIncreasing() {
+ if (ok_) {
+ int64_t result = 0;
+ absl::string_view tmp = MakeStringView(src_);
+ if (OrderedCode::ReadSignedNumIncreasing(&tmp, &result)) {
+ src_ = MakeSlice(tmp);
+ return result;
+ }
+ }
+
+ Fail();
+ return 0;
+ }
+
+ /** OrderedCode::ReadString adapted to leveldb::Slice. */
+ std::string ReadString() {
+ if (ok_) {
+ std::string result;
+ absl::string_view tmp = MakeStringView(src_);
+ if (OrderedCode::ReadString(&tmp, &result)) {
+ src_ = MakeSlice(tmp);
+ return result;
+ }
+ }
+
+ Fail();
+ return "";
+ }
+
+ /**
+ * Reads a component label from the key.
+ *
+ * If the read is unsuccessful, returns ComponentLabel::Unknown and fails the
+ * Reader.
+ *
+ * Otherwise, returns the ComponentLabel and advances the Reader to the next
+ * unread byte.
+ */
+ ComponentLabel ReadComponentLabel() {
+ if (ok_) {
+ int64_t raw_result = ReadSignedNumIncreasing();
+ if (ok_ && raw_result >= ComponentLabel::Terminator &&
+ raw_result <= ComponentLabel::Unknown) {
+ return static_cast<ComponentLabel>(raw_result);
+ }
+ }
+
+ Fail();
+ return ComponentLabel::Unknown;
+ }
+
+ /**
+ * Reads a component label from the key.
+ *
+ * If the read is unsuccessful, returns false, and fails the Reader.
+ *
+ * Otherwise returns whether or not the component label is equal to the
+ * `expected_label` and advances the Reader to the next unread byte.
+ */
+ ABSL_MUST_USE_RESULT
+ bool ReadComponentLabelMatching(ComponentLabel expected_label) {
+ if (ok_) {
+ int64_t raw_result = ReadSignedNumIncreasing();
+ if (ok_) {
+ // Note: mismatch of a component label is not necessarily a failure.
+ // It's treated as a potential branch point within the parser.
+ return raw_result == expected_label;
+ }
+ }
+
+ Fail();
+ return false;
+ }
+
+ /**
+ * Reads a signed number from the key and verifies that the value fits in a
+ * 32-bit integer.
+ *
+ * If the read is unsuccessful or the number was out of range, returns 0 and
+ * fails the Reader.
+ *
+ * Otherwise, returns the number and advances the Reader to the next unread
+ * byte.
+ */
+ int32_t ReadInt32() {
+ if (ok_) {
+ int64_t raw_result = ReadSignedNumIncreasing();
+ if (ok_ && raw_result >= INT32_MIN && raw_result <= INT32_MAX) {
+ return static_cast<int32_t>(raw_result);
+ }
+ }
+
+ Fail();
+ return 0;
+ }
+
+ /**
+ * Reads a component label and signed number from the key and verifies that
+ * the label matches the expected_label and the value fits in a 32-bit
+ * integer.
+ *
+ * If the read is unsuccessful, the label didn't match, or the number was out
+ * of range, returns 0 and fails the Reader.
+ *
+ * Otherwise, returns the number and advances the Reader to the next unread
+ * byte.
+ */
+ int32_t ReadLabeledInt32(ComponentLabel expected_label) {
+ if (!ReadComponentLabelMatching(expected_label)) {
+ Fail();
+ }
+ return ReadInt32();
+ }
+
+ /**
+ * Reads a component label and a string from the key verifies that the label
+ * matches the expected_label.
+ *
+ * If the read is unsuccessful or the label didn't match, returns an empty
+ * string and fails the Reader.
+ *
+ * Otherwise, returns the string and advances the Reader to the next unread
+ * byte.
+ */
+ std::string ReadLabeledString(ComponentLabel expected_label) {
+ if (!ReadComponentLabelMatching(expected_label)) {
+ Fail();
+ }
+ return ReadString();
+ }
+
+ /**
+ * Reads a component label and a string from the key and verifies that the
+ * label matches the expected_label and the string matches the
+ * expected_value.
+ *
+ * If the read is unsuccessful or the label wasn't a string, returns false
+ * and fails the Reader.
+ *
+ * Otherwise returns whether or not the string that was read was equal to the
+ * expected value and advances the reader to the next unread byte.
+ */
+ ABSL_MUST_USE_RESULT
+ bool ReadLabeledStringMatching(ComponentLabel expected_label,
+ const char *expected_value) {
+ std::string value = ReadLabeledString(expected_label);
+ if (ok_) {
+ // Value mismatch does not constitute a failure:
+ return value == expected_value;
+ }
+
+ Fail();
+ return false;
+ }
+
+ /**
+ * Fails the Reader. All subsequent read operations will exit early if
+ * possible. Return values from any method will be defaults, as if those
+ * methods had failed themselves.
+ */
+ void Fail() {
+ ok_ = false;
+ }
+
+ leveldb::Slice src_;
+ bool ok_;
+};
+
+DocumentKey Reader::ReadDocumentKey() {
+ std::vector<std::string> path_segments;
+ while (!empty()) {
+ // Advance a temporary slice to avoid advancing contents into the next key
+ // component which may not be a path segment.
+ leveldb::Slice saved_position = src_;
+ if (!ReadComponentLabelMatching(ComponentLabel::PathSegment)) {
+ src_ = saved_position;
+ break;
+ }
+
+ std::string segment = ReadString();
+ if (!ok_) break;
+
+ path_segments.push_back(std::move(segment));
+ }
+
+ ResourcePath path{std::move(path_segments)};
+ if (ok_ && !path.empty() && DocumentKey::IsDocumentKey(path)) {
+ return DocumentKey{std::move(path)};
+ }
+
+ Fail();
+ return DocumentKey{};
+}
+
+/**
+ * Returns a base64-encoded string for an invalid key, used for debug-friendly
+ * description text.
+ */
+std::string InvalidKey(leveldb::Slice key) {
+ std::string result;
+ absl::Base64Escape(MakeStringView(key), &result);
+ return result;
+}
+
+std::string Reader::Describe() {
+ leveldb::Slice original = src_;
+
+ bool is_terminated = false;
+
+ std::string description;
+ absl::StrAppend(&description, "[");
+
+ while (!empty()) {
+ leveldb::Slice saved_source = src_;
+
+ ComponentLabel label = ReadComponentLabel();
+ if (!ok_) {
+ break;
+ }
+ if (label == ComponentLabel::Terminator) {
+ is_terminated = true;
+ break;
+ }
+
+ // Reset the reader since all the different read routines expect to see the
+ // separator first
+ src_ = saved_source;
+
+ if (label == ComponentLabel::PathSegment) {
+ DocumentKey document_key = ReadDocumentKey();
+ if (ok_) {
+ absl::StrAppend(&description,
+ " key=", document_key.path().CanonicalString());
+ }
+
+ } else if (label == ComponentLabel::TableName) {
+ std::string table = ReadLabeledString(ComponentLabel::TableName);
+ if (ok_) {
+ absl::StrAppend(&description, table, ":");
+ }
+
+ } else if (label == ComponentLabel::BatchId) {
+ model::BatchId batch_id = ReadBatchId();
+ if (ok_) {
+ absl::StrAppend(&description, " batch_id=", batch_id);
+ }
+
+ } else if (label == ComponentLabel::CanonicalId) {
+ std::string canonical_id = ReadCanonicalId();
+ if (ok_) {
+ absl::StrAppend(&description, " canonical_id=", canonical_id);
+ }
+
+ } else if (label == ComponentLabel::TargetId) {
+ model::TargetId target_id = ReadTargetId();
+ if (ok_) {
+ absl::StrAppend(&description, " target_id=", target_id);
+ }
+
+ } else if (label == ComponentLabel::UserId) {
+ std::string user_id = ReadUserId();
+ if (ok_) {
+ absl::StrAppend(&description, " user_id=", user_id);
+ }
+
+ } else {
+ absl::StrAppend(&description, " unknown label=", static_cast<int>(label));
+ Fail();
+ }
+ }
+
+ if (!ok_ || !empty()) {
+ absl::StrAppend(&description, " invalid key=<", InvalidKey(original), ">");
+
+ } else if (!is_terminated) {
+ absl::StrAppend(&description, " incomplete key");
+ }
+
+ absl::StrAppend(&description, "]");
+ return description;
+}
+
+class Writer {
+ public:
+ std::string result() const {
+ return dest_;
+ }
+
+ void WriteTerminator() {
+ OrderedCode::WriteSignedNumIncreasing(&dest_, ComponentLabel::Terminator);
+ }
+
+ void WriteTableName(const char *table_name) {
+ WriteLabeledString(ComponentLabel::TableName, table_name);
+ }
+
+ void WriteBatchId(model::BatchId batch_id) {
+ WriteLabeledInt32(ComponentLabel::BatchId, batch_id);
+ }
+
+ void WriteCanonicalId(absl::string_view canonical_id) {
+ WriteLabeledString(ComponentLabel::CanonicalId, canonical_id);
+ }
+
+ void WriteTargetId(model::TargetId target_id) {
+ WriteLabeledInt32(ComponentLabel::TargetId, target_id);
+ }
+
+ void WriteUserId(absl::string_view user_id) {
+ WriteLabeledString(ComponentLabel::UserId, user_id);
+ }
+
+ /**
+ * For each segment in the given resource path writes a
+ * ComponentLabel::PathSegment component label and a string containing the
+ * path segment.
+ */
+ void WriteResourcePath(const ResourcePath &path) {
+ for (const auto &segment : path) {
+ WriteComponentLabel(ComponentLabel::PathSegment);
+ OrderedCode::WriteString(&dest_, segment);
+ }
+ }
+
+ private:
+ /** Writes a component label to the given key destination. */
+ void WriteComponentLabel(ComponentLabel label) {
+ OrderedCode::WriteSignedNumIncreasing(&dest_, label);
+ }
+
+ /**
+ * Writes a component label and a signed integer to the given key destination.
+ */
+ void WriteLabeledInt32(ComponentLabel label, int32_t value) {
+ WriteComponentLabel(label);
+ OrderedCode::WriteSignedNumIncreasing(&dest_, value);
+ }
+
+ /**
+ * Writes a component label and an encoded string to the given key
+ * destination.
+ */
+ void WriteLabeledString(ComponentLabel label, absl::string_view value) {
+ WriteComponentLabel(label);
+ OrderedCode::WriteString(&dest_, value);
+ }
+
+ std::string dest_{};
+};
+
+} // namespace
+
+std::string Describe(leveldb::Slice key) {
+ Reader reader{key};
+ return reader.Describe();
+}
+
+std::string LevelDbVersionKey::Key() {
+ Writer writer;
+ writer.WriteTableName(kVersionGlobalTable);
+ writer.WriteTerminator();
+ return writer.result();
+}
+
+std::string LevelDbMutationKey::KeyPrefix() {
+ Writer writer;
+ writer.WriteTableName(kMutationsTable);
+ return writer.result();
+}
+
+std::string LevelDbMutationKey::KeyPrefix(absl::string_view user_id) {
+ Writer writer;
+ writer.WriteTableName(kMutationsTable);
+ writer.WriteUserId(user_id);
+ return writer.result();
+}
+
+std::string LevelDbMutationKey::Key(absl::string_view user_id,
+ model::BatchId batch_id) {
+ Writer writer;
+ writer.WriteTableName(kMutationsTable);
+ writer.WriteUserId(user_id);
+ writer.WriteBatchId(batch_id);
+ writer.WriteTerminator();
+ return writer.result();
+}
+
+bool LevelDbMutationKey::Decode(leveldb::Slice key) {
+ Reader reader{key};
+ reader.ReadTableNameMatching(kMutationsTable);
+ user_id_ = reader.ReadUserId();
+ batch_id_ = reader.ReadBatchId();
+ reader.ReadTerminator();
+ return reader.ok();
+}
+
+std::string LevelDbDocumentMutationKey::KeyPrefix() {
+ Writer writer;
+ writer.WriteTableName(kDocumentMutationsTable);
+ return writer.result();
+}
+
+std::string LevelDbDocumentMutationKey::KeyPrefix(absl::string_view user_id) {
+ Writer writer;
+ writer.WriteTableName(kDocumentMutationsTable);
+ writer.WriteUserId(user_id);
+ return writer.result();
+}
+
+std::string LevelDbDocumentMutationKey::KeyPrefix(
+ absl::string_view user_id, const ResourcePath &resource_path) {
+ Writer writer;
+ writer.WriteTableName(kDocumentMutationsTable);
+ writer.WriteUserId(user_id);
+ writer.WriteResourcePath(resource_path);
+ return writer.result();
+}
+
+std::string LevelDbDocumentMutationKey::Key(absl::string_view user_id,
+ const DocumentKey &document_key,
+ model::BatchId batch_id) {
+ Writer writer;
+ writer.WriteTableName(kDocumentMutationsTable);
+ writer.WriteUserId(user_id);
+ writer.WriteResourcePath(document_key.path());
+ writer.WriteBatchId(batch_id);
+ writer.WriteTerminator();
+ return writer.result();
+}
+
+bool LevelDbDocumentMutationKey::Decode(leveldb::Slice key) {
+ Reader reader{key};
+ reader.ReadTableNameMatching(kDocumentMutationsTable);
+ user_id_ = reader.ReadUserId();
+ document_key_ = reader.ReadDocumentKey();
+ batch_id_ = reader.ReadBatchId();
+ reader.ReadTerminator();
+ return reader.ok();
+}
+
+std::string LevelDbMutationQueueKey::KeyPrefix() {
+ Writer writer;
+ writer.WriteTableName(kMutationQueuesTable);
+ return writer.result();
+}
+
+std::string LevelDbMutationQueueKey::Key(absl::string_view user_id) {
+ Writer writer;
+ writer.WriteTableName(kMutationQueuesTable);
+ writer.WriteUserId(user_id);
+ writer.WriteTerminator();
+ return writer.result();
+}
+
+bool LevelDbMutationQueueKey::Decode(leveldb::Slice key) {
+ Reader reader{key};
+ reader.ReadTableNameMatching(kMutationQueuesTable);
+ user_id_ = reader.ReadUserId();
+ reader.ReadTerminator();
+ return reader.ok();
+}
+
+std::string LevelDbTargetGlobalKey::Key() {
+ Writer writer;
+ writer.WriteTableName(kTargetGlobalTable);
+ writer.WriteTerminator();
+ return writer.result();
+}
+
+bool LevelDbTargetGlobalKey::Decode(leveldb::Slice key) {
+ Reader reader{key};
+ reader.ReadTableNameMatching(kTargetGlobalTable);
+ reader.ReadTerminator();
+ return reader.ok();
+}
+
+std::string LevelDbTargetKey::KeyPrefix() {
+ Writer writer;
+ writer.WriteTableName(kTargetsTable);
+ return writer.result();
+}
+
+std::string LevelDbTargetKey::Key(model::TargetId target_id) {
+ Writer writer;
+ writer.WriteTableName(kTargetsTable);
+ writer.WriteTargetId(target_id);
+ writer.WriteTerminator();
+ return writer.result();
+}
+
+bool LevelDbTargetKey::Decode(leveldb::Slice key) {
+ Reader reader{key};
+ reader.ReadTableNameMatching(kTargetsTable);
+ target_id_ = reader.ReadTargetId();
+ reader.ReadTerminator();
+ return reader.ok();
+}
+
+std::string LevelDbQueryTargetKey::KeyPrefix() {
+ Writer writer;
+ writer.WriteTableName(kQueryTargetsTable);
+ return writer.result();
+}
+
+std::string LevelDbQueryTargetKey::KeyPrefix(absl::string_view canonical_id) {
+ Writer writer;
+ writer.WriteTableName(kQueryTargetsTable);
+ writer.WriteCanonicalId(canonical_id);
+ return writer.result();
+}
+
+std::string LevelDbQueryTargetKey::Key(absl::string_view canonical_id,
+ model::TargetId target_id) {
+ Writer writer;
+ writer.WriteTableName(kQueryTargetsTable);
+ writer.WriteCanonicalId(canonical_id);
+ writer.WriteTargetId(target_id);
+ writer.WriteTerminator();
+ return writer.result();
+}
+
+bool LevelDbQueryTargetKey::Decode(leveldb::Slice key) {
+ Reader reader{key};
+ reader.ReadTableNameMatching(kQueryTargetsTable);
+ canonical_id_ = reader.ReadCanonicalId();
+ target_id_ = reader.ReadTargetId();
+ reader.ReadTerminator();
+ return reader.ok();
+}
+
+std::string LevelDbTargetDocumentKey::KeyPrefix() {
+ Writer writer;
+ writer.WriteTableName(kTargetDocumentsTable);
+ return writer.result();
+}
+
+std::string LevelDbTargetDocumentKey::KeyPrefix(model::TargetId target_id) {
+ Writer writer;
+ writer.WriteTableName(kTargetDocumentsTable);
+ writer.WriteTargetId(target_id);
+ return writer.result();
+}
+
+std::string LevelDbTargetDocumentKey::Key(model::TargetId target_id,
+ const DocumentKey &document_key) {
+ Writer writer;
+ writer.WriteTableName(kTargetDocumentsTable);
+ writer.WriteTargetId(target_id);
+ writer.WriteResourcePath(document_key.path());
+ writer.WriteTerminator();
+ return writer.result();
+}
+
+bool LevelDbTargetDocumentKey::Decode(leveldb::Slice key) {
+ Reader reader{key};
+ reader.ReadTableNameMatching(kTargetDocumentsTable);
+ target_id_ = reader.ReadTargetId();
+ document_key_ = reader.ReadDocumentKey();
+ reader.ReadTerminator();
+ return reader.ok();
+}
+
+std::string LevelDbDocumentTargetKey::KeyPrefix() {
+ Writer writer;
+ writer.WriteTableName(kDocumentTargetsTable);
+ return writer.result();
+}
+
+std::string LevelDbDocumentTargetKey::KeyPrefix(
+ const ResourcePath &resource_path) {
+ Writer writer;
+ writer.WriteTableName(kDocumentTargetsTable);
+ writer.WriteResourcePath(resource_path);
+ return writer.result();
+}
+
+std::string LevelDbDocumentTargetKey::Key(const DocumentKey &document_key,
+ model::TargetId target_id) {
+ Writer writer;
+ writer.WriteTableName(kDocumentTargetsTable);
+ writer.WriteResourcePath(document_key.path());
+ writer.WriteTargetId(target_id);
+ writer.WriteTerminator();
+ return writer.result();
+}
+
+bool LevelDbDocumentTargetKey::Decode(leveldb::Slice key) {
+ Reader reader{key};
+ reader.ReadTableNameMatching(kDocumentTargetsTable);
+ document_key_ = reader.ReadDocumentKey();
+ target_id_ = reader.ReadTargetId();
+ reader.ReadTerminator();
+ return reader.ok();
+}
+
+std::string LevelDbRemoteDocumentKey::KeyPrefix() {
+ Writer writer;
+ writer.WriteTableName(kRemoteDocumentsTable);
+ return writer.result();
+}
+
+std::string LevelDbRemoteDocumentKey::KeyPrefix(
+ const ResourcePath &resource_path) {
+ Writer writer;
+ writer.WriteTableName(kRemoteDocumentsTable);
+ writer.WriteResourcePath(resource_path);
+ return writer.result();
+}
+
+std::string LevelDbRemoteDocumentKey::Key(const DocumentKey &key) {
+ Writer writer;
+ writer.WriteTableName(kRemoteDocumentsTable);
+ writer.WriteResourcePath(key.path());
+ writer.WriteTerminator();
+ return writer.result();
+}
+
+bool LevelDbRemoteDocumentKey::Decode(leveldb::Slice key) {
+ Reader reader{key};
+ reader.ReadTableNameMatching(kRemoteDocumentsTable);
+ document_key_ = reader.ReadDocumentKey();
+ reader.ReadTerminator();
+ return reader.ok();
+}
+
+} // namespace local
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/src/firebase/firestore/local/leveldb_key.h b/Firestore/core/src/firebase/firestore/local/leveldb_key.h
new file mode 100644
index 0000000..9f78849
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/local/leveldb_key.h
@@ -0,0 +1,485 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_LOCAL_LEVELDB_KEY_H_
+#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_LOCAL_LEVELDB_KEY_H_
+
+#include <string>
+
+#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+#include "Firestore/core/src/firebase/firestore/model/resource_path.h"
+#include "Firestore/core/src/firebase/firestore/model/types.h"
+#include "absl/strings/string_view.h"
+#include "leveldb/slice.h"
+
+namespace firebase {
+namespace firestore {
+namespace local {
+
+// Utilities for encoding and decoding LevelDB row keys and key prefixes.
+//
+// LevelDB keys are strings, so all the routines in here operate on strings to
+// be able to produce and consume leveldb APIs directly.
+//
+// All leveldb logical tables should have their keys structures described in
+// this file.
+//
+// mutations:
+// - table_name: string = "mutation"
+// - user_id: string
+// - batch_id: model::BatchId
+//
+// document_mutations:
+// - table_name: string = "document_mutation"
+// - user_id: string
+// - path: ResourcePath
+// - batch_id: model::BatchId
+//
+// mutation_queues:
+// - table_name: string = "mutation_queue"
+// - user_id: string
+//
+// targets:
+// - table_name: string = "target"
+// - target_id: model::TargetId
+//
+// target_globals:
+// - table_name: string = "target_global"
+//
+// query_targets:
+// - table_name: string = "query_target"
+// - canonical_id: string
+// - target_id: model::TargetId
+//
+// target_documents:
+// - table_name: string = "target_document"
+// - target_id: model::TargetId
+// - path: ResourcePath
+//
+// document_targets:
+// - table_name: string = "document_target"
+// - path: ResourcePath
+// - target_id: model::TargetId
+//
+// remote_documents:
+// - table_name: string = "remote_document"
+// - path: ResourcePath
+
+/**
+ * Parses the given key and returns a human readable description of its
+ * contents, suitable for error messages and logging.
+ */
+std::string Describe(leveldb::Slice key);
+
+/** A key to a singleton row storing the version of the schema. */
+class LevelDbVersionKey {
+ public:
+ /**
+ * Returns the key pointing to the singleton row storing the schema version.
+ */
+ static std::string Key();
+};
+
+/** A key in the mutations table. */
+class LevelDbMutationKey {
+ public:
+ /**
+ * Creates a key prefix that points just before the first key in the table.
+ */
+ static std::string KeyPrefix();
+
+ /**
+ * Creates a key prefix that points just before the first key for the given
+ * user_id.
+ */
+ static std::string KeyPrefix(absl::string_view user_id);
+
+ /** Creates a complete key that points to a specific user_id and batch_id. */
+ static std::string Key(absl::string_view user_id, model::BatchId batch_id);
+
+ /**
+ * Decodes the given complete key, storing the decoded values in this
+ * instance.
+ *
+ * @return true if the key successfully decoded, false otherwise. If false is
+ * returned, this instance is in an undefined state until the next call to
+ * `Decode()`.
+ */
+ bool Decode(leveldb::Slice key);
+
+ /** The user that owns the mutation batches. */
+ const std::string& user_id() const {
+ return user_id_;
+ }
+
+ /** The batch_id of the batch. */
+ model::BatchId batch_id() const {
+ return batch_id_;
+ }
+
+ private:
+ // Deliberately uninitialized: will be assigned in Decode
+ std::string user_id_;
+ model::BatchId batch_id_;
+};
+
+/**
+ * A key in the document mutations index, which stores the batches in which
+ * documents are mutated.
+ */
+class LevelDbDocumentMutationKey {
+ public:
+ /**
+ * Creates a key prefix that points just before the first key in the table.
+ */
+ static std::string KeyPrefix();
+
+ /**
+ * Creates a key prefix that points just before the first key for the given
+ * user_id.
+ */
+ static std::string KeyPrefix(absl::string_view user_id);
+
+ /**
+ * Creates a key prefix that points just before the first key for the user_id
+ * and resource path.
+ *
+ * Note that this uses a ResourcePath rather than an DocumentKey in order to
+ * allow prefix scans over a collection. However a naive scan over those
+ * results isn't useful since it would match both immediate children of the
+ * collection and any subcollections.
+ */
+ static std::string KeyPrefix(absl::string_view user_id,
+ const model::ResourcePath& resource_path);
+
+ /**
+ * Creates a complete key that points to a specific user_id, document key,
+ * and batch_id.
+ */
+ static std::string Key(absl::string_view user_id,
+ const model::DocumentKey& document_key,
+ model::BatchId batch_id);
+
+ /**
+ * Decodes the given complete key, storing the decoded values in this
+ * instance.
+ *
+ * @return true if the key successfully decoded, false otherwise. If false is
+ * returned, this instance is in an undefined state until the next call to
+ * `Decode()`.
+ */
+ bool Decode(leveldb::Slice key);
+
+ /** The user that owns the mutation batches. */
+ const std::string& user_id() const {
+ return user_id_;
+ }
+
+ /** The path to the document, as encoded in the key. */
+ const model::DocumentKey& document_key() const {
+ return document_key_;
+ }
+
+ /** The batch_id in which the document participates. */
+ model::BatchId batch_id() const {
+ return batch_id_;
+ }
+
+ private:
+ // Deliberately uninitialized: will be assigned in Decode
+ std::string user_id_;
+ model::DocumentKey document_key_;
+ model::BatchId batch_id_;
+};
+
+/**
+ * A key in the mutation_queues table.
+ *
+ * Note that where `mutation_queues` table contains one row about each queue,
+ * the `mutations` table contains the actual mutation batches themselves.
+ */
+class LevelDbMutationQueueKey {
+ public:
+ /**
+ * Creates a key prefix that points just before the first key in the table.
+ */
+ static std::string KeyPrefix();
+
+ /**
+ * Creates a complete key that points to a specific mutation queue entry for
+ * the given user_id.
+ */
+ static std::string Key(absl::string_view user_id);
+
+ /**
+ * Decodes the given complete key, storing the decoded values in this
+ * instance.
+ *
+ * @return true if the key successfully decoded, false otherwise. If false is
+ * returned, this instance is in an undefined state until the next call to
+ * `Decode()`.
+ */
+ bool Decode(leveldb::Slice key);
+
+ const std::string& user_id() const {
+ return user_id_;
+ }
+
+ private:
+ // Deliberately uninitialized: will be assigned in Decode
+ std::string user_id_;
+};
+
+/**
+ * A key in the target globals table, a record of global values across all
+ * targets.
+ */
+class LevelDbTargetGlobalKey {
+ public:
+ /** Creates a key that points to the single target global row. */
+ static std::string Key();
+
+ /**
+ * Decodes the contents of a target global key, essentially just verifying
+ * that the key has the correct table name.
+ */
+ bool Decode(leveldb::Slice key);
+};
+
+/** A key in the targets table. */
+class LevelDbTargetKey {
+ public:
+ /**
+ * Creates a key prefix that points just before the first key in the table.
+ */
+ static std::string KeyPrefix();
+
+ /** Creates a complete key that points to a specific target, by target_id. */
+ static std::string Key(model::TargetId target_id);
+
+ /**
+ * Decodes the contents of a target key, storing the decoded values in this
+ * instance.
+ *
+ * @return true if the key successfully decoded, false otherwise. If false is
+ * returned, this instance is in an undefined state until the next call to
+ * `Decode()`.
+ */
+ bool Decode(leveldb::Slice key);
+
+ model::TargetId target_id() {
+ return target_id_;
+ }
+
+ private:
+ model::TargetId target_id_ = 0;
+};
+
+/**
+ * A key in the query targets table, an index of canonical_ids to the targets
+ * they may match. This is not a unique mapping because canonical_id does not
+ * promise a unique name for all possible queries.
+ */
+class LevelDbQueryTargetKey {
+ public:
+ /**
+ * Creates a key that contains just the query targets table prefix and points
+ * just before the first key.
+ */
+ static std::string KeyPrefix();
+
+ /**
+ * Creates a key that points to the first query-target association for a
+ * canonical_id.
+ */
+ static std::string KeyPrefix(absl::string_view canonical_id);
+
+ /** Creates a key that points to a specific query-target entry. */
+ static std::string Key(absl::string_view canonical_id,
+ model::TargetId target_id);
+
+ /**
+ * Decodes the contents of a query target key, storing the decoded values in
+ * this instance.
+ *
+ * @return true if the key successfully decoded, false otherwise. If false is
+ * returned, this instance is in an undefined state until the next call to
+ * `Decode()`.
+ */
+ bool Decode(leveldb::Slice key);
+
+ /** The canonical_id derived from the query. */
+ const std::string& canonical_id() const {
+ return canonical_id_;
+ }
+
+ /** The target_id identifying a target. */
+ model::TargetId target_id() const {
+ return target_id_;
+ }
+
+ private:
+ // Deliberately uninitialized: will be assigned in Decode
+ std::string canonical_id_;
+ model::TargetId target_id_;
+};
+
+/**
+ * A key in the target documents table, an index of target_ids to the documents
+ * they contain.
+ */
+class LevelDbTargetDocumentKey {
+ public:
+ /**
+ * Creates a key that contains just the target documents table prefix and
+ * points just before the first key.
+ */
+ static std::string KeyPrefix();
+
+ /**
+ * Creates a key that points to the first target-document association for a
+ * target_id.
+ */
+ static std::string KeyPrefix(model::TargetId target_id);
+
+ /** Creates a key that points to a specific target-document entry. */
+ static std::string Key(model::TargetId target_id,
+ const model::DocumentKey& document_key);
+
+ /**
+ * Decodes the contents of a target document key, storing the decoded values
+ * in this instance.
+ *
+ * @return true if the key successfully decoded, false otherwise. If false is
+ * returned, this instance is in an undefined state until the next call to
+ * `Decode()`.
+ */
+ bool Decode(leveldb::Slice key);
+
+ /** The target_id identifying a target. */
+ model::TargetId target_id() {
+ return target_id_;
+ }
+
+ /** The path to the document, as encoded in the key. */
+ const model::DocumentKey& document_key() {
+ return document_key_;
+ }
+
+ private:
+ // Deliberately uninitialized: will be assigned in Decode
+ model::TargetId target_id_;
+ model::DocumentKey document_key_;
+};
+
+/**
+ * A key in the document targets table, an index from documents to the targets
+ * that contain them.
+ */
+class LevelDbDocumentTargetKey {
+ public:
+ /**
+ * Creates a key that contains just the document targets table prefix and
+ * points just before the first key.
+ */
+ static std::string KeyPrefix();
+
+ /**
+ * Creates a key that points to the first document-target association for
+ * document.
+ */
+ static std::string KeyPrefix(const model::ResourcePath& resource_path);
+
+ /** Creates a key that points to a specific document-target entry. */
+ static std::string Key(const model::DocumentKey& document_key,
+ model::TargetId target_id);
+
+ /**
+ * Decodes the contents of a document target key, storing the decoded values
+ * in this instance.
+ *
+ * @return true if the key successfully decoded, false otherwise. If false is
+ * returned, this instance is in an undefined state until the next call to
+ * `Decode()`.
+ */
+ bool Decode(leveldb::Slice key);
+
+ /** The target_id identifying a target. */
+ model::TargetId target_id() const {
+ return target_id_;
+ }
+
+ /** The path to the document, as encoded in the key. */
+ const model::DocumentKey& document_key() const {
+ return document_key_;
+ }
+
+ private:
+ // Deliberately uninitialized: will be assigned in Decode
+ model::TargetId target_id_;
+ model::DocumentKey document_key_;
+};
+
+/** A key in the remote documents table. */
+class LevelDbRemoteDocumentKey {
+ public:
+ /**
+ * Creates a key that contains just the remote documents table prefix and
+ * points just before the first remote document key.
+ */
+ static std::string KeyPrefix();
+
+ /**
+ * Creates a complete key that points to a specific document. The document_key
+ * must have an even number of path segments.
+ */
+ static std::string Key(const model::DocumentKey& document_key);
+
+ /**
+ * Creates a key prefix that contains a part of a document path. Odd numbers
+ * of segments create a collection key prefix, while an even number of
+ * segments create a document key prefix. Note that a document key prefix will
+ * match the document itself and any documents that exist in its
+ * subcollections.
+ */
+ static std::string KeyPrefix(const model::ResourcePath& resource_path);
+
+ /**
+ * Decodes the contents of a remote document key, storing the decoded values
+ * in this instance. This can only decode complete document paths (i.e. the
+ * result of Key()).
+ *
+ * @return true if the key successfully decoded, false otherwise. If false is
+ * returned, this instance is in an undefined state until the next call to
+ * `Decode()`.
+ */
+ bool Decode(leveldb::Slice key);
+
+ /** The path to the document, as encoded in the key. */
+ const model::DocumentKey& document_key() const {
+ return document_key_;
+ }
+
+ private:
+ // Deliberately uninitialized: will be assigned in Decode
+ model::DocumentKey document_key_;
+};
+
+} // namespace local
+} // namespace firestore
+} // namespace firebase
+
+#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_LOCAL_LEVELDB_KEY_H_
diff --git a/Firestore/core/src/firebase/firestore/local/leveldb_transaction.cc b/Firestore/core/src/firebase/firestore/local/leveldb_transaction.cc
new file mode 100644
index 0000000..f998550
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/local/leveldb_transaction.cc
@@ -0,0 +1,249 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#include "Firestore/core/src/firebase/firestore/local/leveldb_transaction.h"
+
+#include "Firestore/core/src/firebase/firestore/local/leveldb_key.h"
+#include "Firestore/core/src/firebase/firestore/util/firebase_assert.h"
+#include "Firestore/core/src/firebase/firestore/util/log.h"
+#include "absl/memory/memory.h"
+#include "leveldb/write_batch.h"
+
+using leveldb::DB;
+using leveldb::ReadOptions;
+using leveldb::Slice;
+using leveldb::Status;
+using leveldb::WriteBatch;
+using leveldb::WriteOptions;
+
+namespace firebase {
+namespace firestore {
+namespace local {
+
+LevelDbTransaction::Iterator::Iterator(LevelDbTransaction* txn)
+ : db_iter_(txn->db_->NewIterator(txn->read_options_)),
+ last_version_(txn->version_),
+ txn_(txn),
+ mutations_iter_(txn->mutations_.begin()),
+ current_(),
+ is_mutation_(false),
+ // Iterator doesn't really point to anything yet, so is
+ // invalid
+ is_valid_(false) {
+}
+
+void LevelDbTransaction::Iterator::UpdateCurrent() {
+ bool mutation_is_valid = mutations_iter_ != txn_->mutations_.end();
+ is_valid_ = mutation_is_valid || db_iter_->Valid();
+
+ if (is_valid_) {
+ if (!mutation_is_valid) {
+ is_mutation_ = false;
+ } else if (!db_iter_->Valid()) {
+ is_mutation_ = true;
+ } else {
+ // Both iterators are valid. If the leveldb key is equal to or greater
+ // than the current mutation key, we are looking at a mutation next. It's
+ // either sooner in the iteration or directly shadowing the underlying
+ // committed value in leveldb.
+ is_mutation_ = db_iter_->key().compare(mutations_iter_->first) >= 0;
+ }
+ if (is_mutation_) {
+ current_ = *mutations_iter_;
+ } else {
+ current_ = {db_iter_->key().ToString(), db_iter_->value().ToString()};
+ }
+ }
+}
+
+void LevelDbTransaction::Iterator::Seek(const std::string& key) {
+ db_iter_->Seek(key);
+ FIREBASE_ASSERT_MESSAGE(db_iter_->status().ok(),
+ "leveldb iterator reported an error: %s",
+ db_iter_->status().ToString().c_str());
+ for (; db_iter_->Valid() && IsDeleted(db_iter_->key()); db_iter_->Next()) {
+ }
+ FIREBASE_ASSERT_MESSAGE(db_iter_->status().ok(),
+ "leveldb iterator reported an error: %s",
+ db_iter_->status().ToString().c_str());
+ mutations_iter_ = txn_->mutations_.lower_bound(key);
+ UpdateCurrent();
+ last_version_ = txn_->version_;
+}
+
+absl::string_view LevelDbTransaction::Iterator::key() {
+ FIREBASE_ASSERT_MESSAGE(Valid(), "key() called on invalid iterator");
+ return current_.first;
+}
+
+absl::string_view LevelDbTransaction::Iterator::value() {
+ FIREBASE_ASSERT_MESSAGE(Valid(), "value() called on invalid iterator");
+ return current_.second;
+}
+
+bool LevelDbTransaction::Iterator::IsDeleted(leveldb::Slice slice) {
+ return txn_->deletions_.find(slice.ToString()) != txn_->deletions_.end();
+}
+
+bool LevelDbTransaction::Iterator::SyncToTransaction() {
+ if (last_version_ < txn_->version_) {
+ // Intentionally copying here since Seek() may update current_. We need the
+ // copy to do the comparison below.
+ const std::string current_key = current_.first;
+ Seek(current_key);
+ // If we advanced, we don't need to advance again.
+ return is_valid_ && current_.first > current_key;
+ } else {
+ return false;
+ }
+}
+
+void LevelDbTransaction::Iterator::AdvanceLDB() {
+ do {
+ db_iter_->Next();
+ } while (db_iter_->Valid() && IsDeleted(db_iter_->key()));
+ FIREBASE_ASSERT_MESSAGE(db_iter_->status().ok(),
+ "leveldb iterator reported an error: %s",
+ db_iter_->status().ToString().c_str());
+}
+
+void LevelDbTransaction::Iterator::Next() {
+ FIREBASE_ASSERT_MESSAGE(Valid(), "Next() called on invalid iterator");
+ bool advanced = SyncToTransaction();
+ if (!advanced) {
+ if (is_mutation_) {
+ // A mutation might be shadowing leveldb. If so, advance both.
+ if (db_iter_->Valid() && db_iter_->key() == mutations_iter_->first) {
+ AdvanceLDB();
+ }
+ ++mutations_iter_;
+ } else {
+ AdvanceLDB();
+ }
+ UpdateCurrent();
+ }
+}
+
+bool LevelDbTransaction::Iterator::Valid() {
+ return is_valid_;
+}
+
+LevelDbTransaction::LevelDbTransaction(DB* db,
+ absl::string_view label,
+ const ReadOptions& read_options,
+ const WriteOptions& write_options)
+ : db_(db),
+ mutations_(),
+ deletions_(),
+ read_options_(read_options),
+ write_options_(write_options),
+ version_(0),
+ label_(std::string{label}) {
+}
+
+const ReadOptions& LevelDbTransaction::DefaultReadOptions() {
+ static ReadOptions options = ([]() {
+ ReadOptions read_options;
+ read_options.verify_checksums = true;
+ return read_options;
+ })();
+ return options;
+}
+
+const WriteOptions& LevelDbTransaction::DefaultWriteOptions() {
+ static WriteOptions options;
+ return options;
+}
+
+void LevelDbTransaction::Put(const absl::string_view& key,
+ const absl::string_view& value) {
+ std::string key_string(key);
+ std::string value_string(value);
+ mutations_[key_string] = value_string;
+ deletions_.erase(key_string);
+ version_++;
+}
+
+std::unique_ptr<LevelDbTransaction::Iterator>
+LevelDbTransaction::NewIterator() {
+ return absl::make_unique<LevelDbTransaction::Iterator>(this);
+}
+
+Status LevelDbTransaction::Get(const absl::string_view& key,
+ std::string* value) {
+ std::string key_string(key);
+ if (deletions_.find(key_string) != deletions_.end()) {
+ return Status::NotFound(key_string + " is not present in the transaction");
+ } else {
+ Mutations::iterator iter(mutations_.find(key_string));
+ if (iter != mutations_.end()) {
+ *value = iter->second;
+ return Status::OK();
+ } else {
+ return db_->Get(read_options_, key_string, value);
+ }
+ }
+}
+
+void LevelDbTransaction::Delete(const absl::string_view& key) {
+ std::string to_delete(key);
+ deletions_.insert(to_delete);
+ mutations_.erase(to_delete);
+ version_++;
+}
+
+void LevelDbTransaction::Commit() {
+ WriteBatch batch;
+ for (auto it = deletions_.begin(); it != deletions_.end(); it++) {
+ batch.Delete(*it);
+ }
+
+ for (auto it = mutations_.begin(); it != mutations_.end(); it++) {
+ batch.Put(it->first, it->second);
+ }
+
+ if (util::LogGetLevel() <= util::kLogLevelDebug) {
+ util::LogDebug("Committing transaction: %s", ToString().c_str());
+ }
+
+ Status status = db_->Write(write_options_, &batch);
+ FIREBASE_ASSERT_MESSAGE(status.ok(),
+ "Failed to commit transaction:\n%s\n Failed: %s",
+ ToString().c_str(), status.ToString().c_str());
+}
+
+std::string LevelDbTransaction::ToString() {
+ std::string dest("<LevelDbTransaction " + label_ + ": ");
+ int64_t changes = deletions_.size() + mutations_.size();
+ int64_t bytes = 0; // accumulator for size of individual mutations.
+ dest += std::to_string(changes) + " changes ";
+ std::string items; // accumulator for individual changes.
+ for (auto it = deletions_.begin(); it != deletions_.end(); it++) {
+ items += "\n - Delete " + Describe(*it);
+ }
+ for (auto it = mutations_.begin(); it != mutations_.end(); it++) {
+ int64_t change_bytes = it->second.length();
+ bytes += change_bytes;
+ items += "\n - Put " + Describe(it->first) + " (" +
+ std::to_string(change_bytes) + " bytes)";
+ }
+ dest += "(" + std::to_string(bytes) + " bytes):" + items + ">";
+ return dest;
+}
+
+} // namespace local
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/src/firebase/firestore/local/leveldb_transaction.h b/Firestore/core/src/firebase/firestore/local/leveldb_transaction.h
new file mode 100644
index 0000000..a6ddce2
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/local/leveldb_transaction.h
@@ -0,0 +1,208 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_LOCAL_LEVELDB_TRANSACTION_H_
+#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_LOCAL_LEVELDB_TRANSACTION_H_
+
+#include <cstdint>
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+#include <utility>
+
+#include "absl/strings/string_view.h"
+#include "leveldb/db.h"
+
+#if __OBJC__
+#import <Protobuf/GPBProtocolBuffers.h>
+#endif
+
+namespace firebase {
+namespace firestore {
+namespace local {
+
+/**
+ * LevelDBTransaction tracks pending changes to entries in leveldb, including
+ * deletions. It also provides an Iterator to traverse a merged view of pending
+ * changes and committed values.
+ */
+class LevelDbTransaction {
+ using Deletions = std::set<std::string>;
+ using Mutations = std::map<std::string, std::string>;
+
+ public:
+ /**
+ * Iterator iterates over a merged view of pending changes from the
+ * transaction and any unchanged values in the underlying leveldb instance.
+ */
+ class Iterator {
+ public:
+ explicit Iterator(LevelDbTransaction* txn);
+
+ /**
+ * Returns true if this iterator points to an entry
+ */
+ bool Valid();
+
+ /**
+ * Seeks this iterator to the first key equal to or greater than the given
+ * key
+ */
+ void Seek(const std::string& key);
+
+ /**
+ * Advances the iterator to the next entry
+ */
+ void Next();
+
+ /**
+ * Returns the key of the current entry
+ */
+ absl::string_view key();
+
+ /**
+ * Returns the value of the current entry
+ */
+ absl::string_view value();
+
+ private:
+ /**
+ * Advances to the next non-deleted key in leveldb.
+ */
+ void AdvanceLDB();
+
+ /**
+ * Returns true if the given slice matches a key present in the deletions_
+ * set.
+ */
+ bool IsDeleted(leveldb::Slice slice);
+
+ /**
+ * Syncs with the underlying transaction. If the transaction has been
+ * updated, the mutation iterator may need to be reset. Returns true if this
+ * resulted in moving to a new underlying entry (i.e. the entry represented
+ * by current_ was deleted).
+ */
+ bool SyncToTransaction();
+
+ /**
+ * Given the current state of the internal iterators, set is_valid_,
+ * is_mutation_, and current_.
+ */
+ void UpdateCurrent();
+
+ std::unique_ptr<leveldb::Iterator> db_iter_;
+
+ // The last observed version of the underlying transaction
+ int32_t last_version_;
+ // The underlying transaction.
+ LevelDbTransaction* txn_;
+ Mutations::iterator mutations_iter_;
+ // We save the current key and value so that once an iterator is Valid(), it
+ // remains so at least until the next call to Seek() or Next(), even if the
+ // underlying data is deleted.
+ std::pair<std::string, std::string> current_;
+ // True if current_ represents an entry in the mutations_ map, rather than
+ // committed data.
+ bool is_mutation_;
+ // True if the iterator pointed to a valid entry the last time Next() or
+ // Seek() was called.
+ bool is_valid_;
+ };
+
+ explicit LevelDbTransaction(
+ leveldb::DB* db,
+ absl::string_view label,
+ const leveldb::ReadOptions& read_options = DefaultReadOptions(),
+ const leveldb::WriteOptions& write_options = DefaultWriteOptions());
+
+ LevelDbTransaction(const LevelDbTransaction& other) = delete;
+
+ LevelDbTransaction& operator=(const LevelDbTransaction& other) = delete;
+
+ /**
+ * Returns a default set of ReadOptions
+ */
+ static const leveldb::ReadOptions& DefaultReadOptions();
+
+ /**
+ * Returns a default set of WriteOptions
+ */
+ static const leveldb::WriteOptions& DefaultWriteOptions();
+
+ /**
+ * Remove the database entry (if any) for "key". It is not an error if "key"
+ * did not exist in the database.
+ */
+ void Delete(const absl::string_view& key);
+
+#if __OBJC__
+ /**
+ * Schedules the row identified by `key` to be set to the given protocol
+ * buffer message when this transaction commits.
+ */
+ void Put(const absl::string_view& key, GPBMessage* message) {
+ NSData* data = [message data];
+ std::string key_string(key);
+ mutations_[key_string] = std::string((const char*)data.bytes, data.length);
+ version_++;
+ }
+#endif
+
+ /**
+ * Schedules the row identified by `key` to be set to `value` when this
+ * transaction commits.
+ */
+ void Put(const absl::string_view& key, const absl::string_view& value);
+
+ /**
+ * Sets the contents of `value` to the latest known value for the given key,
+ * including any pending mutations and `Status::OK` is returned. If the key
+ * doesn't exist in leveldb, or it is scheduled for deletion in this
+ * transaction, `Status::NotFound` is returned.
+ */
+ leveldb::Status Get(const absl::string_view& key, std::string* value);
+
+ /**
+ * Returns a new Iterator over the pending changes in this transaction, merged
+ * with the existing values already in leveldb.
+ */
+ std::unique_ptr<Iterator> NewIterator();
+
+ /**
+ * Commits the transaction. All pending changes are written. The transaction
+ * should not be used after calling this method.
+ */
+ void Commit();
+
+ std::string ToString();
+
+ private:
+ leveldb::DB* db_;
+ Mutations mutations_;
+ Deletions deletions_;
+ leveldb::ReadOptions read_options_;
+ leveldb::WriteOptions write_options_;
+ int32_t version_;
+ std::string label_;
+};
+
+} // namespace local
+} // namespace firestore
+} // namespace firebase
+
+#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_LOCAL_LEVELDB_TRANSACTION_H_
diff --git a/Firestore/core/src/firebase/firestore/local/leveldb_util.h b/Firestore/core/src/firebase/firestore/local/leveldb_util.h
new file mode 100644
index 0000000..5738d65
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/local/leveldb_util.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_LOCAL_LEVELDB_UTIL_H_
+#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_LOCAL_LEVELDB_UTIL_H_
+
+#include <string>
+
+#include "absl/strings/string_view.h"
+#include "leveldb/slice.h"
+
+namespace firebase {
+namespace firestore {
+namespace local {
+
+/** Creates a Slice from a string_view. */
+inline leveldb::Slice MakeSlice(absl::string_view view) {
+ return leveldb::Slice{view.data(), view.size()};
+}
+
+/** Creates a string_view from a Slice. */
+inline absl::string_view MakeStringView(leveldb::Slice slice) {
+ return absl::string_view{slice.data(), slice.size()};
+}
+
+} // namespace local
+} // namespace firestore
+} // namespace firebase
+
+#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_LOCAL_LEVELDB_UTIL_H_
diff --git a/Firestore/core/src/firebase/firestore/model/CMakeLists.txt b/Firestore/core/src/firebase/firestore/model/CMakeLists.txt
new file mode 100644
index 0000000..02affdb
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/model/CMakeLists.txt
@@ -0,0 +1,47 @@
+# Copyright 2018 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.
+
+cc_library(
+ firebase_firestore_model
+ SOURCES
+ base_path.h
+ database_id.cc
+ database_id.h
+ document.cc
+ document.h
+ document_key.cc
+ document_key.h
+ field_mask.h
+ field_path.cc
+ field_path.h
+ field_transform.h
+ field_value.cc
+ field_value.h
+ maybe_document.cc
+ maybe_document.h
+ no_document.cc
+ no_document.h
+ precondition.cc
+ precondition.h
+ resource_path.cc
+ resource_path.h
+ snapshot_version.cc
+ snapshot_version.h
+ transform_operations.h
+ types.h
+ DEPENDS
+ absl_strings
+ firebase_firestore_util
+ firebase_firestore_types
+)
diff --git a/Firestore/core/src/firebase/firestore/model/base_path.h b/Firestore/core/src/firebase/firestore/model/base_path.h
new file mode 100644
index 0000000..bc1f89d
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/model/base_path.h
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_BASE_PATH_H_
+#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_BASE_PATH_H_
+
+#include <algorithm>
+#include <cctype>
+#include <initializer_list>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "Firestore/core/src/firebase/firestore/util/firebase_assert.h"
+
+namespace firebase {
+namespace firestore {
+namespace model {
+namespace impl {
+
+/**
+ * BasePath represents a path sequence in the Firestore database. It is composed
+ * of an ordered sequence of string segments.
+ *
+ * BasePath is reassignable and movable. Apart from those, all other mutating
+ * operations return new independent instances.
+ *
+ * ## Subclassing Notes
+ *
+ * BasePath is strictly meant as a base class for concrete implementations. It
+ * doesn't contain a single virtual method, can't be instantiated, and should
+ * never be used in any polymorphic way. BasePath is templated to allow static
+ * factory methods to return objects of the derived class (the expected
+ * inheritance would use CRTP: struct Derived : BasePath<Derived>).
+ */
+template <typename T>
+class BasePath {
+ protected:
+ using SegmentsT = std::vector<std::string>;
+
+ public:
+ using const_iterator = SegmentsT::const_iterator;
+
+ /** Returns i-th segment of the path. */
+ const std::string& operator[](const size_t i) const {
+ FIREBASE_ASSERT_MESSAGE(i < segments_.size(), "index %u out of range", i);
+ return segments_[i];
+ }
+
+ /** Returns the first segment of the path. */
+ const std::string& first_segment() const {
+ FIREBASE_ASSERT_MESSAGE(!empty(),
+ "Cannot call first_segment on empty path");
+ return segments_[0];
+ }
+ /** Returns the last segment of the path. */
+ const std::string& last_segment() const {
+ FIREBASE_ASSERT_MESSAGE(!empty(), "Cannot call last_segment on empty path");
+ return segments_[size() - 1];
+ }
+
+ size_t size() const {
+ return segments_.size();
+ }
+ bool empty() const {
+ return segments_.empty();
+ }
+
+ const_iterator begin() const {
+ return segments_.begin();
+ }
+ const_iterator end() const {
+ return segments_.end();
+ }
+
+ /**
+ * Returns a new path which is the result of concatenating this path with an
+ * additional segment.
+ */
+ T Append(const std::string& segment) const {
+ auto appended = segments_;
+ appended.push_back(segment);
+ return T{std::move(appended)};
+ }
+ T Append(std::string&& segment) const {
+ auto appended = segments_;
+ appended.push_back(std::move(segment));
+ return T{std::move(appended)};
+ }
+
+ /**
+ * Returns a new path which is the result of concatenating this path with an
+ * another path.
+ */
+ T Append(const T& path) const {
+ auto appended = segments_;
+ appended.insert(appended.end(), path.begin(), path.end());
+ return T{std::move(appended)};
+ }
+
+ /**
+ * Returns a new path which is the result of omitting the first n segments of
+ * this path.
+ */
+ T PopFirst(const size_t n = 1) const {
+ FIREBASE_ASSERT_MESSAGE(n <= size(),
+ "Cannot call PopFirst(%u) on path of length %u", n,
+ size());
+ return T{begin() + n, end()};
+ }
+
+ /**
+ * Returns a new path which is the result of omitting the last segment of
+ * this path.
+ */
+ T PopLast() const {
+ FIREBASE_ASSERT_MESSAGE(!empty(), "Cannot call PopLast() on empty path");
+ return T{begin(), end() - 1};
+ }
+
+ /**
+ * Returns true if this path is a prefix of the given path.
+ *
+ * Empty path is a prefix of any path. Any path is a prefix of itself.
+ */
+ bool IsPrefixOf(const T& rhs) const {
+ return size() <= rhs.size() && std::equal(begin(), end(), rhs.begin());
+ }
+
+ bool operator==(const BasePath& rhs) const {
+ return segments_ == rhs.segments_;
+ }
+ bool operator!=(const BasePath& rhs) const {
+ return segments_ != rhs.segments_;
+ }
+ bool operator<(const BasePath& rhs) const {
+ return segments_ < rhs.segments_;
+ }
+ bool operator>(const BasePath& rhs) const {
+ return segments_ > rhs.segments_;
+ }
+ bool operator<=(const BasePath& rhs) const {
+ return segments_ <= rhs.segments_;
+ }
+ bool operator>=(const BasePath& rhs) const {
+ return segments_ >= rhs.segments_;
+ }
+
+#if defined(__OBJC__)
+ // For Objective-C++ hash; to be removed after migration.
+ // Do NOT use in C++ code.
+ NSUInteger Hash() const {
+ std::hash<std::string> hash_fn;
+ NSUInteger hash_result = 0;
+ for (const std::string& segment : segments_) {
+ hash_result = hash_result * 31u + hash_fn(segment);
+ }
+ return hash_result;
+ }
+#endif // defined(__OBJC__)
+
+ protected:
+ BasePath() = default;
+ template <typename IterT>
+ BasePath(const IterT begin, const IterT end) : segments_{begin, end} {
+ }
+ BasePath(std::initializer_list<std::string> list) : segments_{list} {
+ }
+ explicit BasePath(SegmentsT&& segments) : segments_{std::move(segments)} {
+ }
+
+ private:
+ SegmentsT segments_;
+};
+
+} // namespace impl
+} // namespace model
+} // namespace firestore
+} // namespace firebase
+
+#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_BASE_PATH_H_
diff --git a/Firestore/core/src/firebase/firestore/model/database_id.cc b/Firestore/core/src/firebase/firestore/model/database_id.cc
new file mode 100644
index 0000000..a756ea7
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/model/database_id.cc
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#include "Firestore/core/src/firebase/firestore/model/database_id.h"
+
+#include "Firestore/core/src/firebase/firestore/util/firebase_assert.h"
+
+namespace firebase {
+namespace firestore {
+namespace model {
+
+constexpr const char* DatabaseId::kDefault;
+
+DatabaseId::DatabaseId(const absl::string_view project_id,
+ const absl::string_view database_id)
+ : project_id_(project_id), database_id_(database_id) {
+ FIREBASE_ASSERT(!project_id.empty());
+ FIREBASE_ASSERT(!database_id.empty());
+}
+
+} // namespace model
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/src/firebase/firestore/model/database_id.h b/Firestore/core/src/firebase/firestore/model/database_id.h
new file mode 100644
index 0000000..0c0e0ec
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/model/database_id.h
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_DATABASE_ID_H_
+#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_DATABASE_ID_H_
+
+#include <cstdint>
+#include <string>
+
+#include "absl/strings/string_view.h"
+
+namespace firebase {
+namespace firestore {
+namespace model {
+
+/** A DatabaseId represents a particular database in the Firestore. */
+class DatabaseId {
+ public:
+ /** The default name for "unset" database ID in resource names. */
+ static constexpr const char* kDefault = "(default)";
+
+#if defined(__OBJC__)
+ // For objective-c++ initialization; to be removed after migration.
+ // Do NOT use in C++ code.
+ DatabaseId() = default;
+#endif // defined(__OBJC__)
+
+ /**
+ * Creates and returns a new DatabaseId.
+ *
+ * @param project_id The project for the database.
+ * @param database_id The database in the project to use.
+ */
+ DatabaseId(absl::string_view project_id, absl::string_view database_id);
+
+ const std::string& project_id() const {
+ return project_id_;
+ }
+
+ const std::string& database_id() const {
+ return database_id_;
+ }
+
+ /** Whether this is the default database of the project. */
+ bool IsDefaultDatabase() const {
+ return database_id_ == kDefault;
+ }
+
+#if defined(__OBJC__)
+ // For objective-c++ hash; to be removed after migration.
+ // Do NOT use in C++ code.
+ NSUInteger Hash() const {
+ std::hash<std::string> hash_fn;
+ return hash_fn(project_id_) * 31u + hash_fn(database_id_);
+ }
+#endif // defined(__OBJC__)
+
+ friend bool operator<(const DatabaseId& lhs, const DatabaseId& rhs);
+
+ private:
+ std::string project_id_;
+ std::string database_id_;
+};
+
+/** Compares against another DatabaseId. */
+inline bool operator<(const DatabaseId& lhs, const DatabaseId& rhs) {
+ return lhs.project_id_ < rhs.project_id_ ||
+ (lhs.project_id_ == rhs.project_id_ &&
+ lhs.database_id_ < rhs.database_id_);
+}
+
+inline bool operator>(const DatabaseId& lhs, const DatabaseId& rhs) {
+ return rhs < lhs;
+}
+
+inline bool operator>=(const DatabaseId& lhs, const DatabaseId& rhs) {
+ return !(lhs < rhs);
+}
+
+inline bool operator<=(const DatabaseId& lhs, const DatabaseId& rhs) {
+ return !(lhs > rhs);
+}
+
+inline bool operator!=(const DatabaseId& lhs, const DatabaseId& rhs) {
+ return lhs < rhs || lhs > rhs;
+}
+
+inline bool operator==(const DatabaseId& lhs, const DatabaseId& rhs) {
+ return !(lhs != rhs);
+}
+
+} // namespace model
+} // namespace firestore
+} // namespace firebase
+
+#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_DATABASE_ID_H_
diff --git a/Firestore/core/src/firebase/firestore/model/document.cc b/Firestore/core/src/firebase/firestore/model/document.cc
new file mode 100644
index 0000000..ae59d15
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/model/document.cc
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#include "Firestore/core/src/firebase/firestore/model/document.h"
+
+#include <utility>
+
+#include "Firestore/core/src/firebase/firestore/util/firebase_assert.h"
+
+namespace firebase {
+namespace firestore {
+namespace model {
+
+Document::Document(FieldValue&& data,
+ DocumentKey key,
+ SnapshotVersion version,
+ bool has_local_mutations)
+ : MaybeDocument(std::move(key), std::move(version)),
+ data_(std::move(data)),
+ has_local_mutations_(has_local_mutations) {
+ set_type(Type::Document);
+ FIREBASE_ASSERT(FieldValue::Type::Object == data.type());
+}
+
+bool Document::Equals(const MaybeDocument& other) const {
+ if (other.type() != Type::Document) {
+ return false;
+ }
+ auto& other_doc = static_cast<const Document&>(other);
+ return MaybeDocument::Equals(other) &&
+ has_local_mutations_ == other_doc.has_local_mutations_ &&
+ data_ == other_doc.data_;
+}
+
+} // namespace model
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/src/firebase/firestore/model/document.h b/Firestore/core/src/firebase/firestore/model/document.h
new file mode 100644
index 0000000..50a7b90
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/model/document.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_DOCUMENT_H_
+#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_DOCUMENT_H_
+
+#include "Firestore/core/src/firebase/firestore/model/field_value.h"
+#include "Firestore/core/src/firebase/firestore/model/maybe_document.h"
+
+namespace firebase {
+namespace firestore {
+namespace model {
+
+/**
+ * Represents a document in Firestore with a key, version, data and whether the
+ * data has local mutations applied to it.
+ */
+class Document : public MaybeDocument {
+ public:
+ /**
+ * Construct a document. FieldValue must be passed by rvalue.
+ */
+ Document(FieldValue&& data,
+ DocumentKey key,
+ SnapshotVersion version,
+ bool has_local_mutations);
+
+ const FieldValue& data() const {
+ return data_;
+ }
+
+ bool has_local_mutations() const {
+ return has_local_mutations_;
+ }
+
+ protected:
+ bool Equals(const MaybeDocument& other) const override;
+
+ private:
+ FieldValue data_; // This is of type Object.
+ bool has_local_mutations_;
+};
+
+/** Compares against another Document. */
+inline bool operator==(const Document& lhs, const Document& rhs) {
+ return lhs.version() == rhs.version() && lhs.key() == rhs.key() &&
+ lhs.has_local_mutations() == rhs.has_local_mutations() &&
+ lhs.data() == rhs.data();
+}
+
+inline bool operator!=(const Document& lhs, const Document& rhs) {
+ return !(lhs == rhs);
+}
+
+} // namespace model
+} // namespace firestore
+} // namespace firebase
+
+#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_DOCUMENT_H_
diff --git a/Firestore/core/src/firebase/firestore/model/document_key.cc b/Firestore/core/src/firebase/firestore/model/document_key.cc
new file mode 100644
index 0000000..ddda4c9
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/model/document_key.cc
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+
+#include <utility>
+
+#include "Firestore/core/src/firebase/firestore/util/firebase_assert.h"
+
+namespace firebase {
+namespace firestore {
+namespace model {
+
+namespace {
+
+void AssertValidPath(const ResourcePath& path) {
+ FIREBASE_ASSERT_MESSAGE(DocumentKey::IsDocumentKey(path),
+ "invalid document key path: %s",
+ path.CanonicalString().c_str());
+}
+
+} // namespace
+
+DocumentKey::DocumentKey(const ResourcePath& path)
+ : path_{std::make_shared<ResourcePath>(path)} {
+ AssertValidPath(*path_);
+}
+
+DocumentKey::DocumentKey(ResourcePath&& path)
+ : path_{std::make_shared<ResourcePath>(std::move(path))} {
+ AssertValidPath(*path_);
+}
+
+const DocumentKey& DocumentKey::Empty() {
+ static const DocumentKey empty;
+ return empty;
+}
+
+} // namespace model
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/src/firebase/firestore/model/document_key.h b/Firestore/core/src/firebase/firestore/model/document_key.h
new file mode 100644
index 0000000..4bdc04b
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/model/document_key.h
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_DOCUMENT_KEY_H_
+#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_DOCUMENT_KEY_H_
+
+#include <initializer_list>
+#include <memory>
+#include <string>
+
+#if defined(__OBJC__)
+#import "Firestore/Source/Model/FSTDocumentKey.h"
+#endif // defined(__OBJC__)
+
+#include "Firestore/core/src/firebase/firestore/model/resource_path.h"
+#include "absl/strings/string_view.h"
+
+namespace firebase {
+namespace firestore {
+namespace model {
+
+/**
+ * DocumentKey represents the location of a document in the Firestore database.
+ */
+class DocumentKey {
+ public:
+ /** Creates a "blank" document key not associated with any document. */
+ DocumentKey() : path_{std::make_shared<ResourcePath>()} {
+ }
+
+ /** Creates a new document key containing a copy of the given path. */
+ explicit DocumentKey(const ResourcePath& path);
+
+ /** Creates a new document key, taking ownership of the given path. */
+ explicit DocumentKey(ResourcePath&& path);
+
+#if defined(__OBJC__)
+ DocumentKey(FSTDocumentKey* key) // NOLINT(runtime/explicit)
+ : path_(std::make_shared<ResourcePath>(key.path)) {
+ }
+
+ operator FSTDocumentKey*() const {
+ return [FSTDocumentKey keyWithPath:path()];
+ }
+
+ std::string ToString() const {
+ return path().CanonicalString();
+ }
+
+ NSUInteger Hash() const {
+ return std::hash<std::string>{}(ToString());
+ }
+#endif
+
+ /**
+ * Creates and returns a new document key using '/' to split the string into
+ * segments.
+ */
+ static DocumentKey FromPathString(const absl::string_view path) {
+ return DocumentKey{ResourcePath::FromString(path)};
+ }
+
+ /** Creates and returns a new document key with the given segments. */
+ static DocumentKey FromSegments(std::initializer_list<std::string> list) {
+ return DocumentKey{ResourcePath{list}};
+ }
+
+ /** Returns a shared instance of an empty document key. */
+ static const DocumentKey& Empty();
+
+ /** Returns true iff the given path is a path to a document. */
+ static bool IsDocumentKey(const ResourcePath& path) {
+ return path.size() % 2 == 0;
+ }
+
+ /** The path to the document. */
+ const ResourcePath& path() const {
+ return path_ ? *path_ : Empty().path();
+ }
+
+ private:
+ // This is an optimization to make passing DocumentKey around cheaper (it's
+ // copied often).
+ std::shared_ptr<const ResourcePath> path_;
+};
+
+inline bool operator==(const DocumentKey& lhs, const DocumentKey& rhs) {
+ return lhs.path() == rhs.path();
+}
+inline bool operator!=(const DocumentKey& lhs, const DocumentKey& rhs) {
+ return lhs.path() != rhs.path();
+}
+inline bool operator<(const DocumentKey& lhs, const DocumentKey& rhs) {
+ return lhs.path() < rhs.path();
+}
+inline bool operator<=(const DocumentKey& lhs, const DocumentKey& rhs) {
+ return lhs.path() <= rhs.path();
+}
+inline bool operator>(const DocumentKey& lhs, const DocumentKey& rhs) {
+ return lhs.path() > rhs.path();
+}
+inline bool operator>=(const DocumentKey& lhs, const DocumentKey& rhs) {
+ return lhs.path() >= rhs.path();
+}
+
+} // namespace model
+} // namespace firestore
+} // namespace firebase
+
+#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_DOCUMENT_KEY_H_
diff --git a/Firestore/core/src/firebase/firestore/model/field_mask.h b/Firestore/core/src/firebase/firestore/model/field_mask.h
new file mode 100644
index 0000000..b895ab3
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/model/field_mask.h
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_FIELD_MASK_H_
+#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_FIELD_MASK_H_
+
+#include <initializer_list>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "Firestore/core/src/firebase/firestore/model/field_path.h"
+
+namespace firebase {
+namespace firestore {
+namespace model {
+
+/**
+ * Provides a set of fields that can be used to partially patch a document.
+ * FieldMask is used in conjunction with FieldValue of Object type.
+ *
+ * Examples:
+ * foo - Overwrites foo entirely with the provided value. If foo is not
+ * present in the companion FieldValue, the field is deleted.
+ * foo.bar - Overwrites only the field bar of the object foo. If foo is not an
+ * object, foo is replaced with an object containing bar.
+ */
+class FieldMask {
+ public:
+ using const_iterator = std::vector<FieldPath>::const_iterator;
+
+ FieldMask(std::initializer_list<FieldPath> list) : fields_{list} {
+ }
+ explicit FieldMask(std::vector<FieldPath> fields)
+ : fields_{std::move(fields)} {
+ }
+
+ const_iterator begin() const {
+ return fields_.begin();
+ }
+ const_iterator end() const {
+ return fields_.end();
+ }
+
+ std::string ToString() const {
+ // Ideally, one should use a string builder. Since this is only non-critical
+ // code for logging and debugging, the logic is kept simple here.
+ std::string result("{ ");
+ for (const FieldPath& field : fields_) {
+ result += field.CanonicalString() + " ";
+ }
+ return result + "}";
+ }
+
+#if defined(__OBJC__)
+ FieldMask() {
+ }
+
+ NSUInteger Hash() const {
+ NSUInteger hashResult = 0;
+ for (const FieldPath& field : fields_) {
+ hashResult = hashResult * 31u + field.Hash();
+ }
+ return hashResult;
+ }
+#endif
+
+ friend bool operator==(const FieldMask& lhs, const FieldMask& rhs);
+
+ private:
+ std::vector<FieldPath> fields_;
+};
+
+inline bool operator==(const FieldMask& lhs, const FieldMask& rhs) {
+ return lhs.fields_ == rhs.fields_;
+}
+
+} // namespace model
+} // namespace firestore
+} // namespace firebase
+
+#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_FIELD_MASK_H_
diff --git a/Firestore/core/src/firebase/firestore/model/field_path.cc b/Firestore/core/src/firebase/firestore/model/field_path.cc
new file mode 100644
index 0000000..bc0e97c
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/model/field_path.cc
@@ -0,0 +1,173 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#include "Firestore/core/src/firebase/firestore/model/field_path.h"
+
+#include <algorithm>
+#include <utility>
+
+#include "Firestore/core/src/firebase/firestore/util/firebase_assert.h"
+#include "absl/strings/str_join.h"
+#include "absl/strings/str_replace.h"
+#include "absl/strings/str_split.h"
+
+namespace firebase {
+namespace firestore {
+namespace model {
+
+namespace {
+
+/**
+ * True if the string could be used as a segment in a field path without
+ * escaping. Valid identifies follow the regex [a-zA-Z_][a-zA-Z0-9_]*
+ */
+bool IsValidIdentifier(const std::string& segment) {
+ if (segment.empty()) {
+ return false;
+ }
+
+ // Note: strictly speaking, only digits are guaranteed by the Standard to
+ // be a contiguous range, while alphabetic characters may have gaps. Ignoring
+ // this peculiarity, because it doesn't affect the platforms that Firestore
+ // supports.
+ const unsigned char first = segment.front();
+ if (first != '_' && (first < 'a' || first > 'z') &&
+ (first < 'A' || first > 'Z')) {
+ return false;
+ }
+ for (size_t i = 1; i != segment.size(); ++i) {
+ const unsigned char c = segment[i];
+ if (c != '_' && (c < 'a' || c > 'z') && (c < 'A' || c > 'Z') &&
+ (c < '0' || c > '9')) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+} // namespace
+
+FieldPath FieldPath::FromServerFormat(const absl::string_view path) {
+ // TODO(b/37244157): Once we move to v1beta1, we should make this more
+ // strict. Right now, it allows non-identifier path components, even if they
+ // aren't escaped. Technically, this will mangle paths with backticks in
+ // them used in v1alpha1, but that's fine.
+
+ SegmentsT segments;
+ std::string segment;
+ segment.reserve(path.size());
+
+ // string_view doesn't have a c_str() method, because it might not be
+ // null-terminated. Assertions expect C strings, so construct std::string on
+ // the fly, so that c_str() might be called on it.
+ const auto to_string = [](const absl::string_view view) {
+ return std::string{view.data(), view.data() + view.size()};
+ };
+ const auto finish_segment = [&segments, &segment, &path, &to_string] {
+ FIREBASE_ASSERT_MESSAGE(
+ !segment.empty(),
+ "Invalid field path (%s). Paths must not be empty, begin with "
+ "'.', end with '.', or contain '..'",
+ to_string(path).c_str());
+ // Move operation will clear segment, but capacity will remain the same
+ // (not, strictly speaking, required by the standard, but true in practice).
+ segments.push_back(std::move(segment));
+ };
+
+ // Inside backticks, dots are treated literally.
+ bool inside_backticks = false;
+ size_t i = 0;
+ while (i < path.size()) {
+ const char c = path[i];
+ // std::string (and string_view) may contain embedded nulls. For full
+ // compatibility with Objective C behavior, finish upon encountering the
+ // first terminating null.
+ if (c == '\0') {
+ break;
+ }
+
+ switch (c) {
+ case '.':
+ if (!inside_backticks) {
+ finish_segment();
+ } else {
+ segment += c;
+ }
+ break;
+
+ case '`':
+ inside_backticks = !inside_backticks;
+ break;
+
+ case '\\':
+ // TODO(b/37244157): Make this a user-facing exception once we
+ // finalize field escaping.
+ FIREBASE_ASSERT_MESSAGE(i + 1 != path.size(),
+ "Trailing escape characters not allowed in %s",
+ to_string(path).c_str());
+ ++i;
+ segment += path[i];
+ break;
+
+ default:
+ segment += c;
+ break;
+ }
+ ++i;
+ }
+ finish_segment();
+
+ FIREBASE_ASSERT_MESSAGE(!inside_backticks, "Unterminated ` in path %s",
+ to_string(path).c_str());
+
+ return FieldPath{std::move(segments)};
+}
+
+const FieldPath& FieldPath::EmptyPath() {
+ static const FieldPath empty_path;
+ return empty_path;
+}
+
+const FieldPath& FieldPath::KeyFieldPath() {
+ static const FieldPath key_field_path{FieldPath::kDocumentKeyPath};
+ return key_field_path;
+}
+
+bool FieldPath::IsKeyFieldPath() const {
+ return size() == 1 && first_segment() == FieldPath::kDocumentKeyPath;
+}
+
+std::string FieldPath::CanonicalString() const {
+ const auto escaped_segment = [](const std::string& segment) {
+ auto escaped = absl::StrReplaceAll(segment, {{"\\", "\\\\"}, {"`", "\\`"}});
+ const bool needs_escaping = !IsValidIdentifier(escaped);
+ if (needs_escaping) {
+ escaped.insert(escaped.begin(), '`');
+ escaped.push_back('`');
+ }
+ return escaped;
+ };
+ return absl::StrJoin(
+ begin(), end(), ".",
+ [escaped_segment](std::string* out, const std::string& segment) {
+ out->append(escaped_segment(segment));
+ });
+}
+
+} // namespace model
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/src/firebase/firestore/model/field_path.h b/Firestore/core/src/firebase/firestore/model/field_path.h
new file mode 100644
index 0000000..ce3527d
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/model/field_path.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_FIELD_PATH_H_
+#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_FIELD_PATH_H_
+
+#include <initializer_list>
+#include <string>
+#include <utility>
+
+#include "Firestore/core/src/firebase/firestore/model/base_path.h"
+#include "absl/strings/string_view.h"
+
+namespace firebase {
+namespace firestore {
+namespace model {
+
+/**
+ * A dot-separated path for navigating sub-objects within a document.
+ *
+ * Immutable; all instances are fully independent.
+ */
+class FieldPath : public impl::BasePath<FieldPath> {
+ public:
+ /** The field path string that represents the document's key. */
+ static constexpr const char* kDocumentKeyPath = "__name__";
+
+ // Note: Xcode 8.2 requires explicit specification of the constructor.
+ FieldPath() : impl::BasePath<FieldPath>() {
+ }
+
+ /** Constructs the path from segments. */
+ template <typename IterT>
+ FieldPath(const IterT begin, const IterT end) : BasePath{begin, end} {
+ }
+ FieldPath(std::initializer_list<std::string> list) : BasePath{list} {
+ }
+ explicit FieldPath(SegmentsT&& segments) : BasePath{std::move(segments)} {
+ }
+
+ /**
+ * Creates and returns a new path from the server formatted field-path string,
+ * where path segments are separated by a dot "." and optionally encoded using
+ * backticks.
+ */
+ static FieldPath FromServerFormat(absl::string_view path);
+ /** Returns a field path that represents an empty path. */
+ static const FieldPath& EmptyPath();
+ /** Returns a field path that represents a document key. */
+ static const FieldPath& KeyFieldPath();
+
+ /** Returns a standardized string representation of this path. */
+ std::string CanonicalString() const;
+ /** True if this FieldPath represents a document key. */
+ bool IsKeyFieldPath() const;
+
+ bool operator==(const FieldPath& rhs) const {
+ return BasePath::operator==(rhs);
+ }
+ bool operator!=(const FieldPath& rhs) const {
+ return BasePath::operator!=(rhs);
+ }
+ bool operator<(const FieldPath& rhs) const {
+ return BasePath::operator<(rhs);
+ }
+ bool operator>(const FieldPath& rhs) const {
+ return BasePath::operator>(rhs);
+ }
+ bool operator<=(const FieldPath& rhs) const {
+ return BasePath::operator<=(rhs);
+ }
+ bool operator>=(const FieldPath& rhs) const {
+ return BasePath::operator>=(rhs);
+ }
+};
+
+} // namespace model
+} // namespace firestore
+} // namespace firebase
+
+#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_FIELD_PATH_H_
diff --git a/Firestore/core/src/firebase/firestore/model/field_transform.h b/Firestore/core/src/firebase/firestore/model/field_transform.h
new file mode 100644
index 0000000..a1dd96c
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/model/field_transform.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_FIELD_TRANSFORM_H_
+#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_FIELD_TRANSFORM_H_
+
+#include <memory>
+#include <utility>
+
+#include "Firestore/core/src/firebase/firestore/model/field_path.h"
+#include "Firestore/core/src/firebase/firestore/model/transform_operations.h"
+
+namespace firebase {
+namespace firestore {
+namespace model {
+
+/** A field path and the TransformOperation to perform upon it. */
+class FieldTransform {
+ public:
+ FieldTransform(FieldPath path,
+ std::unique_ptr<TransformOperation> transformation) noexcept
+ : path_{std::move(path)}, transformation_{std::move(transformation)} {
+ }
+
+ const FieldPath& path() const {
+ return path_;
+ }
+
+ const TransformOperation& transformation() const {
+ return *transformation_.get();
+ }
+
+ bool operator==(const FieldTransform& other) const {
+ return path_ == other.path_ && *transformation_ == *other.transformation_;
+ }
+
+#if defined(__OBJC__)
+ // For Objective-C++ hash; to be removed after migration.
+ // Do NOT use in C++ code.
+ NSUInteger Hash() const {
+ NSUInteger hash = path_.Hash();
+ hash = hash * 31 + transformation_->Hash();
+ return hash;
+ }
+#endif // defined(__OBJC__)
+
+ private:
+ FieldPath path_;
+ // Shared by copies of the same FieldTransform.
+ std::shared_ptr<const TransformOperation> transformation_;
+};
+
+} // namespace model
+} // namespace firestore
+} // namespace firebase
+
+#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_FIELD_TRANSFORM_H_
diff --git a/Firestore/core/src/firebase/firestore/model/field_value.cc b/Firestore/core/src/firebase/firestore/model/field_value.cc
new file mode 100644
index 0000000..b0519f7
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/model/field_value.cc
@@ -0,0 +1,427 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#include "Firestore/core/src/firebase/firestore/model/field_value.h"
+
+#include <algorithm>
+#include <cmath>
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "Firestore/core/src/firebase/firestore/util/comparison.h"
+#include "Firestore/core/src/firebase/firestore/util/firebase_assert.h"
+
+using firebase::firestore::util::Comparator;
+
+namespace firebase {
+namespace firestore {
+namespace model {
+
+using Type = FieldValue::Type;
+using firebase::firestore::util::ComparisonResult;
+
+namespace {
+/**
+ * This deviates from the other platforms that define TypeOrder. Since
+ * we already define Type for union types, we use it together with this
+ * function to achieve the equivalent order of types i.e.
+ * i) if two types are comparable, then they are of equal order;
+ * ii) otherwise, their order is the same as the order of their Type.
+ */
+bool Comparable(Type lhs, Type rhs) {
+ switch (lhs) {
+ case Type::Integer:
+ case Type::Double:
+ return rhs == Type::Integer || rhs == Type::Double;
+ case Type::Timestamp:
+ case Type::ServerTimestamp:
+ return rhs == Type::Timestamp || rhs == Type::ServerTimestamp;
+ default:
+ return lhs == rhs;
+ }
+}
+
+} // namespace
+
+FieldValue::FieldValue(const FieldValue& value) {
+ *this = value;
+}
+
+FieldValue::FieldValue(FieldValue&& value) {
+ *this = std::move(value);
+}
+
+FieldValue::~FieldValue() {
+ SwitchTo(Type::Null);
+}
+
+FieldValue& FieldValue::operator=(const FieldValue& value) {
+ SwitchTo(value.tag_);
+ switch (tag_) {
+ case Type::Null:
+ break;
+ case Type::Boolean:
+ boolean_value_ = value.boolean_value_;
+ break;
+ case Type::Integer:
+ integer_value_ = value.integer_value_;
+ break;
+ case Type::Double:
+ double_value_ = value.double_value_;
+ break;
+ case Type::Timestamp:
+ timestamp_value_ = value.timestamp_value_;
+ break;
+ case Type::ServerTimestamp:
+ server_timestamp_value_ = value.server_timestamp_value_;
+ break;
+ case Type::String:
+ string_value_ = value.string_value_;
+ break;
+ case Type::Blob: {
+ // copy-and-swap
+ std::vector<uint8_t> tmp = value.blob_value_;
+ std::swap(blob_value_, tmp);
+ break;
+ }
+ case Type::Reference:
+ reference_value_ = value.reference_value_;
+ break;
+ case Type::GeoPoint:
+ geo_point_value_ = value.geo_point_value_;
+ break;
+ case Type::Array: {
+ // copy-and-swap
+ std::vector<FieldValue> tmp = value.array_value_;
+ std::swap(array_value_, tmp);
+ break;
+ }
+ case Type::Object: {
+ // copy-and-swap
+ ObjectValue::Map tmp = value.object_value_.internal_value;
+ std::swap(object_value_.internal_value, tmp);
+ break;
+ }
+ default:
+ FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION(
+ false, lhs.type(), "Unsupported type %d", value.type());
+ }
+ return *this;
+}
+
+FieldValue& FieldValue::operator=(FieldValue&& value) {
+ switch (value.tag_) {
+ case Type::String:
+ SwitchTo(Type::String);
+ string_value_.swap(value.string_value_);
+ return *this;
+ case Type::Blob:
+ SwitchTo(Type::Blob);
+ std::swap(blob_value_, value.blob_value_);
+ return *this;
+ case Type::Reference:
+ SwitchTo(Type::Reference);
+ std::swap(reference_value_.reference, value.reference_value_.reference);
+ reference_value_.database_id = value.reference_value_.database_id;
+ return *this;
+ case Type::Array:
+ SwitchTo(Type::Array);
+ std::swap(array_value_, value.array_value_);
+ return *this;
+ case Type::Object:
+ SwitchTo(Type::Object);
+ std::swap(object_value_, value.object_value_);
+ return *this;
+ default:
+ // We just copy over POD union types.
+ *this = value;
+ return *this;
+ }
+}
+
+const FieldValue& FieldValue::NullValue() {
+ static const FieldValue kNullInstance;
+ return kNullInstance;
+}
+
+const FieldValue& FieldValue::TrueValue() {
+ static const FieldValue kTrueInstance(true);
+ return kTrueInstance;
+}
+
+const FieldValue& FieldValue::FalseValue() {
+ static const FieldValue kFalseInstance(false);
+ return kFalseInstance;
+}
+
+const FieldValue& FieldValue::BooleanValue(bool value) {
+ return value ? TrueValue() : FalseValue();
+}
+
+const FieldValue& FieldValue::NanValue() {
+ static const FieldValue kNanInstance = FieldValue::DoubleValue(NAN);
+ return kNanInstance;
+}
+
+FieldValue FieldValue::IntegerValue(int64_t value) {
+ FieldValue result;
+ result.SwitchTo(Type::Integer);
+ result.integer_value_ = value;
+ return result;
+}
+
+FieldValue FieldValue::DoubleValue(double value) {
+ FieldValue result;
+ result.SwitchTo(Type::Double);
+ result.double_value_ = value;
+ return result;
+}
+
+FieldValue FieldValue::TimestampValue(const Timestamp& value) {
+ FieldValue result;
+ result.SwitchTo(Type::Timestamp);
+ result.timestamp_value_ = value;
+ return result;
+}
+
+FieldValue FieldValue::ServerTimestampValue(const Timestamp& local_write_time,
+ const Timestamp& previous_value) {
+ FieldValue result;
+ result.SwitchTo(Type::ServerTimestamp);
+ result.server_timestamp_value_.local_write_time = local_write_time;
+ result.server_timestamp_value_.previous_value = previous_value;
+ result.server_timestamp_value_.has_previous_value_ = true;
+ return result;
+}
+
+FieldValue FieldValue::ServerTimestampValue(const Timestamp& local_write_time) {
+ FieldValue result;
+ result.SwitchTo(Type::ServerTimestamp);
+ result.server_timestamp_value_.local_write_time = local_write_time;
+ result.server_timestamp_value_.has_previous_value_ = false;
+ return result;
+}
+
+FieldValue FieldValue::StringValue(const char* value) {
+ std::string copy(value);
+ return StringValue(std::move(copy));
+}
+
+FieldValue FieldValue::StringValue(const std::string& value) {
+ std::string copy(value);
+ return StringValue(std::move(copy));
+}
+
+FieldValue FieldValue::StringValue(std::string&& value) {
+ FieldValue result;
+ result.SwitchTo(Type::String);
+ result.string_value_.swap(value);
+ return result;
+}
+
+FieldValue FieldValue::BlobValue(const uint8_t* source, size_t size) {
+ FieldValue result;
+ result.SwitchTo(Type::Blob);
+ std::vector<uint8_t> copy(source, source + size);
+ std::swap(result.blob_value_, copy);
+ return result;
+}
+
+// Does NOT pass ownership of database_id.
+FieldValue FieldValue::ReferenceValue(const DocumentKey& value,
+ const DatabaseId* database_id) {
+ FieldValue result;
+ result.SwitchTo(Type::Reference);
+ result.reference_value_.reference = value;
+ result.reference_value_.database_id = database_id;
+ return result;
+}
+
+// Does NOT pass ownership of database_id.
+FieldValue FieldValue::ReferenceValue(DocumentKey&& value,
+ const DatabaseId* database_id) {
+ FieldValue result;
+ result.SwitchTo(Type::Reference);
+ std::swap(result.reference_value_.reference, value);
+ result.reference_value_.database_id = database_id;
+ return result;
+}
+
+FieldValue FieldValue::GeoPointValue(const GeoPoint& value) {
+ FieldValue result;
+ result.SwitchTo(Type::GeoPoint);
+ result.geo_point_value_ = value;
+ return result;
+}
+
+FieldValue FieldValue::ArrayValue(const std::vector<FieldValue>& value) {
+ std::vector<FieldValue> copy(value);
+ return ArrayValue(std::move(copy));
+}
+
+FieldValue FieldValue::ArrayValue(std::vector<FieldValue>&& value) {
+ FieldValue result;
+ result.SwitchTo(Type::Array);
+ std::swap(result.array_value_, value);
+ return result;
+}
+
+FieldValue FieldValue::ObjectValueFromMap(const ObjectValue::Map& value) {
+ ObjectValue::Map copy(value);
+ return ObjectValueFromMap(std::move(copy));
+}
+
+FieldValue FieldValue::ObjectValueFromMap(ObjectValue::Map&& value) {
+ FieldValue result;
+ result.SwitchTo(Type::Object);
+ std::swap(result.object_value_.internal_value, value);
+ return result;
+}
+
+bool operator<(const FieldValue& lhs, const FieldValue& rhs) {
+ if (!Comparable(lhs.type(), rhs.type())) {
+ return lhs.type() < rhs.type();
+ }
+
+ switch (lhs.type()) {
+ case Type::Null:
+ return false;
+ case Type::Boolean:
+ return Comparator<bool>()(lhs.boolean_value_, rhs.boolean_value_);
+ case Type::Integer:
+ if (rhs.type() == Type::Integer) {
+ return Comparator<int64_t>()(lhs.integer_value_, rhs.integer_value_);
+ } else {
+ return util::CompareMixedNumber(rhs.double_value_,
+ lhs.integer_value_) ==
+ ComparisonResult::Descending;
+ }
+ case Type::Double:
+ if (rhs.type() == Type::Double) {
+ return Comparator<double>()(lhs.double_value_, rhs.double_value_);
+ } else {
+ return util::CompareMixedNumber(lhs.double_value_,
+ rhs.integer_value_) ==
+ ComparisonResult::Ascending;
+ }
+ case Type::Timestamp:
+ if (rhs.type() == Type::Timestamp) {
+ return lhs.timestamp_value_ < rhs.timestamp_value_;
+ } else {
+ return true;
+ }
+ case Type::ServerTimestamp:
+ if (rhs.type() == Type::ServerTimestamp) {
+ return lhs.server_timestamp_value_.local_write_time <
+ rhs.server_timestamp_value_.local_write_time;
+ } else {
+ return false;
+ }
+ case Type::String:
+ return lhs.string_value_.compare(rhs.string_value_) < 0;
+ case Type::Blob:
+ return lhs.blob_value_ < rhs.blob_value_;
+ case Type::Reference:
+ return *lhs.reference_value_.database_id <
+ *rhs.reference_value_.database_id ||
+ (*lhs.reference_value_.database_id ==
+ *rhs.reference_value_.database_id &&
+ lhs.reference_value_.reference < rhs.reference_value_.reference);
+ case Type::GeoPoint:
+ return lhs.geo_point_value_ < rhs.geo_point_value_;
+ case Type::Array:
+ return lhs.array_value_ < rhs.array_value_;
+ case Type::Object:
+ return lhs.object_value_ < rhs.object_value_;
+ default:
+ FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION(
+ false, lhs.type(), "Unsupported type %d", lhs.type());
+ // return false if assertion does not abort the program. We will say
+ // each unsupported type takes only one value thus everything is equal.
+ return false;
+ }
+}
+
+void FieldValue::SwitchTo(const Type type) {
+ if (tag_ == type) {
+ return;
+ }
+ // Not same type. Destruct old type first and then initialize new type.
+ // Must call destructor explicitly for any non-POD type.
+ switch (tag_) {
+ case Type::Timestamp:
+ timestamp_value_.~Timestamp();
+ break;
+ case Type::ServerTimestamp:
+ server_timestamp_value_.~ServerTimestamp();
+ break;
+ case Type::String:
+ string_value_.~basic_string();
+ break;
+ case Type::Blob:
+ blob_value_.~vector();
+ break;
+ case Type::Reference:
+ reference_value_.~ReferenceValue();
+ break;
+ case Type::GeoPoint:
+ geo_point_value_.~GeoPoint();
+ break;
+ case Type::Array:
+ array_value_.~vector();
+ break;
+ case Type::Object:
+ object_value_.internal_value.~map();
+ break;
+ default: {} // The other types where there is nothing to worry about.
+ }
+ tag_ = type;
+ // Must call constructor explicitly for any non-POD type to initialize.
+ switch (tag_) {
+ case Type::Timestamp:
+ new (&timestamp_value_) Timestamp(0, 0);
+ break;
+ case Type::ServerTimestamp:
+ new (&server_timestamp_value_) ServerTimestamp();
+ break;
+ case Type::String:
+ new (&string_value_) std::string();
+ break;
+ case Type::Blob:
+ // Do not even bother to allocate a new array of size 0.
+ new (&blob_value_) std::vector<uint8_t>();
+ break;
+ case Type::Reference:
+ // Qualified name to avoid conflict with the member function of same name.
+ new (&reference_value_) firebase::firestore::model::ReferenceValue();
+ break;
+ case Type::GeoPoint:
+ new (&geo_point_value_) GeoPoint();
+ break;
+ case Type::Array:
+ new (&array_value_) std::vector<FieldValue>();
+ break;
+ case Type::Object:
+ new (&object_value_) ObjectValue{};
+ break;
+ default: {} // The other types where there is nothing to worry about.
+ }
+}
+
+} // namespace model
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/src/firebase/firestore/model/field_value.h b/Firestore/core/src/firebase/firestore/model/field_value.h
new file mode 100644
index 0000000..3c5af9c
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/model/field_value.h
@@ -0,0 +1,238 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_FIELD_VALUE_H_
+#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_FIELD_VALUE_H_
+
+#include <cstdint>
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "Firestore/core/include/firebase/firestore/geo_point.h"
+#include "Firestore/core/include/firebase/firestore/timestamp.h"
+#include "Firestore/core/src/firebase/firestore/model/database_id.h"
+#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+#include "Firestore/core/src/firebase/firestore/util/firebase_assert.h"
+
+namespace firebase {
+namespace firestore {
+namespace model {
+
+struct ServerTimestamp {
+ Timestamp local_write_time;
+ Timestamp previous_value;
+ // TODO(zxu123): adopt absl::optional once abseil is ported.
+ bool has_previous_value_;
+};
+
+struct ReferenceValue {
+ DocumentKey reference;
+ // Does not own the DatabaseId instance.
+ const DatabaseId* database_id;
+};
+
+// TODO(rsgowman): Expand this to roughly match the java class
+// c.g.f.f.model.value.ObjectValue. Probably move it to a similar namespace as
+// well. (FieldValue itself is also in the value package in java.) Also do the
+// same with the other FooValue values that FieldValue can return.
+class FieldValue;
+struct ObjectValue {
+ // TODO(rsgowman): These will eventually be private. We do want the serializer
+ // to be able to directly access these (possibly implying 'friend' usage, or a
+ // getInternalValue() like java has.)
+ using Map = std::map<std::string, FieldValue>;
+ Map internal_value;
+};
+
+/**
+ * tagged-union class representing an immutable data value as stored in
+ * Firestore. FieldValue represents all the different kinds of values
+ * that can be stored in fields in a document.
+ */
+class FieldValue {
+ public:
+ /**
+ * All the different kinds of values that can be stored in fields in
+ * a document. The types of the same comparison order should be defined
+ * together as a group. The order of each group is defined by the Firestore
+ * backend and is available at:
+ * https://firebase.google.com/docs/firestore/manage-data/data-types
+ */
+ enum class Type {
+ Null, // Null
+ Boolean, // Boolean
+ Integer, // Number type starts here
+ Double,
+ Timestamp, // Timestamp type starts here
+ ServerTimestamp,
+ String, // String
+ Blob, // Blob
+ Reference, // Reference
+ GeoPoint, // GeoPoint
+ Array, // Array
+ Object, // Object
+ // New enum should not always been added at the tail. Add it to the correct
+ // position instead, see the doc comment above.
+ };
+
+ FieldValue() {
+ }
+
+ // Do not inline these ctor/dtor below, which contain call to non-trivial
+ // operator=.
+ FieldValue(const FieldValue& value);
+ FieldValue(FieldValue&& value);
+
+ ~FieldValue();
+
+ FieldValue& operator=(const FieldValue& value);
+ FieldValue& operator=(FieldValue&& value);
+
+ /** Returns the true type for this value. */
+ Type type() const {
+ return tag_;
+ }
+
+ bool boolean_value() const {
+ FIREBASE_ASSERT(tag_ == Type::Boolean);
+ return boolean_value_;
+ }
+
+ int64_t integer_value() const {
+ FIREBASE_ASSERT(tag_ == Type::Integer);
+ return integer_value_;
+ }
+
+ const std::string& string_value() const {
+ FIREBASE_ASSERT(tag_ == Type::String);
+ return string_value_;
+ }
+
+ const ObjectValue object_value() const {
+ FIREBASE_ASSERT(tag_ == Type::Object);
+ return ObjectValue{object_value_};
+ }
+
+ /** factory methods. */
+ static const FieldValue& NullValue();
+ static const FieldValue& TrueValue();
+ static const FieldValue& FalseValue();
+ static const FieldValue& BooleanValue(bool value);
+ static const FieldValue& NanValue();
+ static FieldValue IntegerValue(int64_t value);
+ static FieldValue DoubleValue(double value);
+ static FieldValue TimestampValue(const Timestamp& value);
+ static FieldValue ServerTimestampValue(const Timestamp& local_write_time,
+ const Timestamp& previous_value);
+ static FieldValue ServerTimestampValue(const Timestamp& local_write_time);
+ static FieldValue StringValue(const char* value);
+ static FieldValue StringValue(const std::string& value);
+ static FieldValue StringValue(std::string&& value);
+ static FieldValue BlobValue(const uint8_t* source, size_t size);
+ static FieldValue ReferenceValue(const DocumentKey& value,
+ const DatabaseId* database_id);
+ static FieldValue ReferenceValue(DocumentKey&& value,
+ const DatabaseId* database_id);
+ static FieldValue GeoPointValue(const GeoPoint& value);
+ static FieldValue ArrayValue(const std::vector<FieldValue>& value);
+ static FieldValue ArrayValue(std::vector<FieldValue>&& value);
+ static FieldValue ObjectValueFromMap(const ObjectValue::Map& value);
+ static FieldValue ObjectValueFromMap(ObjectValue::Map&& value);
+
+ friend bool operator<(const FieldValue& lhs, const FieldValue& rhs);
+
+ private:
+ explicit FieldValue(bool value) : tag_(Type::Boolean), boolean_value_(value) {
+ }
+
+ /**
+ * Switch to the specified type, if different from the current type.
+ */
+ void SwitchTo(Type type);
+
+ Type tag_ = Type::Null;
+ union {
+ // There is no null type as tag_ alone is enough for Null FieldValue.
+ bool boolean_value_;
+ int64_t integer_value_;
+ double double_value_;
+ Timestamp timestamp_value_;
+ ServerTimestamp server_timestamp_value_;
+ std::string string_value_;
+ std::vector<uint8_t> blob_value_;
+ // Qualified name to avoid conflict with the member function of same name.
+ firebase::firestore::model::ReferenceValue reference_value_;
+ GeoPoint geo_point_value_;
+ std::vector<FieldValue> array_value_;
+ ObjectValue object_value_;
+ };
+};
+
+/** Compares against another FieldValue. */
+bool operator<(const FieldValue& lhs, const FieldValue& rhs);
+
+inline bool operator>(const FieldValue& lhs, const FieldValue& rhs) {
+ return rhs < lhs;
+}
+
+inline bool operator>=(const FieldValue& lhs, const FieldValue& rhs) {
+ return !(lhs < rhs);
+}
+
+inline bool operator<=(const FieldValue& lhs, const FieldValue& rhs) {
+ return !(lhs > rhs);
+}
+
+inline bool operator!=(const FieldValue& lhs, const FieldValue& rhs) {
+ return lhs < rhs || lhs > rhs;
+}
+
+inline bool operator==(const FieldValue& lhs, const FieldValue& rhs) {
+ return !(lhs != rhs);
+}
+
+/** Compares against another ObjectValue. */
+inline bool operator<(const ObjectValue& lhs, const ObjectValue& rhs) {
+ return lhs.internal_value < rhs.internal_value;
+}
+
+inline bool operator>(const ObjectValue& lhs, const ObjectValue& rhs) {
+ return rhs < lhs;
+}
+
+inline bool operator>=(const ObjectValue& lhs, const ObjectValue& rhs) {
+ return !(lhs < rhs);
+}
+
+inline bool operator<=(const ObjectValue& lhs, const ObjectValue& rhs) {
+ return !(lhs > rhs);
+}
+
+inline bool operator!=(const ObjectValue& lhs, const ObjectValue& rhs) {
+ return lhs < rhs || lhs > rhs;
+}
+
+inline bool operator==(const ObjectValue& lhs, const ObjectValue& rhs) {
+ return !(lhs != rhs);
+}
+
+} // namespace model
+} // namespace firestore
+} // namespace firebase
+
+#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_FIELD_VALUE_H_
diff --git a/Firestore/core/src/firebase/firestore/model/maybe_document.cc b/Firestore/core/src/firebase/firestore/model/maybe_document.cc
new file mode 100644
index 0000000..4f3be1d
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/model/maybe_document.cc
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#include "Firestore/core/src/firebase/firestore/model/maybe_document.h"
+
+#include <utility>
+
+#include "Firestore/core/src/firebase/firestore/util/firebase_assert.h"
+
+namespace firebase {
+namespace firestore {
+namespace model {
+
+MaybeDocument::MaybeDocument(DocumentKey key, SnapshotVersion version)
+ : key_(std::move(key)), version_(std::move(version)) {
+}
+
+bool MaybeDocument::Equals(const MaybeDocument& other) const {
+ return type_ == other.type_ && version_ == other.version_ &&
+ key_ == other.key_;
+}
+
+} // namespace model
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/src/firebase/firestore/model/maybe_document.h b/Firestore/core/src/firebase/firestore/model/maybe_document.h
new file mode 100644
index 0000000..71bd3ef
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/model/maybe_document.h
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_MAYBE_DOCUMENT_H_
+#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_MAYBE_DOCUMENT_H_
+
+#include <functional>
+
+#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+#include "Firestore/core/src/firebase/firestore/model/snapshot_version.h"
+
+namespace firebase {
+namespace firestore {
+namespace model {
+
+/**
+ * The result of a lookup for a given path may be an existing document or a
+ * tombstone that marks the path deleted.
+ */
+class MaybeDocument {
+ public:
+ /**
+ * All the different kinds of documents, including MaybeDocument and its
+ * subclasses. This is used to provide RTTI for documents.
+ */
+ enum class Type {
+ Unknown,
+ Document,
+ NoDocument,
+ };
+
+ MaybeDocument(DocumentKey key, SnapshotVersion version);
+
+ /** The runtime type of this document. */
+ Type type() const {
+ return type_;
+ }
+
+ /** The key for this document. */
+ const DocumentKey& key() const {
+ return key_;
+ }
+
+ /**
+ * Returns the version of this document if it exists or a version at which
+ * this document was guaranteed to not exist.
+ */
+ const SnapshotVersion& version() const {
+ return version_;
+ }
+
+ protected:
+ // Only allow subclass to set their types.
+ void set_type(Type type) {
+ type_ = type;
+ }
+
+ virtual bool Equals(const MaybeDocument& other) const;
+
+ friend bool operator==(const MaybeDocument& lhs, const MaybeDocument& rhs);
+
+ private:
+ Type type_ = Type::Unknown;
+ DocumentKey key_;
+ SnapshotVersion version_;
+};
+
+inline bool operator==(const MaybeDocument& lhs, const MaybeDocument& rhs) {
+ return lhs.Equals(rhs);
+}
+
+inline bool operator!=(const MaybeDocument& lhs, const MaybeDocument& rhs) {
+ return !(lhs == rhs);
+}
+
+/** Compares against another MaybeDocument by keys only. */
+struct DocumentKeyComparator : public std::less<MaybeDocument> {
+ bool operator()(const MaybeDocument& lhs, const MaybeDocument& rhs) const {
+ return lhs.key() < rhs.key();
+ }
+};
+
+} // namespace model
+} // namespace firestore
+} // namespace firebase
+
+#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_MAYBE_DOCUMENT_H_
diff --git a/Firestore/core/src/firebase/firestore/model/no_document.cc b/Firestore/core/src/firebase/firestore/model/no_document.cc
new file mode 100644
index 0000000..98cb428
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/model/no_document.cc
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#include "Firestore/core/src/firebase/firestore/model/no_document.h"
+
+#include <utility>
+
+namespace firebase {
+namespace firestore {
+namespace model {
+
+NoDocument::NoDocument(DocumentKey key, SnapshotVersion version)
+ : MaybeDocument(std::move(key), std::move(version)) {
+ set_type(Type::NoDocument);
+}
+
+} // namespace model
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/src/firebase/firestore/model/no_document.h b/Firestore/core/src/firebase/firestore/model/no_document.h
new file mode 100644
index 0000000..7cfd47c
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/model/no_document.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_NO_DOCUMENT_H_
+#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_NO_DOCUMENT_H_
+
+#include "Firestore/core/src/firebase/firestore/model/maybe_document.h"
+
+namespace firebase {
+namespace firestore {
+namespace model {
+
+/** Represents that no documents exists for the key at the given version. */
+class NoDocument : public MaybeDocument {
+ public:
+ NoDocument(DocumentKey key, SnapshotVersion version);
+};
+
+} // namespace model
+} // namespace firestore
+} // namespace firebase
+
+#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_NO_DOCUMENT_H_
diff --git a/Firestore/core/src/firebase/firestore/model/precondition.cc b/Firestore/core/src/firebase/firestore/model/precondition.cc
new file mode 100644
index 0000000..423d5a2
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/model/precondition.cc
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#include "Firestore/core/src/firebase/firestore/model/precondition.h"
+
+#include "Firestore/core/src/firebase/firestore/model/maybe_document.h"
+#include "Firestore/core/src/firebase/firestore/model/snapshot_version.h"
+#include "Firestore/core/src/firebase/firestore/util/firebase_assert.h"
+
+namespace firebase {
+namespace firestore {
+namespace model {
+
+Precondition::Precondition(Type type, SnapshotVersion update_time, bool exists)
+ : type_(type), update_time_(std::move(update_time)), exists_(exists) {
+}
+
+/* static */
+Precondition Precondition::Exists(bool exists) {
+ return Precondition{Type::Exists, SnapshotVersion::None(), exists};
+}
+
+/* static */
+Precondition Precondition::UpdateTime(SnapshotVersion update_time) {
+ // update_time could be SnapshotVersion::None() in particular for locally
+ // deleted documents.
+ return Precondition{Type::UpdateTime, std::move(update_time), false};
+}
+
+/* static */
+Precondition Precondition::None() {
+ return Precondition{Type::None, SnapshotVersion::None(), false};
+}
+
+bool Precondition::IsValidFor(const MaybeDocument& maybe_doc) const {
+ switch (type_) {
+ case Type::UpdateTime:
+ return maybe_doc.type() == MaybeDocument::Type::Document &&
+ maybe_doc.version() == update_time_;
+ case Type::Exists:
+ if (exists_) {
+ return maybe_doc.type() == MaybeDocument::Type::Document;
+ } else {
+ return maybe_doc.type() == MaybeDocument::Type::NoDocument;
+ }
+ case Type::None:
+ return true;
+ }
+ FIREBASE_UNREACHABLE();
+}
+
+} // namespace model
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/src/firebase/firestore/model/precondition.h b/Firestore/core/src/firebase/firestore/model/precondition.h
new file mode 100644
index 0000000..4ab03c2
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/model/precondition.h
@@ -0,0 +1,160 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_PRECONDITION_H_
+#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_PRECONDITION_H_
+
+#include <utility>
+
+#if defined(__OBJC__)
+#import "FIRTimestamp.h"
+#import "Firestore/Source/Core/FSTSnapshotVersion.h"
+#import "Firestore/Source/Model/FSTDocument.h"
+#include "Firestore/core/include/firebase/firestore/timestamp.h"
+#endif // defined(__OBJC__)
+
+#include "Firestore/core/src/firebase/firestore/model/maybe_document.h"
+#include "Firestore/core/src/firebase/firestore/model/snapshot_version.h"
+#include "Firestore/core/src/firebase/firestore/util/firebase_assert.h"
+
+namespace firebase {
+namespace firestore {
+namespace model {
+
+/**
+ * Encodes a precondition for a mutation. This follows the model that the
+ * backend accepts with the special case of an explicit "empty" precondition
+ * (meaning no precondition).
+ */
+class Precondition {
+ public:
+ enum class Type {
+ None,
+ Exists,
+ UpdateTime,
+ };
+
+ /** Creates a new Precondition with an exists flag. */
+ static Precondition Exists(bool exists);
+
+ /** Creates a new Precondition based on a time the document exists at. */
+ static Precondition UpdateTime(SnapshotVersion update_time);
+
+ /** Returns a precondition representing no precondition. */
+ static Precondition None();
+
+ /**
+ * Returns true if the precondition is valid for the given document (and the
+ * document is available).
+ */
+ bool IsValidFor(const MaybeDocument& maybe_doc) const;
+
+ /** Returns whether this Precondition represents no precondition. */
+ bool IsNone() const {
+ return type_ == Type::None;
+ }
+
+ Type type() const {
+ return type_;
+ }
+
+ const SnapshotVersion& update_time() const {
+ return update_time_;
+ }
+
+ bool operator==(const Precondition& other) const {
+ return type_ == other.type_ && update_time_ == other.update_time_ &&
+ exists_ == other.exists_;
+ }
+
+#if defined(__OBJC__)
+ // Objective-C requires a default constructor.
+ Precondition()
+ : type_(Type::None),
+ update_time_(SnapshotVersion::None()),
+ exists_(false) {
+ }
+
+ // MaybeDocument is not fully ported yet. So we suppose this addition helper.
+ bool IsValidFor(FSTMaybeDocument* maybe_doc) const {
+ switch (type_) {
+ case Type::UpdateTime:
+ return [maybe_doc isKindOfClass:[FSTDocument class]] &&
+ firebase::firestore::model::SnapshotVersion(maybe_doc.version) ==
+ update_time_;
+ case Type::Exists:
+ if (exists_) {
+ return [maybe_doc isKindOfClass:[FSTDocument class]];
+ } else {
+ return maybe_doc == nil ||
+ [maybe_doc isKindOfClass:[FSTDeletedDocument class]];
+ }
+ case Type::None:
+ return true;
+ }
+ FIREBASE_UNREACHABLE();
+ }
+
+ // For Objective-C++ hash; to be removed after migration.
+ // Do NOT use in C++ code.
+ NSUInteger Hash() const {
+ NSUInteger hash = std::hash<Timestamp>()(update_time_.timestamp());
+ hash = hash * 31 + exists_;
+ hash = hash * 31 + static_cast<NSUInteger>(type_);
+ return hash;
+ }
+
+ NSString* description() const {
+ switch (type_) {
+ case Type::None:
+ return @"<Precondition <none>>";
+ case Type::Exists:
+ if (exists_) {
+ return @"<Precondition exists=yes>";
+ } else {
+ return @"<Precondition exists=no>";
+ }
+ case Type::UpdateTime:
+ return [NSString
+ stringWithFormat:@"<Precondition update_time=%s>",
+ update_time_.timestamp().ToString().c_str()];
+ }
+ // We only raise dev assertion here. This function is mainly used in
+ // logging.
+ FIREBASE_DEV_ASSERT_MESSAGE(false, "precondition invalid");
+ return @"<Precondition invalid>";
+ }
+#endif // defined(__OBJC__)
+
+ private:
+ Precondition(Type type, SnapshotVersion update_time, bool exists);
+
+ // The actual time of this precondition.
+ Type type_ = Type::None;
+
+ // For UpdateTime type, preconditions a mutation based on the last updateTime.
+ SnapshotVersion update_time_;
+
+ // For Exists type, preconditions a mutation based on whether the document
+ // exists.
+ bool exists_;
+};
+
+} // namespace model
+} // namespace firestore
+} // namespace firebase
+
+#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_PRECONDITION_H_
diff --git a/Firestore/core/src/firebase/firestore/model/resource_path.cc b/Firestore/core/src/firebase/firestore/model/resource_path.cc
new file mode 100644
index 0000000..c95aa63
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/model/resource_path.cc
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#include "Firestore/core/src/firebase/firestore/model/resource_path.h"
+
+#include <algorithm>
+#include <utility>
+#include <vector>
+
+#include "Firestore/core/src/firebase/firestore/util/firebase_assert.h"
+#include "absl/strings/str_join.h"
+#include "absl/strings/str_split.h"
+
+namespace firebase {
+namespace firestore {
+namespace model {
+
+ResourcePath ResourcePath::FromString(const absl::string_view path) {
+ // NOTE: The client is ignorant of any path segments containing escape
+ // sequences (e.g. __id123__) and just passes them through raw (they exist
+ // for legacy reasons and should not be used frequently).
+
+ FIREBASE_ASSERT_MESSAGE(
+ path.find("//") == std::string::npos,
+ "Invalid path (%s). Paths must not contain // in them.",
+ std::string{path.data(), path.data() + path.size()}.c_str());
+
+ // SkipEmpty because we may still have an empty segment at the beginning or
+ // end if they had a leading or trailing slash (which we allow).
+ std::vector<std::string> segments =
+ absl::StrSplit(path, '/', absl::SkipEmpty());
+ return ResourcePath{std::move(segments)};
+}
+
+std::string ResourcePath::CanonicalString() const {
+ // NOTE: The client is ignorant of any path segments containing escape
+ // sequences (e.g. __id123__) and just passes them through raw (they exist
+ // for legacy reasons and should not be used frequently).
+
+ return absl::StrJoin(begin(), end(), "/");
+}
+
+} // namespace model
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/src/firebase/firestore/model/resource_path.h b/Firestore/core/src/firebase/firestore/model/resource_path.h
new file mode 100644
index 0000000..acdf1e2
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/model/resource_path.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_RESOURCE_PATH_H_
+#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_RESOURCE_PATH_H_
+
+#include <initializer_list>
+#include <string>
+#include <utility>
+
+#include "Firestore/core/src/firebase/firestore/model/base_path.h"
+#include "absl/strings/string_view.h"
+
+namespace firebase {
+namespace firestore {
+namespace model {
+
+/**
+ * A slash-separated path for navigating resources (documents and collections)
+ * within Firestore. Immutable; all instances are fully independent.
+ */
+class ResourcePath : public impl::BasePath<ResourcePath> {
+ public:
+ ResourcePath() = default;
+ /** Constructs the path from segments. */
+ template <typename IterT>
+ ResourcePath(const IterT begin, const IterT end) : BasePath{begin, end} {
+ }
+ ResourcePath(std::initializer_list<std::string> list) : BasePath{list} {
+ }
+ explicit ResourcePath(SegmentsT&& segments) : BasePath{std::move(segments)} {
+ }
+ /**
+ * Creates and returns a new path from the given resource-path string, where
+ * the path segments are separated by a slash "/".
+ */
+ static ResourcePath FromString(absl::string_view path);
+
+ /** Returns a standardized string representation of this path. */
+ std::string CanonicalString() const;
+
+ bool operator==(const ResourcePath& rhs) const {
+ return BasePath::operator==(rhs);
+ }
+ bool operator!=(const ResourcePath& rhs) const {
+ return BasePath::operator!=(rhs);
+ }
+ bool operator<(const ResourcePath& rhs) const {
+ return BasePath::operator<(rhs);
+ }
+ bool operator>(const ResourcePath& rhs) const {
+ return BasePath::operator>(rhs);
+ }
+ bool operator<=(const ResourcePath& rhs) const {
+ return BasePath::operator<=(rhs);
+ }
+ bool operator>=(const ResourcePath& rhs) const {
+ return BasePath::operator>=(rhs);
+ }
+};
+
+} // namespace model
+} // namespace firestore
+} // namespace firebase
+
+#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_RESOURCE_PATH_H_
diff --git a/Firestore/core/src/firebase/firestore/model/snapshot_version.cc b/Firestore/core/src/firebase/firestore/model/snapshot_version.cc
new file mode 100644
index 0000000..114e313
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/model/snapshot_version.cc
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#include "Firestore/core/src/firebase/firestore/model/snapshot_version.h"
+
+namespace firebase {
+namespace firestore {
+namespace model {
+
+SnapshotVersion::SnapshotVersion(const Timestamp& timestamp)
+ : timestamp_(timestamp) {
+}
+
+const SnapshotVersion& SnapshotVersion::None() {
+ static const SnapshotVersion kNone(Timestamp{});
+ return kNone;
+}
+
+} // namespace model
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/src/firebase/firestore/model/snapshot_version.h b/Firestore/core/src/firebase/firestore/model/snapshot_version.h
new file mode 100644
index 0000000..1fbba1c
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/model/snapshot_version.h
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_SNAPSHOT_VERSION_H_
+#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_SNAPSHOT_VERSION_H_
+
+#include "Firestore/core/include/firebase/firestore/timestamp.h"
+
+#if defined(__OBJC__)
+#import "FIRTimestamp.h"
+#import "Firestore/Source/Core/FSTSnapshotVersion.h"
+#endif // defined(__OBJC__)
+
+namespace firebase {
+namespace firestore {
+namespace model {
+
+/**
+ * A version of a document in Firestore. This corresponds to the version
+ * timestamp, such as update_time or read_time.
+ */
+class SnapshotVersion {
+ public:
+ explicit SnapshotVersion(const Timestamp& timestamp);
+
+ const Timestamp& timestamp() const {
+ return timestamp_;
+ }
+
+ /** Creates a new version that is smaller than all other versions. */
+ static const SnapshotVersion& None();
+
+#if defined(__OBJC__)
+ SnapshotVersion(FSTSnapshotVersion* version) // NOLINT(runtime/explicit)
+ : timestamp_{version.timestamp.seconds, version.timestamp.nanoseconds} {
+ }
+
+ operator FSTSnapshotVersion*() const {
+ if (timestamp_ == Timestamp{}) {
+ return [FSTSnapshotVersion noVersion];
+ } else {
+ return [FSTSnapshotVersion
+ versionWithTimestamp:[FIRTimestamp
+ timestampWithSeconds:timestamp_.seconds()
+ nanoseconds:timestamp_
+ .nanoseconds()]];
+ }
+ }
+#endif // defined(__OBJC__)
+
+ private:
+ Timestamp timestamp_;
+};
+
+/** Compares against another SnapshotVersion. */
+inline bool operator<(const SnapshotVersion& lhs, const SnapshotVersion& rhs) {
+ return lhs.timestamp() < rhs.timestamp();
+}
+
+inline bool operator>(const SnapshotVersion& lhs, const SnapshotVersion& rhs) {
+ return lhs.timestamp() > rhs.timestamp();
+}
+
+inline bool operator>=(const SnapshotVersion& lhs, const SnapshotVersion& rhs) {
+ return lhs.timestamp() >= rhs.timestamp();
+}
+
+inline bool operator<=(const SnapshotVersion& lhs, const SnapshotVersion& rhs) {
+ return lhs.timestamp() <= rhs.timestamp();
+}
+
+inline bool operator!=(const SnapshotVersion& lhs, const SnapshotVersion& rhs) {
+ return lhs.timestamp() != rhs.timestamp();
+}
+
+inline bool operator==(const SnapshotVersion& lhs, const SnapshotVersion& rhs) {
+ return lhs.timestamp() == rhs.timestamp();
+}
+
+} // namespace model
+} // namespace firestore
+} // namespace firebase
+
+#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_SNAPSHOT_VERSION_H_
diff --git a/Firestore/core/src/firebase/firestore/model/transform_operations.h b/Firestore/core/src/firebase/firestore/model/transform_operations.h
new file mode 100644
index 0000000..aad5a9b
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/model/transform_operations.h
@@ -0,0 +1,164 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_TRANSFORM_OPERATIONS_H_
+#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_TRANSFORM_OPERATIONS_H_
+
+#include <vector>
+
+#if defined(__OBJC__)
+#import "Firestore/Source/Model/FSTFieldValue.h"
+#endif
+
+namespace firebase {
+namespace firestore {
+namespace model {
+
+// TODO(zxu123): We might want to refactor transform_operations.h into several
+// files when the number of different types of operations grows gigantically.
+// For now, put the interface and the only operation here.
+
+/** Represents a transform within a TransformMutation. */
+class TransformOperation {
+ public:
+ /** All the different kinds to TransformOperation. */
+ enum class Type {
+ ServerTimestamp,
+ ArrayUnion,
+ ArrayRemove,
+ Test, // Purely for test purpose.
+ };
+
+ virtual ~TransformOperation() {
+ }
+
+ /** Returns the actual type. */
+ virtual Type type() const = 0;
+
+ /** Returns whether the two are equal. */
+ virtual bool operator==(const TransformOperation& other) const = 0;
+
+ /** Returns whether the two are not equal. */
+ bool operator!=(const TransformOperation& other) const {
+ return !operator==(other);
+ }
+
+#if defined(__OBJC__)
+ // For Objective-C++ hash; to be removed after migration.
+ // Do NOT use in C++ code.
+ virtual NSUInteger Hash() const = 0;
+#endif // defined(__OBJC__)
+};
+
+/** Transforms a value into a server-generated timestamp. */
+class ServerTimestampTransform : public TransformOperation {
+ public:
+ ~ServerTimestampTransform() override {
+ }
+
+ Type type() const override {
+ return Type::ServerTimestamp;
+ }
+
+ bool operator==(const TransformOperation& other) const override {
+ // All ServerTimestampTransform objects are equal.
+ return other.type() == Type::ServerTimestamp;
+ }
+
+ static const ServerTimestampTransform& Get() {
+ static ServerTimestampTransform shared_instance;
+ return shared_instance;
+ }
+
+#if defined(__OBJC__)
+ // For Objective-C++ hash; to be removed after migration.
+ // Do NOT use in C++ code.
+ NSUInteger Hash() const override {
+ // arbitrary number, the same as used in ObjC implementation, since all
+ // instances are equal.
+ return 37;
+ }
+#endif // defined(__OBJC__)
+
+ private:
+ ServerTimestampTransform() {
+ }
+};
+
+// TODO(mikelehen): ArrayTransform can only be used from Obj-C until we switch
+// to using FieldValue instead of FSTFieldValue.
+#if defined(__OBJC__)
+/**
+ * Transforms an array via a union or remove operation (for convenience, we use
+ * this class for both Type::ArrayUnion and Type::ArrayRemove).
+ */
+class ArrayTransform : public TransformOperation {
+ public:
+ ArrayTransform(const Type& type, const std::vector<FSTFieldValue*> elements)
+ : type_(type), elements_(elements) {
+ }
+
+ ~ArrayTransform() override {
+ }
+
+ Type type() const override {
+ return type_;
+ }
+
+ const std::vector<FSTFieldValue*>& elements() const {
+ return elements_;
+ }
+
+ bool operator==(const TransformOperation& other) const override {
+ if (other.type() != type()) {
+ return false;
+ }
+ auto array_transform = static_cast<const ArrayTransform&>(other);
+ if (array_transform.elements_.size() != elements_.size()) {
+ return false;
+ }
+ for (int i = 0; i < elements_.size(); i++) {
+ if (![array_transform.elements_[i] isEqual:elements_[i]]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+#if defined(__OBJC__)
+ // For Objective-C++ hash; to be removed after migration.
+ // Do NOT use in C++ code.
+ NSUInteger Hash() const override {
+ NSUInteger result = 37;
+ result = 31 * result + (type() == Type::ArrayUnion ? 1231 : 1237);
+ for (FSTFieldValue* element : elements_) {
+ result = 31 * result + [element hash];
+ }
+ return result;
+ }
+#endif // defined(__OBJC__)
+
+ private:
+ Type type_;
+ std::vector<FSTFieldValue*> elements_;
+};
+#endif
+
+} // namespace model
+} // namespace firestore
+} // namespace firebase
+
+#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_TRANSFORM_OPERATIONS_H_
diff --git a/Firestore/core/src/firebase/firestore/model/types.h b/Firestore/core/src/firebase/firestore/model/types.h
new file mode 100644
index 0000000..3f813be
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/model/types.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_TYPES_H_
+#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_TYPES_H_
+
+#include <cstdint>
+
+namespace firebase {
+namespace firestore {
+namespace model {
+
+/**
+ * BatchId is a locally assigned identifier for a batch of mutations that have
+ * been applied by the user but have not yet been fully committed at the server.
+ */
+using BatchId = int32_t;
+
+/**
+ * TargetId is a stable numeric identifier assigned for a specific query
+ * applied.
+ */
+using TargetId = int32_t;
+
+} // namespace model
+} // namespace firestore
+} // namespace firebase
+
+#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_TYPES_H_
diff --git a/Firestore/core/src/firebase/firestore/remote/CMakeLists.txt b/Firestore/core/src/firebase/firestore/remote/CMakeLists.txt
new file mode 100644
index 0000000..7f528fb
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/remote/CMakeLists.txt
@@ -0,0 +1,27 @@
+# Copyright 2018 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.
+
+cc_library(
+ firebase_firestore_remote
+ SOURCES
+ datastore.h
+ datastore.cc
+ serializer.h
+ serializer.cc
+ DEPENDS
+ firebase_firestore_model
+ firebase_firestore_protos_nanopb
+ grpc::grpc
+ nanopb
+)
diff --git a/Firestore/core/src/firebase/firestore/remote/datastore.cc b/Firestore/core/src/firebase/firestore/remote/datastore.cc
new file mode 100644
index 0000000..f8a8988
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/remote/datastore.cc
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#include "Firestore/core/src/firebase/firestore/remote/datastore.h"
+
+namespace firebase {
+namespace firestore {
+namespace remote {
+
+Datastore::Datastore() {
+}
+
+Datastore::~Datastore() {
+}
+
+} // namespace remote
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/src/firebase/firestore/remote/datastore.h b/Firestore/core/src/firebase/firestore/remote/datastore.h
new file mode 100644
index 0000000..807b75f
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/remote/datastore.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_REMOTE_DATASTORE_H_
+#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_REMOTE_DATASTORE_H_
+
+namespace firebase {
+namespace firestore {
+namespace remote {
+
+class Datastore {
+ public:
+ Datastore();
+ ~Datastore();
+
+ Datastore(const Datastore& other) = delete;
+ Datastore(Datastore&& other) = delete;
+
+ Datastore& operator=(const Datastore& other) = delete;
+ Datastore& operator=(Datastore&& other) = delete;
+};
+
+} // namespace remote
+} // namespace firestore
+} // namespace firebase
+
+#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_REMOTE_DATASTORE_H_
diff --git a/Firestore/core/src/firebase/firestore/remote/serializer.cc b/Firestore/core/src/firebase/firestore/remote/serializer.cc
new file mode 100644
index 0000000..befe032
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/remote/serializer.cc
@@ -0,0 +1,668 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#include "Firestore/core/src/firebase/firestore/remote/serializer.h"
+
+#include <pb_decode.h>
+#include <pb_encode.h>
+
+#include <functional>
+#include <map>
+#include <string>
+#include <utility>
+
+#include "Firestore/core/src/firebase/firestore/util/firebase_assert.h"
+
+namespace firebase {
+namespace firestore {
+namespace remote {
+
+using firebase::firestore::model::FieldValue;
+using firebase::firestore::model::ObjectValue;
+using firebase::firestore::util::Status;
+
+namespace {
+
+class Writer;
+
+class Reader;
+
+void EncodeObject(Writer* writer, const ObjectValue& object_value);
+
+ObjectValue DecodeObject(Reader* reader);
+
+/**
+ * Represents a nanopb tag.
+ *
+ * field_number is one of the field tags that nanopb generates based off of
+ * the proto messages. They're typically named in the format:
+ * <parentNameSpace>_<childNameSpace>_<message>_<field>_tag, e.g.
+ * google_firestore_v1beta1_Document_name_tag.
+ */
+struct Tag {
+ pb_wire_type_t wire_type;
+ uint32_t field_number;
+};
+
+/**
+ * Docs TODO(rsgowman). But currently, this just wraps the underlying nanopb
+ * pb_ostream_t. Also doc how to check status.
+ */
+class Writer {
+ public:
+ /**
+ * Creates an output stream that writes to the specified vector. Note that
+ * this vector pointer must remain valid for the lifetime of this Writer.
+ *
+ * (This is roughly equivalent to the nanopb function
+ * pb_ostream_from_buffer())
+ *
+ * @param out_bytes where the output should be serialized to.
+ */
+ static Writer Wrap(std::vector<uint8_t>* out_bytes);
+
+ /**
+ * Creates a non-writing output stream used to calculate the size of
+ * the serialized output.
+ */
+ static Writer Sizing() {
+ return Writer(PB_OSTREAM_SIZING);
+ }
+
+ /**
+ * Writes a message type to the output stream.
+ *
+ * This essentially wraps calls to nanopb's pb_encode_tag() method.
+ */
+ void WriteTag(Tag tag);
+
+ void WriteSize(size_t size);
+ void WriteNull();
+ void WriteBool(bool bool_value);
+ void WriteInteger(int64_t integer_value);
+
+ void WriteString(const std::string& string_value);
+
+ /**
+ * Writes a message and its length.
+ *
+ * When writing a top level message, protobuf doesn't include the length
+ * (since you can get that already from the length of the binary output.) But
+ * when writing a sub/nested message, you must include the length in the
+ * serialization.
+ *
+ * Call this method when writing a nested message. Provide a function to
+ * write the message itself. This method will calculate the size of the
+ * written message (using the provided function with a non-writing sizing
+ * stream), write out the size (and perform sanity checks), and then serialize
+ * the message by calling the provided function a second time.
+ */
+ void WriteNestedMessage(const std::function<void(Writer*)>& write_message_fn);
+
+ size_t bytes_written() const {
+ return stream_.bytes_written;
+ }
+
+ Status status() const {
+ return status_;
+ }
+
+ private:
+ Status status_ = Status::OK();
+
+ /**
+ * Creates a new Writer, based on the given nanopb pb_ostream_t. Note that
+ * a shallow copy will be taken. (Non-null pointers within this struct must
+ * remain valid for the lifetime of this Writer.)
+ */
+ explicit Writer(const pb_ostream_t& stream) : stream_(stream) {
+ }
+
+ /**
+ * Writes a "varint" to the output stream.
+ *
+ * This essentially wraps calls to nanopb's pb_encode_varint() method.
+ *
+ * Note that (despite the value parameter type) this works for bool, enum,
+ * int32, int64, uint32 and uint64 proto field types.
+ *
+ * Note: This is not expected to be called directly, but rather only
+ * via the other Write* methods (i.e. WriteBool, WriteLong, etc)
+ *
+ * @param value The value to write, represented as a uint64_t.
+ */
+ void WriteVarint(uint64_t value);
+
+ pb_ostream_t stream_;
+};
+
+/**
+ * Docs TODO(rsgowman). But currently, this just wraps the underlying nanopb
+ * pb_istream_t.
+ */
+class Reader {
+ public:
+ /**
+ * Creates an input stream that reads from the specified bytes. Note that
+ * this reference must remain valid for the lifetime of this Reader.
+ *
+ * (This is roughly equivalent to the nanopb function
+ * pb_istream_from_buffer())
+ *
+ * @param bytes where the input should be deserialized from.
+ */
+ static Reader Wrap(const uint8_t* bytes, size_t length);
+
+ /**
+ * Reads a message type from the input stream.
+ *
+ * This essentially wraps calls to nanopb's pb_decode_tag() method.
+ */
+ Tag ReadTag();
+
+ void ReadNull();
+ bool ReadBool();
+ int64_t ReadInteger();
+
+ std::string ReadString();
+
+ /**
+ * Reads a message and its length.
+ *
+ * Analog to Writer::WriteNestedMessage(). See that methods docs for further
+ * details.
+ *
+ * Call this method when reading a nested message. Provide a function to read
+ * the message itself.
+ */
+ template <typename T>
+ T ReadNestedMessage(const std::function<T(Reader*)>& read_message_fn);
+
+ size_t bytes_left() const {
+ return stream_.bytes_left;
+ }
+
+ private:
+ /**
+ * Creates a new Reader, based on the given nanopb pb_istream_t. Note that
+ * a shallow copy will be taken. (Non-null pointers within this struct must
+ * remain valid for the lifetime of this Reader.)
+ */
+ explicit Reader(pb_istream_t stream) : stream_(stream) {
+ }
+
+ /**
+ * Reads a "varint" from the input stream.
+ *
+ * This essentially wraps calls to nanopb's pb_decode_varint() method.
+ *
+ * Note that (despite the return type) this works for bool, enum, int32,
+ * int64, uint32 and uint64 proto field types.
+ *
+ * Note: This is not expected to be called direclty, but rather only via the
+ * other Decode* methods (i.e. DecodeBool, DecodeLong, etc)
+ *
+ * @return The decoded varint as a uint64_t.
+ */
+ uint64_t ReadVarint();
+
+ pb_istream_t stream_;
+};
+
+Writer Writer::Wrap(std::vector<uint8_t>* out_bytes) {
+ // TODO(rsgowman): find a better home for this constant.
+ // A document is defined to have a max size of 1MiB - 4 bytes.
+ static const size_t kMaxDocumentSize = 1 * 1024 * 1024 - 4;
+
+ // Construct a nanopb output stream.
+ //
+ // Set the max_size to be the max document size (as an upper bound; one would
+ // expect individual FieldValue's to be smaller than this).
+ //
+ // bytes_written is (always) initialized to 0. (NB: nanopb does not know or
+ // care about the underlying output vector, so where we are in the vector
+ // itself is irrelevant. i.e. don't use out_bytes->size())
+ pb_ostream_t raw_stream = {
+ /*callback=*/[](pb_ostream_t* stream, const pb_byte_t* buf,
+ size_t count) -> bool {
+ auto* out_bytes = static_cast<std::vector<uint8_t>*>(stream->state);
+ out_bytes->insert(out_bytes->end(), buf, buf + count);
+ return true;
+ },
+ /*state=*/out_bytes,
+ /*max_size=*/kMaxDocumentSize,
+ /*bytes_written=*/0,
+ /*errmsg=*/nullptr};
+ return Writer(raw_stream);
+}
+
+Reader Reader::Wrap(const uint8_t* bytes, size_t length) {
+ return Reader{pb_istream_from_buffer(bytes, length)};
+}
+
+// TODO(rsgowman): I've left the methods as near as possible to where they were
+// before, which implies that the Writer methods are interspersed with the
+// Reader methods. This should make it a bit easier to review. Refactor these to
+// group the related methods together (probably within their own file rather
+// than here).
+
+void Writer::WriteTag(Tag tag) {
+ if (!status_.ok()) return;
+
+ if (!pb_encode_tag(&stream_, tag.wire_type, tag.field_number)) {
+ FIREBASE_ASSERT_MESSAGE(false, PB_GET_ERROR(&stream_));
+ }
+}
+
+Tag Reader::ReadTag() {
+ Tag tag;
+ bool eof;
+ bool ok = pb_decode_tag(&stream_, &tag.wire_type, &tag.field_number, &eof);
+ if (!ok || eof) {
+ // TODO(rsgowman): figure out error handling
+ abort();
+ }
+ return tag;
+}
+
+void Writer::WriteSize(size_t size) {
+ return WriteVarint(size);
+}
+
+void Writer::WriteVarint(uint64_t value) {
+ if (!status_.ok()) return;
+
+ if (!pb_encode_varint(&stream_, value)) {
+ FIREBASE_ASSERT_MESSAGE(false, PB_GET_ERROR(&stream_));
+ }
+}
+
+/**
+ * Note that (despite the return type) this works for bool, enum, int32, int64,
+ * uint32 and uint64 proto field types.
+ *
+ * Note: This is not expected to be called directly, but rather only via the
+ * other Decode* methods (i.e. DecodeBool, DecodeLong, etc)
+ *
+ * @return The decoded varint as a uint64_t.
+ */
+uint64_t Reader::ReadVarint() {
+ uint64_t varint_value;
+ if (!pb_decode_varint(&stream_, &varint_value)) {
+ // TODO(rsgowman): figure out error handling
+ abort();
+ }
+ return varint_value;
+}
+
+void Writer::WriteNull() {
+ return WriteVarint(google_protobuf_NullValue_NULL_VALUE);
+}
+
+void Reader::ReadNull() {
+ uint64_t varint = ReadVarint();
+ if (varint != google_protobuf_NullValue_NULL_VALUE) {
+ // TODO(rsgowman): figure out error handling
+ abort();
+ }
+}
+
+void Writer::WriteBool(bool bool_value) {
+ return WriteVarint(bool_value);
+}
+
+bool Reader::ReadBool() {
+ uint64_t varint = ReadVarint();
+ switch (varint) {
+ case 0:
+ return false;
+ case 1:
+ return true;
+ default:
+ // TODO(rsgowman): figure out error handling
+ abort();
+ }
+}
+
+void Writer::WriteInteger(int64_t integer_value) {
+ return WriteVarint(integer_value);
+}
+
+int64_t Reader::ReadInteger() {
+ return ReadVarint();
+}
+
+void Writer::WriteString(const std::string& string_value) {
+ if (!status_.ok()) return;
+
+ if (!pb_encode_string(
+ &stream_, reinterpret_cast<const pb_byte_t*>(string_value.c_str()),
+ string_value.length())) {
+ FIREBASE_ASSERT_MESSAGE(false, PB_GET_ERROR(&stream_));
+ }
+}
+
+std::string Reader::ReadString() {
+ pb_istream_t substream;
+ if (!pb_make_string_substream(&stream_, &substream)) {
+ // TODO(rsgowman): figure out error handling
+ abort();
+ }
+
+ std::string result(substream.bytes_left, '\0');
+ if (!pb_read(&substream, reinterpret_cast<pb_byte_t*>(&result[0]),
+ substream.bytes_left)) {
+ // TODO(rsgowman): figure out error handling
+ abort();
+ }
+
+ // NB: future versions of nanopb read the remaining characters out of the
+ // substream (and return false if that fails) as an additional safety
+ // check within pb_close_string_substream. Unfortunately, that's not present
+ // in the current version (0.38). We'll make a stronger assertion and check
+ // to make sure there *are* no remaining characters in the substream.
+ if (substream.bytes_left != 0) {
+ // TODO(rsgowman): figure out error handling
+ abort();
+ }
+
+ pb_close_string_substream(&stream_, &substream);
+
+ return result;
+}
+
+// Named '..Impl' so as to not conflict with Serializer::EncodeFieldValue.
+// TODO(rsgowman): Refactor to use a helper class that wraps the stream struct.
+// This will help with error handling, and should eliminate the issue of two
+// 'EncodeFieldValue' methods.
+void EncodeFieldValueImpl(Writer* writer, const FieldValue& field_value) {
+ // TODO(rsgowman): some refactoring is in order... but will wait until after a
+ // non-varint, non-fixed-size (i.e. string) type is present before doing so.
+ switch (field_value.type()) {
+ case FieldValue::Type::Null:
+ writer->WriteTag(
+ {PB_WT_VARINT, google_firestore_v1beta1_Value_null_value_tag});
+ writer->WriteNull();
+ break;
+
+ case FieldValue::Type::Boolean:
+ writer->WriteTag(
+ {PB_WT_VARINT, google_firestore_v1beta1_Value_boolean_value_tag});
+ writer->WriteBool(field_value.boolean_value());
+ break;
+
+ case FieldValue::Type::Integer:
+ writer->WriteTag(
+ {PB_WT_VARINT, google_firestore_v1beta1_Value_integer_value_tag});
+ writer->WriteInteger(field_value.integer_value());
+ break;
+
+ case FieldValue::Type::String:
+ writer->WriteTag(
+ {PB_WT_STRING, google_firestore_v1beta1_Value_string_value_tag});
+ writer->WriteString(field_value.string_value());
+ break;
+
+ case FieldValue::Type::Object:
+ writer->WriteTag(
+ {PB_WT_STRING, google_firestore_v1beta1_Value_map_value_tag});
+ EncodeObject(writer, field_value.object_value());
+ break;
+
+ default:
+ // TODO(rsgowman): implement the other types
+ abort();
+ }
+}
+
+FieldValue DecodeFieldValueImpl(Reader* reader) {
+ Tag tag = reader->ReadTag();
+
+ // Ensure the tag matches the wire type
+ // TODO(rsgowman): figure out error handling
+ switch (tag.field_number) {
+ case google_firestore_v1beta1_Value_null_value_tag:
+ case google_firestore_v1beta1_Value_boolean_value_tag:
+ case google_firestore_v1beta1_Value_integer_value_tag:
+ if (tag.wire_type != PB_WT_VARINT) {
+ abort();
+ }
+ break;
+
+ case google_firestore_v1beta1_Value_string_value_tag:
+ case google_firestore_v1beta1_Value_map_value_tag:
+ if (tag.wire_type != PB_WT_STRING) {
+ abort();
+ }
+ break;
+
+ default:
+ abort();
+ }
+
+ switch (tag.field_number) {
+ case google_firestore_v1beta1_Value_null_value_tag:
+ reader->ReadNull();
+ return FieldValue::NullValue();
+ case google_firestore_v1beta1_Value_boolean_value_tag:
+ return FieldValue::BooleanValue(reader->ReadBool());
+ case google_firestore_v1beta1_Value_integer_value_tag:
+ return FieldValue::IntegerValue(reader->ReadInteger());
+ case google_firestore_v1beta1_Value_string_value_tag:
+ return FieldValue::StringValue(reader->ReadString());
+ case google_firestore_v1beta1_Value_map_value_tag:
+ return FieldValue::ObjectValueFromMap(
+ DecodeObject(reader).internal_value);
+
+ default:
+ // TODO(rsgowman): figure out error handling
+ abort();
+ }
+}
+
+void Writer::WriteNestedMessage(
+ const std::function<void(Writer*)>& write_message_fn) {
+ if (!status_.ok()) return;
+
+ // First calculate the message size using a non-writing substream.
+ Writer sizer = Writer::Sizing();
+ write_message_fn(&sizer);
+ status_ = sizer.status();
+ if (!status_.ok()) return;
+ size_t size = sizer.bytes_written();
+
+ // Write out the size to the output writer.
+ WriteSize(size);
+ if (!status_.ok()) return;
+
+ // If this stream is itself a sizing stream, then we don't need to actually
+ // parse field_value a second time; just update the bytes_written via a call
+ // to pb_write. (If we try to write the contents into a sizing stream, it'll
+ // fail since sizing streams don't actually have any buffer space.)
+ if (stream_.callback == nullptr) {
+ if (!pb_write(&stream_, nullptr, size)) {
+ FIREBASE_ASSERT_MESSAGE(false, PB_GET_ERROR(&stream_));
+ }
+ return;
+ }
+
+ // Ensure the output stream has enough space
+ if (stream_.bytes_written + size > stream_.max_size) {
+ FIREBASE_ASSERT_MESSAGE(
+ false,
+ "Insufficient space in the output stream to write the given message");
+ }
+
+ // Use a substream to verify that a callback doesn't write more than what it
+ // did the first time. (Use an initializer rather than setting fields
+ // individually like nanopb does. This gives us a *chance* of noticing if
+ // nanopb adds new fields.)
+ Writer writer({stream_.callback, stream_.state,
+ /*max_size=*/size, /*bytes_written=*/0,
+ /*errmsg=*/nullptr});
+ write_message_fn(&writer);
+ status_ = writer.status();
+ if (!status_.ok()) return;
+
+ stream_.bytes_written += writer.stream_.bytes_written;
+ stream_.state = writer.stream_.state;
+ stream_.errmsg = writer.stream_.errmsg;
+
+ if (writer.bytes_written() != size) {
+ // submsg size changed
+ FIREBASE_ASSERT_MESSAGE(
+ false, "Parsing the nested message twice yielded different sizes");
+ }
+}
+
+template <typename T>
+T Reader::ReadNestedMessage(const std::function<T(Reader*)>& read_message_fn) {
+ // Implementation note: This is roughly modeled on pb_decode_delimited,
+ // adjusted to account for the oneof in FieldValue.
+ pb_istream_t raw_substream;
+ if (!pb_make_string_substream(&stream_, &raw_substream)) {
+ // TODO(rsgowman): figure out error handling
+ abort();
+ }
+ Reader substream(raw_substream);
+
+ T message = read_message_fn(&substream);
+
+ // NB: future versions of nanopb read the remaining characters out of the
+ // substream (and return false if that fails) as an additional safety
+ // check within pb_close_string_substream. Unfortunately, that's not present
+ // in the current version (0.38). We'll make a stronger assertion and check
+ // to make sure there *are* no remaining characters in the substream.
+ if (substream.bytes_left() != 0) {
+ // TODO(rsgowman): figure out error handling
+ abort();
+ }
+ pb_close_string_substream(&stream_, &substream.stream_);
+
+ return message;
+}
+
+/**
+ * Encodes a 'FieldsEntry' object, within a FieldValue's map_value type.
+ *
+ * In protobuf, maps are implemented as a repeated set of key/values. For
+ * instance, this:
+ * message Foo {
+ * map<string, Value> fields = 1;
+ * }
+ * would be written (in proto text format) as:
+ * {
+ * fields: {key:"key string 1", value:{<Value message here>}}
+ * fields: {key:"key string 2", value:{<Value message here>}}
+ * ...
+ * }
+ *
+ * This method writes an individual entry from that list. It is expected that
+ * this method will be called once for each entry in the map.
+ *
+ * @param kv The individual key/value pair to write.
+ */
+void EncodeFieldsEntry(Writer* writer, const ObjectValue::Map::value_type& kv) {
+ // Write the key (string)
+ writer->WriteTag(
+ {PB_WT_STRING, google_firestore_v1beta1_MapValue_FieldsEntry_key_tag});
+ writer->WriteString(kv.first);
+
+ // Write the value (FieldValue)
+ writer->WriteTag(
+ {PB_WT_STRING, google_firestore_v1beta1_MapValue_FieldsEntry_value_tag});
+ writer->WriteNestedMessage(
+ [&kv](Writer* writer) { EncodeFieldValueImpl(writer, kv.second); });
+}
+
+ObjectValue::Map::value_type DecodeFieldsEntry(Reader* reader) {
+ Tag tag = reader->ReadTag();
+
+ // TODO(rsgowman): figure out error handling: We can do better than a failed
+ // assertion.
+ FIREBASE_ASSERT(tag.field_number ==
+ google_firestore_v1beta1_MapValue_FieldsEntry_key_tag);
+ FIREBASE_ASSERT(tag.wire_type == PB_WT_STRING);
+ std::string key = reader->ReadString();
+
+ tag = reader->ReadTag();
+ FIREBASE_ASSERT(tag.field_number ==
+ google_firestore_v1beta1_MapValue_FieldsEntry_value_tag);
+ FIREBASE_ASSERT(tag.wire_type == PB_WT_STRING);
+
+ FieldValue value =
+ reader->ReadNestedMessage<FieldValue>(DecodeFieldValueImpl);
+
+ return {key, value};
+}
+
+void EncodeObject(Writer* writer, const ObjectValue& object_value) {
+ return writer->WriteNestedMessage([&object_value](Writer* writer) {
+ // Write each FieldsEntry (i.e. key-value pair.)
+ for (const auto& kv : object_value.internal_value) {
+ writer->WriteTag({PB_WT_STRING,
+ google_firestore_v1beta1_MapValue_FieldsEntry_key_tag});
+ writer->WriteNestedMessage(
+ [&kv](Writer* writer) { return EncodeFieldsEntry(writer, kv); });
+ }
+ });
+}
+
+ObjectValue DecodeObject(Reader* reader) {
+ ObjectValue::Map internal_value = reader->ReadNestedMessage<ObjectValue::Map>(
+ [](Reader* reader) -> ObjectValue::Map {
+ ObjectValue::Map result;
+ while (reader->bytes_left()) {
+ Tag tag = reader->ReadTag();
+ FIREBASE_ASSERT(tag.field_number ==
+ google_firestore_v1beta1_MapValue_fields_tag);
+ FIREBASE_ASSERT(tag.wire_type == PB_WT_STRING);
+
+ ObjectValue::Map::value_type fv =
+ reader->ReadNestedMessage<ObjectValue::Map::value_type>(
+ DecodeFieldsEntry);
+
+ // Sanity check: ensure that this key doesn't already exist in the
+ // map.
+ // TODO(rsgowman): figure out error handling: We can do better than a
+ // failed assertion.
+ FIREBASE_ASSERT(result.find(fv.first) == result.end());
+
+ // Add this key,fieldvalue to the results map.
+ result.emplace(std::move(fv));
+ }
+ return result;
+ });
+ return ObjectValue{internal_value};
+}
+
+} // namespace
+
+Status Serializer::EncodeFieldValue(const FieldValue& field_value,
+ std::vector<uint8_t>* out_bytes) {
+ Writer writer = Writer::Wrap(out_bytes);
+ EncodeFieldValueImpl(&writer, field_value);
+ return writer.status();
+}
+
+FieldValue Serializer::DecodeFieldValue(const uint8_t* bytes, size_t length) {
+ Reader reader = Reader::Wrap(bytes, length);
+ return DecodeFieldValueImpl(&reader);
+}
+
+} // namespace remote
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/src/firebase/firestore/remote/serializer.h b/Firestore/core/src/firebase/firestore/remote/serializer.h
new file mode 100644
index 0000000..7541ef5
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/remote/serializer.h
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_REMOTE_SERIALIZER_H_
+#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_REMOTE_SERIALIZER_H_
+
+#include <cstdint>
+#include <cstdlib>
+#include <vector>
+
+#include "Firestore/Protos/nanopb/google/firestore/v1beta1/document.pb.h"
+#include "Firestore/core/src/firebase/firestore/model/field_value.h"
+#include "Firestore/core/src/firebase/firestore/util/firebase_assert.h"
+#include "Firestore/core/src/firebase/firestore/util/status.h"
+#include "absl/base/attributes.h"
+
+namespace firebase {
+namespace firestore {
+namespace remote {
+
+/**
+ * @brief Converts internal model objects to their equivalent protocol buffer
+ * form, and protocol buffer objects to their equivalent bytes.
+ *
+ * Methods starting with "Encode" convert from a model object to a protocol
+ * buffer (or directly to bytes in cases where the proto uses a 'oneof', due to
+ * limitations in nanopb), and methods starting with "Decode" convert from a
+ * protocol buffer to a model object (or from bytes directly to a model
+ * objects.)
+ */
+// TODO(rsgowman): Original docs also has this: "Throws an exception if a
+// protocol buffer is missing a critical field or has a value we can't
+// interpret." Adjust for C++.
+class Serializer {
+ public:
+ Serializer() {
+ }
+ // TODO(rsgowman): We eventually need the DatabaseId, but can't add it just
+ // yet since it's not used yet (which travis complains about). So for now,
+ // we'll create a parameterless ctor (above) that likely won't exist in the
+ // final version of this class.
+ ///**
+ // * @param database_id Must remain valid for the lifetime of this Serializer
+ // * object.
+ // */
+ // explicit Serializer(const firebase::firestore::model::DatabaseId&
+ // database_id)
+ // : database_id_(database_id) {
+ //}
+
+ /**
+ * Converts the FieldValue model passed into bytes.
+ *
+ * @param field_value the model to convert.
+ * @param[out] out_bytes A buffer to place the output. The bytes will be
+ * appended to this vector.
+ */
+ // TODO(rsgowman): error handling, incl return code.
+ // TODO(rsgowman): If we never support any output except to a vector, it may
+ // make sense to have Serializer own the vector and provide an accessor rather
+ // than asking the user to create it first.
+ util::Status EncodeFieldValue(
+ const firebase::firestore::model::FieldValue& field_value,
+ std::vector<uint8_t>* out_bytes);
+
+ /**
+ * @brief Converts from bytes to the model FieldValue format.
+ *
+ * @param bytes The bytes to convert. It's assumed that exactly all of the
+ * bytes will be used by this conversion.
+ * @return The model equivalent of the bytes.
+ */
+ // TODO(rsgowman): error handling.
+ model::FieldValue DecodeFieldValue(const uint8_t* bytes, size_t length);
+
+ /**
+ * @brief Converts from bytes to the model FieldValue format.
+ *
+ * @param bytes The bytes to convert. It's assumed that exactly all of the
+ * bytes will be used by this conversion.
+ * @return The model equivalent of the bytes.
+ */
+ // TODO(rsgowman): error handling.
+ model::FieldValue DecodeFieldValue(const std::vector<uint8_t>& bytes) {
+ return DecodeFieldValue(bytes.data(), bytes.size());
+ }
+
+ private:
+ // TODO(rsgowman): We don't need the database_id_ yet (but will eventually).
+ // model::DatabaseId* database_id_;
+};
+
+} // namespace remote
+} // namespace firestore
+} // namespace firebase
+
+#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_REMOTE_SERIALIZER_H_
diff --git a/Firestore/core/src/firebase/firestore/timestamp.cc b/Firestore/core/src/firebase/firestore/timestamp.cc
new file mode 100644
index 0000000..a5f0121
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/timestamp.cc
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#include "Firestore/core/include/firebase/firestore/timestamp.h"
+
+#include "Firestore/core/src/firebase/firestore/util/firebase_assert.h"
+
+namespace firebase {
+
+Timestamp::Timestamp() {
+}
+
+Timestamp::Timestamp(const int64_t seconds, const int32_t nanoseconds)
+ : seconds_(seconds), nanoseconds_(nanoseconds) {
+ ValidateBounds();
+}
+
+Timestamp Timestamp::Now() {
+#if !defined(_STLPORT_VERSION)
+ // Use the standard <chrono> library from C++11 if possible.
+ return FromTimePoint(std::chrono::system_clock::now());
+#else
+ // If <chrono> is unavailable, use clock_gettime from POSIX, which supports up
+ // to nanosecond resolution. Note that it's a non-standard function contained
+ // in <time.h>.
+ //
+ // Note: it's possible to check for availability of POSIX clock_gettime using
+ // macros (see "Availability" at https://linux.die.net/man/3/clock_gettime).
+ // However, the only platform where <chrono> isn't available is Android with
+ // STLPort standard library, where clock_gettime is known to be available.
+ timespec now;
+ clock_gettime(CLOCK_REALTIME, &now);
+ return Timestamp(now.tv_sec, now.tv_nsec);
+#endif // !defined(_STLPORT_VERSION)
+}
+
+Timestamp Timestamp::FromTimeT(const time_t seconds_since_unix_epoch) {
+ return {seconds_since_unix_epoch, 0};
+}
+
+#if !defined(_STLPORT_VERSION)
+Timestamp Timestamp::FromTimePoint(
+ const std::chrono::time_point<std::chrono::system_clock> time_point) {
+ namespace chr = std::chrono;
+ const auto epoch_time = time_point.time_since_epoch();
+ auto seconds = chr::duration_cast<chr::duration<int64_t>>(epoch_time);
+ auto nanoseconds = chr::duration_cast<chr::nanoseconds>(epoch_time - seconds);
+ FIREBASE_DEV_ASSERT(nanoseconds.count() < 1 * 1000 * 1000 * 1000);
+
+ if (nanoseconds.count() < 0) {
+ // Timestamp format always has a positive number of nanoseconds that is
+ // counting forward. For negative time, we need to transform chrono
+ // representation of (negative seconds s1 + negative nanoseconds ns1) to
+ // (negative seconds s2 + positive nanoseconds ns2). Since nanosecond part
+ // is always less than 1 second in our representation, instead of starting
+ // at s1 and going back ns1 nanoseconds, start at (s1 minus one second) and
+ // go *forward* ns2 = (1 second + ns1, ns1 < 0) nanoseconds.
+ //
+ // Note: if nanoseconds are negative, it must mean that seconds are
+ // non-positive, but the formula would still be valid, so no need to check.
+ seconds = seconds - chr::seconds(1);
+ nanoseconds = chr::seconds(1) + nanoseconds;
+ }
+
+ const Timestamp result{seconds.count(),
+ static_cast<int32_t>(nanoseconds.count())};
+ result.ValidateBounds();
+ return result;
+}
+
+#endif // !defined(_STLPORT_VERSION)
+
+std::string Timestamp::ToString() const {
+ return std::string("Timestamp(seconds=") + std::to_string(seconds_) +
+ ", nanoseconds=" + std::to_string(nanoseconds_) + ")";
+}
+
+void Timestamp::ValidateBounds() const {
+ FIREBASE_ASSERT_MESSAGE(nanoseconds_ >= 0,
+ "Timestamp nanoseconds out of range: %d",
+ nanoseconds_);
+ FIREBASE_ASSERT_MESSAGE(nanoseconds_ < 1e9,
+ "Timestamp nanoseconds out of range: %d",
+ nanoseconds_);
+ // Midnight at the beginning of 1/1/1 is the earliest timestamp Firestore
+ // supports.
+ FIREBASE_ASSERT_MESSAGE(seconds_ >= -62135596800L,
+ "Timestamp seconds out of range: %lld", seconds_);
+ // This will break in the year 10,000.
+ FIREBASE_ASSERT_MESSAGE(seconds_ < 253402300800L,
+ "Timestamp seconds out of range: %lld", seconds_);
+}
+
+} // namespace firebase
+
+namespace std {
+size_t hash<firebase::Timestamp>::operator()(
+ const firebase::Timestamp& timestamp) const {
+ // Note: if sizeof(size_t) == 4, this discards high-order bits of seconds.
+ return 37 * static_cast<size_t>(timestamp.seconds()) +
+ static_cast<size_t>(timestamp.nanoseconds());
+}
+
+} // namespace std
diff --git a/Firestore/core/src/firebase/firestore/util/CMakeLists.txt b/Firestore/core/src/firebase/firestore/util/CMakeLists.txt
index ce81363..701d288 100644
--- a/Firestore/core/src/firebase/firestore/util/CMakeLists.txt
+++ b/Firestore/core/src/firebase/firestore/util/CMakeLists.txt
@@ -12,8 +12,136 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-add_library(
+# firebase_firestore_util is the interface of this module. The rest of the
+# libraries in here are an implementation detail of making this a
+# mutli-platform build.
+
+include(CheckSymbolExists)
+include(CheckIncludeFiles)
+
+cc_library(
+ firebase_firestore_util_base
+ SOURCES
+ string_printf.cc
+ string_printf.h
+ DEPENDS
+ absl_base
+)
+
+## assert and log
+
+cc_library(
+ firebase_firestore_util_log_stdio
+ SOURCES
+ firebase_assert.h
+ assert_stdio.cc
+ log.h
+ log_stdio.cc
+ DEPENDS
+ firebase_firestore_util_base
+ absl_base
+ EXCLUDE_FROM_ALL
+)
+
+cc_library(
+ firebase_firestore_util_log_apple
+ SOURCES
+ firebase_assert.h
+ assert_apple.mm
+ log.h
+ log_apple.mm
+ string_apple.h
+ DEPENDS
+ FirebaseCore
+ absl_strings
+ EXCLUDE_FROM_ALL
+)
+
+if(APPLE)
+ set(
+ FIREBASE_FIRESTORE_UTIL_LOG
+ firebase_firestore_util_log_apple
+ )
+else()
+ set(
+ FIREBASE_FIRESTORE_UTIL_LOG
+ firebase_firestore_util_log_stdio
+ )
+endif()
+
+
+## secure_random
+
+check_symbol_exists(arc4random stdlib.h HAVE_ARC4RANDOM)
+cc_library(
+ firebase_firestore_util_random_arc4random
+ SOURCES
+ secure_random_arc4random.cc
+)
+
+get_target_property(
+ CMAKE_REQUIRED_INCLUDES
+ OpenSSL::Crypto INTERFACE_INCLUDE_DIRECTORIES
+)
+check_include_files(openssl/rand.h HAVE_OPENSSL_RAND_H)
+cc_library(
+ firebase_firestore_util_random_openssl
+ SOURCES
+ secure_random_openssl.cc
+ DEPENDS
+ OpenSSL::Crypto
+)
+
+if(HAVE_ARC4RANDOM)
+ set(
+ FIREBASE_FIRESTORE_UTIL_RANDOM
+ firebase_firestore_util_random_arc4random
+ )
+
+elseif(HAVE_OPENSSL_RAND_H)
+ set(
+ FIREBASE_FIRESTORE_UTIL_RANDOM
+ firebase_firestore_util_random_openssl
+ )
+
+else()
+ message(FATAL_ERROR "No implementation for SecureRandom available.")
+
+endif()
+
+
+## main library
+
+configure_file(
+ config.h.in
+ config.h
+)
+
+cc_library(
firebase_firestore_util
- autoid.cc
- secure_random_arc4random.cc
+ SOURCES
+ autoid.cc
+ autoid.h
+ bits.cc
+ bits.h
+ comparator_holder.h
+ comparison.cc
+ comparison.h
+ config.h
+ iterator_adaptors.h
+ ordered_code.cc
+ ordered_code.h
+ secure_random.h
+ status.cc
+ status.h
+ statusor.cc
+ statusor.h
+ statusor_internals.h
+ string_util.cc
+ string_util.h
+ DEPENDS
+ absl_base
+ firebase_firestore_util_base
+ ${FIREBASE_FIRESTORE_UTIL_LOG}
+ ${FIREBASE_FIRESTORE_UTIL_RANDOM}
)
diff --git a/Firestore/core/src/firebase/firestore/util/assert_apple.mm b/Firestore/core/src/firebase/firestore/util/assert_apple.mm
new file mode 100644
index 0000000..9b6a651
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/util/assert_apple.mm
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2018 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/Foundation.h>
+
+// TODO(wilhuff): match basenames so this can move up top
+#include "Firestore/core/src/firebase/firestore/util/firebase_assert.h"
+#include "Firestore/core/src/firebase/firestore/util/string_apple.h"
+
+namespace firebase {
+namespace firestore {
+namespace util {
+
+void FailAssert(const char* file,
+ const char* func,
+ const int line,
+ const char* format,
+ ...) {
+ va_list args;
+ va_start(args, format);
+ NSString* description =
+ [[NSString alloc] initWithFormat:WrapNSStringNoCopy(format)
+ arguments:args];
+ va_end(args);
+ [[NSAssertionHandler currentHandler]
+ handleFailureInFunction:WrapNSStringNoCopy(func)
+ file:WrapNSStringNoCopy(file)
+ lineNumber:line
+ description:@"FIRESTORE INTERNAL ASSERTION FAILED: %@",
+ description];
+ abort();
+}
+
+} // namespace util
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/src/firebase/firestore/util/assert_stdio.cc b/Firestore/core/src/firebase/firestore/util/assert_stdio.cc
new file mode 100644
index 0000000..e01e564
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/util/assert_stdio.cc
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#include <cstdarg>
+#include <stdexcept>
+#include <string>
+
+#include "Firestore/core/src/firebase/firestore/util/firebase_assert.h"
+#include "Firestore/core/src/firebase/firestore/util/string_printf.h"
+#include "absl/base/config.h"
+
+namespace firebase {
+namespace firestore {
+namespace util {
+
+void FailAssert(const char* file,
+ const char* func,
+ const int line,
+ const char* format,
+ ...) {
+ std::string message;
+ StringAppendF(&message, "ASSERT: %s(%d) %s: ", file, line, func);
+
+ va_list args;
+ va_start(args, format);
+ StringAppendV(&message, format, args);
+ va_end(args);
+
+#if ABSL_HAVE_EXCEPTIONS
+ throw std::logic_error(message);
+
+#else
+ fprintf(stderr, "%s\n", message.c_str());
+ std::terminate();
+#endif
+}
+
+} // namespace util
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/Port/bits.cc b/Firestore/core/src/firebase/firestore/util/bits.cc
index 3e61223..0bfe4c3 100644
--- a/Firestore/Port/bits.cc
+++ b/Firestore/core/src/firebase/firestore/util/bits.cc
@@ -14,11 +14,13 @@
* limitations under the License.
*/
-#include "Firestore/Port/bits.h"
+#include "Firestore/core/src/firebase/firestore/util/bits.h"
-#include <assert.h>
+#include "Firestore/core/src/firebase/firestore/util/firebase_assert.h"
-namespace Firestore {
+namespace firebase {
+namespace firestore {
+namespace util {
int Bits::Log2Floor_Portable(uint32_t n) {
if (n == 0) return -1;
@@ -32,8 +34,10 @@ int Bits::Log2Floor_Portable(uint32_t n) {
log += shift;
}
}
- assert(value == 1);
+ FIREBASE_ASSERT(value == 1);
return log;
}
-} // namespace Firestore
+} // namespace util
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/Port/bits.h b/Firestore/core/src/firebase/firestore/util/bits.h
index d212bf8..94a018f 100644
--- a/Firestore/Port/bits.h
+++ b/Firestore/core/src/firebase/firestore/util/bits.h
@@ -14,29 +14,33 @@
* limitations under the License.
*/
-#ifndef IPHONE_FIRESTORE_PORT_BITS_H_
-#define IPHONE_FIRESTORE_PORT_BITS_H_
+#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_BITS_H_
+#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_BITS_H_
// Various bit-twiddling functions, all of which are static members of the Bits
// class (making it effectively a namespace). Operands are unsigned integers.
// Munging bits in _signed_ integers is fraught with peril! For example,
// -5 << n has undefined behavior (for some values of n).
-#include <stdint.h>
+#include <cstdint>
class Bits_Port32_Test;
class Bits_Port64_Test;
-namespace Firestore {
+namespace firebase {
+namespace firestore {
+namespace util {
class Bits {
public:
- // Return floor(log2(n)) for positive integer n. Returns -1 iff n == 0.
+ /** Return floor(log2(n)) for positive integer n. Returns -1 iff n == 0. */
static int Log2Floor(uint32_t n);
static int Log2Floor64(uint64_t n);
- // Potentially faster version of Log2Floor() that returns an
- // undefined value if n == 0
+ /**
+ * Potentially faster version of Log2Floor() that returns an
+ * undefined value if n == 0.
+ */
static int Log2FloorNonZero(uint32_t n);
static int Log2FloorNonZero64(uint64_t n);
@@ -51,8 +55,8 @@ class Bits {
void operator=(Bits const&) = delete;
// Allow tests to call _Portable variants directly.
- friend class ::Bits_Port32_Test;
- friend class ::Bits_Port64_Test;
+ friend class Bits_Port32_Test;
+ friend class Bits_Port64_Test;
};
// ------------------------------------------------------------------------
@@ -135,26 +139,28 @@ inline int Bits::Log2FloorNonZero_Portable(uint32_t n) {
// Log2Floor64() is defined in terms of Log2Floor32(), Log2FloorNonZero32()
inline int Bits::Log2Floor64_Portable(uint64_t n) {
- const uint32_t topbits = static_cast<uint32_t>(n >> 32);
- if (topbits == 0) {
+ const auto top_bits = static_cast<uint32_t>(n >> 32);
+ if (top_bits == 0) {
// Top bits are zero, so scan in bottom bits
return Log2Floor(static_cast<uint32_t>(n));
} else {
- return 32 + Log2FloorNonZero(topbits);
+ return 32 + Log2FloorNonZero(top_bits);
}
}
// Log2FloorNonZero64() is defined in terms of Log2FloorNonZero32()
inline int Bits::Log2FloorNonZero64_Portable(uint64_t n) {
- const uint32_t topbits = static_cast<uint32_t>(n >> 32);
- if (topbits == 0) {
+ const auto top_bits = static_cast<uint32_t>(n >> 32);
+ if (top_bits == 0) {
// Top bits are zero, so scan in bottom bits
return Log2FloorNonZero(static_cast<uint32_t>(n));
} else {
- return 32 + Log2FloorNonZero(topbits);
+ return 32 + Log2FloorNonZero(top_bits);
}
}
-} // namespace Firestore
+} // namespace util
+} // namespace firestore
+} // namespace firebase
-#endif // IPHONE_FIRESTORE_PORT_BITS_H_
+#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_BITS_H_
diff --git a/Firestore/core/src/firebase/firestore/util/comparator_holder.h b/Firestore/core/src/firebase/firestore/util/comparator_holder.h
new file mode 100644
index 0000000..c7f6144
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/util/comparator_holder.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_COMPARATOR_HOLDER_H_
+#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_COMPARATOR_HOLDER_H_
+
+#include <type_traits>
+
+namespace firebase {
+namespace firestore {
+namespace util {
+
+/**
+ * A holder of some comparator (e.g. std::less or util::Comparator) that
+ * implements the empty base optimization for the common case where the
+ * comparator occupies no storage.
+ */
+template <typename C, bool = std::is_empty<C>::value>
+class ComparatorHolder {
+ protected:
+ explicit ComparatorHolder(const C& comparator) noexcept
+ : comparator_(comparator) {
+ }
+
+ const C& comparator() const noexcept {
+ return comparator_;
+ }
+
+ private:
+ C comparator_;
+};
+
+// Implementation to use when C is empty.
+template <typename C>
+class ComparatorHolder<C, true> : private C {
+ protected:
+ explicit ComparatorHolder(const C& /* comparator */) noexcept {
+ }
+
+ const C& comparator() const noexcept {
+ return *this;
+ }
+};
+
+} // namespace util
+} // namespace firestore
+} // namespace firebase
+
+#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_COMPARATOR_HOLDER_H_
diff --git a/Firestore/core/src/firebase/firestore/util/comparison.cc b/Firestore/core/src/firebase/firestore/util/comparison.cc
new file mode 100644
index 0000000..5ac4c27
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/util/comparison.cc
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#include "Firestore/core/src/firebase/firestore/util/comparison.h"
+
+#include <cmath>
+#include <limits>
+
+using std::isnan;
+
+namespace firebase {
+namespace firestore {
+namespace util {
+
+bool Comparator<absl::string_view>::operator()(
+ const absl::string_view& left, const absl::string_view& right) const {
+ // TODO(wilhuff): truncation aware comparison
+ return left < right;
+}
+
+bool Comparator<double>::operator()(double left, double right) const {
+ // NaN sorts equal to itself and before any other number.
+ if (left < right) {
+ return true;
+ } else if (left >= right) {
+ return false;
+ } else {
+ // One or both left and right is NaN.
+ return isnan(left) && !isnan(right);
+ }
+}
+
+static constexpr double INT64_MIN_VALUE_AS_DOUBLE =
+ static_cast<double>(std::numeric_limits<int64_t>::min());
+
+static constexpr double INT64_MAX_VALUE_AS_DOUBLE =
+ static_cast<double>(std::numeric_limits<int64_t>::max());
+
+ComparisonResult CompareMixedNumber(double double_value, int64_t int64_value) {
+ // LLONG_MIN has an exact representation as double, so to check for a value
+ // outside the range representable by long, we have to check for strictly less
+ // than LLONG_MIN. Note that this also handles negative infinity.
+ if (double_value < INT64_MIN_VALUE_AS_DOUBLE) {
+ return ComparisonResult::Ascending;
+ }
+
+ // LLONG_MAX has no exact representation as double (casting as we've done
+ // makes 2^63, which is larger than LLONG_MAX), so consider any value greater
+ // than or equal to the threshold to be out of range. This also handles
+ // positive infinity.
+ if (double_value >= INT64_MAX_VALUE_AS_DOUBLE) {
+ return ComparisonResult::Descending;
+ }
+
+ // In Firestore NaN is defined to compare before all other numbers.
+ if (isnan(double_value)) {
+ return ComparisonResult::Ascending;
+ }
+
+ auto double_as_int64 = static_cast<int64_t>(double_value);
+ ComparisonResult cmp = Compare<int64_t>(double_as_int64, int64_value);
+ if (cmp != ComparisonResult::Same) {
+ return cmp;
+ }
+
+ // At this point the long representations are equal but this could be due to
+ // rounding.
+ auto int64_as_double = static_cast<double>(int64_value);
+ return Compare<double>(double_value, int64_as_double);
+}
+
+/** Helper to normalize a double and then return the raw bits as a uint64_t. */
+uint64_t DoubleBits(double d) {
+ if (isnan(d)) {
+ d = NAN;
+ }
+
+ // Unlike C, C++ does not define type punning through a union type.
+
+ // TODO(wilhuff): replace with absl::bit_cast
+ static_assert(sizeof(double) == sizeof(uint64_t), "doubles must be 8 bytes");
+ uint64_t bits;
+ memcpy(&bits, &d, sizeof(bits));
+ return bits;
+}
+
+bool DoubleBitwiseEquals(double left, double right) {
+ return DoubleBits(left) == DoubleBits(right);
+}
+
+size_t DoubleBitwiseHash(double d) {
+ uint64_t bits = DoubleBits(d);
+ // Note that x ^ (x >> 32) works fine for both 32 and 64 bit definitions of
+ // size_t
+ return static_cast<size_t>(bits) ^ static_cast<size_t>(bits >> 32);
+}
+
+} // namespace util
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/src/firebase/firestore/util/comparison.h b/Firestore/core/src/firebase/firestore/util/comparison.h
new file mode 100644
index 0000000..23207f5
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/util/comparison.h
@@ -0,0 +1,181 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_COMPARISON_H_
+#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_COMPARISON_H_
+
+#if __OBJC__
+#import <Foundation/Foundation.h>
+#endif
+
+#include <sys/types.h>
+
+#include <cstdint>
+#include <functional>
+#include <string>
+#include <vector>
+
+#include "Firestore/core/src/firebase/firestore/util/string_apple.h"
+#include "absl/strings/string_view.h"
+
+namespace firebase {
+namespace firestore {
+namespace util {
+
+/**
+ * An enumeration describing the result of a three-way comparison among
+ * strongly-ordered values (i.e. where comparison between values always yields
+ * less-than, equal-to, or greater-than).
+ *
+ * This is equivalent to:
+ *
+ * * NSComparisonResult from the iOS/macOS Foundation framework.
+ * * std::strong_ordering from C++20
+ *
+ * The values of the constants are specifically chosen so as to make casting
+ * between this type and NSComparisonResult possible.
+ */
+enum class ComparisonResult {
+ /** The left hand side was less than the right. */
+ Ascending = -1,
+
+ /** The left hand side was equal to the right. */
+ Same = 0,
+
+ /** The left hand side was greater than the right. */
+ Descending = 1
+};
+
+/**
+ * Returns the reverse order (i.e. Ascending => Descending) etc.
+ */
+constexpr ComparisonResult ReverseOrder(ComparisonResult result) {
+ return static_cast<ComparisonResult>(-static_cast<int>(result));
+}
+
+/**
+ * A generalized comparator for types in Firestore, with ordering defined
+ * according to Firestore's semantics. This is useful as argument to e.g.
+ * std::sort.
+ *
+ * Comparators are only defined for the limited set of types for which
+ * Firestore defines an ordering.
+ */
+template <typename T>
+struct Comparator {
+ // By default comparison is not defined
+};
+
+/** Compares two strings. */
+template <>
+struct Comparator<absl::string_view> {
+ bool operator()(const absl::string_view& left,
+ const absl::string_view& right) const;
+};
+
+/** Compares two bools: false < true. */
+template <>
+struct Comparator<bool> : public std::less<bool> {};
+
+/** Compares two int32_t. */
+template <>
+struct Comparator<int32_t> : public std::less<int32_t> {};
+
+/** Compares two int64_t. */
+template <>
+struct Comparator<int64_t> : public std::less<int64_t> {};
+
+/** Compares two doubles (using Firestore semantics for NaN). */
+template <>
+struct Comparator<double> {
+ bool operator()(double left, double right) const;
+};
+
+/** Compare two byte sequences. */
+// TODO(wilhuff): perhaps absl::Span<uint8_t> would be better?
+template <>
+struct Comparator<std::vector<uint8_t>>
+ : public std::less<std::vector<uint8_t>> {};
+
+/**
+ * Perform a three-way comparison between the left and right values using
+ * the appropriate Comparator for the values based on their type.
+ */
+template <typename T>
+ComparisonResult Compare(const T& left, const T& right) {
+ Comparator<T> less_than;
+ if (less_than(left, right)) {
+ return ComparisonResult::Ascending;
+ } else if (less_than(right, left)) {
+ return ComparisonResult::Descending;
+ } else {
+ return ComparisonResult::Same;
+ }
+}
+
+#if __OBJC__
+/**
+ * Returns true if the given ComparisonResult and NSComparisonResult have the
+ * same integer values (at compile time).
+ */
+constexpr bool EqualValue(ComparisonResult lhs, NSComparisonResult rhs) {
+ return static_cast<int>(lhs) == static_cast<int>(rhs);
+}
+
+/**
+ * Performs a three-way comparison, identically to Compare, but converts the
+ * result to an NSComparisonResult.
+ *
+ * This function exists for interoperation with Objective-C++ and should
+ * eventually be removed.
+ */
+template <typename T>
+inline NSComparisonResult WrapCompare(const T& left, const T& right) {
+ static_assert(EqualValue(ComparisonResult::Ascending, NSOrderedAscending),
+ "Ascending invalid");
+ static_assert(EqualValue(ComparisonResult::Same, NSOrderedSame),
+ "Same invalid");
+ static_assert(EqualValue(ComparisonResult::Descending, NSOrderedDescending),
+ "Descending invalid");
+
+ return static_cast<NSComparisonResult>(Compare<T>(left, right));
+}
+#endif
+
+/** Compares a double and an int64_t. */
+ComparisonResult CompareMixedNumber(double doubleValue, int64_t longValue);
+
+/** Normalizes a double and then return the raw bits as a uint64_t. */
+uint64_t DoubleBits(double d);
+
+/**
+ * Compares the bitwise representation of two doubles, but normalizes NaN
+ * values. This is similar to what the backend and android clients do, including
+ * comparing -0.0 as not equal to 0.0.
+ */
+bool DoubleBitwiseEquals(double left, double right);
+
+/**
+ * Computes a bitwise hash of a double, but normalizes NaN values, suitable for
+ * use when using FSTDoublesAreBitwiseEqual for equality.
+ */
+size_t DoubleBitwiseHash(double d);
+
+} // namespace util
+} // namespace firestore
+} // namespace firebase
+
+#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_COMPARISON_H_
diff --git a/Firestore/core/src/firebase/firestore/util/config.h.in b/Firestore/core/src/firebase/firestore/util/config.h.in
new file mode 100644
index 0000000..e7a0c03
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/util/config.h.in
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_CONFIG_H_
+#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_CONFIG_H_
+
+// This header defines macros for all available platform configuration values.
+// When building with CMake, it will substitute the lines marked with
+// cmakedefine with values corresponding to the local configuration.
+//
+// On Apple platforms we support building via CocoaPods without CMake. When
+// building this way we can't test the presence of features before building so
+// predefine all the platform-support feature macros to their expected values.
+
+#cmakedefine HAVE_ARC4RANDOM 1
+#if COCOAPODS
+# define HAVE_ARC4RANDOM 1
+#endif
+
+#cmakedefine HAVE_OPENSSL_RAND_H 1
+
+#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_CONFIG_H_
diff --git a/Firestore/core/src/firebase/firestore/util/error_apple.h b/Firestore/core/src/firebase/firestore/util/error_apple.h
new file mode 100644
index 0000000..e7c77c9
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/util/error_apple.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_ERROR_APPLE_H_
+#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_ERROR_APPLE_H_
+
+// Everything in this header exists for compatibility with Objective-C.
+#if __OBJC__
+
+#import <Foundation/Foundation.h>
+
+#include "Firestore/Source/Public/FIRFirestoreErrors.h" // for FIRFirestoreErrorDomain
+#include "Firestore/core/include/firebase/firestore/firestore_errors.h"
+#include "Firestore/core/src/firebase/firestore/util/status.h"
+#include "Firestore/core/src/firebase/firestore/util/string_apple.h"
+#include "absl/strings/string_view.h"
+
+namespace firebase {
+namespace firestore {
+namespace util {
+
+// Translates a set of error_code and error_msg to an NSError.
+inline NSError* MakeNSError(const int64_t error_code,
+ const absl::string_view error_msg) {
+ if (error_code == FirestoreErrorCode::Ok) {
+ return nil;
+ }
+ return [NSError
+ errorWithDomain:FIRFirestoreErrorDomain
+ code:error_code
+ userInfo:@{NSLocalizedDescriptionKey : WrapNSString(error_msg)}];
+}
+
+inline NSError* MakeNSError(const util::Status& status) {
+ return MakeNSError(status.code(), status.error_message());
+}
+
+} // namespace util
+} // namespace firestore
+} // namespace firebase
+
+#endif // __OBJC__
+
+#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_ERROR_APPLE_H_
diff --git a/Firestore/core/src/firebase/firestore/util/firebase_assert.h b/Firestore/core/src/firebase/firestore/util/firebase_assert.h
new file mode 100644
index 0000000..6a9c2eb
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/util/firebase_assert.h
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2018 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.
+ */
+
+// To avoid naming-collision, this header is called firebase_assert.h instead
+// of assert.h.
+
+#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_FIREBASE_ASSERT_H_
+#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_FIREBASE_ASSERT_H_
+
+#include <cstdlib>
+
+#include "Firestore/core/src/firebase/firestore/util/log.h"
+#include "absl/base/attributes.h"
+
+#define FIREBASE_EXPAND_STRINGIFY_(X) #X
+#define FIREBASE_EXPAND_STRINGIFY(X) FIREBASE_EXPAND_STRINGIFY_(X)
+
+// FIREBASE_ASSERT_* macros are not compiled out of release builds. They should
+// be used for assertions that need to be propagated to end-users of SDKs.
+// FIREBASE_DEV_ASSERT_* macros are compiled out of release builds, similar to
+// the C assert() macro. They should be used for internal assertions that are
+// only shown to SDK developers.
+
+// Assert condition is true, if it's false log an assert with the specified
+// expression as a string.
+#define FIREBASE_ASSERT_WITH_EXPRESSION(condition, expression) \
+ do { \
+ if (!(condition)) { \
+ firebase::firestore::util::FailAssert( \
+ __FILE__, __PRETTY_FUNCTION__, __LINE__, \
+ FIREBASE_EXPAND_STRINGIFY(expression)); \
+ } \
+ } while (0)
+
+// Assert condition is true, if it's false log an assert with the specified
+// expression as a string. Compiled out of release builds.
+#if defined(NDEBUG)
+#define FIREBASE_DEV_ASSERT_WITH_EXPRESSION(condition, expression) \
+ { (void)(condition); }
+#else
+#define FIREBASE_DEV_ASSERT_WITH_EXPRESSION(condition, expression) \
+ FIREBASE_ASSERT_WITH_EXPRESSION(condition, expression)
+#endif // defined(NDEBUG)
+
+// Custom assert() implementation that is not compiled out in release builds.
+#define FIREBASE_ASSERT(expression) \
+ FIREBASE_ASSERT_WITH_EXPRESSION(expression, expression)
+
+// Custom assert() implementation that is compiled out in release builds.
+// Compiled out of release builds.
+#define FIREBASE_DEV_ASSERT(expression) \
+ FIREBASE_DEV_ASSERT_WITH_EXPRESSION(expression, expression)
+
+// Assert condition is true otherwise display the specified expression,
+// message and abort.
+#define FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION(condition, expression, ...) \
+ do { \
+ if (!(condition)) { \
+ firebase::firestore::util::LogError( \
+ FIREBASE_EXPAND_STRINGIFY(expression)); \
+ firebase::firestore::util::FailAssert(__FILE__, __PRETTY_FUNCTION__, \
+ __LINE__, __VA_ARGS__); \
+ } \
+ } while (0)
+
+// Assert condition is true otherwise display the specified expression,
+// message and abort. Compiled out of release builds.
+#if defined(NDEBUG)
+#define FIREBASE_DEV_ASSERT_MESSAGE_WITH_EXPRESSION(condition, expression, \
+ ...) \
+ { (void)(condition); }
+#else
+#define FIREBASE_DEV_ASSERT_MESSAGE_WITH_EXPRESSION(condition, expression, \
+ ...) \
+ FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION(condition, expression, __VA_ARGS__)
+#endif // defined(NDEBUG)
+
+// Assert expression is true otherwise display the specified message and
+// abort.
+#define FIREBASE_ASSERT_MESSAGE(expression, ...) \
+ FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION(expression, expression, __VA_ARGS__)
+
+// Assert expression is true otherwise display the specified message and
+// abort. Compiled out of release builds.
+#define FIREBASE_DEV_ASSERT_MESSAGE(expression, ...) \
+ FIREBASE_DEV_ASSERT_MESSAGE_WITH_EXPRESSION(expression, expression, \
+ __VA_ARGS__)
+
+// Indicates an area of the code that cannot be reached (except possibly due to
+// undefined behaviour or other similar badness). The only reasonable thing to
+// do in these cases is to immediately abort.
+#define FIREBASE_UNREACHABLE() abort()
+
+namespace firebase {
+namespace firestore {
+namespace util {
+
+// A no-return helper function. To raise an assertion, use Macro instead.
+ABSL_ATTRIBUTE_NORETURN void FailAssert(
+ const char* file, const char* func, int line, const char* format, ...);
+
+} // namespace util
+} // namespace firestore
+} // namespace firebase
+
+#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_FIREBASE_ASSERT_H_
diff --git a/Firestore/core/src/firebase/firestore/util/iterator_adaptors.h b/Firestore/core/src/firebase/firestore/util/iterator_adaptors.h
new file mode 100644
index 0000000..042fd72
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/util/iterator_adaptors.h
@@ -0,0 +1,812 @@
+/*
+ * Copyright 2005, 2018 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.
+ */
+
+// Provides some iterator adaptors and views.
+
+#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_ITERATOR_ADAPTORS_H_
+#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_ITERATOR_ADAPTORS_H_
+
+#include <iterator>
+#include <memory>
+#include <type_traits>
+
+#include "absl/base/port.h"
+#include "absl/meta/type_traits.h"
+
+namespace firebase {
+namespace firestore {
+namespace util {
+
+namespace internal {
+
+// value == true if Iter prohibits modification of its pointees.
+template <typename Iter>
+struct IsConstIter
+ : std::is_const<typename std::remove_reference<
+ typename std::iterator_traits<Iter>::reference>::type> {};
+
+template <bool Cond, typename T>
+struct AddConstIf : std::conditional<Cond, const T, T> {};
+
+// SynthIterTraits propagates the constness of the 'BaseIter' iterator
+// type to its own exported 'pointer' and 'reference' typedefs.
+template <typename BaseIter, typename Val>
+struct SynthIterTraits : std::iterator_traits<BaseIter> {
+ private:
+ static constexpr bool kIterConst = IsConstIter<BaseIter>::value;
+
+ public:
+ using value_type = typename std::remove_cv<Val>::type;
+ using pointer = typename AddConstIf<kIterConst, Val>::type*;
+ using reference = typename AddConstIf<kIterConst, Val>::type&;
+};
+
+// PointeeSynthIterTraits is similar to SynthIterTraits, but the 'Ptr'
+// parameter is a pointer-like type, and value_type is the pointee.
+template <typename BaseIter, typename Ptr>
+struct PointeeSynthIterTraits : std::iterator_traits<BaseIter> {
+ private:
+ static constexpr bool kIterConst = IsConstIter<BaseIter>::value;
+
+ public:
+ using value_type = typename std::pointer_traits<Ptr>::element_type;
+ using pointer = typename AddConstIf<kIterConst, value_type>::type*;
+ using reference = typename AddConstIf<kIterConst, value_type>::type&;
+};
+
+// CRTP base class for generating iterator adaptors.
+// 'Sub' is the derived type, and 'Policy' encodes
+// all of the behavior for the adaptor.
+// Policy requirements:
+// - type 'underlying_iterator': the underlying iterator type.
+// - type 'adapted_traits': the traits of the adaptor.
+// - static 'Extract(underlying_iterator)': convert iterator to reference.
+//
+template <typename Sub, typename Policy>
+class IteratorAdaptorBase {
+ private:
+ // Everything needed from the Policy type is expressed in this section.
+ using Iterator = typename Policy::underlying_iterator;
+ using OutTraits = typename Policy::adapted_traits;
+ static typename OutTraits::reference Extract(const Iterator& it) {
+ return Policy::Extract(it);
+ }
+
+ public:
+ using iterator_category = typename OutTraits::iterator_category;
+ using value_type = typename OutTraits::value_type;
+ using pointer = typename OutTraits::pointer;
+ using reference = typename OutTraits::reference;
+ using difference_type = typename OutTraits::difference_type;
+
+ IteratorAdaptorBase() : it_() {
+ }
+ // NOLINTNEXTLINE(runtime/explicit)
+ IteratorAdaptorBase(Iterator it) : it_(it) {
+ }
+
+ Sub& sub() {
+ return static_cast<Sub&>(*this);
+ }
+ const Sub& sub() const {
+ return static_cast<const Sub&>(*this);
+ }
+
+ const Iterator& base() const {
+ return it_;
+ }
+
+ reference get() const {
+ return Extract(base());
+ }
+ reference operator*() const {
+ return get();
+ }
+ pointer operator->() const {
+ return &get();
+ }
+ reference operator[](difference_type d) const {
+ return *(sub() + d);
+ }
+
+ Sub& operator++() {
+ ++it_;
+ return sub();
+ }
+ Sub& operator--() {
+ --it_;
+ return sub();
+ }
+ Sub operator++(int /*unused*/) {
+ return it_++;
+ }
+ Sub operator--(int /*unused*/) {
+ return it_--;
+ }
+
+ Sub& operator+=(difference_type d) {
+ it_ += d;
+ return sub();
+ }
+ Sub& operator-=(difference_type d) {
+ it_ -= d;
+ return sub();
+ }
+
+ bool operator==(Sub b) const {
+ return base() == b.base();
+ }
+ bool operator!=(Sub b) const {
+ return base() != b.base();
+ }
+ // These shouldn't be necessary, as implicit conversion from 'Iterator'
+ // should be enough to make such comparisons work.
+ bool operator==(Iterator b) const {
+ return *this == Sub(b);
+ }
+ bool operator!=(Iterator b) const {
+ return *this != Sub(b);
+ }
+
+ friend Sub operator+(Sub it, difference_type d) {
+ return it.base() + d;
+ }
+ friend Sub operator+(difference_type d, Sub it) {
+ return it + d;
+ }
+ friend Sub operator-(Sub it, difference_type d) {
+ return it.base() - d;
+ }
+ friend difference_type operator-(Sub a, Sub b) {
+ return a.base() - b.base();
+ }
+
+ friend bool operator<(Sub a, Sub b) {
+ return a.base() < b.base();
+ }
+ friend bool operator>(Sub a, Sub b) {
+ return a.base() > b.base();
+ }
+ friend bool operator<=(Sub a, Sub b) {
+ return a.base() <= b.base();
+ }
+ friend bool operator>=(Sub a, Sub b) {
+ return a.base() >= b.base();
+ }
+
+ private:
+ Iterator it_;
+};
+
+template <typename It>
+struct FirstPolicy {
+ using underlying_iterator = It;
+ using adapted_traits =
+ SynthIterTraits<underlying_iterator,
+ typename std::iterator_traits<
+ underlying_iterator>::value_type::first_type>;
+ static typename adapted_traits::reference Extract(
+ const underlying_iterator& it) {
+ return it->first;
+ }
+};
+
+template <typename It>
+struct SecondPolicy {
+ using underlying_iterator = It;
+ using adapted_traits =
+ SynthIterTraits<underlying_iterator,
+ typename std::iterator_traits<
+ underlying_iterator>::value_type::second_type>;
+ static typename adapted_traits::reference Extract(
+ const underlying_iterator& it) {
+ return it->second;
+ }
+};
+
+template <typename It>
+struct SecondPtrPolicy {
+ using underlying_iterator = It;
+ using adapted_traits =
+ PointeeSynthIterTraits<underlying_iterator,
+ typename std::iterator_traits<
+ underlying_iterator>::value_type::second_type>;
+ static typename adapted_traits::reference Extract(
+ const underlying_iterator& it) {
+ return *it->second;
+ }
+};
+
+template <typename It>
+struct PtrPolicy {
+ using underlying_iterator = It;
+ using adapted_traits = PointeeSynthIterTraits<
+ underlying_iterator,
+ typename std::iterator_traits<underlying_iterator>::value_type>;
+ static typename adapted_traits::reference Extract(
+ const underlying_iterator& it) {
+ return **it;
+ }
+};
+
+} // namespace internal
+
+// In both iterator adaptors, iterator_first<> and iterator_second<>,
+// we build a new iterator based on a parameterized iterator type, "It".
+// The value type, "Val" is determined by "It::value_type::first" or
+// "It::value_type::second", respectively.
+
+// iterator_first<> adapts an iterator to return the first value of a pair.
+// It is equivalent to calling it->first on every value.
+// Example:
+//
+// hash_map<string, int> values;
+// values["foo"] = 1;
+// values["bar"] = 2;
+// for (iterator_first<hash_map<string, int>::iterator> x = values.begin();
+// x != values.end(); ++x) {
+// printf("%s", x->c_str());
+// }
+template <typename It>
+struct iterator_first
+ : internal::IteratorAdaptorBase<iterator_first<It>,
+ internal::FirstPolicy<It>> {
+ using Base = internal::IteratorAdaptorBase<iterator_first<It>,
+ internal::FirstPolicy<It>>;
+ iterator_first() {
+ }
+ iterator_first(It it) // NOLINT(runtime/explicit)
+ : Base(it) {
+ }
+ template <typename It2>
+ iterator_first(iterator_first<It2> o) // NOLINT(runtime/explicit)
+ : Base(o.base()) {
+ }
+};
+
+template <typename It>
+iterator_first<It> make_iterator_first(It it) {
+ return iterator_first<It>(it);
+}
+
+// iterator_second<> adapts an iterator to return the second value of a pair.
+// It is equivalent to calling it->second on every value.
+// Example:
+//
+// hash_map<string, int> values;
+// values["foo"] = 1;
+// values["bar"] = 2;
+// for (iterator_second<hash_map<string, int>::iterator> x = values.begin();
+// x != values.end(); ++x) {
+// int v = *x;
+// printf("%d", v);
+// }
+template <typename It>
+struct iterator_second
+ : internal::IteratorAdaptorBase<iterator_second<It>,
+ internal::SecondPolicy<It>> {
+ using Base = internal::IteratorAdaptorBase<iterator_second<It>,
+ internal::SecondPolicy<It>>;
+ iterator_second() {
+ }
+ iterator_second(It it) // NOLINT(runtime/explicit)
+ : Base(it) {
+ }
+ template <typename It2>
+ iterator_second(iterator_second<It2> o) // NOLINT(runtime/explicit)
+ : Base(o.base()) {
+ }
+};
+
+template <typename It>
+iterator_second<It> make_iterator_second(It it) {
+ return iterator_second<It>(it);
+}
+
+// iterator_second_ptr<> adapts an iterator to return the dereferenced second
+// value of a pair.
+// It is equivalent to calling *it->second on every value.
+// The same result can be achieved by composition
+// iterator_ptr<iterator_second<> >
+// Can be used with maps where values are regular pointers or pointers wrapped
+// into linked_ptr. This iterator adaptor can be used by classes to give their
+// clients access to some of their internal data without exposing too much of
+// it.
+//
+// Example:
+// class MyClass {
+// public:
+// MyClass(const string& s);
+// string DebugString() const;
+// };
+// typedef hash_map<string, linked_ptr<MyClass> > MyMap;
+// typedef iterator_second_ptr<MyMap::iterator> MyMapValuesIterator;
+// MyMap values;
+// values["foo"].reset(new MyClass("foo"));
+// values["bar"].reset(new MyClass("bar"));
+// for (MyMapValuesIterator it = values.begin(); it != values.end(); ++it) {
+// printf("%s", it->DebugString().c_str());
+// }
+template <typename It>
+struct iterator_second_ptr
+ : internal::IteratorAdaptorBase<iterator_second_ptr<It>,
+ internal::SecondPtrPolicy<It>> {
+ using Base = internal::IteratorAdaptorBase<iterator_second_ptr<It>,
+ internal::SecondPtrPolicy<It>>;
+ iterator_second_ptr() {
+ }
+ iterator_second_ptr(It it) // NOLINT(runtime/explicit)
+ : Base(it) {
+ }
+ template <typename It2>
+ iterator_second_ptr(iterator_second_ptr<It2> o) // NOLINT(runtime/explicit)
+ : Base(o.base()) {
+ }
+};
+
+template <typename It>
+iterator_second_ptr<It> make_iterator_second_ptr(It it) {
+ return iterator_second_ptr<It>(it);
+}
+
+// iterator_ptr<> adapts an iterator to return the dereferenced value.
+// With this adaptor you can write *it instead of **it, or it->something instead
+// of (*it)->something.
+// Can be used with vectors and lists where values are regular pointers
+// or pointers wrapped into linked_ptr. This iterator adaptor can be used by
+// classes to give their clients access to some of their internal data without
+// exposing too much of it.
+//
+// Example:
+// class MyClass {
+// public:
+// MyClass(const string& s);
+// string DebugString() const;
+// };
+// typedef vector<linked_ptr<MyClass> > MyVector;
+// typedef iterator_ptr<MyVector::iterator> DereferencingIterator;
+// MyVector values;
+// values.push_back(make_linked_ptr(new MyClass("foo")));
+// values.push_back(make_linked_ptr(new MyClass("bar")));
+// for (DereferencingIterator it = values.begin(); it != values.end(); ++it) {
+// printf("%s", it->DebugString().c_str());
+// }
+//
+// Without iterator_ptr you would have to do (*it)->DebugString()
+template <typename It, typename Ptr /* ignored */ = void>
+struct iterator_ptr : internal::IteratorAdaptorBase<iterator_ptr<It, Ptr>,
+ internal::PtrPolicy<It>> {
+ using Base = internal::IteratorAdaptorBase<iterator_ptr<It, Ptr>,
+ internal::PtrPolicy<It>>;
+ iterator_ptr() {
+ }
+ iterator_ptr(It it) // NOLINT(runtime/explicit)
+ : Base(it) {
+ }
+ template <typename It2>
+ iterator_ptr(iterator_ptr<It2> o) // NOLINT(runtime/explicit)
+ : Base(o.base()) {
+ }
+};
+
+template <typename It>
+iterator_ptr<It> make_iterator_ptr(It it) {
+ return iterator_ptr<It>(it);
+}
+
+namespace internal {
+
+// Template that uses SFINAE to inspect Container abilities:
+// . Set has_size_type true, iff T::size_type is defined
+// . Define size_type as T::size_type if defined, or size_t otherwise
+template <typename C>
+struct container_traits {
+ private:
+ // Test for availability of C::size_type.
+ template <typename U, typename = void>
+ struct test_size_type : std::false_type {};
+ template <typename U>
+ struct test_size_type<U, absl::void_t<typename U::size_type>>
+ : std::true_type {};
+
+ // Conditional provisioning of a size_type which defaults to size_t.
+ template <bool Cond, typename U = void>
+ struct size_type_def {
+ using type = typename U::size_type;
+ };
+ template <typename U>
+ struct size_type_def<false, U> {
+ using type = size_t;
+ };
+
+ public:
+ // Determine whether C::size_type is available.
+ static const bool has_size_type = test_size_type<C>::value;
+
+ // Provide size_type as either C::size_type if available, or as size_t.
+ using size_type = typename size_type_def<has_size_type, C>::type;
+};
+
+template <typename C>
+struct IterGenerator {
+ using container_type = C;
+ using iterator = typename C::iterator;
+ using const_iterator = typename C::const_iterator;
+
+ static iterator begin(container_type& c) { // NOLINT(runtime/references)
+ return c.begin();
+ }
+ static iterator end(container_type& c) { // NOLINT(runtime/references)
+ return c.end();
+ }
+ static const_iterator begin(const container_type& c) {
+ return c.begin();
+ }
+ static const_iterator end(const container_type& c) {
+ return c.end();
+ }
+};
+
+template <typename SubIterGenerator>
+struct ReversingIterGeneratorAdaptor {
+ using container_type = typename SubIterGenerator::container_type;
+ using iterator = std::reverse_iterator<typename SubIterGenerator::iterator>;
+ using const_iterator =
+ std::reverse_iterator<typename SubIterGenerator::const_iterator>;
+
+ static iterator begin(container_type& c) { // NOLINT(runtime/references)
+ return iterator(SubIterGenerator::end(c));
+ }
+ static iterator end(container_type& c) { // NOLINT(runtime/references)
+ return iterator(SubIterGenerator::begin(c));
+ }
+ static const_iterator begin(const container_type& c) {
+ return const_iterator(SubIterGenerator::end(c));
+ }
+ static const_iterator end(const container_type& c) {
+ return const_iterator(SubIterGenerator::begin(c));
+ }
+};
+
+// C: the container type
+// Iter: the type of mutable iterator to generate
+// ConstIter: the type of constant iterator to generate
+// IterGenerator: a policy type that returns native iterators from a C
+template <typename C,
+ typename Iter,
+ typename ConstIter,
+ typename IterGenerator = internal::IterGenerator<C>>
+class iterator_view_helper {
+ public:
+ using container_type = C;
+ using iterator = Iter;
+ using const_iterator = ConstIter;
+ using value_type = typename std::iterator_traits<iterator>::value_type;
+ using size_type = typename internal::container_traits<C>::size_type;
+
+ explicit iterator_view_helper(
+ container_type& c) // NOLINT(runtime/references)
+ : c_(&c) {
+ }
+
+ iterator begin() {
+ return iterator(IterGenerator::begin(container()));
+ }
+ iterator end() {
+ return iterator(IterGenerator::end(container()));
+ }
+ const_iterator begin() const {
+ return const_iterator(IterGenerator::begin(container()));
+ }
+ const_iterator end() const {
+ return const_iterator(IterGenerator::end(container()));
+ }
+ const_iterator cbegin() const {
+ return begin();
+ }
+ const_iterator cend() const {
+ return end();
+ }
+ const container_type& container() const {
+ return *c_;
+ }
+ container_type& container() {
+ return *c_;
+ }
+
+ bool empty() const {
+ return begin() == end();
+ }
+ size_type size() const {
+ return c_->size();
+ }
+
+ private:
+ container_type* c_;
+};
+
+template <typename C,
+ typename ConstIter,
+ typename IterGenerator = internal::IterGenerator<C>>
+class const_iterator_view_helper {
+ public:
+ using container_type = C;
+ using const_iterator = ConstIter;
+ using value_type = typename std::iterator_traits<const_iterator>::value_type;
+ using size_type = typename internal::container_traits<C>::size_type;
+
+ explicit const_iterator_view_helper(const container_type& c) : c_(&c) {
+ }
+
+ // Allow implicit conversion from the corresponding iterator_view_helper.
+ // Erring on the side of constness should be allowed. E.g.:
+ // MyMap m;
+ // key_view_type<MyMap>::type keys = key_view(m); // ok
+ // key_view_type<const MyMap>::type const_keys = key_view(m); // ok
+ template <typename Iter>
+ const_iterator_view_helper(const iterator_view_helper<container_type,
+ Iter,
+ const_iterator,
+ IterGenerator>& v)
+ : c_(&v.container()) {
+ }
+
+ const_iterator begin() const {
+ return const_iterator(IterGenerator::begin(container()));
+ }
+ const_iterator end() const {
+ return const_iterator(IterGenerator::end(container()));
+ }
+ const_iterator cbegin() const {
+ return begin();
+ }
+ const_iterator cend() const {
+ return end();
+ }
+ const container_type& container() const {
+ return *c_;
+ }
+
+ bool empty() const {
+ return begin() == end();
+ }
+ size_type size() const {
+ return c_->size();
+ }
+
+ private:
+ const container_type* c_;
+};
+
+} // namespace internal
+
+// Note: The views like value_view, key_view should be in gtl namespace.
+// Currently there are lot of callers that reference the methods in the global
+// namespace.
+//
+// Traits to provide a typedef abstraction for the return value
+// of the key_view() and value_view() functions, such that
+// they can be declared as:
+//
+// template <typename C> key_view_t<C> key_view(C& c);
+// template <typename C> value_view_t<C> value_view(C& c);
+//
+// This abstraction allows callers of these functions to use readable
+// type names, and allows the maintainers of iterator_adaptors.h to
+// change the return types if needed without updating callers.
+
+template <typename C>
+struct key_view_type {
+ using type = internal::iterator_view_helper<
+ C,
+ iterator_first<typename C::iterator>,
+ iterator_first<typename C::const_iterator>>;
+};
+
+template <typename C>
+struct key_view_type<const C> {
+ using type = internal::
+ const_iterator_view_helper<C, iterator_first<typename C::const_iterator>>;
+};
+
+template <typename C>
+struct value_view_type {
+ using type = internal::iterator_view_helper<
+ C,
+ iterator_second<typename C::iterator>,
+ iterator_second<typename C::const_iterator>>;
+};
+
+template <typename C>
+struct value_view_type<const C> {
+ using type = internal::const_iterator_view_helper<
+ C,
+ iterator_second<typename C::const_iterator>>;
+};
+
+// The key_view and value_view functions provide pretty ways to iterate either
+// the keys or the values of a map using range based for loops.
+//
+// Example:
+// hash_map<int, string> my_map;
+// ...
+// for (string val : value_view(my_map)) {
+// ...
+// }
+//
+// Note: If you pass a temporary container to key_view or value_view, be careful
+// that the temporary container outlives the wrapper view to avoid dangling
+// references.
+// This is fine: PublishAll(value_view(Make());
+// This is not: for (const auto& v : value_view(Make())) Publish(v);
+
+template <typename C>
+typename key_view_type<C>::type key_view(
+ C& map) { // NOLINT(runtime/references)
+ return typename key_view_type<C>::type(map);
+}
+
+template <typename C>
+typename key_view_type<const C>::type key_view(const C& map) {
+ return typename key_view_type<const C>::type(map);
+}
+
+template <typename C>
+typename value_view_type<C>::type value_view(
+ C& map) { // NOLINT(runtime/references)
+ return typename value_view_type<C>::type(map);
+}
+
+template <typename C>
+typename value_view_type<const C>::type value_view(const C& map) {
+ return typename value_view_type<const C>::type(map);
+}
+
+// Abstract container view that dereferences the pointer-like .second member
+// of a container's std::pair elements, such as the elements of std::map<K,V*>
+// or of std::vector<std::pair<K,V*>>.
+//
+// Example:
+// map<int, string*> elements;
+// for (const string& element : deref_second_view(elements)) {
+// ...
+// }
+//
+// Note: If you pass a temporary container to deref_second_view, be careful that
+// the temporary container outlives the deref_second_view to avoid dangling
+// references.
+// This is fine: PublishAll(deref_second_view(Make());
+// This is not: for (const auto& v : deref_second_view(Make())) {
+// Publish(v);
+// }
+
+template <typename C>
+struct deref_second_view_type {
+ using type = internal::iterator_view_helper<
+ C,
+ iterator_second_ptr<typename C::iterator>,
+ iterator_second_ptr<typename C::const_iterator>>;
+};
+
+template <typename C>
+struct deref_second_view_type<const C> {
+ using type = internal::const_iterator_view_helper<
+ C,
+ iterator_second_ptr<typename C::const_iterator>>;
+};
+
+template <typename C>
+typename deref_second_view_type<C>::type deref_second_view(
+ C& map) { // NOLINT(runtime/references)
+ return typename deref_second_view_type<C>::type(map);
+}
+
+template <typename C>
+typename deref_second_view_type<const C>::type deref_second_view(const C& map) {
+ return typename deref_second_view_type<const C>::type(map);
+}
+
+// Abstract container view that dereferences pointer elements.
+//
+// Example:
+// vector<string*> elements;
+// for (const string& element : deref_view(elements)) {
+// ...
+// }
+//
+// Note: If you pass a temporary container to deref_view, be careful that the
+// temporary container outlives the deref_view to avoid dangling references.
+// This is fine: PublishAll(deref_view(Make());
+// This is not: for (const auto& v : deref_view(Make())) { Publish(v); }
+
+template <typename C>
+struct deref_view_type {
+ using type =
+ internal::iterator_view_helper<C,
+ iterator_ptr<typename C::iterator>,
+ iterator_ptr<typename C::const_iterator>>;
+};
+
+template <typename C>
+struct deref_view_type<const C> {
+ using type = internal::
+ const_iterator_view_helper<C, iterator_ptr<typename C::const_iterator>>;
+};
+
+template <typename C>
+typename deref_view_type<C>::type deref_view(
+ C& c) { // NOLINT(runtime/references)
+ return typename deref_view_type<C>::type(c);
+}
+
+template <typename C>
+typename deref_view_type<const C>::type deref_view(const C& c) {
+ return typename deref_view_type<const C>::type(c);
+}
+
+// Abstract container view that iterates backwards.
+//
+// Example:
+// vector<string> elements;
+// for (const string& element : reversed_view(elements)) {
+// ...
+// }
+//
+// Note: If you pass a temporary container to reversed_view_type, be careful
+// that the temporary container outlives the reversed_view to avoid dangling
+// references. This is fine: PublishAll(reversed_view(Make());
+// This is not: for (const auto& v : reversed_view(Make())) { Publish(v); }
+
+template <typename C>
+struct reversed_view_type {
+ private:
+ using policy =
+ internal::ReversingIterGeneratorAdaptor<internal::IterGenerator<C>>;
+
+ public:
+ using type = internal::iterator_view_helper<C,
+ typename policy::iterator,
+ typename policy::const_iterator,
+ policy>;
+};
+
+template <typename C>
+struct reversed_view_type<const C> {
+ private:
+ using policy =
+ internal::ReversingIterGeneratorAdaptor<internal::IterGenerator<C>>;
+
+ public:
+ using type = internal::
+ const_iterator_view_helper<C, typename policy::const_iterator, policy>;
+};
+
+template <typename C>
+typename reversed_view_type<C>::type reversed_view(
+ C& c) { // NOLINT(runtime/references)
+ return typename reversed_view_type<C>::type(c);
+}
+
+template <typename C>
+typename reversed_view_type<const C>::type reversed_view(const C& c) {
+ return typename reversed_view_type<const C>::type(c);
+}
+
+} // namespace util
+} // namespace firestore
+} // namespace firebase
+
+#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_ITERATOR_ADAPTORS_H_
diff --git a/Firestore/core/src/firebase/firestore/util/log.h b/Firestore/core/src/firebase/firestore/util/log.h
new file mode 100644
index 0000000..1944596
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/util/log.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2017 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.
+ */
+
+#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_LOG_H_
+#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_LOG_H_
+
+#include <cstdarg>
+
+namespace firebase {
+namespace firestore {
+namespace util {
+
+/// @brief Levels used when logging messages.
+enum LogLevel {
+ /// Verbose Log Level
+ kLogLevelVerbose = 0,
+ /// Debug Log Level
+ kLogLevelDebug,
+ /// Info Log Level
+ kLogLevelInfo,
+ /// Warning Log Level
+ kLogLevelWarning,
+ /// Error Log Level
+ kLogLevelError,
+};
+
+// Common log methods.
+
+// All messages at or above the specified log level value are displayed.
+void LogSetLevel(LogLevel level);
+// Get the currently set log level.
+LogLevel LogGetLevel();
+// Log a debug message to the system log.
+void LogDebug(const char* format, ...);
+// Log an info message to the system log.
+void LogInfo(const char* format, ...);
+// Log a warning to the system log.
+void LogWarning(const char* format, ...);
+// Log an error to the system log.
+void LogError(const char* format, ...);
+// Log a firebase message (implemented by the platform specific logger).
+void LogMessageV(LogLevel log_level, const char* format, va_list args);
+// Log a firebase message via LogMessageV().
+void LogMessage(LogLevel log_level, const char* format, ...);
+
+} // namespace util
+} // namespace firestore
+} // namespace firebase
+
+#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_LOG_H_
diff --git a/Firestore/core/src/firebase/firestore/util/log_apple.mm b/Firestore/core/src/firebase/firestore/util/log_apple.mm
new file mode 100644
index 0000000..cb2c58e
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/util/log_apple.mm
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2017 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.
+ */
+
+#include "Firestore/core/src/firebase/firestore/util/log.h"
+
+#import <FirebaseCore/FIRLogger.h>
+#import <Foundation/Foundation.h>
+
+#include <string>
+
+#include "Firestore/core/src/firebase/firestore/util/string_apple.h"
+
+namespace firebase {
+namespace firestore {
+namespace util {
+
+namespace {
+
+// Translates a C++ LogLevel to the equivalent Objective-C FIRLoggerLevel
+FIRLoggerLevel ToFIRLoggerLevel(LogLevel level) {
+ switch (level) {
+ case kLogLevelVerbose: // fall through
+ case kLogLevelDebug:
+ return FIRLoggerLevelDebug;
+ case kLogLevelInfo:
+ return FIRLoggerLevelInfo;
+ case kLogLevelWarning:
+ return FIRLoggerLevelWarning;
+ case kLogLevelError:
+ return FIRLoggerLevelError;
+ default:
+ // Unsupported log level. FIRSetLoggerLevel will deal with it.
+ return static_cast<FIRLoggerLevel>(-1);
+ }
+}
+
+} // namespace
+
+void LogSetLevel(LogLevel level) {
+ FIRSetLoggerLevel(ToFIRLoggerLevel(level));
+}
+
+LogLevel LogGetLevel() {
+ // We return the true log level. True log level is what the SDK used to
+ // determine whether to log instead of what parameter is used in the last call
+ // of LogSetLevel().
+ if (FIRIsLoggableLevel(FIRLoggerLevelInfo, NO)) {
+ if (FIRIsLoggableLevel(FIRLoggerLevelDebug, NO)) {
+ // FIRLoggerLevelMax is actually kLogLevelDebug right now. We do not check
+ // further.
+ return kLogLevelDebug;
+ } else {
+ return kLogLevelInfo;
+ }
+ } else {
+ if (FIRIsLoggableLevel(FIRLoggerLevelWarning, NO)) {
+ return kLogLevelWarning;
+ } else {
+ return kLogLevelError;
+ }
+ }
+}
+
+void LogDebug(const char* format, ...) {
+ va_list list;
+ va_start(list, format);
+ FIRLogBasic(FIRLoggerLevelDebug, kFIRLoggerFirestore, @"I-FST000001",
+ WrapNSStringNoCopy(format), list);
+ va_end(list);
+}
+
+void LogInfo(const char* format, ...) {
+ va_list list;
+ va_start(list, format);
+ FIRLogBasic(FIRLoggerLevelInfo, kFIRLoggerFirestore, @"I-FST000001",
+ WrapNSStringNoCopy(format), list);
+ va_end(list);
+}
+
+void LogWarning(const char* format, ...) {
+ va_list list;
+ va_start(list, format);
+ FIRLogBasic(FIRLoggerLevelWarning, kFIRLoggerFirestore, @"I-FST000001",
+ WrapNSStringNoCopy(format), list);
+ va_end(list);
+}
+
+void LogError(const char* format, ...) {
+ va_list list;
+ va_start(list, format);
+ FIRLogBasic(FIRLoggerLevelError, kFIRLoggerFirestore, @"I-FST000001",
+ WrapNSStringNoCopy(format), list);
+ va_end(list);
+}
+
+void LogMessageV(LogLevel log_level, const char* format, va_list args) {
+ FIRLogBasic(ToFIRLoggerLevel(log_level), kFIRLoggerFirestore, @"I-FST000001",
+ WrapNSStringNoCopy(format), args);
+}
+
+void LogMessage(LogLevel log_level, const char* format, ...) {
+ va_list list;
+ va_start(list, format);
+ LogMessageV(log_level, format, list);
+ va_end(list);
+}
+
+} // namespace util
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/src/firebase/firestore/util/log_stdio.cc b/Firestore/core/src/firebase/firestore/util/log_stdio.cc
new file mode 100644
index 0000000..b277406
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/util/log_stdio.cc
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2017 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.
+ */
+
+#include "Firestore/core/src/firebase/firestore/util/log.h"
+
+#include <cstdio>
+#include <string>
+
+namespace firebase {
+namespace firestore {
+namespace util {
+
+LogLevel g_log_level = kLogLevelInfo;
+
+void LogSetLevel(LogLevel level) {
+ g_log_level = level;
+}
+
+LogLevel LogGetLevel() {
+ return g_log_level;
+}
+
+void LogDebug(const char* format, ...) {
+ va_list list;
+ va_start(list, format);
+ LogMessageV(kLogLevelDebug, format, list);
+ va_end(list);
+}
+
+void LogInfo(const char* format, ...) {
+ va_list list;
+ va_start(list, format);
+ LogMessageV(kLogLevelInfo, format, list);
+ va_end(list);
+}
+
+void LogWarning(const char* format, ...) {
+ va_list list;
+ va_start(list, format);
+ LogMessageV(kLogLevelWarning, format, list);
+ va_end(list);
+}
+
+void LogError(const char* format, ...) {
+ va_list list;
+ va_start(list, format);
+ LogMessageV(kLogLevelError, format, list);
+ va_end(list);
+}
+
+void LogMessageV(LogLevel log_level, const char* format, va_list args) {
+ if (log_level < g_log_level) {
+ return;
+ }
+ switch (log_level) {
+ case kLogLevelVerbose:
+ printf("VERBOSE: ");
+ break;
+ case kLogLevelDebug:
+ printf("DEBUG: ");
+ break;
+ case kLogLevelInfo:
+ break;
+ case kLogLevelWarning:
+ printf("WARNING: ");
+ break;
+ case kLogLevelError:
+ printf("ERROR: ");
+ break;
+ }
+ vprintf(format, args);
+ printf("\n");
+}
+
+void LogMessage(LogLevel log_level, const char* format, ...) {
+ va_list list;
+ va_start(list, format);
+ LogMessageV(log_level, format, list);
+ va_end(list);
+}
+
+} // namespace util
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/Port/ordered_code.cc b/Firestore/core/src/firebase/firestore/util/ordered_code.cc
index 038d445..cb53b09 100644
--- a/Firestore/Port/ordered_code.cc
+++ b/Firestore/core/src/firebase/firestore/util/ordered_code.cc
@@ -14,15 +14,18 @@
* limitations under the License.
*/
-#include "Firestore/Port/ordered_code.h"
+#include "Firestore/core/src/firebase/firestore/util/ordered_code.h"
-#include <assert.h>
+#include "Firestore/core/src/firebase/firestore/util/bits.h"
+#include "Firestore/core/src/firebase/firestore/util/firebase_assert.h"
+#include "absl/base/internal/endian.h"
+#include "absl/base/internal/unaligned_access.h"
+#include "absl/base/port.h"
-#include "Firestore/Port/bits.h"
-
-#include "Firestore/Port/absl/absl_endian.h"
-#include "Firestore/Port/absl/absl_port.h"
-#include <leveldb/db.h> // For Slice
+#define UNALIGNED_LOAD32 ABSL_INTERNAL_UNALIGNED_LOAD32
+#define UNALIGNED_LOAD64 ABSL_INTERNAL_UNALIGNED_LOAD64
+#define UNALIGNED_STORE32 ABSL_INTERNAL_UNALIGNED_STORE32
+#define UNALIGNED_STORE64 ABSL_INTERNAL_UNALIGNED_STORE64
// We encode a string in different ways depending on whether the item
// should be in lexicographically increasing or decreasing order.
@@ -59,9 +62,9 @@
// turn is the same as the ordering of the encodings of those two
// characters. Moreover, for every finite string x, F(x) < F(<infinity>).
-namespace Firestore {
-
-using leveldb::Slice;
+namespace firebase {
+namespace firestore {
+namespace util {
static const char kEscape1 = '\000';
static const char kNullCharacter = '\xff'; // Combined with kEscape1
@@ -73,50 +76,57 @@ static const char kFFCharacter = '\000'; // Combined with kEscape2
static const char kEscape1_Separator[2] = {kEscape1, kSeparator};
-// Append to "*dest" the "len" bytes starting from "*src".
+/** Append to "*dest" the "len" bytes starting from "*src". */
inline static void AppendBytes(std::string* dest, const char* src, size_t len) {
dest->append(src, len);
}
-inline bool IsSpecialByte(char c) { return ((unsigned char)(c + 1)) < 2; }
+inline static bool IsSpecialByte(char c) {
+ return ((unsigned char)(c + 1)) < 2;
+}
-// Returns 0 if one or more of the bytes in the specified uint32 value
-// are the special values 0 or 255, and returns 4 otherwise. The
-// result of this routine can be added to "p" to either advance past
-// the next 4 bytes if they do not contain a special byte, or to
-// remain on this set of four bytes if they contain the next special
-// byte occurrence.
-//
-// REQUIRES: v is the value of loading the next 4 bytes from "*p" (we
-// pass in v rather than loading it because in some cases, the client
-// may already have the value in a register: "p" is just used for
-// assertion checking).
-inline int AdvanceIfNoSpecialBytes(uint32_t v_32, const char* p) {
- assert(UNALIGNED_LOAD32(p) == v_32);
+/**
+ * Returns 0 if one or more of the bytes in the specified uint32 value
+ * are the special values 0 or 255, and returns 4 otherwise. The
+ * result of this routine can be added to "p" to either advance past
+ * the next 4 bytes if they do not contain a special byte, or to
+ * remain on this set of four bytes if they contain the next special
+ * byte occurrence.
+ *
+ * REQUIRES: v_32 is the value of loading the next 4 bytes from "*p" (we
+ * pass in v_32 rather than loading it because in some cases, the client
+ * may already have the value in a register: "p" is just used for
+ * assertion checking).
+ */
+inline static int AdvanceIfNoSpecialBytes(uint32_t v_32, const char* p) {
+ FIREBASE_DEV_ASSERT(UNALIGNED_LOAD32(p) == v_32);
// See comments in SkipToNextSpecialByte if you wish to
// understand this expression (which checks for the occurrence
// of the special byte values 0 or 255 in any of the bytes of v_32).
if ((v_32 - 0x01010101u) & ~(v_32 + 0x01010101u) & 0x80808080u) {
// Special byte is in p[0..3]
- assert(IsSpecialByte(p[0]) || IsSpecialByte(p[1]) || IsSpecialByte(p[2]) ||
- IsSpecialByte(p[3]));
+ FIREBASE_DEV_ASSERT(IsSpecialByte(p[0]) || IsSpecialByte(p[1]) ||
+ IsSpecialByte(p[2]) || IsSpecialByte(p[3]));
return 0;
} else {
- assert(!IsSpecialByte(p[0]));
- assert(!IsSpecialByte(p[1]));
- assert(!IsSpecialByte(p[2]));
- assert(!IsSpecialByte(p[3]));
+ FIREBASE_DEV_ASSERT(!IsSpecialByte(p[0]));
+ FIREBASE_DEV_ASSERT(!IsSpecialByte(p[1]));
+ FIREBASE_DEV_ASSERT(!IsSpecialByte(p[2]));
+ FIREBASE_DEV_ASSERT(!IsSpecialByte(p[3]));
return 4;
}
}
-// Return a pointer to the first byte in the range "[start..limit)"
-// whose value is 0 or 255 (kEscape1 or kEscape2). If no such byte
-// exists in the range, returns "limit".
-inline const char* SkipToNextSpecialByte(const char* start, const char* limit) {
+/**
+ * Return a pointer to the first byte in the range "[start..limit)"
+ * whose value is 0 or 255 (kEscape1 or kEscape2). If no such byte
+ * exists in the range, returns "limit".
+ */
+inline static const char* SkipToNextSpecialByte(const char* start,
+ const char* limit) {
// If these constants were ever changed, this routine needs to change
- assert(kEscape1 == 0);
- assert((kEscape2 & 0xffu) == 255u);
+ FIREBASE_DEV_ASSERT(kEscape1 == 0);
+ FIREBASE_DEV_ASSERT((kEscape2 & 0xff) == 255);
const char* p = start;
while (p + 8 <= limit) {
// Find out if any of the next 8 bytes are either 0 or 255 (our
@@ -154,7 +164,8 @@ inline const char* SkipToNextSpecialByte(const char* start, const char* limit) {
if (IsSpecialByte(p[0])) return p;
if (IsSpecialByte(p[1])) return p + 1;
if (IsSpecialByte(p[2])) return p + 2;
- assert(IsSpecialByte(p[3])); // Last byte must be the special one
+ FIREBASE_DEV_ASSERT(
+ IsSpecialByte(p[3])); // Last byte must be the special one
return p + 3;
}
}
@@ -174,9 +185,12 @@ const char* OrderedCode::TEST_SkipToNextSpecialByte(const char* start,
return SkipToNextSpecialByte(start, limit);
}
-// Helper routine to encode "s" and append to "*dest", escaping special
-// characters.
-inline static void EncodeStringFragment(std::string* dest, Slice s) {
+/**
+ * Helper routine to encode "s" and append to "*dest", escaping special
+ * characters.
+ */
+inline static void EncodeStringFragment(std::string* dest,
+ absl::string_view s) {
const char* p = s.data();
const char* limit = p + s.size();
const char* copy_start = p;
@@ -184,44 +198,49 @@ inline static void EncodeStringFragment(std::string* dest, Slice s) {
p = SkipToNextSpecialByte(p, limit);
if (p >= limit) break; // No more special characters that need escaping
char c = *(p++);
- assert(IsSpecialByte(c));
+ FIREBASE_DEV_ASSERT(IsSpecialByte(c));
if (c == kEscape1) {
- AppendBytes(dest, copy_start, p - copy_start - 1);
+ AppendBytes(dest, copy_start, static_cast<size_t>(p - copy_start) - 1);
dest->push_back(kEscape1);
dest->push_back(kNullCharacter);
copy_start = p;
} else {
- assert(c == kEscape2);
- AppendBytes(dest, copy_start, p - copy_start - 1);
+ FIREBASE_DEV_ASSERT(c == kEscape2);
+ AppendBytes(dest, copy_start, static_cast<size_t>(p - copy_start) - 1);
dest->push_back(kEscape2);
dest->push_back(kFFCharacter);
copy_start = p;
}
}
if (p > copy_start) {
- AppendBytes(dest, copy_start, p - copy_start);
+ AppendBytes(dest, copy_start, static_cast<size_t>(p - copy_start));
}
}
-void OrderedCode::WriteString(std::string* dest, Slice s) {
+void OrderedCode::WriteString(std::string* dest, absl::string_view s) {
EncodeStringFragment(dest, s);
AppendBytes(dest, kEscape1_Separator, 2);
}
-// Return number of bytes needed to encode the non-length portion
-// of val in ordered coding. Returns number in range [0,8].
+/**
+ * Return number of bytes needed to encode the non-length portion
+ * of val in ordered coding. Returns number in range [0,8].
+ */
static inline unsigned int OrderedNumLength(uint64_t val) {
const int lg = Bits::Log2Floor64(val); // -1 if val==0
return static_cast<unsigned int>(lg + 1 + 7) / 8;
}
-// Append n bytes from src to *dst.
-// REQUIRES: n <= 9
-// REQUIRES: src[0..8] are readable bytes (even if n is smaller)
-//
-// If we use string::append() instead of this routine, it increases the
-// runtime of WriteNumIncreasing from ~9ns to ~13ns.
-static inline void AppendUpto9(std::string* dst, const char* src,
+/**
+ * Append n bytes from src to *dst.
+ * REQUIRES: n <= 9
+ * REQUIRES: src[0..8] are readable bytes (even if n is smaller)
+ *
+ * If we use string::append() instead of this routine, it increases the
+ * runtime of WriteNumIncreasing from ~9ns to ~13ns.
+ */
+static inline void AppendUpto9(std::string* dst,
+ const char* src,
unsigned int n) {
dst->append(src, 9); // Fixed-length append
const size_t extra = 9 - n; // How many extra bytes we added
@@ -238,10 +257,11 @@ void OrderedCode::WriteNumIncreasing(std::string* dest, uint64_t val) {
// call on *dest.
char buf[17];
- UNALIGNED_STORE64(buf + 1, absl::ghtonll(val)); // buf[0] may be needed for length
+ UNALIGNED_STORE64(buf + 1,
+ absl::ghtonll(val)); // buf[0] may be needed for length
const unsigned int length = OrderedNumLength(val);
char* start = buf + 9 - length - 1;
- *start = length;
+ *start = static_cast<char>(length);
AppendUpto9(dest, start, length + 1);
}
@@ -255,15 +275,19 @@ void OrderedCode::WriteInfinity(std::string* dest) {
WriteInfinityInternal(dest);
}
-void OrderedCode::WriteTrailingString(std::string* dest, Slice str) {
+void OrderedCode::WriteTrailingString(std::string* dest,
+ absl::string_view str) {
dest->append(str.data(), str.size());
}
-// Parse the encoding of a string previously encoded with or without
-// inversion. If parse succeeds, return true, consume encoding from
-// "*src", and if result != NULL append the decoded string to "*result".
-// Otherwise, return false and leave both undefined.
-inline static bool ReadStringInternal(Slice* src, std::string* result) {
+/**
+ * Parse the encoding of a string previously encoded with or without
+ * inversion. If parse succeeds, return true, consume encoding from
+ * "*src", and if result != NULL append the decoded string to "*result".
+ * Otherwise, return false and leave both undefined.
+ */
+inline static bool ReadStringInternal(absl::string_view* src,
+ std::string* result) {
const char* start = src->data();
const char* string_limit = src->data() + src->size();
@@ -278,16 +302,17 @@ inline static bool ReadStringInternal(Slice* src, std::string* result) {
// If inversion is required, instead of inverting 'c', we invert the
// character constants to which 'c' is compared. We get the same
// behavior but save the runtime cost of inverting 'c'.
- assert(IsSpecialByte(c));
+ FIREBASE_DEV_ASSERT(IsSpecialByte(c));
if (c == kEscape1) {
if (result) {
- AppendBytes(result, copy_start, start - copy_start - 1);
+ AppendBytes(result, copy_start,
+ static_cast<size_t>(start - copy_start) - 1);
}
// kEscape1 kSeparator ends component
// kEscape1 kNullCharacter represents '\0'
const char next = *(start++);
if (next == kSeparator) {
- src->remove_prefix(start - src->data());
+ src->remove_prefix(static_cast<size_t>(start - src->data()));
return true;
} else if (next == kNullCharacter) {
if (result) {
@@ -298,9 +323,10 @@ inline static bool ReadStringInternal(Slice* src, std::string* result) {
}
copy_start = start;
} else {
- assert(c == kEscape2);
+ FIREBASE_DEV_ASSERT(c == kEscape2);
if (result) {
- AppendBytes(result, copy_start, start - copy_start - 1);
+ AppendBytes(result, copy_start,
+ static_cast<size_t>(start - copy_start) - 1);
}
// kEscape2 kFFCharacter represents '\xff'
// kEscape2 kInfinity is an error
@@ -318,22 +344,22 @@ inline static bool ReadStringInternal(Slice* src, std::string* result) {
return false;
}
-bool OrderedCode::ReadString(Slice* src, std::string* result) {
+bool OrderedCode::ReadString(absl::string_view* src, std::string* result) {
return ReadStringInternal(src, result);
}
-bool OrderedCode::ReadNumIncreasing(Slice* src, uint64_t* result) {
+bool OrderedCode::ReadNumIncreasing(absl::string_view* src, uint64_t* result) {
if (src->empty()) {
return false; // Not enough bytes
}
// Decode length byte
- const int len = static_cast<unsigned char>((*src)[0]);
+ const size_t len = static_cast<size_t>((*src)[0]);
// If len > 0 and src is longer than 1, the first byte of "payload"
// must be non-zero (otherwise the encoding is not minimal).
// In opt mode, we don't enforce that encodings must be minimal.
- assert(0 == len || src->size() == 1 || (*src)[1] != '\0');
+ FIREBASE_DEV_ASSERT(0 == len || src->size() == 1 || (*src)[1] != '\0');
if (len + 1 > src->size() || len > 8) {
return false; // Not enough bytes or too many bytes
@@ -341,7 +367,7 @@ bool OrderedCode::ReadNumIncreasing(Slice* src, uint64_t* result) {
if (result) {
uint64_t tmp = 0;
- for (int i = 0; i < len; i++) {
+ for (size_t i = 0; i < len; i++) {
tmp <<= 8;
tmp |= static_cast<unsigned char>((*src)[1 + i]);
}
@@ -351,7 +377,7 @@ bool OrderedCode::ReadNumIncreasing(Slice* src, uint64_t* result) {
return true;
}
-inline static bool ReadInfinityInternal(Slice* src) {
+inline static bool ReadInfinityInternal(absl::string_view* src) {
if (src->size() >= 2 && ((*src)[0] == kEscape2) && ((*src)[1] == kInfinity)) {
src->remove_prefix(2);
return true;
@@ -360,9 +386,12 @@ inline static bool ReadInfinityInternal(Slice* src) {
}
}
-bool OrderedCode::ReadInfinity(Slice* src) { return ReadInfinityInternal(src); }
+bool OrderedCode::ReadInfinity(absl::string_view* src) {
+ return ReadInfinityInternal(src);
+}
-inline static bool ReadStringOrInfinityInternal(Slice* src, std::string* result,
+inline static bool ReadStringOrInfinityInternal(absl::string_view* src,
+ std::string* result,
bool* inf) {
if (ReadInfinityInternal(src)) {
if (inf) *inf = true;
@@ -381,12 +410,14 @@ inline static bool ReadStringOrInfinityInternal(Slice* src, std::string* result,
}
}
-bool OrderedCode::ReadStringOrInfinity(Slice* src, std::string* result,
+bool OrderedCode::ReadStringOrInfinity(absl::string_view* src,
+ std::string* result,
bool* inf) {
return ReadStringOrInfinityInternal(src, result, inf);
}
-bool OrderedCode::ReadTrailingString(Slice* src, std::string* result) {
+bool OrderedCode::ReadTrailingString(absl::string_view* src,
+ std::string* result) {
if (result) result->assign(src->data(), src->size());
src->remove_prefix(src->size());
return true;
@@ -394,7 +425,7 @@ bool OrderedCode::ReadTrailingString(Slice* src, std::string* result) {
void OrderedCode::TEST_Corrupt(std::string* str, int k) {
int seen_seps = 0;
- for (int i = 0; i < str->size() - 1; i++) {
+ for (size_t i = 0; i < str->size() - 1; i++) {
if ((*str)[i] == kEscape1 && (*str)[i + 1] == kSeparator) {
seen_seps++;
if (seen_seps == k) {
@@ -500,61 +531,70 @@ static const int8_t kBitsToLength[1 + 63] = {
4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 7, 7,
7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 10};
-// Calculates the encoding length in bytes of the signed number n.
+/** Calculates the encoding length in bytes of the signed number n. */
static inline int SignedEncodingLength(int64_t n) {
- return kBitsToLength[Bits::Log2Floor64(n < 0 ? ~n : n) + 1];
+ return kBitsToLength[Bits::Log2Floor64(
+ static_cast<uint64_t>(n < 0 ? ~n : n)) +
+ 1];
}
-// Slightly faster version for n > 0.
+/** Slightly faster version for n > 0. */
static inline int SignedEncodingLengthPositive(int64_t n) {
- return kBitsToLength[Bits::Log2FloorNonZero64(n) + 1];
+ return kBitsToLength[Bits::Log2FloorNonZero64(static_cast<uint64_t>(n)) + 1];
}
void OrderedCode::WriteSignedNumIncreasing(std::string* dest, int64_t val) {
- const uint64_t x = val < 0 ? ~val : val;
+ const int64_t x = val < 0 ? ~val : val;
if (x < 64) { // fast path for encoding length == 1
- *dest += kLengthToHeaderBits[1][0] ^ val;
+ *dest += static_cast<char>(kLengthToHeaderBits[1][0] ^ val);
return;
}
// buf = val in network byte order, sign extended to 10 bytes
const char sign_byte = val < 0 ? '\xff' : '\0';
char buf[10] = {
- sign_byte, sign_byte,
+ sign_byte,
+ sign_byte,
};
- UNALIGNED_STORE64(buf + 2, absl::ghtonll(val));
+ UNALIGNED_STORE64(buf + 2, absl::ghtonll(static_cast<uint64_t>(val)));
- static_assert(sizeof(buf) == kMaxSigned64Length, "max length size mismatch");
- const int len = SignedEncodingLengthPositive(x);
- assert(len >= 2);
+ FIREBASE_DEV_ASSERT_MESSAGE_WITH_EXPRESSION(sizeof(buf) == kMaxSigned64Length,
+ sizeof(buf) == kMaxSigned64Length,
+ "max length size mismatch");
+ const size_t len = static_cast<size_t>(SignedEncodingLengthPositive(x));
+ FIREBASE_DEV_ASSERT(len >= 2);
char* const begin = buf + sizeof(buf) - len;
begin[0] ^= kLengthToHeaderBits[len][0];
begin[1] ^= kLengthToHeaderBits[len][1]; // ok because len >= 2
dest->append(begin, len);
}
-bool OrderedCode::ReadSignedNumIncreasing(Slice* src, int64_t* result) {
+bool OrderedCode::ReadSignedNumIncreasing(absl::string_view* src,
+ int64_t* result) {
if (src->empty()) return false;
const uint64_t xor_mask = (!((*src)[0] & 0x80)) ? ~0ULL : 0ULL;
- const unsigned char first_byte = (*src)[0] ^ (xor_mask & 0xff);
+ const unsigned char first_byte = static_cast<unsigned char>(
+ static_cast<uint64_t>((*src)[0]) ^ (xor_mask & 0xff));
// now calculate and test length, and set x to raw (unmasked) result
- int len;
+ size_t len;
uint64_t x;
if (first_byte != 0xff) {
- len = 7 - Bits::Log2FloorNonZero(first_byte ^ 0xff);
+ len = static_cast<size_t>(7 - Bits::Log2FloorNonZero(first_byte ^ 0xff));
if (src->size() < len) return false;
x = xor_mask; // sign extend using xor_mask
- for (int i = 0; i < len; ++i)
+ for (size_t i = 0; i < len; ++i)
x = (x << 8) | static_cast<unsigned char>((*src)[i]);
} else {
len = 8;
if (src->size() < len) return false;
- const unsigned char second_byte = (*src)[1] ^ (xor_mask & 0xff);
+ const unsigned char second_byte = static_cast<unsigned char>(
+ static_cast<uint64_t>((*src)[1]) ^ (xor_mask & 0xff));
if (second_byte >= 0x80) {
if (second_byte < 0xc0) {
len = 9;
} else {
- const unsigned char third_byte = (*src)[2] ^ (xor_mask & 0xff);
+ const unsigned char third_byte = static_cast<unsigned char>(
+ static_cast<uint64_t>((*src)[2]) ^ (xor_mask & 0xff));
if (second_byte == 0xc0 && third_byte < 0x80) {
len = 10;
} else {
@@ -568,12 +608,14 @@ bool OrderedCode::ReadSignedNumIncreasing(Slice* src, int64_t* result) {
x ^= kLengthToMask[len]; // remove spurious header bits
- assert(len == SignedEncodingLength(x));
+ FIREBASE_DEV_ASSERT(len == static_cast<size_t>(SignedEncodingLength(
+ static_cast<int64_t>(x))));
- if (result) *result = x;
- src->remove_prefix(len);
+ if (result) *result = static_cast<int64_t>(x);
+ src->remove_prefix(static_cast<size_t>(len));
return true;
}
-} // namespace Firestore
-
+} // namespace util
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/Port/ordered_code.h b/Firestore/core/src/firebase/firestore/util/ordered_code.h
index 7c390a5..57b84bd 100644
--- a/Firestore/Port/ordered_code.h
+++ b/Firestore/core/src/firebase/firestore/util/ordered_code.h
@@ -36,16 +36,16 @@
// This module is often useful when generating multi-part sstable
// keys that have to be ordered in a particular fashion.
-#ifndef IPHONE_FIRESTORE_PORT_ORDERED_CODE_H_
-#define IPHONE_FIRESTORE_PORT_ORDERED_CODE_H_
+#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_ORDERED_CODE_H_
+#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_ORDERED_CODE_H_
#include <string>
-namespace leveldb {
-class Slice;
-}
+#include "absl/strings/string_view.h"
-namespace Firestore {
+namespace firebase {
+namespace firestore {
+namespace util {
class OrderedCode {
public:
@@ -60,19 +60,23 @@ class OrderedCode {
// is not called WriteStringIncreasing() for convenience and backward
// compatibility.
- static void WriteString(std::string* dest, leveldb::Slice str);
+ static void WriteString(std::string* dest, absl::string_view str);
static void WriteNumIncreasing(std::string* dest, uint64_t num);
static void WriteSignedNumIncreasing(std::string* dest, int64_t num);
- // Creates an encoding for the "infinite string", a value considered to
- // be lexicographically after any real string. Note that in the case of
- // WriteInfinityDecreasing(), this would come before any real string as
- // the ordering puts lexicographically greater values first.
+ /**
+ * Creates an encoding for the "infinite string", a value considered to
+ * be lexicographically after any real string. Note that in the case of
+ * WriteInfinityDecreasing(), this would come before any real string as
+ * the ordering puts lexicographically greater values first.
+ */
static void WriteInfinity(std::string* dest);
- // Special string append that can only be used at the tail end of
- // an encoded string -- blindly appends "str" to "*dest".
- static void WriteTrailingString(std::string* dest, leveldb::Slice str);
+ /**
+ * Special string append that can only be used at the tail end of
+ * an encoded string -- blindly appends "str" to "*dest".
+ */
+ static void WriteTrailingString(std::string* dest, absl::string_view str);
// -------------------------------------------------------------------
// Decoding routines: these extract an item earlier encoded using
@@ -83,26 +87,33 @@ class OrderedCode {
// "*result". Returns true if the next item was read successfully, false
// otherwise.
- static bool ReadString(leveldb::Slice* src, std::string* result);
- static bool ReadNumIncreasing(leveldb::Slice* src, uint64_t* result);
- static bool ReadSignedNumIncreasing(leveldb::Slice* src, int64_t* result);
+ static bool ReadString(absl::string_view* src, std::string* result);
+ static bool ReadNumIncreasing(absl::string_view* src, uint64_t* result);
+ static bool ReadSignedNumIncreasing(absl::string_view* src, int64_t* result);
- static bool ReadInfinity(leveldb::Slice* src);
- static bool ReadTrailingString(leveldb::Slice* src, std::string* result);
+ static bool ReadInfinity(absl::string_view* src);
+ static bool ReadTrailingString(absl::string_view* src, std::string* result);
- // REQUIRES: next item was encoded by WriteInfinity() or WriteString()
- static bool ReadStringOrInfinity(leveldb::Slice* src, std::string* result, bool* inf);
+ /** REQUIRES: next item was encoded by WriteInfinity() or WriteString(). */
+ static bool ReadStringOrInfinity(absl::string_view* src,
+ std::string* result,
+ bool* inf);
- // Helper for testing: corrupt "*str" by changing the kth item separator
- // in the string.
+ /**
+ * Helper for testing: corrupt "*str" by changing the kth item separator
+ * in the string.
+ */
static void TEST_Corrupt(std::string* str, int k);
- // Helper for testing.
- // SkipToNextSpecialByte is an internal routine defined in the .cc file
- // with the following semantics. Return a pointer to the first byte
- // in the range "[start..limit)" whose value is 0 or 255. If no such
- // byte exists in the range, returns "limit".
- static const char* TEST_SkipToNextSpecialByte(const char* start, const char* limit);
+ /**
+ * Helper for testing.
+ * SkipToNextSpecialByte is an internal routine defined in the .cc file
+ * with the following semantics. Return a pointer to the first byte
+ * in the range "[start..limit)" whose value is 0 or 255. If no such
+ * byte exists in the range, returns "limit".
+ */
+ static const char* TEST_SkipToNextSpecialByte(const char* start,
+ const char* limit);
// Not an instantiable class, but the class exists to make it easy to
// use with a single using statement.
@@ -111,6 +122,8 @@ class OrderedCode {
OrderedCode& operator=(const OrderedCode&) = delete;
};
-} // namespace Firestore
+} // namespace util
+} // namespace firestore
+} // namespace firebase
-#endif // IPHONE_FIRESTORE_PORT_ORDERED_CODE_H_
+#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_ORDERED_CODE_H_
diff --git a/Firestore/core/src/firebase/firestore/util/secure_random.h b/Firestore/core/src/firebase/firestore/util/secure_random.h
index 72be1bd..f030b5e 100644
--- a/Firestore/core/src/firebase/firestore/util/secure_random.h
+++ b/Firestore/core/src/firebase/firestore/util/secure_random.h
@@ -17,7 +17,7 @@
#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_SECURE_RANDOM_H_
#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_SECURE_RANDOM_H_
-#include <stdint.h>
+#include <cstdint>
#include <limits>
@@ -50,6 +50,22 @@ class SecureRandom {
}
result_type operator()();
+
+ /** Returns a uniformly distributed pseudorandom integer in [0, n). */
+ inline result_type Uniform(result_type n) {
+ // Divides the range into buckets of size n plus leftovers.
+ const result_type rem = (max() - min()) % n + min() + 1;
+ result_type rnd;
+ // Generates random number until the number falls into a bucket.
+ do {
+ rnd = (*this)();
+ } while (rnd < rem);
+ return rnd % n;
+ }
+
+ inline bool OneIn(result_type n) {
+ return Uniform(n) == 0;
+ }
};
} // namespace util
diff --git a/Firestore/core/src/firebase/firestore/util/secure_random_arc4random.cc b/Firestore/core/src/firebase/firestore/util/secure_random_arc4random.cc
index a76ade3..d7e9be3 100644
--- a/Firestore/core/src/firebase/firestore/util/secure_random_arc4random.cc
+++ b/Firestore/core/src/firebase/firestore/util/secure_random_arc4random.cc
@@ -16,11 +16,11 @@
#include "Firestore/core/src/firebase/firestore/util/secure_random.h"
-#include "Firestore/core/src/firebase/firestore/base/port.h"
+#include "Firestore/core/src/firebase/firestore/util/config.h"
#if HAVE_ARC4RANDOM
-#include <stdlib.h>
+#include <cstdlib>
namespace firebase {
namespace firestore {
diff --git a/Firestore/core/src/firebase/firestore/util/secure_random_openssl.cc b/Firestore/core/src/firebase/firestore/util/secure_random_openssl.cc
new file mode 100644
index 0000000..e024846
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/util/secure_random_openssl.cc
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#include "Firestore/core/src/firebase/firestore/util/secure_random.h"
+
+#include "Firestore/core/src/firebase/firestore/util/config.h"
+
+#if HAVE_OPENSSL_RAND_H
+
+#include "openssl/err.h"
+#include "openssl/rand.h"
+
+namespace firebase {
+namespace firestore {
+namespace util {
+
+SecureRandom::result_type SecureRandom::operator()() {
+ result_type result;
+ int rc = RAND_bytes(reinterpret_cast<uint8_t*>(&result), sizeof(result));
+ if (rc <= 0) {
+ // OpenSSL's RAND_bytes can fail if there's not enough entropy. BoringSSL
+ // won't fail this way.
+ ERR_print_errors_fp(stderr);
+ abort();
+ }
+ return result;
+}
+
+} // namespace util
+} // namespace firestore
+} // namespace firebase
+
+#endif // HAVE_OPENSSL_RAND_H
diff --git a/Firestore/core/src/firebase/firestore/util/status.cc b/Firestore/core/src/firebase/firestore/util/status.cc
new file mode 100644
index 0000000..662fa5d
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/util/status.cc
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2015, 2018 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.
+ */
+
+#include "Firestore/core/src/firebase/firestore/util/status.h"
+
+#include "Firestore/core/src/firebase/firestore/util/string_printf.h"
+
+namespace firebase {
+namespace firestore {
+namespace util {
+
+Status::Status(FirestoreErrorCode code, absl::string_view msg) {
+ FIREBASE_ASSERT(code != FirestoreErrorCode::Ok);
+ state_ = std::unique_ptr<State>(new State);
+ state_->code = code;
+ state_->msg = static_cast<std::string>(msg);
+}
+
+void Status::Update(const Status& new_status) {
+ if (ok()) {
+ *this = new_status;
+ }
+}
+
+void Status::SlowCopyFrom(const State* src) {
+ if (src == nullptr) {
+ state_ = nullptr;
+ } else {
+ state_ = std::unique_ptr<State>(new State(*src));
+ }
+}
+
+const std::string& Status::empty_string() {
+ static std::string* empty = new std::string;
+ return *empty;
+}
+
+std::string Status::ToString() const {
+ if (state_ == nullptr) {
+ return "OK";
+ } else {
+ std::string result;
+ switch (code()) {
+ case FirestoreErrorCode::Cancelled:
+ result = "Cancelled";
+ break;
+ case FirestoreErrorCode::Unknown:
+ result = "Unknown";
+ break;
+ case FirestoreErrorCode::InvalidArgument:
+ result = "Invalid argument";
+ break;
+ case FirestoreErrorCode::DeadlineExceeded:
+ result = "Deadline exceeded";
+ break;
+ case FirestoreErrorCode::NotFound:
+ result = "Not found";
+ break;
+ case FirestoreErrorCode::AlreadyExists:
+ result = "Already exists";
+ break;
+ case FirestoreErrorCode::PermissionDenied:
+ result = "Permission denied";
+ break;
+ case FirestoreErrorCode::Unauthenticated:
+ result = "Unauthenticated";
+ break;
+ case FirestoreErrorCode::ResourceExhausted:
+ result = "Resource exhausted";
+ break;
+ case FirestoreErrorCode::FailedPrecondition:
+ result = "Failed precondition";
+ break;
+ case FirestoreErrorCode::Aborted:
+ result = "Aborted";
+ break;
+ case FirestoreErrorCode::OutOfRange:
+ result = "Out of range";
+ break;
+ case FirestoreErrorCode::Unimplemented:
+ result = "Unimplemented";
+ break;
+ case FirestoreErrorCode::Internal:
+ result = "Internal";
+ break;
+ case FirestoreErrorCode::Unavailable:
+ result = "Unavailable";
+ break;
+ case FirestoreErrorCode::DataLoss:
+ result = "Data loss";
+ break;
+ default:
+ result = StringPrintf("Unknown code(%d)", static_cast<int>(code()));
+ break;
+ }
+ result += ": ";
+ result += state_->msg;
+ return result;
+ }
+}
+
+void Status::IgnoreError() const {
+ // no-op
+}
+
+std::string StatusCheckOpHelperOutOfLine(const Status& v, const char* msg) {
+ FIREBASE_ASSERT(!v.ok());
+ std::string r("Non-OK-status: ");
+ r += msg;
+ r += " status: ";
+ r += v.ToString();
+ return r;
+}
+
+} // namespace util
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/src/firebase/firestore/util/status.h b/Firestore/core/src/firebase/firestore/util/status.h
new file mode 100644
index 0000000..13bf320
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/util/status.h
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2015, 2018 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.
+ */
+
+#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_STATUS_H_
+#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_STATUS_H_
+
+#include <functional>
+#include <iosfwd>
+#include <memory>
+#include <string>
+
+#include "Firestore/core/include/firebase/firestore/firestore_errors.h"
+#include "Firestore/core/src/firebase/firestore/util/firebase_assert.h"
+#include "absl/base/attributes.h"
+#include "absl/strings/string_view.h"
+
+namespace firebase {
+namespace firestore {
+namespace util {
+
+/// Denotes success or failure of a call.
+class ABSL_MUST_USE_RESULT Status {
+ public:
+ /// Create a success status.
+ Status() {
+ }
+
+ /// \brief Create a status with the specified error code and msg as a
+ /// human-readable string containing more detailed information.
+ Status(FirestoreErrorCode code, absl::string_view msg);
+
+ /// Copy the specified status.
+ Status(const Status& s);
+ void operator=(const Status& s);
+
+ static Status OK() {
+ return Status();
+ }
+
+ /// Returns true iff the status indicates success.
+ bool ok() const {
+ return (state_ == nullptr);
+ }
+
+ FirestoreErrorCode code() const {
+ return ok() ? FirestoreErrorCode::Ok : state_->code;
+ }
+
+ const std::string& error_message() const {
+ return ok() ? empty_string() : state_->msg;
+ }
+
+ bool operator==(const Status& x) const;
+ bool operator!=(const Status& x) const;
+
+ /// \brief If `ok()`, stores `new_status` into `*this`. If `!ok()`,
+ /// preserves the current status, but may augment with additional
+ /// information about `new_status`.
+ ///
+ /// Convenient way of keeping track of the first error encountered.
+ /// Instead of:
+ /// `if (overall_status.ok()) overall_status = new_status`
+ /// Use:
+ /// `overall_status.Update(new_status);`
+ void Update(const Status& new_status);
+
+ /// \brief Return a string representation of this status suitable for
+ /// printing. Returns the string `"OK"` for success.
+ std::string ToString() const;
+
+ // Ignores any errors. This method does nothing except potentially suppress
+ // complaints from any tools that are checking that errors are not dropped on
+ // the floor.
+ void IgnoreError() const;
+
+ private:
+ static const std::string& empty_string();
+ struct State {
+ FirestoreErrorCode code;
+ std::string msg;
+ };
+ // OK status has a `NULL` state_. Otherwise, `state_` points to
+ // a `State` structure containing the error code and message(s)
+ std::unique_ptr<State> state_;
+
+ void SlowCopyFrom(const State* src);
+};
+
+inline Status::Status(const Status& s)
+ : state_((s.state_ == nullptr) ? nullptr : new State(*s.state_)) {
+}
+
+inline void Status::operator=(const Status& s) {
+ // The following condition catches both aliasing (when this == &s),
+ // and the common case where both s and *this are ok.
+ if (state_ != s.state_) {
+ SlowCopyFrom(s.state_.get());
+ }
+}
+
+inline bool Status::operator==(const Status& x) const {
+ return (this->state_ == x.state_) || (ToString() == x.ToString());
+}
+
+inline bool Status::operator!=(const Status& x) const {
+ return !(*this == x);
+}
+
+typedef std::function<void(const Status&)> StatusCallback;
+
+extern std::string StatusCheckOpHelperOutOfLine(const Status& v,
+ const char* msg);
+
+#define STATUS_CHECK_OK(val) \
+ FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION( \
+ val.ok(), val.ok(), StatusCheckOpHelperOutOfLine(val, #val).c_str())
+
+// DEBUG only version of STATUS_CHECK_OK. Compiler still parses 'val' even in
+// opt mode.
+#define STATUS_DCHECK_OK(val) \
+ FIREBASE_DEV_ASSERT_MESSAGE_WITH_EXPRESSION( \
+ val.ok(), val.ok(), StatusCheckOpHelperOutOfLine(val, #val).c_str())
+
+} // namespace util
+} // namespace firestore
+} // namespace firebase
+
+#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_STATUS_H_
diff --git a/Firestore/core/src/firebase/firestore/util/statusor.cc b/Firestore/core/src/firebase/firestore/util/statusor.cc
new file mode 100644
index 0000000..be1e03a
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/util/statusor.cc
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2017, 2018 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.
+ */
+
+#include "Firestore/core/src/firebase/firestore/util/statusor.h"
+
+namespace firebase {
+namespace firestore {
+namespace util {
+namespace internal_statusor {
+
+void Helper::HandleInvalidStatusCtorArg(Status* status) {
+ const char* kMessage =
+ "An OK status is not a valid constructor argument to StatusOr<T>";
+ FIREBASE_DEV_ASSERT_MESSAGE(false, kMessage);
+ // Fall back to Internal for non-debug builds
+ *status = Status(FirestoreErrorCode::Internal, kMessage);
+}
+
+void Helper::Crash(const Status& status) {
+ FIREBASE_ASSERT_MESSAGE(
+ false, "Attempting to fetch value instead of handling error ",
+ status.ToString().c_str());
+}
+
+} // namespace internal_statusor
+} // namespace util
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/src/firebase/firestore/util/statusor.h b/Firestore/core/src/firebase/firestore/util/statusor.h
new file mode 100644
index 0000000..dc5644a
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/util/statusor.h
@@ -0,0 +1,322 @@
+/*
+ * Copyright 2017, 2018 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.
+ */
+
+// StatusOr<T> is the union of a Status object and a T
+// object. StatusOr models the concept of an object that is either a
+// usable value, or an error Status explaining why such a value is
+// not present. To this end, StatusOr<T> does not allow its Status
+// value to be Status::OK. Furthermore, the value of a StatusOr<T*>
+// must not be null. This is enforced by a debug check in most cases,
+// but even when it is not, clients must not set the value to null.
+//
+// The primary use-case for StatusOr<T> is as the return value of a
+// function which may fail.
+//
+// Example client usage for a StatusOr<T>, where T is not a pointer:
+//
+// StatusOr<float> result = DoBigCalculationThatCouldFail();
+// if (result.ok()) {
+// float answer = result.ValueOrDie();
+// printf("Big calculation yielded: %f", answer);
+// } else {
+// LOG(ERROR) << result.status();
+// }
+//
+// Example client usage for a StatusOr<T*>:
+//
+// StatusOr<Foo*> result = FooFactory::MakeNewFoo(arg);
+// if (result.ok()) {
+// std::unique_ptr<Foo> foo(result.ValueOrDie());
+// foo->DoSomethingCool();
+// } else {
+// LOG(ERROR) << result.status();
+// }
+//
+// Example client usage for a StatusOr<std::unique_ptr<T>>:
+//
+// StatusOr<std::unique_ptr<Foo>> result = FooFactory::MakeNewFoo(arg);
+// if (result.ok()) {
+// std::unique_ptr<Foo> foo = std::move(result.ValueOrDie());
+// foo->DoSomethingCool();
+// } else {
+// LOG(ERROR) << result.status();
+// }
+//
+// Example factory implementation returning StatusOr<T*>:
+//
+// StatusOr<Foo*> FooFactory::MakeNewFoo(int arg) {
+// if (arg <= 0) {
+// return tensorflow::InvalidArgument("Arg must be positive");
+// } else {
+// return new Foo(arg);
+// }
+// }
+//
+// Note that the assignment operators require that destroying the currently
+// stored value cannot invalidate the argument; in other words, the argument
+// cannot be an alias for the current value, or anything owned by the current
+// value.
+
+#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_STATUSOR_H_
+#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_STATUSOR_H_
+
+#include <utility>
+
+#include "Firestore/core/include/firebase/firestore/firestore_errors.h"
+#include "Firestore/core/src/firebase/firestore/util/status.h"
+#include "Firestore/core/src/firebase/firestore/util/statusor_internals.h"
+#include "absl/base/attributes.h"
+#include "absl/strings/string_view.h"
+
+namespace firebase {
+namespace firestore {
+namespace util {
+
+template <typename T>
+class ABSL_MUST_USE_RESULT StatusOr
+ : private internal_statusor::StatusOrData<T>,
+ private internal_statusor::TraitsBase<
+ std::is_copy_constructible<T>::value,
+ std::is_move_constructible<T>::value> {
+ template <typename U>
+ friend class StatusOr;
+
+ typedef internal_statusor::StatusOrData<T> Base;
+
+ public:
+ typedef T element_type;
+
+ // Constructs a new StatusOr with FirebaseErrorCode::Unknown status. This is
+ // marked 'explicit' to try to catch cases like 'return {};', where people
+ // think StatusOr<std::vector<int>> will be initialized with an empty vector,
+ // instead of a FirebaseErrorCode::Unknown status.
+ explicit StatusOr(); // NOLINT: allow explicit zero-parameter ctor
+
+ // StatusOr<T> will be copy constructible/assignable if T is copy
+ // constructible.
+ StatusOr(const StatusOr&) = default;
+ StatusOr& operator=(const StatusOr&) = default;
+
+ // StatusOr<T> will be move constructible/assignable if T is move
+ // constructible.
+ StatusOr(StatusOr&&) = default;
+ StatusOr& operator=(StatusOr&&) = default;
+
+ // Conversion copy/move constructor, T must be convertible from U.
+ // TODO(b/62186717): These should not participate in overload resolution if U
+ // is not convertible to T.
+ template <typename U>
+ StatusOr(const StatusOr<U>& other);
+ template <typename U>
+ StatusOr(StatusOr<U>&& other);
+
+ // Conversion copy/move assignment operator, T must be convertible from U.
+ template <typename U>
+ StatusOr& operator=(const StatusOr<U>& other);
+ template <typename U>
+ StatusOr& operator=(StatusOr<U>&& other);
+
+ // Constructs a new StatusOr with the given value. After calling this
+ // constructor, calls to ValueOrDie() will succeed, and calls to status() will
+ // return OK.
+ //
+ // NOTE: Not explicit - we want to use StatusOr<T> as a return type
+ // so it is convenient and sensible to be able to do 'return T()'
+ // when the return type is StatusOr<T>.
+ //
+ // REQUIRES: T is copy constructible.
+ StatusOr(const T& value); // NOLINT: allow non-explicit 1-param ctor
+
+ // Constructs a new StatusOr with the given non-ok status. After calling
+ // this constructor, calls to ValueOrDie() will CHECK-fail.
+ //
+ // NOTE: Not explicit - we want to use StatusOr<T> as a return
+ // value, so it is convenient and sensible to be able to do 'return
+ // Status()' when the return type is StatusOr<T>.
+ //
+ // REQUIRES: !status.ok(). This requirement is DCHECKed.
+ // In optimized builds, passing Status::OK() here will have the effect
+ // of passing tensorflow::error::INTERNAL as a fallback.
+ StatusOr(const Status& status); // NOLINT: allow non-explicit 1-param ctor
+ StatusOr& operator=(const Status& status);
+
+ // TODO(b/62186997): Add operator=(T) overloads.
+
+ // Similar to the `const T&` overload.
+ //
+ // REQUIRES: T is move constructible.
+ StatusOr(T&& value); // NOLINT: allow non-explicit 1-param ctor
+
+ // RValue versions of the operations declared above.
+ StatusOr(Status&& status); // NOLINT: allow non-explicit 1-param ctor
+ StatusOr& operator=(Status&& status);
+
+ // Returns this->status().ok()
+ bool ok() const {
+ return this->status_.ok();
+ }
+
+ // Returns a reference to our status. If this contains a T, then
+ // returns Status::OK().
+ const Status& status() const&;
+ Status status() &&;
+
+ // Returns a reference to our current value, or CHECK-fails if !this->ok().
+ //
+ // Note: for value types that are cheap to copy, prefer simple code:
+ //
+ // T value = statusor.ValueOrDie();
+ //
+ // Otherwise, if the value type is expensive to copy, but can be left
+ // in the StatusOr, simply assign to a reference:
+ //
+ // T& value = statusor.ValueOrDie(); // or `const T&`
+ //
+ // Otherwise, if the value type supports an efficient move, it can be
+ // used as follows:
+ //
+ // T value = std::move(statusor).ValueOrDie();
+ //
+ // The std::move on statusor instead of on the whole expression enables
+ // warnings about possible uses of the statusor object after the move.
+ // C++ style guide waiver for ref-qualified overloads granted in cl/143176389
+ // See go/ref-qualifiers for more details on such overloads.
+ const T& ValueOrDie() const&;
+ T& ValueOrDie() &;
+ const T&& ValueOrDie() const&&;
+ T&& ValueOrDie() &&;
+
+ T ConsumeValueOrDie() {
+ return std::move(ValueOrDie());
+ }
+
+ // Ignores any errors. This method does nothing except potentially suppress
+ // complaints from any tools that are checking that errors are not dropped on
+ // the floor.
+ void IgnoreError() const;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+// Implementation details for StatusOr<T>
+
+template <typename T>
+StatusOr<T>::StatusOr() : Base(Status(FirestoreErrorCode::Unknown, "")) {
+}
+
+template <typename T>
+StatusOr<T>::StatusOr(const T& value) : Base(value) {
+}
+
+template <typename T>
+StatusOr<T>::StatusOr(const Status& status) : Base(status) {
+}
+
+template <typename T>
+StatusOr<T>& StatusOr<T>::operator=(const Status& status) {
+ this->Assign(status);
+ return *this;
+}
+
+template <typename T>
+StatusOr<T>::StatusOr(T&& value) : Base(std::move(value)) {
+}
+
+template <typename T>
+StatusOr<T>::StatusOr(Status&& status) : Base(std::move(status)) {
+}
+
+template <typename T>
+StatusOr<T>& StatusOr<T>::operator=(Status&& status) {
+ this->Assign(std::move(status));
+ return *this;
+}
+
+template <typename T>
+template <typename U>
+inline StatusOr<T>::StatusOr(const StatusOr<U>& other)
+ : Base(static_cast<const typename StatusOr<U>::Base&>(other)) {
+}
+
+template <typename T>
+template <typename U>
+inline StatusOr<T>& StatusOr<T>::operator=(const StatusOr<U>& other) {
+ if (other.ok())
+ this->Assign(other.ValueOrDie());
+ else
+ this->Assign(other.status());
+ return *this;
+}
+
+template <typename T>
+template <typename U>
+inline StatusOr<T>::StatusOr(StatusOr<U>&& other)
+ : Base(static_cast<typename StatusOr<U>::Base&&>(other)) {
+}
+
+template <typename T>
+template <typename U>
+inline StatusOr<T>& StatusOr<T>::operator=(StatusOr<U>&& other) {
+ if (other.ok()) {
+ this->Assign(std::move(other).ValueOrDie());
+ } else {
+ this->Assign(std::move(other).status());
+ }
+ return *this;
+}
+
+template <typename T>
+const Status& StatusOr<T>::status() const& {
+ return this->status_;
+}
+template <typename T>
+Status StatusOr<T>::status() && {
+ return ok() ? Status::OK() : std::move(this->status_);
+}
+
+template <typename T>
+const T& StatusOr<T>::ValueOrDie() const& {
+ this->EnsureOk();
+ return this->data_;
+}
+
+template <typename T>
+T& StatusOr<T>::ValueOrDie() & {
+ this->EnsureOk();
+ return this->data_;
+}
+
+template <typename T>
+const T&& StatusOr<T>::ValueOrDie() const&& {
+ this->EnsureOk();
+ return std::move(this->data_);
+}
+
+template <typename T>
+T&& StatusOr<T>::ValueOrDie() && {
+ this->EnsureOk();
+ return std::move(this->data_);
+}
+
+template <typename T>
+void StatusOr<T>::IgnoreError() const {
+ // no-op
+}
+
+} // namespace util
+} // namespace firestore
+} // namespace firebase
+
+#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_STATUSOR_H_
diff --git a/Firestore/core/src/firebase/firestore/util/statusor_internals.h b/Firestore/core/src/firebase/firestore/util/statusor_internals.h
new file mode 100644
index 0000000..d6c8de1
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/util/statusor_internals.h
@@ -0,0 +1,258 @@
+/*
+ * Copyright 2017, 2018 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.
+ */
+
+#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_STATUSOR_INTERNALS_H_
+#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_STATUSOR_INTERNALS_H_
+
+#include <utility>
+
+#include "Firestore/core/src/firebase/firestore/util/status.h"
+#include "absl/base/attributes.h"
+
+namespace firebase {
+namespace firestore {
+namespace util {
+namespace internal_statusor {
+
+class Helper {
+ public:
+ // Move type-agnostic error handling to the .cc.
+ static void HandleInvalidStatusCtorArg(Status*);
+ ABSL_ATTRIBUTE_NORETURN static void Crash(const Status& status);
+};
+
+// Construct an instance of T in `p` through placement new, passing Args... to
+// the constructor.
+// This abstraction is here mostly for the gcc performance fix.
+template <typename T, typename... Args>
+void PlacementNew(void* p, Args&&... args) {
+#if defined(__GNUC__) && !defined(__clang__)
+ // Teach gcc that 'p' cannot be null, fixing code size issues.
+ if (p == nullptr) __builtin_unreachable();
+#endif
+ new (p) T(std::forward<Args>(args)...);
+}
+
+// Helper base class to hold the data and all operations.
+// We move all this to a base class to allow mixing with the appropriate
+// TraitsBase specialization.
+template <typename T>
+class StatusOrData {
+ template <typename U>
+ friend class StatusOrData;
+
+ public:
+ StatusOrData() = delete;
+
+ StatusOrData(const StatusOrData& other) {
+ if (other.ok()) {
+ MakeValue(other.data_);
+ MakeStatus();
+ } else {
+ MakeStatus(other.status_);
+ }
+ }
+
+ StatusOrData(StatusOrData&& other) noexcept {
+ if (other.ok()) {
+ MakeValue(std::move(other.data_));
+ MakeStatus();
+ } else {
+ MakeStatus(std::move(other.status_));
+ }
+ }
+
+ template <typename U>
+ StatusOrData(const StatusOrData<U>& other) {
+ if (other.ok()) {
+ MakeValue(other.data_);
+ MakeStatus();
+ } else {
+ MakeStatus(other.status_);
+ }
+ }
+
+ template <typename U>
+ StatusOrData(StatusOrData<U>&& other) {
+ if (other.ok()) {
+ MakeValue(std::move(other.data_));
+ MakeStatus();
+ } else {
+ MakeStatus(std::move(other.status_));
+ }
+ }
+
+ explicit StatusOrData(const T& value) : data_(value) {
+ MakeStatus();
+ }
+ explicit StatusOrData(T&& value) : data_(std::move(value)) {
+ MakeStatus();
+ }
+
+ explicit StatusOrData(const Status& status) : status_(status) {
+ EnsureNotOk();
+ }
+ explicit StatusOrData(Status&& status) : status_(std::move(status)) {
+ EnsureNotOk();
+ }
+
+ StatusOrData& operator=(const StatusOrData& other) {
+ if (this == &other) return *this;
+ if (other.ok())
+ Assign(other.data_);
+ else
+ Assign(other.status_);
+ return *this;
+ }
+
+ StatusOrData& operator=(StatusOrData&& other) {
+ if (this == &other) return *this;
+ if (other.ok())
+ Assign(std::move(other.data_));
+ else
+ Assign(std::move(other.status_));
+ return *this;
+ }
+
+ ~StatusOrData() {
+ if (ok()) {
+ status_.~Status();
+ data_.~T();
+ } else {
+ status_.~Status();
+ }
+ }
+
+ void Assign(const T& value) {
+ if (ok()) {
+ data_.~T();
+ MakeValue(value);
+ } else {
+ MakeValue(value);
+ status_ = Status::OK();
+ }
+ }
+
+ void Assign(T&& value) {
+ if (ok()) {
+ data_.~T();
+ MakeValue(std::move(value));
+ } else {
+ MakeValue(std::move(value));
+ status_ = Status::OK();
+ }
+ }
+
+ void Assign(const Status& status) {
+ Clear();
+ status_ = status;
+ EnsureNotOk();
+ }
+
+ void Assign(Status&& status) {
+ Clear();
+ status_ = std::move(status);
+ EnsureNotOk();
+ }
+
+ bool ok() const {
+ return status_.ok();
+ }
+
+ protected:
+ // status_ will always be active after the constructor.
+ // We make it a union to be able to initialize exactly how we need without
+ // waste.
+ // Eg. in the copy constructor we use the default constructor of Status in
+ // the ok() path to avoid an extra Ref call.
+ union {
+ Status status_;
+ };
+
+ // data_ is active iff status_.ok()==true
+ struct Dummy {};
+ union {
+ // When T is const, we need some non-const object we can cast to void* for
+ // the placement new. dummy_ is that object.
+ Dummy dummy_;
+ T data_;
+ };
+
+ void Clear() {
+ if (ok()) data_.~T();
+ }
+
+ void EnsureOk() const {
+ if (!ok()) Helper::Crash(status_);
+ }
+
+ void EnsureNotOk() {
+ if (ok()) Helper::HandleInvalidStatusCtorArg(&status_);
+ }
+
+ // Construct the value (ie. data_) through placement new with the passed
+ // argument.
+ template <typename Arg>
+ void MakeValue(Arg&& arg) {
+ internal_statusor::PlacementNew<T>(&dummy_, std::forward<Arg>(arg));
+ }
+
+ // Construct the status (ie. status_) through placement new with the passed
+ // argument.
+ template <typename... Args>
+ void MakeStatus(Args&&... args) {
+ internal_statusor::PlacementNew<Status>(&status_,
+ std::forward<Args>(args)...);
+ }
+};
+
+// Helper base class to allow implicitly deleted constructors and assignment
+// operations in StatusOr.
+// TraitsBase will explicitly delete what it can't support and StatusOr will
+// inherit that behavior implicitly.
+template <bool Copy, bool Move>
+struct TraitsBase {
+ TraitsBase() = default;
+ TraitsBase(const TraitsBase&) = default;
+ TraitsBase(TraitsBase&&) = default;
+ TraitsBase& operator=(const TraitsBase&) = default;
+ TraitsBase& operator=(TraitsBase&&) = default;
+};
+
+template <>
+struct TraitsBase<false, true> {
+ TraitsBase() = default;
+ TraitsBase(const TraitsBase&) = delete;
+ TraitsBase(TraitsBase&&) = default;
+ TraitsBase& operator=(const TraitsBase&) = delete;
+ TraitsBase& operator=(TraitsBase&&) = default;
+};
+
+template <>
+struct TraitsBase<false, false> {
+ TraitsBase() = default;
+ TraitsBase(const TraitsBase&) = delete;
+ TraitsBase(TraitsBase&&) = delete;
+ TraitsBase& operator=(const TraitsBase&) = delete;
+ TraitsBase& operator=(TraitsBase&&) = delete;
+};
+
+} // namespace internal_statusor
+} // namespace util
+} // namespace firestore
+} // namespace firebase
+
+#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_STATUSOR_INTERNALS_H_
diff --git a/Firestore/core/src/firebase/firestore/util/string_apple.h b/Firestore/core/src/firebase/firestore/util/string_apple.h
new file mode 100644
index 0000000..73388be
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/util/string_apple.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_STRING_APPLE_H_
+#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_STRING_APPLE_H_
+
+// Everything in this header exists for compatibility with Objective-C.
+#if __OBJC__
+
+#import <Foundation/Foundation.h>
+
+#include <string>
+
+#include "absl/strings/string_view.h"
+
+namespace firebase {
+namespace firestore {
+namespace util {
+
+// Translates a C string to the equivalent NSString without making a copy.
+inline NSString* WrapNSStringNoCopy(const char* c_str) {
+ return [[NSString alloc]
+ initWithBytesNoCopy:const_cast<void*>(static_cast<const void*>(c_str))
+ length:strlen(c_str)
+ encoding:NSUTF8StringEncoding
+ freeWhenDone:NO];
+}
+
+// Translates a string_view to the equivalent NSString without making a copy.
+inline NSString* WrapNSStringNoCopy(const absl::string_view str) {
+ return WrapNSStringNoCopy(str.data());
+}
+
+// Translates a string_view string to the equivalent NSString by making a copy.
+inline NSString* WrapNSString(const absl::string_view str) {
+ return [[NSString alloc]
+ initWithBytes:const_cast<void*>(static_cast<const void*>(str.data()))
+ length:str.length()
+ encoding:NSUTF8StringEncoding];
+}
+
+// Creates an absl::string_view wrapper for the contents of the given
+// NSString.
+inline absl::string_view MakeStringView(NSString* str) {
+ return absl::string_view(
+ [str UTF8String], [str lengthOfBytesUsingEncoding:NSUTF8StringEncoding]);
+}
+
+// Creates a std::string wrapper for the contents of the given NSString.
+inline std::string MakeString(NSString* str) {
+ return std::string([str UTF8String],
+ [str lengthOfBytesUsingEncoding:NSUTF8StringEncoding]);
+}
+
+} // namespace util
+} // namespace firestore
+} // namespace firebase
+
+#endif // __OBJC__
+
+#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_STRING_APPLE_H_
diff --git a/Firestore/core/src/firebase/firestore/util/string_printf.cc b/Firestore/core/src/firebase/firestore/util/string_printf.cc
new file mode 100644
index 0000000..c5483f4
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/util/string_printf.cc
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#include "Firestore/core/src/firebase/firestore/util/string_printf.h"
+
+#include <cstdio>
+
+namespace firebase {
+namespace firestore {
+namespace util {
+
+void StringAppendV(std::string* dst, const char* format, va_list ap) {
+ // First try with a small fixed size buffer
+ static const int kSpaceLength = 1024;
+ char space[kSpaceLength];
+
+ // It's possible for methods that use a va_list to invalidate
+ // the data in it upon use. The fix is to make a copy
+ // of the structure before using it and use that copy instead.
+ va_list backup_ap;
+ va_copy(backup_ap, ap);
+ int result = vsnprintf(space, kSpaceLength, format, backup_ap);
+ va_end(backup_ap);
+
+ if (result < kSpaceLength) {
+ if (result >= 0) {
+ // Normal case -- everything fit.
+ dst->append(space, static_cast<size_t>(result));
+ return;
+ }
+
+#ifdef _MSC_VER
+ // Error or MSVC running out of space. MSVC 8.0 and higher
+ // can be asked about space needed with the special idiom below:
+ va_copy(backup_ap, ap);
+ result = vsnprintf(nullptr, 0, format, backup_ap);
+ va_end(backup_ap);
+#endif
+ }
+
+ if (result < 0) {
+ // Just an error.
+ return;
+ }
+ size_t result_size = static_cast<size_t>(result);
+
+ // Increase the buffer size to the size requested by vsnprintf,
+ // plus one for the closing \0.
+ size_t initial_size = dst->size();
+ size_t target_size = initial_size + result_size;
+
+ dst->resize(target_size + 1);
+ char* buf = &(*dst)[initial_size];
+ size_t buf_remain = result_size + 1;
+
+ // Restore the va_list before we use it again
+ va_copy(backup_ap, ap);
+ result = vsnprintf(buf, buf_remain, format, backup_ap);
+ va_end(backup_ap);
+
+ if (result >= 0 && static_cast<size_t>(result) < buf_remain) {
+ // It fit and vsnprintf copied in directly. Resize down one to
+ // remove the trailing \0.
+ dst->resize(target_size);
+ } else {
+ // Didn't fit. Leave the original string unchanged.
+ dst->resize(initial_size);
+ }
+}
+
+std::string StringPrintf(const char* format, ...) {
+ va_list ap;
+ va_start(ap, format);
+ std::string result;
+ StringAppendV(&result, format, ap);
+ va_end(ap);
+ return result;
+}
+
+void StringAppendF(std::string* dst, const char* format, ...) {
+ va_list ap;
+ va_start(ap, format);
+ StringAppendV(dst, format, ap);
+ va_end(ap);
+}
+
+} // namespace util
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/src/firebase/firestore/util/string_printf.h b/Firestore/core/src/firebase/firestore/util/string_printf.h
new file mode 100644
index 0000000..553af66
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/util/string_printf.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_STRING_PRINTF_H_
+#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_STRING_PRINTF_H_
+
+#include <cstdarg>
+#include <string>
+
+#include "absl/base/attributes.h"
+
+namespace firebase {
+namespace firestore {
+namespace util {
+
+/** Return a C++ string. */
+std::string StringPrintf(const char* format, ...) ABSL_PRINTF_ATTRIBUTE(1, 2);
+
+/** Append result to a supplied string. */
+void StringAppendF(std::string* dst, const char* format, ...)
+ ABSL_PRINTF_ATTRIBUTE(2, 3);
+
+/**
+ * Lower-level routine that takes a va_list and appends to a specified
+ * string. All other routines are just convenience wrappers around it.
+ */
+void StringAppendV(std::string* dst, const char* format, va_list ap);
+
+} // namespace util
+} // namespace firestore
+} // namespace firebase
+
+#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_STRING_PRINTF_H_
diff --git a/Firestore/Port/string_util.cc b/Firestore/core/src/firebase/firestore/util/string_util.cc
index 2587860..b7f1ed9 100644
--- a/Firestore/Port/string_util.cc
+++ b/Firestore/core/src/firebase/firestore/util/string_util.cc
@@ -14,19 +14,19 @@
* limitations under the License.
*/
-#include "Firestore/Port/string_util.h"
+#include "Firestore/core/src/firebase/firestore/util/string_util.h"
-#include <leveldb/db.h>
+namespace firebase {
+namespace firestore {
+namespace util {
-namespace Firestore {
-
-std::string PrefixSuccessor(leveldb::Slice prefix) {
+std::string PrefixSuccessor(absl::string_view prefix) {
// We can increment the last character in the string and be done
// unless that character is 255 (0xff), in which case we have to erase the
// last character and increment the previous character, unless that
// is 255, etc. If the string is empty or consists entirely of
// 255's, we just return the empty string.
- std::string limit(prefix.data(), prefix.size());
+ std::string limit(prefix);
while (!limit.empty()) {
size_t index = limit.length() - 1;
if (limit[index] == '\xff') { // char literal avoids signed/unsigned.
@@ -39,7 +39,7 @@ std::string PrefixSuccessor(leveldb::Slice prefix) {
return limit;
}
-std::string ImmediateSuccessor(leveldb::Slice s) {
+std::string ImmediateSuccessor(absl::string_view s) {
// Return the input string, with an additional NUL byte appended.
std::string out;
out.reserve(s.size() + 1);
@@ -48,4 +48,6 @@ std::string ImmediateSuccessor(leveldb::Slice s) {
return out;
}
-} // namespace Firestore
+} // namespace util
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/src/firebase/firestore/util/string_util.h b/Firestore/core/src/firebase/firestore/util/string_util.h
new file mode 100644
index 0000000..96ba0b0
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/util/string_util.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2017 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.
+ */
+
+// Useful string functions and so forth. This is a grab-bag file.
+//
+// These functions work fine for UTF-8 strings as long as you can
+// consider them to be just byte strings. For example, due to the
+// design of UTF-8 you do not need to worry about accidental matches,
+// as long as all your inputs are valid UTF-8 (use \uHHHH, not \xHH or \oOOO).
+
+#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_STRING_UTIL_H_
+#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_STRING_UTIL_H_
+
+#include <string>
+
+#include "absl/strings/string_view.h"
+
+namespace firebase {
+namespace firestore {
+namespace util {
+
+/**
+ * Returns the smallest lexicographically larger string of equal or smaller
+ * length. Returns an empty string if there is no such successor (if the input
+ * is empty or consists entirely of 0xff bytes).
+ * Useful for calculating the smallest lexicographically larger string
+ * that will not be prefixed by the input string.
+ *
+ * Examples:
+ * "a" -> "b", "aaa" -> "aab", "aa\xff" -> "ab", "\xff" -> "", "" -> ""
+ */
+std::string PrefixSuccessor(absl::string_view prefix);
+
+/**
+ * Returns the immediate lexicographically-following string. This is useful to
+ * turn an inclusive range into something that can be used with Bigtable's
+ * SetLimitRow():
+ *
+ * // Inclusive range [min_element, max_element].
+ * string min_element = ...;
+ * string max_element = ...;
+ *
+ * // Equivalent range [range_start, range_end).
+ * string range_start = min_element;
+ * string range_end = ImmediateSuccessor(max_element);
+ *
+ * WARNING: Returns the input string with a '\0' appended; if you call c_str()
+ * on the result, it will compare equal to s.
+ *
+ * WARNING: Transforms "" -> "\0"; this doesn't account for Bigtable's special
+ * treatment of "" as infinity.
+ */
+std::string ImmediateSuccessor(absl::string_view s);
+
+/**
+ * Returns true if the given value starts with the given prefix.
+ */
+bool StartsWith(const std::string &value, const std::string &prefix);
+
+} // namespace util
+} // namespace firestore
+} // namespace firebase
+
+#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_STRING_UTIL_H_
diff --git a/Firestore/core/test/firebase/firestore/CMakeLists.txt b/Firestore/core/test/firebase/firestore/CMakeLists.txt
new file mode 100644
index 0000000..61692de
--- /dev/null
+++ b/Firestore/core/test/firebase/firestore/CMakeLists.txt
@@ -0,0 +1,22 @@
+# Copyright 2018 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.
+
+cc_test(
+ firebase_firestore_types_test
+ SOURCES
+ geo_point_test.cc
+ timestamp_test.cc
+ DEPENDS
+ firebase_firestore_types
+)
diff --git a/Firestore/core/test/firebase/firestore/auth/CMakeLists.txt b/Firestore/core/test/firebase/firestore/auth/CMakeLists.txt
new file mode 100644
index 0000000..a6aa2ef
--- /dev/null
+++ b/Firestore/core/test/firebase/firestore/auth/CMakeLists.txt
@@ -0,0 +1,35 @@
+# Copyright 2018 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.
+
+cc_test(
+ firebase_firestore_auth_test
+ SOURCES
+ credentials_provider_test.cc
+ empty_credentials_provider_test.cc
+ token_test.cc
+ user_test.cc
+ DEPENDS
+ firebase_firestore_auth
+)
+
+if(APPLE)
+ cc_test(
+ firebase_firestore_auth_apple_test
+ SOURCES
+ firebase_credentials_provider_test.mm
+ DEPENDS
+ firebase_firestore_auth_apple
+ firebase_firestore_testutil
+ )
+endif(APPLE)
diff --git a/Firestore/core/test/firebase/firestore/auth/credentials_provider_test.cc b/Firestore/core/test/firebase/firestore/auth/credentials_provider_test.cc
new file mode 100644
index 0000000..69c3def
--- /dev/null
+++ b/Firestore/core/test/firebase/firestore/auth/credentials_provider_test.cc
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#include "Firestore/core/src/firebase/firestore/auth/credentials_provider.h"
+
+#include "Firestore/core/src/firebase/firestore/util/statusor.h"
+#include "gtest/gtest.h"
+
+namespace firebase {
+namespace firestore {
+namespace auth {
+
+#define UNUSED(x) (void)(x)
+
+TEST(CredentialsProvider, Typedef) {
+ TokenListener token_listener = [](util::StatusOr<Token> token) {
+ UNUSED(token);
+ };
+ EXPECT_NE(nullptr, token_listener);
+ EXPECT_TRUE(token_listener);
+
+ token_listener = nullptr;
+ EXPECT_EQ(nullptr, token_listener);
+ EXPECT_FALSE(token_listener);
+
+ UserChangeListener user_change_listener = [](User user) { UNUSED(user); };
+ EXPECT_NE(nullptr, user_change_listener);
+ EXPECT_TRUE(user_change_listener);
+
+ user_change_listener = nullptr;
+ EXPECT_EQ(nullptr, user_change_listener);
+ EXPECT_FALSE(user_change_listener);
+}
+
+} // namespace auth
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/test/firebase/firestore/auth/empty_credentials_provider_test.cc b/Firestore/core/test/firebase/firestore/auth/empty_credentials_provider_test.cc
new file mode 100644
index 0000000..60845e5
--- /dev/null
+++ b/Firestore/core/test/firebase/firestore/auth/empty_credentials_provider_test.cc
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#include "Firestore/core/src/firebase/firestore/auth/empty_credentials_provider.h"
+
+#include "Firestore/core/src/firebase/firestore/util/statusor.h"
+#include "gtest/gtest.h"
+
+namespace firebase {
+namespace firestore {
+namespace auth {
+
+TEST(EmptyCredentialsProvider, GetToken) {
+ EmptyCredentialsProvider credentials_provider;
+ credentials_provider.GetToken(
+ /*force_refresh=*/true, [](util::StatusOr<Token> result) {
+ EXPECT_TRUE(result.ok());
+ const Token& token = result.ValueOrDie();
+ EXPECT_ANY_THROW(token.token());
+ const User& user = token.user();
+ EXPECT_EQ("", user.uid());
+ EXPECT_FALSE(user.is_authenticated());
+ });
+}
+
+TEST(EmptyCredentialsProvider, SetListener) {
+ EmptyCredentialsProvider credentials_provider;
+ credentials_provider.SetUserChangeListener([](User user) {
+ EXPECT_EQ("", user.uid());
+ EXPECT_FALSE(user.is_authenticated());
+ });
+
+ credentials_provider.SetUserChangeListener(nullptr);
+}
+
+} // namespace auth
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/test/firebase/firestore/auth/firebase_credentials_provider_test.mm b/Firestore/core/test/firebase/firestore/auth/firebase_credentials_provider_test.mm
new file mode 100644
index 0000000..9d358b5
--- /dev/null
+++ b/Firestore/core/test/firebase/firestore/auth/firebase_credentials_provider_test.mm
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#include "Firestore/core/src/firebase/firestore/auth/firebase_credentials_provider_apple.h"
+
+#import <FirebaseCore/FIRApp.h>
+
+#import <FirebaseCore/FIRAppInternal.h>
+#import <FirebaseCore/FIROptionsInternal.h>
+
+#include "Firestore/core/src/firebase/firestore/util/statusor.h"
+#include "Firestore/core/src/firebase/firestore/util/string_apple.h"
+#include "Firestore/core/test/firebase/firestore/testutil/app_testing.h"
+
+#include "gtest/gtest.h"
+
+namespace firebase {
+namespace firestore {
+namespace auth {
+
+FIRApp* AppWithFakeUidAndToken(NSString* _Nullable uid,
+ NSString* _Nullable token) {
+ FIRApp* app = testutil::AppForUnitTesting();
+ app.getUIDImplementation = ^NSString* {
+ return uid;
+ };
+ app.getTokenImplementation = ^(BOOL, FIRTokenCallback callback) {
+ callback(token, nil);
+ };
+ return app;
+}
+
+FIRApp* AppWithFakeUid(NSString* _Nullable uid) {
+ return AppWithFakeUidAndToken(uid, uid == nil ? nil : @"default token");
+}
+
+TEST(FirebaseCredentialsProviderTest, GetTokenUnauthenticated) {
+ FIRApp* app = AppWithFakeUid(nil);
+
+ FirebaseCredentialsProvider credentials_provider(app);
+ credentials_provider.GetToken(
+ /*force_refresh=*/true, [](util::StatusOr<Token> result) {
+ EXPECT_TRUE(result.ok());
+ const Token& token = result.ValueOrDie();
+ EXPECT_ANY_THROW(token.token());
+ const User& user = token.user();
+ EXPECT_EQ("", user.uid());
+ EXPECT_FALSE(user.is_authenticated());
+ });
+}
+
+TEST(FirebaseCredentialsProviderTest, GetToken) {
+ FIRApp* app = AppWithFakeUidAndToken(@"fake uid", @"token for fake uid");
+
+ FirebaseCredentialsProvider credentials_provider(app);
+ credentials_provider.GetToken(
+ /*force_refresh=*/true, [](util::StatusOr<Token> result) {
+ EXPECT_TRUE(result.ok());
+ const Token& token = result.ValueOrDie();
+ EXPECT_EQ("token for fake uid", token.token());
+ const User& user = token.user();
+ EXPECT_EQ("fake uid", user.uid());
+ EXPECT_TRUE(user.is_authenticated());
+ });
+}
+
+TEST(FirebaseCredentialsProviderTest, SetListener) {
+ FIRApp* app = AppWithFakeUid(@"fake uid");
+
+ FirebaseCredentialsProvider credentials_provider(app);
+ credentials_provider.SetUserChangeListener([](User user) {
+ EXPECT_EQ("fake uid", user.uid());
+ EXPECT_TRUE(user.is_authenticated());
+ });
+
+ credentials_provider.SetUserChangeListener(nullptr);
+}
+
+} // namespace auth
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/test/firebase/firestore/auth/token_test.cc b/Firestore/core/test/firebase/firestore/auth/token_test.cc
new file mode 100644
index 0000000..e34053e
--- /dev/null
+++ b/Firestore/core/test/firebase/firestore/auth/token_test.cc
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#include "Firestore/core/src/firebase/firestore/auth/token.h"
+
+#include "gtest/gtest.h"
+
+namespace firebase {
+namespace firestore {
+namespace auth {
+
+TEST(Token, Getter) {
+ Token token("token", User("abc"));
+ EXPECT_EQ("token", token.token());
+ EXPECT_EQ(User("abc"), token.user());
+}
+
+TEST(Token, UnauthenticatedToken) {
+ const Token& token = Token::Unauthenticated();
+ EXPECT_ANY_THROW(token.token());
+ EXPECT_EQ(User::Unauthenticated(), token.user());
+}
+
+} // namespace auth
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/test/firebase/firestore/auth/user_test.cc b/Firestore/core/test/firebase/firestore/auth/user_test.cc
new file mode 100644
index 0000000..7277283
--- /dev/null
+++ b/Firestore/core/test/firebase/firestore/auth/user_test.cc
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#include "Firestore/core/src/firebase/firestore/auth/user.h"
+
+#include "gtest/gtest.h"
+
+namespace firebase {
+namespace firestore {
+namespace auth {
+
+TEST(User, Getter) {
+ User anonymous;
+ EXPECT_EQ("", anonymous.uid());
+ EXPECT_FALSE(anonymous.is_authenticated());
+
+ User signin("abc");
+ EXPECT_EQ("abc", signin.uid());
+ EXPECT_TRUE(signin.is_authenticated());
+
+ User copy;
+ copy = signin;
+ EXPECT_EQ(signin, copy);
+}
+
+TEST(User, Unauthenticated) {
+ User unauthenticated = User::Unauthenticated();
+ EXPECT_EQ("", unauthenticated.uid());
+ EXPECT_FALSE(unauthenticated.is_authenticated());
+}
+
+TEST(User, Comparison) {
+ EXPECT_EQ(User(), User());
+ EXPECT_EQ(User("abc"), User("abc"));
+ EXPECT_NE(User(), User("abc"));
+ EXPECT_NE(User("abc"), User("xyz"));
+}
+
+TEST(User, Hash) {
+ const HashUser hash;
+ EXPECT_EQ(hash(User("abc")), hash(User("abc")));
+}
+
+} // namespace auth
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/test/firebase/firestore/core/CMakeLists.txt b/Firestore/core/test/firebase/firestore/core/CMakeLists.txt
new file mode 100644
index 0000000..5b4c55a
--- /dev/null
+++ b/Firestore/core/test/firebase/firestore/core/CMakeLists.txt
@@ -0,0 +1,22 @@
+# Copyright 2018 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.
+
+cc_test(
+ firebase_firestore_core_test
+ SOURCES
+ database_info_test.cc
+ target_id_generator_test.cc
+ DEPENDS
+ firebase_firestore_core
+)
diff --git a/Firestore/core/test/firebase/firestore/core/database_info_test.cc b/Firestore/core/test/firebase/firestore/core/database_info_test.cc
new file mode 100644
index 0000000..4c9691c
--- /dev/null
+++ b/Firestore/core/test/firebase/firestore/core/database_info_test.cc
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#include "Firestore/core/src/firebase/firestore/core/database_info.h"
+
+#include "gtest/gtest.h"
+
+namespace firebase {
+namespace firestore {
+namespace core {
+
+using firebase::firestore::model::DatabaseId;
+
+TEST(DatabaseInfo, Getter) {
+ DatabaseInfo info(DatabaseId("project id", "database id"), "key",
+ "http://host", true);
+ EXPECT_EQ(DatabaseId("project id", "database id"), info.database_id());
+ EXPECT_EQ("key", info.persistence_key());
+ EXPECT_EQ("http://host", info.host());
+ EXPECT_TRUE(info.ssl_enabled());
+}
+
+TEST(DatabaseInfo, DefaultDatabase) {
+ DatabaseInfo info(DatabaseId("project id", DatabaseId::kDefault), "key",
+ "http://host", false);
+ EXPECT_EQ("project id", info.database_id().project_id());
+ EXPECT_EQ("(default)", info.database_id().database_id());
+ EXPECT_EQ("key", info.persistence_key());
+ EXPECT_EQ("http://host", info.host());
+ EXPECT_FALSE(info.ssl_enabled());
+}
+
+} // namespace core
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/test/firebase/firestore/core/target_id_generator_test.cc b/Firestore/core/test/firebase/firestore/core/target_id_generator_test.cc
new file mode 100644
index 0000000..7eca335
--- /dev/null
+++ b/Firestore/core/test/firebase/firestore/core/target_id_generator_test.cc
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#include "Firestore/core/src/firebase/firestore/core/target_id_generator.h"
+
+#include "gtest/gtest.h"
+
+namespace firebase {
+namespace firestore {
+namespace core {
+
+TEST(TargetIdGenerator, Constructor) {
+ TargetIdGenerator local_store_generator =
+ TargetIdGenerator::LocalStoreTargetIdGenerator(0);
+ TargetIdGenerator sync_engine_generator =
+ TargetIdGenerator::SyncEngineTargetIdGenerator(0);
+ EXPECT_EQ(TargetIdGeneratorId::LocalStore,
+ local_store_generator.generator_id());
+ EXPECT_EQ(2, local_store_generator.NextId());
+ EXPECT_EQ(TargetIdGeneratorId::SyncEngine,
+ sync_engine_generator.generator_id());
+ EXPECT_EQ(1, sync_engine_generator.NextId());
+}
+
+TEST(TargetIdGenerator, SkipPast) {
+ EXPECT_EQ(1, TargetIdGenerator::SyncEngineTargetIdGenerator(-1).NextId());
+ EXPECT_EQ(3, TargetIdGenerator::SyncEngineTargetIdGenerator(2).NextId());
+ EXPECT_EQ(5, TargetIdGenerator::SyncEngineTargetIdGenerator(4).NextId());
+
+ for (int i = 4; i < 12; ++i) {
+ TargetIdGenerator a = TargetIdGenerator::LocalStoreTargetIdGenerator(i);
+ TargetIdGenerator b = TargetIdGenerator::SyncEngineTargetIdGenerator(i);
+ EXPECT_EQ((i + 2) & ~1, a.NextId());
+ EXPECT_EQ((i + 1) | 1, b.NextId());
+ }
+
+ EXPECT_EQ(13, TargetIdGenerator::SyncEngineTargetIdGenerator(12).NextId());
+ EXPECT_EQ(24, TargetIdGenerator::LocalStoreTargetIdGenerator(22).NextId());
+}
+
+TEST(TargetIdGenerator, Increment) {
+ TargetIdGenerator a = TargetIdGenerator::LocalStoreTargetIdGenerator(0);
+ EXPECT_EQ(2, a.NextId());
+ EXPECT_EQ(4, a.NextId());
+ EXPECT_EQ(6, a.NextId());
+
+ TargetIdGenerator b = TargetIdGenerator::LocalStoreTargetIdGenerator(46);
+ EXPECT_EQ(48, b.NextId());
+ EXPECT_EQ(50, b.NextId());
+ EXPECT_EQ(52, b.NextId());
+ EXPECT_EQ(54, b.NextId());
+
+ TargetIdGenerator c = TargetIdGenerator::SyncEngineTargetIdGenerator(0);
+ EXPECT_EQ(1, c.NextId());
+ EXPECT_EQ(3, c.NextId());
+ EXPECT_EQ(5, c.NextId());
+
+ TargetIdGenerator d = TargetIdGenerator::SyncEngineTargetIdGenerator(46);
+ EXPECT_EQ(47, d.NextId());
+ EXPECT_EQ(49, d.NextId());
+ EXPECT_EQ(51, d.NextId());
+ EXPECT_EQ(53, d.NextId());
+}
+
+} // namespace core
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/test/firebase/firestore/geo_point_test.cc b/Firestore/core/test/firebase/firestore/geo_point_test.cc
new file mode 100644
index 0000000..bd8d76f
--- /dev/null
+++ b/Firestore/core/test/firebase/firestore/geo_point_test.cc
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#include "Firestore/core/include/firebase/firestore/geo_point.h"
+
+#include "gtest/gtest.h"
+
+namespace firebase {
+namespace firestore {
+
+TEST(GeoPoint, Getter) {
+ const GeoPoint zero;
+ EXPECT_EQ(0, zero.latitude());
+ EXPECT_EQ(0, zero.longitude());
+
+ const GeoPoint point{12, 34};
+ EXPECT_EQ(12, point.latitude());
+ EXPECT_EQ(34, point.longitude());
+}
+
+TEST(GeoPoint, Comparison) {
+ EXPECT_EQ(GeoPoint(12, 34), GeoPoint(12, 34));
+ EXPECT_LT(GeoPoint(12, 34), GeoPoint(34, 12));
+ EXPECT_LT(GeoPoint(12, 34), GeoPoint(12, 56));
+}
+
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/test/firebase/firestore/immutable/CMakeLists.txt b/Firestore/core/test/firebase/firestore/immutable/CMakeLists.txt
new file mode 100644
index 0000000..753e2d0
--- /dev/null
+++ b/Firestore/core/test/firebase/firestore/immutable/CMakeLists.txt
@@ -0,0 +1,25 @@
+# Copyright 2018 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.
+
+cc_test(
+ firebase_firestore_immutable_test
+ SOURCES
+ array_sorted_map_test.cc
+ testing.h
+ sorted_map_test.cc
+ tree_sorted_map_test.cc
+ DEPENDS
+ firebase_firestore_immutable
+ firebase_firestore_util
+)
diff --git a/Firestore/core/test/firebase/firestore/immutable/array_sorted_map_test.cc b/Firestore/core/test/firebase/firestore/immutable/array_sorted_map_test.cc
new file mode 100644
index 0000000..6758dd5
--- /dev/null
+++ b/Firestore/core/test/firebase/firestore/immutable/array_sorted_map_test.cc
@@ -0,0 +1,204 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#include "Firestore/core/src/firebase/firestore/immutable/array_sorted_map.h"
+
+#include <numeric>
+#include <random>
+
+#include "Firestore/core/src/firebase/firestore/util/secure_random.h"
+
+#include "Firestore/core/test/firebase/firestore/immutable/testing.h"
+#include "gtest/gtest.h"
+
+namespace firebase {
+namespace firestore {
+namespace immutable {
+namespace impl {
+
+using IntMap = ArraySortedMap<int, int>;
+constexpr IntMap::size_type kFixedSize = IntMap::kFixedSize;
+
+// TODO(wilhuff): ReverseTraversal
+
+#define ASSERT_SEQ_EQ(x, y) ASSERT_EQ((x), Append(y));
+#define EXPECT_SEQ_EQ(x, y) EXPECT_EQ((x), Append(y));
+
+TEST(ArraySortedMap, SearchForSpecificKey) {
+ IntMap map{{1, 3}, {2, 4}};
+
+ ASSERT_TRUE(Found(map, 1, 3));
+ ASSERT_TRUE(Found(map, 2, 4));
+ ASSERT_TRUE(NotFound(map, 3));
+}
+
+TEST(ArraySortedMap, RemoveKeyValuePair) {
+ IntMap map{{1, 3}, {2, 4}};
+
+ IntMap new_map = map.erase(1);
+ ASSERT_TRUE(Found(new_map, 2, 4));
+ ASSERT_TRUE(NotFound(new_map, 1));
+
+ // Make sure the original one is not mutated
+ ASSERT_TRUE(Found(map, 1, 3));
+ ASSERT_TRUE(Found(map, 2, 4));
+}
+
+TEST(ArraySortedMap, MoreRemovals) {
+ IntMap map = IntMap{}
+ .insert(1, 1)
+ .insert(50, 50)
+ .insert(3, 3)
+ .insert(4, 4)
+ .insert(7, 7)
+ .insert(9, 9)
+ .insert(1, 20)
+ .insert(18, 18)
+ .insert(3, 2)
+ .insert(4, 71)
+ .insert(7, 42)
+ .insert(9, 88);
+
+ ASSERT_TRUE(Found(map, 7, 42));
+ ASSERT_TRUE(Found(map, 3, 2));
+ ASSERT_TRUE(Found(map, 1, 20));
+
+ IntMap s1 = map.erase(7);
+ IntMap s2 = map.erase(3);
+ IntMap s3 = map.erase(1);
+
+ ASSERT_TRUE(NotFound(s1, 7));
+ ASSERT_TRUE(Found(s1, 3, 2));
+ ASSERT_TRUE(Found(s1, 1, 20));
+
+ ASSERT_TRUE(Found(s2, 7, 42));
+ ASSERT_TRUE(NotFound(s2, 3));
+ ASSERT_TRUE(Found(s2, 1, 20));
+
+ ASSERT_TRUE(Found(s3, 7, 42));
+ ASSERT_TRUE(Found(s3, 3, 2));
+ ASSERT_TRUE(NotFound(s3, 1));
+}
+
+TEST(ArraySortedMap, RemovesMiddle) {
+ IntMap map{{1, 1}, {2, 2}, {3, 3}};
+ ASSERT_TRUE(Found(map, 1, 1));
+ ASSERT_TRUE(Found(map, 2, 2));
+ ASSERT_TRUE(Found(map, 3, 3));
+
+ IntMap s1 = map.erase(2);
+ ASSERT_TRUE(Found(s1, 1, 1));
+ ASSERT_TRUE(NotFound(s1, 2));
+ ASSERT_TRUE(Found(s1, 3, 3));
+}
+
+TEST(ArraySortedMap, Increasing) {
+ auto total = static_cast<int>(kFixedSize);
+ IntMap map;
+
+ for (int i = 0; i < total; i++) {
+ map = map.insert(i, i);
+ }
+ ASSERT_EQ(kFixedSize, map.size());
+
+ for (int i = 0; i < total; i++) {
+ map = map.erase(i);
+ }
+ ASSERT_EQ(0u, map.size());
+}
+
+TEST(ArraySortedMap, Override) {
+ IntMap map = IntMap{}.insert(10, 10).insert(10, 8);
+
+ ASSERT_TRUE(Found(map, 10, 8));
+ ASSERT_FALSE(Found(map, 10, 10));
+}
+
+TEST(ArraySortedMap, ChecksSize) {
+ std::vector<int> to_insert = Sequence(kFixedSize);
+ IntMap map = ToMap<IntMap>(to_insert);
+
+ // Replacing an existing entry should not hit increase size
+ map = map.insert(5, 10);
+
+ int next = kFixedSize;
+ ASSERT_ANY_THROW(map.insert(next, next));
+}
+
+TEST(ArraySortedMap, EmptyGet) {
+ IntMap map;
+ EXPECT_TRUE(NotFound(map, 10));
+}
+
+TEST(ArraySortedMap, EmptyRemoval) {
+ IntMap map;
+ IntMap new_map = map.erase(1);
+ EXPECT_TRUE(new_map.empty());
+ EXPECT_EQ(0u, new_map.size());
+ EXPECT_TRUE(NotFound(new_map, 1));
+}
+
+TEST(ArraySortedMap, InsertionAndRemovalOfMaxItems) {
+ auto expected_size = kFixedSize;
+ auto n = static_cast<int>(expected_size);
+ std::vector<int> to_insert = Shuffled(Sequence(n));
+ std::vector<int> to_remove = Shuffled(to_insert);
+
+ // Add them to the map
+ IntMap map = ToMap<IntMap>(to_insert);
+ ASSERT_EQ(expected_size, map.size())
+ << "Check if all N objects are in the map";
+
+ // check the order is correct
+ ASSERT_SEQ_EQ(Pairs(Sorted(to_insert)), map);
+
+ for (int i : to_remove) {
+ map = map.erase(i);
+ }
+ ASSERT_EQ(0u, map.size()) << "Check we removed all of the items";
+}
+
+TEST(ArraySortedMap, BalanceProblem) {
+ std::vector<int> to_insert{1, 7, 8, 5, 2, 6, 4, 0, 3};
+
+ IntMap map = ToMap<IntMap>(to_insert);
+ ASSERT_SEQ_EQ(Pairs(Sorted(to_insert)), map);
+}
+
+// TODO(wilhuff): Iterators
+
+// TODO(wilhuff): IndexOf
+
+TEST(ArraySortedMap, AvoidsCopying) {
+ IntMap map = IntMap{}.insert(10, 20);
+ auto found = map.find(10);
+ ASSERT_NE(found, map.end());
+ EXPECT_EQ(20, found->second);
+
+ // Verify that inserting something with equal keys and values just returns
+ // the same underlying array.
+ IntMap duped = map.insert(10, 20);
+ auto duped_found = duped.find(10);
+
+ // If everything worked correctly, the backing array should not have been
+ // copied and the pointer to the entry with 10 as key should be the same.
+ EXPECT_EQ(found, duped_found);
+}
+
+} // namespace impl
+} // namespace immutable
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/test/firebase/firestore/immutable/sorted_map_test.cc b/Firestore/core/test/firebase/firestore/immutable/sorted_map_test.cc
new file mode 100644
index 0000000..747c66b
--- /dev/null
+++ b/Firestore/core/test/firebase/firestore/immutable/sorted_map_test.cc
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#include "Firestore/core/src/firebase/firestore/immutable/sorted_map.h"
+
+#include <numeric>
+#include <random>
+
+#include "Firestore/core/src/firebase/firestore/immutable/array_sorted_map.h"
+#include "Firestore/core/src/firebase/firestore/immutable/tree_sorted_map.h"
+#include "Firestore/core/src/firebase/firestore/util/secure_random.h"
+
+#include "Firestore/core/test/firebase/firestore/immutable/testing.h"
+#include "gtest/gtest.h"
+
+using firebase::firestore::immutable::impl::SortedMapBase;
+
+namespace firebase {
+namespace firestore {
+namespace immutable {
+
+using SizeType = SortedMapBase::size_type;
+
+template <typename MapType>
+struct TestPolicy {
+ // TODO(mcg): increase beyond what ArraySortedMap supports
+ static const SizeType kFixedSize = SortedMapBase::kFixedSize;
+};
+
+template <typename IntMap>
+class SortedMapTest : public ::testing::Test {
+ public:
+ template <typename Integer = SizeType>
+ Integer fixed_size() {
+ return static_cast<Integer>(TestPolicy<IntMap>::kFixedSize);
+ }
+};
+
+// NOLINTNEXTLINE: must be a typedef for the gtest macros
+typedef ::testing::Types<SortedMap<int, int>,
+ impl::ArraySortedMap<int, int>,
+ impl::TreeSortedMap<int, int>>
+ TestedTypes;
+TYPED_TEST_CASE(SortedMapTest, TestedTypes);
+
+TYPED_TEST(SortedMapTest, EmptySize) {
+ TypeParam map;
+ EXPECT_TRUE(map.empty());
+ EXPECT_EQ(0u, map.size());
+}
+
+TYPED_TEST(SortedMapTest, Empty) {
+ TypeParam map = TypeParam{}.insert(10, 10).erase(10);
+ EXPECT_TRUE(map.empty());
+ EXPECT_EQ(0u, map.size());
+
+ // TODO(wilhuff): re-add find
+ // EXPECT_TRUE(NotFound(map, 1));
+ // EXPECT_TRUE(NotFound(map, 10));
+}
+
+} // namespace immutable
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/test/firebase/firestore/immutable/testing.h b/Firestore/core/test/firebase/firestore/immutable/testing.h
new file mode 100644
index 0000000..337140f
--- /dev/null
+++ b/Firestore/core/test/firebase/firestore/immutable/testing.h
@@ -0,0 +1,162 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#ifndef FIRESTORE_CORE_TEST_FIREBASE_FIRESTORE_IMMUTABLE_TESTING_H_
+#define FIRESTORE_CORE_TEST_FIREBASE_FIRESTORE_IMMUTABLE_TESTING_H_
+
+#include <algorithm>
+#include <utility>
+#include <vector>
+
+#include "Firestore/core/src/firebase/firestore/util/secure_random.h"
+#include "gtest/gtest.h"
+
+namespace firebase {
+namespace firestore {
+namespace immutable {
+
+template <typename Container, typename K>
+testing::AssertionResult NotFound(const Container& map, const K& key) {
+ auto found = map.find(key);
+ if (found == map.end()) {
+ return testing::AssertionSuccess();
+ } else {
+ return testing::AssertionFailure()
+ << "Should not have found (" << found->first << ", " << found->second
+ << ")";
+ }
+}
+
+template <typename Container, typename K, typename V>
+testing::AssertionResult Found(const Container& map,
+ const K& key,
+ const V& expected) {
+ auto found = map.find(key);
+ if (found == map.end()) {
+ return testing::AssertionFailure() << "Did not find key " << key;
+ }
+ if (found->second == expected) {
+ return testing::AssertionSuccess();
+ } else {
+ return testing::AssertionFailure() << "Found entry was (" << found->first
+ << ", " << found->second << ")";
+ }
+}
+
+/** Creates an empty vector (for readability). */
+inline std::vector<int> Empty() {
+ return {};
+}
+
+/**
+ * Creates a vector containing a sequence of integers from the given starting
+ * element up to, but not including, the given end element, with values
+ * incremented by the given step.
+ *
+ * If step is negative the sequence is in descending order (but still starting
+ * at start and ending before end).
+ */
+inline std::vector<int> Sequence(int start, int end, int step = 1) {
+ std::vector<int> result;
+ if (step > 0) {
+ for (int i = start; i < end; i += step) {
+ result.push_back(i);
+ }
+ } else {
+ for (int i = start; i > end; i += step) {
+ result.push_back(i);
+ }
+ }
+ return result;
+}
+
+/**
+ * Creates a vector containing a sequence of integers with the given number of
+ * elements, from zero up to, but not including the given value.
+ */
+inline std::vector<int> Sequence(int num_elements) {
+ return Sequence(0, num_elements);
+}
+
+/**
+ * Creates a copy of the given vector with contents shuffled randomly.
+ */
+inline std::vector<int> Shuffled(const std::vector<int>& values) {
+ std::vector<int> result{values};
+ util::SecureRandom rng;
+ std::shuffle(result.begin(), result.end(), rng);
+ return result;
+}
+
+/**
+ * Creates a copy of the given vector with contents sorted.
+ */
+inline std::vector<int> Sorted(const std::vector<int>& values) {
+ std::vector<int> result{values};
+ std::sort(result.begin(), result.end());
+ return result;
+}
+
+/**
+ * Creates a copy of the given vector with contents reversed.
+ */
+inline std::vector<int> Reversed(const std::vector<int>& values) {
+ std::vector<int> result{values};
+ std::reverse(result.begin(), result.end());
+ return result;
+}
+
+/**
+ * Creates a vector of pairs where each pair has the same first and second
+ * corresponding to an element in the given vector.
+ */
+inline std::vector<std::pair<int, int>> Pairs(const std::vector<int>& values) {
+ std::vector<std::pair<int, int>> result;
+ for (auto&& value : values) {
+ result.emplace_back(value, value);
+ }
+ return result;
+}
+
+/**
+ * Creates a SortedMap by inserting a pair for each value in the vector.
+ * Each pair will have the same key and value.
+ */
+template <typename Container>
+Container ToMap(const std::vector<int>& values) {
+ Container result;
+ for (auto&& value : values) {
+ result = result.insert(value, value);
+ }
+ return result;
+}
+
+/**
+ * Appends the contents of the given container to a new vector.
+ */
+template <typename Container>
+std::vector<typename Container::value_type> Append(const Container& container) {
+ return {container.begin(), container.end()};
+}
+
+#define ASSERT_SEQ_EQ(x, y) ASSERT_EQ((x), Append(y));
+#define EXPECT_SEQ_EQ(x, y) EXPECT_EQ((x), Append(y));
+
+} // namespace immutable
+} // namespace firestore
+} // namespace firebase
+
+#endif // FIRESTORE_CORE_TEST_FIREBASE_FIRESTORE_IMMUTABLE_TESTING_H_
diff --git a/Firestore/core/test/firebase/firestore/immutable/tree_sorted_map_test.cc b/Firestore/core/test/firebase/firestore/immutable/tree_sorted_map_test.cc
new file mode 100644
index 0000000..c03dc6c
--- /dev/null
+++ b/Firestore/core/test/firebase/firestore/immutable/tree_sorted_map_test.cc
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#include "Firestore/core/src/firebase/firestore/immutable/tree_sorted_map.h"
+
+#include "Firestore/core/src/firebase/firestore/util/secure_random.h"
+#include "Firestore/core/test/firebase/firestore/immutable/testing.h"
+#include "gtest/gtest.h"
+
+namespace firebase {
+namespace firestore {
+namespace immutable {
+namespace impl {
+
+using IntMap = TreeSortedMap<int, int>;
+
+TEST(TreeSortedMap, EmptySize) {
+ IntMap map;
+ EXPECT_TRUE(map.empty());
+ EXPECT_EQ(0u, map.size());
+ EXPECT_EQ(Color::Black, map.root().color());
+}
+
+} // namespace impl
+} // namespace immutable
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/test/firebase/firestore/local/CMakeLists.txt b/Firestore/core/test/firebase/firestore/local/CMakeLists.txt
new file mode 100644
index 0000000..f52e394
--- /dev/null
+++ b/Firestore/core/test/firebase/firestore/local/CMakeLists.txt
@@ -0,0 +1,22 @@
+# Copyright 2018 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.
+
+cc_test(
+ firebase_firestore_local_test
+ SOURCES
+ leveldb_key_test.cc
+ DEPENDS
+ firebase_firestore_local
+ firebase_firestore_model
+)
diff --git a/Firestore/core/test/firebase/firestore/local/leveldb_key_test.cc b/Firestore/core/test/firebase/firestore/local/leveldb_key_test.cc
new file mode 100644
index 0000000..9032473
--- /dev/null
+++ b/Firestore/core/test/firebase/firestore/local/leveldb_key_test.cc
@@ -0,0 +1,364 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#include "Firestore/core/src/firebase/firestore/local/leveldb_key.h"
+
+#include "Firestore/core/src/firebase/firestore/util/string_util.h"
+
+#include "Firestore/core/test/firebase/firestore/testutil/testutil.h"
+#include "absl/strings/match.h"
+#include "gtest/gtest.h"
+
+using firebase::firestore::model::BatchId;
+using firebase::firestore::model::DocumentKey;
+using firebase::firestore::model::TargetId;
+
+namespace firebase {
+namespace firestore {
+namespace local {
+
+namespace {
+
+std::string RemoteDocKey(absl::string_view path_string) {
+ return LevelDbRemoteDocumentKey::Key(testutil::Key(path_string));
+}
+
+std::string RemoteDocKeyPrefix(absl::string_view path_string) {
+ return LevelDbRemoteDocumentKey::KeyPrefix(testutil::Resource(path_string));
+}
+
+std::string DocMutationKey(absl::string_view user_id,
+ absl::string_view key,
+ model::BatchId batch_id) {
+ return LevelDbDocumentMutationKey::Key(user_id, testutil::Key(key), batch_id);
+}
+
+std::string TargetDocKey(TargetId target_id, absl::string_view key) {
+ return LevelDbTargetDocumentKey::Key(target_id, testutil::Key(key));
+}
+
+std::string DocTargetKey(absl::string_view key, TargetId target_id) {
+ return LevelDbDocumentTargetKey::Key(testutil::Key(key), target_id);
+}
+
+} // namespace
+
+/**
+ * Asserts that the description for given key is equal to the expected
+ * description.
+ *
+ * @param key A StringView of a textual key
+ * @param key An NSString that [LevelDbKey descriptionForKey:] is expected to
+ * produce.
+ */
+#define AssertExpectedKeyDescription(expected_description, key) \
+ ASSERT_EQ((expected_description), Describe(key))
+
+TEST(LevelDbMutationKeyTest, Prefixing) {
+ auto tableKey = LevelDbMutationKey::KeyPrefix();
+ auto emptyUserKey = LevelDbMutationKey::KeyPrefix("");
+ auto fooUserKey = LevelDbMutationKey::KeyPrefix("foo");
+
+ auto foo2Key = LevelDbMutationKey::Key("foo", 2);
+
+ ASSERT_TRUE(absl::StartsWith(emptyUserKey, tableKey));
+
+ // This is critical: prefixes of the a value don't convert into prefixes of
+ // the key.
+ ASSERT_TRUE(absl::StartsWith(fooUserKey, tableKey));
+ ASSERT_FALSE(absl::StartsWith(fooUserKey, emptyUserKey));
+
+ // However whole segments in common are prefixes.
+ ASSERT_TRUE(absl::StartsWith(foo2Key, tableKey));
+ ASSERT_TRUE(absl::StartsWith(foo2Key, fooUserKey));
+}
+
+TEST(LevelDbMutationKeyTest, EncodeDecodeCycle) {
+ LevelDbMutationKey key;
+ std::string user("foo");
+
+ std::vector<BatchId> batch_ids{0, 1, 100, INT_MAX - 1, INT_MAX};
+ for (auto batch_id : batch_ids) {
+ auto encoded = LevelDbMutationKey::Key(user, batch_id);
+
+ bool ok = key.Decode(encoded);
+ ASSERT_TRUE(ok);
+ ASSERT_EQ(user, key.user_id());
+ ASSERT_EQ(batch_id, key.batch_id());
+ }
+}
+
+TEST(LevelDbMutationKeyTest, Description) {
+ AssertExpectedKeyDescription("[mutation: incomplete key]",
+ LevelDbMutationKey::KeyPrefix());
+
+ AssertExpectedKeyDescription("[mutation: user_id=user1 incomplete key]",
+ LevelDbMutationKey::KeyPrefix("user1"));
+
+ auto key = LevelDbMutationKey::Key("user1", 42);
+ AssertExpectedKeyDescription("[mutation: user_id=user1 batch_id=42]", key);
+
+ AssertExpectedKeyDescription(
+ "[mutation: user_id=user1 batch_id=42 invalid "
+ "key=<hW11dGF0aW9uAAGNdXNlcjEAAYqqgCBleHRyYQ==>]",
+ key + " extra");
+
+ // Truncate the key so that it's missing its terminator.
+ key.resize(key.size() - 1);
+ AssertExpectedKeyDescription(
+ "[mutation: user_id=user1 batch_id=42 incomplete key]", key);
+}
+
+TEST(LevelDbDocumentMutationKeyTest, Prefixing) {
+ auto table_key = LevelDbDocumentMutationKey::KeyPrefix();
+ auto empty_user_key = LevelDbDocumentMutationKey::KeyPrefix("");
+ auto foo_user_key = LevelDbDocumentMutationKey::KeyPrefix("foo");
+
+ DocumentKey document_key = testutil::Key("foo/bar");
+ auto foo2_key = LevelDbDocumentMutationKey::Key("foo", document_key, 2);
+
+ ASSERT_TRUE(absl::StartsWith(empty_user_key, table_key));
+
+ // While we want a key with whole segments in common be considered a prefix
+ // it's vital that partial segments in common not be prefixes.
+ ASSERT_TRUE(absl::StartsWith(foo_user_key, table_key));
+
+ // Here even though "" is a prefix of "foo", that prefix is within a segment,
+ // so keys derived from those segments cannot be prefixes of each other.
+ ASSERT_FALSE(absl::StartsWith(foo_user_key, empty_user_key));
+ ASSERT_FALSE(absl::StartsWith(empty_user_key, foo_user_key));
+
+ // However whole segments in common are prefixes.
+ ASSERT_TRUE(absl::StartsWith(foo2_key, table_key));
+ ASSERT_TRUE(absl::StartsWith(foo2_key, foo_user_key));
+}
+
+TEST(LevelDbDocumentMutationKeyTest, EncodeDecodeCycle) {
+ LevelDbDocumentMutationKey key;
+ std::string user("foo");
+
+ std::vector<DocumentKey> document_keys{testutil::Key("a/b"),
+ testutil::Key("a/b/c/d")};
+
+ std::vector<BatchId> batch_ids{0, 1, 100, INT_MAX - 1, INT_MAX};
+
+ for (BatchId batch_id : batch_ids) {
+ for (auto&& document_key : document_keys) {
+ auto encoded =
+ LevelDbDocumentMutationKey::Key(user, document_key, batch_id);
+
+ bool ok = key.Decode(encoded);
+ ASSERT_TRUE(ok);
+ ASSERT_EQ(user, key.user_id());
+ ASSERT_EQ(document_key, key.document_key());
+ ASSERT_EQ(batch_id, key.batch_id());
+ }
+ }
+}
+
+TEST(LevelDbDocumentMutationKeyTest, Ordering) {
+ // Different user:
+ ASSERT_LT(DocMutationKey("1", "foo/bar", 0),
+ DocMutationKey("10", "foo/bar", 0));
+ ASSERT_LT(DocMutationKey("1", "foo/bar", 0),
+ DocMutationKey("2", "foo/bar", 0));
+
+ // Different paths:
+ ASSERT_LT(DocMutationKey("1", "foo/bar", 0),
+ DocMutationKey("1", "foo/baz", 0));
+ ASSERT_LT(DocMutationKey("1", "foo/bar", 0),
+ DocMutationKey("1", "foo/bar2", 0));
+ ASSERT_LT(DocMutationKey("1", "foo/bar", 0),
+ DocMutationKey("1", "foo/bar/suffix/key", 0));
+ ASSERT_LT(DocMutationKey("1", "foo/bar/suffix/key", 0),
+ DocMutationKey("1", "foo/bar2", 0));
+
+ // Different batch_id:
+ ASSERT_LT(DocMutationKey("1", "foo/bar", 0),
+ DocMutationKey("1", "foo/bar", 1));
+}
+
+TEST(LevelDbDocumentMutationKeyTest, Description) {
+ AssertExpectedKeyDescription("[document_mutation: incomplete key]",
+ LevelDbDocumentMutationKey::KeyPrefix());
+
+ AssertExpectedKeyDescription(
+ "[document_mutation: user_id=user1 incomplete key]",
+ LevelDbDocumentMutationKey::KeyPrefix("user1"));
+
+ auto key = LevelDbDocumentMutationKey::KeyPrefix(
+ "user1", testutil::Resource("foo/bar"));
+ AssertExpectedKeyDescription(
+ "[document_mutation: user_id=user1 key=foo/bar incomplete key]", key);
+
+ key = LevelDbDocumentMutationKey::Key("user1", testutil::Key("foo/bar"), 42);
+ AssertExpectedKeyDescription(
+ "[document_mutation: user_id=user1 key=foo/bar batch_id=42]", key);
+}
+
+TEST(LevelDbTargetGlobalKeyTest, EncodeDecodeCycle) {
+ LevelDbTargetGlobalKey key;
+
+ auto encoded = LevelDbTargetGlobalKey::Key();
+ bool ok = key.Decode(encoded);
+ ASSERT_TRUE(ok);
+}
+
+TEST(LevelDbTargetGlobalKeyTest, Description) {
+ AssertExpectedKeyDescription("[target_global:]",
+ LevelDbTargetGlobalKey::Key());
+}
+
+TEST(LevelDbTargetKeyTest, EncodeDecodeCycle) {
+ LevelDbTargetKey key;
+ TargetId target_id = 42;
+
+ auto encoded = LevelDbTargetKey::Key(42);
+ bool ok = key.Decode(encoded);
+ ASSERT_TRUE(ok);
+ ASSERT_EQ(target_id, key.target_id());
+}
+
+TEST(LevelDbTargetKeyTest, Description) {
+ AssertExpectedKeyDescription("[target: target_id=42]",
+ LevelDbTargetKey::Key(42));
+}
+
+TEST(LevelDbQueryTargetKeyTest, EncodeDecodeCycle) {
+ LevelDbQueryTargetKey key;
+ std::string canonical_id("foo");
+ TargetId target_id = 42;
+
+ auto encoded = LevelDbQueryTargetKey::Key(canonical_id, 42);
+ bool ok = key.Decode(encoded);
+ ASSERT_TRUE(ok);
+ ASSERT_EQ(canonical_id, key.canonical_id());
+ ASSERT_EQ(target_id, key.target_id());
+}
+
+TEST(LevelDbQueryKeyTest, Description) {
+ AssertExpectedKeyDescription("[query_target: canonical_id=foo target_id=42]",
+ LevelDbQueryTargetKey::Key("foo", 42));
+}
+
+TEST(TargetDocumentKeyTest, EncodeDecodeCycle) {
+ LevelDbTargetDocumentKey key;
+
+ auto encoded = LevelDbTargetDocumentKey::Key(42, testutil::Key("foo/bar"));
+ bool ok = key.Decode(encoded);
+ ASSERT_TRUE(ok);
+ ASSERT_EQ(42, key.target_id());
+ ASSERT_EQ(testutil::Key("foo/bar"), key.document_key());
+}
+
+TEST(TargetDocumentKeyTest, Ordering) {
+ // Different target_id:
+ ASSERT_LT(TargetDocKey(1, "foo/bar"), TargetDocKey(2, "foo/bar"));
+ ASSERT_LT(TargetDocKey(2, "foo/bar"), TargetDocKey(10, "foo/bar"));
+ ASSERT_LT(TargetDocKey(10, "foo/bar"), TargetDocKey(100, "foo/bar"));
+ ASSERT_LT(TargetDocKey(42, "foo/bar"), TargetDocKey(100, "foo/bar"));
+
+ // Different paths:
+ ASSERT_LT(TargetDocKey(1, "foo/bar"), TargetDocKey(1, "foo/baz"));
+ ASSERT_LT(TargetDocKey(1, "foo/bar"), TargetDocKey(1, "foo/bar2"));
+ ASSERT_LT(TargetDocKey(1, "foo/bar"), TargetDocKey(1, "foo/bar/suffix/key"));
+ ASSERT_LT(TargetDocKey(1, "foo/bar/suffix/key"), TargetDocKey(1, "foo/bar2"));
+}
+
+TEST(TargetDocumentKeyTest, Description) {
+ auto key = LevelDbTargetDocumentKey::Key(42, testutil::Key("foo/bar"));
+ ASSERT_EQ("[target_document: target_id=42 key=foo/bar]", Describe(key));
+}
+
+TEST(DocumentTargetKeyTest, EncodeDecodeCycle) {
+ LevelDbDocumentTargetKey key;
+
+ auto encoded = LevelDbDocumentTargetKey::Key(testutil::Key("foo/bar"), 42);
+ bool ok = key.Decode(encoded);
+ ASSERT_TRUE(ok);
+ ASSERT_EQ(testutil::Key("foo/bar"), key.document_key());
+ ASSERT_EQ(42, key.target_id());
+}
+
+TEST(DocumentTargetKeyTest, Description) {
+ auto key = LevelDbDocumentTargetKey::Key(testutil::Key("foo/bar"), 42);
+ ASSERT_EQ("[document_target: key=foo/bar target_id=42]", Describe(key));
+}
+
+TEST(DocumentTargetKeyTest, Ordering) {
+ // Different paths:
+ ASSERT_LT(DocTargetKey("foo/bar", 1), DocTargetKey("foo/baz", 1));
+ ASSERT_LT(DocTargetKey("foo/bar", 1), DocTargetKey("foo/bar2", 1));
+ ASSERT_LT(DocTargetKey("foo/bar", 1), DocTargetKey("foo/bar/suffix/key", 1));
+ ASSERT_LT(DocTargetKey("foo/bar/suffix/key", 1), DocTargetKey("foo/bar2", 1));
+
+ // Different target_id:
+ ASSERT_LT(DocTargetKey("foo/bar", 1), DocTargetKey("foo/bar", 2));
+ ASSERT_LT(DocTargetKey("foo/bar", 2), DocTargetKey("foo/bar", 10));
+ ASSERT_LT(DocTargetKey("foo/bar", 10), DocTargetKey("foo/bar", 100));
+ ASSERT_LT(DocTargetKey("foo/bar", 42), DocTargetKey("foo/bar", 100));
+}
+
+TEST(RemoteDocumentKeyTest, Prefixing) {
+ auto tableKey = LevelDbRemoteDocumentKey::KeyPrefix();
+
+ ASSERT_TRUE(absl::StartsWith(RemoteDocKey("foo/bar"), tableKey));
+
+ // This is critical: foo/bar2 should not contain foo/bar.
+ ASSERT_FALSE(
+ absl::StartsWith(RemoteDocKey("foo/bar2"), RemoteDocKey("foo/bar")));
+
+ // Prefixes must be encoded specially
+ ASSERT_FALSE(absl::StartsWith(RemoteDocKey("foo/bar/baz/quu"),
+ RemoteDocKey("foo/bar")));
+ ASSERT_TRUE(absl::StartsWith(RemoteDocKey("foo/bar/baz/quu"),
+ RemoteDocKeyPrefix("foo/bar")));
+ ASSERT_TRUE(absl::StartsWith(RemoteDocKeyPrefix("foo/bar/baz/quu"),
+ RemoteDocKeyPrefix("foo/bar")));
+ ASSERT_TRUE(absl::StartsWith(RemoteDocKeyPrefix("foo/bar/baz"),
+ RemoteDocKeyPrefix("foo/bar")));
+ ASSERT_TRUE(absl::StartsWith(RemoteDocKeyPrefix("foo/bar"),
+ RemoteDocKeyPrefix("foo")));
+}
+
+TEST(RemoteDocumentKeyTest, Ordering) {
+ ASSERT_LT(RemoteDocKey("foo/bar"), RemoteDocKey("foo/bar2"));
+ ASSERT_LT(RemoteDocKey("foo/bar"), RemoteDocKey("foo/bar/suffix/key"));
+}
+
+TEST(RemoteDocumentKeyTest, EncodeDecodeCycle) {
+ LevelDbRemoteDocumentKey key;
+
+ std::vector<std::string> paths{"foo/bar", "foo/bar2", "foo/bar/baz/quux"};
+ for (auto&& path : paths) {
+ auto encoded = RemoteDocKey(path);
+ bool ok = key.Decode(encoded);
+ ASSERT_TRUE(ok);
+ ASSERT_EQ(testutil::Key(path), key.document_key());
+ }
+}
+
+TEST(RemoteDocumentKeyTest, Description) {
+ AssertExpectedKeyDescription(
+ "[remote_document: key=foo/bar/baz/quux]",
+ LevelDbRemoteDocumentKey::Key(testutil::Key("foo/bar/baz/quux")));
+}
+
+#undef AssertExpectedKeyDescription
+
+} // namespace local
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/test/firebase/firestore/model/CMakeLists.txt b/Firestore/core/test/firebase/firestore/model/CMakeLists.txt
new file mode 100644
index 0000000..9c94677
--- /dev/null
+++ b/Firestore/core/test/firebase/firestore/model/CMakeLists.txt
@@ -0,0 +1,33 @@
+# Copyright 2018 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.
+
+cc_test(
+ firebase_firestore_model_test
+ SOURCES
+ database_id_test.cc
+ document_key_test.cc
+ document_test.cc
+ field_mask_test.cc
+ field_path_test.cc
+ field_transform_test.cc
+ field_value_test.cc
+ maybe_document_test.cc
+ no_document_test.cc
+ precondition_test.cc
+ resource_path_test.cc
+ snapshot_version_test.cc
+ transform_operations_test.cc
+ DEPENDS
+ firebase_firestore_model
+)
diff --git a/Firestore/core/test/firebase/firestore/model/database_id_test.cc b/Firestore/core/test/firebase/firestore/model/database_id_test.cc
new file mode 100644
index 0000000..c00415a
--- /dev/null
+++ b/Firestore/core/test/firebase/firestore/model/database_id_test.cc
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#include "Firestore/core/src/firebase/firestore/model/database_id.h"
+
+#include "gtest/gtest.h"
+
+namespace firebase {
+namespace firestore {
+namespace model {
+
+TEST(DatabaseId, Constructor) {
+ DatabaseId id("p", "d");
+ EXPECT_EQ("p", id.project_id());
+ EXPECT_EQ("d", id.database_id());
+ EXPECT_FALSE(id.IsDefaultDatabase());
+}
+
+TEST(DatabaseId, DefaultDb) {
+ DatabaseId id("p", DatabaseId::kDefault);
+ EXPECT_EQ("p", id.project_id());
+ EXPECT_EQ("(default)", id.database_id());
+ EXPECT_TRUE(id.IsDefaultDatabase());
+}
+
+TEST(DatabaseId, Comparison) {
+ EXPECT_LT(DatabaseId("a", "b"), DatabaseId("b", "a"));
+ EXPECT_LT(DatabaseId("a", "b"), DatabaseId("a", "c"));
+ EXPECT_EQ(DatabaseId("a", "b"), DatabaseId("a", "b"));
+}
+
+} // namespace model
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/test/firebase/firestore/model/document_key_test.cc b/Firestore/core/test/firebase/firestore/model/document_key_test.cc
new file mode 100644
index 0000000..619ee7f
--- /dev/null
+++ b/Firestore/core/test/firebase/firestore/model/document_key_test.cc
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#include <initializer_list>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+#include "Firestore/core/src/firebase/firestore/model/resource_path.h"
+#include "gtest/gtest.h"
+
+namespace firebase {
+namespace firestore {
+namespace model {
+
+TEST(DocumentKey, Constructor_Empty) {
+ const DocumentKey default_key;
+ EXPECT_TRUE(default_key.path().empty());
+
+ const auto& empty_key = DocumentKey::Empty();
+ const auto& another_empty_key = DocumentKey::Empty();
+ EXPECT_EQ(default_key, empty_key);
+ EXPECT_EQ(empty_key, another_empty_key);
+ EXPECT_EQ(&empty_key, &another_empty_key);
+}
+
+TEST(DocumentKey, Constructor_FromPath) {
+ ResourcePath path{"rooms", "firestore", "messages", "1"};
+ const DocumentKey key_from_path_copy{path};
+ // path shouldn't have been moved from.
+ EXPECT_FALSE(path.empty());
+ EXPECT_EQ(key_from_path_copy.path(), path);
+
+ const DocumentKey key_from_moved_path{std::move(path)};
+ EXPECT_TRUE(path.empty()); // NOLINT: use after move intended
+ EXPECT_FALSE(key_from_moved_path.path().empty());
+ EXPECT_EQ(key_from_path_copy.path(), key_from_moved_path.path());
+}
+
+TEST(DocumentKey, CopyAndMove) {
+ DocumentKey key({"rooms", "firestore", "messages", "1"});
+ const std::string path_string = "rooms/firestore/messages/1";
+ EXPECT_EQ(path_string, key.path().CanonicalString());
+
+ DocumentKey copied = key;
+ EXPECT_EQ(path_string, copied.path().CanonicalString());
+ EXPECT_EQ(key, copied);
+
+ const DocumentKey moved = std::move(key);
+ EXPECT_EQ(path_string, moved.path().CanonicalString());
+ EXPECT_NE(key, moved); // NOLINT: use after move intended
+ EXPECT_TRUE(key.path().empty());
+
+ // Reassignment.
+
+ key = copied;
+ EXPECT_EQ(copied, key);
+ EXPECT_EQ(path_string, key.path().CanonicalString());
+
+ key = {};
+ EXPECT_TRUE(key.path().empty());
+ key = std::move(copied);
+ EXPECT_NE(copied, key); // NOLINT: use after move intended
+ EXPECT_TRUE(copied.path().empty());
+ EXPECT_EQ(path_string, key.path().CanonicalString());
+}
+
+TEST(DocumentKey, Constructor_StaticFactory) {
+ const auto key_from_segments =
+ DocumentKey::FromSegments({"rooms", "firestore", "messages", "1"});
+ const std::string path_string = "rooms/firestore/messages/1";
+ const auto key_from_string = DocumentKey::FromPathString(path_string);
+ EXPECT_EQ(path_string, key_from_string.path().CanonicalString());
+ EXPECT_EQ(path_string, key_from_segments.path().CanonicalString());
+ EXPECT_EQ(key_from_segments, key_from_string);
+
+ const auto from_empty_path = DocumentKey::FromPathString("");
+ EXPECT_EQ(from_empty_path, DocumentKey{});
+}
+
+TEST(DocumentKey, Constructor_BadArguments) {
+ ASSERT_ANY_THROW(DocumentKey(ResourcePath{"foo"}));
+ ASSERT_ANY_THROW(DocumentKey(ResourcePath{"foo", "bar", "baz"}));
+
+ ASSERT_ANY_THROW(DocumentKey::FromSegments({"foo"}));
+ ASSERT_ANY_THROW(DocumentKey::FromSegments({"foo", "bar", "baz"}));
+
+ ASSERT_ANY_THROW(DocumentKey::FromPathString("invalid"));
+ ASSERT_ANY_THROW(DocumentKey::FromPathString("invalid//string"));
+ ASSERT_ANY_THROW(DocumentKey::FromPathString("invalid/key/path"));
+}
+
+TEST(DocumentKey, IsDocumentKey) {
+ EXPECT_TRUE(DocumentKey::IsDocumentKey({}));
+ EXPECT_FALSE(DocumentKey::IsDocumentKey({"foo"}));
+ EXPECT_TRUE(DocumentKey::IsDocumentKey({"foo", "bar"}));
+ EXPECT_FALSE(DocumentKey::IsDocumentKey({"foo", "bar", "baz"}));
+}
+
+TEST(DocumentKey, Comparison) {
+ const DocumentKey abcd({"a", "b", "c", "d"});
+ const DocumentKey abcd_too({"a", "b", "c", "d"});
+ const DocumentKey xyzw({"x", "y", "z", "w"});
+ EXPECT_EQ(abcd, abcd_too);
+ EXPECT_NE(abcd, xyzw);
+
+ const DocumentKey empty;
+ const DocumentKey a({"a", "a"});
+ const DocumentKey b({"b", "b"});
+ const DocumentKey ab({"a", "a", "b", "b"});
+
+ EXPECT_FALSE(empty < empty);
+ EXPECT_TRUE(empty <= empty);
+ EXPECT_TRUE(empty < a);
+ EXPECT_TRUE(empty <= a);
+ EXPECT_TRUE(a > empty);
+ EXPECT_TRUE(a >= empty);
+
+ EXPECT_FALSE(a < a);
+ EXPECT_TRUE(a <= a);
+ EXPECT_FALSE(a > a);
+ EXPECT_TRUE(a >= a);
+ EXPECT_TRUE(a == a);
+ EXPECT_FALSE(a != a);
+
+ EXPECT_TRUE(a < b);
+ EXPECT_TRUE(a <= b);
+ EXPECT_TRUE(b > a);
+ EXPECT_TRUE(b >= a);
+
+ EXPECT_TRUE(a < ab);
+ EXPECT_TRUE(a <= ab);
+ EXPECT_TRUE(ab > a);
+ EXPECT_TRUE(ab >= a);
+}
+
+} // namespace model
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/test/firebase/firestore/model/document_test.cc b/Firestore/core/test/firebase/firestore/model/document_test.cc
new file mode 100644
index 0000000..066c875
--- /dev/null
+++ b/Firestore/core/test/firebase/firestore/model/document_test.cc
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#include "Firestore/core/src/firebase/firestore/model/document.h"
+
+#include "absl/strings/string_view.h"
+#include "gtest/gtest.h"
+
+namespace firebase {
+namespace firestore {
+namespace model {
+
+namespace {
+
+inline Document MakeDocument(const absl::string_view data,
+ const absl::string_view path,
+ const Timestamp& timestamp,
+ bool has_local_mutations) {
+ return Document(FieldValue::ObjectValueFromMap(
+ {{"field", FieldValue::StringValue(data.data())}}),
+ DocumentKey::FromPathString(path.data()),
+ SnapshotVersion(timestamp), has_local_mutations);
+}
+
+} // anonymous namespace
+
+TEST(Document, Getter) {
+ const Document& doc =
+ MakeDocument("foo", "i/am/a/path", Timestamp(123, 456), true);
+ EXPECT_EQ(MaybeDocument::Type::Document, doc.type());
+ EXPECT_EQ(FieldValue::ObjectValueFromMap(
+ {{"field", FieldValue::StringValue("foo")}}),
+ doc.data());
+ EXPECT_EQ(DocumentKey::FromPathString("i/am/a/path"), doc.key());
+ EXPECT_EQ(SnapshotVersion(Timestamp(123, 456)), doc.version());
+ EXPECT_TRUE(doc.has_local_mutations());
+}
+
+TEST(Document, Comparison) {
+ EXPECT_EQ(MakeDocument("foo", "i/am/a/path", Timestamp(123, 456), true),
+ MakeDocument("foo", "i/am/a/path", Timestamp(123, 456), true));
+ EXPECT_NE(MakeDocument("foo", "i/am/a/path", Timestamp(123, 456), true),
+ MakeDocument("bar", "i/am/a/path", Timestamp(123, 456), true));
+ EXPECT_NE(
+ MakeDocument("foo", "i/am/a/path", Timestamp(123, 456), true),
+ MakeDocument("foo", "i/am/another/path", Timestamp(123, 456), true));
+ EXPECT_NE(MakeDocument("foo", "i/am/a/path", Timestamp(123, 456), true),
+ MakeDocument("foo", "i/am/a/path", Timestamp(456, 123), true));
+ EXPECT_NE(MakeDocument("foo", "i/am/a/path", Timestamp(123, 456), true),
+ MakeDocument("foo", "i/am/a/path", Timestamp(123, 456), false));
+
+ // Document and MaybeDocument will not equal. In particular, Document and
+ // NoDocument will not equal, which I won't test here.
+ EXPECT_NE(Document(FieldValue::ObjectValueFromMap({}),
+ DocumentKey::FromPathString("same/path"),
+ SnapshotVersion(Timestamp()), false),
+ MaybeDocument(DocumentKey::FromPathString("same/path"),
+ SnapshotVersion(Timestamp())));
+}
+
+} // namespace model
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/test/firebase/firestore/model/field_mask_test.cc b/Firestore/core/test/firebase/firestore/model/field_mask_test.cc
new file mode 100644
index 0000000..52d5951
--- /dev/null
+++ b/Firestore/core/test/firebase/firestore/model/field_mask_test.cc
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#include "Firestore/core/src/firebase/firestore/model/field_mask.h"
+
+#include <vector>
+
+#include "Firestore/core/src/firebase/firestore/model/field_path.h"
+#include "gtest/gtest.h"
+
+namespace firebase {
+namespace firestore {
+namespace model {
+
+TEST(FieldMask, ConstructorAndEqual) {
+ FieldMask mask_a{FieldPath::FromServerFormat("foo"),
+ FieldPath::FromServerFormat("bar")};
+ std::vector<FieldPath> field_path_vector{FieldPath::FromServerFormat("foo"),
+ FieldPath::FromServerFormat("bar")};
+ FieldMask mask_b{field_path_vector};
+ FieldMask mask_c{std::vector<FieldPath>{FieldPath::FromServerFormat("foo"),
+ FieldPath::FromServerFormat("bar")}};
+ EXPECT_EQ(mask_a, mask_b);
+ EXPECT_EQ(mask_b, mask_c);
+}
+
+TEST(FieldMask, Getter) {
+ FieldMask mask{FieldPath::FromServerFormat("foo"),
+ FieldPath::FromServerFormat("bar")};
+ EXPECT_EQ(std::vector<FieldPath>({FieldPath::FromServerFormat("foo"),
+ FieldPath::FromServerFormat("bar")}),
+ std::vector<FieldPath>(mask.begin(), mask.end()));
+}
+
+TEST(FieldMask, ToString) {
+ FieldMask mask{FieldPath::FromServerFormat("foo"),
+ FieldPath::FromServerFormat("bar")};
+ EXPECT_EQ("{ foo bar }", mask.ToString());
+}
+
+} // namespace model
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/test/firebase/firestore/model/field_path_test.cc b/Firestore/core/test/firebase/firestore/model/field_path_test.cc
new file mode 100644
index 0000000..a215a96
--- /dev/null
+++ b/Firestore/core/test/firebase/firestore/model/field_path_test.cc
@@ -0,0 +1,277 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#include "Firestore/core/src/firebase/firestore/model/field_path.h"
+
+#include <initializer_list>
+#include <string>
+#include <vector>
+
+#include "gtest/gtest.h"
+
+namespace firebase {
+namespace firestore {
+namespace model {
+
+TEST(FieldPath, Constructors) {
+ const FieldPath empty_path;
+ EXPECT_TRUE(empty_path.empty());
+ EXPECT_EQ(0u, empty_path.size());
+ EXPECT_TRUE(empty_path.begin() == empty_path.end());
+
+ const FieldPath path_from_list = {"rooms", "Eros", "messages"};
+ EXPECT_FALSE(path_from_list.empty());
+ EXPECT_EQ(3u, path_from_list.size());
+ EXPECT_TRUE(path_from_list.begin() + 3 == path_from_list.end());
+
+ std::vector<std::string> segments{"rooms", "Eros", "messages"};
+ const FieldPath path_from_segments{segments.begin(), segments.end()};
+ EXPECT_FALSE(path_from_segments.empty());
+ EXPECT_EQ(3u, path_from_segments.size());
+ EXPECT_TRUE(path_from_segments.begin() + 3 == path_from_segments.end());
+
+ FieldPath copied = path_from_list;
+ EXPECT_EQ(path_from_list, copied);
+ const FieldPath moved = std::move(copied);
+ EXPECT_EQ(path_from_list, moved);
+ EXPECT_NE(copied, moved); // NOLINT: use after move intended
+ EXPECT_EQ(empty_path, copied);
+}
+
+TEST(FieldPath, Indexing) {
+ const FieldPath path{"rooms", "Eros", "messages"};
+
+ EXPECT_EQ(path.first_segment(), "rooms");
+ EXPECT_EQ(path[0], "rooms");
+
+ EXPECT_EQ(path[1], "Eros");
+
+ EXPECT_EQ(path[2], "messages");
+ EXPECT_EQ(path.last_segment(), "messages");
+}
+
+TEST(FieldPath, PopFirst) {
+ const FieldPath abc{"rooms", "Eros", "messages"};
+ const FieldPath bc{"Eros", "messages"};
+ const FieldPath c{"messages"};
+ const FieldPath empty;
+ const FieldPath abc_dup{"rooms", "Eros", "messages"};
+
+ EXPECT_NE(empty, c);
+ EXPECT_NE(c, bc);
+ EXPECT_NE(bc, abc);
+
+ EXPECT_EQ(bc, abc.PopFirst());
+ EXPECT_EQ(c, abc.PopFirst(2));
+ EXPECT_EQ(empty, abc.PopFirst(3));
+ EXPECT_EQ(abc_dup, abc);
+}
+
+TEST(FieldPath, PopLast) {
+ const FieldPath abc{"rooms", "Eros", "messages"};
+ const FieldPath ab{"rooms", "Eros"};
+ const FieldPath a{"rooms"};
+ const FieldPath empty;
+ const FieldPath abc_dup{"rooms", "Eros", "messages"};
+
+ EXPECT_EQ(ab, abc.PopLast());
+ EXPECT_EQ(a, abc.PopLast().PopLast());
+ EXPECT_EQ(empty, abc.PopLast().PopLast().PopLast());
+}
+
+TEST(FieldPath, Concatenation) {
+ const FieldPath path;
+ const FieldPath a{"rooms"};
+ const FieldPath ab{"rooms", "Eros"};
+ const FieldPath abc{"rooms", "Eros", "messages"};
+
+ EXPECT_EQ(a, path.Append("rooms"));
+ EXPECT_EQ(ab, path.Append("rooms").Append("Eros"));
+ EXPECT_EQ(abc, path.Append("rooms").Append("Eros").Append("messages"));
+ EXPECT_EQ(abc, path.Append(FieldPath{"rooms", "Eros", "messages"}));
+ EXPECT_EQ(abc, path.Append({"rooms", "Eros", "messages"}));
+
+ const FieldPath bcd{"Eros", "messages", "this_week"};
+ EXPECT_EQ(bcd, abc.PopFirst().Append("this_week"));
+}
+
+TEST(FieldPath, Comparison) {
+ const FieldPath abc{"a", "b", "c"};
+ const FieldPath abc2{"a", "b", "c"};
+ const FieldPath xyz{"x", "y", "z"};
+ EXPECT_EQ(abc, abc2);
+ EXPECT_NE(abc, xyz);
+
+ const FieldPath empty;
+ const FieldPath a{"a"};
+ const FieldPath b{"b"};
+ const FieldPath ab{"a", "b"};
+
+ EXPECT_TRUE(empty < a);
+ EXPECT_TRUE(a < b);
+ EXPECT_TRUE(a < ab);
+
+ EXPECT_TRUE(a > empty);
+ EXPECT_TRUE(b > a);
+ EXPECT_TRUE(ab > a);
+}
+
+TEST(FieldPath, IsPrefixOf) {
+ const FieldPath empty;
+ const FieldPath a{"a"};
+ const FieldPath ab{"a", "b"};
+ const FieldPath abc{"a", "b", "c"};
+ const FieldPath b{"b"};
+ const FieldPath ba{"b", "a"};
+
+ EXPECT_TRUE(empty.IsPrefixOf(empty));
+ EXPECT_TRUE(empty.IsPrefixOf(a));
+ EXPECT_TRUE(empty.IsPrefixOf(ab));
+ EXPECT_TRUE(empty.IsPrefixOf(abc));
+ EXPECT_TRUE(empty.IsPrefixOf(b));
+ EXPECT_TRUE(empty.IsPrefixOf(ba));
+
+ EXPECT_FALSE(a.IsPrefixOf(empty));
+ EXPECT_TRUE(a.IsPrefixOf(a));
+ EXPECT_TRUE(a.IsPrefixOf(ab));
+ EXPECT_TRUE(a.IsPrefixOf(abc));
+ EXPECT_FALSE(a.IsPrefixOf(b));
+ EXPECT_FALSE(a.IsPrefixOf(ba));
+
+ EXPECT_FALSE(ab.IsPrefixOf(empty));
+ EXPECT_FALSE(ab.IsPrefixOf(a));
+ EXPECT_TRUE(ab.IsPrefixOf(ab));
+ EXPECT_TRUE(ab.IsPrefixOf(abc));
+ EXPECT_FALSE(ab.IsPrefixOf(b));
+ EXPECT_FALSE(ab.IsPrefixOf(ba));
+
+ EXPECT_FALSE(abc.IsPrefixOf(empty));
+ EXPECT_FALSE(abc.IsPrefixOf(a));
+ EXPECT_FALSE(abc.IsPrefixOf(ab));
+ EXPECT_TRUE(abc.IsPrefixOf(abc));
+ EXPECT_FALSE(abc.IsPrefixOf(b));
+ EXPECT_FALSE(abc.IsPrefixOf(ba));
+}
+
+TEST(FieldPath, AccessFailures) {
+ const FieldPath path;
+ ASSERT_ANY_THROW(path.first_segment());
+ ASSERT_ANY_THROW(path.last_segment());
+ ASSERT_ANY_THROW(path[0]);
+ ASSERT_ANY_THROW(path[1]);
+ ASSERT_ANY_THROW(path.PopFirst());
+ ASSERT_ANY_THROW(path.PopFirst(2));
+ ASSERT_ANY_THROW(path.PopLast());
+}
+
+TEST(FieldPath, Parsing) {
+ const auto parse = [](const std::pair<std::string, size_t> expected) {
+ const auto path = FieldPath::FromServerFormat(expected.first);
+ return std::make_pair(path.CanonicalString(), path.size());
+ };
+ const auto make_expected = [](const std::string& str, const size_t size) {
+ return std::make_pair(str, size);
+ };
+
+ auto expected = make_expected("foo", 1);
+ EXPECT_EQ(expected, parse(expected));
+ expected = make_expected("foo.bar", 2);
+ EXPECT_EQ(expected, parse(expected));
+ expected = make_expected("foo.bar.baz", 3);
+ EXPECT_EQ(expected, parse(expected));
+ expected = make_expected(R"(`.foo\\`)", 1);
+ EXPECT_EQ(expected, parse(expected));
+ expected = make_expected(R"(`.foo\\`.`.foo`)", 2);
+ EXPECT_EQ(expected, parse(expected));
+ expected = make_expected(R"(foo.`\``.bar)", 3);
+ EXPECT_EQ(expected, parse(expected));
+
+ const auto path_with_dot = FieldPath::FromServerFormat(R"(foo\.bar)");
+ EXPECT_EQ(path_with_dot.CanonicalString(), "`foo.bar`");
+ EXPECT_EQ(path_with_dot.size(), 1u);
+}
+
+// This is a special case in C++: std::string may contain embedded nulls. To
+// fully mimic behavior of Objective-C code, parsing must terminate upon
+// encountering the first null terminator in the string.
+TEST(FieldPath, ParseEmbeddedNull) {
+ std::string str{"foo"};
+ str += '\0';
+ str += ".bar";
+
+ const auto path = FieldPath::FromServerFormat(str);
+ EXPECT_EQ(path.size(), 1u);
+ EXPECT_EQ(path.CanonicalString(), "foo");
+}
+
+TEST(FieldPath, ParseFailures) {
+ ASSERT_ANY_THROW(FieldPath::FromServerFormat(""));
+ ASSERT_ANY_THROW(FieldPath::FromServerFormat("."));
+ ASSERT_ANY_THROW(FieldPath::FromServerFormat(".."));
+ ASSERT_ANY_THROW(FieldPath::FromServerFormat("foo."));
+ ASSERT_ANY_THROW(FieldPath::FromServerFormat(".bar"));
+ ASSERT_ANY_THROW(FieldPath::FromServerFormat("foo..bar"));
+ ASSERT_ANY_THROW(FieldPath::FromServerFormat(R"(foo\)"));
+ ASSERT_ANY_THROW(FieldPath::FromServerFormat(R"(foo.\)"));
+ ASSERT_ANY_THROW(FieldPath::FromServerFormat("foo`"));
+ ASSERT_ANY_THROW(FieldPath::FromServerFormat("foo```"));
+ ASSERT_ANY_THROW(FieldPath::FromServerFormat("`foo"));
+}
+
+TEST(FieldPath, CanonicalStringOfSubstring) {
+ const auto path = FieldPath::FromServerFormat("foo.bar.baz");
+ EXPECT_EQ(path.CanonicalString(), "foo.bar.baz");
+ EXPECT_EQ(path.PopFirst().CanonicalString(), "bar.baz");
+ EXPECT_EQ(path.PopLast().CanonicalString(), "foo.bar");
+ EXPECT_EQ(path.PopFirst().PopLast().CanonicalString(), "bar");
+ EXPECT_EQ(path.PopFirst().PopLast().CanonicalString(), "bar");
+ EXPECT_EQ(path.PopLast().PopFirst().PopLast().CanonicalString(), "");
+}
+
+TEST(FieldPath, CanonicalStringEscaping) {
+ // Should be escaped
+ EXPECT_EQ(FieldPath::FromServerFormat("1").CanonicalString(), "`1`");
+ EXPECT_EQ(FieldPath::FromServerFormat("1ab").CanonicalString(), "`1ab`");
+ EXPECT_EQ(FieldPath::FromServerFormat("ab!").CanonicalString(), "`ab!`");
+ EXPECT_EQ(FieldPath::FromServerFormat("/ab").CanonicalString(), "`/ab`");
+ EXPECT_EQ(FieldPath::FromServerFormat("a#b").CanonicalString(), "`a#b`");
+
+ // Should not be escaped
+ EXPECT_EQ(FieldPath::FromServerFormat("_ab").CanonicalString(), "_ab");
+ EXPECT_EQ(FieldPath::FromServerFormat("a1").CanonicalString(), "a1");
+ EXPECT_EQ(FieldPath::FromServerFormat("a_").CanonicalString(), "a_");
+}
+
+TEST(FieldPath, EmptyPath) {
+ const auto& empty_path = FieldPath::EmptyPath();
+ EXPECT_EQ(empty_path, FieldPath{empty_path});
+ EXPECT_EQ(empty_path, FieldPath{});
+ EXPECT_EQ(&empty_path, &FieldPath::EmptyPath());
+}
+
+TEST(FieldPath, KeyFieldPath) {
+ const auto& key_field_path = FieldPath::KeyFieldPath();
+ EXPECT_EQ(key_field_path, FieldPath{key_field_path});
+ EXPECT_EQ(key_field_path,
+ FieldPath::FromServerFormat(key_field_path.CanonicalString()));
+ EXPECT_EQ(&key_field_path, &FieldPath::KeyFieldPath());
+ EXPECT_NE(key_field_path, FieldPath::FromServerFormat(
+ key_field_path.CanonicalString().substr(1)));
+}
+
+} // namespace model
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/test/firebase/firestore/model/field_transform_test.cc b/Firestore/core/test/firebase/firestore/model/field_transform_test.cc
new file mode 100644
index 0000000..b66aeef
--- /dev/null
+++ b/Firestore/core/test/firebase/firestore/model/field_transform_test.cc
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#include "Firestore/core/src/firebase/firestore/model/field_transform.h"
+#include "Firestore/core/src/firebase/firestore/model/transform_operations.h"
+#include "Firestore/core/test/firebase/firestore/testutil/testutil.h"
+
+#include "absl/memory/memory.h"
+#include "gtest/gtest.h"
+
+namespace firebase {
+namespace firestore {
+namespace model {
+
+TEST(FieldTransform, Getter) {
+ FieldTransform transform(testutil::Field("foo"),
+ absl::make_unique<ServerTimestampTransform>(
+ ServerTimestampTransform::Get()));
+
+ EXPECT_EQ(testutil::Field("foo"), transform.path());
+ EXPECT_EQ(ServerTimestampTransform::Get(), transform.transformation());
+}
+
+} // namespace model
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/test/firebase/firestore/model/field_value_test.cc b/Firestore/core/test/firebase/firestore/model/field_value_test.cc
new file mode 100644
index 0000000..c5645a4
--- /dev/null
+++ b/Firestore/core/test/firebase/firestore/model/field_value_test.cc
@@ -0,0 +1,497 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#include "Firestore/core/src/firebase/firestore/model/field_value.h"
+
+#include <climits>
+#include <vector>
+
+#include "gtest/gtest.h"
+
+namespace firebase {
+namespace firestore {
+namespace model {
+
+using Type = FieldValue::Type;
+
+namespace {
+
+const uint8_t* Bytes(const char* value) {
+ return reinterpret_cast<const uint8_t*>(value);
+}
+
+} // namespace
+
+TEST(FieldValue, NullType) {
+ const FieldValue value = FieldValue::NullValue();
+ EXPECT_EQ(Type::Null, value.type());
+ EXPECT_FALSE(value < value);
+}
+
+TEST(FieldValue, BooleanType) {
+ const FieldValue true_value = FieldValue::BooleanValue(true);
+ const FieldValue false_value = FieldValue::BooleanValue(false);
+ EXPECT_EQ(Type::Boolean, true_value.type());
+ EXPECT_FALSE(true_value < true_value);
+ EXPECT_FALSE(true_value < false_value);
+ EXPECT_FALSE(false_value < false_value);
+ EXPECT_TRUE(false_value < true_value);
+}
+
+TEST(FieldValue, NumberType) {
+ const FieldValue nan_value = FieldValue::NanValue();
+ const FieldValue integer_value = FieldValue::IntegerValue(10L);
+ const FieldValue double_value = FieldValue::DoubleValue(10.1);
+ EXPECT_EQ(Type::Double, nan_value.type());
+ EXPECT_EQ(Type::Integer, integer_value.type());
+ EXPECT_EQ(Type::Double, double_value.type());
+ EXPECT_TRUE(nan_value < integer_value);
+ EXPECT_TRUE(nan_value < double_value);
+ EXPECT_FALSE(nan_value < nan_value);
+ EXPECT_FALSE(integer_value < nan_value);
+ EXPECT_FALSE(integer_value < nan_value);
+ EXPECT_TRUE(integer_value < double_value); // 10 < 10.1
+ EXPECT_FALSE(double_value < integer_value);
+ EXPECT_FALSE(integer_value < integer_value);
+ EXPECT_FALSE(double_value < double_value);
+
+ // Number comparison craziness
+ // Integers
+ EXPECT_TRUE(FieldValue::IntegerValue(1L) < FieldValue::IntegerValue(2L));
+ EXPECT_FALSE(FieldValue::IntegerValue(1L) < FieldValue::IntegerValue(1L));
+ EXPECT_FALSE(FieldValue::IntegerValue(2L) < FieldValue::IntegerValue(1L));
+ // Doubles
+ EXPECT_TRUE(FieldValue::DoubleValue(1.0) < FieldValue::DoubleValue(2.0));
+ EXPECT_FALSE(FieldValue::DoubleValue(1.0) < FieldValue::DoubleValue(1.0));
+ EXPECT_FALSE(FieldValue::DoubleValue(2.0) < FieldValue::DoubleValue(1.0));
+ EXPECT_TRUE(FieldValue::NanValue() < FieldValue::DoubleValue(1.0));
+ EXPECT_FALSE(FieldValue::NanValue() < FieldValue::NanValue());
+ EXPECT_FALSE(FieldValue::DoubleValue(1.0) < FieldValue::NanValue());
+ // Mixed
+ EXPECT_TRUE(FieldValue::DoubleValue(-1e20) <
+ FieldValue::IntegerValue(LLONG_MIN));
+ EXPECT_FALSE(FieldValue::DoubleValue(1e20) <
+ FieldValue::IntegerValue(LLONG_MAX));
+ EXPECT_TRUE(FieldValue::DoubleValue(1.234) < FieldValue::IntegerValue(2L));
+ EXPECT_FALSE(FieldValue::DoubleValue(2.345) < FieldValue::IntegerValue(1L));
+ EXPECT_FALSE(FieldValue::DoubleValue(1.0) < FieldValue::IntegerValue(1L));
+ EXPECT_FALSE(FieldValue::DoubleValue(1.234) < FieldValue::IntegerValue(1L));
+ EXPECT_FALSE(FieldValue::IntegerValue(LLONG_MIN) <
+ FieldValue::DoubleValue(-1e20));
+ EXPECT_TRUE(FieldValue::IntegerValue(LLONG_MAX) <
+ FieldValue::DoubleValue(1e20));
+ EXPECT_FALSE(FieldValue::IntegerValue(1) < FieldValue::DoubleValue(1.0));
+ EXPECT_TRUE(FieldValue::IntegerValue(1) < FieldValue::DoubleValue(1.234));
+}
+
+TEST(FieldValue, TimestampType) {
+ const FieldValue o = FieldValue::TimestampValue(Timestamp());
+ const FieldValue a = FieldValue::TimestampValue({100, 0});
+ const FieldValue b = FieldValue::TimestampValue({200, 0});
+ EXPECT_EQ(Type::Timestamp, a.type());
+ EXPECT_TRUE(o < a);
+ EXPECT_TRUE(a < b);
+ EXPECT_FALSE(a < a);
+ const FieldValue c = FieldValue::ServerTimestampValue({100, 0});
+ const FieldValue d = FieldValue::ServerTimestampValue({200, 0}, {300, 0});
+ EXPECT_EQ(Type::ServerTimestamp, c.type());
+ EXPECT_EQ(Type::ServerTimestamp, d.type());
+ EXPECT_TRUE(c < d);
+ EXPECT_FALSE(c < c);
+ // Mixed
+ EXPECT_TRUE(o < c);
+ EXPECT_TRUE(a < c);
+ EXPECT_TRUE(b < c);
+ EXPECT_TRUE(b < d);
+ EXPECT_FALSE(c < o);
+ EXPECT_FALSE(c < a);
+ EXPECT_FALSE(c < b);
+ EXPECT_FALSE(d < b);
+}
+
+TEST(FieldValue, StringType) {
+ const FieldValue a = FieldValue::StringValue("abc");
+ std::string xyz("xyz");
+ const FieldValue b = FieldValue::StringValue(xyz);
+ const FieldValue c = FieldValue::StringValue(std::move(xyz));
+ EXPECT_EQ(Type::String, a.type());
+ EXPECT_EQ(Type::String, b.type());
+ EXPECT_EQ(Type::String, c.type());
+ EXPECT_TRUE(a < b);
+ EXPECT_FALSE(a < a);
+}
+
+TEST(FieldValue, BlobType) {
+ const FieldValue a = FieldValue::BlobValue(Bytes("abc"), 4);
+ const FieldValue b = FieldValue::BlobValue(Bytes("def"), 4);
+ EXPECT_EQ(Type::Blob, a.type());
+ EXPECT_EQ(Type::Blob, b.type());
+ EXPECT_TRUE(a < b);
+ EXPECT_FALSE(a < a);
+}
+
+TEST(FieldValue, ReferenceType) {
+ const DatabaseId id("project", "database");
+ const FieldValue a =
+ FieldValue::ReferenceValue(DocumentKey::FromPathString("root/abc"), &id);
+ DocumentKey key = DocumentKey::FromPathString("root/def");
+ const FieldValue b = FieldValue::ReferenceValue(key, &id);
+ const FieldValue c = FieldValue::ReferenceValue(std::move(key), &id);
+ EXPECT_EQ(Type::Reference, a.type());
+ EXPECT_EQ(Type::Reference, b.type());
+ EXPECT_EQ(Type::Reference, c.type());
+ EXPECT_TRUE(a < b);
+ EXPECT_FALSE(a < a);
+}
+
+TEST(FieldValue, GeoPointType) {
+ const FieldValue a = FieldValue::GeoPointValue({1, 2});
+ const FieldValue b = FieldValue::GeoPointValue({3, 4});
+ EXPECT_EQ(Type::GeoPoint, a.type());
+ EXPECT_EQ(Type::GeoPoint, b.type());
+ EXPECT_TRUE(a < b);
+ EXPECT_FALSE(a < a);
+}
+
+TEST(FieldValue, ArrayType) {
+ const FieldValue empty = FieldValue::ArrayValue(std::vector<FieldValue>{});
+ std::vector<FieldValue> array{FieldValue::NullValue(),
+ FieldValue::BooleanValue(true),
+ FieldValue::BooleanValue(false)};
+ // copy the array
+ const FieldValue small = FieldValue::ArrayValue(array);
+ std::vector<FieldValue> another_array{FieldValue::BooleanValue(true),
+ FieldValue::BooleanValue(false)};
+ // move the array
+ const FieldValue large = FieldValue::ArrayValue(std::move(another_array));
+ EXPECT_EQ(Type::Array, empty.type());
+ EXPECT_EQ(Type::Array, small.type());
+ EXPECT_EQ(Type::Array, large.type());
+ EXPECT_TRUE(empty < small);
+ EXPECT_FALSE(small < empty);
+ EXPECT_FALSE(small < small);
+ EXPECT_TRUE(small < large);
+ EXPECT_FALSE(large < small);
+}
+
+TEST(FieldValue, ObjectType) {
+ const FieldValue empty = FieldValue::ObjectValueFromMap({});
+ ObjectValue::Map object{{"null", FieldValue::NullValue()},
+ {"true", FieldValue::TrueValue()},
+ {"false", FieldValue::FalseValue()}};
+ // copy the map
+ const FieldValue small = FieldValue::ObjectValueFromMap(object);
+ ObjectValue::Map another_object{{"null", FieldValue::NullValue()},
+ {"true", FieldValue::FalseValue()}};
+ // move the array
+ const FieldValue large =
+ FieldValue::ObjectValueFromMap(std::move(another_object));
+ EXPECT_EQ(Type::Object, empty.type());
+ EXPECT_EQ(Type::Object, small.type());
+ EXPECT_EQ(Type::Object, large.type());
+ EXPECT_TRUE(empty < small);
+ EXPECT_FALSE(small < empty);
+ EXPECT_FALSE(small < small);
+ EXPECT_TRUE(small < large);
+ EXPECT_FALSE(large < small);
+}
+
+TEST(FieldValue, Copy) {
+ FieldValue clone = FieldValue::TrueValue();
+ const FieldValue null_value = FieldValue::NullValue();
+ clone = null_value;
+ EXPECT_EQ(FieldValue::NullValue(), clone);
+ EXPECT_EQ(FieldValue::NullValue(), null_value);
+ clone = clone;
+ EXPECT_EQ(FieldValue::NullValue(), clone);
+
+ const FieldValue true_value = FieldValue::TrueValue();
+ clone = true_value;
+ EXPECT_EQ(FieldValue::TrueValue(), clone);
+ EXPECT_EQ(FieldValue::TrueValue(), true_value);
+ clone = clone;
+ EXPECT_EQ(FieldValue::TrueValue(), clone);
+ clone = null_value;
+ EXPECT_EQ(FieldValue::NullValue(), clone);
+
+ const FieldValue nan_value = FieldValue::NanValue();
+ clone = nan_value;
+ EXPECT_EQ(FieldValue::NanValue(), clone);
+ EXPECT_EQ(FieldValue::NanValue(), nan_value);
+ clone = clone;
+ EXPECT_EQ(FieldValue::NanValue(), clone);
+ clone = null_value;
+ EXPECT_EQ(FieldValue::NullValue(), clone);
+
+ const FieldValue integer_value = FieldValue::IntegerValue(1L);
+ clone = integer_value;
+ EXPECT_EQ(FieldValue::IntegerValue(1L), clone);
+ EXPECT_EQ(FieldValue::IntegerValue(1L), integer_value);
+ clone = clone;
+ EXPECT_EQ(FieldValue::IntegerValue(1L), clone);
+ clone = null_value;
+ EXPECT_EQ(FieldValue::NullValue(), clone);
+
+ const FieldValue double_value = FieldValue::DoubleValue(1.0);
+ clone = double_value;
+ EXPECT_EQ(FieldValue::DoubleValue(1.0), clone);
+ EXPECT_EQ(FieldValue::DoubleValue(1.0), double_value);
+ clone = clone;
+ EXPECT_EQ(FieldValue::DoubleValue(1.0), clone);
+ clone = null_value;
+ EXPECT_EQ(FieldValue::NullValue(), clone);
+
+ const FieldValue timestamp_value = FieldValue::TimestampValue({100, 200});
+ clone = timestamp_value;
+ EXPECT_EQ(FieldValue::TimestampValue({100, 200}), clone);
+ EXPECT_EQ(FieldValue::TimestampValue({100, 200}), timestamp_value);
+ clone = clone;
+ EXPECT_EQ(FieldValue::TimestampValue({100, 200}), clone);
+ clone = null_value;
+ EXPECT_EQ(FieldValue::NullValue(), clone);
+
+ const FieldValue server_timestamp_value =
+ FieldValue::ServerTimestampValue({1, 2}, {3, 4});
+ clone = server_timestamp_value;
+ EXPECT_EQ(FieldValue::ServerTimestampValue({1, 2}, {3, 4}), clone);
+ EXPECT_EQ(FieldValue::ServerTimestampValue({1, 2}, {3, 4}),
+ server_timestamp_value);
+ clone = clone;
+ EXPECT_EQ(FieldValue::ServerTimestampValue({1, 2}, {3, 4}), clone);
+ clone = null_value;
+ EXPECT_EQ(FieldValue::NullValue(), clone);
+
+ const FieldValue string_value = FieldValue::StringValue("abc");
+ clone = string_value;
+ EXPECT_EQ(FieldValue::StringValue("abc"), clone);
+ EXPECT_EQ(FieldValue::StringValue("abc"), string_value);
+ clone = clone;
+ EXPECT_EQ(FieldValue::StringValue("abc"), clone);
+ clone = null_value;
+ EXPECT_EQ(FieldValue::NullValue(), clone);
+
+ const FieldValue blob_value = FieldValue::BlobValue(Bytes("abc"), 4);
+ clone = blob_value;
+ EXPECT_EQ(FieldValue::BlobValue(Bytes("abc"), 4), clone);
+ EXPECT_EQ(FieldValue::BlobValue(Bytes("abc"), 4), blob_value);
+ clone = clone;
+ EXPECT_EQ(FieldValue::BlobValue(Bytes("abc"), 4), clone);
+ clone = null_value;
+ EXPECT_EQ(FieldValue::NullValue(), clone);
+
+ const DatabaseId database_id("project", "database");
+ const FieldValue reference_value = FieldValue::ReferenceValue(
+ DocumentKey::FromPathString("root/abc"), &database_id);
+ clone = reference_value;
+ EXPECT_EQ(FieldValue::ReferenceValue(DocumentKey::FromPathString("root/abc"),
+ &database_id),
+ clone);
+ EXPECT_EQ(FieldValue::ReferenceValue(DocumentKey::FromPathString("root/abc"),
+ &database_id),
+ reference_value);
+ clone = clone;
+ EXPECT_EQ(FieldValue::ReferenceValue(DocumentKey::FromPathString("root/abc"),
+ &database_id),
+ clone);
+ clone = null_value;
+ EXPECT_EQ(FieldValue::NullValue(), clone);
+
+ const FieldValue geo_point_value = FieldValue::GeoPointValue({1, 2});
+ clone = geo_point_value;
+ EXPECT_EQ(FieldValue::GeoPointValue({1, 2}), clone);
+ EXPECT_EQ(FieldValue::GeoPointValue({1, 2}), geo_point_value);
+ clone = clone;
+ EXPECT_EQ(FieldValue::GeoPointValue({1, 2}), clone);
+ clone = null_value;
+ EXPECT_EQ(FieldValue::NullValue(), clone);
+
+ const FieldValue array_value = FieldValue::ArrayValue(std::vector<FieldValue>{
+ FieldValue::TrueValue(), FieldValue::FalseValue()});
+ clone = array_value;
+ EXPECT_EQ(FieldValue::ArrayValue(std::vector<FieldValue>{
+ FieldValue::TrueValue(), FieldValue::FalseValue()}),
+ clone);
+ EXPECT_EQ(FieldValue::ArrayValue(std::vector<FieldValue>{
+ FieldValue::TrueValue(), FieldValue::FalseValue()}),
+ array_value);
+ clone = clone;
+ EXPECT_EQ(FieldValue::ArrayValue(std::vector<FieldValue>{
+ FieldValue::TrueValue(), FieldValue::FalseValue()}),
+ clone);
+ clone = null_value;
+ EXPECT_EQ(FieldValue::NullValue(), clone);
+
+ const FieldValue object_value = FieldValue::ObjectValueFromMap(
+ ObjectValue::Map{{"true", FieldValue::TrueValue()},
+ {"false", FieldValue::FalseValue()}});
+ clone = object_value;
+ EXPECT_EQ(FieldValue::ObjectValueFromMap(
+ ObjectValue::Map{{"true", FieldValue::TrueValue()},
+ {"false", FieldValue::FalseValue()}}),
+ clone);
+ EXPECT_EQ(FieldValue::ObjectValueFromMap(
+ ObjectValue::Map{{"true", FieldValue::TrueValue()},
+ {"false", FieldValue::FalseValue()}}),
+ object_value);
+ clone = clone;
+ EXPECT_EQ(FieldValue::ObjectValueFromMap(
+ ObjectValue::Map{{"true", FieldValue::TrueValue()},
+ {"false", FieldValue::FalseValue()}}),
+ clone);
+ clone = null_value;
+ EXPECT_EQ(FieldValue::NullValue(), clone);
+}
+
+TEST(FieldValue, Move) {
+ FieldValue clone = FieldValue::TrueValue();
+
+ FieldValue null_value = FieldValue::NullValue();
+ clone = std::move(null_value);
+ EXPECT_EQ(FieldValue::NullValue(), clone);
+
+ FieldValue true_value = FieldValue::TrueValue();
+ clone = std::move(true_value);
+ EXPECT_EQ(FieldValue::TrueValue(), clone);
+ clone = FieldValue::NullValue();
+ EXPECT_EQ(FieldValue::NullValue(), clone);
+
+ FieldValue nan_value = FieldValue::NanValue();
+ clone = std::move(nan_value);
+ EXPECT_EQ(FieldValue::NanValue(), clone);
+ clone = FieldValue::NullValue();
+ EXPECT_EQ(FieldValue::NullValue(), clone);
+
+ FieldValue integer_value = FieldValue::IntegerValue(1L);
+ clone = std::move(integer_value);
+ EXPECT_EQ(FieldValue::IntegerValue(1L), clone);
+ clone = FieldValue::NullValue();
+ EXPECT_EQ(FieldValue::NullValue(), clone);
+
+ FieldValue double_value = FieldValue::DoubleValue(1.0);
+ clone = std::move(double_value);
+ EXPECT_EQ(FieldValue::DoubleValue(1.0), clone);
+ clone = FieldValue::NullValue();
+ EXPECT_EQ(FieldValue::NullValue(), clone);
+
+ FieldValue timestamp_value = FieldValue::TimestampValue({100, 200});
+ clone = std::move(timestamp_value);
+ EXPECT_EQ(FieldValue::TimestampValue({100, 200}), clone);
+ clone = FieldValue::NullValue();
+ EXPECT_EQ(FieldValue::NullValue(), clone);
+
+ FieldValue string_value = FieldValue::StringValue("abc");
+ clone = std::move(string_value);
+ EXPECT_EQ(FieldValue::StringValue("abc"), clone);
+ clone = FieldValue::NullValue();
+ EXPECT_EQ(FieldValue::NullValue(), clone);
+
+ FieldValue blob_value = FieldValue::BlobValue(Bytes("abc"), 4);
+ clone = std::move(blob_value);
+ EXPECT_EQ(FieldValue::BlobValue(Bytes("abc"), 4), clone);
+ clone = FieldValue::NullValue();
+ EXPECT_EQ(FieldValue::NullValue(), clone);
+
+ const DatabaseId database_id("project", "database");
+ FieldValue reference_value = FieldValue::ReferenceValue(
+ DocumentKey::FromPathString("root/abc"), &database_id);
+ clone = std::move(reference_value);
+ EXPECT_EQ(FieldValue::ReferenceValue(DocumentKey::FromPathString("root/abc"),
+ &database_id),
+ clone);
+ clone = null_value; // NOLINT: use after move intended
+ EXPECT_EQ(FieldValue::NullValue(), clone);
+
+ FieldValue geo_point_value = FieldValue::GeoPointValue({1, 2});
+ clone = std::move(geo_point_value);
+ EXPECT_EQ(FieldValue::GeoPointValue({1, 2}), clone);
+ clone = null_value;
+ EXPECT_EQ(FieldValue::NullValue(), clone);
+
+ FieldValue array_value = FieldValue::ArrayValue(std::vector<FieldValue>{
+ FieldValue::TrueValue(), FieldValue::FalseValue()});
+ clone = std::move(array_value);
+ EXPECT_EQ(FieldValue::ArrayValue(std::vector<FieldValue>{
+ FieldValue::TrueValue(), FieldValue::FalseValue()}),
+ clone);
+ clone = FieldValue::NullValue();
+ EXPECT_EQ(FieldValue::NullValue(), clone);
+
+ FieldValue object_value = FieldValue::ObjectValueFromMap(ObjectValue::Map{
+ {"true", FieldValue::TrueValue()}, {"false", FieldValue::FalseValue()}});
+ clone = std::move(object_value);
+ EXPECT_EQ(FieldValue::ObjectValueFromMap(
+ ObjectValue::Map{{"true", FieldValue::TrueValue()},
+ {"false", FieldValue::FalseValue()}}),
+ clone);
+ clone = FieldValue::NullValue();
+ EXPECT_EQ(FieldValue::NullValue(), clone);
+}
+
+TEST(FieldValue, CompareMixedType) {
+ const FieldValue null_value = FieldValue::NullValue();
+ const FieldValue true_value = FieldValue::TrueValue();
+ const FieldValue number_value = FieldValue::NanValue();
+ const FieldValue timestamp_value = FieldValue::TimestampValue({100, 200});
+ const FieldValue string_value = FieldValue::StringValue("abc");
+ const FieldValue blob_value = FieldValue::BlobValue(Bytes("abc"), 4);
+ const DatabaseId database_id("project", "database");
+ const FieldValue reference_value = FieldValue::ReferenceValue(
+ DocumentKey::FromPathString("root/abc"), &database_id);
+ const FieldValue geo_point_value = FieldValue::GeoPointValue({1, 2});
+ const FieldValue array_value =
+ FieldValue::ArrayValue(std::vector<FieldValue>());
+ const FieldValue object_value = FieldValue::ObjectValueFromMap({});
+ EXPECT_TRUE(null_value < true_value);
+ EXPECT_TRUE(true_value < number_value);
+ EXPECT_TRUE(number_value < timestamp_value);
+ EXPECT_TRUE(timestamp_value < string_value);
+ EXPECT_TRUE(string_value < blob_value);
+ EXPECT_TRUE(blob_value < reference_value);
+ EXPECT_TRUE(reference_value < geo_point_value);
+ EXPECT_TRUE(geo_point_value < array_value);
+ EXPECT_TRUE(array_value < object_value);
+}
+
+TEST(FieldValue, CompareWithOperator) {
+ const FieldValue small = FieldValue::NullValue();
+ const FieldValue large = FieldValue::TrueValue();
+
+ EXPECT_TRUE(small < large);
+ EXPECT_FALSE(small < small);
+ EXPECT_FALSE(large < small);
+
+ EXPECT_TRUE(large > small);
+ EXPECT_FALSE(small > small);
+ EXPECT_FALSE(small > large);
+
+ EXPECT_TRUE(large >= small);
+ EXPECT_TRUE(small >= small);
+ EXPECT_FALSE(small >= large);
+
+ EXPECT_TRUE(small <= large);
+ EXPECT_TRUE(small <= small);
+ EXPECT_FALSE(large <= small);
+
+ EXPECT_TRUE(small != large);
+ EXPECT_FALSE(small != small);
+
+ EXPECT_TRUE(small == small);
+ EXPECT_FALSE(small == large);
+}
+
+} // namespace model
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/test/firebase/firestore/model/maybe_document_test.cc b/Firestore/core/test/firebase/firestore/model/maybe_document_test.cc
new file mode 100644
index 0000000..70ae319
--- /dev/null
+++ b/Firestore/core/test/firebase/firestore/model/maybe_document_test.cc
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#include "Firestore/core/src/firebase/firestore/model/maybe_document.h"
+
+#include "absl/strings/string_view.h"
+#include "gtest/gtest.h"
+
+namespace firebase {
+namespace firestore {
+namespace model {
+
+namespace {
+
+inline MaybeDocument MakeMaybeDocument(const absl::string_view path,
+ const Timestamp& timestamp) {
+ return MaybeDocument(DocumentKey::FromPathString(path.data()),
+ SnapshotVersion(timestamp));
+}
+
+inline bool operator<(const MaybeDocument& lhs, const MaybeDocument& rhs) {
+ static const DocumentKeyComparator less;
+ return less(lhs, rhs);
+}
+
+} // anonymous namespace
+
+TEST(MaybeDocument, Getter) {
+ const MaybeDocument& doc =
+ MakeMaybeDocument("i/am/a/path", Timestamp(123, 456));
+ EXPECT_EQ(MaybeDocument::Type::Unknown, doc.type());
+ EXPECT_EQ(DocumentKey::FromPathString("i/am/a/path"), doc.key());
+ EXPECT_EQ(SnapshotVersion(Timestamp(123, 456)), doc.version());
+}
+
+TEST(MaybeDocument, Comparison) {
+ EXPECT_TRUE(MakeMaybeDocument("root/123", Timestamp(456, 123)) <
+ MakeMaybeDocument("root/456", Timestamp(123, 456)));
+ // MaybeDocument comparison is purely key-based.
+ EXPECT_FALSE(MakeMaybeDocument("root/123", Timestamp(111, 111)) <
+ MakeMaybeDocument("root/123", Timestamp(222, 222)));
+
+ EXPECT_EQ(MakeMaybeDocument("root/123", Timestamp(456, 123)),
+ MakeMaybeDocument("root/123", Timestamp(456, 123)));
+ EXPECT_NE(MakeMaybeDocument("root/123", Timestamp(456, 123)),
+ MakeMaybeDocument("root/456", Timestamp(456, 123)));
+ EXPECT_NE(MakeMaybeDocument("root/123", Timestamp(456, 123)),
+ MakeMaybeDocument("root/123", Timestamp(123, 456)));
+}
+
+} // namespace model
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/test/firebase/firestore/model/no_document_test.cc b/Firestore/core/test/firebase/firestore/model/no_document_test.cc
new file mode 100644
index 0000000..825820f
--- /dev/null
+++ b/Firestore/core/test/firebase/firestore/model/no_document_test.cc
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#include "Firestore/core/src/firebase/firestore/model/no_document.h"
+
+#include "absl/strings/string_view.h"
+#include "gtest/gtest.h"
+
+namespace firebase {
+namespace firestore {
+namespace model {
+
+namespace {
+
+inline NoDocument MakeNoDocument(const absl::string_view path,
+ const Timestamp& timestamp) {
+ return NoDocument(DocumentKey::FromPathString(path.data()),
+ SnapshotVersion(timestamp));
+}
+
+} // anonymous namespace
+
+TEST(NoDocument, Getter) {
+ const NoDocument& doc = MakeNoDocument("i/am/a/path", Timestamp(123, 456));
+ EXPECT_EQ(MaybeDocument::Type::NoDocument, doc.type());
+ EXPECT_EQ(DocumentKey::FromPathString("i/am/a/path"), doc.key());
+ EXPECT_EQ(SnapshotVersion(Timestamp(123, 456)), doc.version());
+
+ // NoDocument and MaybeDocument will not equal.
+ EXPECT_NE(NoDocument(DocumentKey::FromPathString("same/path"),
+ SnapshotVersion(Timestamp())),
+ MaybeDocument(DocumentKey::FromPathString("same/path"),
+ SnapshotVersion(Timestamp())));
+}
+
+} // namespace model
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/test/firebase/firestore/model/precondition_test.cc b/Firestore/core/test/firebase/firestore/model/precondition_test.cc
new file mode 100644
index 0000000..3ddb2ba
--- /dev/null
+++ b/Firestore/core/test/firebase/firestore/model/precondition_test.cc
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#include "Firestore/core/src/firebase/firestore/model/precondition.h"
+
+#include "Firestore/core/src/firebase/firestore/model/document.h"
+#include "Firestore/core/src/firebase/firestore/model/no_document.h"
+#include "Firestore/core/src/firebase/firestore/model/snapshot_version.h"
+#include "Firestore/core/test/firebase/firestore/testutil/testutil.h"
+
+#include "gtest/gtest.h"
+
+namespace firebase {
+namespace firestore {
+namespace model {
+
+TEST(Precondition, None) {
+ const Precondition none = Precondition::None();
+ EXPECT_EQ(Precondition::Type::None, none.type());
+ EXPECT_TRUE(none.IsNone());
+ EXPECT_EQ(SnapshotVersion::None(), none.update_time());
+
+ const NoDocument deleted_doc = testutil::DeletedDoc("foo/doc", 1234567);
+ const Document doc = testutil::Doc("bar/doc", 7654321);
+ EXPECT_TRUE(none.IsValidFor(deleted_doc));
+ EXPECT_TRUE(none.IsValidFor(doc));
+}
+
+TEST(Precondition, Exists) {
+ const Precondition exists = Precondition::Exists(true);
+ const Precondition no_exists = Precondition::Exists(false);
+ EXPECT_EQ(Precondition::Type::Exists, exists.type());
+ EXPECT_EQ(Precondition::Type::Exists, no_exists.type());
+ EXPECT_FALSE(exists.IsNone());
+ EXPECT_FALSE(no_exists.IsNone());
+ EXPECT_EQ(SnapshotVersion::None(), exists.update_time());
+ EXPECT_EQ(SnapshotVersion::None(), no_exists.update_time());
+
+ const NoDocument deleted_doc = testutil::DeletedDoc("foo/doc", 1234567);
+ const Document doc = testutil::Doc("bar/doc", 7654321);
+ EXPECT_FALSE(exists.IsValidFor(deleted_doc));
+ EXPECT_TRUE(exists.IsValidFor(doc));
+ EXPECT_TRUE(no_exists.IsValidFor(deleted_doc));
+ EXPECT_FALSE(no_exists.IsValidFor(doc));
+}
+
+TEST(Precondition, UpdateTime) {
+ const Precondition update_time =
+ Precondition::UpdateTime(testutil::Version(1234567));
+ EXPECT_EQ(Precondition::Type::UpdateTime, update_time.type());
+ EXPECT_FALSE(update_time.IsNone());
+ EXPECT_EQ(testutil::Version(1234567), update_time.update_time());
+
+ const NoDocument deleted_doc = testutil::DeletedDoc("foo/doc", 1234567);
+ const Document not_match = testutil::Doc("bar/doc", 7654321);
+ const Document match = testutil::Doc("baz/doc", 1234567);
+ EXPECT_FALSE(update_time.IsValidFor(deleted_doc));
+ EXPECT_FALSE(update_time.IsValidFor(not_match));
+ EXPECT_TRUE(update_time.IsValidFor(match));
+}
+
+} // namespace model
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/test/firebase/firestore/model/resource_path_test.cc b/Firestore/core/test/firebase/firestore/model/resource_path_test.cc
new file mode 100644
index 0000000..8545884
--- /dev/null
+++ b/Firestore/core/test/firebase/firestore/model/resource_path_test.cc
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#include "Firestore/core/src/firebase/firestore/model/resource_path.h"
+
+#include <initializer_list>
+#include <string>
+#include <vector>
+
+#include "gtest/gtest.h"
+
+namespace firebase {
+namespace firestore {
+namespace model {
+
+TEST(ResourcePath, Constructor) {
+ const ResourcePath empty_path;
+ EXPECT_TRUE(empty_path.empty());
+ EXPECT_EQ(0u, empty_path.size());
+ EXPECT_TRUE(empty_path.begin() == empty_path.end());
+
+ const ResourcePath path_from_list{{"rooms", "Eros", "messages"}};
+ EXPECT_FALSE(path_from_list.empty());
+ EXPECT_EQ(3u, path_from_list.size());
+ EXPECT_TRUE(path_from_list.begin() + 3 == path_from_list.end());
+
+ std::vector<std::string> segments{"rooms", "Eros", "messages"};
+ const ResourcePath path_from_segments{segments.begin(), segments.end()};
+ EXPECT_FALSE(path_from_segments.empty());
+ EXPECT_EQ(3u, path_from_segments.size());
+ EXPECT_TRUE(path_from_segments.begin() + 3 == path_from_segments.end());
+
+ ResourcePath copied = path_from_list;
+ EXPECT_EQ(path_from_list, copied);
+ const ResourcePath moved = std::move(copied);
+ EXPECT_EQ(path_from_list, moved);
+ EXPECT_NE(copied, moved); // NOLINT: use after move intended
+ EXPECT_EQ(empty_path, copied);
+}
+
+TEST(ResourcePath, Comparison) {
+ const ResourcePath abc{"a", "b", "c"};
+ const ResourcePath abc2{"a", "b", "c"};
+ const ResourcePath xyz{"x", "y", "z"};
+ EXPECT_EQ(abc, abc2);
+ EXPECT_NE(abc, xyz);
+
+ const ResourcePath empty;
+ const ResourcePath a{"a"};
+ const ResourcePath b{"b"};
+ const ResourcePath ab{"a", "b"};
+
+ EXPECT_TRUE(empty < a);
+ EXPECT_TRUE(a < b);
+ EXPECT_TRUE(a < ab);
+
+ EXPECT_TRUE(a > empty);
+ EXPECT_TRUE(b > a);
+ EXPECT_TRUE(ab > a);
+}
+
+TEST(ResourcePath, Parsing) {
+ const auto parse = [](const std::pair<std::string, size_t> expected) {
+ const auto path = ResourcePath::FromString(expected.first);
+ return std::make_pair(path.CanonicalString(), path.size());
+ };
+ const auto make_expected = [](const std::string& str, const size_t size) {
+ return std::make_pair(str, size);
+ };
+
+ auto expected = make_expected("", 0);
+ EXPECT_EQ(expected, parse(expected));
+ expected = make_expected("foo", 1);
+ EXPECT_EQ(expected, parse(expected));
+ expected = make_expected("foo/bar", 2);
+ EXPECT_EQ(expected, parse(expected));
+ expected = make_expected("foo/bar/baz", 3);
+ EXPECT_EQ(expected, parse(expected));
+ expected = make_expected(R"(foo/__!?#@..`..\`/baz)", 3);
+ EXPECT_EQ(expected, parse(expected));
+
+ EXPECT_EQ(ResourcePath::FromString("/foo/").CanonicalString(), "foo");
+}
+
+TEST(ResourcePath, ParseFailures) {
+ ASSERT_ANY_THROW(ResourcePath::FromString("//"));
+ ASSERT_ANY_THROW(ResourcePath::FromString("foo//bar"));
+}
+
+} // namespace model
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/test/firebase/firestore/model/snapshot_version_test.cc b/Firestore/core/test/firebase/firestore/model/snapshot_version_test.cc
new file mode 100644
index 0000000..e359f84
--- /dev/null
+++ b/Firestore/core/test/firebase/firestore/model/snapshot_version_test.cc
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#include "Firestore/core/src/firebase/firestore/model/snapshot_version.h"
+
+#include "gtest/gtest.h"
+
+namespace firebase {
+namespace firestore {
+namespace model {
+
+TEST(SnapshotVersion, Getter) {
+ SnapshotVersion version(Timestamp(123, 456));
+ EXPECT_EQ(Timestamp(123, 456), version.timestamp());
+
+ const SnapshotVersion& no_version = SnapshotVersion::None();
+ EXPECT_EQ(Timestamp(), no_version.timestamp());
+}
+
+TEST(SnapshotVersion, Comparison) {
+ EXPECT_LT(SnapshotVersion::None(), SnapshotVersion(Timestamp(123, 456)));
+
+ EXPECT_LT(SnapshotVersion(Timestamp(123, 456)),
+ SnapshotVersion(Timestamp(456, 123)));
+ EXPECT_GT(SnapshotVersion(Timestamp(456, 123)),
+ SnapshotVersion(Timestamp(123, 456)));
+ EXPECT_LE(SnapshotVersion(Timestamp(123, 456)),
+ SnapshotVersion(Timestamp(456, 123)));
+ EXPECT_LE(SnapshotVersion(Timestamp(123, 456)),
+ SnapshotVersion(Timestamp(123, 456)));
+ EXPECT_GE(SnapshotVersion(Timestamp(456, 123)),
+ SnapshotVersion(Timestamp(123, 456)));
+ EXPECT_GE(SnapshotVersion(Timestamp(123, 456)),
+ SnapshotVersion(Timestamp(123, 456)));
+ EXPECT_EQ(SnapshotVersion(Timestamp(123, 456)),
+ SnapshotVersion(Timestamp(123, 456)));
+ EXPECT_NE(SnapshotVersion(Timestamp(123, 456)),
+ SnapshotVersion(Timestamp(456, 123)));
+}
+
+} // namespace model
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/test/firebase/firestore/model/transform_operations_test.cc b/Firestore/core/test/firebase/firestore/model/transform_operations_test.cc
new file mode 100644
index 0000000..ec0882a
--- /dev/null
+++ b/Firestore/core/test/firebase/firestore/model/transform_operations_test.cc
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#include "Firestore/core/src/firebase/firestore/model/transform_operations.h"
+
+#include "gtest/gtest.h"
+
+namespace firebase {
+namespace firestore {
+namespace model {
+
+class DummyOperation : public TransformOperation {
+ public:
+ DummyOperation() {
+ }
+
+ Type type() const override {
+ return Type::Test;
+ }
+
+ bool operator==(const TransformOperation& other) const override {
+ return this == &other;
+ }
+};
+
+TEST(TransformOperations, ServerTimestamp) {
+ ServerTimestampTransform transform = ServerTimestampTransform::Get();
+ EXPECT_EQ(TransformOperation::Type::ServerTimestamp, transform.type());
+
+ ServerTimestampTransform another = ServerTimestampTransform::Get();
+ DummyOperation dummy;
+ EXPECT_EQ(transform, another);
+ EXPECT_NE(transform, dummy);
+}
+
+// TODO(mikelehen): Add ArrayTransform test once it no longer depends on
+// FSTFieldValue and can be exposed to C++ code.
+
+} // namespace model
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/test/firebase/firestore/remote/CMakeLists.txt b/Firestore/core/test/firebase/firestore/remote/CMakeLists.txt
new file mode 100644
index 0000000..d42b107
--- /dev/null
+++ b/Firestore/core/test/firebase/firestore/remote/CMakeLists.txt
@@ -0,0 +1,22 @@
+# Copyright 2018 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.
+
+cc_test(
+ firebase_firestore_remote_test
+ SOURCES
+ datastore_test.cc
+ serializer_test.cc
+ DEPENDS
+ firebase_firestore_remote
+)
diff --git a/Firestore/core/test/firebase/firestore/remote/datastore_test.cc b/Firestore/core/test/firebase/firestore/remote/datastore_test.cc
new file mode 100644
index 0000000..53e95a9
--- /dev/null
+++ b/Firestore/core/test/firebase/firestore/remote/datastore_test.cc
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#include "Firestore/core/src/firebase/firestore/remote/datastore.h"
+
+#include "grpc/grpc.h"
+
+#include "gtest/gtest.h"
+
+TEST(Datastore, CanLinkToGrpc) {
+ // This test doesn't actually do anything interesting as far as actually
+ // using gRPC is concerned but that it can run at all is proof that all the
+ // libraries required for gRPC to work are actually linked correctly into the
+ // test.
+ grpc_init();
+ grpc_shutdown();
+}
diff --git a/Firestore/core/test/firebase/firestore/remote/serializer_test.cc b/Firestore/core/test/firebase/firestore/remote/serializer_test.cc
new file mode 100644
index 0000000..addc830
--- /dev/null
+++ b/Firestore/core/test/firebase/firestore/remote/serializer_test.cc
@@ -0,0 +1,282 @@
+/*
+ * Copyright 2018 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.
+ */
+
+/* NB: proto bytes were created via:
+ echo 'TEXT_FORMAT_PROTO' \
+ | ./build/external/protobuf/src/protobuf-build/src/protoc \
+ -I./Firestore/Protos/protos \
+ -I./build/external/protobuf/src/protobuf/src \
+ --encode=google.firestore.v1beta1.Value \
+ google/firestore/v1beta1/document.proto \
+ | hexdump -C
+ * where TEXT_FORMAT_PROTO is the text format of the protobuf. (go/textformat).
+ *
+ * Examples:
+ * - For null, TEXT_FORMAT_PROTO would be 'null_value: NULL_VALUE' and would
+ * yield the bytes: { 0x58, 0x00 }.
+ * - For true, TEXT_FORMAT_PROTO would be 'boolean_value: true' and would yield
+ * the bytes { 0x08, 0x01 }.
+ *
+ * All uses are documented below, so search for TEXT_FORMAT_PROTO to find more
+ * examples.
+ */
+
+#include "Firestore/core/src/firebase/firestore/remote/serializer.h"
+
+#include <pb.h>
+#include <pb_encode.h>
+#include <limits>
+#include <vector>
+
+#include "Firestore/core/src/firebase/firestore/model/field_value.h"
+#include "Firestore/core/src/firebase/firestore/util/status.h"
+#include "gtest/gtest.h"
+
+using firebase::firestore::model::FieldValue;
+using firebase::firestore::remote::Serializer;
+using firebase::firestore::util::Status;
+
+TEST(Serializer, CanLinkToNanopb) {
+ // This test doesn't actually do anything interesting as far as actually using
+ // nanopb is concerned but that it can run at all is proof that all the
+ // libraries required for nanopb to work are actually linked correctly into
+ // the test.
+ pb_ostream_from_buffer(nullptr, 0);
+}
+
+// Fixture for running serializer tests.
+class SerializerTest : public ::testing::Test {
+ public:
+ SerializerTest() : serializer(/*DatabaseId("p", "d")*/) {
+ }
+ Serializer serializer;
+
+ void ExpectRoundTrip(const FieldValue& model,
+ const std::vector<uint8_t>& bytes,
+ FieldValue::Type type) {
+ EXPECT_EQ(type, model.type());
+ std::vector<uint8_t> actual_bytes;
+ Status status = serializer.EncodeFieldValue(model, &actual_bytes);
+ EXPECT_TRUE(status.ok());
+ EXPECT_EQ(bytes, actual_bytes);
+ FieldValue actual_model = serializer.DecodeFieldValue(bytes);
+ EXPECT_EQ(type, actual_model.type());
+ EXPECT_EQ(model, actual_model);
+ }
+};
+
+TEST_F(SerializerTest, WritesNullModelToBytes) {
+ FieldValue model = FieldValue::NullValue();
+
+ // TEXT_FORMAT_PROTO: 'null_value: NULL_VALUE'
+ std::vector<uint8_t> bytes{0x58, 0x00};
+ ExpectRoundTrip(model, bytes, FieldValue::Type::Null);
+}
+
+TEST_F(SerializerTest, WritesBoolModelToBytes) {
+ struct TestCase {
+ bool value;
+ std::vector<uint8_t> bytes;
+ };
+
+ std::vector<TestCase> cases{// TEXT_FORMAT_PROTO: 'boolean_value: true'
+ {true, {0x08, 0x01}},
+ // TEXT_FORMAT_PROTO: 'boolean_value: false'
+ {false, {0x08, 0x00}}};
+
+ for (const TestCase& test : cases) {
+ FieldValue model = FieldValue::BooleanValue(test.value);
+ ExpectRoundTrip(model, test.bytes, FieldValue::Type::Boolean);
+ }
+}
+
+TEST_F(SerializerTest, WritesIntegersModelToBytes) {
+ // NB: because we're calculating the bytes via protoc (instead of
+ // libprotobuf) we have to look at values from numeric_limits<T> ahead of
+ // time. So these test cases make the following assumptions about
+ // numeric_limits: (linking libprotobuf is starting to sound like a better
+ // idea. :)
+ // -9223372036854775808
+ // == -8000000000000000
+ // == numeric_limits<int64_t>::min()
+ // 9223372036854775807
+ // == 7FFFFFFFFFFFFFFF
+ // == numeric_limits<int64_t>::max()
+ //
+ // For now, lets at least verify these values:
+ EXPECT_EQ(-9223372036854775807 - 1, std::numeric_limits<int64_t>::min());
+ EXPECT_EQ(9223372036854775807, std::numeric_limits<int64_t>::max());
+ // TODO(rsgowman): link libprotobuf to the test suite and eliminate the
+ // above.
+
+ struct TestCase {
+ int64_t value;
+ std::vector<uint8_t> bytes;
+ };
+
+ std::vector<TestCase> cases{
+ // TEXT_FORMAT_PROTO: 'integer_value: 0'
+ TestCase{0, {0x10, 0x00}},
+ // TEXT_FORMAT_PROTO: 'integer_value: 1'
+ TestCase{1, {0x10, 0x01}},
+ // TEXT_FORMAT_PROTO: 'integer_value: -1'
+ TestCase{
+ -1,
+ {0x10, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01}},
+ // TEXT_FORMAT_PROTO: 'integer_value: 100'
+ TestCase{100, {0x10, 0x64}},
+ // TEXT_FORMAT_PROTO: 'integer_value: -100'
+ TestCase{
+ -100,
+ {0x10, 0x9c, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01}},
+ // TEXT_FORMAT_PROTO: 'integer_value: -9223372036854775808'
+ TestCase{
+ -9223372036854775807 - 1,
+ {0x10, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x01}},
+ // TEXT_FORMAT_PROTO: 'integer_value: 9223372036854775807'
+ TestCase{9223372036854775807,
+ {0x10, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f}}};
+
+ for (const TestCase& test : cases) {
+ FieldValue model = FieldValue::IntegerValue(test.value);
+ ExpectRoundTrip(model, test.bytes, FieldValue::Type::Integer);
+ }
+}
+
+TEST_F(SerializerTest, WritesStringModelToBytes) {
+ struct TestCase {
+ std::string value;
+ std::vector<uint8_t> bytes;
+ };
+
+ std::vector<TestCase> cases{
+ // TEXT_FORMAT_PROTO: 'string_value: ""'
+ {"", {0x8a, 0x01, 0x00}},
+ // TEXT_FORMAT_PROTO: 'string_value: "a"'
+ {"a", {0x8a, 0x01, 0x01, 0x61}},
+ // TEXT_FORMAT_PROTO: 'string_value: "abc def"'
+ {"abc def", {0x8a, 0x01, 0x07, 0x61, 0x62, 0x63, 0x20, 0x64, 0x65, 0x66}},
+ // TEXT_FORMAT_PROTO: 'string_value: "æ"'
+ {"æ", {0x8a, 0x01, 0x02, 0xc3, 0xa6}},
+ // TEXT_FORMAT_PROTO: 'string_value: "\0\ud7ff\ue000\uffff"'
+ // Note: Each one of the three embedded universal character names
+ // (\u-escaped) maps to three chars, so the total length of the string
+ // literal is 10 (ignoring the terminating null), and the resulting string
+ // literal is the same as '\0\xed\x9f\xbf\xee\x80\x80\xef\xbf\xbf'". The
+ // size of 10 must be added, or else std::string will see the \0 at the
+ // start and assume that's the end of the string.
+ {{"\0\ud7ff\ue000\uffff", 10},
+ {0x8a, 0x01, 0x0a, 0x00, 0xed, 0x9f, 0xbf, 0xee, 0x80, 0x80, 0xef, 0xbf,
+ 0xbf}},
+ {{"\0\xed\x9f\xbf\xee\x80\x80\xef\xbf\xbf", 10},
+ {0x8a, 0x01, 0x0a, 0x00, 0xed, 0x9f, 0xbf, 0xee, 0x80, 0x80, 0xef, 0xbf,
+ 0xbf}},
+ // TEXT_FORMAT_PROTO: 'string_value: "(╯°□°)╯︵ ┻━┻"'
+ {"(╯°□°)╯︵ ┻━┻",
+ {0x8a, 0x01, 0x1e, 0x28, 0xe2, 0x95, 0xaf, 0xc2, 0xb0, 0xe2, 0x96,
+ 0xa1, 0xc2, 0xb0, 0xef, 0xbc, 0x89, 0xe2, 0x95, 0xaf, 0xef, 0xb8,
+ 0xb5, 0x20, 0xe2, 0x94, 0xbb, 0xe2, 0x94, 0x81, 0xe2, 0x94, 0xbb}}};
+
+ for (const TestCase& test : cases) {
+ FieldValue model = FieldValue::StringValue(test.value);
+ ExpectRoundTrip(model, test.bytes, FieldValue::Type::String);
+ }
+}
+
+TEST_F(SerializerTest, WritesEmptyMapToBytes) {
+ FieldValue model = FieldValue::ObjectValueFromMap({});
+ // TEXT_FORMAT_PROTO: 'map_value: {}'
+ std::vector<uint8_t> bytes{0x32, 0x00};
+ ExpectRoundTrip(model, bytes, FieldValue::Type::Object);
+}
+
+TEST_F(SerializerTest, WritesNestedObjectsToBytes) {
+ // As above, verify max int64_t value.
+ EXPECT_EQ(9223372036854775807, std::numeric_limits<int64_t>::max());
+ // TODO(rsgowman): link libprotobuf to the test suite and eliminate the
+ // above.
+
+ FieldValue model = FieldValue::ObjectValueFromMap(
+ {{"b", FieldValue::TrueValue()},
+ // TODO(rsgowman): add doubles (once they're supported)
+ // {"d", FieldValue::DoubleValue(std::numeric_limits<double>::max())},
+ {"i", FieldValue::IntegerValue(1)},
+ {"n", FieldValue::NullValue()},
+ {"s", FieldValue::StringValue("foo")},
+ // TODO(rsgowman): add arrays (once they're supported)
+ // {"a", [2, "bar", {"b", false}]},
+ {"o", FieldValue::ObjectValueFromMap(
+ {{"d", FieldValue::IntegerValue(100)},
+ {"nested",
+ FieldValue::ObjectValueFromMap(
+ {{"e", FieldValue::IntegerValue(
+ std::numeric_limits<int64_t>::max())}})}})}});
+
+ /* WARNING: "Wire format ordering and map iteration ordering of map values is
+ * undefined, so you cannot rely on your map items being in a particular
+ * order."
+ * - https://developers.google.com/protocol-buffers/docs/proto#maps-features
+ *
+ * In reality, the map items are serialized by protoc in whatever order you
+ * provide them in. Since FieldValue::ObjectValue is currently backed by a
+ * std::map (and not an unordered_map) this implies ~alpha ordering. So we
+ * need to provide the text format input in alpha ordering for things to match
+ * up.
+ *
+ * This is... not ideal. Nothing stops libprotobuf from changing this
+ * behaviour (since it's not guaranteed) nor does anything stop us from
+ * switching map->unordered_map in FieldValue. (Arguably, that would be
+ * better.) But the alternative is to not test the serializing to bytes, and
+ * instead just assume we got that right. A *better* solution is to serialize
+ * to bytes, and then deserialize with libprotobuf (rather than nanopb) and
+ * then do a second test of serializing via libprotobuf and deserializing via
+ * nanopb. In both cases, we would ignore the bytes themselves (since the
+ * ordering is not defined) and instead compare the input objects with the
+ * output objects.
+ *
+ * TODO(rsgowman): ^
+ *
+ * TEXT_FORMAT_PROTO (with multi-line formatting to preserve sanity):
+ 'map_value: {
+ fields: {key:"b", value:{boolean_value: true}}
+ fields: {key:"i", value:{integer_value: 1}}
+ fields: {key:"n", value:{null_value: NULL_VALUE}}
+ fields: {key:"o", value:{map_value: {
+ fields: {key:"d", value:{integer_value: 100}}
+ fields: {key:"nested", value{map_value: {
+ fields: {key:"e", value:{integer_value: 9223372036854775807}}
+ }}}
+ }}}
+ fields: {key:"s", value:{string_value: "foo"}}
+ }'
+ */
+ std::vector<uint8_t> bytes{
+ 0x32, 0x59, 0x0a, 0x07, 0x0a, 0x01, 0x62, 0x12, 0x02, 0x08, 0x01, 0x0a,
+ 0x07, 0x0a, 0x01, 0x69, 0x12, 0x02, 0x10, 0x01, 0x0a, 0x07, 0x0a, 0x01,
+ 0x6e, 0x12, 0x02, 0x58, 0x00, 0x0a, 0x2f, 0x0a, 0x01, 0x6f, 0x12, 0x2a,
+ 0x32, 0x28, 0x0a, 0x07, 0x0a, 0x01, 0x64, 0x12, 0x02, 0x10, 0x64, 0x0a,
+ 0x1d, 0x0a, 0x06, 0x6e, 0x65, 0x73, 0x74, 0x65, 0x64, 0x12, 0x13, 0x32,
+ 0x11, 0x0a, 0x0f, 0x0a, 0x01, 0x65, 0x12, 0x0a, 0x10, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0x0a, 0x0b, 0x0a, 0x01, 0x73, 0x12,
+ 0x06, 0x8a, 0x01, 0x03, 0x66, 0x6f, 0x6f};
+
+ ExpectRoundTrip(model, bytes, FieldValue::Type::Object);
+}
+
+// TODO(rsgowman): Test [en|de]coding multiple protos into the same output
+// vector.
+
+// TODO(rsgowman): Death test for decoding invalid bytes.
diff --git a/Firestore/core/test/firebase/firestore/testutil/CMakeLists.txt b/Firestore/core/test/firebase/firestore/testutil/CMakeLists.txt
new file mode 100644
index 0000000..636acb0
--- /dev/null
+++ b/Firestore/core/test/firebase/firestore/testutil/CMakeLists.txt
@@ -0,0 +1,38 @@
+# Copyright 2018 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.
+
+cc_library(
+ firebase_firestore_testutil_apple
+ SOURCES
+ app_testing.h
+ app_testing.mm
+ DEPENDS
+ FirebaseCore
+ absl_strings
+ EXCLUDE_FROM_ALL
+)
+
+if(APPLE)
+ list(APPEND TESTUTIL_DEPENDS firebase_firestore_testutil_apple)
+endif()
+
+cc_library(
+ firebase_firestore_testutil
+ SOURCES
+ testutil.cc
+ testutil.h
+ DEPENDS
+ ${TESTUTIL_DEPENDS}
+ firebase_firestore_model
+)
diff --git a/Firestore/core/test/firebase/firestore/testutil/app_testing.h b/Firestore/core/test/firebase/firestore/testutil/app_testing.h
new file mode 100644
index 0000000..d18ba4f
--- /dev/null
+++ b/Firestore/core/test/firebase/firestore/testutil/app_testing.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#ifndef FIRESTORE_CORE_TEST_FIREBASE_FIRESTORE_TESTUTIL_APP_TESTING_H_
+#define FIRESTORE_CORE_TEST_FIREBASE_FIRESTORE_TESTUTIL_APP_TESTING_H_
+
+#include "absl/strings/string_view.h"
+
+#if __OBJC__
+
+@class FIRApp;
+
+namespace firebase {
+namespace firestore {
+namespace testutil {
+
+/** Creates a set of default Firebase Options for testing. */
+FIROptions* OptionsForUnitTesting(
+ const absl::string_view project_id = "project_id");
+
+/** Creates a new Firebase App for testing. */
+FIRApp* AppForUnitTesting(const absl::string_view project_id = "project_id");
+
+} // namespace testutil
+} // namespace firestore
+} // namespace firebase
+
+#endif // __OBJC__
+
+#endif // FIRESTORE_CORE_TEST_FIREBASE_FIRESTORE_TESTUTIL_APP_TESTING_H_
diff --git a/Firestore/core/test/firebase/firestore/testutil/app_testing.mm b/Firestore/core/test/firebase/firestore/testutil/app_testing.mm
new file mode 100644
index 0000000..21db0db
--- /dev/null
+++ b/Firestore/core/test/firebase/firestore/testutil/app_testing.mm
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2018 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 <FirebaseCore/FIRApp.h>
+#import <FirebaseCore/FIROptions.h>
+
+#include "Firestore/core/src/firebase/firestore/util/string_apple.h"
+#include "Firestore/core/test/firebase/firestore/testutil/app_testing.h"
+
+namespace firebase {
+namespace firestore {
+namespace testutil {
+
+FIROptions* OptionsForUnitTesting(const absl::string_view project_id) {
+ FIROptions* options =
+ [[FIROptions alloc] initWithGoogleAppID:@"1:123:ios:123ab"
+ GCMSenderID:@"gcm_sender_id"];
+ options.projectID = util::WrapNSString(project_id);
+ return options;
+}
+
+FIRApp* AppForUnitTesting(const absl::string_view project_id) {
+ static int counter = 0;
+
+ NSString* appName =
+ [NSString stringWithFormat:@"app_for_unit_testing_%d", counter++];
+ FIROptions* options = OptionsForUnitTesting(project_id);
+ [FIRApp configureWithName:appName options:options];
+
+ return [FIRApp appNamed:appName];
+}
+
+} // namespace testutil
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/test/firebase/firestore/testutil/testutil.cc b/Firestore/core/test/firebase/firestore/testutil/testutil.cc
new file mode 100644
index 0000000..9b9228d
--- /dev/null
+++ b/Firestore/core/test/firebase/firestore/testutil/testutil.cc
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#include "Firestore/core/test/firebase/firestore/testutil/testutil.h"
+
+namespace firebase {
+namespace firestore {
+namespace testutil {
+
+void dummy() {
+}
+
+} // namespace testutil
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/test/firebase/firestore/testutil/testutil.h b/Firestore/core/test/firebase/firestore/testutil/testutil.h
new file mode 100644
index 0000000..9a875f4
--- /dev/null
+++ b/Firestore/core/test/firebase/firestore/testutil/testutil.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#ifndef FIRESTORE_CORE_TEST_FIREBASE_FIRESTORE_TESTUTIL_TESTUTIL_H_
+#define FIRESTORE_CORE_TEST_FIREBASE_FIRESTORE_TESTUTIL_TESTUTIL_H_
+
+#include <chrono> // NOLINT(build/c++11)
+#include <cstdint>
+
+#include "Firestore/core/include/firebase/firestore/timestamp.h"
+#include "Firestore/core/src/firebase/firestore/model/document.h"
+#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+#include "Firestore/core/src/firebase/firestore/model/field_path.h"
+#include "Firestore/core/src/firebase/firestore/model/no_document.h"
+#include "Firestore/core/src/firebase/firestore/model/resource_path.h"
+#include "Firestore/core/src/firebase/firestore/model/snapshot_version.h"
+#include "absl/strings/string_view.h"
+
+namespace firebase {
+namespace firestore {
+namespace testutil {
+
+// Below are convenience methods for creating instances for tests.
+
+inline model::DocumentKey Key(absl::string_view path) {
+ return model::DocumentKey::FromPathString(path);
+}
+
+inline model::FieldPath Field(absl::string_view field) {
+ return model::FieldPath::FromServerFormat(field);
+}
+
+inline model::ResourcePath Resource(absl::string_view field) {
+ return model::ResourcePath::FromString(field);
+}
+
+/**
+ * Creates a snapshot version from the given version timestamp.
+ *
+ * @param version a timestamp in microseconds since the epoch.
+ */
+inline model::SnapshotVersion Version(int64_t version) {
+ namespace chr = std::chrono;
+ auto timepoint =
+ chr::time_point<chr::system_clock>(chr::microseconds(version));
+ return model::SnapshotVersion{Timestamp::FromTimePoint(timepoint)};
+}
+
+inline model::Document Doc(absl::string_view key, int64_t version) {
+ return model::Document{model::FieldValue::ObjectValueFromMap({}), Key(key),
+ Version(version),
+ /* has_local_mutations= */ false};
+}
+
+inline model::NoDocument DeletedDoc(absl::string_view key, int64_t version) {
+ return model::NoDocument{Key(key), Version(version)};
+}
+
+// Add a non-inline function to make this library buildable.
+// TODO(zxu123): remove once there is non-inline function.
+void dummy();
+
+} // namespace testutil
+} // namespace firestore
+} // namespace firebase
+
+#endif // FIRESTORE_CORE_TEST_FIREBASE_FIRESTORE_TESTUTIL_TESTUTIL_H_
diff --git a/Firestore/core/test/firebase/firestore/timestamp_test.cc b/Firestore/core/test/firebase/firestore/timestamp_test.cc
new file mode 100644
index 0000000..e7e8587
--- /dev/null
+++ b/Firestore/core/test/firebase/firestore/timestamp_test.cc
@@ -0,0 +1,299 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#include "Firestore/core/include/firebase/firestore/timestamp.h"
+
+#include <limits>
+#include <utility>
+#include <vector>
+
+#include "gtest/gtest.h"
+
+namespace firebase {
+
+namespace {
+
+using TimePoint = std::chrono::time_point<std::chrono::system_clock>;
+using Sec = std::chrono::seconds;
+using Ms = std::chrono::milliseconds;
+
+const auto kUpperBound = 253402300800L - 1;
+const auto kLowerBound = -62135596800L;
+
+// For near-bounds tests that use <chrono>, it's important to only run them if
+// system_clock::duration can represent values this large (e.g., on Linux, it's
+// system_clock::duration uses nanoseconds precision and thus would overflow
+// trying to represent very large numbers).
+bool CanSystemClockDurationHold(const Sec seconds) {
+ namespace chr = std::chrono;
+ if (seconds.count() >= 0) {
+ const auto max_seconds =
+ chr::duration_cast<chr::seconds>(TimePoint::duration::max()).count();
+ return max_seconds >= seconds.count();
+ } else {
+ const auto min_seconds =
+ chr::duration_cast<chr::seconds>(TimePoint::duration::min()).count();
+ return min_seconds <= seconds.count();
+ }
+}
+
+} // namespace
+
+TEST(Timestamp, Constructors) {
+ const Timestamp zero;
+ EXPECT_EQ(0, zero.seconds());
+ EXPECT_EQ(0, zero.nanoseconds());
+
+ const Timestamp positive(100, 200);
+ EXPECT_EQ(100, positive.seconds());
+ EXPECT_EQ(200, positive.nanoseconds());
+
+ const Timestamp negative(-100, 200);
+ EXPECT_EQ(-100, negative.seconds());
+ EXPECT_EQ(200, negative.nanoseconds());
+
+ const Timestamp now = Timestamp::Now();
+ EXPECT_LT(0, now.seconds());
+ EXPECT_LE(0, now.nanoseconds());
+
+ Timestamp copy_now = now;
+ EXPECT_EQ(now, copy_now);
+ EXPECT_EQ(now.seconds(), copy_now.seconds());
+ EXPECT_EQ(now.nanoseconds(), copy_now.nanoseconds());
+ const Timestamp move_now = std::move(copy_now);
+ EXPECT_EQ(now, move_now);
+}
+
+TEST(Timestamp, Bounds) {
+ const Timestamp max_timestamp{kUpperBound, 999999999};
+ EXPECT_EQ(kUpperBound, max_timestamp.seconds());
+ EXPECT_EQ(999999999, max_timestamp.nanoseconds());
+
+ const Timestamp min_timestamp{kLowerBound, 0};
+ EXPECT_EQ(kLowerBound, min_timestamp.seconds());
+ EXPECT_EQ(0, min_timestamp.nanoseconds());
+}
+
+TEST(Timestamp, FromTimeT) {
+ const Timestamp zero = Timestamp::FromTimeT(std::time_t{});
+ EXPECT_EQ(0, zero.seconds());
+ EXPECT_EQ(0, zero.nanoseconds());
+
+ const Timestamp positive = Timestamp::FromTimeT(std::time_t{123456});
+ EXPECT_EQ(123456, positive.seconds());
+ EXPECT_EQ(0, positive.nanoseconds());
+
+ const Timestamp negative = Timestamp::FromTimeT(std::time_t{-123456});
+ EXPECT_EQ(-123456, negative.seconds());
+ EXPECT_EQ(0, negative.nanoseconds());
+}
+
+TEST(Timestamp, FromChrono) {
+ const auto zero = Timestamp::FromTimePoint(TimePoint{});
+ EXPECT_EQ(0, zero.seconds());
+ EXPECT_EQ(0, zero.nanoseconds());
+
+ const auto sec = Timestamp::FromTimePoint(TimePoint{Sec(123)});
+ EXPECT_EQ(123, sec.seconds());
+ EXPECT_EQ(0, sec.nanoseconds());
+
+ const auto ms = Timestamp::FromTimePoint(TimePoint{Sec(123) + Ms(456)});
+ EXPECT_EQ(123, ms.seconds());
+ EXPECT_EQ(456000000, ms.nanoseconds());
+}
+
+TEST(Timestamp, FromChronoNegativeTime) {
+ const auto no_fraction = Timestamp::FromTimePoint(TimePoint{Sec(-123)});
+ EXPECT_EQ(-123, no_fraction.seconds());
+ EXPECT_EQ(0, no_fraction.nanoseconds());
+
+ const auto with_positive_fraction =
+ Timestamp::FromTimePoint(TimePoint{Sec(-123) + Ms(456)});
+ EXPECT_EQ(-123, with_positive_fraction.seconds());
+ EXPECT_EQ(456000000, with_positive_fraction.nanoseconds());
+
+ const auto with_negative_fraction =
+ Timestamp::FromTimePoint(TimePoint{Sec(-122) + Ms(-544)});
+ EXPECT_EQ(-123, with_negative_fraction.seconds());
+ EXPECT_EQ(456000000, with_negative_fraction.nanoseconds());
+
+ const auto with_large_negative_fraction =
+ Timestamp::FromTimePoint(TimePoint{Sec(-122) + Ms(-100544)});
+ EXPECT_EQ(-223, with_large_negative_fraction.seconds());
+ EXPECT_EQ(456000000, with_large_negative_fraction.nanoseconds());
+
+ const auto only_negative_fraction =
+ Timestamp::FromTimePoint(TimePoint{Ms(-544)});
+ EXPECT_EQ(-1, only_negative_fraction.seconds());
+ EXPECT_EQ(456000000, only_negative_fraction.nanoseconds());
+
+ const auto positive_time_negative_fraction =
+ Timestamp::FromTimePoint(TimePoint{Sec(1) + Ms(-544)});
+ EXPECT_EQ(0, positive_time_negative_fraction.seconds());
+ EXPECT_EQ(456000000, positive_time_negative_fraction.nanoseconds());
+
+ if (CanSystemClockDurationHold(Sec(kUpperBound + 1))) {
+ const auto near_bounds =
+ Timestamp::FromTimePoint(TimePoint{Sec(kUpperBound + 1) + Ms(-544)});
+ EXPECT_EQ(kUpperBound, near_bounds.seconds());
+ EXPECT_EQ(456000000, near_bounds.nanoseconds());
+ }
+}
+
+TEST(Timestamp, ToChrono) {
+ namespace chr = std::chrono;
+
+ // Note: this line is outside the inner block because otherwise clang-format
+ // gets confused about namespace alias on the line above.
+ const Timestamp positive{123, 456789000};
+ {
+ const auto micros = positive.ToTimePoint().time_since_epoch();
+ EXPECT_EQ(123456789, chr::duration_cast<chr::microseconds>(micros).count());
+
+ const auto millis =
+ positive.ToTimePoint<chr::system_clock, chr::milliseconds>()
+ .time_since_epoch();
+ EXPECT_EQ(123456000, chr::duration_cast<chr::microseconds>(millis).count());
+
+ const auto nanos =
+ positive.ToTimePoint<chr::system_clock, chr::nanoseconds>()
+ .time_since_epoch();
+ EXPECT_EQ(123456789000,
+ chr::duration_cast<chr::nanoseconds>(nanos).count());
+ }
+
+ {
+ const Timestamp negative{-123, 456000000};
+
+ const auto millis =
+ negative.ToTimePoint<chr::system_clock, chr::milliseconds>()
+ .time_since_epoch();
+ const auto seconds = chr::duration_cast<chr::seconds>(millis);
+ EXPECT_EQ(-122, seconds.count());
+ EXPECT_EQ(-544,
+ chr::duration_cast<chr::milliseconds>(millis - seconds).count());
+ }
+
+ // Bounds
+ {
+ const Timestamp max{kUpperBound, 999999999};
+ const auto max_micros = max.ToTimePoint().time_since_epoch();
+ EXPECT_EQ(kUpperBound * 1000 * 1000 + 999999,
+ chr::duration_cast<chr::microseconds>(max_micros).count());
+
+ const Timestamp min{kLowerBound, 0};
+ const auto min_micros = min.ToTimePoint().time_since_epoch();
+ EXPECT_EQ(kLowerBound * 1000 * 1000,
+ chr::duration_cast<chr::microseconds>(min_micros).count());
+ }
+
+ // Overflow
+ {
+ const Timestamp max{kUpperBound, 999999999};
+
+ const auto max_nanos =
+ max.ToTimePoint<chr::system_clock, chr::nanoseconds>()
+ .time_since_epoch();
+ EXPECT_EQ(std::numeric_limits<chr::nanoseconds::rep>::max(),
+ chr::duration_cast<chr::nanoseconds>(max_nanos).count());
+
+ const Timestamp min{kLowerBound, 0};
+ const auto min_nanos =
+ min.ToTimePoint<chr::system_clock, chr::nanoseconds>()
+ .time_since_epoch();
+ EXPECT_EQ(std::numeric_limits<chr::nanoseconds::rep>::min(),
+ chr::duration_cast<chr::nanoseconds>(min_nanos).count());
+ }
+}
+
+TEST(Timestamp, Comparison) {
+ EXPECT_LT(Timestamp(), Timestamp(1, 2));
+ EXPECT_LT(Timestamp(1, 2), Timestamp(2, 1));
+ EXPECT_LT(Timestamp(2, 1), Timestamp(2, 2));
+
+ EXPECT_GT(Timestamp(1, 1), Timestamp());
+ EXPECT_GT(Timestamp(2, 1), Timestamp(1, 2));
+ EXPECT_GT(Timestamp(2, 2), Timestamp(2, 1));
+
+ EXPECT_LE(Timestamp(), Timestamp());
+ EXPECT_LE(Timestamp(), Timestamp(1, 2));
+ EXPECT_LE(Timestamp(1, 2), Timestamp(2, 1));
+ EXPECT_LE(Timestamp(2, 1), Timestamp(2, 1));
+ EXPECT_LE(Timestamp(2, 1), Timestamp(2, 2));
+
+ EXPECT_GE(Timestamp(), Timestamp());
+ EXPECT_GE(Timestamp(1, 1), Timestamp());
+ EXPECT_GE(Timestamp(1, 1), Timestamp(1, 1));
+ EXPECT_GE(Timestamp(2, 1), Timestamp(1, 2));
+ EXPECT_GE(Timestamp(2, 1), Timestamp(2, 1));
+ EXPECT_GE(Timestamp(2, 2), Timestamp(2, 1));
+
+ EXPECT_EQ(Timestamp(), Timestamp());
+ EXPECT_EQ(Timestamp(), Timestamp(0, 0));
+ EXPECT_EQ(Timestamp(123, 123456789), Timestamp(123, 123456789));
+
+ EXPECT_NE(Timestamp(), Timestamp(0, 1));
+ EXPECT_NE(Timestamp(), Timestamp(1, 0));
+ EXPECT_NE(Timestamp(123, 123456789), Timestamp(123, 123456780));
+}
+
+TEST(Timestamp, Hash) {
+ const Timestamp foo1{123, 456000000};
+ const Timestamp foo2 = foo1;
+ const Timestamp foo3 =
+ Timestamp::FromTimePoint(TimePoint{Sec(123) + Ms(456)});
+ EXPECT_EQ(std::hash<Timestamp>()(foo1), std::hash<Timestamp>()(foo2));
+ EXPECT_EQ(std::hash<Timestamp>()(foo2), std::hash<Timestamp>()(foo3));
+
+ const Timestamp bar{123, 456};
+ EXPECT_NE(std::hash<Timestamp>()(foo1), std::hash<Timestamp>()(bar));
+}
+
+TEST(Timestamp, InvalidArguments) {
+ // Negative nanoseconds.
+ ASSERT_ANY_THROW(Timestamp(0, -1));
+ ASSERT_ANY_THROW(Timestamp(100, -1));
+ ASSERT_ANY_THROW(Timestamp(100, -12346789));
+
+ // Nanoseconds that are more than one second.
+ ASSERT_ANY_THROW(Timestamp(0, 999999999 + 1));
+
+ // Seconds beyond supported range.
+ ASSERT_ANY_THROW(Timestamp(kLowerBound - 1, 0));
+ ASSERT_ANY_THROW(Timestamp(kUpperBound + 1, 0));
+}
+
+TEST(Timestamp, InvalidArgumentsChrono) {
+ // Make sure Timestamp doesn't accept values beyond the supported range, if
+ // system clock-based time_point on this platform can represent values this
+ // large.
+ if (CanSystemClockDurationHold(Sec(kUpperBound + 1))) {
+ ASSERT_ANY_THROW(Timestamp::FromTimePoint(TimePoint{Sec(kUpperBound + 1)}));
+ }
+ if (CanSystemClockDurationHold(Sec(kLowerBound - 1))) {
+ ASSERT_ANY_THROW(Timestamp::FromTimePoint(TimePoint{Sec(kLowerBound - 1)}));
+ }
+}
+
+TEST(Timestamp, ToString) {
+ EXPECT_EQ(Timestamp().ToString(), "Timestamp(seconds=0, nanoseconds=0)");
+ EXPECT_EQ(Timestamp(123, 123456789).ToString(),
+ "Timestamp(seconds=123, nanoseconds=123456789)");
+ EXPECT_EQ(Timestamp(-123, 123456789).ToString(),
+ "Timestamp(seconds=-123, nanoseconds=123456789)");
+}
+
+} // namespace firebase
diff --git a/Firestore/core/test/firebase/firestore/util/CMakeLists.txt b/Firestore/core/test/firebase/firestore/util/CMakeLists.txt
index 223fa41..e5dbec5 100644
--- a/Firestore/core/test/firebase/firestore/util/CMakeLists.txt
+++ b/Firestore/core/test/firebase/firestore/util/CMakeLists.txt
@@ -12,12 +12,73 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+set(CMAKE_CXX_EXTENSIONS ON)
+
+# Required to allow 0 length printf style strings for testing purposes.
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-format-zero-length")
+
+## assert and log
+
+if(APPLE)
+ cc_test(
+ firebase_firestore_util_log_apple_test
+ SOURCES
+ assert_test.cc
+ log_test.cc
+ DEPENDS
+ firebase_firestore_util_log_apple
+ )
+endif(APPLE)
+
cc_test(
- firebase_firestore_util_test
- autoid_test.cc
- secure_random_test.cc
+ firebase_firestore_util_log_stdio_test
+ SOURCES
+ assert_test.cc
+ log_test.cc
+ DEPENDS
+ firebase_firestore_util_log_stdio
)
-target_link_libraries(
+
+## secure random
+
+if(HAVE_ARC4RANDOM)
+ cc_test(
+ firebase_firestore_util_random_arc4random_test
+ SOURCES
+ secure_random_test.cc
+ DEPENDS
+ firebase_firestore_util_random_arc4random
+ )
+endif()
+
+if(HAVE_OPENSSL_RAND_H)
+ cc_test(
+ firebase_firestore_util_random_openssl_test
+ SOURCES
+ secure_random_test.cc
+ DEPENDS
+ firebase_firestore_util_random_openssl
+ )
+endif()
+
+## main library
+
+cc_test(
firebase_firestore_util_test
- firebase_firestore_util
+ SOURCES
+ autoid_test.cc
+ bits_test.cc
+ comparison_test.cc
+ iterator_adaptors_test.cc
+ ordered_code_test.cc
+ status_test.cc
+ status_test_util.h
+ statusor_test.cc
+ string_printf_test.cc
+ string_util_test.cc
+ DEPENDS
+ absl_base
+ absl_strings
+ firebase_firestore_util
+ gmock
)
diff --git a/Firestore/core/test/firebase/firestore/util/assert_test.cc b/Firestore/core/test/firebase/firestore/util/assert_test.cc
new file mode 100644
index 0000000..fb15e61
--- /dev/null
+++ b/Firestore/core/test/firebase/firestore/util/assert_test.cc
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#include <exception>
+
+#include "Firestore/core/src/firebase/firestore/util/firebase_assert.h"
+#include "gtest/gtest.h"
+
+namespace firebase {
+namespace firestore {
+namespace util {
+
+namespace {
+
+void AssertWithExpression(bool condition) {
+ FIREBASE_ASSERT_WITH_EXPRESSION(condition, 1 + 2 + 3);
+}
+
+void Assert(bool condition) {
+ FIREBASE_ASSERT(condition == true);
+}
+
+void AssertMessageWithExpression(bool condition) {
+ FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION(condition, 1 + 2 + 3, "connection %s",
+ condition ? "succeeded" : "failed");
+}
+
+} // namespace
+
+TEST(Assert, WithExpression) {
+ AssertWithExpression(true);
+
+ EXPECT_ANY_THROW(AssertWithExpression(false));
+}
+
+TEST(Assert, Vanilla) {
+ Assert(true);
+
+ EXPECT_ANY_THROW(Assert(false));
+}
+
+TEST(Assert, WithMessageAndExpression) {
+ AssertMessageWithExpression(true);
+
+ EXPECT_ANY_THROW(AssertMessageWithExpression(false));
+}
+
+} // namespace util
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/test/firebase/firestore/util/autoid_test.cc b/Firestore/core/test/firebase/firestore/util/autoid_test.cc
index 730c683..808850b 100644
--- a/Firestore/core/test/firebase/firestore/util/autoid_test.cc
+++ b/Firestore/core/test/firebase/firestore/util/autoid_test.cc
@@ -16,7 +16,7 @@
#include "Firestore/core/src/firebase/firestore/util/autoid.h"
-#include <ctype.h>
+#include <cctype>
#include "gtest/gtest.h"
@@ -25,8 +25,8 @@ using firebase::firestore::util::CreateAutoId;
TEST(AutoId, IsSane) {
for (int i = 0; i < 50; i++) {
std::string auto_id = CreateAutoId();
- EXPECT_EQ(20, auto_id.length());
- for (int pos = 0; pos < 20; pos++) {
+ EXPECT_EQ(20u, auto_id.length());
+ for (size_t pos = 0; pos < 20; pos++) {
char c = auto_id[pos];
EXPECT_TRUE(isalpha(c) || isdigit(c))
<< "Should be printable ascii character: '" << c << "' in \""
diff --git a/Firestore/Port/bits_test.cc b/Firestore/core/test/firebase/firestore/util/bits_test.cc
index 8c3c246..572721f 100644
--- a/Firestore/Port/bits_test.cc
+++ b/Firestore/core/test/firebase/firestore/util/bits_test.cc
@@ -14,24 +14,24 @@
* limitations under the License.
*/
-#include "Firestore/Port/bits.h"
+#include "Firestore/core/src/firebase/firestore/util/bits.h"
+#include <algorithm>
#include <iostream>
+#include <limits>
-#include "base/commandlineflags.h"
-#include "testing/base/public/gunit.h"
-#include "util/random/mt_random.h"
+#include "Firestore/core/src/firebase/firestore/util/secure_random.h"
+#include "gtest/gtest.h"
-using Firestore::Bits;
+namespace firebase {
+namespace firestore {
+namespace util {
-DEFINE_int32(num_iterations, 10000, "Number of test iterations to run.");
+const int kNumIterations = 10000; // "Number of test iterations to run.
class BitsTest : public testing::Test {
- public:
- BitsTest() : random_(testing::FLAGS_gunit_random_seed) {}
-
protected:
- MTRandom random_;
+ SecureRandom random_;
};
TEST_F(BitsTest, Log2EdgeCases) {
@@ -41,7 +41,7 @@ TEST_F(BitsTest, Log2EdgeCases) {
EXPECT_EQ(-1, Bits::Log2Floor64(0));
for (int i = 0; i < 32; i++) {
- uint32 n = 1U << i;
+ uint32_t n = 1U << i;
EXPECT_EQ(i, Bits::Log2Floor(n));
EXPECT_EQ(i, Bits::Log2FloorNonZero(n));
if (n > 2) {
@@ -53,7 +53,7 @@ TEST_F(BitsTest, Log2EdgeCases) {
}
for (int i = 0; i < 64; i++) {
- uint64 n = 1ULL << i;
+ uint64_t n = 1ULL << i;
EXPECT_EQ(i, Bits::Log2Floor64(n));
EXPECT_EQ(i, Bits::Log2FloorNonZero64(n));
if (n > 2) {
@@ -68,17 +68,17 @@ TEST_F(BitsTest, Log2EdgeCases) {
TEST_F(BitsTest, Log2Random) {
std::cout << "TestLog2Random" << std::endl;
- for (int i = 0; i < FLAGS_num_iterations; i++) {
- int maxbit = -1;
- uint32 n = 0;
- while (!random_.OneIn(32)) {
- int bit = random_.Uniform(32);
+ for (int i = 0; i < kNumIterations; i++) {
+ int max_bit = -1;
+ uint32_t n = 0;
+ while (!random_.OneIn(32u)) {
+ int bit = static_cast<int>(random_.Uniform(32u));
n |= (1U << bit);
- maxbit = std::max(bit, maxbit);
+ max_bit = std::max(bit, max_bit);
}
- EXPECT_EQ(maxbit, Bits::Log2Floor(n));
+ EXPECT_EQ(max_bit, Bits::Log2Floor(n));
if (n != 0) {
- EXPECT_EQ(maxbit, Bits::Log2FloorNonZero(n));
+ EXPECT_EQ(max_bit, Bits::Log2FloorNonZero(n));
}
}
}
@@ -86,25 +86,25 @@ TEST_F(BitsTest, Log2Random) {
TEST_F(BitsTest, Log2Random64) {
std::cout << "TestLog2Random64" << std::endl;
- for (int i = 0; i < FLAGS_num_iterations; i++) {
- int maxbit = -1;
- uint64 n = 0;
- while (!random_.OneIn(64)) {
- int bit = random_.Uniform(64);
+ for (int i = 0; i < kNumIterations; i++) {
+ int max_bit = -1;
+ uint64_t n = 0;
+ while (!random_.OneIn(64u)) {
+ int bit = static_cast<int>(random_.Uniform(64u));
n |= (1ULL << bit);
- maxbit = std::max(bit, maxbit);
+ max_bit = std::max(bit, max_bit);
}
- EXPECT_EQ(maxbit, Bits::Log2Floor64(n));
+ EXPECT_EQ(max_bit, Bits::Log2Floor64(n));
if (n != 0) {
- EXPECT_EQ(maxbit, Bits::Log2FloorNonZero64(n));
+ EXPECT_EQ(max_bit, Bits::Log2FloorNonZero64(n));
}
}
}
TEST(Bits, Port32) {
for (int shift = 0; shift < 32; shift++) {
- for (int delta = -1; delta <= +1; delta++) {
- const uint32 v = (static_cast<uint32>(1) << shift) + delta;
+ for (uint32_t delta = 0; delta <= 2; delta++) {
+ const uint32_t v = (static_cast<uint32_t>(1) << shift) - 1 + delta;
EXPECT_EQ(Bits::Log2Floor_Portable(v), Bits::Log2Floor(v)) << v;
if (v != 0) {
EXPECT_EQ(Bits::Log2FloorNonZero_Portable(v), Bits::Log2FloorNonZero(v))
@@ -112,7 +112,7 @@ TEST(Bits, Port32) {
}
}
}
- static const uint32 M32 = kuint32max;
+ static const uint32_t M32 = std::numeric_limits<uint32_t>::max();
EXPECT_EQ(Bits::Log2Floor_Portable(M32), Bits::Log2Floor(M32)) << M32;
EXPECT_EQ(Bits::Log2FloorNonZero_Portable(M32), Bits::Log2FloorNonZero(M32))
<< M32;
@@ -120,8 +120,8 @@ TEST(Bits, Port32) {
TEST(Bits, Port64) {
for (int shift = 0; shift < 64; shift++) {
- for (int delta = -1; delta <= +1; delta++) {
- const uint64 v = (static_cast<uint64>(1) << shift) + delta;
+ for (uint64_t delta = 0; delta <= 2; delta++) {
+ const uint64_t v = (static_cast<uint64_t>(1) << shift) - 1 + delta;
EXPECT_EQ(Bits::Log2Floor64_Portable(v), Bits::Log2Floor64(v)) << v;
if (v != 0) {
EXPECT_EQ(Bits::Log2FloorNonZero64_Portable(v),
@@ -130,9 +130,13 @@ TEST(Bits, Port64) {
}
}
}
- static const uint64 M64 = kuint64max;
+ static const uint64_t M64 = std::numeric_limits<uint64_t>::max();
EXPECT_EQ(Bits::Log2Floor64_Portable(M64), Bits::Log2Floor64(M64)) << M64;
EXPECT_EQ(Bits::Log2FloorNonZero64_Portable(M64),
Bits::Log2FloorNonZero64(M64))
<< M64;
}
+
+} // namespace util
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/test/firebase/firestore/util/comparison_test.cc b/Firestore/core/test/firebase/firestore/util/comparison_test.cc
new file mode 100644
index 0000000..a03aec8
--- /dev/null
+++ b/Firestore/core/test/firebase/firestore/util/comparison_test.cc
@@ -0,0 +1,210 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#include "Firestore/core/src/firebase/firestore/util/comparison.h"
+
+#include <cinttypes>
+#include <cmath>
+#include <limits>
+
+#include "Firestore/core/src/firebase/firestore/util/string_printf.h"
+#include "gtest/gtest.h"
+
+namespace firebase {
+namespace firestore {
+namespace util {
+
+#define ASSERT_SAME(comparison) \
+ do { \
+ ASSERT_EQ(ComparisonResult::Same, comparison); \
+ } while (0)
+
+#define ASSERT_ASCENDING(comparison) \
+ do { \
+ ASSERT_EQ(ComparisonResult::Ascending, comparison); \
+ } while (0)
+
+#define ASSERT_DESCENDING(comparison) \
+ do { \
+ ASSERT_EQ(ComparisonResult::Descending, comparison); \
+ } while (0)
+
+TEST(Comparison, ReverseOrder) {
+ ASSERT_ASCENDING(ReverseOrder(ComparisonResult::Descending));
+ ASSERT_DESCENDING(ReverseOrder(ComparisonResult::Ascending));
+ ASSERT_SAME(ReverseOrder(ComparisonResult::Same));
+}
+
+TEST(Comparison, StringCompare) {
+ ASSERT_ASCENDING(Compare<absl::string_view>("", "a"));
+ ASSERT_ASCENDING(Compare<absl::string_view>("a", "b"));
+ ASSERT_ASCENDING(Compare<absl::string_view>("a", "aa"));
+
+ ASSERT_DESCENDING(Compare<absl::string_view>("a", ""));
+ ASSERT_DESCENDING(Compare<absl::string_view>("b", "a"));
+ ASSERT_DESCENDING(Compare<absl::string_view>("aa", "a"));
+
+ ASSERT_SAME(Compare<absl::string_view>("", ""));
+ ASSERT_SAME(Compare<absl::string_view>("", std::string()));
+ ASSERT_SAME(Compare<absl::string_view>("a", "a"));
+}
+
+TEST(Comparison, BooleanCompare) {
+ ASSERT_SAME(Compare<bool>(false, false));
+ ASSERT_SAME(Compare<bool>(true, true));
+ ASSERT_ASCENDING(Compare<bool>(false, true));
+ ASSERT_DESCENDING(Compare<bool>(true, false));
+}
+
+TEST(Comparison, DoubleCompare) {
+ ASSERT_SAME(Compare<double>(NAN, NAN));
+ ASSERT_ASCENDING(Compare<double>(NAN, 0));
+ ASSERT_DESCENDING(Compare<double>(0, NAN));
+
+ ASSERT_SAME(Compare<double>(-INFINITY, -INFINITY));
+ ASSERT_SAME(Compare<double>(INFINITY, INFINITY));
+ ASSERT_ASCENDING(Compare<double>(-INFINITY, INFINITY));
+ ASSERT_DESCENDING(Compare<double>(INFINITY, -INFINITY));
+
+ ASSERT_SAME(Compare<double>(0, 0));
+ ASSERT_SAME(Compare<double>(-0, -0));
+ ASSERT_SAME(Compare<double>(-0, 0));
+}
+
+#define ASSERT_BIT_EQUALS(expected, actual) \
+ do { \
+ uint64_t expectedBits = DoubleBits(expected); \
+ uint64_t actualBits = DoubleBits(actual); \
+ if (expectedBits != actualBits) { \
+ std::string message = StringPrintf( \
+ "Expected <%f> to compare equal to <%f> " \
+ "with bits <%" PRIu64 "> equal to <%" PRIu64 ">", \
+ actual, expected, actualBits, expectedBits); \
+ FAIL() << message; \
+ } \
+ } while (0);
+
+#define ASSERT_MIXED_SAME(doubleValue, longValue) \
+ do { \
+ ComparisonResult result = CompareMixedNumber(doubleValue, longValue); \
+ if (result != ComparisonResult::Same) { \
+ std::string message = StringPrintf( \
+ "Expected <%f> to compare equal to <%lld>", doubleValue, longValue); \
+ FAIL() << message; \
+ } \
+ } while (0);
+
+#define ASSERT_MIXED_DESCENDING(doubleValue, longValue) \
+ do { \
+ ComparisonResult result = CompareMixedNumber(doubleValue, longValue); \
+ if (result != ComparisonResult::Descending) { \
+ std::string message = StringPrintf( \
+ "Expected <%f> to compare equal to <%lld>", doubleValue, longValue); \
+ FAIL() << message; \
+ } \
+ } while (0);
+
+#define ASSERT_MIXED_ASCENDING(doubleValue, longValue) \
+ do { \
+ ComparisonResult result = CompareMixedNumber(doubleValue, longValue); \
+ if (result != ComparisonResult::Ascending) { \
+ std::string message = StringPrintf( \
+ "Expected <%f> to compare equal to <%lld>", doubleValue, longValue); \
+ FAIL() << message; \
+ } \
+ } while (0);
+
+TEST(Comparison, MixedNumberCompare) {
+ // Infinities
+ ASSERT_MIXED_ASCENDING(-INFINITY, LLONG_MIN);
+ ASSERT_MIXED_ASCENDING(-INFINITY, LLONG_MAX);
+ ASSERT_MIXED_ASCENDING(-INFINITY, 0LL);
+
+ ASSERT_MIXED_DESCENDING(INFINITY, LLONG_MIN);
+ ASSERT_MIXED_DESCENDING(INFINITY, LLONG_MAX);
+ ASSERT_MIXED_DESCENDING(INFINITY, 0LL);
+
+ // NaN
+ ASSERT_MIXED_ASCENDING(NAN, LLONG_MIN);
+ ASSERT_MIXED_ASCENDING(NAN, LLONG_MAX);
+ ASSERT_MIXED_ASCENDING(NAN, 0LL);
+
+ // Large values (note DBL_MIN is positive and near zero).
+ ASSERT_MIXED_ASCENDING(-DBL_MAX, LLONG_MIN);
+
+ // Tests around LLONG_MIN
+ ASSERT_BIT_EQUALS((double)LLONG_MIN, -0x1.0p63);
+ ASSERT_MIXED_SAME(-0x1.0p63, LLONG_MIN);
+ ASSERT_MIXED_ASCENDING(-0x1.0p63, LLONG_MIN + 1);
+
+ ASSERT_LT(-0x1.0000000000001p63, -0x1.0p63);
+ ASSERT_MIXED_ASCENDING(-0x1.0000000000001p63, LLONG_MIN);
+ ASSERT_MIXED_DESCENDING(-0x1.FFFFFFFFFFFFFp62, LLONG_MIN);
+
+ // Tests around LLONG_MAX
+ // Note LLONG_MAX cannot be exactly represented by a double, so the system
+ // rounds it to the nearest double, which is 2^63. This number, in turn is
+ // larger than the maximum representable as a long.
+ ASSERT_BIT_EQUALS(0x1.0p63, (double)LLONG_MAX);
+ ASSERT_MIXED_DESCENDING(0x1.0p63, LLONG_MAX);
+
+ // The largest value with an exactly long representation
+ ASSERT_EQ((int64_t)0x1.FFFFFFFFFFFFFp62, 0x7FFFFFFFFFFFFC00LL);
+ ASSERT_MIXED_SAME(0x1.FFFFFFFFFFFFFp62, 0x7FFFFFFFFFFFFC00LL);
+
+ ASSERT_MIXED_DESCENDING(0x1.FFFFFFFFFFFFFp62, 0x7FFFFFFFFFFFFB00LL);
+ ASSERT_MIXED_DESCENDING(0x1.FFFFFFFFFFFFFp62, 0x7FFFFFFFFFFFFBFFLL);
+ ASSERT_MIXED_ASCENDING(0x1.FFFFFFFFFFFFFp62, 0x7FFFFFFFFFFFFC01LL);
+ ASSERT_MIXED_ASCENDING(0x1.FFFFFFFFFFFFFp62, 0x7FFFFFFFFFFFFD00LL);
+
+ ASSERT_MIXED_ASCENDING(0x1.FFFFFFFFFFFFEp62, 0x7FFFFFFFFFFFFC00LL);
+
+ // Tests around MAX_SAFE_INTEGER
+ ASSERT_MIXED_SAME(0x1.FFFFFFFFFFFFFp52, 0x1FFFFFFFFFFFFFLL);
+ ASSERT_MIXED_DESCENDING(0x1.FFFFFFFFFFFFFp52, 0x1FFFFFFFFFFFFELL);
+ ASSERT_MIXED_ASCENDING(0x1.FFFFFFFFFFFFEp52, 0x1FFFFFFFFFFFFFLL);
+ ASSERT_MIXED_ASCENDING(0x1.FFFFFFFFFFFFFp52, 0x20000000000000LL);
+
+ // Tests around MIN_SAFE_INTEGER
+ ASSERT_MIXED_SAME(-0x1.FFFFFFFFFFFFFp52, -0x1FFFFFFFFFFFFFLL);
+ ASSERT_MIXED_ASCENDING(-0x1.FFFFFFFFFFFFFp52, -0x1FFFFFFFFFFFFELL);
+ ASSERT_MIXED_DESCENDING(-0x1.FFFFFFFFFFFFEp52, -0x1FFFFFFFFFFFFFLL);
+ ASSERT_MIXED_DESCENDING(-0x1.FFFFFFFFFFFFFp52, -0x20000000000000LL);
+
+ // Tests around zero.
+ ASSERT_MIXED_SAME(-0.0, 0LL);
+ ASSERT_MIXED_SAME(0.0, 0LL);
+
+ // The smallest representable positive value should be greater than zero
+ ASSERT_MIXED_DESCENDING(DBL_MIN, 0LL);
+ ASSERT_MIXED_ASCENDING(-DBL_MIN, 0LL);
+
+ // Note that 0x1.0p-1074 is a hex floating point literal representing the
+ // minimum subnormal number: <https://en.wikipedia.org/wiki/Denormal_number>.
+ double minSubNormal = 0x1.0p-1074;
+ ASSERT_MIXED_DESCENDING(minSubNormal, 0LL);
+ ASSERT_MIXED_ASCENDING(-minSubNormal, 0LL);
+
+ // Other sanity checks
+ ASSERT_MIXED_ASCENDING(0.5, 1LL);
+ ASSERT_MIXED_DESCENDING(0.5, 0LL);
+ ASSERT_MIXED_ASCENDING(1.5, 2LL);
+ ASSERT_MIXED_DESCENDING(1.5, 1LL);
+}
+
+} // namespace util
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/test/firebase/firestore/util/iterator_adaptors_test.cc b/Firestore/core/test/firebase/firestore/util/iterator_adaptors_test.cc
new file mode 100644
index 0000000..4cd44cc
--- /dev/null
+++ b/Firestore/core/test/firebase/firestore/util/iterator_adaptors_test.cc
@@ -0,0 +1,1277 @@
+/*
+ * Copyright 2005, 2018 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.
+ */
+
+#include "Firestore/core/src/firebase/firestore/util/iterator_adaptors.h"
+
+#include <iterator>
+#include <list>
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+#include <type_traits>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include "absl/base/macros.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+using std::unordered_map;
+using std::unordered_set;
+
+using firebase::firestore::util::deref_second_view;
+using firebase::firestore::util::deref_view;
+using firebase::firestore::util::iterator_first;
+using firebase::firestore::util::iterator_ptr;
+using firebase::firestore::util::iterator_second;
+using firebase::firestore::util::iterator_second_ptr;
+using firebase::firestore::util::key_view;
+using firebase::firestore::util::key_view_type;
+using firebase::firestore::util::make_iterator_first;
+using firebase::firestore::util::make_iterator_ptr;
+using firebase::firestore::util::make_iterator_second;
+using firebase::firestore::util::make_iterator_second_ptr;
+using firebase::firestore::util::value_view;
+using firebase::firestore::util::value_view_type;
+using testing::ElementsAre;
+using testing::Eq;
+using testing::IsEmpty;
+using testing::Not;
+using testing::Pair;
+using testing::Pointwise;
+using testing::SizeIs;
+
+namespace {
+
+const char* kFirst[] = {"foo", "bar"};
+int kSecond[] = {1, 2};
+const int kCount = ABSL_ARRAYSIZE(kFirst);
+
+template <typename T>
+struct IsConst : std::false_type {};
+template <typename T>
+struct IsConst<const T> : std::true_type {};
+template <typename T>
+struct IsConst<T&> : IsConst<T> {};
+
+class IteratorAdaptorTest : public testing::Test {
+ protected:
+ // Objects declared here can be used by all tests in the test case for Foo.
+
+ virtual void SetUp() {
+ ASSERT_EQ(ABSL_ARRAYSIZE(kFirst), ABSL_ARRAYSIZE(kSecond));
+ }
+
+ virtual void TearDown() {
+ }
+
+ template <typename T>
+ class InlineStorageIter : public std::iterator<std::input_iterator_tag, T> {
+ public:
+ T* operator->() const {
+ return get();
+ }
+ T& operator*() const {
+ return *get();
+ }
+
+ private:
+ T* get() const {
+ return &v_;
+ }
+ mutable T v_;
+ };
+
+ struct X {
+ int d;
+ };
+};
+
+TEST_F(IteratorAdaptorTest, HashMapFirst) {
+ // Adapts an iterator to return the first value of a unordered_map::iterator.
+ typedef unordered_map<std::string, int> my_container;
+ my_container values;
+ for (int i = 0; i < kCount; ++i) {
+ values[kFirst[i]] = kSecond[i];
+ }
+ for (iterator_first<my_container::iterator> it = values.begin();
+ it != values.end(); ++it) {
+ ASSERT_GT(it->length(), 0u);
+ }
+}
+
+TEST_F(IteratorAdaptorTest, IteratorPtrUniquePtr) {
+ // Tests iterator_ptr with a vector<unique_ptr<int>>.
+ typedef std::vector<std::unique_ptr<int>> my_container;
+ typedef iterator_ptr<my_container::iterator> my_iterator;
+ my_container values;
+ for (int i = 0; i < kCount; ++i) {
+ values.push_back(std::unique_ptr<int>(new int(kSecond[i])));
+ }
+ int i = 0;
+ for (my_iterator it = values.begin(); it != values.end(); ++it, ++i) {
+ int v = *it;
+ *it = v;
+ ASSERT_EQ(v, kSecond[i]);
+ }
+}
+
+TEST_F(IteratorAdaptorTest, IteratorFirstConvertsToConst) {
+ // Adapts an iterator to return the first value of a unordered_map::iterator.
+ typedef unordered_map<std::string, int> my_container;
+ my_container values;
+ for (int i = 0; i < kCount; ++i) {
+ values[kFirst[i]] = kSecond[i];
+ }
+ iterator_first<my_container::iterator> iter = values.begin();
+ iterator_first<my_container::const_iterator> c_iter = iter;
+ for (; c_iter != values.end(); ++c_iter) {
+ ASSERT_GT(c_iter->length(), 0u);
+ }
+}
+
+TEST_F(IteratorAdaptorTest, IteratorFirstConstEqNonConst) {
+ // verify that const and non-const iterators return the same reference.
+ typedef std::vector<std::pair<int, int>> my_container;
+ typedef iterator_first<my_container::iterator> my_iterator;
+ typedef iterator_first<my_container::const_iterator> my_const_iterator;
+ my_container values;
+ for (int i = 0; i < kCount; ++i) {
+ values.push_back(std::make_pair(i, i + 1));
+ }
+ my_iterator iter1 = values.begin();
+ const my_iterator iter2 = iter1;
+ my_const_iterator c_iter1 = iter1;
+ const my_const_iterator c_iter2 = c_iter1;
+ for (int i = 0; i < kCount; ++i) {
+ int& v1 = iter1[i];
+ int& v2 = iter2[i];
+ EXPECT_EQ(&v1, &values[i].first);
+ EXPECT_EQ(&v1, &v2);
+ const int& cv1 = c_iter1[i];
+ const int& cv2 = c_iter2[i];
+ EXPECT_EQ(&cv1, &values[i].first);
+ EXPECT_EQ(&cv1, &cv2);
+ }
+}
+
+TEST_F(IteratorAdaptorTest, HashMapSecond) {
+ // Adapts an iterator to return the second value of a unordered_map::iterator.
+ typedef unordered_map<std::string, int> my_container;
+ my_container values;
+ for (int i = 0; i < kCount; ++i) {
+ values[kFirst[i]] = kSecond[i];
+ }
+ for (iterator_second<my_container::iterator> it = values.begin();
+ it != values.end(); ++it) {
+ int v = *it;
+ ASSERT_GT(v, 0);
+ }
+}
+
+TEST_F(IteratorAdaptorTest, IteratorSecondConvertsToConst) {
+ // Adapts an iterator to return the first value of a unordered_map::iterator.
+ typedef unordered_map<std::string, int> my_container;
+ my_container values;
+ for (int i = 0; i < kCount; ++i) {
+ values[kFirst[i]] = kSecond[i];
+ }
+ iterator_second<my_container::iterator> iter = values.begin();
+ iterator_second<my_container::const_iterator> c_iter = iter;
+ for (; c_iter != values.end(); ++c_iter) {
+ int v = *c_iter;
+ ASSERT_GT(v, 0);
+ }
+}
+
+TEST_F(IteratorAdaptorTest, IteratorSecondConstEqNonConst) {
+ // verify that const and non-const iterators return the same reference.
+ typedef std::vector<std::pair<int, int>> my_container;
+ typedef iterator_second<my_container::iterator> my_iterator;
+ typedef iterator_second<my_container::const_iterator> my_const_iterator;
+ my_container values;
+ for (int i = 0; i < kCount; ++i) {
+ values.push_back(std::make_pair(i, i + 1));
+ }
+ my_iterator iter1 = values.begin();
+ const my_iterator iter2 = iter1;
+ my_const_iterator c_iter1 = iter1;
+ const my_const_iterator c_iter2 = c_iter1;
+ for (int i = 0; i < kCount; ++i) {
+ int& v1 = iter1[i];
+ int& v2 = iter2[i];
+ EXPECT_EQ(&v1, &values[i].second);
+ EXPECT_EQ(&v1, &v2);
+ const int& cv1 = c_iter1[i];
+ const int& cv2 = c_iter2[i];
+ EXPECT_EQ(&cv1, &values[i].second);
+ EXPECT_EQ(&cv1, &cv2);
+ }
+}
+
+TEST_F(IteratorAdaptorTest, IteratorSecondPtrConvertsToConst) {
+ // Adapts an iterator to return the first value of a unordered_map::iterator.
+ typedef unordered_map<std::string, int*> my_container;
+ my_container values;
+ for (int i = 0; i < kCount; ++i) {
+ values[kFirst[i]] = &kSecond[i];
+ }
+ iterator_second_ptr<my_container::iterator> iter = values.begin();
+ iterator_second_ptr<my_container::const_iterator> c_iter = iter;
+ for (; c_iter != values.end(); ++c_iter) {
+ int v = *c_iter;
+ ASSERT_GT(v, 0);
+ }
+}
+
+TEST_F(IteratorAdaptorTest, IteratorSecondPtrConstMap) {
+ typedef const std::map<int, int*> ConstMap;
+ ConstMap empty_map;
+
+ iterator_second_ptr<ConstMap::const_iterator> it(empty_map.begin());
+ ASSERT_TRUE(it == make_iterator_second_ptr(empty_map.end()));
+ if ((false)) {
+ // Just checking syntax/compilation/type-checking.
+ // iterator_second_ptr<ConstMap::const_iterator>::value_type* v1 = &*it;
+ iterator_second_ptr<ConstMap::const_iterator>::pointer v1 = &*it;
+ iterator_second_ptr<ConstMap::const_iterator>::pointer v2 =
+ &*it.operator->();
+ if (&v1 != &v2) v1 = v2;
+ }
+}
+
+TEST_F(IteratorAdaptorTest, IteratorPtrConst) {
+ // This is a regression test for a const-related bug that bit CL 47984515,
+ // where a client created an iterator whose value type was "T* const".
+ std::map<int*, int> m;
+ make_iterator_ptr(make_iterator_first(m.begin()));
+}
+
+TEST_F(IteratorAdaptorTest, IteratorSecondPtrConstEqNonConst) {
+ // verify that const and non-const iterators return the same reference.
+ typedef std::vector<std::pair<int, int*>> my_container;
+ typedef iterator_second_ptr<my_container::iterator> my_iterator;
+ typedef iterator_second_ptr<my_container::const_iterator> my_const_iterator;
+ my_container values;
+ int ivalues[kCount];
+ for (int i = 0; i < kCount; ++i) {
+ ivalues[i] = i;
+ values.push_back(std::make_pair(i, &ivalues[i]));
+ }
+ my_iterator iter1 = values.begin();
+ const my_iterator iter2 = iter1;
+ my_const_iterator c_iter1 = iter1;
+ const my_const_iterator c_iter2 = c_iter1;
+ for (int i = 0; i < kCount; ++i) {
+ int& v1 = iter1[i];
+ int& v2 = iter2[i];
+ EXPECT_EQ(&v1, &ivalues[i]);
+ EXPECT_EQ(&v1, &v2);
+ const int& cv1 = c_iter1[i];
+ const int& cv2 = c_iter2[i];
+ EXPECT_EQ(&cv1, &ivalues[i]);
+ EXPECT_EQ(&cv1, &cv2);
+ }
+}
+
+TEST_F(IteratorAdaptorTest, HashMapFirstConst) {
+ // Adapts an iterator to return the first value of a
+ // unordered_map::const_iterator.
+ typedef unordered_map<std::string, int> my_container;
+ my_container values;
+ for (int i = 0; i < kCount; ++i) {
+ values[kFirst[i]] = kSecond[i];
+ }
+ const unordered_map<std::string, int>* cvalues = &values;
+ for (iterator_first<my_container::const_iterator> it = cvalues->begin();
+ it != cvalues->end(); ++it) {
+ ASSERT_GT(it->length(), 0u);
+ }
+}
+
+TEST_F(IteratorAdaptorTest, ListFirst) {
+ // Adapts an iterator to return the first value of a list::iterator.
+ typedef std::pair<std::string, int> my_pair;
+ typedef std::list<my_pair> my_list;
+ my_list values;
+ for (int i = 0; i < kCount; ++i) {
+ values.push_back(my_pair(kFirst[i], kSecond[i]));
+ }
+ int i = 0;
+ for (iterator_first<my_list::iterator> it = values.begin();
+ it != values.end(); ++it) {
+ ASSERT_EQ(*it, kFirst[i++]);
+ }
+}
+
+TEST_F(IteratorAdaptorTest, ListSecondConst) {
+ // Adapts an iterator to return the second value from a list::const_iterator.
+ typedef std::pair<std::string, int> my_pair;
+ typedef std::list<my_pair> my_list;
+ my_list values;
+ for (int i = 0; i < kCount; ++i) {
+ values.push_back(my_pair(kFirst[i], kSecond[i]));
+ }
+ int i = 0;
+ const my_list* cvalues = &values;
+ for (iterator_second<my_list::const_iterator> it = cvalues->begin();
+ it != cvalues->end(); ++it) {
+ ASSERT_EQ(*it, kSecond[i++]);
+ }
+}
+
+TEST_F(IteratorAdaptorTest, VectorSecond) {
+ // Adapts an iterator to return the second value of a vector::iterator.
+ std::vector<std::pair<std::string, int>> values;
+ for (int i = 0; i < kCount; ++i) {
+ values.push_back(std::pair<std::string, int>(kFirst[i], kSecond[i]));
+ }
+ int i = 0;
+ for (iterator_second<std::vector<std::pair<std::string, int>>::iterator> it =
+ values.begin();
+ it != values.end(); ++it) {
+ ASSERT_EQ(*it, kSecond[i++]);
+ }
+}
+
+// Tests iterator_second_ptr with a map where values are regular pointers.
+TEST_F(IteratorAdaptorTest, HashMapSecondPtr) {
+ typedef unordered_map<std::string, int*> my_container;
+ typedef iterator_second_ptr<my_container::iterator> my_iterator;
+ my_container values;
+ for (int i = 0; i < kCount; ++i) {
+ values[kFirst[i]] = kSecond + i;
+ }
+ for (my_iterator it = values.begin(); it != values.end(); ++it) {
+ int v = *it;
+
+ // Make sure the iterator reference type is assignable ("int&" and not
+ // "const int&"). If it isn't, this becomes a compile-time error.
+ *it = v;
+
+ ASSERT_GT(v, 0);
+ }
+}
+
+// Tests iterator_second_ptr with a map where values are wrapped into
+// linked_ptr.
+TEST_F(IteratorAdaptorTest, HashMapSecondPtrLinkedPtr) {
+ typedef unordered_map<std::string, std::shared_ptr<int>> my_container;
+ typedef iterator_second_ptr<my_container::iterator> my_iterator;
+ my_container values;
+ for (int i = 0; i < kCount; ++i) {
+ values[kFirst[i]].reset(new int(kSecond[i]));
+ }
+ for (my_iterator it = values.begin(); it != values.end(); ++it) {
+ ASSERT_EQ(&*it, it.operator->());
+ int v = *it;
+ *it = v;
+ ASSERT_GT(v, 0);
+ }
+}
+
+// Tests iterator_ptr with a vector where values are regular pointers.
+TEST_F(IteratorAdaptorTest, IteratorPtrPtr) {
+ typedef std::vector<int*> my_container;
+ typedef iterator_ptr<my_container::iterator> my_iterator;
+ my_container values;
+ for (int i = 0; i < kCount; ++i) {
+ values.push_back(kSecond + i);
+ }
+ int i = 0;
+ for (my_iterator it = values.begin(); it != values.end(); ++it, ++i) {
+ int v = *it;
+ *it = v;
+ ASSERT_EQ(v, kSecond[i]);
+ }
+}
+
+TEST_F(IteratorAdaptorTest, IteratorPtrExplicitPtrType) {
+ struct A {};
+ struct B : A {};
+ std::vector<B*> v;
+ const std::vector<B*>& cv = v;
+ iterator_ptr<std::vector<B*>::iterator, A*> ip(v.begin());
+ iterator_ptr<std::vector<B*>::const_iterator, A*> cip(cv.begin());
+}
+
+TEST_F(IteratorAdaptorTest, IteratorPtrtConstEqNonConst) {
+ // verify that const and non-const iterators return the same reference.
+ typedef std::vector<int*> my_container;
+ typedef iterator_ptr<my_container::iterator> my_iterator;
+ typedef iterator_ptr<my_container::const_iterator> my_const_iterator;
+ my_container values;
+
+ for (int i = 0; i < kCount; ++i) {
+ values.push_back(kSecond + i);
+ }
+ my_iterator iter1 = values.begin();
+ const my_iterator iter2 = iter1;
+ my_const_iterator c_iter1 = iter1;
+ const my_const_iterator c_iter2 = iter1;
+ for (int i = 0; i < kCount; ++i) {
+ int& v1 = iter1[i];
+ int& v2 = iter2[i];
+ EXPECT_EQ(&v1, kSecond + i);
+ EXPECT_EQ(&v1, &v2);
+ const int& cv1 = c_iter1[i];
+ const int& cv2 = c_iter2[i];
+ EXPECT_EQ(&cv1, kSecond + i);
+ EXPECT_EQ(&cv1, &cv2);
+ }
+}
+
+// Tests iterator_ptr with a vector where values are wrapped into
+// std::shared_ptr.
+TEST_F(IteratorAdaptorTest, IteratorPtrLinkedPtr) {
+ typedef std::vector<std::shared_ptr<int>> my_container;
+ typedef iterator_ptr<my_container::iterator> my_iterator;
+ my_container values;
+ for (int i = 0; i < kCount; ++i) {
+ values.push_back(std::make_shared<int>(kSecond[i]));
+ }
+ int i = 0;
+ for (my_iterator it = values.begin(); it != values.end(); ++it, ++i) {
+ ASSERT_EQ(&*it, it.operator->());
+ int v = *it;
+ *it = v;
+ ASSERT_EQ(v, kSecond[i]);
+ }
+}
+
+TEST_F(IteratorAdaptorTest, IteratorPtrConvertsToConst) {
+ int value = 1;
+ std::vector<int*> values;
+ values.push_back(&value);
+ iterator_ptr<std::vector<int*>::iterator> iter = values.begin();
+ iterator_ptr<std::vector<int*>::const_iterator> c_iter = iter;
+ EXPECT_EQ(1, *c_iter);
+}
+
+TEST_F(IteratorAdaptorTest, IteratorFirstHasRandomAccessMethods) {
+ typedef std::vector<std::pair<std::string, int>> my_container;
+ typedef iterator_first<my_container::iterator> my_iterator;
+
+ my_container values;
+ for (int i = 0; i < kCount; ++i) {
+ values.push_back(std::pair<std::string, int>(kFirst[i], kSecond[i]));
+ }
+
+ my_iterator it1 = values.begin(), it2 = values.end();
+
+ EXPECT_EQ(kCount, it2 - it1);
+ EXPECT_TRUE(it1 < it2);
+ it1 += kCount;
+ EXPECT_TRUE(it1 == it2);
+ it1 -= kCount;
+ EXPECT_EQ(kFirst[0], *it1);
+ EXPECT_EQ(kFirst[1], *(it1 + 1));
+ EXPECT_TRUE(it1 == it2 - kCount);
+ EXPECT_TRUE(kCount + it1 == it2);
+ EXPECT_EQ(kFirst[1], it1[1]);
+ it2[-1] = "baz";
+ EXPECT_EQ("baz", values[kCount - 1].first);
+}
+
+TEST_F(IteratorAdaptorTest, IteratorSecondHasRandomAccessMethods) {
+ typedef std::vector<std::pair<std::string, int>> my_container;
+ typedef iterator_second<my_container::iterator> my_iterator;
+
+ my_container values;
+ for (int i = 0; i < kCount; ++i) {
+ values.push_back(std::pair<std::string, int>(kFirst[i], kSecond[i]));
+ }
+
+ my_iterator it1 = values.begin(), it2 = values.end();
+
+ EXPECT_EQ(kCount, it2 - it1);
+ EXPECT_TRUE(it1 < it2);
+ it1 += kCount;
+ EXPECT_TRUE(it1 == it2);
+ it1 -= kCount;
+ EXPECT_EQ(kSecond[0], *it1);
+ EXPECT_EQ(kSecond[1], *(it1 + 1));
+ EXPECT_TRUE(it1 == it2 - kCount);
+ EXPECT_TRUE(kCount + it1 == it2);
+ EXPECT_EQ(kSecond[1], it1[1]);
+ it2[-1] = 99;
+ EXPECT_EQ(99, values[kCount - 1].second);
+}
+
+TEST_F(IteratorAdaptorTest, IteratorSecondPtrHasRandomAccessMethods) {
+ typedef std::vector<std::pair<std::string, int*>> my_container;
+ typedef iterator_second_ptr<my_container::iterator> my_iterator;
+
+ ASSERT_GE(kCount, 2);
+ int value1 = 17;
+ int value2 = 99;
+ my_container values;
+ values.push_back(std::pair<std::string, int*>(kFirst[0], &value1));
+ values.push_back(std::pair<std::string, int*>(kFirst[1], &value2));
+
+ my_iterator it1 = values.begin(), it2 = values.end();
+
+ EXPECT_EQ(2, it2 - it1);
+ EXPECT_TRUE(it1 < it2);
+ it1 += 2;
+ EXPECT_TRUE(it1 == it2);
+ it1 -= 2;
+ EXPECT_EQ(17, *it1);
+ EXPECT_EQ(99, *(it1 + 1));
+ EXPECT_TRUE(it1 == it2 - 2);
+ EXPECT_TRUE(2 + it1 == it2);
+ EXPECT_EQ(99, it1[1]);
+ it2[-1] = 88;
+ EXPECT_EQ(88, value2);
+}
+
+TEST_F(IteratorAdaptorTest, IteratorPtrHasRandomAccessMethods) {
+ typedef std::vector<int*> my_container;
+ typedef iterator_ptr<my_container::iterator> my_iterator;
+
+ int value1 = 17;
+ int value2 = 99;
+ my_container values;
+ values.push_back(&value1);
+ values.push_back(&value2);
+
+ my_iterator it1 = values.begin(), it2 = values.end();
+
+ EXPECT_EQ(2, it2 - it1);
+ EXPECT_TRUE(it1 < it2);
+ it1 += 2;
+ EXPECT_TRUE(it1 == it2);
+ it1 -= 2;
+ EXPECT_EQ(17, *it1);
+ EXPECT_EQ(99, *(it1 + 1));
+ EXPECT_TRUE(it1 == it2 - 2);
+ EXPECT_TRUE(2 + it1 == it2);
+ EXPECT_EQ(99, it1[1]);
+ it2[-1] = 88;
+ EXPECT_EQ(88, value2);
+}
+
+class MyInputIterator
+ : public std::iterator<std::input_iterator_tag, const int*> {
+ public:
+ explicit MyInputIterator(int* x) : x_(x) {
+ }
+ const int* operator*() const {
+ return x_;
+ }
+ MyInputIterator& operator++() {
+ ++*x_;
+ return *this;
+ }
+
+ private:
+ int* x_;
+};
+
+TEST_F(IteratorAdaptorTest, IteratorPtrCanWrapInputIterator) {
+ int x = 0;
+ MyInputIterator it(&x);
+ iterator_ptr<MyInputIterator> it1(it);
+
+ EXPECT_EQ(0, *it1);
+ ++it1;
+ EXPECT_EQ(1, *it1);
+ ++it1;
+ EXPECT_EQ(2, *it1);
+ ++it1;
+}
+
+// Tests that a default-constructed adaptor is equal to an adaptor explicitly
+// constructed with a default underlying iterator.
+TEST_F(IteratorAdaptorTest, DefaultAdaptorConstructorUsesDefaultValue) {
+ iterator_first<std::pair<int, int>*> first_default;
+ iterator_first<std::pair<int, int>*> first_null(nullptr);
+ ASSERT_TRUE(first_default == first_null);
+
+ iterator_second<std::pair<int, int>*> second_default;
+ iterator_second<std::pair<int, int>*> second_null(nullptr);
+ ASSERT_TRUE(second_default == second_null);
+
+ iterator_second_ptr<std::pair<int, int*>*> second_ptr_default;
+ iterator_second_ptr<std::pair<int, int*>*> second_ptr_null(nullptr);
+ ASSERT_TRUE(second_ptr_default == second_ptr_null);
+
+ iterator_ptr<int**> ptr_default;
+ iterator_ptr<int**> ptr_null(nullptr);
+ ASSERT_TRUE(ptr_default == ptr_null);
+}
+
+// Non C++11 test.
+TEST_F(IteratorAdaptorTest, ValueView) {
+ typedef unordered_map<int, std::string> MapType;
+ MapType my_map;
+ my_map[0] = "a";
+ my_map[1] = "b";
+ my_map[2] = "c";
+ const MapType c_map(my_map);
+
+ std::set<std::string> vals;
+ auto view = value_view(c_map);
+ std::copy(view.begin(), view.end(), inserter(vals, vals.end()));
+
+ EXPECT_THAT(vals, ElementsAre("a", "b", "c"));
+}
+
+TEST_F(IteratorAdaptorTest, ValueView_Modify) {
+ typedef std::map<int, int> MapType;
+ MapType my_map;
+ my_map[0] = 0;
+ my_map[1] = 1;
+ my_map[2] = 2;
+ EXPECT_THAT(my_map, ElementsAre(Pair(0, 0), Pair(1, 1), Pair(2, 2)));
+
+ value_view_type<MapType>::type vv = value_view(my_map);
+ std::replace(vv.begin(), vv.end(), 2, 3);
+ std::replace(vv.begin(), vv.end(), 1, 2);
+
+ EXPECT_THAT(my_map, ElementsAre(Pair(0, 0), Pair(1, 2), Pair(2, 3)));
+}
+
+TEST_F(IteratorAdaptorTest, ValueViewOfValueView) {
+ typedef std::pair<int, std::string> pair_int_str;
+ typedef std::map<int, pair_int_str> map_int_pair_int_str;
+ map_int_pair_int_str my_map;
+ my_map[0] = std::make_pair(1, std::string("a"));
+ my_map[2] = std::make_pair(3, std::string("b"));
+ my_map[4] = std::make_pair(5, std::string("c"));
+
+ // This is basically typechecking of the generated views. So we generate the
+ // types and have the compiler verify the generated template instantiation.
+ typedef value_view_type<map_int_pair_int_str>::type
+ value_view_map_int_pair_int_str_type;
+
+ static_assert(
+ (std::is_same<pair_int_str,
+ value_view_map_int_pair_int_str_type::value_type>::value),
+ "value_view_value_type_");
+
+ typedef value_view_type<value_view_map_int_pair_int_str_type>::type
+ view_view_type;
+
+ static_assert((std::is_same<std::string, view_view_type::value_type>::value),
+ "view_view_type_");
+
+ value_view_map_int_pair_int_str_type vv = value_view(my_map);
+ view_view_type helper = value_view(vv);
+
+ EXPECT_THAT(std::set<std::string>(helper.begin(), helper.end()),
+ ElementsAre("a", "b", "c"));
+}
+
+TEST_F(IteratorAdaptorTest, ValueViewAndKeyViewCopy) {
+ std::map<int, std::string> my_map;
+ my_map[0] = "0";
+ my_map[1] = "1";
+ my_map[2] = "2";
+ std::set<int> keys;
+ std::set<std::string> vals;
+
+ auto kv = key_view(my_map);
+ std::copy(kv.begin(), kv.end(), inserter(keys, keys.end()));
+
+ auto vv = value_view(my_map);
+ std::copy(vv.begin(), vv.end(), inserter(vals, vals.end()));
+ EXPECT_THAT(keys, ElementsAre(0, 1, 2));
+ EXPECT_THAT(vals, ElementsAre("0", "1", "2"));
+}
+
+TEST_F(IteratorAdaptorTest, ValueViewAndKeyViewRangeBasedLoop) {
+ std::map<int, std::string> my_map;
+ my_map[0] = "0";
+ my_map[1] = "1";
+ my_map[2] = "2";
+ std::set<int> keys;
+ std::set<std::string> vals;
+ for (auto key : key_view(my_map)) {
+ keys.insert(key);
+ }
+ for (auto val : value_view(my_map)) {
+ vals.insert(val);
+ }
+ EXPECT_THAT(keys, ElementsAre(0, 1, 2));
+ EXPECT_THAT(vals, ElementsAre("0", "1", "2"));
+}
+
+template <int N, typename Value, typename Key>
+class FixedSizeContainer {
+ public:
+ // NOTE: the container does on purpose not define:
+ // reference, const_reference, pointer, const_pointer, size_type,
+ // difference_type, empty().
+ typedef std::pair<Value, Key> value_type;
+ typedef value_type* iterator;
+ typedef const value_type* const_iterator;
+
+ FixedSizeContainer() {
+ }
+ const_iterator begin() const {
+ return &values[0];
+ }
+ iterator begin() {
+ return &values[0];
+ }
+ const_iterator end() const {
+ return &values[N];
+ }
+ iterator end() {
+ return &values[N];
+ }
+ value_type at(int n) const {
+ return values[n];
+ }
+ value_type& operator[](int n) {
+ return values[n];
+ }
+ int size() const {
+ return N;
+ }
+
+ private:
+ static constexpr int kAllocatedSize = N ? N : 1;
+ value_type values[kAllocatedSize];
+ // NOTE: the container does on purpose not define:
+ // reference, const_reference, pointer, const_pointer, size_type,
+ // difference_type, empty().
+};
+
+TEST_F(IteratorAdaptorTest, ProvidesEmpty) {
+ {
+ FixedSizeContainer<0, int, int> container0;
+ EXPECT_TRUE(value_view(container0).empty());
+ FixedSizeContainer<1, int, int> container1;
+ EXPECT_FALSE(value_view(container1).empty());
+ }
+ {
+ std::map<int, int> container;
+ EXPECT_TRUE(value_view(container).empty());
+ container.insert(std::make_pair(0, 0));
+ EXPECT_FALSE(value_view(container).empty());
+ }
+}
+
+TEST_F(IteratorAdaptorTest, ValueViewWithPoorlyTypedHomeGrownContainer) {
+ FixedSizeContainer<3, int, std::string> container;
+ container[0] = std::make_pair(0, std::string("0"));
+ container[1] = std::make_pair(1, std::string("1"));
+ container[2] = std::make_pair(2, std::string("2"));
+ EXPECT_EQ(3, container.size());
+ EXPECT_EQ(container.at(0), std::make_pair(0, std::string("0")));
+ EXPECT_EQ(container.at(1), std::make_pair(1, std::string("1")));
+ EXPECT_EQ(container.at(2), std::make_pair(2, std::string("2")));
+ std::vector<int> keys;
+ std::vector<std::string> vals;
+
+ auto kv = key_view(container);
+ std::copy(kv.begin(), kv.end(), back_inserter(keys));
+ auto vv = value_view(container);
+ std::copy(vv.begin(), vv.end(), back_inserter(vals));
+ EXPECT_THAT(keys, ElementsAre(0, 1, 2));
+ EXPECT_THAT(vals, ElementsAre("0", "1", "2"));
+}
+
+TEST_F(IteratorAdaptorTest, ValueViewConstIterators) {
+ unordered_map<int, std::string> my_map;
+ my_map[0] = "a";
+ my_map[1] = "b";
+ my_map[2] = "c";
+
+ std::set<std::string> vals;
+ // iterator_view_helper defines cbegin() and cend(); we're not invoking the
+ // C++11 functions of the same name.
+ for (iterator_second<unordered_map<int, std::string>::const_iterator> it =
+ value_view(my_map).cbegin();
+ it != value_view(my_map).cend(); ++it) {
+ vals.insert(*it);
+ }
+
+ EXPECT_TRUE(vals.find("a") != vals.end());
+ EXPECT_TRUE(vals.find("b") != vals.end());
+ EXPECT_TRUE(vals.find("c") != vals.end());
+}
+
+TEST_F(IteratorAdaptorTest, ValueViewInConstContext) {
+ using firebase::firestore::util::internal::iterator_view_helper;
+ unordered_map<int, std::string> my_map;
+ my_map[0] = "a";
+ my_map[1] = "b";
+ my_map[2] = "c";
+
+ std::set<std::string> vals;
+ const iterator_view_helper<
+ unordered_map<int, std::string>,
+ iterator_second<unordered_map<int, std::string>::iterator>,
+ iterator_second<unordered_map<int, std::string>::const_iterator>>
+ const_view = value_view(my_map);
+ for (iterator_second<unordered_map<int, std::string>::const_iterator> it =
+ const_view.begin();
+ it != const_view.end(); ++it) {
+ vals.insert(*it);
+ }
+
+ EXPECT_TRUE(vals.find("a") != vals.end());
+ EXPECT_TRUE(vals.find("b") != vals.end());
+ EXPECT_TRUE(vals.find("c") != vals.end());
+}
+
+TEST_F(IteratorAdaptorTest, ConstValueView) {
+ unordered_map<int, std::string> my_map;
+ my_map[0] = "a";
+ my_map[1] = "b";
+ my_map[2] = "c";
+
+ const unordered_map<int, std::string>& const_map = my_map;
+
+ std::set<std::string> vals;
+ for (iterator_second<unordered_map<int, std::string>::const_iterator> it =
+ value_view(const_map).begin();
+ it != value_view(const_map).end(); ++it) {
+ vals.insert(*it);
+ }
+
+ EXPECT_TRUE(vals.find("a") != vals.end());
+ EXPECT_TRUE(vals.find("b") != vals.end());
+ EXPECT_TRUE(vals.find("c") != vals.end());
+}
+
+TEST_F(IteratorAdaptorTest, ConstValueViewConstIterators) {
+ unordered_map<int, std::string> my_map;
+ my_map[0] = "a";
+ my_map[1] = "b";
+ my_map[2] = "c";
+
+ const unordered_map<int, std::string>& const_map = my_map;
+
+ std::set<std::string> vals;
+ // iterator_view_helper defines cbegin() and cend(); we're not invoking the
+ // C++11 functions of the same name.
+ for (iterator_second<unordered_map<int, std::string>::const_iterator> it =
+ value_view(const_map).cbegin();
+ it != value_view(const_map).cend(); ++it) {
+ vals.insert(*it);
+ }
+
+ EXPECT_TRUE(vals.find("a") != vals.end());
+ EXPECT_TRUE(vals.find("b") != vals.end());
+ EXPECT_TRUE(vals.find("c") != vals.end());
+}
+
+TEST_F(IteratorAdaptorTest, ConstValueViewInConstContext) {
+ unordered_map<int, std::string> my_map;
+ my_map[0] = "a";
+ my_map[1] = "b";
+ my_map[2] = "c";
+
+ const unordered_map<int, std::string>& const_map = my_map;
+
+ std::set<std::string> vals;
+ const value_view_type<const unordered_map<int, std::string>>::type
+ const_view = value_view(const_map);
+ for (iterator_second<unordered_map<int, std::string>::const_iterator> it =
+ const_view.begin();
+ it != const_view.end(); ++it) {
+ vals.insert(*it);
+ }
+
+ EXPECT_TRUE(vals.find("a") != vals.end());
+ EXPECT_TRUE(vals.find("b") != vals.end());
+ EXPECT_TRUE(vals.find("c") != vals.end());
+}
+
+TEST_F(IteratorAdaptorTest, KeyView) {
+ unordered_map<int, std::string> my_map;
+ my_map[0] = "a";
+ my_map[1] = "b";
+ my_map[2] = "c";
+
+ std::set<int> vals;
+ for (iterator_first<unordered_map<int, std::string>::iterator> it =
+ key_view(my_map).begin();
+ it != key_view(my_map).end(); ++it) {
+ vals.insert(*it);
+ }
+
+ EXPECT_TRUE(vals.find(0) != vals.end());
+ EXPECT_TRUE(vals.find(1) != vals.end());
+ EXPECT_TRUE(vals.find(2) != vals.end());
+}
+
+TEST_F(IteratorAdaptorTest, KeyViewConstIterators) {
+ unordered_map<int, std::string> my_map;
+ my_map[0] = "a";
+ my_map[1] = "b";
+ my_map[2] = "c";
+
+ std::set<int> vals;
+ // iterator_view_helper defines cbegin() and cend(); we're not invoking the
+ // C++11 functions of the same name.
+ for (iterator_first<unordered_map<int, std::string>::const_iterator> it =
+ key_view(my_map).cbegin();
+ it != key_view(my_map).cend(); ++it) {
+ vals.insert(*it);
+ }
+
+ EXPECT_TRUE(vals.find(0) != vals.end());
+ EXPECT_TRUE(vals.find(1) != vals.end());
+ EXPECT_TRUE(vals.find(2) != vals.end());
+}
+
+TEST_F(IteratorAdaptorTest, KeyViewInConstContext) {
+ unordered_map<int, std::string> my_map;
+ my_map[0] = "a";
+ my_map[1] = "b";
+ my_map[2] = "c";
+
+ std::set<int> vals;
+ const key_view_type<unordered_map<int, std::string>>::type const_view =
+ key_view(my_map);
+ for (iterator_first<unordered_map<int, std::string>::const_iterator> it =
+ const_view.begin();
+ it != const_view.end(); ++it) {
+ vals.insert(*it);
+ }
+
+ EXPECT_TRUE(vals.find(0) != vals.end());
+ EXPECT_TRUE(vals.find(1) != vals.end());
+ EXPECT_TRUE(vals.find(2) != vals.end());
+}
+
+TEST_F(IteratorAdaptorTest, ConstKeyView) {
+ unordered_map<int, std::string> my_map;
+ my_map[0] = "a";
+ my_map[1] = "b";
+ my_map[2] = "c";
+
+ const unordered_map<int, std::string>& const_map = my_map;
+
+ std::set<int> vals;
+ for (iterator_first<unordered_map<int, std::string>::const_iterator> it =
+ key_view(const_map).begin();
+ it != key_view(const_map).end(); ++it) {
+ vals.insert(*it);
+ }
+
+ EXPECT_TRUE(vals.find(0) != vals.end());
+ EXPECT_TRUE(vals.find(1) != vals.end());
+ EXPECT_TRUE(vals.find(2) != vals.end());
+}
+
+TEST_F(IteratorAdaptorTest, ConstKeyViewConstIterators) {
+ unordered_map<int, std::string> my_map;
+ my_map[0] = "a";
+ my_map[1] = "b";
+ my_map[2] = "c";
+
+ const unordered_map<int, std::string>& const_map = my_map;
+
+ std::set<int> vals;
+ // iterator_view_helper defines cbegin() and cend(); we're not invoking the
+ // C++11 functions of the same name.
+ for (iterator_first<unordered_map<int, std::string>::const_iterator> it =
+ key_view(const_map).cbegin();
+ it != key_view(const_map).cend(); ++it) {
+ vals.insert(*it);
+ }
+
+ EXPECT_TRUE(vals.find(0) != vals.end());
+ EXPECT_TRUE(vals.find(1) != vals.end());
+ EXPECT_TRUE(vals.find(2) != vals.end());
+}
+
+TEST_F(IteratorAdaptorTest, ConstKeyViewInConstContext) {
+ unordered_map<int, std::string> my_map;
+ my_map[0] = "a";
+ my_map[1] = "b";
+ my_map[2] = "c";
+
+ const unordered_map<int, std::string>& const_map = my_map;
+
+ std::set<int> vals;
+ const key_view_type<const unordered_map<int, std::string>>::type const_view =
+ key_view(const_map);
+ for (iterator_first<unordered_map<int, std::string>::const_iterator> it =
+ const_view.begin();
+ it != const_view.end(); ++it) {
+ vals.insert(*it);
+ }
+
+ EXPECT_TRUE(vals.find(0) != vals.end());
+ EXPECT_TRUE(vals.find(1) != vals.end());
+ EXPECT_TRUE(vals.find(2) != vals.end());
+}
+
+TEST_F(IteratorAdaptorTest, IteratorViewHelperDefinesIterator) {
+ using firebase::firestore::util::internal::iterator_view_helper;
+ unordered_set<int> my_set;
+ my_set.insert(1);
+ my_set.insert(0);
+ my_set.insert(2);
+
+ typedef iterator_view_helper<unordered_set<int>, unordered_set<int>::iterator,
+ unordered_set<int>::const_iterator>
+ SetView;
+ SetView set_view(my_set);
+ unordered_set<int> vals;
+ for (SetView::iterator it = set_view.begin(); it != set_view.end(); ++it) {
+ vals.insert(*it);
+ }
+
+ EXPECT_TRUE(vals.find(0) != vals.end());
+ EXPECT_TRUE(vals.find(1) != vals.end());
+ EXPECT_TRUE(vals.find(2) != vals.end());
+}
+
+TEST_F(IteratorAdaptorTest, IteratorViewHelperDefinesConstIterator) {
+ using firebase::firestore::util::internal::iterator_view_helper;
+ unordered_set<int> my_set;
+ my_set.insert(1);
+ my_set.insert(0);
+ my_set.insert(2);
+
+ typedef iterator_view_helper<unordered_set<int>, unordered_set<int>::iterator,
+ unordered_set<int>::const_iterator>
+ SetView;
+ SetView set_view(my_set);
+ unordered_set<int> vals;
+ for (SetView::const_iterator it = set_view.begin(); it != set_view.end();
+ ++it) {
+ vals.insert(*it);
+ }
+
+ EXPECT_TRUE(vals.find(0) != vals.end());
+ EXPECT_TRUE(vals.find(1) != vals.end());
+ EXPECT_TRUE(vals.find(2) != vals.end());
+}
+
+TEST_F(IteratorAdaptorTest, ViewTypeParameterConstVsNonConst) {
+ typedef unordered_map<int, int> M;
+ M m;
+ const M& cm = m;
+
+ typedef key_view_type<M>::type KV;
+ typedef key_view_type<const M>::type KVC;
+ typedef value_view_type<M>::type VV;
+ typedef value_view_type<const M>::type VVC;
+
+ // key_view:
+ KV ABSL_ATTRIBUTE_UNUSED kv1 = key_view(m); // lvalue
+ KVC ABSL_ATTRIBUTE_UNUSED kv2 = key_view(m); // conversion to const
+ KVC ABSL_ATTRIBUTE_UNUSED kv3 = key_view(cm); // const from const lvalue
+ KVC ABSL_ATTRIBUTE_UNUSED kv4 = key_view(M()); // const from rvalue
+ // Direct initialization (without key_view function)
+ KV ABSL_ATTRIBUTE_UNUSED kv5(m);
+ KVC ABSL_ATTRIBUTE_UNUSED kv6(m);
+ KVC ABSL_ATTRIBUTE_UNUSED kv7(cm);
+ KVC ABSL_ATTRIBUTE_UNUSED kv8((M()));
+
+ // value_view:
+ VV ABSL_ATTRIBUTE_UNUSED vv1 = value_view(m); // lvalue
+ VVC ABSL_ATTRIBUTE_UNUSED vv2 = value_view(m); // conversion to const
+ VVC ABSL_ATTRIBUTE_UNUSED vv3 = value_view(cm); // const from const lvalue
+ VVC ABSL_ATTRIBUTE_UNUSED vv4 = value_view(M()); // const from rvalue
+ // Direct initialization (without value_view function)
+ VV ABSL_ATTRIBUTE_UNUSED vv5(m);
+ VVC ABSL_ATTRIBUTE_UNUSED vv6(m);
+ VVC ABSL_ATTRIBUTE_UNUSED vv7(cm);
+ VVC ABSL_ATTRIBUTE_UNUSED vv8((M()));
+}
+
+TEST_F(IteratorAdaptorTest, EmptyAndSize) {
+ {
+ FixedSizeContainer<0, int, std::string*> container;
+ EXPECT_TRUE(key_view(container).empty());
+ EXPECT_TRUE(value_view(container).empty());
+ EXPECT_EQ(0u, key_view(container).size());
+ EXPECT_EQ(0u, value_view(container).size());
+ }
+ {
+ FixedSizeContainer<2, int, std::string*> container;
+ EXPECT_FALSE(key_view(container).empty());
+ EXPECT_FALSE(value_view(container).empty());
+ EXPECT_EQ(2u, key_view(container).size());
+ EXPECT_EQ(2u, value_view(container).size());
+ }
+ {
+ std::map<std::string, std::string*> container;
+ EXPECT_TRUE(key_view(container).empty());
+ EXPECT_TRUE(value_view(container).empty());
+ EXPECT_EQ(0u, key_view(container).size());
+ EXPECT_EQ(0u, value_view(container).size());
+ std::string s0 = "s0";
+ std::string s1 = "s1";
+ container.insert(std::make_pair("0", &s0));
+ container.insert(std::make_pair("1", &s0));
+ EXPECT_FALSE(key_view(container).empty());
+ EXPECT_FALSE(value_view(container).empty());
+ EXPECT_EQ(2u, key_view(container).size());
+ EXPECT_EQ(2u, value_view(container).size());
+ }
+}
+
+TEST_F(IteratorAdaptorTest, View_IsEmpty) {
+ EXPECT_THAT(key_view(std::map<int, int>()), IsEmpty());
+ EXPECT_THAT(key_view(FixedSizeContainer<2, int, int>()), Not(IsEmpty()));
+}
+
+TEST_F(IteratorAdaptorTest, View_SizeIs) {
+ EXPECT_THAT(key_view(std::map<int, int>()), SizeIs(0));
+ EXPECT_THAT(key_view(FixedSizeContainer<2, int, int>()), SizeIs(2));
+}
+
+TEST_F(IteratorAdaptorTest, View_Pointwise) {
+ typedef std::map<int, std::string> MapType;
+ MapType my_map;
+ my_map[0] = "a";
+ my_map[1] = "b";
+ my_map[2] = "c";
+
+ std::vector<std::string> expected;
+ expected.push_back("a");
+ expected.push_back("b");
+ expected.push_back("c");
+
+ EXPECT_THAT(value_view(my_map), Pointwise(Eq(), expected));
+}
+
+TEST_F(IteratorAdaptorTest, DerefView) {
+ typedef std::vector<int*> ContainerType;
+ int v0 = 0;
+ int v1 = 1;
+ ContainerType c;
+ c.push_back(&v0);
+ c.push_back(&v1);
+ EXPECT_THAT(deref_view(c), ElementsAre(0, 1));
+ *deref_view(c).begin() = 2;
+ EXPECT_THAT(v0, 2);
+ EXPECT_THAT(deref_view(c), ElementsAre(2, 1));
+ const std::vector<int*> cc(c);
+ EXPECT_THAT(deref_view(cc), ElementsAre(2, 1));
+}
+
+TEST_F(IteratorAdaptorTest, ConstDerefView) {
+ typedef std::vector<const std::string*> ContainerType;
+ const std::string s0 = "0";
+ const std::string s1 = "1";
+ ContainerType c;
+ c.push_back(&s0);
+ c.push_back(&s1);
+ EXPECT_THAT(deref_view(c), ElementsAre("0", "1"));
+}
+
+TEST_F(IteratorAdaptorTest, DerefSecondView) {
+ typedef std::map<int, int*> ContainerType;
+ int v0 = 0;
+ int v1 = 1;
+ ContainerType c;
+ c.insert({10, &v0});
+ c.insert({11, &v1});
+ EXPECT_THAT(deref_second_view(c), ElementsAre(0, 1));
+ *deref_second_view(c).begin() = 2;
+ EXPECT_THAT(v0, 2);
+ EXPECT_THAT(deref_second_view(c), ElementsAre(2, 1));
+ const std::map<int, int*> cc(c);
+ EXPECT_THAT(deref_second_view(cc), ElementsAre(2, 1));
+}
+
+TEST_F(IteratorAdaptorTest, ConstDerefSecondView) {
+ typedef std::map<int, const std::string*> ContainerType;
+ const std::string s0 = "0";
+ const std::string s1 = "1";
+ ContainerType c;
+ c.insert({10, &s0});
+ c.insert({11, &s1});
+ EXPECT_THAT(deref_second_view(c), ElementsAre("0", "1"));
+}
+
+namespace {
+template <class T>
+std::vector<int> ToVec(const T& t) {
+ return std::vector<int>(t.begin(), t.end());
+}
+} // namespace
+
+TEST_F(IteratorAdaptorTest, ReverseView) {
+ using firebase::firestore::util::reversed_view;
+
+ int arr[] = {0, 1, 2, 3, 4, 5, 6};
+ int* arr_end = arr + sizeof(arr) / sizeof(arr[0]);
+ std::vector<int> vec(arr, arr_end);
+ const std::vector<int> cvec(arr, arr_end);
+
+ EXPECT_THAT(ToVec(reversed_view(vec)), ElementsAre(6, 5, 4, 3, 2, 1, 0));
+ EXPECT_THAT(ToVec(reversed_view(cvec)), ElementsAre(6, 5, 4, 3, 2, 1, 0));
+}
+
+TEST_F(IteratorAdaptorTest, IteratorPtrConstConversions) {
+ // Users depend on this. It has to keep working.
+ std::vector<int*> v;
+ const std::vector<int*>& cv = v;
+ EXPECT_TRUE(make_iterator_ptr(cv.end()) == make_iterator_ptr(v.end()));
+ EXPECT_FALSE(make_iterator_ptr(cv.end()) != make_iterator_ptr(v.end()));
+ // EXPECT_TRUE(make_iterator_ptr(v.end()) == make_iterator_ptr(cv.end()));
+ // EXPECT_FALSE(make_iterator_ptr(v.end()) != make_iterator_ptr(cv.end()));
+}
+
+TEST_F(IteratorAdaptorTest, IteratorPtrDeepConst) {
+ typedef std::vector<int*> PtrsToMutable;
+ typedef iterator_ptr<PtrsToMutable::const_iterator> ConstIter;
+ EXPECT_TRUE((std::is_same<ConstIter::reference, const int&>::value));
+ EXPECT_TRUE(IsConst<ConstIter::reference>::value);
+
+ typedef iterator_ptr<PtrsToMutable::iterator> Iter;
+ EXPECT_TRUE((std::is_same<Iter::reference, int&>::value));
+ EXPECT_FALSE(IsConst<Iter::reference>::value);
+}
+
+TEST_F(IteratorAdaptorTest, ReverseViewCxx11) {
+ using firebase::firestore::util::reversed_view;
+
+ int arr[] = {0, 1, 2, 3, 4, 5, 6};
+ int* arr_end = arr + sizeof(arr) / sizeof(arr[0]);
+ std::vector<int> vec(arr, arr_end);
+
+ // Try updates and demonstrate this work with C++11 for loops.
+ for (auto& i : reversed_view(vec)) ++i;
+ EXPECT_THAT(vec, ElementsAre(1, 2, 3, 4, 5, 6, 7));
+}
+
+TEST_F(IteratorAdaptorTest, BaseIterDanglingRefFirst) {
+ // Some iterators will hold 'on-board storage' for a synthesized value.
+ // We must take care not to pull our adapted reference from
+ // a temporary copy of the base iterator. See b/15113033.
+ typedef std::pair<X, int> Val;
+ InlineStorageIter<Val> iter;
+ iterator_first<InlineStorageIter<Val>> iter2(iter);
+ EXPECT_EQ(&iter2.base()->first, &*iter2);
+ EXPECT_EQ(&iter2.base()->first.d, &iter2->d);
+}
+
+TEST_F(IteratorAdaptorTest, BaseIterDanglingRefSecond) {
+ typedef std::pair<int, X> Val;
+ InlineStorageIter<Val> iter;
+ iterator_second<InlineStorageIter<Val>> iter2(iter);
+ EXPECT_EQ(&iter2.base()->second, &*iter2);
+ EXPECT_EQ(&iter2.base()->second.d, &iter2->d);
+}
+
+} // namespace
diff --git a/Firestore/core/test/firebase/firestore/util/log_test.cc b/Firestore/core/test/firebase/firestore/util/log_test.cc
new file mode 100644
index 0000000..973b174
--- /dev/null
+++ b/Firestore/core/test/firebase/firestore/util/log_test.cc
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2017 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.
+ */
+
+#include "Firestore/core/src/firebase/firestore/util/log.h"
+
+#include "gtest/gtest.h"
+
+namespace firebase {
+namespace firestore {
+namespace util {
+
+// When running against the log_apple.mm implementation (backed by FIRLogger)
+// this test can fail if debug_mode gets persisted in the user defaults. Check
+// for defaults getting in your way with
+//
+// defaults read firebase_firestore_util_log_apple_test
+//
+// You can fix it with:
+//
+// defaults write firebase_firestore_util_log_apple_test
+// /google/firebase/debug_mode NO
+TEST(Log, SetAndGet) {
+ LogSetLevel(kLogLevelVerbose);
+
+ LogSetLevel(kLogLevelDebug);
+ EXPECT_EQ(kLogLevelDebug, LogGetLevel());
+
+ LogSetLevel(kLogLevelInfo);
+ EXPECT_EQ(kLogLevelInfo, LogGetLevel());
+
+ LogSetLevel(kLogLevelWarning);
+ EXPECT_EQ(kLogLevelWarning, LogGetLevel());
+
+ LogSetLevel(kLogLevelError);
+ EXPECT_EQ(kLogLevelError, LogGetLevel());
+}
+
+TEST(Log, LogAllKinds) {
+ LogDebug("test debug logging %d", 1);
+ LogInfo("test info logging %d", 2);
+ LogWarning("test warning logging %d", 3);
+ LogError("test error logging %d", 4);
+ LogMessage(kLogLevelError, "test va-args %s %c %d", "abc", ':', 123);
+}
+
+} // namespace util
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/Port/ordered_code_test.cc b/Firestore/core/test/firebase/firestore/util/ordered_code_test.cc
index 0a339fc..fd2ce83 100644
--- a/Firestore/Port/ordered_code_test.cc
+++ b/Firestore/core/test/firebase/firestore/util/ordered_code_test.cc
@@ -14,63 +14,22 @@
* limitations under the License.
*/
-#include "Firestore/Port/ordered_code.h"
+#include "Firestore/core/src/firebase/firestore/util/ordered_code.h"
-// #include <float.h>
-// #include <stddef.h>
#include <iostream>
#include <limits>
-#include "base/logging.h"
-#include "testing/base/public/gunit.h"
-#include <leveldb/db.h>
-#include "util/random/acmrandom.h"
-
-using Firestore::OrderedCode;
-using leveldb::Slice;
-
-// Make Slices writeable to ostream, making all the CHECKs happy below.
-namespace {
-void WritePadding(std::ostream& o, size_t pad) {
- char fill_buf[32];
- memset(fill_buf, o.fill(), sizeof(fill_buf));
- while (pad) {
- size_t n = std::min(pad, sizeof(fill_buf));
- o.write(fill_buf, n);
- pad -= n;
- }
-}
-} // namespace
-
-namespace leveldb {
-
-std::ostream& operator<<(std::ostream& o, const Slice slice) {
- std::ostream::sentry sentry(o);
- if (sentry) {
- size_t lpad = 0;
- size_t rpad = 0;
- if (o.width() > slice.size()) {
- size_t pad = o.width() - slice.size();
- if ((o.flags() & o.adjustfield) == o.left) {
- rpad = pad;
- } else {
- lpad = pad;
- }
- }
- if (lpad) WritePadding(o, lpad);
- o.write(slice.data(), slice.size());
- if (rpad) WritePadding(o, rpad);
- o.width(0);
- }
- return o;
-}
+#include "Firestore/core/src/firebase/firestore/util/secure_random.h"
+#include "gtest/gtest.h"
-} // namespace leveldb
+namespace firebase {
+namespace firestore {
+namespace util {
-static std::string RandomString(ACMRandom* rnd, int len) {
+static std::string RandomString(SecureRandom* rnd, int len) {
std::string x;
for (int i = 0; i < len; i++) {
- x += rnd->Uniform(256);
+ x += static_cast<char>(rnd->Uniform(256));
}
return x;
}
@@ -82,7 +41,7 @@ static std::string RandomString(ACMRandom* rnd, int len) {
template <typename T>
static void OCWriteIncreasing(std::string* dest, const T& val);
template <typename T>
-static bool OCReadIncreasing(Slice* src, T* result);
+static bool OCReadIncreasing(absl::string_view* src, T* result);
// Read/WriteIncreasing<std::string>
template <>
@@ -90,7 +49,8 @@ void OCWriteIncreasing<std::string>(std::string* dest, const std::string& val) {
OrderedCode::WriteString(dest, val);
}
template <>
-bool OCReadIncreasing<std::string>(Slice* src, std::string* result) {
+bool OCReadIncreasing<std::string>(absl::string_view* src,
+ std::string* result) {
return OrderedCode::ReadString(src, result);
}
@@ -100,7 +60,7 @@ void OCWriteIncreasing<uint64_t>(std::string* dest, const uint64_t& val) {
OrderedCode::WriteNumIncreasing(dest, val);
}
template <>
-bool OCReadIncreasing<uint64_t>(Slice* src, uint64_t* result) {
+bool OCReadIncreasing<uint64_t>(absl::string_view* src, uint64_t* result) {
return OrderedCode::ReadNumIncreasing(src, result);
}
@@ -112,12 +72,13 @@ void OCWriteIncreasing<int64_t>(std::string* dest, const int64_t& val) {
OrderedCode::WriteSignedNumIncreasing(dest, val);
}
template <>
-bool OCReadIncreasing<int64_t>(Slice* src, int64_t* result) {
+bool OCReadIncreasing<int64_t>(absl::string_view* src, int64_t* result) {
return OrderedCode::ReadSignedNumIncreasing(src, result);
}
template <typename T>
std::string OCWrite(T val, Direction direction) {
+ EXPECT_EQ(INCREASING, direction); // DECREASING never implemented.
std::string result;
OCWriteIncreasing<T>(&result, val);
return result;
@@ -125,11 +86,13 @@ std::string OCWrite(T val, Direction direction) {
template <typename T>
void OCWriteToString(std::string* result, T val, Direction direction) {
+ EXPECT_EQ(INCREASING, direction); // DECREASING never implemented.
OCWriteIncreasing<T>(result, val);
}
template <typename T>
-bool OCRead(Slice* s, T* val, Direction direction) {
+bool OCRead(absl::string_view* s, T* val, Direction direction) {
+ EXPECT_EQ(INCREASING, direction); // DECREASING never implemented.
return OCReadIncreasing<T>(s, val);
}
@@ -139,16 +102,16 @@ bool OCRead(Slice* s, T* val, Direction direction) {
template <typename T>
static T TestRead(Direction d, const std::string& a) {
// gracefully reject any proper prefix of an encoding
- for (int i = 0; i < a.size() - 1; ++i) {
- Slice s(a.data(), i);
- CHECK(!OCRead<T>(&s, NULL, d));
- CHECK_EQ(s, a.substr(0, i));
+ for (size_t i = 0; i < a.size() - 1; ++i) {
+ absl::string_view s(a.data(), i);
+ EXPECT_TRUE(!OCRead<T>(&s, NULL, d));
+ EXPECT_EQ(s, a.substr(0, i));
}
- Slice s(a);
+ absl::string_view s(a);
T v;
- CHECK(OCRead<T>(&s, &v, d));
- CHECK(s.empty());
+ EXPECT_TRUE(OCRead<T>(&s, &v, d));
+ EXPECT_TRUE(s.empty());
return v;
}
@@ -166,12 +129,13 @@ static void TestWriteAppends(Direction d, T first, U second) {
std::string encoded_first_only = encoded;
OCWriteToString<U>(&encoded, second, d);
EXPECT_NE(encoded, encoded_first_only);
- EXPECT_TRUE(Slice(encoded).starts_with(encoded_first_only));
+ EXPECT_EQ(absl::string_view(encoded).substr(0, encoded_first_only.length()),
+ encoded_first_only);
}
template <typename T>
static void TestNumbers(T multiplier) {
- for (int j = 0; j < 2; ++j) {
+ for (int j = 0; j < 1; ++j) {
const Direction d = static_cast<Direction>(j);
// first test powers of 2 (and nearby numbers)
@@ -180,19 +144,23 @@ static void TestNumbers(T multiplier) {
TestWriteRead(d, multiplier * x);
if (x != std::numeric_limits<T>::max()) {
TestWriteRead(d, multiplier * (x + 1));
- } else if (multiplier < 0 && multiplier == -1) {
+ } else if (multiplier < 0 && static_cast<int64_t>(multiplier) == -1) {
TestWriteRead(d, -x - 1);
}
}
- ACMRandom rnd(301);
+ SecureRandom rnd; // Generate 32bit pseudo-random integer.
for (int bits = 1; bits <= std::numeric_limits<T>().digits; ++bits) {
// test random non-negative numbers with given number of significant bits
const uint64_t mask = (~0ULL) >> (64 - bits);
for (int i = 0; i < 1000; i++) {
- T x = rnd.Next64() & mask;
+ T x = static_cast<T>((static_cast<uint64_t>(rnd()) << 32 |
+ static_cast<uint64_t>(rnd())) &
+ mask);
TestWriteRead(d, multiplier * x);
- T y = rnd.Next64() & mask;
+ T y = static_cast<T>((static_cast<uint64_t>(rnd()) << 32 |
+ static_cast<uint64_t>(rnd())) &
+ mask);
TestWriteAppends(d, multiplier * x, multiplier * y);
}
}
@@ -200,7 +168,8 @@ static void TestNumbers(T multiplier) {
}
// Return true iff 'a' is "before" 'b' according to 'direction'
-static bool CompareStrings(const std::string& a, const std::string& b,
+static bool CompareStrings(const std::string& a,
+ const std::string& b,
Direction d) {
return (INCREASING == d) ? (a < b) : (b < a);
}
@@ -216,12 +185,12 @@ static void TestNumberOrdering() {
std::string str = OCWrite<T>(num, d);
std::string strplus1 = OCWrite<T>(num + 1, d);
- CHECK(CompareStrings(strminus1, str, d));
- CHECK(CompareStrings(str, strplus1, d));
+ EXPECT_TRUE(CompareStrings(strminus1, str, d));
+ EXPECT_TRUE(CompareStrings(str, strplus1, d));
// Compare 'str' with 'laststr'. When we approach 0, 'laststr' is
// not necessarily before 'strminus1'.
- CHECK(CompareStrings(laststr, str, d));
+ EXPECT_TRUE(CompareStrings(laststr, str, d));
laststr = str;
}
@@ -234,46 +203,46 @@ static void TestNumberOrdering() {
std::string str = OCWrite<T>(num, d);
std::string strplus1 = OCWrite<T>(num + 1, d);
- CHECK(CompareStrings(strminus1, str, d));
- CHECK(CompareStrings(str, strplus1, d));
+ EXPECT_TRUE(CompareStrings(strminus1, str, d));
+ EXPECT_TRUE(CompareStrings(str, strplus1, d));
// Compare 'str' with 'laststr'.
- CHECK(CompareStrings(laststr, str, d));
+ EXPECT_TRUE(CompareStrings(laststr, str, d));
laststr = str;
}
}
// Helper routine for testing TEST_SkipToNextSpecialByte
-static int FindSpecial(const std::string& x) {
+static size_t FindSpecial(const std::string& x) {
const char* p = x.data();
const char* limit = p + x.size();
const char* result = OrderedCode::TEST_SkipToNextSpecialByte(p, limit);
- return result - p;
+ return static_cast<size_t>(result - p);
}
TEST(OrderedCode, SkipToNextSpecialByte) {
- for (int len = 0; len < 256; len++) {
- ACMRandom rnd(301);
+ for (size_t len = 0; len < 256; len++) {
+ SecureRandom rnd;
std::string x;
while (x.size() < len) {
- char c = 1 + rnd.Uniform(254);
+ char c = 1 + static_cast<char>(rnd.Uniform(254));
ASSERT_NE(c, 0);
ASSERT_NE(c, 255);
x += c; // No 0 bytes, no 255 bytes
}
EXPECT_EQ(FindSpecial(x), x.size());
- for (int special_pos = 0; special_pos < len; special_pos++) {
+ for (size_t special_pos = 0; special_pos < len; special_pos++) {
for (int special_test = 0; special_test < 2; special_test++) {
- const char special_byte = (special_test == 0) ? 0 : 255;
+ const char special_byte = (special_test == 0) ? 0 : '\xff';
std::string y = x;
y[special_pos] = special_byte;
EXPECT_EQ(FindSpecial(y), special_pos);
if (special_pos < 16) {
// Add some special bytes after the one at special_pos to make sure
// we still return the earliest special byte in the string
- for (int rest = special_pos + 1; rest < len; rest++) {
+ for (size_t rest = special_pos + 1; rest < len; rest++) {
if (rnd.OneIn(3)) {
- y[rest] = rnd.OneIn(2) ? 0 : 255;
+ y[rest] = rnd.OneIn(2) ? 0 : '\xff';
EXPECT_EQ(FindSpecial(y), special_pos);
}
}
@@ -297,9 +266,9 @@ TEST(OrderedCode, ExhaustiveFindSpecial) {
for (int b0 = 0; b0 < 256; b0++) {
for (int b1 = 0; b1 < 256; b1++) {
for (int b2 = 0; b2 < 256; b2++) {
- buf[start_offset + 0] = b0;
- buf[start_offset + 1] = b1;
- buf[start_offset + 2] = b2;
+ buf[start_offset + 0] = static_cast<char>(b0);
+ buf[start_offset + 1] = static_cast<char>(b1);
+ buf[start_offset + 2] = static_cast<char>(b2);
char* expected;
if (b0 == 0 || b0 == 255) {
expected = &buf[start_offset];
@@ -320,16 +289,22 @@ TEST(OrderedCode, ExhaustiveFindSpecial) {
EXPECT_EQ(count, 256 * 256 * 256 * 2);
}
-TEST(Uint64, EncodeDecode) { TestNumbers<uint64_t>(1); }
+TEST(OrderedCodeUint64, EncodeDecode) {
+ TestNumbers<uint64_t>(1);
+}
-TEST(Uint64, Ordering) { TestNumberOrdering<uint64_t>(); }
+TEST(OrderedCodeUint64, Ordering) {
+ TestNumberOrdering<uint64_t>();
+}
-TEST(Int64, EncodeDecode) {
+TEST(OrderedCodeInt64, EncodeDecode) {
TestNumbers<int64_t>(1);
TestNumbers<int64_t>(-1);
}
-TEST(Int64, Ordering) { TestNumberOrdering<int64_t>(); }
+TEST(OrderedCodeInt64, Ordering) {
+ TestNumberOrdering<int64_t>();
+}
// Returns the bitwise complement of s.
static inline std::string StrNot(const std::string& s) {
@@ -340,7 +315,7 @@ static inline std::string StrNot(const std::string& s) {
template <typename T>
static void TestInvalidEncoding(Direction d, const std::string& s) {
- Slice p(s);
+ absl::string_view p(s);
EXPECT_FALSE(OCRead<T>(&p, static_cast<T*>(NULL), d));
EXPECT_EQ(s, p);
}
@@ -363,22 +338,22 @@ TEST(OrderedCodeInvalidEncodingsTest, NonCanonical) {
// and thus should be avoided to not mess up the string ordering of
// encodings.
- ACMRandom rnd(301);
+ SecureRandom rnd;
for (int n = 2; n <= 9; ++n) {
// The zero in non_minimal[1] is "redundant".
std::string non_minimal =
std::string(1, n - 1) + std::string(1, 0) + RandomString(&rnd, n - 2);
- EXPECT_EQ(n, non_minimal.length());
+ EXPECT_EQ(static_cast<size_t>(n), non_minimal.length());
EXPECT_NE(OCWrite<uint64_t>(0, INCREASING), non_minimal);
- if (DEBUG_MODE) {
- Slice s(non_minimal);
- EXPECT_DEATH_IF_SUPPORTED(OrderedCode::ReadNumIncreasing(&s, NULL),
- "ssertion failed");
- } else {
- TestRead<uint64_t>(INCREASING, non_minimal);
- }
+
+#if defined(NDEBUG)
+ TestRead<uint64_t>(INCREASING, non_minimal);
+#else // defined(NDEBUG)
+ absl::string_view s(non_minimal);
+ EXPECT_ANY_THROW(OrderedCode::ReadNumIncreasing(&s, NULL));
+#endif // defined(NDEBUG)
}
for (int n = 2; n <= 10; ++n) {
@@ -387,31 +362,32 @@ TEST(OrderedCodeInvalidEncodingsTest, NonCanonical) {
std::string(n / 8, 0xff) + std::string(1, 0xff << (8 - (n % 8)));
// There are more than 7 zero bits between header bits and "payload".
std::string non_minimal =
- header + std::string(1, rnd.Uniform(256) & ~*header.rbegin()) +
- RandomString(&rnd, n - header.length() - 1);
- EXPECT_EQ(n, non_minimal.length());
+ header +
+ std::string(1,
+ static_cast<char>(rnd.Uniform(256)) & ~*header.rbegin()) +
+ RandomString(&rnd, n - static_cast<int>(header.length()) - 1);
+ EXPECT_EQ(static_cast<size_t>(n), non_minimal.length());
EXPECT_NE(OCWrite<int64_t>(0, INCREASING), non_minimal);
- if (DEBUG_MODE) {
- Slice s(non_minimal);
- EXPECT_DEATH_IF_SUPPORTED(OrderedCode::ReadSignedNumIncreasing(&s, NULL),
- "ssertion failed")
- << n;
- s = non_minimal;
- } else {
- TestRead<int64_t>(INCREASING, non_minimal);
- }
+
+#if defined(NDEBUG)
+ TestRead<int64_t>(INCREASING, non_minimal);
+#else // defined(NDEBUG)
+ absl::string_view s(non_minimal);
+ EXPECT_ANY_THROW(OrderedCode::ReadSignedNumIncreasing(&s, NULL));
+ s = non_minimal;
+#endif // defined(NDEBUG)
}
}
// ---------------------------------------------------------------------
// Strings
-TEST(String, Infinity) {
+TEST(OrderedCodeString, Infinity) {
const std::string value("\xff\xff foo");
bool is_inf;
std::string encoding, parsed;
- Slice s;
+ absl::string_view s;
// Check encoding/decoding of "infinity" for ascending order
encoding.clear();
@@ -419,11 +395,11 @@ TEST(String, Infinity) {
encoding.push_back('a');
s = encoding;
EXPECT_TRUE(OrderedCode::ReadInfinity(&s));
- EXPECT_EQ(1, s.size());
+ EXPECT_EQ(1u, s.size());
s = encoding;
is_inf = false;
EXPECT_TRUE(OrderedCode::ReadStringOrInfinity(&s, NULL, &is_inf));
- EXPECT_EQ(1, s.size());
+ EXPECT_EQ(1u, s.size());
EXPECT_TRUE(is_inf);
// Check ReadStringOrInfinity() can parse ordinary strings
@@ -434,14 +410,14 @@ TEST(String, Infinity) {
is_inf = false;
parsed.clear();
EXPECT_TRUE(OrderedCode::ReadStringOrInfinity(&s, &parsed, &is_inf));
- EXPECT_EQ(1, s.size());
+ EXPECT_EQ(1u, s.size());
EXPECT_FALSE(is_inf);
EXPECT_EQ(value, parsed);
}
-TEST(String, EncodeDecode) {
- ACMRandom rnd(301);
- for (int i = 0; i < 2; ++i) {
+TEST(OrderedCodeString, EncodeDecode) {
+ SecureRandom rnd;
+ for (int i = 0; i < 1; ++i) {
const Direction d = static_cast<Direction>(i);
for (int len = 0; len < 256; len++) {
@@ -457,48 +433,48 @@ TEST(String, EncodeDecode) {
OCWriteToString<std::string>(&out, b, d);
std::string a2, b2, dummy;
- Slice s = out;
- Slice s2 = out;
- CHECK(OCRead<std::string>(&s, &a2, d));
- CHECK(OCRead<std::string>(&s2, NULL, d));
- CHECK_EQ(s, s2);
-
- CHECK(OCRead<std::string>(&s, &b2, d));
- CHECK(OCRead<std::string>(&s2, NULL, d));
- CHECK_EQ(s, s2);
-
- CHECK(!OCRead<std::string>(&s, &dummy, d));
- CHECK(!OCRead<std::string>(&s2, NULL, d));
- CHECK_EQ(a, a2);
- CHECK_EQ(b, b2);
- CHECK(s.empty());
- CHECK(s2.empty());
+ absl::string_view s = out;
+ absl::string_view s2 = out;
+ EXPECT_TRUE(OCRead<std::string>(&s, &a2, d));
+ EXPECT_TRUE(OCRead<std::string>(&s2, NULL, d));
+ EXPECT_EQ(s, s2);
+
+ EXPECT_TRUE(OCRead<std::string>(&s, &b2, d));
+ EXPECT_TRUE(OCRead<std::string>(&s2, NULL, d));
+ EXPECT_EQ(s, s2);
+
+ EXPECT_TRUE(!OCRead<std::string>(&s, &dummy, d));
+ EXPECT_TRUE(!OCRead<std::string>(&s2, NULL, d));
+ EXPECT_EQ(a, a2);
+ EXPECT_EQ(b, b2);
+ EXPECT_TRUE(s.empty());
+ EXPECT_TRUE(s2.empty());
}
}
}
}
// 'str' is a static C-style string that may contain '\0'
-#define STATIC_STR(str) Slice((str), sizeof(str) - 1)
+#define STATIC_STR(str) absl::string_view((str), sizeof(str) - 1)
-static std::string EncodeStringIncreasing(Slice value) {
+static std::string EncodeStringIncreasing(absl::string_view value) {
std::string encoded;
OrderedCode::WriteString(&encoded, value);
return encoded;
}
-TEST(String, Increasing) {
+TEST(OrderedCodeString, Increasing) {
// Here are a series of strings in non-decreasing order, including
// consecutive strings such that the second one is equal to, a proper
// prefix of, or has the same length as the first one. Most also contain
// the special escaping characters '\x00' and '\xff'.
- ASSERT_EQ(EncodeStringIncreasing(STATIC_STR("")),
+ EXPECT_EQ(EncodeStringIncreasing(STATIC_STR("")),
EncodeStringIncreasing(STATIC_STR("")));
ASSERT_LT(EncodeStringIncreasing(STATIC_STR("")),
EncodeStringIncreasing(STATIC_STR("\x00")));
- ASSERT_EQ(EncodeStringIncreasing(STATIC_STR("\x00")),
+ EXPECT_EQ(EncodeStringIncreasing(STATIC_STR("\x00")),
EncodeStringIncreasing(STATIC_STR("\x00")));
ASSERT_LT(EncodeStringIncreasing(STATIC_STR("\x00")),
@@ -507,7 +483,7 @@ TEST(String, Increasing) {
ASSERT_LT(EncodeStringIncreasing(STATIC_STR("\x01")),
EncodeStringIncreasing(STATIC_STR("a")));
- ASSERT_EQ(EncodeStringIncreasing(STATIC_STR("a")),
+ EXPECT_EQ(EncodeStringIncreasing(STATIC_STR("a")),
EncodeStringIncreasing(STATIC_STR("a")));
ASSERT_LT(EncodeStringIncreasing(STATIC_STR("a")),
@@ -526,3 +502,7 @@ TEST(String, Increasing) {
OrderedCode::WriteInfinity(&infinity);
ASSERT_LT(EncodeStringIncreasing(std::string(1 << 20, '\xff')), infinity);
}
+
+} // namespace util
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/test/firebase/firestore/util/secure_random_test.cc b/Firestore/core/test/firebase/firestore/util/secure_random_test.cc
index f96f3de..b0dfdd6 100644
--- a/Firestore/core/test/firebase/firestore/util/secure_random_test.cc
+++ b/Firestore/core/test/firebase/firestore/util/secure_random_test.cc
@@ -30,3 +30,28 @@ TEST(SecureRandomTest, ResultsAreBounded) {
EXPECT_LE(value, rng.max());
}
}
+
+TEST(SecureRandomTest, Uniform) {
+ SecureRandom rng;
+ int count[10] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+
+ for (int i = 0; i < 1000; i++) {
+ count[rng.Uniform(10)]++;
+ }
+ for (int i = 0; i < 10; i++) {
+ // Practically, each count should be close to 100.
+ EXPECT_LT(50, count[i]) << count[i];
+ }
+}
+
+TEST(SecureRandomTest, OneIn) {
+ SecureRandom rng;
+ int count = 0;
+
+ for (int i = 0; i < 1000; i++) {
+ if (rng.OneIn(10)) count++;
+ }
+ // Practically, count should be close to 100.
+ EXPECT_LT(50, count) << count;
+ EXPECT_GT(150, count) << count;
+}
diff --git a/Firestore/core/test/firebase/firestore/util/status_test.cc b/Firestore/core/test/firebase/firestore/util/status_test.cc
new file mode 100644
index 0000000..e5cb8dc
--- /dev/null
+++ b/Firestore/core/test/firebase/firestore/util/status_test.cc
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2015, 2018 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.
+ */
+
+#include "Firestore/core/src/firebase/firestore/util/status.h"
+
+#include "Firestore/core/test/firebase/firestore/util/status_test_util.h"
+#include "gtest/gtest.h"
+
+namespace firebase {
+namespace firestore {
+namespace util {
+
+TEST(Status, OK) {
+ EXPECT_EQ(Status::OK().code(), FirestoreErrorCode::Ok);
+ EXPECT_EQ(Status::OK().error_message(), "");
+ EXPECT_OK(Status::OK());
+ ASSERT_OK(Status::OK());
+ STATUS_CHECK_OK(Status::OK());
+ EXPECT_EQ(Status::OK(), Status());
+ Status s;
+ EXPECT_TRUE(s.ok());
+}
+
+TEST(DeathStatus, CheckOK) {
+ Status status(FirestoreErrorCode::InvalidArgument, "Invalid");
+ ASSERT_ANY_THROW(STATUS_CHECK_OK(status));
+}
+
+TEST(Status, Set) {
+ Status status;
+ status = Status(FirestoreErrorCode::Cancelled, "Error message");
+ EXPECT_EQ(status.code(), FirestoreErrorCode::Cancelled);
+ EXPECT_EQ(status.error_message(), "Error message");
+}
+
+TEST(Status, Copy) {
+ Status a(FirestoreErrorCode::InvalidArgument, "Invalid");
+ Status b(a);
+ ASSERT_EQ(a.ToString(), b.ToString());
+}
+
+TEST(Status, Assign) {
+ Status a(FirestoreErrorCode::InvalidArgument, "Invalid");
+ Status b;
+ b = a;
+ ASSERT_EQ(a.ToString(), b.ToString());
+}
+
+TEST(Status, Update) {
+ Status s;
+ s.Update(Status::OK());
+ ASSERT_TRUE(s.ok());
+ Status a(FirestoreErrorCode::InvalidArgument, "Invalid");
+ s.Update(a);
+ ASSERT_EQ(s.ToString(), a.ToString());
+ Status b(FirestoreErrorCode::Internal, "Internal");
+ s.Update(b);
+ ASSERT_EQ(s.ToString(), a.ToString());
+ s.Update(Status::OK());
+ ASSERT_EQ(s.ToString(), a.ToString());
+ ASSERT_FALSE(s.ok());
+}
+
+TEST(Status, EqualsOK) {
+ ASSERT_EQ(Status::OK(), Status());
+}
+
+TEST(Status, EqualsSame) {
+ Status a(FirestoreErrorCode::InvalidArgument, "Invalid");
+ Status b(FirestoreErrorCode::InvalidArgument, "Invalid");
+ ASSERT_EQ(a, b);
+}
+
+TEST(Status, EqualsCopy) {
+ const Status a(FirestoreErrorCode::InvalidArgument, "Invalid");
+ const Status b = a;
+ ASSERT_EQ(a, b);
+}
+
+TEST(Status, EqualsDifferentCode) {
+ const Status a(FirestoreErrorCode::InvalidArgument, "message");
+ const Status b(FirestoreErrorCode::Internal, "message");
+ ASSERT_NE(a, b);
+}
+
+TEST(Status, EqualsDifferentMessage) {
+ const Status a(FirestoreErrorCode::InvalidArgument, "message");
+ const Status b(FirestoreErrorCode::InvalidArgument, "another");
+ ASSERT_NE(a, b);
+}
+
+} // namespace util
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/test/firebase/firestore/util/status_test_util.h b/Firestore/core/test/firebase/firestore/util/status_test_util.h
new file mode 100644
index 0000000..745f3aa
--- /dev/null
+++ b/Firestore/core/test/firebase/firestore/util/status_test_util.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2015, 2018 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.
+ */
+
+#ifndef FIRESTORE_CORE_TEST_FIREBASE_FIRESTORE_UTIL_STATUS_TEST_UTIL_H_
+#define FIRESTORE_CORE_TEST_FIREBASE_FIRESTORE_UTIL_STATUS_TEST_UTIL_H_
+
+#include "Firestore/core/src/firebase/firestore/util/status.h"
+#include "gtest/gtest.h"
+
+// Macros for testing the results of functions that return tensorflow::Status.
+#define EXPECT_OK(statement) \
+ EXPECT_EQ(::firebase::firestore::util::Status::OK(), (statement))
+#define ASSERT_OK(statement) \
+ ASSERT_EQ(::firebase::firestore::util::Status::OK(), (statement))
+
+// There are no EXPECT_NOT_OK/ASSERT_NOT_OK macros since they would not
+// provide much value (when they fail, they would just print the OK status
+// which conveys no more information than EXPECT_FALSE(status.ok());
+// If you want to check for particular errors, a better alternative is:
+// EXPECT_EQ(..expected tensorflow::error::Code..., status.code());
+
+#endif // FIRESTORE_CORE_TEST_FIREBASE_FIRESTORE_UTIL_STATUS_TEST_UTIL_H_
diff --git a/Firestore/core/test/firebase/firestore/util/statusor_test.cc b/Firestore/core/test/firebase/firestore/util/statusor_test.cc
new file mode 100644
index 0000000..6c9ccf5
--- /dev/null
+++ b/Firestore/core/test/firebase/firestore/util/statusor_test.cc
@@ -0,0 +1,436 @@
+/*
+ * Copyright 2017, 2018 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.
+ */
+
+// Unit tests for StatusOr
+
+#include "Firestore/core/src/firebase/firestore/util/statusor.h"
+#include "gtest/gtest.h"
+
+namespace firebase {
+namespace firestore {
+namespace util {
+namespace {
+
+using std::string;
+
+class Base1 {
+ public:
+ virtual ~Base1() {
+ }
+ int pad_;
+};
+
+class Base2 {
+ public:
+ virtual ~Base2() {
+ }
+ int yetotherpad_;
+};
+
+class Derived : public Base1, public Base2 {
+ public:
+ ~Derived() override {
+ }
+ int evenmorepad_;
+};
+
+class CopyNoAssign {
+ public:
+ explicit CopyNoAssign(int value) : foo_(value) {
+ }
+ CopyNoAssign(const CopyNoAssign& other) : foo_(other.foo_) {
+ }
+ int foo_;
+
+ private:
+ const CopyNoAssign& operator=(const CopyNoAssign&);
+};
+
+class NoDefaultConstructor {
+ public:
+ explicit NoDefaultConstructor(int foo);
+};
+
+static_assert(!std::is_default_constructible<NoDefaultConstructor>(),
+ "Should not be default-constructible.");
+
+StatusOr<std::unique_ptr<int>> ReturnUniquePtr() {
+ // Uses implicit constructor from T&&
+ return std::unique_ptr<int>(new int(0));
+}
+
+TEST(StatusOr, ElementType) {
+ static_assert(std::is_same<StatusOr<int>::element_type, int>(), "");
+ static_assert(std::is_same<StatusOr<char>::element_type, char>(), "");
+}
+
+TEST(StatusOr, TestNoDefaultConstructorInitialization) {
+ // Explicitly initialize it with an error code.
+ StatusOr<NoDefaultConstructor> statusor(
+ Status(FirestoreErrorCode::Cancelled, ""));
+ EXPECT_FALSE(statusor.ok());
+ EXPECT_EQ(statusor.status().code(), FirestoreErrorCode::Cancelled);
+
+ // Default construction of StatusOr initializes it with an UNKNOWN error code.
+ StatusOr<NoDefaultConstructor> statusor2;
+ EXPECT_FALSE(statusor2.ok());
+ EXPECT_EQ(statusor2.status().code(), FirestoreErrorCode::Unknown);
+}
+
+TEST(StatusOr, TestMoveOnlyInitialization) {
+ StatusOr<std::unique_ptr<int>> thing(ReturnUniquePtr());
+ ASSERT_TRUE(thing.ok());
+ EXPECT_EQ(0, *thing.ValueOrDie());
+ int* previous = thing.ValueOrDie().get();
+
+ thing = ReturnUniquePtr();
+ EXPECT_TRUE(thing.ok());
+ EXPECT_EQ(0, *thing.ValueOrDie());
+ EXPECT_NE(previous, thing.ValueOrDie().get());
+}
+
+TEST(StatusOr, TestMoveOnlyStatusCtr) {
+ StatusOr<std::unique_ptr<int>> thing(
+ Status(FirestoreErrorCode::Cancelled, ""));
+ ASSERT_FALSE(thing.ok());
+}
+
+TEST(StatusOr, TestMoveOnlyValueExtraction) {
+ StatusOr<std::unique_ptr<int>> thing(ReturnUniquePtr());
+ ASSERT_TRUE(thing.ok());
+ std::unique_ptr<int> ptr = thing.ConsumeValueOrDie();
+ EXPECT_EQ(0, *ptr);
+
+ thing = std::move(ptr);
+ ptr = std::move(thing.ValueOrDie());
+ EXPECT_EQ(0, *ptr);
+}
+
+TEST(StatusOr, TestMoveOnlyConversion) {
+ StatusOr<std::unique_ptr<const int>> const_thing(ReturnUniquePtr());
+ EXPECT_TRUE(const_thing.ok());
+ EXPECT_EQ(0, *const_thing.ValueOrDie());
+
+ // Test rvalue converting assignment
+ const int* const_previous = const_thing.ValueOrDie().get();
+ const_thing = ReturnUniquePtr();
+ EXPECT_TRUE(const_thing.ok());
+ EXPECT_EQ(0, *const_thing.ValueOrDie());
+ EXPECT_NE(const_previous, const_thing.ValueOrDie().get());
+}
+
+TEST(StatusOr, TestMoveOnlyVector) {
+ // Sanity check that StatusOr<MoveOnly> works in vector.
+ std::vector<StatusOr<std::unique_ptr<int>>> vec;
+ vec.push_back(ReturnUniquePtr());
+ vec.resize(2);
+ auto another_vec = std::move(vec);
+ EXPECT_EQ(0, *another_vec[0].ValueOrDie());
+ EXPECT_EQ(FirestoreErrorCode::Unknown, another_vec[1].status().code());
+}
+
+TEST(StatusOr, TestMoveWithValuesAndErrors) {
+ StatusOr<string> status_or(string(1000, '0'));
+ StatusOr<string> value1(string(1000, '1'));
+ StatusOr<string> value2(string(1000, '2'));
+ StatusOr<string> error1(Status(FirestoreErrorCode::Unknown, "error1"));
+ StatusOr<string> error2(Status(FirestoreErrorCode::Unknown, "error2"));
+
+ ASSERT_TRUE(status_or.ok());
+ EXPECT_EQ(string(1000, '0'), status_or.ValueOrDie());
+
+ // Overwrite the value in status_or with another value.
+ status_or = std::move(value1);
+ ASSERT_TRUE(status_or.ok());
+ EXPECT_EQ(string(1000, '1'), status_or.ValueOrDie());
+
+ // Overwrite the value in status_or with an error.
+ status_or = std::move(error1);
+ ASSERT_FALSE(status_or.ok());
+ EXPECT_EQ("error1", status_or.status().error_message());
+
+ // Overwrite the error in status_or with another error.
+ status_or = std::move(error2);
+ ASSERT_FALSE(status_or.ok());
+ EXPECT_EQ("error2", status_or.status().error_message());
+
+ // Overwrite the error with a value.
+ status_or = std::move(value2);
+ ASSERT_TRUE(status_or.ok());
+ EXPECT_EQ(string(1000, '2'), status_or.ValueOrDie());
+}
+
+TEST(StatusOr, TestCopyWithValuesAndErrors) {
+ StatusOr<string> status_or(string(1000, '0'));
+ StatusOr<string> value1(string(1000, '1'));
+ StatusOr<string> value2(string(1000, '2'));
+ StatusOr<string> error1(Status(FirestoreErrorCode::Unknown, "error1"));
+ StatusOr<string> error2(Status(FirestoreErrorCode::Unknown, "error2"));
+
+ ASSERT_TRUE(status_or.ok());
+ EXPECT_EQ(string(1000, '0'), status_or.ValueOrDie());
+
+ // Overwrite the value in status_or with another value.
+ status_or = value1;
+ ASSERT_TRUE(status_or.ok());
+ EXPECT_EQ(string(1000, '1'), status_or.ValueOrDie());
+
+ // Overwrite the value in status_or with an error.
+ status_or = error1;
+ ASSERT_FALSE(status_or.ok());
+ EXPECT_EQ("error1", status_or.status().error_message());
+
+ // Overwrite the error in status_or with another error.
+ status_or = error2;
+ ASSERT_FALSE(status_or.ok());
+ EXPECT_EQ("error2", status_or.status().error_message());
+
+ // Overwrite the error with a value.
+ status_or = value2;
+ ASSERT_TRUE(status_or.ok());
+ EXPECT_EQ(string(1000, '2'), status_or.ValueOrDie());
+
+ // Verify original values unchanged.
+ EXPECT_EQ(string(1000, '1'), value1.ValueOrDie());
+ EXPECT_EQ("error1", error1.status().error_message());
+ EXPECT_EQ("error2", error2.status().error_message());
+ EXPECT_EQ(string(1000, '2'), value2.ValueOrDie());
+}
+
+TEST(StatusOr, TestDefaultCtor) {
+ StatusOr<int> thing;
+ EXPECT_FALSE(thing.ok());
+ EXPECT_EQ(thing.status().code(), FirestoreErrorCode::Unknown);
+}
+
+TEST(StatusOrDeathTest, TestDefaultCtorValue) {
+ StatusOr<int> thing;
+ EXPECT_ANY_THROW(thing.ValueOrDie());
+
+ const StatusOr<int> thing2;
+ EXPECT_ANY_THROW(thing.ValueOrDie());
+}
+
+TEST(StatusOr, TestStatusCtor) {
+ StatusOr<int> thing(Status(FirestoreErrorCode::Cancelled, ""));
+ EXPECT_FALSE(thing.ok());
+ EXPECT_EQ(thing.status().code(), FirestoreErrorCode::Cancelled);
+}
+
+TEST(StatusOr, TestValueCtor) {
+ const int kI = 4;
+ const StatusOr<int> thing(kI);
+ EXPECT_TRUE(thing.ok());
+ EXPECT_EQ(kI, thing.ValueOrDie());
+}
+
+TEST(StatusOr, TestCopyCtorStatusOk) {
+ const int kI = 4;
+ const StatusOr<int> original(kI);
+ const StatusOr<int> copy(original);
+ EXPECT_EQ(copy.status(), original.status());
+ EXPECT_EQ(original.ValueOrDie(), copy.ValueOrDie());
+}
+
+TEST(StatusOr, TestCopyCtorStatusNotOk) {
+ StatusOr<int> original(Status(FirestoreErrorCode::Cancelled, ""));
+ StatusOr<int> copy(original);
+ EXPECT_EQ(copy.status(), original.status());
+}
+
+TEST(StatusOr, TestCopyCtorNonAssignable) {
+ const int kI = 4;
+ CopyNoAssign value(kI);
+ StatusOr<CopyNoAssign> original(value);
+ StatusOr<CopyNoAssign> copy(original);
+ EXPECT_EQ(copy.status(), original.status());
+ EXPECT_EQ(original.ValueOrDie().foo_, copy.ValueOrDie().foo_);
+}
+
+TEST(StatusOr, TestCopyCtorStatusOKConverting) {
+ const int kI = 4;
+ StatusOr<int> original(kI);
+ StatusOr<double> copy(original);
+ EXPECT_EQ(copy.status(), original.status());
+ EXPECT_DOUBLE_EQ(original.ValueOrDie(), copy.ValueOrDie());
+}
+
+TEST(StatusOr, TestCopyCtorStatusNotOkConverting) {
+ StatusOr<int> original(Status(FirestoreErrorCode::Cancelled, ""));
+ StatusOr<double> copy(original);
+ EXPECT_EQ(copy.status(), original.status());
+}
+
+TEST(StatusOr, TestAssignmentStatusOk) {
+ const int kI = 4;
+ StatusOr<int> source(kI);
+ StatusOr<int> target;
+ target = source;
+ EXPECT_EQ(target.status(), source.status());
+ EXPECT_EQ(source.ValueOrDie(), target.ValueOrDie());
+}
+
+TEST(StatusOr, TestAssignmentStatusNotOk) {
+ StatusOr<int> source(Status(FirestoreErrorCode::Cancelled, ""));
+ StatusOr<int> target;
+ target = source;
+ EXPECT_EQ(target.status(), source.status());
+}
+
+TEST(StatusOr, TestStatus) {
+ StatusOr<int> good(4);
+ EXPECT_TRUE(good.ok());
+ StatusOr<int> bad(Status(FirestoreErrorCode::Cancelled, ""));
+ EXPECT_FALSE(bad.ok());
+ EXPECT_EQ(bad.status(), Status(FirestoreErrorCode::Cancelled, ""));
+}
+
+TEST(StatusOr, TestValue) {
+ const int kI = 4;
+ StatusOr<int> thing(kI);
+ EXPECT_EQ(kI, thing.ValueOrDie());
+}
+
+TEST(StatusOr, TestValueConst) {
+ const int kI = 4;
+ const StatusOr<int> thing(kI);
+ EXPECT_EQ(kI, thing.ValueOrDie());
+}
+
+TEST(StatusOrDeathTest, TestValueNotOk) {
+ StatusOr<int> thing(Status(FirestoreErrorCode::Cancelled, "cancelled"));
+ EXPECT_ANY_THROW(thing.ValueOrDie());
+}
+
+TEST(StatusOrDeathTest, TestValueNotOkConst) {
+ const StatusOr<int> thing(Status(FirestoreErrorCode::Unknown, ""));
+ EXPECT_ANY_THROW(thing.ValueOrDie());
+}
+
+TEST(StatusOr, TestPointerDefaultCtor) {
+ StatusOr<int*> thing;
+ EXPECT_FALSE(thing.ok());
+ EXPECT_EQ(thing.status().code(), FirestoreErrorCode::Unknown);
+}
+
+TEST(StatusOrDeathTest, TestPointerDefaultCtorValue) {
+ StatusOr<int*> thing;
+ EXPECT_ANY_THROW(thing.ValueOrDie());
+}
+
+TEST(StatusOr, TestPointerStatusCtor) {
+ StatusOr<int*> thing(Status(FirestoreErrorCode::Cancelled, ""));
+ EXPECT_FALSE(thing.ok());
+ EXPECT_EQ(thing.status(), Status(FirestoreErrorCode::Cancelled, ""));
+}
+
+TEST(StatusOr, TestPointerValueCtor) {
+ const int kI = 4;
+ StatusOr<const int*> thing(&kI);
+ EXPECT_TRUE(thing.ok());
+ EXPECT_EQ(&kI, thing.ValueOrDie());
+}
+
+TEST(StatusOr, TestPointerCopyCtorStatusOk) {
+ const int kI = 0;
+ StatusOr<const int*> original(&kI);
+ StatusOr<const int*> copy(original);
+ EXPECT_EQ(copy.status(), original.status());
+ EXPECT_EQ(original.ValueOrDie(), copy.ValueOrDie());
+}
+
+TEST(StatusOr, TestPointerCopyCtorStatusNotOk) {
+ StatusOr<int*> original(Status(FirestoreErrorCode::Cancelled, ""));
+ StatusOr<int*> copy(original);
+ EXPECT_EQ(copy.status(), original.status());
+}
+
+TEST(StatusOr, TestPointerCopyCtorStatusOKConverting) {
+ Derived derived;
+ StatusOr<Derived*> original(&derived);
+ StatusOr<Base2*> copy(original);
+ EXPECT_EQ(copy.status(), original.status());
+ EXPECT_EQ(static_cast<const Base2*>(original.ValueOrDie()),
+ copy.ValueOrDie());
+}
+
+TEST(StatusOr, TestPointerCopyCtorStatusNotOkConverting) {
+ StatusOr<Derived*> original(Status(FirestoreErrorCode::Cancelled, ""));
+ StatusOr<Base2*> copy(original);
+ EXPECT_EQ(copy.status(), original.status());
+}
+
+TEST(StatusOr, TestPointerAssignmentStatusOk) {
+ const int kI = 0;
+ StatusOr<const int*> source(&kI);
+ StatusOr<const int*> target;
+ target = source;
+ EXPECT_EQ(target.status(), source.status());
+ EXPECT_EQ(source.ValueOrDie(), target.ValueOrDie());
+}
+
+TEST(StatusOr, TestPointerAssignmentStatusNotOk) {
+ StatusOr<int*> source(Status(FirestoreErrorCode::Cancelled, ""));
+ StatusOr<int*> target;
+ target = source;
+ EXPECT_EQ(target.status(), source.status());
+}
+
+TEST(StatusOr, TestPointerStatus) {
+ const int kI = 0;
+ StatusOr<const int*> good(&kI);
+ EXPECT_TRUE(good.ok());
+ StatusOr<const int*> bad(Status(FirestoreErrorCode::Cancelled, ""));
+ EXPECT_EQ(bad.status(), Status(FirestoreErrorCode::Cancelled, ""));
+}
+
+TEST(StatusOr, TestPointerValue) {
+ const int kI = 0;
+ StatusOr<const int*> thing(&kI);
+ EXPECT_EQ(&kI, thing.ValueOrDie());
+}
+
+TEST(StatusOr, TestPointerValueConst) {
+ const int kI = 0;
+ const StatusOr<const int*> thing(&kI);
+ EXPECT_EQ(&kI, thing.ValueOrDie());
+}
+
+// NOTE(tucker): tensorflow::StatusOr does not support this kind
+// of resize op.
+// NOTE(rsgowman): We stole StatusOr from tensorflow, so this applies here too.
+// TEST(StatusOr, StatusOrVectorOfUniquePointerCanResize) {
+// using EvilType = std::vector<std::unique_ptr<int>>;
+// static_assert(std::is_copy_constructible<EvilType>::value, "");
+// std::vector<StatusOr<EvilType>> v(5);
+// v.reserve(v.capacity() + 10);
+// }
+
+TEST(StatusOrDeathTest, TestPointerValueNotOk) {
+ StatusOr<int*> thing(Status(FirestoreErrorCode::Cancelled, "cancelled"));
+ EXPECT_ANY_THROW(thing.ValueOrDie());
+}
+
+TEST(StatusOrDeathTest, TestPointerValueNotOkConst) {
+ const StatusOr<int*> thing(
+ Status(FirestoreErrorCode::Cancelled, "cancelled"));
+ EXPECT_ANY_THROW(thing.ValueOrDie());
+}
+
+} // namespace
+} // namespace util
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/test/firebase/firestore/util/string_printf_test.cc b/Firestore/core/test/firebase/firestore/util/string_printf_test.cc
new file mode 100644
index 0000000..14cc9c8
--- /dev/null
+++ b/Firestore/core/test/firebase/firestore/util/string_printf_test.cc
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2017 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.
+ */
+
+#include "Firestore/core/src/firebase/firestore/util/string_printf.h"
+
+#include "gtest/gtest.h"
+
+namespace firebase {
+namespace firestore {
+namespace util {
+
+TEST(StringPrintf, Empty) {
+ EXPECT_EQ("", StringPrintf(""));
+ EXPECT_EQ("", StringPrintf("%s", std::string().c_str()));
+ EXPECT_EQ("", StringPrintf("%s", ""));
+}
+
+TEST(StringAppendFTest, Empty) {
+ std::string value("Hello");
+ const char* empty = "";
+ StringAppendF(&value, "%s", empty);
+ EXPECT_EQ("Hello", value);
+}
+
+TEST(StringAppendFTest, EmptyString) {
+ std::string value("Hello");
+ StringAppendF(&value, "%s", "");
+ EXPECT_EQ("Hello", value);
+}
+
+TEST(StringAppendFTest, String) {
+ std::string value("Hello");
+ StringAppendF(&value, " %s", "World");
+ EXPECT_EQ("Hello World", value);
+}
+
+TEST(StringAppendFTest, Int) {
+ std::string value("Hello");
+ StringAppendF(&value, " %d", 123);
+ EXPECT_EQ("Hello 123", value);
+}
+
+TEST(StringPrintf, DontOverwriteErrno) {
+ // Check that errno isn't overwritten unless we're printing
+ // something significantly larger than what people are normally
+ // printing in their badly written PLOG() statements.
+ errno = ECHILD;
+ std::string value = StringPrintf("Hello, %s!", "World");
+ EXPECT_EQ(ECHILD, errno);
+}
+
+TEST(StringPrintf, LargeBuf) {
+ // Check that the large buffer is handled correctly.
+ size_t n = 2048;
+ char* buf = new char[n + 1];
+ memset(buf, ' ', n);
+ buf[n] = 0;
+ std::string value = StringPrintf("%s", buf);
+ EXPECT_EQ(buf, value);
+ delete[] buf;
+}
+
+} // namespace util
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/Port/string_util_test.cc b/Firestore/core/test/firebase/firestore/util/string_util_test.cc
index ac8ce56..a85c849 100644
--- a/Firestore/Port/string_util_test.cc
+++ b/Firestore/core/test/firebase/firestore/util/string_util_test.cc
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-#include "Firestore/Port/string_util.h"
-
-#include "leveldb/db.h"
+#include "Firestore/core/src/firebase/firestore/util/string_util.h"
#include "gtest/gtest.h"
-using Firestore::PrefixSuccessor;
-using Firestore::ImmediateSuccessor;
-using leveldb::Slice;
+namespace firebase {
+namespace firestore {
+namespace util {
-TEST(Util, PrefixSuccessor) {
+TEST(StringUtil, PrefixSuccessor) {
EXPECT_EQ(PrefixSuccessor("a"), "b");
EXPECT_EQ(PrefixSuccessor("aaAA"), "aaAB");
EXPECT_EQ(PrefixSuccessor("aaa\xff"), "aab");
@@ -34,7 +32,11 @@ TEST(Util, PrefixSuccessor) {
EXPECT_EQ(PrefixSuccessor(""), "");
}
-TEST(Util, ImmediateSuccessor) {
- EXPECT_EQ(ImmediateSuccessor("hello"), Slice("hello\0", 6));
- EXPECT_EQ(ImmediateSuccessor(""), Slice("\0", 1));
+TEST(StringUtil, ImmediateSuccessor) {
+ EXPECT_EQ(ImmediateSuccessor("hello"), std::string("hello\0", 6));
+ EXPECT_EQ(ImmediateSuccessor(""), std::string("\0", 1));
}
+
+} // namespace util
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/test.sh b/Firestore/test.sh
index 7e26e3f..50c6c7c 100755
--- a/Firestore/test.sh
+++ b/Firestore/test.sh
@@ -13,37 +13,8 @@
set -euo pipefail
-FIRESTORE_DIR=$(dirname "${BASH_SOURCE[0]}")
+firestore_dir=$(dirname "${BASH_SOURCE[0]}")
+scripts_dir="$firestore_dir/../scripts"
-test_iOS() {
- xcodebuild \
- -workspace "$FIRESTORE_DIR/Example/Firestore.xcworkspace" \
- -scheme Firestore_Tests \
- -sdk iphonesimulator \
- -destination 'platform=iOS Simulator,name=iPhone 7' \
- build \
- test \
- ONLY_ACTIVE_ARCH=YES \
- CODE_SIGNING_REQUIRED=NO \
- | xcpretty
-
- xcodebuild \
- -workspace "$FIRESTORE_DIR/Example/Firestore.xcworkspace" \
- -scheme SwiftBuildTest \
- -sdk iphonesimulator \
- -destination 'platform=iOS Simulator,name=iPhone 7' \
- build \
- ONLY_ACTIVE_ARCH=YES \
- CODE_SIGNING_REQUIRED=NO \
- | xcpretty
-}
-
-test_iOS; RESULT=$?
-if [[ $RESULT == 65 ]]; then
- echo "xcodebuild exited with 65, retrying"
- sleep 5
-
- test_iOS; RESULT=$?
-fi
-
-exit $RESULT
+$scripts_dir/build.sh Firestore iOS
+$scripts_dir/build.sh Firestore macOS cmake
diff --git a/Firestore/third_party/Immutable/FSTArraySortedDictionary.m b/Firestore/third_party/Immutable/FSTArraySortedDictionary.m
index ad1d68a..e9325a7 100644
--- a/Firestore/third_party/Immutable/FSTArraySortedDictionary.m
+++ b/Firestore/third_party/Immutable/FSTArraySortedDictionary.m
@@ -142,19 +142,6 @@ NS_ASSUME_NONNULL_BEGIN
}
}
-- (nullable id)predecessorKey:(id)key {
- NSInteger pos = [self findKey:key];
- if (pos == NSNotFound) {
- [NSException raise:NSInternalInconsistencyException
- format:@"Can't get predecessor key for non-existent key"];
- return nil;
- } else if (pos == 0) {
- return nil;
- } else {
- return self.keys[pos - 1];
- }
-}
-
- (NSUInteger)indexOfKey:(id)key {
return [self findKey:key];
}
diff --git a/Firestore/third_party/Immutable/FSTImmutableSortedDictionary.h b/Firestore/third_party/Immutable/FSTImmutableSortedDictionary.h
index a2264ec..cbb4da3 100644
--- a/Firestore/third_party/Immutable/FSTImmutableSortedDictionary.h
+++ b/Firestore/third_party/Immutable/FSTImmutableSortedDictionary.h
@@ -71,14 +71,6 @@ extern const int kSortedDictionaryArrayToRBTreeSizeThreshold;
- (ValueType)objectForKeyedSubscript:(KeyType)key;
/**
- * Gets the key before the given key in sorted order.
- *
- * @param key The key to look before.
- * @return The key before the given one.
- */
-- (nullable KeyType)predecessorKey:(KeyType)key;
-
-/**
* Returns the index of the key or NSNotFound if the key is not found.
*
* @param key The key to return the index for.
diff --git a/Firestore/third_party/Immutable/FSTImmutableSortedDictionary.m b/Firestore/third_party/Immutable/FSTImmutableSortedDictionary.m
index 6e78961..87c21a5 100644
--- a/Firestore/third_party/Immutable/FSTImmutableSortedDictionary.m
+++ b/Firestore/third_party/Immutable/FSTImmutableSortedDictionary.m
@@ -82,10 +82,6 @@ const int kSortedDictionaryArrayToRBTreeSizeThreshold = 25;
return [self objectForKey:key];
}
-- (nullable id)predecessorKey:(id)key {
- @throw FSTAbstractMethodException(); // NOLINT
-}
-
- (NSUInteger)indexOfKey:(id)key {
@throw FSTAbstractMethodException(); // NOLINT
}
diff --git a/Firestore/third_party/Immutable/FSTImmutableSortedSet.h b/Firestore/third_party/Immutable/FSTImmutableSortedSet.h
index d0f9906..b432f98 100644
--- a/Firestore/third_party/Immutable/FSTImmutableSortedSet.h
+++ b/Firestore/third_party/Immutable/FSTImmutableSortedSet.h
@@ -23,8 +23,6 @@ NS_ASSUME_NONNULL_BEGIN
- (NSUInteger)count;
- (BOOL)isEmpty;
-- (KeyType)predecessorObject:(KeyType)entry;
-
/**
* Returns the index of the object or NSNotFound if the object is not found.
*
diff --git a/Firestore/third_party/Immutable/FSTImmutableSortedSet.m b/Firestore/third_party/Immutable/FSTImmutableSortedSet.m
index a23068e..1b63c2c 100644
--- a/Firestore/third_party/Immutable/FSTImmutableSortedSet.m
+++ b/Firestore/third_party/Immutable/FSTImmutableSortedSet.m
@@ -76,10 +76,6 @@ NS_ASSUME_NONNULL_BEGIN
return [self.dictionary maxKey];
}
-- (id)predecessorObject:(id)entry {
- return [self.dictionary predecessorKey:entry];
-}
-
- (NSUInteger)indexOfObject:(id)object {
return [self.dictionary indexOfKey:object];
}
diff --git a/Firestore/third_party/Immutable/FSTTreeSortedDictionary.m b/Firestore/third_party/Immutable/FSTTreeSortedDictionary.m
index b3e691f..ec0c483 100644
--- a/Firestore/third_party/Immutable/FSTTreeSortedDictionary.m
+++ b/Firestore/third_party/Immutable/FSTTreeSortedDictionary.m
@@ -87,36 +87,6 @@ NS_ASSUME_NONNULL_BEGIN
return nil;
}
-- (nullable id)predecessorKey:(id)key {
- NSComparisonResult cmp;
- id<FSTLLRBNode> node = self.root;
- id<FSTLLRBNode> rightParent = nil;
- while (![node isEmpty]) {
- cmp = self.comparator(key, node.key);
- if (cmp == NSOrderedSame) {
- if (![node.left isEmpty]) {
- node = node.left;
- while (![node.right isEmpty]) {
- node = node.right;
- }
- return node.key;
- } else if (rightParent != nil) {
- return rightParent.key;
- } else {
- return nil;
- }
- } else if (cmp == NSOrderedAscending) {
- node = node.left;
- } else if (cmp == NSOrderedDescending) {
- rightParent = node;
- node = node.right;
- }
- }
- @throw [NSException exceptionWithName:@"NonexistentKey"
- reason:@"getPredecessorKey called with nonexistent key."
- userInfo:@{@"key" : [key description]}];
-}
-
- (NSUInteger)indexOfKey:(id)key {
NSUInteger prunedNodes = 0;
id<FSTLLRBNode> node = self.root;
diff --git a/Firestore/third_party/Immutable/Tests/FSTArraySortedDictionaryTests.m b/Firestore/third_party/Immutable/Tests/FSTArraySortedDictionaryTests.m
index a799686..bf17496 100644
--- a/Firestore/third_party/Immutable/Tests/FSTArraySortedDictionaryTests.m
+++ b/Firestore/third_party/Immutable/Tests/FSTArraySortedDictionaryTests.m
@@ -252,25 +252,6 @@
XCTAssertEqual((int)next, (int)toInsert.count, @"Check we traversed all of the items");
}
-- (void)testPredecessorKey {
- FSTArraySortedDictionary *map =
- [[FSTArraySortedDictionary alloc] initWithComparator:[self defaultComparator]];
- map = [map dictionaryBySettingObject:@1 forKey:@1];
- map = [map dictionaryBySettingObject:@50 forKey:@50];
- map = [map dictionaryBySettingObject:@3 forKey:@3];
- map = [map dictionaryBySettingObject:@4 forKey:@4];
- map = [map dictionaryBySettingObject:@7 forKey:@7];
- map = [map dictionaryBySettingObject:@9 forKey:@9];
-
- XCTAssertNil([map predecessorKey:@1], @"First object doesn't have a predecessor");
- XCTAssertEqualObjects([map predecessorKey:@3], @1, @"@1");
- XCTAssertEqualObjects([map predecessorKey:@4], @3, @"@3");
- XCTAssertEqualObjects([map predecessorKey:@7], @4, @"@4");
- XCTAssertEqualObjects([map predecessorKey:@9], @7, @"@7");
- XCTAssertEqualObjects([map predecessorKey:@50], @9, @"@9");
- XCTAssertThrows([map predecessorKey:@777], @"Expect exception about nonexistent key");
-}
-
// This is a macro instead of a method so that the failures show on the proper lines.
#define ASSERT_ENUMERATOR(enumerator, start, end, step) \
do { \
@@ -288,15 +269,12 @@
- (void)testEnumerator {
NSUInteger n = 100;
NSMutableArray *toInsert = [NSMutableArray arrayWithCapacity:n];
- NSMutableArray *toRemove = [NSMutableArray arrayWithCapacity:n];
for (int i = 0; i < n; i++) {
[toInsert addObject:@(i)];
- [toRemove addObject:@(i)];
}
[self shuffleArray:toInsert];
- [self shuffleArray:toRemove];
FSTArraySortedDictionary *map =
[[FSTArraySortedDictionary alloc] initWithComparator:self.defaultComparator];
diff --git a/Firestore/third_party/Immutable/Tests/FSTTreeSortedDictionaryTests.m b/Firestore/third_party/Immutable/Tests/FSTTreeSortedDictionaryTests.m
index 344efba..3e06b30 100644
--- a/Firestore/third_party/Immutable/Tests/FSTTreeSortedDictionaryTests.m
+++ b/Firestore/third_party/Immutable/Tests/FSTTreeSortedDictionaryTests.m
@@ -437,25 +437,6 @@
}
}
-- (void)testPredecessorKey {
- FSTTreeSortedDictionary *map =
- [[FSTTreeSortedDictionary alloc] initWithComparator:[self defaultComparator]];
- map = [map dictionaryBySettingObject:@1 forKey:@1];
- map = [map dictionaryBySettingObject:@50 forKey:@50];
- map = [map dictionaryBySettingObject:@3 forKey:@3];
- map = [map dictionaryBySettingObject:@4 forKey:@4];
- map = [map dictionaryBySettingObject:@7 forKey:@7];
- map = [map dictionaryBySettingObject:@9 forKey:@9];
-
- XCTAssertNil([map predecessorKey:@1], @"First object doesn't have a predecessor");
- XCTAssertEqualObjects([map predecessorKey:@3], @1, @"@1");
- XCTAssertEqualObjects([map predecessorKey:@4], @3, @"@3");
- XCTAssertEqualObjects([map predecessorKey:@7], @4, @"@4");
- XCTAssertEqualObjects([map predecessorKey:@9], @7, @"@7");
- XCTAssertEqualObjects([map predecessorKey:@50], @9, @"@9");
- XCTAssertThrows([map predecessorKey:@777], @"Expect exception about nonexistent key");
-}
-
// This is a macro instead of a method so that the failures show on the proper lines.
#define ASSERT_ENUMERATOR(enumerator, start, end, step) \
do { \
diff --git a/Firestore/third_party/abseil-cpp/CMakeLists.txt b/Firestore/third_party/abseil-cpp/CMakeLists.txt
index 6e715af..7318cd3 100644
--- a/Firestore/third_party/abseil-cpp/CMakeLists.txt
+++ b/Firestore/third_party/abseil-cpp/CMakeLists.txt
@@ -73,7 +73,6 @@ endif()
# Don't remove these or else CMake CI will break
#add_subdirectory(cctz)
#add_subdirectory(googletest)
-check_target(${ABSL_CCTZ_TARGET})
## check targets
if(BUILD_TESTING)
diff --git a/Firestore/third_party/abseil-cpp/absl/CMakeLists.txt b/Firestore/third_party/abseil-cpp/absl/CMakeLists.txt
index 689f64e..e7b5139 100644
--- a/Firestore/third_party/abseil-cpp/absl/CMakeLists.txt
+++ b/Firestore/third_party/abseil-cpp/absl/CMakeLists.txt
@@ -17,14 +17,7 @@
add_subdirectory(base)
-add_subdirectory(algorithm)
-add_subdirectory(container)
-add_subdirectory(debugging)
add_subdirectory(memory)
add_subdirectory(meta)
add_subdirectory(numeric)
add_subdirectory(strings)
-add_subdirectory(synchronization)
-add_subdirectory(time)
-add_subdirectory(types)
-add_subdirectory(utility)
diff --git a/Firestore/third_party/abseil-cpp/absl/base/CMakeLists.txt b/Firestore/third_party/abseil-cpp/absl/base/CMakeLists.txt
index ced81b6..09c8746 100644
--- a/Firestore/third_party/abseil-cpp/absl/base/CMakeLists.txt
+++ b/Firestore/third_party/abseil-cpp/absl/base/CMakeLists.txt
@@ -16,8 +16,6 @@
list(APPEND BASE_PUBLIC_HEADERS
"attributes.h"
- "call_once.h"
- "casts.h"
"config.h"
"dynamic_annotations.h"
"log_severity.h"
@@ -25,51 +23,23 @@ list(APPEND BASE_PUBLIC_HEADERS
"optimization.h"
"policy_checks.h"
"port.h"
- "thread_annotations.h"
)
list(APPEND BASE_INTERNAL_HEADERS
"internal/atomic_hook.h"
- "internal/cycleclock.h"
- "internal/direct_mmap.h"
"internal/endian.h"
- "internal/exception_testing.h"
- "internal/exception_safety_testing.h"
"internal/identity.h"
- "internal/invoke.h"
"internal/inline_variable.h"
- "internal/low_level_alloc.h"
- "internal/low_level_scheduling.h"
- "internal/malloc_extension.h"
- "internal/malloc_hook_c.h"
- "internal/malloc_hook.h"
- "internal/malloc_hook_invoke.h"
- "internal/per_thread_tls.h"
- "internal/pretty_function.h"
"internal/raw_logging.h"
- "internal/scheduling_mode.h"
- "internal/spinlock.h"
- "internal/spinlock_wait.h"
- "internal/sysinfo.h"
- "internal/thread_identity.h"
"internal/throw_delegate.h"
- "internal/tsan_mutex_interface.h"
"internal/unaligned_access.h"
- "internal/unscaledcycleclock.h"
)
# absl_base main library
list(APPEND BASE_SRC
- "internal/cycleclock.cc"
"internal/raw_logging.cc"
- "internal/spinlock.cc"
- "internal/sysinfo.cc"
- "internal/thread_identity.cc"
- "internal/unscaledcycleclock.cc"
- "internal/low_level_alloc.cc"
- "internal/malloc_hook.cc"
${BASE_PUBLIC_HEADERS}
${BASE_INTERNAL_HEADERS}
)
@@ -81,25 +51,10 @@ absl_library(
${BASE_SRC}
PUBLIC_LIBRARIES
absl_dynamic_annotations
- absl_spinlock_wait
EXPORT_NAME
base
)
-# malloc extension library
-set(MALLOC_EXTENSION_SRC "internal/malloc_extension.cc")
-set(MALLOC_EXTENSION_PUBLIC_LIBRARIES absl::base)
-
-absl_library(
- TARGET
- absl_malloc_extension
- SOURCES
- ${MALLOC_EXTENSION_SRC}
- PUBLIC_LIBRARIES
- ${MALLOC_EXTENSION_PUBLIC_LIBRARIES}
- EXPORT_NAME
- malloc_extension
-)
# throw delegate library
set(THROW_DELEGATE_SRC "internal/throw_delegate.cc")
@@ -117,28 +72,6 @@ absl_library(
throw_delegate
)
-if(BUILD_TESTING)
- # exception-safety testing library
- set(EXCEPTION_SAFETY_TESTING_SRC "internal/exception_safety_testing.cc")
- set(EXCEPTION_SAFETY_TESTING_PUBLIC_LIBRARIES
- ${ABSL_TEST_COMMON_LIBRARIES}
- absl::base
- absl::memory
- absl::meta
- absl::strings
- absl::types
- )
-
-absl_library(
- TARGET
- absl_base_internal_exception_safety_testing
- SOURCES
- ${EXCEPTION_SAFETY_TESTING_SRC}
- PUBLIC_LIBRARIES
- ${EXCEPTION_SAFETY_TESTING_PUBLIC_LIBRARIES}
-)
-endif()
-
# dynamic_annotations library
set(DYNAMIC_ANNOTATIONS_SRC "dynamic_annotations.cc")
@@ -151,92 +84,10 @@ absl_library(
)
-# spinlock_wait library
-set(SPINLOCK_WAIT_SRC "internal/spinlock_wait.cc")
-
-absl_library(
- TARGET
- absl_spinlock_wait
- SOURCES
- ${SPINLOCK_WAIT_SRC}
-)
-
-
-# malloc_internal library
-list(APPEND MALLOC_INTERNAL_SRC
- "internal/low_level_alloc.cc"
- "internal/malloc_hook.cc"
- "internal/malloc_hook_mmap_linux.inc"
-)
-
-absl_library(
- TARGET
- absl_malloc_internal
- SOURCES
- ${MALLOC_INTERNAL_SRC}
- PUBLIC_LIBRARIES
- absl_dynamic_annotations
-)
-
-
-
#
## TESTS
#
-# call once test
-set(CALL_ONCE_TEST_SRC "call_once_test.cc")
-set(CALL_ONCE_TEST_PUBLIC_LIBRARIES absl::base absl::synchronization)
-
-absl_test(
- TARGET
- call_once_test
- SOURCES
- ${CALL_ONCE_TEST_SRC}
- PUBLIC_LIBRARIES
- ${CALL_ONCE_TEST_PUBLIC_LIBRARIES}
-)
-
-
-# test bit_cast_test
-set(BIT_CAST_TEST_SRC "bit_cast_test.cc")
-
-absl_test(
- TARGET
- bit_cast_test
- SOURCES
- ${BIT_CAST_TEST_SRC}
-)
-
-
-# test absl_throw_delegate_test
-set(THROW_DELEGATE_TEST_SRC "throw_delegate_test.cc")
-set(THROW_DELEGATE_TEST_PUBLIC_LIBRARIES absl::base absl_throw_delegate)
-
-absl_test(
- TARGET
- throw_delegate_test
- SOURCES
- ${THROW_DELEGATE_TEST_SRC}
- PUBLIC_LIBRARIES
- ${THROW_DELEGATE_TEST_PUBLIC_LIBRARIES}
-)
-
-
-# test invoke_test
-set(INVOKE_TEST_SRC "invoke_test.cc")
-set(INVOKE_TEST_PUBLIC_LIBRARIES absl::strings)
-
-absl_test(
- TARGET
- invoke_test
- SOURCES
- ${INVOKE_TEST_SRC}
- PUBLIC_LIBRARIES
- ${INVOKE_TEST_PUBLIC_LIBRARIES}
-)
-
-
# test inline_variable_test
list(APPEND INLINE_VARIABLE_TEST_SRC
"internal/inline_variable_testing.h"
@@ -257,34 +108,6 @@ absl_test(
)
-# test spinlock_test_common
-set(SPINLOCK_TEST_COMMON_SRC "spinlock_test_common.cc")
-set(SPINLOCK_TEST_COMMON_PUBLIC_LIBRARIES absl::base absl::synchronization)
-
-absl_test(
- TARGET
- spinlock_test_common
- SOURCES
- ${SPINLOCK_TEST_COMMON_SRC}
- PUBLIC_LIBRARIES
- ${SPINLOCK_TEST_COMMON_PUBLIC_LIBRARIES}
-)
-
-
-# test spinlock_test
-set(SPINLOCK_TEST_SRC "spinlock_test_common.cc")
-set(SPINLOCK_TEST_PUBLIC_LIBRARIES absl::base absl::synchronization)
-
-absl_test(
- TARGET
- spinlock_test
- SOURCES
- ${SPINLOCK_TEST_SRC}
- PUBLIC_LIBRARIES
- ${SPINLOCK_TEST_PUBLIC_LIBRARIES}
-)
-
-
# test endian_test
set(ENDIAN_TEST_SRC "internal/endian_test.cc")
@@ -298,7 +121,7 @@ absl_test(
# test config_test
set(CONFIG_TEST_SRC "config_test.cc")
-set(CONFIG_TEST_PUBLIC_LIBRARIES absl::base absl::synchronization)
+set(CONFIG_TEST_PUBLIC_LIBRARIES absl::base)
absl_test(
TARGET
config_test
@@ -321,80 +144,3 @@ absl_test(
PUBLIC_LIBRARIES
${RAW_LOGGING_TEST_PUBLIC_LIBRARIES}
)
-
-
-# test sysinfo_test
-set(SYSINFO_TEST_SRC "internal/sysinfo_test.cc")
-set(SYSINFO_TEST_PUBLIC_LIBRARIES absl::base absl::synchronization)
-
-absl_test(
- TARGET
- sysinfo_test
- SOURCES
- ${SYSINFO_TEST_SRC}
- PUBLIC_LIBRARIES
- ${SYSINFO_TEST_PUBLIC_LIBRARIES}
-)
-
-
-# test low_level_alloc_test
-set(LOW_LEVEL_ALLOC_TEST_SRC "internal/low_level_alloc_test.cc")
-set(LOW_LEVEL_ALLOC_TEST_PUBLIC_LIBRARIES absl::base)
-
-absl_test(
- TARGET
- low_level_alloc_test
- SOURCES
- ${LOW_LEVEL_ALLOC_TEST_SRC}
- PUBLIC_LIBRARIES
- ${LOW_LEVEL_ALLOC_TEST_PUBLIC_LIBRARIES}
-)
-
-
-# test thread_identity_test
-set(THREAD_IDENTITY_TEST_SRC "internal/thread_identity_test.cc")
-set(THREAD_IDENTITY_TEST_PUBLIC_LIBRARIES absl::base absl::synchronization)
-
-absl_test(
- TARGET
- thread_identity_test
- SOURCES
- ${THREAD_IDENTITY_TEST_SRC}
- PUBLIC_LIBRARIES
- ${THREAD_IDENTITY_TEST_PUBLIC_LIBRARIES}
-)
-
-#test exceptions_safety_testing_test
-set(EXCEPTION_SAFETY_TESTING_TEST_SRC "exception_safety_testing_test.cc")
-set(EXCEPTION_SAFETY_TESTING_TEST_PUBLIC_LIBRARIES absl::base absl::memory absl::meta absl::strings absl::optional)
-
-absl_test(
- TARGET
- absl_exception_safety_testing_test
- SOURCES
- ${EXCEPTION_SAFETY_TESTING_TEST_SRC}
- PUBLIC_LIBRARIES
- ${EXCEPTION_SAFETY_TESTING_TEST_PUBLIC_LIBRARIES}
- PRIVATE_COMPILE_FLAGS
- ${ABSL_EXCEPTIONS_FLAG}
-)
-
-# test absl_malloc_extension_system_malloc_test
-set(MALLOC_EXTENSION_SYSTEM_MALLOC_TEST_SRC "internal/malloc_extension_test.cc")
-set(MALLOC_EXTENSION_SYSTEM_MALLOC_TEST_PUBLIC_LIBRARIES absl::base absl_malloc_extension)
-set(MALLOC_EXTENSION_SYSTEM_MALLOC_TEST_PRIVATE_COMPILE_FLAGS "-DABSL_MALLOC_EXTENSION_TEST_ALLOW_MISSING_EXTENSION=1")
-
-absl_test(
- TARGET
- absl_malloc_extension_system_malloc_test
- SOURCES
- ${MALLOC_EXTENSION_SYSTEM_MALLOC_TEST_SRC}
- PUBLIC_LIBRARIES
- ${MALLOC_EXTENSION_SYSTEM_MALLOC_TEST_PUBLIC_LIBRARIES}
- PRIVATE_COMPILE_FLAGS
- ${MALLOC_EXTENSION_SYSTEM_MALLOC_TEST_PRIVATE_COMPILE_FLAGS}
-)
-
-
-
-
diff --git a/Firestore/third_party/abseil-cpp/absl/base/config_test.cc b/Firestore/third_party/abseil-cpp/absl/base/config_test.cc
index c839712..4e6dd6a 100644
--- a/Firestore/third_party/abseil-cpp/absl/base/config_test.cc
+++ b/Firestore/third_party/abseil-cpp/absl/base/config_test.cc
@@ -17,7 +17,6 @@
#include <cstdint>
#include "gtest/gtest.h"
-#include "absl/synchronization/internal/thread_pool.h"
namespace {
@@ -41,20 +40,4 @@ TEST(ConfigTest, Endianness) {
#endif
}
-#if defined(ABSL_HAVE_THREAD_LOCAL)
-TEST(ConfigTest, ThreadLocal) {
- static thread_local int mine_mine_mine = 16;
- EXPECT_EQ(16, mine_mine_mine);
- {
- absl::synchronization_internal::ThreadPool pool(1);
- pool.Schedule([&] {
- EXPECT_EQ(16, mine_mine_mine);
- mine_mine_mine = 32;
- EXPECT_EQ(32, mine_mine_mine);
- });
- }
- EXPECT_EQ(16, mine_mine_mine);
-}
-#endif
-
} // namespace
diff --git a/Firestore/third_party/abseil-cpp/absl/base/macros.h b/Firestore/third_party/abseil-cpp/absl/base/macros.h
index 114a7be..5a2773b 100644
--- a/Firestore/third_party/abseil-cpp/absl/base/macros.h
+++ b/Firestore/third_party/abseil-cpp/absl/base/macros.h
@@ -196,7 +196,7 @@ enum LinkerInitialized {
#define ABSL_ASSERT(expr) (false ? (void)(expr) : (void)0)
#else
#define ABSL_ASSERT(expr) \
- (ABSL_PREDICT_TRUE((expr)) ? (void)0 : [] { assert(false && #expr); }())
+ (void) (ABSL_PREDICT_TRUE((expr)) ? (void)0 : [] { assert(false && #expr); }())
#endif
#endif // ABSL_BASE_MACROS_H_
diff --git a/Firestore/third_party/abseil-cpp/absl/meta/CMakeLists.txt b/Firestore/third_party/abseil-cpp/absl/meta/CMakeLists.txt
index d56fced..a25dd61 100644
--- a/Firestore/third_party/abseil-cpp/absl/meta/CMakeLists.txt
+++ b/Firestore/third_party/abseil-cpp/absl/meta/CMakeLists.txt
@@ -44,6 +44,3 @@ absl_test(
PUBLIC_LIBRARIES
${TYPE_TRAITS_TEST_PUBLIC_LIBRARIES} absl::meta
)
-
-
-
diff --git a/Firestore/third_party/abseil-cpp/absl/numeric/int128_test.cc b/Firestore/third_party/abseil-cpp/absl/numeric/int128_test.cc
index 79bcca9..74d362d 100644
--- a/Firestore/third_party/abseil-cpp/absl/numeric/int128_test.cc
+++ b/Firestore/third_party/abseil-cpp/absl/numeric/int128_test.cc
@@ -22,7 +22,6 @@
#include <vector>
#include "gtest/gtest.h"
-#include "absl/base/internal/cycleclock.h"
#include "absl/meta/type_traits.h"
#if defined(_MSC_VER) && _MSC_VER == 1900
diff --git a/Firestore/third_party/abseil-cpp/absl/strings/CMakeLists.txt b/Firestore/third_party/abseil-cpp/absl/strings/CMakeLists.txt
index 83cb934..a31f05d 100644
--- a/Firestore/third_party/abseil-cpp/absl/strings/CMakeLists.txt
+++ b/Firestore/third_party/abseil-cpp/absl/strings/CMakeLists.txt
@@ -48,7 +48,6 @@ list(APPEND STRINGS_SRC
"ascii.cc"
"escaping.cc"
"internal/memutil.cc"
- "internal/memutil.h"
"internal/utf8.cc"
"internal/ostringstream.cc"
"match.cc"
diff --git a/Functions/Backend/index.js b/Functions/Backend/index.js
new file mode 100644
index 0000000..0245f1b
--- /dev/null
+++ b/Functions/Backend/index.js
@@ -0,0 +1,95 @@
+const assert = require('assert');
+const functions = require('firebase-functions');
+
+exports.dataTest = functions.https.onRequest((request, response) => {
+ assert.deepEqual(request.body, {
+ data: {
+ bool: true,
+ int: 2,
+ long: {
+ value: '3',
+ '@type': 'type.googleapis.com/google.protobuf.Int64Value',
+ },
+ string: 'four',
+ array: [5, 6],
+ 'null': null,
+ }
+ });
+ response.send({
+ data: {
+ message: 'stub response',
+ code: 42,
+ long: {
+ value: '420',
+ '@type': 'type.googleapis.com/google.protobuf.Int64Value',
+ },
+ }
+ });
+});
+
+exports.scalarTest = functions.https.onRequest((request, response) => {
+ assert.deepEqual(request.body, { data: 17 });
+ response.send({ data: 76 });
+});
+
+exports.tokenTest = functions.https.onRequest((request, response) => {
+ assert.equal('Bearer token', request.get('Authorization'));
+ assert.deepEqual(request.body, { data: {} });
+ response.send({ data: {} });
+});
+
+exports.instanceIdTest = functions.https.onRequest((request, response) => {
+ assert.equal(request.get('Firebase-Instance-ID-Token'), 'iid');
+ assert.deepEqual(request.body, { data: {} });
+ response.send({ data: {} });
+});
+
+exports.nullTest = functions.https.onRequest((request, response) => {
+ assert.deepEqual(request.body, { data: null });
+ response.send({ data: null });
+});
+
+exports.missingResultTest = functions.https.onRequest((request, response) => {
+ assert.deepEqual(request.body, { data: null });
+ response.send({});
+});
+
+exports.unhandledErrorTest = functions.https.onRequest((request, response) => {
+ // Fail in a way that the client shouldn't see.
+ throw 'nope';
+});
+
+exports.unknownErrorTest = functions.https.onRequest((request, response) => {
+ // Send an http error with a body with an explicit code.
+ response.status(400).send({
+ error: {
+ status: 'THIS_IS_NOT_VALID',
+ message: 'this should be ignored',
+ },
+ });
+});
+
+exports.explicitErrorTest = functions.https.onRequest((request, response) => {
+ // Send an http error with a body with an explicit code.
+ // Note that eventually the SDK will have a helper to automatically return
+ // the appropriate http status code for an error.
+ response.status(400).send({
+ error: {
+ status: 'OUT_OF_RANGE',
+ message: 'explicit nope',
+ details: {
+ start: 10,
+ end: 20,
+ long: {
+ value: '30',
+ '@type': 'type.googleapis.com/google.protobuf.Int64Value',
+ },
+ },
+ },
+ });
+});
+
+exports.httpErrorTest = functions.https.onRequest((request, response) => {
+ // Send an http error with no body.
+ response.status(400).send();
+});
diff --git a/Functions/Backend/package.json b/Functions/Backend/package.json
new file mode 100644
index 0000000..c227bb1
--- /dev/null
+++ b/Functions/Backend/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "functions",
+ "description": "Cloud Functions for Firebase",
+ "dependencies": {
+ "firebase-admin": "~4.2.1",
+ "firebase-functions": "^0.5.7"
+ },
+ "private": true,
+ "devDependencies": {
+ "@google-cloud/functions-emulator": "^1.0.0-alpha.21"
+ }
+}
diff --git a/Functions/Backend/start.sh b/Functions/Backend/start.sh
new file mode 100755
index 0000000..b09675a
--- /dev/null
+++ b/Functions/Backend/start.sh
@@ -0,0 +1,55 @@
+#!/usr/bin/env bash
+
+# Copyright 2018 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.
+
+# Sets up a project with the functions CLI and starts a backend to run
+# integration tests against.
+
+set -e
+
+# Get the absolute path to the directory containing this script.
+SCRIPT_DIR="$(cd $(dirname ${BASH_SOURCE[0]}) && pwd)"
+TEMP_DIR="$(mktemp -d -t firebase-functions)"
+echo "Creating functions in ${TEMP_DIR}"
+
+# Set up the functions directory.
+cp "${SCRIPT_DIR}/index.js" "${TEMP_DIR}/"
+cp "${SCRIPT_DIR}/package.json" "${TEMP_DIR}/"
+cd "${TEMP_DIR}"
+npm install
+
+# Start the server.
+FUNCTIONS_BIN="./node_modules/.bin/functions"
+"${FUNCTIONS_BIN}" config set projectId functions-integration-test
+"${FUNCTIONS_BIN}" config set supervisorPort 5005
+"${FUNCTIONS_BIN}" config set region us-central1
+"${FUNCTIONS_BIN}" config set verbose true
+"${FUNCTIONS_BIN}" restart
+"${FUNCTIONS_BIN}" deploy dataTest --trigger-http
+"${FUNCTIONS_BIN}" deploy scalarTest --trigger-http
+"${FUNCTIONS_BIN}" deploy tokenTest --trigger-http
+"${FUNCTIONS_BIN}" deploy instanceIdTest --trigger-http
+"${FUNCTIONS_BIN}" deploy nullTest --trigger-http
+"${FUNCTIONS_BIN}" deploy missingResultTest --trigger-http
+"${FUNCTIONS_BIN}" deploy unhandledErrorTest --trigger-http
+"${FUNCTIONS_BIN}" deploy unknownErrorTest --trigger-http
+"${FUNCTIONS_BIN}" deploy explicitErrorTest --trigger-http
+"${FUNCTIONS_BIN}" deploy httpErrorTest --trigger-http
+
+# Wait for the user to tell us to stop the server.
+echo "Functions emulator now running in ${TEMP_DIR}."
+read -n 1 -p "*** Press any key to stop the server. ***"
+echo "\nStopping the emulator..."
+"${FUNCTIONS_BIN}" stop
diff --git a/Functions/CHANGELOG.md b/Functions/CHANGELOG.md
new file mode 100644
index 0000000..d2ea864
--- /dev/null
+++ b/Functions/CHANGELOG.md
@@ -0,0 +1,2 @@
+# v1.0.0
+- Initial public release \ No newline at end of file
diff --git a/Functions/Example/FirebaseFunctions.xcodeproj/project.pbxproj b/Functions/Example/FirebaseFunctions.xcodeproj/project.pbxproj
new file mode 100644
index 0000000..d70f4b5
--- /dev/null
+++ b/Functions/Example/FirebaseFunctions.xcodeproj/project.pbxproj
@@ -0,0 +1,848 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 46;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ 6003F58E195388D20070C39A /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58D195388D20070C39A /* Foundation.framework */; };
+ 6003F590195388D20070C39A /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58F195388D20070C39A /* CoreGraphics.framework */; };
+ 6003F592195388D20070C39A /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F591195388D20070C39A /* UIKit.framework */; };
+ 6003F598195388D20070C39A /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6003F596195388D20070C39A /* InfoPlist.strings */; };
+ 6003F59A195388D20070C39A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 6003F599195388D20070C39A /* main.m */; };
+ 6003F59E195388D20070C39A /* FIRAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 6003F59D195388D20070C39A /* FIRAppDelegate.m */; };
+ 6003F5A7195388D20070C39A /* FIRViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 6003F5A6195388D20070C39A /* FIRViewController.m */; };
+ 6003F5A9195388D20070C39A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6003F5A8195388D20070C39A /* Images.xcassets */; };
+ 6003F5B0195388D20070C39A /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F5AF195388D20070C39A /* XCTest.framework */; };
+ 6003F5B1195388D20070C39A /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58D195388D20070C39A /* Foundation.framework */; };
+ 6003F5B2195388D20070C39A /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F591195388D20070C39A /* UIKit.framework */; };
+ 6003F5BA195388D20070C39A /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6003F5B8195388D20070C39A /* InfoPlist.strings */; };
+ 6003F5BC195388D20070C39A /* FIRFunctionsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6003F5BB195388D20070C39A /* FIRFunctionsTests.m */; };
+ 633D50991C0FDF68085030B5 /* Pods_FirebaseFunctions_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B954281766FFD7C7C9970E9B /* Pods_FirebaseFunctions_Example.framework */; };
+ 6358342A9F1AF395C6C52471 /* Pods_FirebaseFunctions_IntegrationTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0A4C1781DC97515D32B33DBD /* Pods_FirebaseFunctions_IntegrationTests.framework */; };
+ 71719F9F1E33DC2100824A3D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 71719F9D1E33DC2100824A3D /* LaunchScreen.storyboard */; };
+ 78F883E44C88CC18414F3DAF /* Pods_FirebaseFunctions_Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A19679D6C21E3206B4A13697 /* Pods_FirebaseFunctions_Tests.framework */; };
+ 7C58B03A1F1441F0005ED954 /* FUNSerializerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 7C58B0391F1441F0005ED954 /* FUNSerializerTests.m */; };
+ 7CBFAA82205702AB00A65866 /* FIRIntegrationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 7C9BFD3F1F10A12F001A19ED /* FIRIntegrationTests.m */; };
+ 7CBFAA86205702AB00A65866 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F5AF195388D20070C39A /* XCTest.framework */; };
+ 7CBFAA87205702AB00A65866 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F591195388D20070C39A /* UIKit.framework */; };
+ 7CBFAA88205702AB00A65866 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58D195388D20070C39A /* Foundation.framework */; };
+ 7CBFAA89205702AB00A65866 /* Pods_FirebaseFunctions_Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A19679D6C21E3206B4A13697 /* Pods_FirebaseFunctions_Tests.framework */; };
+ 7CBFAA8B205702AB00A65866 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6003F5B8195388D20070C39A /* InfoPlist.strings */; };
+ 7CF3BEC21F97EE2F00B16B6E /* FUNFakeInstanceID.m in Sources */ = {isa = PBXBuildFile; fileRef = 7CF3BEC01F97EE2F00B16B6E /* FUNFakeInstanceID.m */; };
+ 7CF563091F1FE70600FEE1F4 /* FUNFakeApp.m in Sources */ = {isa = PBXBuildFile; fileRef = 7CF563081F1FE70600FEE1F4 /* FUNFakeApp.m */; };
+ 873B8AEB1B1F5CCA007FD442 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 873B8AEA1B1F5CCA007FD442 /* Main.storyboard */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXContainerItemProxy section */
+ 6003F5B3195388D20070C39A /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 6003F582195388D10070C39A /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 6003F589195388D20070C39A;
+ remoteInfo = FirebaseFunctions;
+ };
+ 7CBFAA7F205702AB00A65866 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 6003F582195388D10070C39A /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 6003F589195388D20070C39A;
+ remoteInfo = FirebaseFunctions;
+ };
+/* End PBXContainerItemProxy section */
+
+/* Begin PBXFileReference section */
+ 0A4C1781DC97515D32B33DBD /* Pods_FirebaseFunctions_IntegrationTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_FirebaseFunctions_IntegrationTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ 501CFEC7487BD4C12FCEAB03 /* Pods-FirebaseFunctions_IntegrationTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FirebaseFunctions_IntegrationTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-FirebaseFunctions_IntegrationTests/Pods-FirebaseFunctions_IntegrationTests.debug.xcconfig"; sourceTree = "<group>"; };
+ 59519EC771AF4770F85B6534 /* Pods-FirebaseFunctions_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FirebaseFunctions_Example.debug.xcconfig"; path = "Pods/Target Support Files/Pods-FirebaseFunctions_Example/Pods-FirebaseFunctions_Example.debug.xcconfig"; sourceTree = "<group>"; };
+ 5B43C813C653DE3455952E9B /* Pods-FirebaseFunctions_Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FirebaseFunctions_Tests.release.xcconfig"; path = "Pods/Target Support Files/Pods-FirebaseFunctions_Tests/Pods-FirebaseFunctions_Tests.release.xcconfig"; sourceTree = "<group>"; };
+ 6003F58A195388D20070C39A /* FirebaseFunctions_Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = FirebaseFunctions_Example.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ 6003F58D195388D20070C39A /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
+ 6003F58F195388D20070C39A /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };
+ 6003F591195388D20070C39A /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
+ 6003F595195388D20070C39A /* FirebaseFunctions-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "FirebaseFunctions-Info.plist"; sourceTree = "<group>"; };
+ 6003F597195388D20070C39A /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
+ 6003F599195388D20070C39A /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
+ 6003F59C195388D20070C39A /* FIRAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FIRAppDelegate.h; sourceTree = "<group>"; };
+ 6003F59D195388D20070C39A /* FIRAppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FIRAppDelegate.m; sourceTree = "<group>"; };
+ 6003F5A5195388D20070C39A /* FIRViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FIRViewController.h; sourceTree = "<group>"; };
+ 6003F5A6195388D20070C39A /* FIRViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FIRViewController.m; sourceTree = "<group>"; };
+ 6003F5A8195388D20070C39A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
+ 6003F5AE195388D20070C39A /* FirebaseFunctions_Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FirebaseFunctions_Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+ 6003F5AF195388D20070C39A /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; };
+ 6003F5B7195388D20070C39A /* Tests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Tests-Info.plist"; sourceTree = "<group>"; };
+ 6003F5B9195388D20070C39A /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
+ 6003F5BB195388D20070C39A /* FIRFunctionsTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FIRFunctionsTests.m; sourceTree = "<group>"; };
+ 71719F9E1E33DC2100824A3D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
+ 7BEA793625C8DE7C8EC60006 /* FirebaseFunctions.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = FirebaseFunctions.podspec; path = ../FirebaseFunctions.podspec; sourceTree = "<group>"; };
+ 7C58B0391F1441F0005ED954 /* FUNSerializerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FUNSerializerTests.m; sourceTree = "<group>"; };
+ 7C9BFD3F1F10A12F001A19ED /* FIRIntegrationTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FIRIntegrationTests.m; sourceTree = "<group>"; };
+ 7CBFAA91205702AB00A65866 /* FirebaseFunctions_IntegrationTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FirebaseFunctions_IntegrationTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+ 7CBFAA92205702AC00A65866 /* FirebaseFunctions_Tests copy-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "FirebaseFunctions_Tests copy-Info.plist"; path = "/Users/klimt/src/firebase-ios-sdk/Functions/Example/FirebaseFunctions_Tests copy-Info.plist"; sourceTree = "<absolute>"; };
+ 7CF3BEC01F97EE2F00B16B6E /* FUNFakeInstanceID.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FUNFakeInstanceID.m; sourceTree = "<group>"; };
+ 7CF3BEC11F97EE2F00B16B6E /* FUNFakeInstanceID.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FUNFakeInstanceID.h; sourceTree = "<group>"; };
+ 7CF563081F1FE70600FEE1F4 /* FUNFakeApp.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FUNFakeApp.m; sourceTree = "<group>"; };
+ 7CF5630A1F1FE76700FEE1F4 /* FUNFakeApp.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FUNFakeApp.h; sourceTree = "<group>"; };
+ 873B8AEA1B1F5CCA007FD442 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = Main.storyboard; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
+ A19679D6C21E3206B4A13697 /* Pods_FirebaseFunctions_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_FirebaseFunctions_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ B954281766FFD7C7C9970E9B /* Pods_FirebaseFunctions_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_FirebaseFunctions_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ E0A8D570636E99E7C3396DF8 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = "<group>"; };
+ E2E6249788F1618471335BE7 /* Pods-FirebaseFunctions_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FirebaseFunctions_Example.release.xcconfig"; path = "Pods/Target Support Files/Pods-FirebaseFunctions_Example/Pods-FirebaseFunctions_Example.release.xcconfig"; sourceTree = "<group>"; };
+ F1F2A7C03C10A3A03F9502B8 /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = "<group>"; };
+ F840F9EDCB950E7509527AB7 /* Pods-FirebaseFunctions_IntegrationTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FirebaseFunctions_IntegrationTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-FirebaseFunctions_IntegrationTests/Pods-FirebaseFunctions_IntegrationTests.release.xcconfig"; sourceTree = "<group>"; };
+ FFE894EDD87F1EF24F0ED8DD /* Pods-FirebaseFunctions_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FirebaseFunctions_Tests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-FirebaseFunctions_Tests/Pods-FirebaseFunctions_Tests.debug.xcconfig"; sourceTree = "<group>"; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ 6003F587195388D20070C39A /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 6003F590195388D20070C39A /* CoreGraphics.framework in Frameworks */,
+ 6003F592195388D20070C39A /* UIKit.framework in Frameworks */,
+ 6003F58E195388D20070C39A /* Foundation.framework in Frameworks */,
+ 633D50991C0FDF68085030B5 /* Pods_FirebaseFunctions_Example.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 6003F5AB195388D20070C39A /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 6003F5B0195388D20070C39A /* XCTest.framework in Frameworks */,
+ 6003F5B2195388D20070C39A /* UIKit.framework in Frameworks */,
+ 6003F5B1195388D20070C39A /* Foundation.framework in Frameworks */,
+ 78F883E44C88CC18414F3DAF /* Pods_FirebaseFunctions_Tests.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 7CBFAA85205702AB00A65866 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 7CBFAA86205702AB00A65866 /* XCTest.framework in Frameworks */,
+ 7CBFAA87205702AB00A65866 /* UIKit.framework in Frameworks */,
+ 7CBFAA88205702AB00A65866 /* Foundation.framework in Frameworks */,
+ 7CBFAA89205702AB00A65866 /* Pods_FirebaseFunctions_Tests.framework in Frameworks */,
+ 6358342A9F1AF395C6C52471 /* Pods_FirebaseFunctions_IntegrationTests.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 004DC60D1A62294B06E83453 /* Pods */ = {
+ isa = PBXGroup;
+ children = (
+ 59519EC771AF4770F85B6534 /* Pods-FirebaseFunctions_Example.debug.xcconfig */,
+ E2E6249788F1618471335BE7 /* Pods-FirebaseFunctions_Example.release.xcconfig */,
+ FFE894EDD87F1EF24F0ED8DD /* Pods-FirebaseFunctions_Tests.debug.xcconfig */,
+ 5B43C813C653DE3455952E9B /* Pods-FirebaseFunctions_Tests.release.xcconfig */,
+ 501CFEC7487BD4C12FCEAB03 /* Pods-FirebaseFunctions_IntegrationTests.debug.xcconfig */,
+ F840F9EDCB950E7509527AB7 /* Pods-FirebaseFunctions_IntegrationTests.release.xcconfig */,
+ );
+ name = Pods;
+ sourceTree = "<group>";
+ };
+ 6003F581195388D10070C39A = {
+ isa = PBXGroup;
+ children = (
+ 60FF7A9C1954A5C5007DD14C /* Podspec Metadata */,
+ 6003F593195388D20070C39A /* Example for FirebaseFunctions */,
+ 7C70065B20572C1B007A5573 /* TestUtils */,
+ 7CFC6F8720570365005630C2 /* IntegrationTests */,
+ 6003F5B5195388D20070C39A /* Tests */,
+ 6003F58C195388D20070C39A /* Frameworks */,
+ 6003F58B195388D20070C39A /* Products */,
+ 004DC60D1A62294B06E83453 /* Pods */,
+ 7CBFAA92205702AC00A65866 /* FirebaseFunctions_Tests copy-Info.plist */,
+ );
+ sourceTree = "<group>";
+ };
+ 6003F58B195388D20070C39A /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 6003F58A195388D20070C39A /* FirebaseFunctions_Example.app */,
+ 6003F5AE195388D20070C39A /* FirebaseFunctions_Tests.xctest */,
+ 7CBFAA91205702AB00A65866 /* FirebaseFunctions_IntegrationTests.xctest */,
+ );
+ name = Products;
+ sourceTree = "<group>";
+ };
+ 6003F58C195388D20070C39A /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ 6003F58D195388D20070C39A /* Foundation.framework */,
+ 6003F58F195388D20070C39A /* CoreGraphics.framework */,
+ 6003F591195388D20070C39A /* UIKit.framework */,
+ 6003F5AF195388D20070C39A /* XCTest.framework */,
+ B954281766FFD7C7C9970E9B /* Pods_FirebaseFunctions_Example.framework */,
+ A19679D6C21E3206B4A13697 /* Pods_FirebaseFunctions_Tests.framework */,
+ 0A4C1781DC97515D32B33DBD /* Pods_FirebaseFunctions_IntegrationTests.framework */,
+ );
+ name = Frameworks;
+ sourceTree = "<group>";
+ };
+ 6003F593195388D20070C39A /* Example for FirebaseFunctions */ = {
+ isa = PBXGroup;
+ children = (
+ 6003F59C195388D20070C39A /* FIRAppDelegate.h */,
+ 6003F59D195388D20070C39A /* FIRAppDelegate.m */,
+ 873B8AEA1B1F5CCA007FD442 /* Main.storyboard */,
+ 6003F5A5195388D20070C39A /* FIRViewController.h */,
+ 6003F5A6195388D20070C39A /* FIRViewController.m */,
+ 71719F9D1E33DC2100824A3D /* LaunchScreen.storyboard */,
+ 6003F5A8195388D20070C39A /* Images.xcassets */,
+ 6003F594195388D20070C39A /* Supporting Files */,
+ );
+ name = "Example for FirebaseFunctions";
+ path = FirebaseFunctions;
+ sourceTree = "<group>";
+ };
+ 6003F594195388D20070C39A /* Supporting Files */ = {
+ isa = PBXGroup;
+ children = (
+ 6003F595195388D20070C39A /* FirebaseFunctions-Info.plist */,
+ 6003F596195388D20070C39A /* InfoPlist.strings */,
+ 6003F599195388D20070C39A /* main.m */,
+ );
+ name = "Supporting Files";
+ sourceTree = "<group>";
+ };
+ 6003F5B5195388D20070C39A /* Tests */ = {
+ isa = PBXGroup;
+ children = (
+ 6003F5BB195388D20070C39A /* FIRFunctionsTests.m */,
+ 7C58B0391F1441F0005ED954 /* FUNSerializerTests.m */,
+ 6003F5B6195388D20070C39A /* Supporting Files */,
+ );
+ path = Tests;
+ sourceTree = "<group>";
+ };
+ 6003F5B6195388D20070C39A /* Supporting Files */ = {
+ isa = PBXGroup;
+ children = (
+ 6003F5B7195388D20070C39A /* Tests-Info.plist */,
+ 6003F5B8195388D20070C39A /* InfoPlist.strings */,
+ );
+ name = "Supporting Files";
+ sourceTree = "<group>";
+ };
+ 60FF7A9C1954A5C5007DD14C /* Podspec Metadata */ = {
+ isa = PBXGroup;
+ children = (
+ 7BEA793625C8DE7C8EC60006 /* FirebaseFunctions.podspec */,
+ E0A8D570636E99E7C3396DF8 /* README.md */,
+ F1F2A7C03C10A3A03F9502B8 /* LICENSE */,
+ );
+ name = "Podspec Metadata";
+ sourceTree = "<group>";
+ };
+ 7C70065B20572C1B007A5573 /* TestUtils */ = {
+ isa = PBXGroup;
+ children = (
+ 7CF5630A1F1FE76700FEE1F4 /* FUNFakeApp.h */,
+ 7CF563081F1FE70600FEE1F4 /* FUNFakeApp.m */,
+ 7CF3BEC11F97EE2F00B16B6E /* FUNFakeInstanceID.h */,
+ 7CF3BEC01F97EE2F00B16B6E /* FUNFakeInstanceID.m */,
+ );
+ path = TestUtils;
+ sourceTree = "<group>";
+ };
+ 7CFC6F8720570365005630C2 /* IntegrationTests */ = {
+ isa = PBXGroup;
+ children = (
+ 7C9BFD3F1F10A12F001A19ED /* FIRIntegrationTests.m */,
+ );
+ path = IntegrationTests;
+ sourceTree = "<group>";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+ 6003F589195388D20070C39A /* FirebaseFunctions_Example */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 6003F5BF195388D20070C39A /* Build configuration list for PBXNativeTarget "FirebaseFunctions_Example" */;
+ buildPhases = (
+ C1B1E870DA1D732EC2F1F946 /* [CP] Check Pods Manifest.lock */,
+ 6003F586195388D20070C39A /* Sources */,
+ 6003F587195388D20070C39A /* Frameworks */,
+ 6003F588195388D20070C39A /* Resources */,
+ BB7A7FC40453827CC7541BB7 /* [CP] Embed Pods Frameworks */,
+ 0409BC62B2C369D14416E480 /* [CP] Copy Pods Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = FirebaseFunctions_Example;
+ productName = FirebaseFunctions;
+ productReference = 6003F58A195388D20070C39A /* FirebaseFunctions_Example.app */;
+ productType = "com.apple.product-type.application";
+ };
+ 6003F5AD195388D20070C39A /* FirebaseFunctions_Tests */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 6003F5C2195388D20070C39A /* Build configuration list for PBXNativeTarget "FirebaseFunctions_Tests" */;
+ buildPhases = (
+ D9E332DA9E27ECA02C2B45D2 /* [CP] Check Pods Manifest.lock */,
+ 6003F5AA195388D20070C39A /* Sources */,
+ 6003F5AB195388D20070C39A /* Frameworks */,
+ 6003F5AC195388D20070C39A /* Resources */,
+ 3B7914C40161367B84BC9E52 /* [CP] Embed Pods Frameworks */,
+ F0A35EE9B43049BA133AD88F /* [CP] Copy Pods Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ 6003F5B4195388D20070C39A /* PBXTargetDependency */,
+ );
+ name = FirebaseFunctions_Tests;
+ productName = FirebaseFunctionsTests;
+ productReference = 6003F5AE195388D20070C39A /* FirebaseFunctions_Tests.xctest */;
+ productType = "com.apple.product-type.bundle.unit-test";
+ };
+ 7CBFAA7D205702AB00A65866 /* FirebaseFunctions_IntegrationTests */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 7CBFAA8E205702AB00A65866 /* Build configuration list for PBXNativeTarget "FirebaseFunctions_IntegrationTests" */;
+ buildPhases = (
+ 7CBFAA80205702AB00A65866 /* [CP] Check Pods Manifest.lock */,
+ 7CBFAA81205702AB00A65866 /* Sources */,
+ 7CBFAA85205702AB00A65866 /* Frameworks */,
+ 7CBFAA8A205702AB00A65866 /* Resources */,
+ 7CBFAA8C205702AB00A65866 /* [CP] Embed Pods Frameworks */,
+ 7CBFAA8D205702AB00A65866 /* [CP] Copy Pods Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ 7CBFAA7E205702AB00A65866 /* PBXTargetDependency */,
+ );
+ name = FirebaseFunctions_IntegrationTests;
+ productName = FirebaseFunctionsTests;
+ productReference = 7CBFAA91205702AB00A65866 /* FirebaseFunctions_IntegrationTests.xctest */;
+ productType = "com.apple.product-type.bundle.unit-test";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ 6003F582195388D10070C39A /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ CLASSPREFIX = FIR;
+ LastUpgradeCheck = 0720;
+ ORGANIZATIONNAME = "Google, Inc.";
+ TargetAttributes = {
+ 6003F5AD195388D20070C39A = {
+ TestTargetID = 6003F589195388D20070C39A;
+ };
+ };
+ };
+ buildConfigurationList = 6003F585195388D10070C39A /* Build configuration list for PBXProject "FirebaseFunctions" */;
+ compatibilityVersion = "Xcode 3.2";
+ developmentRegion = English;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ Base,
+ );
+ mainGroup = 6003F581195388D10070C39A;
+ productRefGroup = 6003F58B195388D20070C39A /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ 6003F589195388D20070C39A /* FirebaseFunctions_Example */,
+ 6003F5AD195388D20070C39A /* FirebaseFunctions_Tests */,
+ 7CBFAA7D205702AB00A65866 /* FirebaseFunctions_IntegrationTests */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+ 6003F588195388D20070C39A /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 873B8AEB1B1F5CCA007FD442 /* Main.storyboard in Resources */,
+ 71719F9F1E33DC2100824A3D /* LaunchScreen.storyboard in Resources */,
+ 6003F5A9195388D20070C39A /* Images.xcassets in Resources */,
+ 6003F598195388D20070C39A /* InfoPlist.strings in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 6003F5AC195388D20070C39A /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 6003F5BA195388D20070C39A /* InfoPlist.strings in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 7CBFAA8A205702AB00A65866 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 7CBFAA8B205702AB00A65866 /* InfoPlist.strings in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXShellScriptBuildPhase section */
+ 0409BC62B2C369D14416E480 /* [CP] Copy Pods Resources */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ );
+ name = "[CP] Copy Pods Resources";
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-FirebaseFunctions_Example/Pods-FirebaseFunctions_Example-resources.sh\"\n";
+ showEnvVarsInLog = 0;
+ };
+ 3B7914C40161367B84BC9E52 /* [CP] Embed Pods Frameworks */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ );
+ name = "[CP] Embed Pods Frameworks";
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-FirebaseFunctions_Tests/Pods-FirebaseFunctions_Tests-frameworks.sh\"\n";
+ showEnvVarsInLog = 0;
+ };
+ 7CBFAA80205702AB00A65866 /* [CP] Check Pods Manifest.lock */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
+ "${PODS_ROOT}/Manifest.lock",
+ );
+ name = "[CP] Check Pods Manifest.lock";
+ outputPaths = (
+ "$(DERIVED_FILE_DIR)/Pods-FirebaseFunctions_IntegrationTests-checkManifestLockResult.txt",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
+ showEnvVarsInLog = 0;
+ };
+ 7CBFAA8C205702AB00A65866 /* [CP] Embed Pods Frameworks */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ );
+ name = "[CP] Embed Pods Frameworks";
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-FirebaseFunctions_IntegrationTests/Pods-FirebaseFunctions_IntegrationTests-frameworks.sh\"\n";
+ showEnvVarsInLog = 0;
+ };
+ 7CBFAA8D205702AB00A65866 /* [CP] Copy Pods Resources */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ );
+ name = "[CP] Copy Pods Resources";
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-FirebaseFunctions_IntegrationTests/Pods-FirebaseFunctions_IntegrationTests-resources.sh\"\n";
+ showEnvVarsInLog = 0;
+ };
+ BB7A7FC40453827CC7541BB7 /* [CP] Embed Pods Frameworks */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ "${SRCROOT}/Pods/Target Support Files/Pods-FirebaseFunctions_Example/Pods-FirebaseFunctions_Example-frameworks.sh",
+ "${BUILT_PRODUCTS_DIR}/GTMSessionFetcher/GTMSessionFetcher.framework",
+ "${BUILT_PRODUCTS_DIR}/GoogleToolboxForMac/GoogleToolboxForMac.framework",
+ );
+ name = "[CP] Embed Pods Frameworks";
+ outputPaths = (
+ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GTMSessionFetcher.framework",
+ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleToolboxForMac.framework",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-FirebaseFunctions_Example/Pods-FirebaseFunctions_Example-frameworks.sh\"\n";
+ showEnvVarsInLog = 0;
+ };
+ C1B1E870DA1D732EC2F1F946 /* [CP] Check Pods Manifest.lock */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
+ "${PODS_ROOT}/Manifest.lock",
+ );
+ name = "[CP] Check Pods Manifest.lock";
+ outputPaths = (
+ "$(DERIVED_FILE_DIR)/Pods-FirebaseFunctions_Example-checkManifestLockResult.txt",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
+ showEnvVarsInLog = 0;
+ };
+ D9E332DA9E27ECA02C2B45D2 /* [CP] Check Pods Manifest.lock */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
+ "${PODS_ROOT}/Manifest.lock",
+ );
+ name = "[CP] Check Pods Manifest.lock";
+ outputPaths = (
+ "$(DERIVED_FILE_DIR)/Pods-FirebaseFunctions_Tests-checkManifestLockResult.txt",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
+ showEnvVarsInLog = 0;
+ };
+ F0A35EE9B43049BA133AD88F /* [CP] Copy Pods Resources */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ );
+ name = "[CP] Copy Pods Resources";
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-FirebaseFunctions_Tests/Pods-FirebaseFunctions_Tests-resources.sh\"\n";
+ showEnvVarsInLog = 0;
+ };
+/* End PBXShellScriptBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ 6003F586195388D20070C39A /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 7CF563091F1FE70600FEE1F4 /* FUNFakeApp.m in Sources */,
+ 6003F59E195388D20070C39A /* FIRAppDelegate.m in Sources */,
+ 6003F5A7195388D20070C39A /* FIRViewController.m in Sources */,
+ 7CF3BEC21F97EE2F00B16B6E /* FUNFakeInstanceID.m in Sources */,
+ 6003F59A195388D20070C39A /* main.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 6003F5AA195388D20070C39A /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 7C58B03A1F1441F0005ED954 /* FUNSerializerTests.m in Sources */,
+ 6003F5BC195388D20070C39A /* FIRFunctionsTests.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 7CBFAA81205702AB00A65866 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 7CBFAA82205702AB00A65866 /* FIRIntegrationTests.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXTargetDependency section */
+ 6003F5B4195388D20070C39A /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 6003F589195388D20070C39A /* FirebaseFunctions_Example */;
+ targetProxy = 6003F5B3195388D20070C39A /* PBXContainerItemProxy */;
+ };
+ 7CBFAA7E205702AB00A65866 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 6003F589195388D20070C39A /* FirebaseFunctions_Example */;
+ targetProxy = 7CBFAA7F205702AB00A65866 /* PBXContainerItemProxy */;
+ };
+/* End PBXTargetDependency section */
+
+/* Begin PBXVariantGroup section */
+ 6003F596195388D20070C39A /* InfoPlist.strings */ = {
+ isa = PBXVariantGroup;
+ children = (
+ 6003F597195388D20070C39A /* en */,
+ );
+ name = InfoPlist.strings;
+ sourceTree = "<group>";
+ };
+ 6003F5B8195388D20070C39A /* InfoPlist.strings */ = {
+ isa = PBXVariantGroup;
+ children = (
+ 6003F5B9195388D20070C39A /* en */,
+ );
+ name = InfoPlist.strings;
+ sourceTree = "<group>";
+ };
+ 71719F9D1E33DC2100824A3D /* LaunchScreen.storyboard */ = {
+ isa = PBXVariantGroup;
+ children = (
+ 71719F9E1E33DC2100824A3D /* Base */,
+ );
+ name = LaunchScreen.storyboard;
+ sourceTree = "<group>";
+ };
+/* End PBXVariantGroup section */
+
+/* Begin XCBuildConfiguration section */
+ 6003F5BD195388D20070C39A /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ COPY_PHASE_STRIP = NO;
+ ENABLE_TESTABILITY = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_SYMBOLS_PRIVATE_EXTERN = NO;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 8.3;
+ ONLY_ACTIVE_ARCH = YES;
+ SDKROOT = iphoneos;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Debug;
+ };
+ 6003F5BE195388D20070C39A /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ COPY_PHASE_STRIP = YES;
+ ENABLE_NS_ASSERTIONS = NO;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 8.3;
+ SDKROOT = iphoneos;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ VALIDATE_PRODUCT = YES;
+ };
+ name = Release;
+ };
+ 6003F5C0195388D20070C39A /* Debug */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 59519EC771AF4770F85B6534 /* Pods-FirebaseFunctions_Example.debug.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ GCC_PRECOMPILE_PREFIX_HEADER = NO;
+ INFOPLIST_FILE = "FirebaseFunctions/FirebaseFunctions-Info.plist";
+ MODULE_NAME = ExampleApp;
+ PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ WRAPPER_EXTENSION = app;
+ };
+ name = Debug;
+ };
+ 6003F5C1195388D20070C39A /* Release */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = E2E6249788F1618471335BE7 /* Pods-FirebaseFunctions_Example.release.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ GCC_PRECOMPILE_PREFIX_HEADER = NO;
+ INFOPLIST_FILE = "FirebaseFunctions/FirebaseFunctions-Info.plist";
+ MODULE_NAME = ExampleApp;
+ PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ WRAPPER_EXTENSION = app;
+ };
+ name = Release;
+ };
+ 6003F5C3195388D20070C39A /* Debug */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = FFE894EDD87F1EF24F0ED8DD /* Pods-FirebaseFunctions_Tests.debug.xcconfig */;
+ buildSettings = {
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ FRAMEWORK_SEARCH_PATHS = (
+ "$(SDKROOT)/Developer/Library/Frameworks",
+ "$(inherited)",
+ "$(DEVELOPER_FRAMEWORKS_DIR)",
+ );
+ GCC_PRECOMPILE_PREFIX_HEADER = NO;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ HEADER_SEARCH_PATHS = "$(SRCROOT)/../FirebaseFunctions";
+ INFOPLIST_FILE = "Tests/Tests-Info.plist";
+ PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/FirebaseFunctions_Example.app/FirebaseFunctions_Example";
+ WRAPPER_EXTENSION = xctest;
+ };
+ name = Debug;
+ };
+ 6003F5C4195388D20070C39A /* Release */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 5B43C813C653DE3455952E9B /* Pods-FirebaseFunctions_Tests.release.xcconfig */;
+ buildSettings = {
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ FRAMEWORK_SEARCH_PATHS = (
+ "$(SDKROOT)/Developer/Library/Frameworks",
+ "$(inherited)",
+ "$(DEVELOPER_FRAMEWORKS_DIR)",
+ );
+ GCC_PRECOMPILE_PREFIX_HEADER = NO;
+ HEADER_SEARCH_PATHS = "$(SRCROOT)/../FirebaseFunctions";
+ INFOPLIST_FILE = "Tests/Tests-Info.plist";
+ PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/FirebaseFunctions_Example.app/FirebaseFunctions_Example";
+ WRAPPER_EXTENSION = xctest;
+ };
+ name = Release;
+ };
+ 7CBFAA8F205702AB00A65866 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 501CFEC7487BD4C12FCEAB03 /* Pods-FirebaseFunctions_IntegrationTests.debug.xcconfig */;
+ buildSettings = {
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ FRAMEWORK_SEARCH_PATHS = (
+ "$(SDKROOT)/Developer/Library/Frameworks",
+ "$(inherited)",
+ "$(DEVELOPER_FRAMEWORKS_DIR)",
+ );
+ GCC_PRECOMPILE_PREFIX_HEADER = NO;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ HEADER_SEARCH_PATHS = "$(SRCROOT)/../FirebaseFunctions";
+ INFOPLIST_FILE = "IntegrationTests/IntegrationTests-Info.plist";
+ PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/FirebaseFunctions_Example.app/FirebaseFunctions_Example";
+ WRAPPER_EXTENSION = xctest;
+ };
+ name = Debug;
+ };
+ 7CBFAA90205702AB00A65866 /* Release */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = F840F9EDCB950E7509527AB7 /* Pods-FirebaseFunctions_IntegrationTests.release.xcconfig */;
+ buildSettings = {
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ FRAMEWORK_SEARCH_PATHS = (
+ "$(SDKROOT)/Developer/Library/Frameworks",
+ "$(inherited)",
+ "$(DEVELOPER_FRAMEWORKS_DIR)",
+ );
+ GCC_PRECOMPILE_PREFIX_HEADER = NO;
+ HEADER_SEARCH_PATHS = "$(SRCROOT)/../FirebaseFunctions";
+ INFOPLIST_FILE = "IntegrationTests/IntegrationTests-Info.plist";
+ PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/FirebaseFunctions_Example.app/FirebaseFunctions_Example";
+ WRAPPER_EXTENSION = xctest;
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ 6003F585195388D10070C39A /* Build configuration list for PBXProject "FirebaseFunctions" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 6003F5BD195388D20070C39A /* Debug */,
+ 6003F5BE195388D20070C39A /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 6003F5BF195388D20070C39A /* Build configuration list for PBXNativeTarget "FirebaseFunctions_Example" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 6003F5C0195388D20070C39A /* Debug */,
+ 6003F5C1195388D20070C39A /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 6003F5C2195388D20070C39A /* Build configuration list for PBXNativeTarget "FirebaseFunctions_Tests" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 6003F5C3195388D20070C39A /* Debug */,
+ 6003F5C4195388D20070C39A /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 7CBFAA8E205702AB00A65866 /* Build configuration list for PBXNativeTarget "FirebaseFunctions_IntegrationTests" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 7CBFAA8F205702AB00A65866 /* Debug */,
+ 7CBFAA90205702AB00A65866 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = 6003F582195388D10070C39A /* Project object */;
+}
diff --git a/Functions/Example/FirebaseFunctions.xcodeproj/xcshareddata/xcschemes/FirebaseFunctions-Example.xcscheme b/Functions/Example/FirebaseFunctions.xcodeproj/xcshareddata/xcschemes/FirebaseFunctions-Example.xcscheme
new file mode 100644
index 0000000..b9983e8
--- /dev/null
+++ b/Functions/Example/FirebaseFunctions.xcodeproj/xcshareddata/xcschemes/FirebaseFunctions-Example.xcscheme
@@ -0,0 +1,113 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+ LastUpgradeVersion = "0720"
+ version = "1.3">
+ <BuildAction
+ parallelizeBuildables = "YES"
+ buildImplicitDependencies = "YES">
+ <BuildActionEntries>
+ <BuildActionEntry
+ buildForTesting = "YES"
+ buildForRunning = "YES"
+ buildForProfiling = "YES"
+ buildForArchiving = "YES"
+ buildForAnalyzing = "YES">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "6003F589195388D20070C39A"
+ BuildableName = "FirebaseFunctions_Example.app"
+ BlueprintName = "FirebaseFunctions_Example"
+ ReferencedContainer = "container:FirebaseFunctions.xcodeproj">
+ </BuildableReference>
+ </BuildActionEntry>
+ </BuildActionEntries>
+ </BuildAction>
+ <TestAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ language = ""
+ shouldUseLaunchSchemeArgsEnv = "YES">
+ <Testables>
+ <TestableReference
+ skipped = "NO">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "6003F5AD195388D20070C39A"
+ BuildableName = "FirebaseFunctions_Tests.xctest"
+ BlueprintName = "FirebaseFunctions_Tests"
+ ReferencedContainer = "container:FirebaseFunctions.xcodeproj">
+ </BuildableReference>
+ </TestableReference>
+ <TestableReference
+ skipped = "NO">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "7CBFAA7D205702AB00A65866"
+ BuildableName = "FirebaseFunctions_IntegrationTests.xctest"
+ BlueprintName = "FirebaseFunctions_IntegrationTests"
+ ReferencedContainer = "container:FirebaseFunctions.xcodeproj">
+ </BuildableReference>
+ </TestableReference>
+ </Testables>
+ <MacroExpansion>
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "6003F589195388D20070C39A"
+ BuildableName = "FirebaseFunctions_Example.app"
+ BlueprintName = "FirebaseFunctions_Example"
+ ReferencedContainer = "container:FirebaseFunctions.xcodeproj">
+ </BuildableReference>
+ </MacroExpansion>
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </TestAction>
+ <LaunchAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ language = ""
+ launchStyle = "0"
+ useCustomWorkingDirectory = "NO"
+ ignoresPersistentStateOnLaunch = "NO"
+ debugDocumentVersioning = "YES"
+ debugServiceExtension = "internal"
+ allowLocationSimulation = "YES">
+ <BuildableProductRunnable
+ runnableDebuggingMode = "0">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "6003F589195388D20070C39A"
+ BuildableName = "FirebaseFunctions_Example.app"
+ BlueprintName = "FirebaseFunctions_Example"
+ ReferencedContainer = "container:FirebaseFunctions.xcodeproj">
+ </BuildableReference>
+ </BuildableProductRunnable>
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </LaunchAction>
+ <ProfileAction
+ buildConfiguration = "Release"
+ shouldUseLaunchSchemeArgsEnv = "YES"
+ savedToolIdentifier = ""
+ useCustomWorkingDirectory = "NO"
+ debugDocumentVersioning = "YES">
+ <BuildableProductRunnable
+ runnableDebuggingMode = "0">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "6003F589195388D20070C39A"
+ BuildableName = "FirebaseFunctions_Example.app"
+ BlueprintName = "FirebaseFunctions_Example"
+ ReferencedContainer = "container:FirebaseFunctions.xcodeproj">
+ </BuildableReference>
+ </BuildableProductRunnable>
+ </ProfileAction>
+ <AnalyzeAction
+ buildConfiguration = "Debug">
+ </AnalyzeAction>
+ <ArchiveAction
+ buildConfiguration = "Release"
+ revealArchiveInOrganizer = "YES">
+ </ArchiveAction>
+</Scheme>
diff --git a/Functions/Example/FirebaseFunctions.xcodeproj/xcshareddata/xcschemes/FirebaseFunctions_IntegrationTests.xcscheme b/Functions/Example/FirebaseFunctions.xcodeproj/xcshareddata/xcschemes/FirebaseFunctions_IntegrationTests.xcscheme
new file mode 100644
index 0000000..46914fb
--- /dev/null
+++ b/Functions/Example/FirebaseFunctions.xcodeproj/xcshareddata/xcschemes/FirebaseFunctions_IntegrationTests.xcscheme
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+ LastUpgradeVersion = "0930"
+ version = "1.3">
+ <BuildAction
+ parallelizeBuildables = "YES"
+ buildImplicitDependencies = "YES">
+ </BuildAction>
+ <TestAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ shouldUseLaunchSchemeArgsEnv = "YES">
+ <Testables>
+ <TestableReference
+ skipped = "NO">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "7CBFAA7D205702AB00A65866"
+ BuildableName = "FirebaseFunctions_IntegrationTests.xctest"
+ BlueprintName = "FirebaseFunctions_IntegrationTests"
+ ReferencedContainer = "container:FirebaseFunctions.xcodeproj">
+ </BuildableReference>
+ </TestableReference>
+ </Testables>
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </TestAction>
+ <LaunchAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ launchStyle = "0"
+ useCustomWorkingDirectory = "NO"
+ ignoresPersistentStateOnLaunch = "NO"
+ debugDocumentVersioning = "YES"
+ debugServiceExtension = "internal"
+ allowLocationSimulation = "YES">
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </LaunchAction>
+ <ProfileAction
+ buildConfiguration = "Release"
+ shouldUseLaunchSchemeArgsEnv = "YES"
+ savedToolIdentifier = ""
+ useCustomWorkingDirectory = "NO"
+ debugDocumentVersioning = "YES">
+ </ProfileAction>
+ <AnalyzeAction
+ buildConfiguration = "Debug">
+ </AnalyzeAction>
+ <ArchiveAction
+ buildConfiguration = "Release"
+ revealArchiveInOrganizer = "YES">
+ </ArchiveAction>
+</Scheme>
diff --git a/Functions/Example/FirebaseFunctions.xcodeproj/xcshareddata/xcschemes/FirebaseFunctions_Tests.xcscheme b/Functions/Example/FirebaseFunctions.xcodeproj/xcshareddata/xcschemes/FirebaseFunctions_Tests.xcscheme
new file mode 100644
index 0000000..2380cb8
--- /dev/null
+++ b/Functions/Example/FirebaseFunctions.xcodeproj/xcshareddata/xcschemes/FirebaseFunctions_Tests.xcscheme
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+ LastUpgradeVersion = "0930"
+ version = "1.3">
+ <BuildAction
+ parallelizeBuildables = "YES"
+ buildImplicitDependencies = "YES">
+ </BuildAction>
+ <TestAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ shouldUseLaunchSchemeArgsEnv = "YES">
+ <Testables>
+ <TestableReference
+ skipped = "NO">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "6003F5AD195388D20070C39A"
+ BuildableName = "FirebaseFunctions_Tests.xctest"
+ BlueprintName = "FirebaseFunctions_Tests"
+ ReferencedContainer = "container:FirebaseFunctions.xcodeproj">
+ </BuildableReference>
+ </TestableReference>
+ </Testables>
+ <MacroExpansion>
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "6003F589195388D20070C39A"
+ BuildableName = "FirebaseFunctions_Example.app"
+ BlueprintName = "FirebaseFunctions_Example"
+ ReferencedContainer = "container:FirebaseFunctions.xcodeproj">
+ </BuildableReference>
+ </MacroExpansion>
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </TestAction>
+ <LaunchAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ launchStyle = "0"
+ useCustomWorkingDirectory = "NO"
+ ignoresPersistentStateOnLaunch = "NO"
+ debugDocumentVersioning = "YES"
+ debugServiceExtension = "internal"
+ allowLocationSimulation = "YES">
+ <BuildableProductRunnable
+ runnableDebuggingMode = "0">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "6003F589195388D20070C39A"
+ BuildableName = "FirebaseFunctions_Example.app"
+ BlueprintName = "FirebaseFunctions_Example"
+ ReferencedContainer = "container:FirebaseFunctions.xcodeproj">
+ </BuildableReference>
+ </BuildableProductRunnable>
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </LaunchAction>
+ <ProfileAction
+ buildConfiguration = "Release"
+ shouldUseLaunchSchemeArgsEnv = "YES"
+ savedToolIdentifier = ""
+ useCustomWorkingDirectory = "NO"
+ debugDocumentVersioning = "YES">
+ <MacroExpansion>
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "6003F589195388D20070C39A"
+ BuildableName = "FirebaseFunctions_Example.app"
+ BlueprintName = "FirebaseFunctions_Example"
+ ReferencedContainer = "container:FirebaseFunctions.xcodeproj">
+ </BuildableReference>
+ </MacroExpansion>
+ </ProfileAction>
+ <AnalyzeAction
+ buildConfiguration = "Debug">
+ </AnalyzeAction>
+ <ArchiveAction
+ buildConfiguration = "Release"
+ revealArchiveInOrganizer = "YES">
+ </ArchiveAction>
+</Scheme>
diff --git a/Functions/Example/FirebaseFunctions/Base.lproj/LaunchScreen.storyboard b/Functions/Example/FirebaseFunctions/Base.lproj/LaunchScreen.storyboard
new file mode 100644
index 0000000..66a7681
--- /dev/null
+++ b/Functions/Example/FirebaseFunctions/Base.lproj/LaunchScreen.storyboard
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="16C67" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" initialViewController="01J-lp-oVM">
+ <dependencies>
+ <deployment identifier="iOS"/>
+ <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
+ </dependencies>
+ <scenes>
+ <!--View Controller-->
+ <scene sceneID="EHf-IW-A2E">
+ <objects>
+ <viewController id="01J-lp-oVM" sceneMemberID="viewController">
+ <layoutGuides>
+ <viewControllerLayoutGuide type="top" id="Llm-lL-Icb"/>
+ <viewControllerLayoutGuide type="bottom" id="xb3-aO-Qok"/>
+ </layoutGuides>
+ <view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
+ <rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+ <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
+ </view>
+ </viewController>
+ <placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
+ </objects>
+ <point key="canvasLocation" x="53" y="375"/>
+ </scene>
+ </scenes>
+</document>
diff --git a/Functions/Example/FirebaseFunctions/Base.lproj/Main.storyboard b/Functions/Example/FirebaseFunctions/Base.lproj/Main.storyboard
new file mode 100644
index 0000000..d164a23
--- /dev/null
+++ b/Functions/Example/FirebaseFunctions/Base.lproj/Main.storyboard
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="7706" systemVersion="14D136" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="whP-gf-Uak">
+ <dependencies>
+ <deployment identifier="iOS"/>
+ <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="7703"/>
+ </dependencies>
+ <scenes>
+ <!--View Controller-->
+ <scene sceneID="wQg-tq-qST">
+ <objects>
+ <viewController id="whP-gf-Uak" customClass="FIRViewController" sceneMemberID="viewController">
+ <layoutGuides>
+ <viewControllerLayoutGuide type="top" id="uEw-UM-LJ8"/>
+ <viewControllerLayoutGuide type="bottom" id="Mvr-aV-6Um"/>
+ </layoutGuides>
+ <view key="view" contentMode="scaleToFill" id="TpU-gO-2f1">
+ <rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+ <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
+ </view>
+ </viewController>
+ <placeholder placeholderIdentifier="IBFirstResponder" id="tc2-Qw-aMS" userLabel="First Responder" sceneMemberID="firstResponder"/>
+ </objects>
+ <point key="canvasLocation" x="305" y="433"/>
+ </scene>
+ </scenes>
+</document>
diff --git a/Functions/Example/FirebaseFunctions/FIRAppDelegate.h b/Functions/Example/FirebaseFunctions/FIRAppDelegate.h
new file mode 100644
index 0000000..8349c18
--- /dev/null
+++ b/Functions/Example/FirebaseFunctions/FIRAppDelegate.h
@@ -0,0 +1,21 @@
+// Copyright 2017 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 UIKit;
+
+@interface FIRAppDelegate : UIResponder <UIApplicationDelegate>
+
+@property(strong, nonatomic) UIWindow *window;
+
+@end
diff --git a/Functions/Example/FirebaseFunctions/FIRAppDelegate.m b/Functions/Example/FirebaseFunctions/FIRAppDelegate.m
new file mode 100644
index 0000000..9568d06
--- /dev/null
+++ b/Functions/Example/FirebaseFunctions/FIRAppDelegate.m
@@ -0,0 +1,55 @@
+// Copyright 2017 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 "FIRAppDelegate.h"
+
+@implementation FIRAppDelegate
+
+- (BOOL)application:(UIApplication *)application
+ didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
+ // Override point for customization after application launch.
+ return YES;
+}
+
+- (void)applicationWillResignActive:(UIApplication *)application {
+ // Sent when the application is about to move from active to inactive state. This can occur for
+ // certain types of temporary interruptions (such as an incoming phone call or SMS message) or
+ // when the user quits the application and it begins the transition to the background state. Use
+ // this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates.
+ // Games should use this method to pause the game.
+}
+
+- (void)applicationDidEnterBackground:(UIApplication *)application {
+ // Use this method to release shared resources, save user data, invalidate timers, and store
+ // enough application state information to restore your application to its current state in case
+ // it is terminated later. If your application supports background execution, this method is
+ // called instead of applicationWillTerminate: when the user quits.
+}
+
+- (void)applicationWillEnterForeground:(UIApplication *)application {
+ // Called as part of the transition from the background to the inactive state; here you can undo
+ // many of the changes made on entering the background.
+}
+
+- (void)applicationDidBecomeActive:(UIApplication *)application {
+ // Restart any tasks that were paused (or not yet started) while the application was inactive. If
+ // the application was previously in the background, optionally refresh the user interface.
+}
+
+- (void)applicationWillTerminate:(UIApplication *)application {
+ // Called when the application is about to terminate. Save data if appropriate. See also
+ // applicationDidEnterBackground:.
+}
+
+@end
diff --git a/Functions/Example/FirebaseFunctions/FIRViewController.h b/Functions/Example/FirebaseFunctions/FIRViewController.h
new file mode 100644
index 0000000..5c4dff5
--- /dev/null
+++ b/Functions/Example/FirebaseFunctions/FIRViewController.h
@@ -0,0 +1,19 @@
+// Copyright 2017 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 UIKit;
+
+@interface FIRViewController : UIViewController
+
+@end
diff --git a/Functions/Example/FirebaseFunctions/FIRViewController.m b/Functions/Example/FirebaseFunctions/FIRViewController.m
new file mode 100644
index 0000000..027aabf
--- /dev/null
+++ b/Functions/Example/FirebaseFunctions/FIRViewController.m
@@ -0,0 +1,33 @@
+// Copyright 2017 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 "FIRViewController.h"
+
+@interface FIRViewController ()
+
+@end
+
+@implementation FIRViewController
+
+- (void)viewDidLoad {
+ [super viewDidLoad];
+ // Do any additional setup after loading the view, typically from a nib.
+}
+
+- (void)didReceiveMemoryWarning {
+ [super didReceiveMemoryWarning];
+ // Dispose of any resources that can be recreated.
+}
+
+@end
diff --git a/Functions/Example/FirebaseFunctions/FirebaseFunctions-Info.plist b/Functions/Example/FirebaseFunctions/FirebaseFunctions-Info.plist
new file mode 100644
index 0000000..fc26896
--- /dev/null
+++ b/Functions/Example/FirebaseFunctions/FirebaseFunctions-Info.plist
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>en</string>
+ <key>CFBundleDisplayName</key>
+ <string>${PRODUCT_NAME}</string>
+ <key>CFBundleExecutable</key>
+ <string>${EXECUTABLE_NAME}</string>
+ <key>CFBundleIdentifier</key>
+ <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>${PRODUCT_NAME}</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>1.0</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>CFBundleVersion</key>
+ <string>1.0</string>
+ <key>LSRequiresIPhoneOS</key>
+ <true/>
+ <key>NSAppTransportSecurity</key>
+ <dict>
+ <key>NSAllowsArbitraryLoads</key>
+ <true/>
+ </dict>
+ <key>UILaunchStoryboardName</key>
+ <string>LaunchScreen</string>
+ <key>UIMainStoryboardFile</key>
+ <string>Main</string>
+ <key>UIRequiredDeviceCapabilities</key>
+ <array>
+ <string>armv7</string>
+ </array>
+ <key>UISupportedInterfaceOrientations</key>
+ <array>
+ <string>UIInterfaceOrientationPortrait</string>
+ <string>UIInterfaceOrientationLandscapeLeft</string>
+ <string>UIInterfaceOrientationLandscapeRight</string>
+ </array>
+ <key>UISupportedInterfaceOrientations~ipad</key>
+ <array>
+ <string>UIInterfaceOrientationPortrait</string>
+ <string>UIInterfaceOrientationPortraitUpsideDown</string>
+ <string>UIInterfaceOrientationLandscapeLeft</string>
+ <string>UIInterfaceOrientationLandscapeRight</string>
+ </array>
+</dict>
+</plist>
diff --git a/Functions/Example/FirebaseFunctions/Images.xcassets/AppIcon.appiconset/Contents.json b/Functions/Example/FirebaseFunctions/Images.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 0000000..d7070bc
--- /dev/null
+++ b/Functions/Example/FirebaseFunctions/Images.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,93 @@
+{
+ "images" : [
+ {
+ "idiom" : "iphone",
+ "size" : "20x20",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "iphone",
+ "size" : "20x20",
+ "scale" : "3x"
+ },
+ {
+ "idiom" : "iphone",
+ "size" : "29x29",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "iphone",
+ "size" : "29x29",
+ "scale" : "3x"
+ },
+ {
+ "idiom" : "iphone",
+ "size" : "40x40",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "iphone",
+ "size" : "40x40",
+ "scale" : "3x"
+ },
+ {
+ "idiom" : "iphone",
+ "size" : "60x60",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "iphone",
+ "size" : "60x60",
+ "scale" : "3x"
+ },
+ {
+ "idiom" : "ipad",
+ "size" : "20x20",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "ipad",
+ "size" : "20x20",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "ipad",
+ "size" : "29x29",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "ipad",
+ "size" : "29x29",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "ipad",
+ "size" : "40x40",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "ipad",
+ "size" : "40x40",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "ipad",
+ "size" : "76x76",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "ipad",
+ "size" : "76x76",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "ipad",
+ "size" : "83.5x83.5",
+ "scale" : "2x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
diff --git a/Functions/Example/FirebaseFunctions/en.lproj/InfoPlist.strings b/Functions/Example/FirebaseFunctions/en.lproj/InfoPlist.strings
new file mode 100644
index 0000000..477b28f
--- /dev/null
+++ b/Functions/Example/FirebaseFunctions/en.lproj/InfoPlist.strings
@@ -0,0 +1,2 @@
+/* Localized versions of Info.plist keys */
+
diff --git a/Functions/Example/FirebaseFunctions/main.m b/Functions/Example/FirebaseFunctions/main.m
new file mode 100644
index 0000000..39c05a5
--- /dev/null
+++ b/Functions/Example/FirebaseFunctions/main.m
@@ -0,0 +1,22 @@
+// Copyright 2017 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 UIKit;
+#import "FIRAppDelegate.h"
+
+int main(int argc, char* argv[]) {
+ @autoreleasepool {
+ return UIApplicationMain(argc, argv, nil, NSStringFromClass([FIRAppDelegate class]));
+ }
+}
diff --git a/Functions/Example/IntegrationTests/FIRIntegrationTests.m b/Functions/Example/IntegrationTests/FIRIntegrationTests.m
new file mode 100644
index 0000000..bd4b127
--- /dev/null
+++ b/Functions/Example/IntegrationTests/FIRIntegrationTests.m
@@ -0,0 +1,190 @@
+// Copyright 2017 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 <XCTest/XCTest.h>
+
+#import "FIRError.h"
+#import "FIRFunctions+Internal.h"
+#import "FIRFunctions.h"
+#import "FIRHTTPSCallable.h"
+#import "FUNFakeApp.h"
+#import "FUNFakeInstanceID.h"
+
+@interface FIRIntegrationTests : XCTestCase {
+ FIRFunctions *_functions;
+}
+@end
+
+@implementation FIRIntegrationTests
+
+- (void)setUp {
+ [super setUp];
+ id app = [[FUNFakeApp alloc] initWithProjectID:@"functions-integration-test"];
+ _functions = [FIRFunctions functionsForApp:app];
+ [_functions useLocalhost];
+}
+
+- (void)tearDown {
+ [super tearDown];
+}
+
+- (void)testData {
+ NSDictionary *data = @{
+ @"bool" : @YES,
+ @"int" : @2,
+ @"long" : @3L,
+ @"string" : @"four",
+ @"array" : @[ @5, @6 ],
+ @"null" : [NSNull null],
+ };
+
+ XCTestExpectation *expectation = [[XCTestExpectation alloc] init];
+ FIRHTTPSCallable *function = [_functions HTTPSCallableWithName:@"dataTest"];
+ [function callWithObject:data
+ completion:^(FIRHTTPSCallableResult *_Nullable result, NSError *_Nullable error) {
+ XCTAssertNil(error);
+ XCTAssertEqualObjects(@"stub response", result.data[@"message"]);
+ XCTAssertEqualObjects(@42, result.data[@"code"]);
+ XCTAssertEqualObjects(@420L, result.data[@"long"]);
+ [expectation fulfill];
+ }];
+ [self waitForExpectations:@[ expectation ] timeout:10];
+}
+
+- (void)testScalar {
+ XCTestExpectation *expectation = [[XCTestExpectation alloc] init];
+ FIRHTTPSCallable *function = [_functions HTTPSCallableWithName:@"scalarTest"];
+ [function callWithObject:@17
+ completion:^(FIRHTTPSCallableResult *_Nullable result, NSError *_Nullable error) {
+ XCTAssertNil(error);
+ XCTAssertEqualObjects(@76, result.data);
+ [expectation fulfill];
+ }];
+ [self waitForExpectations:@[ expectation ] timeout:10];
+}
+
+- (void)testToken {
+ // Recreate _functions with a token.
+ id app = [[FUNFakeApp alloc] initWithProjectID:@"functions-integration-test" token:@"token"];
+ FIRFunctions *functions = [FIRFunctions functionsForApp:app];
+ [functions useLocalhost];
+
+ XCTestExpectation *expectation = [[XCTestExpectation alloc] init];
+ FIRHTTPSCallable *function = [functions HTTPSCallableWithName:@"tokenTest"];
+ [function callWithObject:@{}
+ completion:^(FIRHTTPSCallableResult *_Nullable result, NSError *_Nullable error) {
+ XCTAssertNil(error);
+ XCTAssertEqualObjects(@{}, result.data);
+ [expectation fulfill];
+ }];
+ [self waitForExpectations:@[ expectation ] timeout:10];
+}
+
+- (void)testInstanceID {
+ XCTestExpectation *expectation = [[XCTestExpectation alloc] init];
+ FIRHTTPSCallable *function = [_functions HTTPSCallableWithName:@"instanceIdTest"];
+ [function callWithObject:@{}
+ completion:^(FIRHTTPSCallableResult *_Nullable result, NSError *_Nullable error) {
+ XCTAssertNil(error);
+ XCTAssertEqualObjects(@{}, result.data);
+ [expectation fulfill];
+ }];
+ [self waitForExpectations:@[ expectation ] timeout:10];
+}
+
+- (void)testNull {
+ XCTestExpectation *expectation = [[XCTestExpectation alloc] init];
+ FIRHTTPSCallable *function = [_functions HTTPSCallableWithName:@"nullTest"];
+ [function callWithObject:[NSNull null]
+ completion:^(FIRHTTPSCallableResult *_Nullable result, NSError *_Nullable error) {
+ XCTAssertEqualObjects([NSNull null], result.data);
+ XCTAssertNil(error);
+ [expectation fulfill];
+ }];
+ [self waitForExpectations:@[ expectation ] timeout:10];
+
+ // Test the version with no arguments.
+ [function
+ callWithCompletion:^(FIRHTTPSCallableResult *_Nullable result, NSError *_Nullable error) {
+ XCTAssertEqualObjects([NSNull null], result.data);
+ XCTAssertNil(error);
+ [expectation fulfill];
+ }];
+ [self waitForExpectations:@[ expectation ] timeout:10];
+}
+
+- (void)testMissingResult {
+ XCTestExpectation *expectation = [[XCTestExpectation alloc] init];
+ FIRHTTPSCallable *function = [_functions HTTPSCallableWithName:@"missingResultTest"];
+ [function
+ callWithCompletion:^(FIRHTTPSCallableResult *_Nullable result, NSError *_Nullable error) {
+ XCTAssertNotNil(error);
+ XCTAssertEqual(FIRFunctionsErrorCodeInternal, error.code);
+ [expectation fulfill];
+ }];
+ [self waitForExpectations:@[ expectation ] timeout:10];
+}
+
+- (void)testUnhandledError {
+ XCTestExpectation *expectation = [[XCTestExpectation alloc] init];
+ FIRHTTPSCallable *function = [_functions HTTPSCallableWithName:@"unhandledErrorTest"];
+ [function callWithObject:@{}
+ completion:^(FIRHTTPSCallableResult *_Nullable result, NSError *_Nullable error) {
+ XCTAssertNotNil(error);
+ XCTAssertEqual(FIRFunctionsErrorCodeInternal, error.code);
+ [expectation fulfill];
+ }];
+ [self waitForExpectations:@[ expectation ] timeout:10];
+}
+
+- (void)testUnknownError {
+ XCTestExpectation *expectation = [[XCTestExpectation alloc] init];
+ FIRHTTPSCallable *function = [_functions HTTPSCallableWithName:@"unknownErrorTest"];
+ [function callWithObject:@{}
+ completion:^(FIRHTTPSCallableResult *_Nullable result, NSError *_Nullable error) {
+ XCTAssertNotNil(error);
+ XCTAssertEqual(FIRFunctionsErrorCodeInternal, error.code);
+ [expectation fulfill];
+ }];
+ [self waitForExpectations:@[ expectation ] timeout:10];
+}
+
+- (void)testExplicitError {
+ XCTestExpectation *expectation = [[XCTestExpectation alloc] init];
+ FIRHTTPSCallable *function = [_functions HTTPSCallableWithName:@"explicitErrorTest"];
+ [function callWithObject:@{}
+ completion:^(FIRHTTPSCallableResult *_Nullable result, NSError *_Nullable error) {
+ XCTAssertNotNil(error);
+ XCTAssertEqual(FIRFunctionsErrorCodeOutOfRange, error.code);
+ XCTAssertEqualObjects(@"explicit nope", error.userInfo[NSLocalizedDescriptionKey]);
+ NSDictionary *expectedDetails = @{ @"start" : @10, @"end" : @20, @"long" : @30L };
+ XCTAssertEqualObjects(expectedDetails, error.userInfo[FIRFunctionsErrorDetailsKey]);
+ [expectation fulfill];
+ }];
+ [self waitForExpectations:@[ expectation ] timeout:10];
+}
+
+- (void)testHttpError {
+ XCTestExpectation *expectation = [[XCTestExpectation alloc] init];
+ FIRHTTPSCallable *function = [_functions HTTPSCallableWithName:@"httpErrorTest"];
+ [function callWithObject:@{}
+ completion:^(FIRHTTPSCallableResult *_Nullable result, NSError *_Nullable error) {
+ XCTAssertNotNil(error);
+ XCTAssertEqual(FIRFunctionsErrorCodeInvalidArgument, error.code);
+ [expectation fulfill];
+ }];
+ [self waitForExpectations:@[ expectation ] timeout:10];
+}
+
+@end
diff --git a/Functions/Example/IntegrationTests/IntegrationTests-Info.plist b/Functions/Example/IntegrationTests/IntegrationTests-Info.plist
new file mode 100644
index 0000000..169b6f7
--- /dev/null
+++ b/Functions/Example/IntegrationTests/IntegrationTests-Info.plist
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>en</string>
+ <key>CFBundleExecutable</key>
+ <string>${EXECUTABLE_NAME}</string>
+ <key>CFBundleIdentifier</key>
+ <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundlePackageType</key>
+ <string>BNDL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>1.0</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>CFBundleVersion</key>
+ <string>1</string>
+</dict>
+</plist>
diff --git a/Functions/Example/Podfile b/Functions/Example/Podfile
new file mode 100644
index 0000000..ea07e11
--- /dev/null
+++ b/Functions/Example/Podfile
@@ -0,0 +1,16 @@
+use_frameworks!
+
+target 'FirebaseFunctions_Example' do
+ platform :ios, '8.0'
+
+ pod 'FirebaseCore', :path => '../../'
+ pod 'FirebaseFunctions', :path => '../../'
+
+ target 'FirebaseFunctions_Tests' do
+ inherit! :search_paths
+ end
+
+ target 'FirebaseFunctions_IntegrationTests' do
+ inherit! :search_paths
+ end
+end
diff --git a/Functions/Example/TestUtils/FUNFakeApp.h b/Functions/Example/TestUtils/FUNFakeApp.h
new file mode 100644
index 0000000..d07262b
--- /dev/null
+++ b/Functions/Example/TestUtils/FUNFakeApp.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2017 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/Foundation.h>
+
+@class FUNFakeOptions;
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ * FUNFakeApp is a mock app to use for tests.
+ */
+@interface FUNFakeApp : NSObject
+
+- (id)init NS_UNAVAILABLE;
+
+- (instancetype)initWithProjectID:(NSString *)projectID;
+
+- (instancetype)initWithProjectID:(NSString *)projectID
+ token:(NSString *_Nullable)token NS_DESIGNATED_INITIALIZER;
+
+@property(nonatomic, strong, readonly) FUNFakeOptions *options;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Functions/Example/TestUtils/FUNFakeApp.m b/Functions/Example/TestUtils/FUNFakeApp.m
new file mode 100644
index 0000000..370273b
--- /dev/null
+++ b/Functions/Example/TestUtils/FUNFakeApp.m
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2017 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/Foundation.h>
+
+#import "FUNFakeApp.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface FUNFakeOptions : NSObject
+
+@property(nonatomic, readonly, copy) NSString *projectID;
+
+- (id)init NS_UNAVAILABLE;
+
+- (instancetype)initWithProjectID:(NSString *)projectID NS_DESIGNATED_INITIALIZER;
+
+@end
+
+@implementation FUNFakeOptions
+
+- (instancetype)initWithProjectID:(NSString *)projectID {
+ self = [super init];
+ if (self) {
+ self->_projectID = [projectID copy];
+ }
+ return self;
+}
+
+@end
+
+@interface FUNFakeApp () {
+ NSString *_token;
+}
+@end
+
+@implementation FUNFakeApp
+
+- (instancetype)initWithProjectID:(NSString *)projectID {
+ return [self initWithProjectID:projectID token:nil];
+}
+
+- (instancetype)initWithProjectID:(NSString *)projectID token:(NSString *_Nullable)token {
+ self = [super init];
+ if (self) {
+ _options = [[FUNFakeOptions alloc] initWithProjectID:projectID];
+ _token = [token copy];
+ }
+ return self;
+}
+
+- (void)getTokenForcingRefresh:(BOOL)forceRefresh
+ withCallback:
+ (void (^)(NSString *_Nullable token, NSError *_Nullable error))callback {
+ callback(_token, nil);
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Functions/Example/TestUtils/FUNFakeInstanceID.h b/Functions/Example/TestUtils/FUNFakeInstanceID.h
new file mode 100644
index 0000000..9925410
--- /dev/null
+++ b/Functions/Example/TestUtils/FUNFakeInstanceID.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2017 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/Foundation.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ * This FIRInstanceID is a mock instance ID provider to use for tests.
+ * Since FirebaseFunctions loads FIRInstanceID as a weak dependency by reflection, we just have to
+ * make a class with the same name.
+ */
+@interface FIRInstanceID : NSObject
+
++ (instancetype)instanceID;
+
+- (NSString *)token;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/API/FIRSetOptions+Internal.h b/Functions/Example/TestUtils/FUNFakeInstanceID.m
index 9118096..a81e67a 100644
--- a/Firestore/Source/API/FIRSetOptions+Internal.h
+++ b/Functions/Example/TestUtils/FUNFakeInstanceID.m
@@ -14,19 +14,19 @@
* limitations under the License.
*/
-#import "FIRSetOptions.h"
+#import "FUNFakeInstanceID.h"
NS_ASSUME_NONNULL_BEGIN
-@interface FIRSetOptions ()
+@implementation FIRInstanceID
-- (instancetype)initWithMerge:(BOOL)merge NS_DESIGNATED_INITIALIZER;
++ (instancetype)instanceID {
+ return [[FIRInstanceID alloc] init];
+}
-@end
-
-@interface FIRSetOptions (Internal)
-
-+ (instancetype)overwrite;
+- (NSString *)token {
+ return @"iid";
+}
@end
diff --git a/Functions/Example/Tests/FIRFunctionsTests.m b/Functions/Example/Tests/FIRFunctionsTests.m
new file mode 100644
index 0000000..5d11601
--- /dev/null
+++ b/Functions/Example/Tests/FIRFunctionsTests.m
@@ -0,0 +1,50 @@
+// Copyright 2017 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 <XCTest/XCTest.h>
+
+#import "FIRFunctions+Internal.h"
+#import "FIRFunctions.h"
+
+#import "FUNFakeApp.h"
+
+@interface FIRFunctionsTests : XCTestCase
+@end
+
+@implementation FIRFunctionsTests
+
+- (void)setUp {
+ [super setUp];
+}
+
+- (void)tearDown {
+ [super tearDown];
+}
+
+- (void)testURLWithName {
+ // TODO(klimt): Add this test back when we add the constructor back.
+ /*
+ id app = [[FUNFakeApp alloc] initWithProjectID:@"my-project"];
+ FIRFunctions *functions = [FIRFunctions functionsForApp:app region:@"my-region"];
+ NSString *url = [functions URLWithName:@"my-endpoint"];
+ XCTAssertEqualObjects(@"https://my-region-my-project.cloudfunctions.net/my-endpoint", url);
+ */
+
+ id app = [[FUNFakeApp alloc] initWithProjectID:@"my-project"];
+ FIRFunctions *functions = [FIRFunctions functionsForApp:app];
+ NSString *url = [functions URLWithName:@"my-endpoint"];
+ XCTAssertEqualObjects(@"https://us-central1-my-project.cloudfunctions.net/my-endpoint", url);
+}
+
+@end
diff --git a/Functions/Example/Tests/FUNSerializerTests.m b/Functions/Example/Tests/FUNSerializerTests.m
new file mode 100644
index 0000000..ca2198e
--- /dev/null
+++ b/Functions/Example/Tests/FUNSerializerTests.m
@@ -0,0 +1,240 @@
+// Copyright 2017 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 <XCTest/XCTest.h>
+
+#import "FIRError.h"
+#import "FUNSerializer.h"
+
+@interface FUNSerializerTests : XCTestCase
+@end
+
+@implementation FUNSerializerTests
+
+- (void)setUp {
+ [super setUp];
+}
+
+- (void)tearDown {
+ [super tearDown];
+}
+
+- (void)testEncodeNull {
+ FUNSerializer *serializer = [[FUNSerializer alloc] init];
+ XCTAssertEqualObjects([NSNull null], [serializer encode:[NSNull null]]);
+}
+
+- (void)testDecodeNull {
+ FUNSerializer *serializer = [[FUNSerializer alloc] init];
+ NSError *error = nil;
+ XCTAssertEqualObjects([NSNull null], [serializer decode:[NSNull null] error:&error]);
+ XCTAssertNil(error);
+}
+
+- (void)testEncodeInt {
+ FUNSerializer *serializer = [[FUNSerializer alloc] init];
+ XCTAssertEqualObjects(@1, [serializer encode:@1]);
+}
+
+- (void)testDecodeInt {
+ FUNSerializer *serializer = [[FUNSerializer alloc] init];
+ NSError *error = nil;
+ XCTAssertEqualObjects(@1, [serializer decode:@1 error:&error]);
+ XCTAssertNil(error);
+}
+
+- (void)testEncodeLong {
+ FUNSerializer *serializer = [[FUNSerializer alloc] init];
+ NSDictionary *expected = @{
+ @"@type" : @"type.googleapis.com/google.protobuf.Int64Value",
+ @"value" : @"-9223372036854775800",
+ };
+ XCTAssertEqualObjects(expected, [serializer encode:@-9223372036854775800L]);
+}
+
+- (void)testDecodeLong {
+ FUNSerializer *serializer = [[FUNSerializer alloc] init];
+ NSDictionary *input = @{
+ @"@type" : @"type.googleapis.com/google.protobuf.Int64Value",
+ @"value" : @"-9223372036854775800",
+ };
+ NSError *error = nil;
+ NSNumber *actual = [serializer decode:input error:&error];
+ XCTAssertEqualObjects(@-9223372036854775800L, actual);
+ // A naive implementation might convert a number to a double and think that's close enough.
+ // We need to make sure it's a long long for accuracy.
+ XCTAssertEqual('q', actual.objCType[0]);
+ XCTAssertNil(error);
+}
+
+- (void)testDecodeInvalidLong {
+ FUNSerializer *serializer = [[FUNSerializer alloc] init];
+ NSDictionary *input = @{
+ @"@type" : @"type.googleapis.com/google.protobuf.Int64Value",
+ @"value" : @"-9223372036854775800 and some other junk",
+ };
+ NSError *error = nil;
+ NSNumber *actual = [serializer decode:input error:&error];
+ XCTAssertNil(actual);
+ XCTAssertNotNil(error);
+ XCTAssertEqualObjects(FIRFunctionsErrorDomain, error.domain);
+ XCTAssertEqual(FIRFunctionsErrorCodeInternal, error.code);
+}
+
+- (void)testEncodeUnsignedLong {
+ FUNSerializer *serializer = [[FUNSerializer alloc] init];
+ NSDictionary *expected = @{
+ @"@type" : @"type.googleapis.com/google.protobuf.UInt64Value",
+ @"value" : @"18446744073709551600",
+ };
+ XCTAssertEqualObjects(expected, [serializer encode:@18446744073709551600UL]);
+}
+
+- (void)testDecodeUnsignedLong {
+ FUNSerializer *serializer = [[FUNSerializer alloc] init];
+ NSDictionary *input = @{
+ @"@type" : @"type.googleapis.com/google.protobuf.UInt64Value",
+ @"value" : @"17446744073709551688",
+ };
+ NSError *error = nil;
+ NSNumber *actual = [serializer decode:input error:&error];
+ XCTAssertEqualObjects(@17446744073709551688UL, actual);
+ // A naive NSNumberFormatter implementation will convert the number to a double and think
+ // that's close enough. We need to make sure it's an unsigned long long for accuracy.
+ XCTAssertEqual('Q', actual.objCType[0]);
+ XCTAssertNil(error);
+}
+
+- (void)testEncodeDouble {
+ FUNSerializer *serializer = [[FUNSerializer alloc] init];
+ XCTAssertEqualObjects(@1.2, [serializer encode:@1.2]);
+}
+
+- (void)testDecodeDouble {
+ FUNSerializer *serializer = [[FUNSerializer alloc] init];
+ NSError *error = nil;
+ XCTAssertEqualObjects(@1.2, [serializer decode:@1.2 error:&error]);
+ XCTAssertNil(error);
+}
+
+- (void)testEncodeBool {
+ FUNSerializer *serializer = [[FUNSerializer alloc] init];
+ XCTAssertEqualObjects(@YES, [serializer encode:@YES]);
+}
+
+- (void)testDecodeBool {
+ FUNSerializer *serializer = [[FUNSerializer alloc] init];
+ NSError *error = nil;
+ XCTAssertEqualObjects(@NO, [serializer decode:@NO error:&error]);
+ XCTAssertNil(error);
+}
+
+- (void)testEncodeString {
+ FUNSerializer *serializer = [[FUNSerializer alloc] init];
+ XCTAssertEqualObjects(@"hello", [serializer encode:@"hello"]);
+}
+
+- (void)testDecodeString {
+ FUNSerializer *serializer = [[FUNSerializer alloc] init];
+ NSError *error = nil;
+ XCTAssertEqualObjects(@"hello", [serializer decode:@"hello" error:&error]);
+ XCTAssertNil(error);
+}
+
+- (void)testEncodeArray {
+ NSArray *input = @[ @1, @"two", @[ @3, @4L ] ];
+ NSArray *expected = @[
+ @1, @"two",
+ @[
+ @3, @{
+ @"@type" : @"type.googleapis.com/google.protobuf.Int64Value",
+ @"value" : @"4",
+ }
+ ]
+ ];
+ FUNSerializer *serializer = [[FUNSerializer alloc] init];
+ XCTAssertEqualObjects(expected, [serializer encode:input]);
+}
+
+- (void)testDecodeArray {
+ NSArray *input = @[
+ @1, @"two",
+ @[
+ @3, @{
+ @"@type" : @"type.googleapis.com/google.protobuf.Int64Value",
+ @"value" : @"4",
+ }
+ ]
+ ];
+ NSArray *expected = @[ @1, @"two", @[ @3, @4L ] ];
+ FUNSerializer *serializer = [[FUNSerializer alloc] init];
+ NSError *error = nil;
+
+ XCTAssertEqualObjects(expected, [serializer decode:input error:&error]);
+ XCTAssertNil(error);
+}
+
+- (void)testEncodeMap {
+ NSDictionary *input = @{ @"foo" : @1, @"bar" : @"hello", @"baz" : @[ @3, @4L ] };
+ NSDictionary *expected = @{
+ @"foo" : @1,
+ @"bar" : @"hello",
+ @"baz" : @[
+ @3, @{
+ @"@type" : @"type.googleapis.com/google.protobuf.Int64Value",
+ @"value" : @"4",
+ }
+ ]
+ };
+ FUNSerializer *serializer = [[FUNSerializer alloc] init];
+ XCTAssertEqualObjects(expected, [serializer encode:input]);
+}
+
+- (void)testDecodeMap {
+ NSDictionary *input = @{
+ @"foo" : @1,
+ @"bar" : @"hello",
+ @"baz" : @[
+ @3, @{
+ @"@type" : @"type.googleapis.com/google.protobuf.Int64Value",
+ @"value" : @"4",
+ }
+ ]
+ };
+ NSDictionary *expected = @{ @"foo" : @1, @"bar" : @"hello", @"baz" : @[ @3, @4L ] };
+ FUNSerializer *serializer = [[FUNSerializer alloc] init];
+ NSError *error = nil;
+ XCTAssertEqualObjects(expected, [serializer decode:input error:&error]);
+ XCTAssertNil(error);
+}
+
+- (void)testDecodeUnknownType {
+ NSDictionary *input = @{@"@type" : @"unknown", @"value" : @"whatever"};
+ FUNSerializer *serializer = [[FUNSerializer alloc] init];
+ NSError *error = nil;
+ XCTAssertEqualObjects(input, [serializer decode:input error:&error]);
+ XCTAssertNil(error);
+}
+
+- (void)testDecodeUnknownTypeWithoutValue {
+ NSDictionary *input = @{
+ @"@type" : @"unknown",
+ };
+ FUNSerializer *serializer = [[FUNSerializer alloc] init];
+ NSError *error = nil;
+ XCTAssertEqualObjects(input, [serializer decode:input error:&error]);
+ XCTAssertNil(error);
+}
+
+@end
diff --git a/Functions/Example/Tests/Tests-Info.plist b/Functions/Example/Tests/Tests-Info.plist
new file mode 100644
index 0000000..169b6f7
--- /dev/null
+++ b/Functions/Example/Tests/Tests-Info.plist
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>en</string>
+ <key>CFBundleExecutable</key>
+ <string>${EXECUTABLE_NAME}</string>
+ <key>CFBundleIdentifier</key>
+ <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundlePackageType</key>
+ <string>BNDL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>1.0</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>CFBundleVersion</key>
+ <string>1</string>
+</dict>
+</plist>
diff --git a/Functions/Example/Tests/en.lproj/InfoPlist.strings b/Functions/Example/Tests/en.lproj/InfoPlist.strings
new file mode 100644
index 0000000..477b28f
--- /dev/null
+++ b/Functions/Example/Tests/en.lproj/InfoPlist.strings
@@ -0,0 +1,2 @@
+/* Localized versions of Info.plist keys */
+
diff --git a/Functions/FirebaseFunctions/FIRFunctions+Internal.h b/Functions/FirebaseFunctions/FIRFunctions+Internal.h
new file mode 100644
index 0000000..6c555f0
--- /dev/null
+++ b/Functions/FirebaseFunctions/FIRFunctions+Internal.h
@@ -0,0 +1,50 @@
+// Copyright 2017 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/Foundation.h>
+
+#import "FIRFunctions.h"
+
+@class FIRHTTPSCallableResult;
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface FIRFunctions (Internal)
+
+/**
+ * Calls an http trigger endpoint.
+ * @param name The name of the http trigger.
+ * @param data Parameters to pass to the function. Can be anything encodable as JSON.
+ * @param completion The block to call when the request is complete.
+ */
+- (void)callFunction:(NSString *)name
+ withObject:(nullable id)data
+ completion:(void (^)(FIRHTTPSCallableResult *_Nullable result,
+ NSError *_Nullable error))completion;
+
+/**
+ * Constructs the url for an http trigger. This is exposed only for testing.
+ * @param name The name of the endpoint.
+ */
+- (NSString *)URLWithName:(NSString *)name;
+
+/**
+ * Sets the functions client to send requests to localhost instead of Firebase.
+ * For testing only.
+ */
+- (void)useLocalhost;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Functions/FirebaseFunctions/FIRFunctions.m b/Functions/FirebaseFunctions/FIRFunctions.m
new file mode 100644
index 0000000..274d058
--- /dev/null
+++ b/Functions/FirebaseFunctions/FIRFunctions.m
@@ -0,0 +1,247 @@
+// Copyright 2017 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 "FIRFunctions.h"
+#import "FIRFunctions+Internal.h"
+
+#import "FIRError.h"
+#import "FIRHTTPSCallable+Internal.h"
+#import "FIRHTTPSCallable.h"
+#import "FUNContext.h"
+#import "FUNError.h"
+#import "FUNSerializer.h"
+#import "FUNUsageValidation.h"
+
+#import "FIRApp.h"
+#import "FIRAppInternal.h"
+#import "FIROptions.h"
+#import "GTMSessionFetcherService.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+NSString *const kFUNInstanceIDTokenHeader = @"Firebase-Instance-ID-Token";
+
+@interface FIRFunctions () {
+ // The network client to use for http requests.
+ GTMSessionFetcherService *_fetcherService;
+ // The projectID to use for all function references.
+ FIRApp *_app;
+ // The region to use for all function references.
+ NSString *_region;
+ // A serializer to encode/decode data and return values.
+ FUNSerializer *_serializer;
+ // A factory for getting the metadata to include with function calls.
+ FUNContextProvider *_contextProvider;
+ // For testing only. If this is set, functions will be called against localhost instead of
+ // Firebase.
+ BOOL _useLocalhost;
+}
+
+/**
+ * Initialize the Cloud Functions client with the given app and region.
+ * @param app The app for the Firebase project.
+ * @param region The region for the http trigger, such as "us-central1".
+ */
+- (id)initWithApp:(FIRApp *)app region:(NSString *)region NS_DESIGNATED_INITIALIZER;
+
+@end
+
+@implementation FIRFunctions
+
++ (instancetype)functions {
+ return [[self alloc] initWithApp:[FIRApp defaultApp] region:@"us-central1"];
+}
+
++ (instancetype)functionsForApp:(FIRApp *)app {
+ return [[self alloc] initWithApp:app region:@"us-central1"];
+}
+
++ (instancetype)functionsForRegion:(NSString *)region {
+ return [[self alloc] initWithApp:[FIRApp defaultApp] region:region];
+}
+
++ (instancetype)functionsForApp:(FIRApp *)app region:(NSString *)region {
+ return [[self alloc] initWithApp:app region:region];
+}
+
+- (instancetype)initWithApp:(FIRApp *)app region:(NSString *)region {
+ self = [super init];
+ if (self) {
+ if (!region) {
+ FUNThrowInvalidArgument(@"FIRFunctions region cannot be nil.");
+ }
+ _fetcherService = [[GTMSessionFetcherService alloc] init];
+ _app = app;
+ _region = [region copy];
+ _serializer = [[FUNSerializer alloc] init];
+ _contextProvider = [[FUNContextProvider alloc] initWithApp:app];
+ _useLocalhost = NO;
+ }
+ return self;
+}
+
+- (void)useLocalhost {
+ _useLocalhost = YES;
+}
+
+- (NSString *)URLWithName:(NSString *)name {
+ if (!name) {
+ FUNThrowInvalidArgument(@"FIRFunctions function name cannot be nil.");
+ }
+ NSString *projectID = _app.options.projectID;
+ if (!projectID) {
+ FUNThrowInvalidArgument(@"FIRFunctions app projectID cannot be nil.");
+ }
+ if (_useLocalhost) {
+ return [NSString stringWithFormat:@"http://localhost:5005/%@/%@/%@", projectID, _region, name];
+ }
+ return
+ [NSString stringWithFormat:@"https://%@-%@.cloudfunctions.net/%@", _region, projectID, name];
+}
+
+- (void)callFunction:(NSString *)name
+ withObject:(nullable id)data
+ completion:(void (^)(FIRHTTPSCallableResult *_Nullable result,
+ NSError *_Nullable error))completion {
+ [_contextProvider getContext:^(FUNContext *_Nullable context, NSError *_Nullable error) {
+ if (error) {
+ if (completion) {
+ completion(nil, error);
+ }
+ return;
+ }
+ return [self callFunction:name withObject:data context:context completion:completion];
+ }];
+}
+
+- (void)callFunction:(NSString *)name
+ withObject:(nullable id)data
+ context:(FUNContext *)context
+ completion:(void (^)(FIRHTTPSCallableResult *_Nullable result,
+ NSError *_Nullable error))completion {
+ GTMSessionFetcher *fetcher = [_fetcherService fetcherWithURLString:[self URLWithName:name]];
+
+ NSMutableDictionary *body = [NSMutableDictionary dictionary];
+ // Encode the data in the body.
+ if (!data) {
+ data = [NSNull null];
+ }
+ id encoded = [_serializer encode:data];
+ if (!encoded) {
+ FUNThrowInvalidArgument(@"FIRFunctions data encoded as nil. This should not happen.");
+ }
+ body[@"data"] = encoded;
+
+ NSError *error = nil;
+ NSData *payload = [NSJSONSerialization dataWithJSONObject:body options:0 error:&error];
+ if (error) {
+ if (completion) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ completion(nil, error);
+ });
+ }
+ return;
+ }
+ fetcher.bodyData = payload;
+
+ // Set the headers.
+ [fetcher setRequestValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
+ if (context.authToken) {
+ NSString *value = [NSString stringWithFormat:@"Bearer %@", context.authToken];
+ [fetcher setRequestValue:value forHTTPHeaderField:@"Authorization"];
+ }
+ if (context.instanceIDToken) {
+ [fetcher setRequestValue:context.instanceIDToken forHTTPHeaderField:kFUNInstanceIDTokenHeader];
+ }
+
+ // Override normal security rules if this is a local test.
+ if (_useLocalhost) {
+ fetcher.allowLocalhostRequest = YES;
+ fetcher.allowedInsecureSchemes = @[ @"http" ];
+ }
+
+ FUNSerializer *serializer = _serializer;
+ [fetcher beginFetchWithCompletionHandler:^(NSData *_Nullable data, NSError *_Nullable error) {
+ // If there was an HTTP error, convert it to our own error domain.
+ if (error) {
+ if ([error.domain isEqualToString:kGTMSessionFetcherStatusDomain]) {
+ error = FUNErrorForResponse(error.code, data, serializer);
+ }
+ } else {
+ // If there wasn't an HTTP error, see if there was an error in the body.
+ error = FUNErrorForResponse(200, data, serializer);
+ }
+ // If there was an error, report it to the user and stop.
+ if (error) {
+ if (completion) {
+ completion(nil, error);
+ }
+ return;
+ }
+
+ id responseJSON = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
+ if (error) {
+ if (completion) {
+ completion(nil, error);
+ }
+ return;
+ }
+ if (![responseJSON isKindOfClass:[NSDictionary class]]) {
+ NSDictionary *userInfo = @{NSLocalizedDescriptionKey : @"Response was not a dictionary."};
+ error = [NSError errorWithDomain:FIRFunctionsErrorDomain
+ code:FIRFunctionsErrorCodeInternal
+ userInfo:userInfo];
+ if (completion) {
+ completion(nil, error);
+ }
+ return;
+ }
+ id dataJSON = responseJSON[@"data"];
+ // TODO(klimt): Allow "result" instead of "data" for now, for backwards compatibility.
+ if (!dataJSON) {
+ dataJSON = responseJSON[@"result"];
+ }
+ if (!dataJSON) {
+ NSDictionary *userInfo =
+ @{NSLocalizedDescriptionKey : @"Response did not include data field."};
+ error = [NSError errorWithDomain:FIRFunctionsErrorDomain
+ code:FIRFunctionsErrorCodeInternal
+ userInfo:userInfo];
+ if (completion) {
+ completion(nil, error);
+ }
+ return;
+ }
+ id resultData = [serializer decode:dataJSON error:&error];
+ if (error) {
+ if (completion) {
+ completion(nil, error);
+ }
+ return;
+ }
+ id result = [[FIRHTTPSCallableResult alloc] initWithData:resultData];
+ if (completion) {
+ // If there's no result field, this will return nil, which is fine.
+ completion(result, nil);
+ }
+ }];
+}
+
+- (FIRHTTPSCallable *)HTTPSCallableWithName:(NSString *)name {
+ return [[FIRHTTPSCallable alloc] initWithFunctions:self name:name];
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Functions/FirebaseFunctions/FIRHTTPSCallable+Internal.h b/Functions/FirebaseFunctions/FIRHTTPSCallable+Internal.h
new file mode 100644
index 0000000..0a8dae1
--- /dev/null
+++ b/Functions/FirebaseFunctions/FIRHTTPSCallable+Internal.h
@@ -0,0 +1,46 @@
+// Copyright 2017 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/Foundation.h>
+
+#import "FIRHTTPSCallable.h"
+
+@class FIRFunctions;
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface FIRHTTPSCallableResult (Internal)
+
+/**
+ * Initializes a callable result.
+ *
+ * @param result The data to wrap.
+ */
+- (instancetype)initWithData:(id)result;
+
+@end
+
+@interface FIRHTTPSCallable (Internal)
+
+/**
+ * Initializes a reference to the given http trigger.
+ *
+ * @param functionsClient The functions client to use for making network requests.
+ * @param name The name of the http trigger.
+ */
+- (instancetype)initWithFunctions:(FIRFunctions *)functionsClient name:(NSString *)name;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Functions/FirebaseFunctions/FIRHTTPSCallable.m b/Functions/FirebaseFunctions/FIRHTTPSCallable.m
new file mode 100644
index 0000000..2979ca5
--- /dev/null
+++ b/Functions/FirebaseFunctions/FIRHTTPSCallable.m
@@ -0,0 +1,71 @@
+// Copyright 2017 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 "FIRHTTPSCallable.h"
+#import "FIRHTTPSCallable+Internal.h"
+
+#import "FIRFunctions+Internal.h"
+#import "FUNUsageValidation.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@implementation FIRHTTPSCallableResult
+
+- (instancetype)initWithData:(id)data {
+ self = [super init];
+ if (self) {
+ _data = data;
+ }
+ return self;
+}
+
+@end
+
+@interface FIRHTTPSCallable () {
+ // The functions client to use for making calls.
+ FIRFunctions *_functions;
+ // The name of the http endpoint this reference refers to.
+ NSString *_name;
+}
+
+@end
+
+@implementation FIRHTTPSCallable
+
+- (instancetype)initWithFunctions:(FIRFunctions *)functions name:(NSString *)name {
+ self = [super init];
+ if (self) {
+ if (!name) {
+ FUNThrowInvalidArgument(@"FIRHTTPSCallable name cannot be nil.");
+ }
+ _name = [name copy];
+ _functions = functions;
+ }
+ return self;
+}
+
+- (void)callWithCompletion:(void (^)(FIRHTTPSCallableResult *_Nullable result,
+ NSError *_Nullable error))completion {
+ [self callWithObject:nil completion:completion];
+}
+
+- (void)callWithObject:(nullable id)data
+ completion:(void (^)(FIRHTTPSCallableResult *_Nullable result,
+ NSError *_Nullable error))completion {
+ [_functions callFunction:_name withObject:data completion:completion];
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Functions/FirebaseFunctions/FUNContext.h b/Functions/FirebaseFunctions/FUNContext.h
new file mode 100644
index 0000000..5cbc474
--- /dev/null
+++ b/Functions/FirebaseFunctions/FUNContext.h
@@ -0,0 +1,44 @@
+//
+// Copyright 2017 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/Foundation.h>
+
+@class FIRApp;
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ * FUNContext is a helper class for gathering metadata for a function call.
+ */
+@interface FUNContext : NSObject
+- (id)init NS_UNAVAILABLE;
+@property(nonatomic, copy, nullable, readonly) NSString *authToken;
+@property(nonatomic, copy, nullable, readonly) NSString *instanceIDToken;
+@end
+
+/**
+ * A FUNContextProvider gathers metadata and creats a FUNContext.
+ */
+@interface FUNContextProvider : NSObject
+
+- (id)init NS_UNAVAILABLE;
+
+- (instancetype)initWithApp:(FIRApp *)app NS_DESIGNATED_INITIALIZER;
+
+- (void)getContext:(void (^)(FUNContext *_Nullable context, NSError *_Nullable error))completion;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Functions/FirebaseFunctions/FUNContext.m b/Functions/FirebaseFunctions/FUNContext.m
new file mode 100644
index 0000000..890b5bf
--- /dev/null
+++ b/Functions/FirebaseFunctions/FUNContext.m
@@ -0,0 +1,87 @@
+//
+// Copyright 2017 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 "FUNContext.h"
+
+#import "FIRApp.h"
+#import "FIRAppInternal.h"
+#import "FUNInstanceIDProxy.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface FUNContext ()
+
+- (instancetype)initWithAuthToken:(NSString *_Nullable)authToken
+ instanceIDToken:(NSString *_Nullable)instanceIDToken NS_DESIGNATED_INITIALIZER;
+
+@end
+
+@implementation FUNContext
+
+- (instancetype)initWithAuthToken:(NSString *_Nullable)authToken
+ instanceIDToken:(NSString *_Nullable)instanceIDToken {
+ self = [super init];
+ if (self) {
+ _authToken = [authToken copy];
+ _instanceIDToken = [instanceIDToken copy];
+ }
+ return self;
+}
+
+@end
+
+@interface FUNContextProvider () {
+ FIRApp *_app;
+ FUNInstanceIDProxy *_instanceIDProxy;
+}
+@end
+
+@implementation FUNContextProvider
+
+- (instancetype)initWithApp:(FIRApp *)app {
+ self = [super init];
+ if (self) {
+ _app = app;
+ _instanceIDProxy = [[FUNInstanceIDProxy alloc] init];
+ }
+ return self;
+}
+
+// This is broken out so it can be mocked for tests.
+- (NSString *)instanceIDToken {
+ return [_instanceIDProxy token];
+}
+
+- (void)getContext:(void (^)(FUNContext *_Nullable context, NSError *_Nullable error))completion {
+ // Get the auth token.
+ [_app getTokenForcingRefresh:NO
+ withCallback:^(NSString *_Nullable token, NSError *_Nullable error) {
+ if (error) {
+ completion(nil, error);
+ return;
+ }
+
+ // Get the instance id token.
+ NSString *_Nullable instanceIDToken = [self instanceIDToken];
+
+ FUNContext *context = [[FUNContext alloc] initWithAuthToken:token
+ instanceIDToken:instanceIDToken];
+ completion(context, nil);
+ }];
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Functions/FirebaseFunctions/FUNError.h b/Functions/FirebaseFunctions/FUNError.h
new file mode 100644
index 0000000..d66a814
--- /dev/null
+++ b/Functions/FirebaseFunctions/FUNError.h
@@ -0,0 +1,34 @@
+//
+// Copyright 2017 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/Foundation.h>
+
+@class FUNSerializer;
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ * Takes an HTTP status code and optional body and returns a corresponding NSError.
+ * If an explicit error is encoded in the JSON body, it will be used.
+ * Otherwise, uses the standard HTTP status code -> error mapping defined in:
+ * https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto
+ * @param status An HTTP status code.
+ * @param body Optional body of the HTTP response.
+ * @param serializer A serializer to use to decode the details in the error response.
+ * @return The corresponding error.
+ */
+NSError *FUNErrorForResponse(NSInteger status, NSData *_Nullable body, FUNSerializer *serializer);
+
+NS_ASSUME_NONNULL_END
diff --git a/Functions/FirebaseFunctions/FUNError.m b/Functions/FirebaseFunctions/FUNError.m
new file mode 100644
index 0000000..abe0287
--- /dev/null
+++ b/Functions/FirebaseFunctions/FUNError.m
@@ -0,0 +1,189 @@
+//
+// Copyright 2017 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 "FUNError.h"
+#import "FIRError.h"
+
+#import "FUNSerializer.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+NSString *const FIRFunctionsErrorDomain = @"com.firebase.functions";
+NSString *const FIRFunctionsErrorDetailsKey = @"details";
+
+/**
+ * Takes an HTTP status code and returns the corresponding FIRFunctionsErrorCode error code.
+ * This is the standard HTTP status code -> error mapping defined in:
+ * https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto
+ * @param status An HTTP status code.
+ * @return The corresponding error code, or FIRFunctionsErrorCodeUnknown if none.
+ */
+FIRFunctionsErrorCode FIRFunctionsErrorCodeForHTTPStatus(NSInteger status) {
+ switch (status) {
+ case 200:
+ return FIRFunctionsErrorCodeOK;
+ case 400:
+ return FIRFunctionsErrorCodeInvalidArgument;
+ case 401:
+ return FIRFunctionsErrorCodeUnauthenticated;
+ case 403:
+ return FIRFunctionsErrorCodePermissionDenied;
+ case 404:
+ return FIRFunctionsErrorCodeNotFound;
+ case 409:
+ return FIRFunctionsErrorCodeAborted;
+ case 429:
+ return FIRFunctionsErrorCodeResourceExhausted;
+ case 499:
+ return FIRFunctionsErrorCodeCancelled;
+ case 500:
+ return FIRFunctionsErrorCodeInternal;
+ case 501:
+ return FIRFunctionsErrorCodeUnimplemented;
+ case 503:
+ return FIRFunctionsErrorCodeUnavailable;
+ case 504:
+ return FIRFunctionsErrorCodeDeadlineExceeded;
+ }
+ return FIRFunctionsErrorCodeInternal;
+}
+
+/**
+ * Takes the name of an error code and returns the enum value for it.
+ * @param name An error name.
+ * @return The error code with this name, or FIRFunctionsErrorCodeUnknown if none.
+ */
+FIRFunctionsErrorCode FIRFunctionsErrorCodeForName(NSString *name) {
+ static NSDictionary<NSString *, NSNumber *> *errors;
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ errors = @{
+ @"OK" : @(FIRFunctionsErrorCodeOK),
+ @"CANCELLED" : @(FIRFunctionsErrorCodeCancelled),
+ @"UNKNOWN" : @(FIRFunctionsErrorCodeUnknown),
+ @"INVALID_ARGUMENT" : @(FIRFunctionsErrorCodeInvalidArgument),
+ @"DEADLINE_EXCEEDED" : @(FIRFunctionsErrorCodeDeadlineExceeded),
+ @"NOT_FOUND" : @(FIRFunctionsErrorCodeNotFound),
+ @"ALREADY_EXISTS" : @(FIRFunctionsErrorCodeAlreadyExists),
+ @"PERMISSION_DENIED" : @(FIRFunctionsErrorCodePermissionDenied),
+ @"RESOURCE_EXHAUSTED" : @(FIRFunctionsErrorCodeResourceExhausted),
+ @"FAILED_PRECONDITION" : @(FIRFunctionsErrorCodeFailedPrecondition),
+ @"ABORTED" : @(FIRFunctionsErrorCodeAborted),
+ @"OUT_OF_RANGE" : @(FIRFunctionsErrorCodeOutOfRange),
+ @"UNIMPLEMENTED" : @(FIRFunctionsErrorCodeUnimplemented),
+ @"INTERNAL" : @(FIRFunctionsErrorCodeInternal),
+ @"UNAVAILABLE" : @(FIRFunctionsErrorCodeUnavailable),
+ @"DATA_LOSS" : @(FIRFunctionsErrorCodeDataLoss),
+ @"UNAUTHENTICATED" : @(FIRFunctionsErrorCodeUnauthenticated),
+ };
+ });
+ NSNumber *code = errors[name];
+ if (code) {
+ return code.intValue;
+ }
+ return FIRFunctionsErrorCodeInternal;
+}
+
+/**
+ * Takes a FIRFunctionsErrorCode and returns an English description of it.
+ * @param code An error code.
+ * @return A description of the code, or "UNKNOWN" if none.
+ */
+NSString *FUNDescriptionForErrorCode(FIRFunctionsErrorCode code) {
+ switch (code) {
+ case FIRFunctionsErrorCodeOK:
+ return @"OK";
+ case FIRFunctionsErrorCodeCancelled:
+ return @"CANCELLED";
+ case FIRFunctionsErrorCodeUnknown:
+ return @"UNKNOWN";
+ case FIRFunctionsErrorCodeInvalidArgument:
+ return @"INVALID ARGUMENT";
+ case FIRFunctionsErrorCodeDeadlineExceeded:
+ return @"DEADLINE EXCEEDED";
+ case FIRFunctionsErrorCodeNotFound:
+ return @"NOT FOUND";
+ case FIRFunctionsErrorCodeAlreadyExists:
+ return @"ALREADY EXISTS";
+ case FIRFunctionsErrorCodePermissionDenied:
+ return @"PERMISSION DENIED";
+ case FIRFunctionsErrorCodeResourceExhausted:
+ return @"RESOURCE EXHAUSTED";
+ case FIRFunctionsErrorCodeFailedPrecondition:
+ return @"FAILED PRECONDITION";
+ case FIRFunctionsErrorCodeAborted:
+ return @"ABORTED";
+ case FIRFunctionsErrorCodeOutOfRange:
+ return @"OUT OF RANGE";
+ case FIRFunctionsErrorCodeUnimplemented:
+ return @"UNIMPLEMENTED";
+ case FIRFunctionsErrorCodeInternal:
+ return @"INTERNAL";
+ case FIRFunctionsErrorCodeUnavailable:
+ return @"UNAVAILABLE";
+ case FIRFunctionsErrorCodeDataLoss:
+ return @"DATA LOSS";
+ case FIRFunctionsErrorCodeUnauthenticated:
+ return @"UNAUTHENTICATED";
+ }
+ return @"UNKNOWN";
+}
+
+NSError *FUNErrorForResponse(NSInteger status, NSData *_Nullable body, FUNSerializer *serializer) {
+ // Start with reasonable defaults from the status code.
+ FIRFunctionsErrorCode code = FIRFunctionsErrorCodeForHTTPStatus(status);
+ NSString *description = FUNDescriptionForErrorCode(code);
+ id details = nil;
+
+ // Then look through the body for explicit details.
+ if (body) {
+ NSError *parseError = nil;
+ id json = [NSJSONSerialization JSONObjectWithData:body options:0 error:&parseError];
+ if (!parseError && [json isKindOfClass:[NSDictionary class]]) {
+ id errorDetails = json[@"error"];
+ if ([errorDetails isKindOfClass:[NSDictionary class]]) {
+ if ([errorDetails[@"status"] isKindOfClass:[NSString class]]) {
+ code = FIRFunctionsErrorCodeForName(errorDetails[@"status"]);
+ // The default description needs to be updated for the new code.
+ description = FUNDescriptionForErrorCode(code);
+ }
+ if ([errorDetails[@"message"] isKindOfClass:[NSString class]]) {
+ description = (NSString *)errorDetails[@"message"];
+ }
+ details = errorDetails[@"details"];
+ if (details) {
+ NSError *decodeError = nil;
+ details = [serializer decode:details error:&decodeError];
+ // Just ignore the details if there an error decoding them.
+ }
+ }
+ }
+ }
+
+ if (code == FIRFunctionsErrorCodeOK) {
+ // Technically, there's an edge case where a developer could explicitly return an error code of
+ // OK, and we will treat it as success, but that seems reasonable.
+ return nil;
+ }
+
+ NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
+ userInfo[NSLocalizedDescriptionKey] = description;
+ if (details) {
+ userInfo[FIRFunctionsErrorDetailsKey] = details;
+ }
+ return [NSError errorWithDomain:FIRFunctionsErrorDomain code:code userInfo:userInfo];
+}
+
+NS_ASSUME_NONNULL_END
diff --git a/Functions/FirebaseFunctions/FUNInstanceIDProxy.h b/Functions/FirebaseFunctions/FUNInstanceIDProxy.h
new file mode 100644
index 0000000..17ec9ef
--- /dev/null
+++ b/Functions/FirebaseFunctions/FUNInstanceIDProxy.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2017 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.
+ */
+
+// Note: This file is forked from FIRMessagingInstanceIDProxy.h
+
+#import <Foundation/Foundation.h>
+
+/**
+ * FirebaseFunctions cannot always depend on FIRInstanceID directly, due to how it is
+ * packaged. To make it easier to make calls to FIRInstanceID, this proxy class, will provide
+ * method names duplicated from FIRInstanceID, while using reflection-based called to proxy
+ * the requests.
+ */
+@interface FUNInstanceIDProxy : NSObject
+- (nullable NSString *)token;
+@end
diff --git a/Functions/FirebaseFunctions/FUNInstanceIDProxy.m b/Functions/FirebaseFunctions/FUNInstanceIDProxy.m
new file mode 100644
index 0000000..f89ec98
--- /dev/null
+++ b/Functions/FirebaseFunctions/FUNInstanceIDProxy.m
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2017 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.
+ */
+
+// Note: This is forked from FIRMessagingInstanceIDProxy.m
+
+#import "FUNInstanceIDProxy.h"
+
+@implementation FUNInstanceIDProxy
+
++ (nonnull instancetype)instanceIDProxy {
+ static id proxyInstanceID = nil;
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ Class instanceIDClass = NSClassFromString(@"FIRInstanceID");
+ if (!instanceIDClass) {
+ proxyInstanceID = nil;
+ return;
+ }
+ SEL instanceIDSelector = NSSelectorFromString(@"instanceID");
+ if (![instanceIDClass respondsToSelector:instanceIDSelector]) {
+ proxyInstanceID = nil;
+ return;
+ }
+ IMP instanceIDImp = [instanceIDClass methodForSelector:instanceIDSelector];
+ id (*instanceIDFunc)(id, SEL) = (void *)instanceIDImp;
+ proxyInstanceID = instanceIDFunc(instanceIDClass, instanceIDSelector);
+ });
+ return (FUNInstanceIDProxy *)proxyInstanceID;
+}
+
+#pragma mark - Tokens
+
+- (nullable NSString *)token {
+ id proxy = [[self class] instanceIDProxy];
+ SEL getTokenSelector = NSSelectorFromString(@"token");
+ if (![proxy respondsToSelector:getTokenSelector]) {
+ return nil;
+ }
+ IMP getTokenIMP = [proxy methodForSelector:getTokenSelector];
+ NSString *(*getToken)(id, SEL) = (void *)getTokenIMP;
+ return getToken(proxy, getTokenSelector);
+}
+
+@end
diff --git a/Functions/FirebaseFunctions/FUNSerializer.h b/Functions/FirebaseFunctions/FUNSerializer.h
new file mode 100644
index 0000000..598a2a1
--- /dev/null
+++ b/Functions/FirebaseFunctions/FUNSerializer.h
@@ -0,0 +1,33 @@
+// Copyright 2017 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/Foundation.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface FUNSerializer : NSObject
+
+/**
+ * Converts raw Objective-C types into JSON objects.
+ */
+- (id)encode:(id)object;
+
+/**
+ * Converts objects in JSON to Objective-C types.
+ */
+- (id)decode:(id)object error:(NSError **)error;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Functions/FirebaseFunctions/FUNSerializer.m b/Functions/FirebaseFunctions/FUNSerializer.m
new file mode 100644
index 0000000..682f981
--- /dev/null
+++ b/Functions/FirebaseFunctions/FUNSerializer.m
@@ -0,0 +1,231 @@
+// Copyright 2017 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 "FUNSerializer.h"
+
+#import "FIRError.h"
+#import "FUNUsageValidation.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+static NSString *const kLongType = @"type.googleapis.com/google.protobuf.Int64Value";
+static NSString *const kUnsignedLongType = @"type.googleapis.com/google.protobuf.UInt64Value";
+static NSString *const kDateType = @"type.googleapis.com/google.protobuf.Timestamp";
+
+@interface FUNSerializer () {
+ NSDateFormatter *_dateFormatter;
+}
+@end
+
+@implementation FUNSerializer
+
+- (instancetype)init {
+ self = [super init];
+ if (self) {
+ _dateFormatter = [[NSDateFormatter alloc] init];
+ _dateFormatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
+ _dateFormatter.timeZone = [NSTimeZone timeZoneWithName:@"UTC"];
+ }
+ return self;
+}
+
+- (id)encodeNumber:(NSNumber *)number {
+ // Recover the underlying type of the number, using the method described here:
+ // http://stackoverflow.com/questions/2518761/get-type-of-nsnumber
+ const char *cType = [number objCType];
+
+ // Type Encoding values taken from
+ // https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/
+ // Articles/ocrtTypeEncodings.html
+ switch (cType[0]) {
+ case 'q':
+ // "long long" might be larger than JS supports, so make it a string.
+ return @{
+ @"@type" : kLongType,
+ @"value" : [NSString stringWithFormat:@"%@", number],
+ };
+ case 'Q':
+ // "unsigned long long" might be larger than JS supports, so make it a string.
+ return @{
+ @"@type" : kUnsignedLongType,
+ @"value" : [NSString stringWithFormat:@"%@", number],
+ };
+
+ case 'i':
+ case 's':
+ case 'l':
+ case 'I':
+ case 'S':
+ // If it's an integer that isn't too long, so just use the number.
+ return number;
+
+ case 'f':
+ case 'd':
+ // It's a float/double that's not too large.
+ return number;
+
+ case 'B':
+ case 'c':
+ case 'C':
+ // Boolean values are weird.
+ //
+ // On arm64, objCType of a BOOL-valued NSNumber will be "c", even though @encode(BOOL)
+ // returns "B". "c" is the same as @encode(signed char). Unfortunately this means that
+ // legitimate usage of signed chars is impossible, but this should be rare.
+ //
+ // Just return Boolean values as-is.
+ return number;
+
+ default:
+ // All documented codes should be handled above, so this shouldn't happen.
+ FUNThrowInvalidArgument(@"Unknown NSNumber objCType %s on %@", cType, number);
+ }
+}
+
+- (id)encode:(id)object {
+ if ([object isEqual:[NSNull null]]) {
+ return object;
+ }
+ if ([object isKindOfClass:[NSNumber class]]) {
+ return [self encodeNumber:object];
+ }
+ if ([object isKindOfClass:[NSString class]]) {
+ return object;
+ }
+ if ([object isKindOfClass:[NSDictionary class]]) {
+ NSMutableDictionary *encoded = [NSMutableDictionary dictionary];
+ [object
+ enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL *_Nonnull stop) {
+ encoded[key] = [self encode:obj];
+ }];
+ return encoded;
+ }
+ if ([object isKindOfClass:[NSArray class]]) {
+ NSMutableArray *encoded = [NSMutableArray arrayWithCapacity:[object count]];
+ for (id obj in object) {
+ [encoded addObject:[self encode:obj]];
+ }
+ return encoded;
+ }
+ // TODO(klimt): Add this back when we support NSDate.
+ /*
+ if ([object isKindOfClass:[NSDate class]]) {
+ NSString *iso8601 = [_dateFormatter stringFromDate:object];
+ return @{
+ @"@type" : kDateType,
+ @"value" : iso8601,
+ };
+ }
+ */
+ FUNThrowInvalidArgument(@"Unsupported type: %@ for value %@", NSStringFromClass([object class]),
+ object);
+}
+
+NSError *FUNInvalidNumberError(id value, id wrapped) {
+ NSString *description = [NSString stringWithFormat:@"Invalid number: %@ for %@", value, wrapped];
+ NSDictionary *userInfo = @{
+ NSLocalizedDescriptionKey : description,
+ };
+ return [NSError errorWithDomain:FIRFunctionsErrorDomain
+ code:FIRFunctionsErrorCodeInternal
+ userInfo:userInfo];
+}
+
+- (id)decodeWrappedType:(NSDictionary *)wrapped error:(NSError **)error {
+ NSAssert(error, @"error must not be nil");
+ NSString *type = wrapped[@"@type"];
+ NSString *value = wrapped[@"value"];
+ if (!value) {
+ return nil;
+ }
+ if ([type isEqualToString:kLongType]) {
+ NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
+ NSNumber *n = [formatter numberFromString:value];
+ if (!n) {
+ *error = FUNInvalidNumberError(value, wrapped);
+ return nil;
+ }
+ return n;
+ } else if ([type isEqualToString:kUnsignedLongType]) {
+ // NSNumber formatter doesn't handle unsigned long long, so we have to parse it.
+ const char *str = value.UTF8String;
+ char *end = NULL;
+ unsigned long long n = strtoull(str, &end, 10);
+ if (errno == ERANGE) {
+ // This number was actually too big for an unsigned long long.
+ *error = FUNInvalidNumberError(value, wrapped);
+ return nil;
+ }
+ if (*end) {
+ // The whole string wasn't parsed.
+ *error = FUNInvalidNumberError(value, wrapped);
+ return nil;
+ }
+ return @(n);
+ }
+ return nil;
+}
+
+- (id)decode:(id)object error:(NSError **)error {
+ NSAssert(error, @"error must not be nil");
+ if ([object isKindOfClass:[NSDictionary class]]) {
+ if (object[@"@type"]) {
+ id result = [self decodeWrappedType:object error:error];
+ if (*error) {
+ return nil;
+ }
+ if (result) {
+ return result;
+ }
+ // Treat unknown types as dictionaries, so we don't crash old clients when we add types.
+ }
+ NSMutableDictionary *decoded = [NSMutableDictionary dictionary];
+ __block NSError *decodeError = nil;
+ [object
+ enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL *_Nonnull stop) {
+ id decodedItem = [self decode:obj error:&decodeError];
+ if (decodeError) {
+ *stop = YES;
+ return;
+ }
+ decoded[key] = decodedItem;
+ }];
+ if (decodeError) {
+ *error = decodeError;
+ return nil;
+ }
+ return decoded;
+ }
+ if ([object isKindOfClass:[NSArray class]]) {
+ NSMutableArray *result = [NSMutableArray arrayWithCapacity:[object count]];
+ for (id obj in object) {
+ id decoded = [self decode:obj error:error];
+ if (*error) {
+ return nil;
+ }
+ [result addObject:decoded];
+ }
+ return result;
+ }
+ if ([object isKindOfClass:[NSNumber class]] || [object isKindOfClass:[NSString class]] ||
+ [object isEqual:[NSNull null]]) {
+ return object;
+ }
+ FUNThrowInvalidArgument(@"Unsupported type: %@ for value %@", NSStringFromClass([object class]),
+ object);
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Functions/FirebaseFunctions/FUNUsageValidation.h b/Functions/FirebaseFunctions/FUNUsageValidation.h
new file mode 100644
index 0000000..17b1c4f
--- /dev/null
+++ b/Functions/FirebaseFunctions/FUNUsageValidation.h
@@ -0,0 +1,38 @@
+// Copyright 2017 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/Foundation.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+/** Helper for creating a general exception for invalid usage of an API. */
+NSException *FUNInvalidUsage(NSString *exceptionName, NSString *format, ...);
+
+/**
+ * Macro to throw exceptions in response to API usage errors. Avoids the lint warning you usually
+ * get when using @throw and (unlike a function) doesn't trigger warnings about not all codepaths
+ * returning a value.
+ *
+ * Exceptions should only be used for programmer errors made by consumers of the SDK, e.g.
+ * invalid method arguments.
+ *
+ * For recoverable runtime errors, use NSError**.
+ * For internal programming errors, use FSTFail().
+ */
+#define FUNThrowInvalidArgument(format, ...) \
+ do { \
+ @throw FUNInvalidUsage(@"FIRInvalidArgumentException", format, ##__VA_ARGS__); \
+ } while (0)
+
+NS_ASSUME_NONNULL_END
diff --git a/Functions/FirebaseFunctions/FUNUsageValidation.m b/Functions/FirebaseFunctions/FUNUsageValidation.m
new file mode 100644
index 0000000..a50f525
--- /dev/null
+++ b/Functions/FirebaseFunctions/FUNUsageValidation.m
@@ -0,0 +1,28 @@
+// Copyright 2017 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 "FUNUsageValidation.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+NSException *FUNInvalidUsage(NSString *exceptionName, NSString *format, ...) {
+ va_list arg_list;
+ va_start(arg_list, format);
+ NSString *formattedString = [[NSString alloc] initWithFormat:format arguments:arg_list];
+ va_end(arg_list);
+
+ return [[NSException alloc] initWithName:exceptionName reason:formattedString userInfo:nil];
+}
+
+NS_ASSUME_NONNULL_END
diff --git a/Functions/FirebaseFunctions/Public/FIRError.h b/Functions/FirebaseFunctions/Public/FIRError.h
new file mode 100644
index 0000000..5037f7b
--- /dev/null
+++ b/Functions/FirebaseFunctions/Public/FIRError.h
@@ -0,0 +1,91 @@
+// Copyright 2017 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/Foundation.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+// The error domain for codes in the FIRFunctionsErrorCode enum.
+FOUNDATION_EXPORT NSString *const FIRFunctionsErrorDomain NS_SWIFT_NAME(FunctionsErrorDomain);
+
+// The key for finding error details in the NSError userInfo.
+FOUNDATION_EXPORT NSString *const FIRFunctionsErrorDetailsKey
+ NS_SWIFT_NAME(FunctionsErrorDetailsKey);
+
+/**
+ * The set of error status codes that can be returned from a Callable HTTPS tigger. These are the
+ * canonical error codes for Google APIs, as documented here:
+ * https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto#L26
+ */
+typedef NS_ENUM(NSInteger, FIRFunctionsErrorCode) {
+ /** The operation completed successfully. */
+ FIRFunctionsErrorCodeOK = 0,
+ /** The operation was cancelled (typically by the caller). */
+ FIRFunctionsErrorCodeCancelled = 1,
+ /** Unknown error or an error from a different error domain. */
+ FIRFunctionsErrorCodeUnknown = 2,
+ /**
+ * Client specified an invalid argument. Note that this differs from `FailedPrecondition`.
+ * `InvalidArgument` indicates arguments that are problematic regardless of the state of the
+ * system (e.g., an invalid field name).
+ */
+ FIRFunctionsErrorCodeInvalidArgument = 3,
+ /**
+ * Deadline expired before operation could complete. For operations that change the state of the
+ * system, this error may be returned even if the operation has completed successfully. For
+ * example, a successful response from a server could have been delayed long enough for the
+ * deadline to expire.
+ */
+ FIRFunctionsErrorCodeDeadlineExceeded = 4,
+ /** Some requested document was not found. */
+ FIRFunctionsErrorCodeNotFound = 5,
+ /** Some document that we attempted to create already exists. */
+ FIRFunctionsErrorCodeAlreadyExists = 6,
+ /** The caller does not have permission to execute the specified operation. */
+ FIRFunctionsErrorCodePermissionDenied = 7,
+ /**
+ * Some resource has been exhausted, perhaps a per-user quota, or perhaps the entire file system
+ * is out of space.
+ */
+ FIRFunctionsErrorCodeResourceExhausted = 8,
+ /**
+ * Operation was rejected because the system is not in a state required for the operation's
+ * execution.
+ */
+ FIRFunctionsErrorCodeFailedPrecondition = 9,
+ /**
+ * The operation was aborted, typically due to a concurrency issue like transaction aborts, etc.
+ */
+ FIRFunctionsErrorCodeAborted = 10,
+ /** Operation was attempted past the valid range. */
+ FIRFunctionsErrorCodeOutOfRange = 11,
+ /** Operation is not implemented or not supported/enabled. */
+ FIRFunctionsErrorCodeUnimplemented = 12,
+ /**
+ * Internal errors. Means some invariant expected by underlying system has been broken. If you
+ * see one of these errors, something is very broken.
+ */
+ FIRFunctionsErrorCodeInternal = 13,
+ /**
+ * The service is currently unavailable. This is a most likely a transient condition and may be
+ * corrected by retrying with a backoff.
+ */
+ FIRFunctionsErrorCodeUnavailable = 14,
+ /** Unrecoverable data loss or corruption. */
+ FIRFunctionsErrorCodeDataLoss = 15,
+ /** The request does not have valid authentication credentials for the operation. */
+ FIRFunctionsErrorCodeUnauthenticated = 16,
+} NS_SWIFT_NAME(FunctionsErrorCode);
+
+NS_ASSUME_NONNULL_END
diff --git a/Functions/FirebaseFunctions/Public/FIRFunctions.h b/Functions/FirebaseFunctions/Public/FIRFunctions.h
new file mode 100644
index 0000000..d01175c
--- /dev/null
+++ b/Functions/FirebaseFunctions/Public/FIRFunctions.h
@@ -0,0 +1,66 @@
+// Copyright 2017 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/Foundation.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@class FIRApp;
+@class FIRHTTPSCallable;
+
+/**
+ * `FIRFunctions` is the client for Cloud Functions for a Firebase project.
+ */
+NS_SWIFT_NAME(Functions)
+@interface FIRFunctions : NSObject
+
+- (id)init NS_UNAVAILABLE;
+
+/**
+ * Creates a Cloud Functions client with the default app.
+ */
++ (instancetype)functions NS_SWIFT_NAME(functions());
+
+/**
+ * Creates a Cloud Functions client with the given app.
+ * @param app The app for the Firebase project.
+ */
++ (instancetype)functionsForApp:(FIRApp *)app NS_SWIFT_NAME(functions(app:));
+
+/**
+ * Creates a Cloud Functions client with the default app and given region.
+ * @param region The region for the http trigger, such as "us-central1".
+ */
+// + (instancetype)functionsForRegion:(NSString *)region NS_SWIFT_NAME(functions(region:));
+
+/**
+ * Creates a Cloud Functions client with the given app and region.
+ * @param app The app for the Firebase project.
+ * @param region The region for the http trigger, such as "us-central1".
+ */
+// clang-format off
+// because it incorrectly breaks this NS_SWIFT_NAME.
+// + (instancetype)functionsForApp:(FIRApp *)app
+// region:(NSString *)region NS_SWIFT_NAME(functions(app:region:));
+// clang-format on
+
+/**
+ * Creates a reference to the Callable HTTPS trigger with the given name.
+ * @param name The name of the Callable HTTPS trigger.
+ */
+- (FIRHTTPSCallable *)HTTPSCallableWithName:(NSString *)name NS_SWIFT_NAME(httpsCallable(_:));
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Functions/FirebaseFunctions/Public/FIRHTTPSCallable.h b/Functions/FirebaseFunctions/Public/FIRHTTPSCallable.h
new file mode 100644
index 0000000..473a944
--- /dev/null
+++ b/Functions/FirebaseFunctions/Public/FIRHTTPSCallable.h
@@ -0,0 +1,94 @@
+// Copyright 2017 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/Foundation.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ * A `FIRHTTPSCallableResult` contains the result of calling a `FIRHTTPSCallable`.
+ */
+NS_SWIFT_NAME(HTTPSCallableResult)
+@interface FIRHTTPSCallableResult : NSObject
+
+- (id)init NS_UNAVAILABLE;
+
+/**
+ * The data that was returned from the Callable HTTPS trigger.
+ *
+ * The data is in the form of native objects. For example, if your trigger returned an
+ * array, this object would be an NSArray. If your trigger returned a JavaScript object with
+ * keys and values, this object would be an NSDictionary.
+ */
+@property(nonatomic, strong, readonly) id data;
+
+@end
+
+/**
+ * A `FIRHTTPSCallable` is reference to a particular Callable HTTPS trigger in Cloud Functions.
+ */
+NS_SWIFT_NAME(HTTPSCallable)
+@interface FIRHTTPSCallable : NSObject
+
+- (id)init NS_UNAVAILABLE;
+
+/**
+ * Executes this Callable HTTPS trigger asynchronously without any parameters.
+ *
+ * The request to the Cloud Functions backend made by this method automatically includes a
+ * Firebase Instance ID token to identify the app instance. If a user is logged in with Firebase
+ * Auth, an auth ID token for the user is also automatically included.
+ *
+ * Firebase Instance ID sends data to the Firebase backend periodically to collect information
+ * regarding the app instance. To stop this, see `[FIRInstanceID deleteIDWithHandler:]`. It
+ * resumes with a new Instance ID the next time you call this method.
+ *
+ * @param completion The block to call when the HTTPS request has completed.
+ */
+- (void)callWithCompletion:
+ (void (^)(FIRHTTPSCallableResult *_Nullable result, NSError *_Nullable error))completion
+ NS_SWIFT_NAME(call(completion:));
+
+/**
+ * Executes this Callable HTTPS trigger asynchronously.
+ *
+ * The data passed into the trigger can be any of the following types:
+ * * NSNull
+ * * NSString
+ * * NSNumber
+ * * NSArray<id>, where the contained objects are also one of these types.
+ * * NSDictionary<NSString, id>, where the values are also one of these types.
+ *
+ * The request to the Cloud Functions backend made by this method automatically includes a
+ * Firebase Instance ID token to identify the app instance. If a user is logged in with Firebase
+ * Auth, an auth ID token for the user is also automatically included.
+ *
+ * Firebase Instance ID sends data to the Firebase backend periodically to collect information
+ * regarding the app instance. To stop this, see `[FIRInstanceID deleteIDWithHandler:]`. It
+ * resumes with a new Instance ID the next time you call this method.
+ *
+ * @param data Parameters to pass to the trigger.
+ * @param completion The block to call when the HTTPS request has completed.
+ */
+// clang-format off
+// because it incorrectly breaks this NS_SWIFT_NAME.
+- (void)callWithObject:(nullable id)data
+ completion:(void (^)(FIRHTTPSCallableResult *_Nullable result,
+ NSError *_Nullable error))completion
+ NS_SWIFT_NAME(call(_:completion:));
+// clang-format on
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Functions/README.md b/Functions/README.md
new file mode 100644
index 0000000..f0b8d62
--- /dev/null
+++ b/Functions/README.md
@@ -0,0 +1,21 @@
+# Cloud Functions for Firebase iOS SDK
+
+## To run unit tests
+
+Choose the FirebaseFunctions_Tests scheme and press Command-u.
+
+## To run integration tests
+
+Before running the integration tests, you'll need to start a backend emulator
+for them to talk to.
+
+1. Make sure you have `npm` installed.
+2. Run the backend startup script: `Backend/start.sh`
+ It will use `npm install` to automatically download the libraries it needs
+ to run the [Cloud Functions Local Emulator](https://cloud.google.com/functions/docs/emulator).
+ The first time you run it, it will ask for a projectId.
+ You can put anything you like. It will be ignored.
+3. Create the workspace in Functions/Example with `pod install`.
+4. `open FirebaseFunctions.xcworkspace`
+5. Choose the FirebaseFunctions_IntegrationTests scheme and press Command-u.
+6. When you are finished, you can press any key to stop the backend.
diff --git a/Gemfile.lock b/Gemfile.lock
index ea2bdde..e553857 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,12 +1,12 @@
GIT
remote: https://github.com/CocoaPods/CocoaPods.git
- revision: 71211b5046cb1c9f5ad33e45d42f16b43eabfb8f
+ revision: df6872de04fd1808fde71c824530e3a784e6a2d2
specs:
- cocoapods (1.4.0.beta.2)
+ cocoapods (1.4.0)
activesupport (>= 4.0.2, < 5)
claide (>= 1.0.2, < 2.0)
- cocoapods-core (= 1.4.0.beta.2)
- cocoapods-deintegrate (>= 1.0.1, < 2.0)
+ cocoapods-core (= 1.4.0)
+ cocoapods-deintegrate (>= 1.0.2, < 2.0)
cocoapods-downloader (>= 1.1.3, < 2.0)
cocoapods-plugins (>= 1.0.0, < 2.0)
cocoapods-search (>= 1.0.0, < 2.0)
@@ -20,23 +20,24 @@ GIT
molinillo (~> 0.6.4)
nap (~> 1.0)
ruby-macho (~> 1.1)
- xcodeproj (>= 1.5.3, < 2.0)
+ xcodeproj (>= 1.5.4, < 2.0)
GIT
remote: https://github.com/CocoaPods/Core.git
- revision: 8cf88a076d916cf80821744a59ca1c431ccc046f
+ revision: b4fb2f193897c789c094d126ebca91034edc261d
specs:
- cocoapods-core (1.4.0.beta.2)
+ cocoapods-core (1.4.0)
activesupport (>= 4.0.2, < 6)
fuzzy_match (~> 2.0.4)
nap (~> 1.0)
GIT
remote: https://github.com/CocoaPods/Xcodeproj.git
- revision: aa866f4bd3f4269ae4ad1ec340ae077524f8e24d
+ revision: 531d8aa8ab4805c84f5e0fe94181ae3995430d05
specs:
- xcodeproj (1.5.3)
+ xcodeproj (1.5.4)
CFPropertyList (~> 2.3.3)
+ atomos (~> 0.1.0)
claide (>= 1.0.2, < 2.0)
colored2 (~> 3.1)
nanaimo (~> 0.2.3)
@@ -44,14 +45,15 @@ GIT
GEM
remote: https://rubygems.org/
specs:
- CFPropertyList (2.3.5)
+ CFPropertyList (2.3.6)
activesupport (4.2.10)
i18n (~> 0.7)
minitest (~> 5.1)
thread_safe (~> 0.3, >= 0.3.4)
tzinfo (~> 1.1)
+ atomos (0.1.0)
claide (1.0.2)
- cocoapods-deintegrate (1.0.1)
+ cocoapods-deintegrate (1.0.2)
cocoapods-downloader (1.1.3)
cocoapods-plugins (1.0.0)
nap
@@ -67,9 +69,9 @@ GEM
fourflusher (2.0.1)
fuzzy_match (2.0.4)
gh_inspector (1.0.3)
- i18n (0.9.1)
+ i18n (0.9.3)
concurrent-ruby (~> 1.0)
- minitest (5.10.3)
+ minitest (5.11.1)
molinillo (0.6.4)
nanaimo (0.2.3)
nap (1.1.0)
@@ -88,4 +90,4 @@ DEPENDENCIES
xcodeproj!
BUNDLED WITH
- 1.15.4
+ 1.16.0
diff --git a/README.md b/README.md
index e14e096..7ada9ec 100644
--- a/README.md
+++ b/README.md
@@ -2,61 +2,100 @@
This repository contains a subset of the Firebase iOS SDK source. It currently
includes FirebaseCore, FirebaseAuth, FirebaseDatabase, FirebaseFirestore,
-FirebaseMessaging and FirebaseStorage.
+FirebaseFunctions, FirebaseMessaging and FirebaseStorage.
Firebase is an app development platform with tools to help you build, grow and
monetize your app. More information about Firebase can be found at
[https://firebase.google.com](https://firebase.google.com).
-**Note: This page and repo is for those interested in exploring the internals of
-the Firebase iOS SDK. If you're interested in using the Firebase iOS SDK, start at
-[https://firebase.google.com/docs/ios/setup](https://firebase.google.com/docs/ios/setup).**
+## Installation
-## Context
+See the three subsections for details about three different installation methods.
+1. [Officially supported binary pods](README.md#binary-pods-ios-only)
+1. [Community supported source pods](README.md#source-pods-ios-macos-tvos)
+1. [Experimental Carthage](README.md#carthage-ios-only)
-This repo contains a fully functional development environment for FirebaseCore,
-FirebaseAuth, FirebaseDatabase, FirebaseFirestore, FirebaseMessaging, and
-FirebaseStorage. By following the usage instructions below, they can be
-developed and debugged with unit tests, integration tests, and reference samples.
+### Binary pods (iOS only)
-## Source pod integration
+Go to
+[https://firebase.google.com/docs/ios/setup](https://firebase.google.com/docs/ios/setup).
+
+### Source pods (iOS, macOS, tvOS)
While the official Firebase release remains a binary framework distribution,
in the future, we plan to switch to a source CocoaPod distribution for the
Firebase open source components.
-It is now possible to override the default pod locations with source pod
-locations described via the Podfile syntax documented
-[here](https://guides.cocoapods.org/syntax/podfile.html#pod).
+#### Background
-**CocoaPods 1.4.0** or later is required.
+See
+[the Podfile Syntax Reference](https://guides.cocoapods.org/syntax/podfile.html#pod)
+for instructions and options about overriding pod source locations.
-For example, to access FirebaseMessaging via a checked out version of the
-firebase-ios-sdk repo do:
+#### Step-by-step Source Pod Installation Instructions
+
+For iOS, copy a subset of the following lines to your Podfile:
```
-pod 'FirebaseMessaging', :path => '/path/to/firebase-ios-sdk'
-```
-To access via a branch:
-```
-pod 'FirebaseFirestore', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :branch => 'master'
+pod 'Firebase' # To enable Firebase module, with `@import Firebase` support
+pod 'FirebaseCore', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :tag => '4.11.0'
+pod 'FirebaseAuth', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :tag => '4.11.0'
+pod 'FirebaseDatabase', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :tag => '4.11.0'
+pod 'FirebaseFirestore', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :tag => '4.11.0'
+pod 'FirebaseFunctions', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :tag => '4.11.0'
+pod 'FirebaseMessaging', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :tag => '4.11.0'
+pod 'FirebaseStorage', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :tag => '4.11.0'
```
-To access via a tag (Release tags will be available starting with Firebase 4.7.0:
+For macOS and tvOS, copy a subset of the following:
+
```
-pod 'FirebaseAuth', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :tag => '4.7.0'
+pod 'FirebaseCore', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :tag => '4.11.0'
+pod 'FirebaseAuth', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :tag => '4.11.0'
+pod 'FirebaseDatabase', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :tag => '4.11.0'
+pod 'FirebaseStorage', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :tag => '4.11.0'
```
+1. Make sure you have at least CocoaPods version 1.4.0 - `pod --version`.
+1. Delete pods for any components you don't need, except `FirebaseCore` must always be included.
+1. Update the tags to the latest Firebase release. See the
+[release notes](https://firebase.google.com/support/release-notes/ios).
+1. Run `pod update`.
+
+#### Static library usage
+
If your Podfile does not include *use_frameworks!*, you need to workaround
a build issue with the FirebaseAnalytics umbrella header. Delete the first four lines
-of Pods/FirebaseAnalytics/Frameworks/FirebaseAnalytics.framework/Headers/FirebaseAnalytics.h
+of `Pods/FirebaseAnalytics/Frameworks/FirebaseAnalytics.framework/Headers/FirebaseAnalytics.h`
or copy [patch/FirebaseAnalytics.h](patch/FirebaseAnalytics.h) to
-Pods/FirebaseAnalytics/Frameworks/FirebaseAnalytics.framework/Headers/FirebaseAnalytics.h.
-See the post_install phase of [Example/Podfile](Example/Podfile) for an example
+`Pods/FirebaseAnalytics/Frameworks/FirebaseAnalytics.framework/Headers/FirebaseAnalytics.h`.
+See the `post_install` phase of [Example/Podfile](Example/Podfile) for an example
of applying the workaround automatically - make sure you correct the path of
`patch/FirebaseAnalytics.h`.
-## Usage
+#### Examples
+
+To access FirebaseMessaging via a checked out version of the firebase-ios-sdk repo do:
+
+```
+pod 'FirebaseMessaging', :path => '/path/to/firebase-ios-sdk'
+pod 'FirebaseCore', :path => '/path/to/firebase-ios-sdk'
+```
+To access via a branch:
+```
+pod 'FirebaseFirestore', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :branch => 'master'
+pod 'FirebaseCore', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :branch => 'master'
+```
+
+### Carthage (iOS only)
+
+An experimental Carthage distribution is now available. See
+[Carthage](Carthage.md).
+
+## Development
+
+Follow the subsequent instructions to develop, debug, unit test, run integration
+tests, and try out reference samples:
```
$ git clone git@github.com:firebase/firebase-ios-sdk.git
@@ -65,8 +104,9 @@ $ pod update
$ open Firebase.xcworkspace
```
-Firestore has a self contained Xcode project. See
-[Firestore/README.md](Firestore/README.md).
+Firestore and Functions have self contained Xcode projects. See
+[Firestore/README.md](Firestore/README.md) and
+[Functions/README.md](Functions/README.md).
### Running Unit Tests
@@ -132,16 +172,20 @@ We've seen an amazing amount of interest and contributions to improve the Fireba
very grateful! We'd like to empower as many developers as we can to be able to use Firebase and
participate in the Firebase community.
-### macOS
+### macOS and tvOS
FirebaseAuth, FirebaseCore, FirebaseDatabase and FirebaseStorage now compile, run unit tests, and
-work on macOS, thanks to contributions from the community. There are a few tweaks needed, like
-ensuring iOS-only or macOS-only code is correctly guarded with checks for `TARGET_OS_IOS` and
-`TARGET_OS_OSX`.
-
-Keep in mind that macOS is not officially supported by Firebase, and this repository is actively
-developed primarily for iOS. While we can catch basic unit test issues with Travis, there may be
-some changes where the SDK no longer works as expected on macOS. If you encounter this, please
-[file an issue](https://github.com/firebase/firebase-ios-sdk/issues) for it.
+work on macOS and tvOS, thanks to contributions from the community. There are a few tweaks needed,
+like ensuring iOS-only, macOS-only, or tvOS-only code is correctly guarded with checks for
+`TARGET_OS_IOS`, `TARGET_OS_OSX` and `TARGET_OS_TV`.
+
+For tvOS, checkout the [Sample](Example/tvOSSample).
+
+Keep in mind that macOS and tvOS are not officially supported by Firebase, and this repository is
+actively developed primarily for iOS. While we can catch basic unit test issues with Travis, there
+may be some changes where the SDK no longer works as expected on macOS or tvOS. If you encounter
+this, please [file an issue](https://github.com/firebase/firebase-ios-sdk/issues).
+
+For installation instructions, see [above](README.md#step-by-step-source-pod-installation-instructions).
## Roadmap
diff --git a/cmake/CompilerSetup.cmake b/cmake/CompilerSetup.cmake
new file mode 100644
index 0000000..b560027
--- /dev/null
+++ b/cmake/CompilerSetup.cmake
@@ -0,0 +1,99 @@
+# Copyright 2018 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.
+
+# C++ Compiler setup
+
+# We use C++11
+set(CMAKE_CXX_STANDARD 11)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+set(CMAKE_CXX_EXTENSIONS OFF)
+
+if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
+ set(CLANG ON)
+endif()
+
+if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
+ set(GNU ON)
+endif()
+
+if(CLANG OR GNU)
+ set(
+ common_flags
+ -Wall -Wextra -Werror
+
+ # Be super pedantic about format strings
+ -Wformat
+
+ # Avoid use of uninitialized values
+ -Wuninitialized
+ -fno-common
+
+ # Delete unused things
+ -Wunused-function -Wunused-value -Wunused-variable
+ )
+
+ set(
+ cxx_flags
+
+ # Cut down on symbol clutter
+ # TODO(wilhuff) try -fvisibility=hidden
+ -fvisibility-inlines-hidden
+ )
+
+ set(
+ c_flags
+ -Wstrict-prototypes
+ )
+
+ if(CLANG)
+ list(
+ APPEND common_flags
+ -Wconditional-uninitialized -Werror=return-type -Winfinite-recursion -Wmove
+ -Wrange-loop-analysis -Wunreachable-code
+
+ # Options added to match apple recommended project settings
+ -Wcomma
+ )
+ endif()
+
+ foreach(flag ${common_flags} ${c_flags})
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${flag}")
+ endforeach()
+
+ foreach(flag ${common_flags} ${cxx_flags})
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${flag}")
+ endforeach()
+endif()
+
+if(APPLE)
+ # CMake has no special support for Objective-C as a distinct language but
+ # enabling modules and other clang extensions would apply even to regular C++
+ # sources which is nonportable. Keep these flags separate to avoid misuse.
+ set(
+ OBJC_FLAGS
+ -Werror=deprecated-objc-isa-usage
+ -Werror=non-modular-include-in-framework-module
+ -Werror=objc-root-class
+
+ -Wblock-capture-autoreleasing
+ -Wimplicit-atomic-properties
+ -Wnon-modular-include-in-framework-module
+
+ -fobjc-arc
+ -fmodules
+ -fno-autolink
+
+ -F${FIREBASE_INSTALL_DIR}/Frameworks
+ )
+endif(APPLE)
diff --git a/cmake/ExternalProjectFlags.cmake b/cmake/ExternalProjectFlags.cmake
new file mode 100644
index 0000000..ed4db2c
--- /dev/null
+++ b/cmake/ExternalProjectFlags.cmake
@@ -0,0 +1,71 @@
+# Copyright 2018 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.
+
+include(CMakeParseArguments)
+
+# Assemble the git-related arguments to an external project making use of the
+# latest features where available but avoiding them when run under CMake
+# versions that don't support them.
+#
+# The complete set of git-related arguments are stored as a list in the
+# variable named by RESULT_VAR in the calling scope.
+#
+# Currently this handles:
+# * GIT_SUBMODULES -- added on CMake 3.0 or later. Earlier CMakes will
+# check out all submodules.
+# * GIT_SHALLOW -- added by default on CMake 3.6 or later. Disable by passing
+# GIT_SHALLOW OFF
+# * GIT_PROGRESS -- added by default on CMake 3.8 or later. Disable by
+# passing GIT_PROGRESS OFF
+function(ExternalProject_GitSource RESULT_VAR)
+ # Parse arguments
+ set(options "")
+ set(single_value GIT_REPOSITORY GIT_TAG GIT_PROGRESS GIT_SHALLOW)
+ set(multi_value GIT_SUBMODULES)
+ cmake_parse_arguments(EP "${options}" "${single_value}" "${multi_value}" ${ARGN})
+
+ set(
+ result
+ GIT_REPOSITORY ${EP_GIT_REPOSITORY}
+ GIT_TAG ${EP_GIT_TAG}
+ ${EP_UNPARSED_ARGUMENTS}
+ )
+
+ # CMake 3.0 added support for constraining the set of submodules to clone
+ if(NOT (CMAKE_VERSION VERSION_LESS "3.0") AND EP_GIT_SUBMODULES)
+ list(APPEND result GIT_SUBMODULES ${EP_GIT_SUBMODULES})
+ endif()
+
+ # CMake 3.6 added support for shallow git clones. Use a shallow clone if
+ # available
+ if(NOT (CMAKE_VERSION VERSION_LESS "3.6"))
+ if(NOT EP_GIT_SHALLOW)
+ set(EP_GIT_SHALLOW ON)
+ endif()
+
+ list(APPEND result GIT_SHALLOW ${EP_GIT_SHALLOW})
+ endif()
+
+ # CMake 3.8 added support for showing progress for large downloads
+ if(NOT (CMAKE_VERSION VERSION_LESS "3.8"))
+ if(NOT EP_GIT_PROGRESS)
+ set(EP_GIT_PROGRESS ON)
+ endif()
+
+ list(APPEND result GIT_PROGRESS ${EP_GIT_PROGRESS})
+ endif()
+
+ set(${RESULT_VAR} ${result} PARENT_SCOPE)
+
+endfunction()
diff --git a/cmake/FindFirebaseCore.cmake b/cmake/FindFirebaseCore.cmake
new file mode 100644
index 0000000..eec29dd
--- /dev/null
+++ b/cmake/FindFirebaseCore.cmake
@@ -0,0 +1,56 @@
+# Copyright 2017 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.
+
+find_library(
+ FIREBASECORE_LIBRARY
+ FirebaseCore
+ PATHS ${FIREBASE_INSTALL_DIR}/Frameworks
+)
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(
+ FirebaseCore
+ DEFAULT_MSG
+ FIREBASECORE_LIBRARY
+)
+
+if(FIREBASECORE_FOUND)
+ # Emulate CocoaPods behavior which makes all headers available unqualified.
+ set(
+ FIREBASECORE_INCLUDE_DIRS
+ ${FIREBASECORE_LIBRARY}/Headers
+ ${FIREBASECORE_LIBRARY}/PrivateHeaders
+ )
+
+ set(
+ FIREBASECORE_LIBRARIES
+ ${FIREBASECORE_LIBRARY}
+ "-framework Foundation"
+ )
+
+ if(NOT TARGET FirebaseCore)
+ # Add frameworks as INTERFACE libraries rather than IMPORTED so that
+ # framework behavior is preserved.
+ add_library(FirebaseCore INTERFACE)
+
+ set_property(
+ TARGET FirebaseCore APPEND PROPERTY
+ INTERFACE_INCLUDE_DIRECTORIES ${FIREBASECORE_INCLUDE_DIRS}
+ )
+ set_property(
+ TARGET FirebaseCore APPEND PROPERTY
+ INTERFACE_LINK_LIBRARIES ${FIREBASECORE_LIBRARIES}
+ )
+ endif()
+endif(FIREBASECORE_FOUND)
diff --git a/cmake/FindGRPC.cmake b/cmake/FindGRPC.cmake
new file mode 100644
index 0000000..f594b9e
--- /dev/null
+++ b/cmake/FindGRPC.cmake
@@ -0,0 +1,142 @@
+# Copyright 2018 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.
+
+include(FindPackageHandleStandardArgs)
+include(FindZLIB)
+
+set(BINARY_DIR ${FIREBASE_INSTALL_DIR}/external/grpc)
+
+## ZLIB
+
+# the grpc ExternalProject already figures out if zlib should be built or
+# referenced from its installed location. If it elected to allow grpc to build
+# zlib then it will be available at this location.
+find_library(
+ ZLIB_LIBRARY
+ NAMES z
+ HINTS ${BINARY_DIR}/src/grpc-build/third_party/zlib
+)
+
+# If found above, the standard package will honor the ZLIB_LIBRARY variable.
+find_package(ZLIB REQUIRED)
+
+
+## BoringSSL/OpenSSL
+
+find_path(
+ OPENSSL_INCLUDE_DIR openssl/ssl.h
+ HINTS ${BINARY_DIR}/src/grpc/third_party/boringssl/include
+)
+
+find_library(
+ OPENSSL_SSL_LIBRARY
+ NAMES ssl
+ HINTS ${BINARY_DIR}/src/grpc-build/third_party/boringssl/ssl
+)
+
+find_library(
+ OPENSSL_CRYPTO_LIBRARY
+ NAMES crypto
+ HINTS ${BINARY_DIR}/src/grpc-build/third_party/boringssl/crypto
+)
+
+find_package(OpenSSL REQUIRED)
+
+
+## C-Ares
+
+find_library(
+ CARES_LIBRARY
+ NAMES cares
+ HINTS ${BINARY_DIR}/src/grpc-build/third_party/cares/cares/lib
+)
+if(NOT (CARES_LIBRARY STREQUAL "CARES_LIBRARY-NOTFOUND"))
+ if (NOT TARGET c-ares::ares)
+ add_library(c-ares::ares UNKNOWN IMPORTED)
+ set_target_properties(
+ c-ares::ares PROPERTIES
+ IMPORTED_LOCATION ${CARES_LIBRARY}
+ )
+ endif()
+endif()
+
+
+## GRPC
+
+find_path(
+ GRPC_INCLUDE_DIR grpc/grpc.h
+ HINTS
+ $ENV{GRPC_ROOT}/include
+ ${GRPC_ROOT}/include
+ ${BINARY_DIR}/src/grpc/include
+)
+
+find_library(
+ GPR_LIBRARY
+ NAMES gpr
+ HINTS
+ $ENV{GRPC_ROOT}/lib
+ ${GRPC_ROOT}/lib
+ ${BINARY_DIR}/src/grpc-build
+)
+
+find_library(
+ GRPC_LIBRARY
+ NAMES grpc
+ HINTS
+ $ENV{GRPC_ROOT}/lib
+ ${GRPC_ROOT}/lib
+ ${BINARY_DIR}/src/grpc-build
+)
+
+find_package_handle_standard_args(
+ gRPC
+ DEFAULT_MSG
+ GRPC_INCLUDE_DIR
+ GRPC_LIBRARY
+ GPR_LIBRARY
+)
+
+if(GRPC_FOUND)
+ set(GRPC_INCLUDE_DIRS ${GRPC_INCLUDE_DIR})
+ set(GRPC_LIBRARIES ${GRPC_LIBRARY})
+
+ if (NOT TARGET grpc::gpr)
+ add_library(grpc::gpr UNKNOWN IMPORTED)
+ set_target_properties(
+ grpc::gpr PROPERTIES
+ INTERFACE_INCLUDE_DIRECTORIES ${GRPC_INCLUDE_DIR}
+ IMPORTED_LOCATION ${GPR_LIBRARY}
+ )
+ endif()
+
+ if (NOT TARGET grpc::grpc)
+ set(
+ GRPC_LINK_LIBRARIES
+ c-ares::ares
+ grpc::gpr
+ OpenSSL::SSL
+ OpenSSL::Crypto
+ ZLIB::ZLIB
+ )
+
+ add_library(grpc::grpc UNKNOWN IMPORTED)
+ set_target_properties(
+ grpc::grpc PROPERTIES
+ INTERFACE_INCLUDE_DIRECTORIES ${GRPC_INCLUDE_DIR}
+ INTERFACE_LINK_LIBRARIES "${GRPC_LINK_LIBRARIES}"
+ IMPORTED_LOCATION ${GRPC_LIBRARY}
+ )
+ endif()
+endif(GRPC_FOUND)
diff --git a/cmake/FindLevelDB.cmake b/cmake/FindLevelDB.cmake
new file mode 100644
index 0000000..b664fa8
--- /dev/null
+++ b/cmake/FindLevelDB.cmake
@@ -0,0 +1,55 @@
+# Copyright 2017 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.
+
+set(binary_dir ${FIREBASE_INSTALL_DIR}/external/leveldb/src/leveldb)
+
+find_path(
+ LEVELDB_INCLUDE_DIR leveldb/db.h
+ HINTS
+ $ENV{LEVELDB_ROOT}/include
+ ${LEVELDB_ROOT}/include
+ ${binary_dir}/include
+ PATH_SUFFIXES leveldb
+)
+
+find_library(
+ LEVELDB_LIBRARY
+ NAMES leveldb
+ HINTS
+ $ENV{LEVELDB_ROOT}/lib
+ ${LEVELDB_ROOT}/lib
+ ${binary_dir}/out-static
+)
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(
+ LevelDB
+ DEFAULT_MSG
+ LEVELDB_INCLUDE_DIR
+ LEVELDB_LIBRARY
+)
+
+if(LEVELDB_FOUND)
+ set(LEVELDB_INCLUDE_DIRS ${LEVELDB_INCLUDE_DIR})
+ set(LEVELDB_LIBRARIES ${LEVELDB_LIBRARY})
+
+ if (NOT TARGET LevelDB::LevelDB)
+ add_library(LevelDB::LevelDB UNKNOWN IMPORTED)
+ set_target_properties(
+ LevelDB::LevelDB PROPERTIES
+ INTERFACE_INCLUDE_DIRECTORIES ${LEVELDB_INCLUDE_DIR}
+ IMPORTED_LOCATION ${LEVELDB_LIBRARY}
+ )
+ endif()
+endif(LEVELDB_FOUND)
diff --git a/cmake/FindNanopb.cmake b/cmake/FindNanopb.cmake
new file mode 100644
index 0000000..12a5570
--- /dev/null
+++ b/cmake/FindNanopb.cmake
@@ -0,0 +1,48 @@
+# Copyright 2018 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.
+
+include(FindPackageHandleStandardArgs)
+
+set(BINARY_DIR ${FIREBASE_INSTALL_DIR}/external/nanopb)
+
+find_path(
+ NANOPB_INCLUDE_DIR pb.h
+ HINTS ${BINARY_DIR}/src/nanopb
+)
+
+find_library(
+ NANOPB_LIBRARY
+ NAMES protobuf-nanopb protobuf-nanopbd
+ HINTS ${BINARY_DIR}/src/nanopb
+)
+
+find_package_handle_standard_args(
+ nanopb
+ DEFAULT_MSG
+ NANOPB_INCLUDE_DIR
+ NANOPB_LIBRARY
+)
+
+if(NANOPB_FOUND)
+ set(NANOPB_INCLUDE_DIRS ${NANOPB_INCLUDE_DIR})
+
+ if (NOT TARGET nanopb)
+ add_library(nanopb UNKNOWN IMPORTED)
+ set_target_properties(
+ nanopb PROPERTIES
+ INTERFACE_INCLUDE_DIRECTORIES ${NANOPB_INCLUDE_DIRS}
+ IMPORTED_LOCATION ${NANOPB_LIBRARY}
+ )
+ endif()
+endif(NANOPB_FOUND)
diff --git a/cmake/external/FirebaseCore.cmake b/cmake/external/FirebaseCore.cmake
new file mode 100644
index 0000000..8be6969
--- /dev/null
+++ b/cmake/external/FirebaseCore.cmake
@@ -0,0 +1,23 @@
+# Copyright 2017 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.
+
+include(xcodebuild)
+
+if(APPLE)
+ # FirebaseCore is only available as a CocoaPod build.
+ xcodebuild(FirebaseCore)
+else()
+ # On non-Apple platforms, there's no way to build FirebaseCore.
+ add_custom_target(FirebaseCore)
+endif()
diff --git a/cmake/external/firestore.cmake b/cmake/external/firestore.cmake
index 5316873..94f7cae 100644
--- a/cmake/external/firestore.cmake
+++ b/cmake/external/firestore.cmake
@@ -14,27 +14,27 @@
include(ExternalProject)
-set(source_dir ${PROJECT_SOURCE_DIR}/Firestore)
-set(binary_dir ${PROJECT_BINARY_DIR}/Firestore)
-
ExternalProject_Add(
Firestore
- DEPENDS googletest
+ DEPENDS
+ FirebaseCore
+ googletest
+ leveldb
+ grpc
+ nanopb
# Lay the binary directory out as if this were a subproject. This makes it
# possible to build and test in it directly.
- PREFIX ${binary_dir}
- SOURCE_DIR ${source_dir}
- BINARY_DIR ${binary_dir}
+ PREFIX ${PROJECT_BINARY_DIR}/external/Firestore
+ SOURCE_DIR ${PROJECT_SOURCE_DIR}/Firestore
+ BINARY_DIR ${PROJECT_BINARY_DIR}/Firestore
+
+ CMAKE_ARGS
+ -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE}
+ -DCMAKE_INSTALL_PREFIX:PATH=${FIREBASE_INSTALL_DIR}
+
BUILD_ALWAYS ON
- # Even though this isn't installed, set up the INSTALL_DIR so that
- # find_package can find dependencies built from source.
- INSTALL_DIR ${FIREBASE_INSTALL_DIR}
INSTALL_COMMAND ""
TEST_BEFORE_INSTALL ON
-
- CMAKE_ARGS
- -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE}
- -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR>
)
diff --git a/cmake/external/googletest.cmake b/cmake/external/googletest.cmake
index a956e9f..24da386 100644
--- a/cmake/external/googletest.cmake
+++ b/cmake/external/googletest.cmake
@@ -17,18 +17,17 @@ include(ExternalProject)
ExternalProject_Add(
googletest
- URL "https://github.com/google/googletest/archive/release-1.8.0.tar.gz"
- URL_HASH "SHA256=58a6f4277ca2bc8565222b3bbd58a177609e9c488e8a72649359ba51450db7d8"
+ DOWNLOAD_DIR ${PROJECT_BINARY_DIR}/downloads
+ DOWNLOAD_NAME googletest-1.8.0.tar.gz
+ URL https://github.com/google/googletest/archive/release-1.8.0.tar.gz
+ URL_HASH SHA256=58a6f4277ca2bc8565222b3bbd58a177609e9c488e8a72649359ba51450db7d8
- PREFIX ${PROJECT_BINARY_DIR}/third_party/googletest
-
- DOWNLOAD_DIR ${FIREBASE_DOWNLOAD_DIR}
- INSTALL_DIR ${FIREBASE_INSTALL_DIR}
+ PREFIX ${PROJECT_BINARY_DIR}/external/googletest
+ # Just download the sources without building.
+ UPDATE_COMMAND ""
+ CONFIGURE_COMMAND ""
+ BUILD_COMMAND ""
+ INSTALL_COMMAND ""
TEST_COMMAND ""
-
- CMAKE_ARGS
- -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE}
- -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR>
- -DBUILD_SHARED_LIBS:BOOL=OFF
)
diff --git a/cmake/external/grpc.cmake b/cmake/external/grpc.cmake
new file mode 100644
index 0000000..d545087
--- /dev/null
+++ b/cmake/external/grpc.cmake
@@ -0,0 +1,90 @@
+# Copyright 2018 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.
+
+include(ExternalProject)
+include(ExternalProjectFlags)
+include(FindZLIB)
+
+if(GRPC_ROOT)
+ # If the user has supplied a GRPC_ROOT then just use it. Add an empty custom
+ # target so that the superbuild dependencies still work.
+ add_custom_target(grpc)
+
+else()
+ set(
+ GIT_SUBMODULES
+ third_party/boringssl
+ third_party/cares/cares
+ )
+
+ set(
+ CMAKE_ARGS
+ -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE}
+ -DgRPC_BUILD_TESTS:BOOL=OFF
+ -DBUILD_SHARED_LIBS:BOOL=OFF
+ )
+
+ # zlib can be built by grpc but we can avoid it on platforms that provide it
+ # by default.
+ find_package(ZLIB)
+ if(ZLIB_FOUND)
+ list(
+ APPEND CMAKE_ARGS
+ -DgRPC_ZLIB_PROVIDER:STRING=package
+ -DZLIB_INCLUDE_DIR=${ZLIB_INCLUDE_DIR}
+ -DZLIB_LIBRARY=${ZLIB_LIBRARY}
+ )
+
+ else()
+ list(
+ APPEND GIT_SUBMODULES
+ third_party/zlib
+ )
+
+ endif(ZLIB_FOUND)
+
+ ExternalProject_GitSource(
+ GRPC_GIT
+ GIT_REPOSITORY "https://github.com/grpc/grpc.git"
+ GIT_TAG "v1.8.3"
+ GIT_SUBMODULES ${GIT_SUBMODULES}
+ )
+
+ ExternalProject_Add(
+ grpc
+
+ ${GRPC_GIT}
+
+ PREFIX ${PROJECT_BINARY_DIR}/external/grpc
+
+ # TODO(rsgowman): We're currently building nanopb twice; once via grpc, and
+ # once via nanopb. The version from grpc is the one that actually ends up
+ # being used. We need to fix this such that either:
+ # a) we instruct grpc to use our nanopb
+ # b) we rely on grpc's nanopb instead of using our own.
+ # For now, we'll pass in the necessary nanopb cflags into grpc. (We require
+ # 16 bit fields. Without explicitly requesting this, nanopb uses 8 bit
+ # fields.)
+ CMAKE_ARGS ${CMAKE_ARGS};-DCMAKE_C_FLAGS=-DPB_FIELD_16BIT;DCMAKE_CXX_FLAGS=-DPB_FIELD_16BIT
+
+ BUILD_COMMAND
+ ${CMAKE_COMMAND} --build . --target grpc
+
+ UPDATE_COMMAND ""
+ TEST_COMMAND ""
+ INSTALL_COMMAND ""
+ )
+
+endif(GRPC_ROOT)
+
diff --git a/cmake/external/leveldb.cmake b/cmake/external/leveldb.cmake
new file mode 100644
index 0000000..dcbef6c
--- /dev/null
+++ b/cmake/external/leveldb.cmake
@@ -0,0 +1,74 @@
+# Copyright 2017 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.
+
+include(ExternalProject)
+
+if(WIN32 OR LEVELDB_ROOT)
+ # If the user has supplied a LEVELDB_ROOT then just use it. Add an empty
+ # custom target so that the superbuild depdendencies don't all have to be
+ # conditional.
+ #
+ # Also, unfortunately, LevelDB does not build on Windows (yet)
+ # See:
+ # https://github.com/google/leveldb/issues/363
+ # https://github.com/google/leveldb/issues/466
+ add_custom_target(leveldb)
+
+else()
+ # Clean up warning output to reduce noise in the build
+ if(CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU")
+ set(
+ LEVELDB_CXX_FLAGS "\
+ -Wno-deprecated-declarations"
+ )
+ endif()
+
+ # Map CMake compiler configuration down onto the leveldb Makefile
+ set(
+ LEVELDB_OPT "\
+ $<$<CONFIG:Debug>:${CMAKE_CXX_FLAGS_DEBUG}> \
+ $<$<CONFIG:Release>:${CMAKE_CXX_FLAGS_RELEASE}>"
+ )
+
+ ExternalProject_Add(
+ leveldb
+
+ DOWNLOAD_DIR ${PROJECT_BINARY_DIR}/downloads
+ DOWNLOAD_NAME leveldb-v1.20.tar.gz
+ URL https://github.com/google/leveldb/archive/v1.20.tar.gz
+ URL_HASH SHA256=f5abe8b5b209c2f36560b75f32ce61412f39a2922f7045ae764a2c23335b6664
+
+ PREFIX ${PROJECT_BINARY_DIR}/external/leveldb
+
+ # LevelDB's configuration is done in the Makefile
+ CONFIGURE_COMMAND ""
+
+ # The Makefile-based build of leveldb does not support building
+ # out-of-source.
+ BUILD_IN_SOURCE ON
+
+ # Only build the leveldb library skipping the tools and in-memory
+ # implementation we don't use.
+ BUILD_COMMAND
+ env CXXFLAGS=${LEVELDB_CXX_FLAGS} OPT=${LEVELDB_OPT}
+ make -j out-static/libleveldb.a
+
+ INSTALL_DIR ${FIREBASE_INSTALL_DIR}
+
+ UPDATE_COMMAND ""
+ INSTALL_COMMAND ""
+ TEST_COMMAND ""
+ )
+
+endif(WIN32 OR LEVELDB_ROOT)
diff --git a/cmake/external/nanopb.cmake b/cmake/external/nanopb.cmake
new file mode 100644
index 0000000..f68af0b
--- /dev/null
+++ b/cmake/external/nanopb.cmake
@@ -0,0 +1,64 @@
+# Copyright 2018 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.
+
+include(ExternalProject)
+
+set(
+ NANOPB_PROTOC_BIN
+ ${FIREBASE_INSTALL_DIR}/external/protobuf/src/protobuf-build/src/protoc
+)
+
+ExternalProject_Add(
+ nanopb
+ DEPENDS
+ protobuf
+
+ DOWNLOAD_DIR ${PROJECT_BINARY_DIR}/downloads
+ URL https://github.com/nanopb/nanopb/archive/nanopb-0.3.8.tar.gz
+ URL_HASH SHA256=f192c7c7cc036be36babc303b7d2315d4f62e2fe4be28c172cfed4cfa0ed5f22
+
+ BUILD_IN_SOURCE ON
+
+ PREFIX ${PROJECT_BINARY_DIR}/external/nanopb
+
+ # Note for (not yet released) nanopb 0.4.0: nanopb will (likely) switch to
+ # cmake for the protoc plugin. Set these additional cmake variables to use
+ # it.
+ # -Dnanopb_BUILD_GENERATOR:BOOL=ON
+ # -Dnanopb_PROTOC_PATH:STRING=${FIREBASE_INSTALL_DIR}/external/protobuf/src/protobuf-build/src/protoc
+ CMAKE_ARGS
+ -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE}
+ -DBUILD_SHARED_LIBS:BOOL=OFF
+
+ BUILD_COMMAND
+ COMMAND
+ ${CMAKE_COMMAND} --build .
+ # NB: The following additional command is only necessary to regenerate the
+ # nanopb proto files.
+ COMMAND
+ make -C <SOURCE_DIR>/generator/proto
+
+ # nanopb relies on $PATH for the location of protoc. cmake makes it difficult
+ # to adjust the path, so we'll just patch the build files with the exact
+ # location of protoc.
+ #
+ # NB: cmake sometimes runs the patch command multiple times in the same src
+ # dir, so we need to make sure this is idempotent. (eg 'make && make clean &&
+ # make')
+ PATCH_COMMAND
+ grep ${NANOPB_PROTOC_BIN} ./generator/proto/Makefile
+ || perl -i -pe s,protoc,${NANOPB_PROTOC_BIN},g
+ ./CMakeLists.txt ./generator/proto/Makefile
+
+ UPDATE_COMMAND ""
+ INSTALL_COMMAND ""
+)
diff --git a/cmake/external/protobuf.cmake b/cmake/external/protobuf.cmake
new file mode 100644
index 0000000..e1fdcbb
--- /dev/null
+++ b/cmake/external/protobuf.cmake
@@ -0,0 +1,31 @@
+# Copyright 2018 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.
+
+include(ExternalProject)
+
+ExternalProject_Add(
+ protobuf
+
+ DOWNLOAD_DIR ${PROJECT_BINARY_DIR}/downloads
+ DOWNLOAD_NAME protobuf-v3.5.11.tar.gz
+ URL https://github.com/google/protobuf/archive/v3.5.1.1.tar.gz
+ URL_HASH SHA256=56b5d9e1ab2bf4f5736c4cfba9f4981fbc6976246721e7ded5602fbaee6d6869
+
+ PREFIX ${PROJECT_BINARY_DIR}/external/protobuf
+
+ UPDATE_COMMAND ""
+ CONFIGURE_COMMAND cd <SOURCE_DIR> && ./autogen.sh
+ COMMAND <SOURCE_DIR>/configure --prefix=${PREFIX}
+ INSTALL_COMMAND ""
+)
diff --git a/cmake/utils.cmake b/cmake/utils.cmake
index 54044d6..1c3cbd6 100644
--- a/cmake/utils.cmake
+++ b/cmake/utils.cmake
@@ -12,16 +12,80 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-# Defines a new test executable and does all the things we want done with
-# tests:
+include(CMakeParseArguments)
+
+# cc_library(
+# target
+# SOURCES sources...
+# DEPENDS libraries...
+# )
+#
+# Defines a new library target with the given target name, sources, and dependencies.
+function(cc_library name)
+ set(flag EXCLUDE_FROM_ALL)
+ set(multi DEPENDS SOURCES)
+ cmake_parse_arguments(ccl "${flag}" "" "${multi}" ${ARGN})
+
+ add_library(
+ ${name}
+ ${ccl_SOURCES}
+ )
+ add_objc_flags(${name} ccl)
+ target_link_libraries(
+ ${name}
+ PUBLIC
+ ${ccl_DEPENDS}
+ )
+
+ if(ccl_EXCLUDE_FROM_ALL)
+ set_property(
+ TARGET ${name}
+ PROPERTY EXCLUDE_FROM_ALL ON
+ )
+ endif()
+
+endfunction()
+
+# cc_test(
+# target
+# SOURCES sources...
+# DEPENDS libraries...
+# )
#
-# * add_executable (with the given arguments)
-# * add_Test - defines a test with the same name
-# * declares that the test links against gtest
-# * adds the executable as a dependency of the `check` target.
+# Defines a new test executable target with the given target name, sources, and
+# dependencies. Implicitly adds DEPENDS on GTest::GTest and GTest::Main.
function(cc_test name)
- add_executable(${name} ${ARGN})
+ set(multi DEPENDS SOURCES)
+ cmake_parse_arguments(cct "" "" "${multi}" ${ARGN})
+
+ list(APPEND cct_DEPENDS GTest::GTest GTest::Main)
+
+ add_executable(${name} ${cct_SOURCES})
+ add_objc_flags(${name} cct)
add_test(${name} ${name})
- target_link_libraries(${name} GTest::GTest GTest::Main)
+ target_link_libraries(${name} ${cct_DEPENDS})
+endfunction()
+
+# add_objc_flags(target sources...)
+#
+# Adds OBJC_FLAGS to the compile options of the given target if any of the
+# sources have filenames that indicate they are are Objective-C.
+function(add_objc_flags target)
+ set(_has_objc OFF)
+
+ foreach(source ${ARGN})
+ get_filename_component(ext ${source} EXT)
+ if((ext STREQUAL ".m") OR (ext STREQUAL ".mm"))
+ set(_has_objc ON)
+ endif()
+ endforeach()
+
+ if(_has_objc)
+ target_compile_options(
+ ${target}
+ PRIVATE
+ ${OBJC_FLAGS}
+ )
+ endif()
endfunction()
diff --git a/cmake/xcodebuild.cmake b/cmake/xcodebuild.cmake
new file mode 100644
index 0000000..01a2961
--- /dev/null
+++ b/cmake/xcodebuild.cmake
@@ -0,0 +1,89 @@
+# Copyright 2017 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.
+
+include(CMakeParseArguments)
+include(ExternalProject)
+
+# Builds an existing Xcode project or workspace as an external project in CMake.
+#
+# xcodebuild(<framework> [<option>...])
+#
+# Options:
+# ``DEPENDS <projects>...``
+# Targets on which the project depends
+# ``SCHEME <scheme>``
+# The scheme to build in the workspace, defaults to <framework>-<platform>,
+# where <platform> is always "macOS".
+# ``WORKSPACE <workspace>``
+# Location of the xcworkspace file containing the target to build. Defaults to
+# Example/Firebase.xcworkspace.
+function(xcodebuild framework)
+ # Parse arguments
+ set(options "")
+ set(single_value SCHEME WORKSPACE)
+ set(multi_value DEPENDS)
+ cmake_parse_arguments(xcb "${options}" "${single_value}" "${multi_value}" ${ARGN})
+
+ if(NOT xcb_WORKSPACE)
+ set(xcb_WORKSPACE ${PROJECT_SOURCE_DIR}/Example/Firebase.xcworkspace)
+ endif()
+
+ # TODO(mcg): Investigate supporting non-macOS platforms
+ # The canonical way to build and test for iOS is via Xcode and CocoaPods so
+ # it's not super important to make this work here
+ set(platform macOS)
+ set(destination "platform=macOS,arch=x86_64")
+ set(scheme "${framework}-${platform}")
+
+ # CMake has a variety of release types, but Xcode has just one by default.
+ if(CMAKE_BUILD_TYPE STREQUAL Debug)
+ set(configuration Debug)
+ else()
+ set(configuration Release)
+ endif()
+
+ # Pipe build output through xcpretty if it's available
+ find_program(xcpretty_cmd xcpretty)
+ if(xcpretty_cmd)
+ set(pipe_xcpretty "|" ${xcpretty_cmd})
+ endif()
+
+ ExternalProject_Add(
+ ${framework}
+ DEPENDS ${xcb_DEPENDS}
+
+ PREFIX ${PROJECT_BINARY_DIR}/external/${framework}
+
+ # The source directory doesn't actually matter
+ SOURCE_DIR ${PROJECT_SOURCE_DIR}
+ BINARY_DIR ${PROJECT_BINARY_DIR}/Frameworks
+
+ CONFIGURE_COMMAND ""
+
+ BUILD_COMMAND
+ xcodebuild
+ -workspace ${xcb_WORKSPACE}
+ -scheme ${scheme}
+ -configuration ${configuration}
+ -destination ${destination}
+ CONFIGURATION_BUILD_DIR=<BINARY_DIR>
+ build
+ ${pipe_xcpretty}
+ BUILD_ALWAYS ${BUILD_PODS}
+
+ INSTALL_COMMAND ""
+ TEST_COMMAND ""
+ )
+
+endfunction()
diff --git a/patch/FirebaseAnalytics.h b/patch/FirebaseAnalytics.h
index 72c55c6..4850741 100644
--- a/patch/FirebaseAnalytics.h
+++ b/patch/FirebaseAnalytics.h
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2018 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.
+ */
+
// Firebase Analytics umbrella header for interoperating with open source builds
// More details at https://github.com/firebase/firebase-ios-sdk#source-pod-integration
diff --git a/scripts/build.sh b/scripts/build.sh
new file mode 100755
index 0000000..827fc14
--- /dev/null
+++ b/scripts/build.sh
@@ -0,0 +1,184 @@
+#!/usr/bin/env bash
+
+# Copyright 2018 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.
+
+# USAGE: build.sh product [platform] [method]
+#
+# Builds the given product for the given platform using the given build method
+
+set -euo pipefail
+
+if [[ $# -lt 1 ]]; then
+ cat 1>&2 <<EOF
+USAGE: $0 product [platform] [method]
+
+product can be one of:
+ Firebase
+ Firestore
+
+platform can be one of:
+ iOS (default)
+ macOS
+ tvOS
+
+method can be one of:
+ xcodebuild (default)
+ cmake
+EOF
+ exit 1
+fi
+
+product="$1"
+
+platform="iOS"
+if [[ $# -gt 1 ]]; then
+ platform="$2"
+fi
+
+method="xcodebuild"
+if [[ $# -gt 2 ]]; then
+ method="$3"
+fi
+
+echo "Building $product for $platform using $method"
+
+# Runs xcodebuild with the given flags, piping output to xcpretty
+# If xcodebuild fails with known error codes, retries once.
+function RunXcodebuild() {
+ xcodebuild "$@" | xcpretty; result=$?
+ if [[ $result == 65 ]]; then
+ echo "xcodebuild exited with 65, retrying" 1>&2
+ sleep 5
+
+ xcodebuild "$@" | xcpretty; result=$?
+ fi
+ if [[ $result != 0 ]]; then
+ exit $result
+ fi
+}
+
+# Compute standard flags for all platforms
+case "$platform" in
+ iOS)
+ xcb_flags=(
+ -sdk 'iphonesimulator'
+ -destination 'platform=iOS Simulator,name=iPhone 7'
+ )
+ ;;
+
+ macOS)
+ xcb_flags=(
+ -sdk 'macosx'
+ -destination 'platform=OS X,arch=x86_64'
+ )
+ ;;
+
+ tvOS)
+ xcb_flags=(
+ -sdk "appletvsimulator"
+ -destination 'platform=tvOS Simulator,name=Apple TV'
+ )
+ ;;
+
+ *)
+ echo "Unknown platform '$platform'" 1>&2
+ exit 1
+ ;;
+esac
+
+xcb_flags+=(
+ ONLY_ACTIVE_ARCH=YES
+ CODE_SIGNING_REQUIRED=NO
+)
+
+case "$product-$method-$platform" in
+ Firebase-xcodebuild-*)
+ RunXcodebuild \
+ -workspace 'Example/Firebase.xcworkspace' \
+ -scheme "AllUnitTests_$platform" \
+ "${xcb_flags[@]}" \
+ build \
+ test
+
+ if [[ $platform == 'iOS' ]]; then
+ RunXcodebuild \
+ -workspace 'Functions/Example/FirebaseFunctions.xcworkspace' \
+ -scheme "FirebaseFunctions_Tests" \
+ "${xcb_flags[@]}" \
+ build \
+ test
+
+ # Test iOS Objective-C static library build
+ cd Example
+ sed -i -e 's/use_frameworks/\#use_frameworks/' Podfile
+ pod update --no-repo-update
+ # Workarounds for https://github.com/CocoaPods/CocoaPods/issues/7592.
+ # Remove when updating to CocoaPods 1.5.1
+ sed -i -e 's/-l"FirebaseMessaging"//' "Pods/Target Support Files/Pods-Messaging_Tests_iOS/Pods-Messaging_Tests_iOS.debug.xcconfig"
+ sed -i -e 's/-l"FirebaseAuth-iOS" -l"FirebaseCore-iOS"//' "Pods/Target Support Files/Pods-Auth_Tests_iOS/Pods-Auth_Tests_iOS.debug.xcconfig"
+ cd ..
+ RunXcodebuild \
+ -workspace 'Example/Firebase.xcworkspace' \
+ -scheme "AllUnitTests_$platform" \
+ "${xcb_flags[@]}" \
+ build \
+ test
+
+ cd Functions/Example
+ sed -i -e 's/use_frameworks/\#use_frameworks/' Podfile
+ pod update --no-repo-update
+ cd ../..
+ RunXcodebuild \
+ -workspace 'Functions/Example/FirebaseFunctions.xcworkspace' \
+ -scheme "FirebaseFunctions_Tests" \
+ "${xcb_flags[@]}" \
+ build \
+ test
+ fi
+ ;;
+
+ Firestore-xcodebuild-iOS)
+ RunXcodebuild \
+ -workspace 'Firestore/Example/Firestore.xcworkspace' \
+ -scheme 'Firestore_Tests' \
+ "${xcb_flags[@]}" \
+ build \
+ test
+
+ RunXcodebuild \
+ -workspace 'Firestore/Example/Firestore.xcworkspace' \
+ -scheme 'SwiftBuildTest' \
+ "${xcb_flags[@]}" \
+ build
+ ;;
+
+ Firestore-cmake-macOS)
+ test -d build || mkdir build
+ echo "Preparing cmake build ..."
+ (cd build; cmake ..)
+
+ echo "Building cmake build ..."
+ cpus=$(sysctl -n hw.ncpu)
+ (cd build; make -j $cpus all)
+ ;;
+
+ *)
+ echo "Don't know how to build this product-platform-method combination" 1>&2
+ echo " product=$product" 1>&2
+ echo " platform=$platform" 1>&2
+ echo " method=$method" 1>&2
+ exit 1
+ ;;
+esac
diff --git a/scripts/check_copyright.sh b/scripts/check_copyright.sh
new file mode 100755
index 0000000..14857d3
--- /dev/null
+++ b/scripts/check_copyright.sh
@@ -0,0 +1,31 @@
+# Copyright 2018 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.
+
+# Check source files for copyright notices
+
+options=(
+ -E # Use extended regexps
+ -I # Exclude binary files
+ -L # Show files that don't have a match
+ 'Copyright [0-9]{4}.*Google'
+)
+
+git grep "${options[@]}" \
+ -- '*.'{c,cc,h,m,mm,sh,swift} \
+ ':(exclude)**/third_party/**'
+if [[ $? == 0 ]]; then
+ echo "ERROR: Missing copyright notices in the files above. Please fix."
+ exit 1
+fi
+
diff --git a/scripts/check_no_module_imports.sh b/scripts/check_no_module_imports.sh
new file mode 100755
index 0000000..c00c1db
--- /dev/null
+++ b/scripts/check_no_module_imports.sh
@@ -0,0 +1,41 @@
+# Copyright 2018 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.
+
+# Fail if any source files contain Objective-C module @imports, excluding:
+# * Example sources
+# * Sample sources
+
+options=(
+ -n # show line numbers
+ -I # exclude binary files
+ '^@import'
+)
+
+function exit_with_error {
+ echo "ERROR: @import statement found in the files above. Please use #import instead."
+ exit 1
+}
+
+git grep "${options[@]}" \
+ -- ':(exclude,glob)**/Example/**' ':(exclude,glob)**/Sample/**' && exit_with_error
+
+# Tests are under the Example directory, so we have to separately grep them for
+# @import statements (otherwise they'd be excluded).
+git grep "${options[@]}" \
+ -- ':(glob)**/Tests/**' ':(glob)**/TestUtils/**' ':(glob)**/IntegrationTests/**' && \
+ exit_with_error
+
+# We need to explicitly exit 0, since we expect `git grep` to return an error
+# if no @import calls are found.
+exit 0
diff --git a/scripts/check_whitespace.sh b/scripts/check_whitespace.sh
new file mode 100755
index 0000000..a38d4ce
--- /dev/null
+++ b/scripts/check_whitespace.sh
@@ -0,0 +1,33 @@
+# Copyright 2018 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.
+
+# Fail on an trailing whitespace characters, excluding
+# * binary files (-I)
+# * nanopb-generated files
+#
+# Note: specifying revisions we care about makes this go slower than just
+# grepping through the whole repo.
+options=(
+ -n # show line numbers
+ -I # exclude binary files
+ ' $'
+)
+
+git grep "${options[@]}" \
+ -- ':(exclude)Firestore/Protos/nanopb'
+if [[ $? == 0 ]]; then
+ echo "ERROR: Trailing whitespace found in the files above. Please fix."
+ exit 1
+fi
+
diff --git a/scripts/cpplint.py b/scripts/cpplint.py
new file mode 100644
index 0000000..f09695c
--- /dev/null
+++ b/scripts/cpplint.py
@@ -0,0 +1,6273 @@
+#!/usr/bin/env python
+#
+# Copyright (c) 2009 Google Inc. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""Does google-lint on c++ files.
+
+The goal of this script is to identify places in the code that *may*
+be in non-compliance with google style. It does not attempt to fix
+up these problems -- the point is to educate. It does also not
+attempt to find all problems, or to ensure that everything it does
+find is legitimately a problem.
+
+In particular, we can get very confused by /* and // inside strings!
+We do a small hack, which is to ignore //'s with "'s after them on the
+same line, but it is far from perfect (in either direction).
+"""
+
+import codecs
+import copy
+import getopt
+import math # for log
+import os
+import re
+import sre_compile
+import string
+import sys
+import unicodedata
+
+
+_USAGE = """
+Syntax: cpplint.py [--verbose=#] [--output=vs7] [--filter=-x,+y,...]
+ [--counting=total|toplevel|detailed] [--root=subdir]
+ [--linelength=digits] [--headers=x,y,...]
+ [--quiet]
+ <file> [file] ...
+
+ The style guidelines this tries to follow are those in
+ https://google-styleguide.googlecode.com/svn/trunk/cppguide.xml
+
+ Every problem is given a confidence score from 1-5, with 5 meaning we are
+ certain of the problem, and 1 meaning it could be a legitimate construct.
+ This will miss some errors, and is not a substitute for a code review.
+
+ To suppress false-positive errors of a certain category, add a
+ 'NOLINT(category)' comment to the line. NOLINT or NOLINT(*)
+ suppresses errors of all categories on that line.
+
+ The files passed in will be linted; at least one file must be provided.
+ Default linted extensions are .cc, .cpp, .cu, .cuh and .h. Change the
+ extensions with the --extensions flag.
+
+ Flags:
+
+ output=vs7
+ By default, the output is formatted to ease emacs parsing. Visual Studio
+ compatible output (vs7) may also be used. Other formats are unsupported.
+
+ verbose=#
+ Specify a number 0-5 to restrict errors to certain verbosity levels.
+
+ quiet
+ Don't print anything if no errors are found.
+
+ filter=-x,+y,...
+ Specify a comma-separated list of category-filters to apply: only
+ error messages whose category names pass the filters will be printed.
+ (Category names are printed with the message and look like
+ "[whitespace/indent]".) Filters are evaluated left to right.
+ "-FOO" and "FOO" means "do not print categories that start with FOO".
+ "+FOO" means "do print categories that start with FOO".
+
+ Examples: --filter=-whitespace,+whitespace/braces
+ --filter=whitespace,runtime/printf,+runtime/printf_format
+ --filter=-,+build/include_what_you_use
+
+ To see a list of all the categories used in cpplint, pass no arg:
+ --filter=
+
+ counting=total|toplevel|detailed
+ The total number of errors found is always printed. If
+ 'toplevel' is provided, then the count of errors in each of
+ the top-level categories like 'build' and 'whitespace' will
+ also be printed. If 'detailed' is provided, then a count
+ is provided for each category like 'build/class'.
+
+ root=subdir
+ The root directory used for deriving header guard CPP variable.
+ By default, the header guard CPP variable is calculated as the relative
+ path to the directory that contains .git, .hg, or .svn. When this flag
+ is specified, the relative path is calculated from the specified
+ directory. If the specified directory does not exist, this flag is
+ ignored.
+
+ Examples:
+ Assuming that top/src/.git exists (and cwd=top/src), the header guard
+ CPP variables for top/src/chrome/browser/ui/browser.h are:
+
+ No flag => CHROME_BROWSER_UI_BROWSER_H_
+ --root=chrome => BROWSER_UI_BROWSER_H_
+ --root=chrome/browser => UI_BROWSER_H_
+ --root=.. => SRC_CHROME_BROWSER_UI_BROWSER_H_
+
+ linelength=digits
+ This is the allowed line length for the project. The default value is
+ 80 characters.
+
+ Examples:
+ --linelength=120
+
+ extensions=extension,extension,...
+ The allowed file extensions that cpplint will check
+
+ Examples:
+ --extensions=hpp,cpp
+
+ headers=x,y,...
+ The header extensions that cpplint will treat as .h in checks. Values are
+ automatically added to --extensions list.
+
+ Examples:
+ --headers=hpp,hxx
+ --headers=hpp
+
+ cpplint.py supports per-directory configurations specified in CPPLINT.cfg
+ files. CPPLINT.cfg file can contain a number of key=value pairs.
+ Currently the following options are supported:
+
+ set noparent
+ filter=+filter1,-filter2,...
+ exclude_files=regex
+ linelength=80
+ root=subdir
+ headers=x,y,...
+
+ "set noparent" option prevents cpplint from traversing directory tree
+ upwards looking for more .cfg files in parent directories. This option
+ is usually placed in the top-level project directory.
+
+ The "filter" option is similar in function to --filter flag. It specifies
+ message filters in addition to the |_DEFAULT_FILTERS| and those specified
+ through --filter command-line flag.
+
+ "exclude_files" allows to specify a regular expression to be matched against
+ a file name. If the expression matches, the file is skipped and not run
+ through liner.
+
+ "linelength" allows to specify the allowed line length for the project.
+
+ The "root" option is similar in function to the --root flag (see example
+ above). Paths are relative to the directory of the CPPLINT.cfg.
+
+ The "headers" option is similar in function to the --headers flag
+ (see example above).
+
+ CPPLINT.cfg has an effect on files in the same directory and all
+ sub-directories, unless overridden by a nested configuration file.
+
+ Example file:
+ filter=-build/include_order,+build/include_alpha
+ exclude_files=.*\.cc
+
+ The above example disables build/include_order warning and enables
+ build/include_alpha as well as excludes all .cc from being
+ processed by linter, in the current directory (where the .cfg
+ file is located) and all sub-directories.
+"""
+
+# We categorize each error message we print. Here are the categories.
+# We want an explicit list so we can list them all in cpplint --filter=.
+# If you add a new error message with a new category, add it to the list
+# here! cpplint_unittest.py should tell you if you forget to do this.
+_ERROR_CATEGORIES = [
+ 'build/class',
+ 'build/c++11',
+ 'build/c++14',
+ 'build/c++tr1',
+ 'build/deprecated',
+ 'build/endif_comment',
+ 'build/explicit_make_pair',
+ 'build/forward_decl',
+ 'build/header_guard',
+ 'build/include',
+ 'build/include_alpha',
+ 'build/include_order',
+ 'build/include_what_you_use',
+ 'build/namespaces',
+ 'build/printf_format',
+ 'build/storage_class',
+ 'legal/copyright',
+ 'readability/alt_tokens',
+ 'readability/braces',
+ 'readability/casting',
+ 'readability/check',
+ 'readability/constructors',
+ 'readability/fn_size',
+ 'readability/inheritance',
+ 'readability/multiline_comment',
+ 'readability/multiline_string',
+ 'readability/namespace',
+ 'readability/nolint',
+ 'readability/nul',
+ 'readability/strings',
+ 'readability/todo',
+ 'readability/utf8',
+ 'runtime/arrays',
+ 'runtime/casting',
+ 'runtime/explicit',
+ 'runtime/int',
+ 'runtime/init',
+ 'runtime/invalid_increment',
+ 'runtime/member_string_references',
+ 'runtime/memset',
+ 'runtime/indentation_namespace',
+ 'runtime/operator',
+ 'runtime/printf',
+ 'runtime/printf_format',
+ 'runtime/references',
+ 'runtime/string',
+ 'runtime/threadsafe_fn',
+ 'runtime/vlog',
+ 'whitespace/blank_line',
+ 'whitespace/braces',
+ 'whitespace/comma',
+ 'whitespace/comments',
+ 'whitespace/empty_conditional_body',
+ 'whitespace/empty_if_body',
+ 'whitespace/empty_loop_body',
+ 'whitespace/end_of_line',
+ 'whitespace/ending_newline',
+ 'whitespace/forcolon',
+ 'whitespace/indent',
+ 'whitespace/line_length',
+ 'whitespace/newline',
+ 'whitespace/operators',
+ 'whitespace/parens',
+ 'whitespace/semicolon',
+ 'whitespace/tab',
+ 'whitespace/todo',
+ ]
+
+# These error categories are no longer enforced by cpplint, but for backwards-
+# compatibility they may still appear in NOLINT comments.
+_LEGACY_ERROR_CATEGORIES = [
+ 'readability/streams',
+ 'readability/function',
+ ]
+
+# The default state of the category filter. This is overridden by the --filter=
+# flag. By default all errors are on, so only add here categories that should be
+# off by default (i.e., categories that must be enabled by the --filter= flags).
+# All entries here should start with a '-' or '+', as in the --filter= flag.
+_DEFAULT_FILTERS = ['-build/include_alpha']
+
+# The default list of categories suppressed for C (not C++) files.
+_DEFAULT_C_SUPPRESSED_CATEGORIES = [
+ 'readability/casting',
+ ]
+
+# The default list of categories suppressed for Linux Kernel files.
+_DEFAULT_KERNEL_SUPPRESSED_CATEGORIES = [
+ 'whitespace/tab',
+ ]
+
+# We used to check for high-bit characters, but after much discussion we
+# decided those were OK, as long as they were in UTF-8 and didn't represent
+# hard-coded international strings, which belong in a separate i18n file.
+
+# C++ headers
+_CPP_HEADERS = frozenset([
+ # Legacy
+ 'algobase.h',
+ 'algo.h',
+ 'alloc.h',
+ 'builtinbuf.h',
+ 'bvector.h',
+ 'complex.h',
+ 'defalloc.h',
+ 'deque.h',
+ 'editbuf.h',
+ 'fstream.h',
+ 'function.h',
+ 'hash_map',
+ 'hash_map.h',
+ 'hash_set',
+ 'hash_set.h',
+ 'hashtable.h',
+ 'heap.h',
+ 'indstream.h',
+ 'iomanip.h',
+ 'iostream.h',
+ 'istream.h',
+ 'iterator.h',
+ 'list.h',
+ 'map.h',
+ 'multimap.h',
+ 'multiset.h',
+ 'ostream.h',
+ 'pair.h',
+ 'parsestream.h',
+ 'pfstream.h',
+ 'procbuf.h',
+ 'pthread_alloc',
+ 'pthread_alloc.h',
+ 'rope',
+ 'rope.h',
+ 'ropeimpl.h',
+ 'set.h',
+ 'slist',
+ 'slist.h',
+ 'stack.h',
+ 'stdiostream.h',
+ 'stl_alloc.h',
+ 'stl_relops.h',
+ 'streambuf.h',
+ 'stream.h',
+ 'strfile.h',
+ 'strstream.h',
+ 'tempbuf.h',
+ 'tree.h',
+ 'type_traits.h',
+ 'vector.h',
+ # 17.6.1.2 C++ library headers
+ 'algorithm',
+ 'array',
+ 'atomic',
+ 'bitset',
+ 'chrono',
+ 'codecvt',
+ 'complex',
+ 'condition_variable',
+ 'deque',
+ 'exception',
+ 'forward_list',
+ 'fstream',
+ 'functional',
+ 'future',
+ 'initializer_list',
+ 'iomanip',
+ 'ios',
+ 'iosfwd',
+ 'iostream',
+ 'istream',
+ 'iterator',
+ 'limits',
+ 'list',
+ 'locale',
+ 'map',
+ 'memory',
+ 'mutex',
+ 'new',
+ 'numeric',
+ 'ostream',
+ 'queue',
+ 'random',
+ 'ratio',
+ 'regex',
+ 'scoped_allocator',
+ 'set',
+ 'sstream',
+ 'stack',
+ 'stdexcept',
+ 'streambuf',
+ 'string',
+ 'strstream',
+ 'system_error',
+ 'thread',
+ 'tuple',
+ 'typeindex',
+ 'typeinfo',
+ 'type_traits',
+ 'unordered_map',
+ 'unordered_set',
+ 'utility',
+ 'valarray',
+ 'vector',
+ # 17.6.1.2 C++ headers for C library facilities
+ 'cassert',
+ 'ccomplex',
+ 'cctype',
+ 'cerrno',
+ 'cfenv',
+ 'cfloat',
+ 'cinttypes',
+ 'ciso646',
+ 'climits',
+ 'clocale',
+ 'cmath',
+ 'csetjmp',
+ 'csignal',
+ 'cstdalign',
+ 'cstdarg',
+ 'cstdbool',
+ 'cstddef',
+ 'cstdint',
+ 'cstdio',
+ 'cstdlib',
+ 'cstring',
+ 'ctgmath',
+ 'ctime',
+ 'cuchar',
+ 'cwchar',
+ 'cwctype',
+ ])
+
+_C_SYSTEM_DIRECTORIES = frozenset([
+ 'libkern',
+ 'sys',
+])
+
+# Type names
+_TYPES = re.compile(
+ r'^(?:'
+ # [dcl.type.simple]
+ r'(char(16_t|32_t)?)|wchar_t|'
+ r'bool|short|int|long|signed|unsigned|float|double|'
+ # [support.types]
+ r'(ptrdiff_t|size_t|max_align_t|nullptr_t)|'
+ # [cstdint.syn]
+ r'(u?int(_fast|_least)?(8|16|32|64)_t)|'
+ r'(u?int(max|ptr)_t)|'
+ r')$')
+
+
+# These headers are excluded from [build/include] and [build/include_order]
+# checks:
+# - Anything not following google file name conventions (containing an
+# uppercase character, such as Python.h or nsStringAPI.h, for example).
+# - Lua headers.
+_THIRD_PARTY_HEADERS_PATTERN = re.compile(
+ r'^(?:[^/]*[A-Z][^/]*\.h|lua\.h|lauxlib\.h|lualib\.h)$')
+
+# Pattern for matching FileInfo.BaseName() against test file name
+_TEST_FILE_SUFFIX = r'(_test|_unittest|_regtest)$'
+
+# Pattern that matches only complete whitespace, possibly across multiple lines.
+_EMPTY_CONDITIONAL_BODY_PATTERN = re.compile(r'^\s*$', re.DOTALL)
+
+# Assertion macros. These are defined in base/logging.h and
+# testing/base/public/gunit.h.
+_CHECK_MACROS = [
+ 'DCHECK', 'CHECK',
+ 'EXPECT_TRUE', 'ASSERT_TRUE',
+ 'EXPECT_FALSE', 'ASSERT_FALSE',
+ ]
+
+# Replacement macros for CHECK/DCHECK/EXPECT_TRUE/EXPECT_FALSE
+_CHECK_REPLACEMENT = dict([(m, {}) for m in _CHECK_MACROS])
+
+for op, replacement in [('==', 'EQ'), ('!=', 'NE'),
+ ('>=', 'GE'), ('>', 'GT'),
+ ('<=', 'LE'), ('<', 'LT')]:
+ _CHECK_REPLACEMENT['DCHECK'][op] = 'DCHECK_%s' % replacement
+ _CHECK_REPLACEMENT['CHECK'][op] = 'CHECK_%s' % replacement
+ _CHECK_REPLACEMENT['EXPECT_TRUE'][op] = 'EXPECT_%s' % replacement
+ _CHECK_REPLACEMENT['ASSERT_TRUE'][op] = 'ASSERT_%s' % replacement
+
+for op, inv_replacement in [('==', 'NE'), ('!=', 'EQ'),
+ ('>=', 'LT'), ('>', 'LE'),
+ ('<=', 'GT'), ('<', 'GE')]:
+ _CHECK_REPLACEMENT['EXPECT_FALSE'][op] = 'EXPECT_%s' % inv_replacement
+ _CHECK_REPLACEMENT['ASSERT_FALSE'][op] = 'ASSERT_%s' % inv_replacement
+
+# Alternative tokens and their replacements. For full list, see section 2.5
+# Alternative tokens [lex.digraph] in the C++ standard.
+#
+# Digraphs (such as '%:') are not included here since it's a mess to
+# match those on a word boundary.
+_ALT_TOKEN_REPLACEMENT = {
+ 'and': '&&',
+ 'bitor': '|',
+ 'or': '||',
+ 'xor': '^',
+ 'compl': '~',
+ 'bitand': '&',
+ 'and_eq': '&=',
+ 'or_eq': '|=',
+ 'xor_eq': '^=',
+ 'not': '!',
+ 'not_eq': '!='
+ }
+
+# Compile regular expression that matches all the above keywords. The "[ =()]"
+# bit is meant to avoid matching these keywords outside of boolean expressions.
+#
+# False positives include C-style multi-line comments and multi-line strings
+# but those have always been troublesome for cpplint.
+_ALT_TOKEN_REPLACEMENT_PATTERN = re.compile(
+ r'[ =()](' + ('|'.join(_ALT_TOKEN_REPLACEMENT.keys())) + r')(?=[ (]|$)')
+
+
+# These constants define types of headers for use with
+# _IncludeState.CheckNextIncludeOrder().
+_C_SYS_HEADER = 1
+_CPP_SYS_HEADER = 2
+_LIKELY_MY_HEADER = 3
+_POSSIBLE_MY_HEADER = 4
+_OTHER_HEADER = 5
+
+# These constants define the current inline assembly state
+_NO_ASM = 0 # Outside of inline assembly block
+_INSIDE_ASM = 1 # Inside inline assembly block
+_END_ASM = 2 # Last line of inline assembly block
+_BLOCK_ASM = 3 # The whole block is an inline assembly block
+
+# Match start of assembly blocks
+_MATCH_ASM = re.compile(r'^\s*(?:asm|_asm|__asm|__asm__)'
+ r'(?:\s+(volatile|__volatile__))?'
+ r'\s*[{(]')
+
+# Match strings that indicate we're working on a C (not C++) file.
+_SEARCH_C_FILE = re.compile(r'\b(?:LINT_C_FILE|'
+ r'vim?:\s*.*(\s*|:)filetype=c(\s*|:|$))')
+
+# Match string that indicates we're working on a Linux Kernel file.
+_SEARCH_KERNEL_FILE = re.compile(r'\b(?:LINT_KERNEL_FILE)')
+
+_regexp_compile_cache = {}
+
+# {str, set(int)}: a map from error categories to sets of linenumbers
+# on which those errors are expected and should be suppressed.
+_error_suppressions = {}
+
+# The root directory used for deriving header guard CPP variable.
+# This is set by --root flag.
+_root = None
+_root_debug = False
+
+# The allowed line length of files.
+# This is set by --linelength flag.
+_line_length = 80
+
+# The allowed extensions for file names
+# This is set by --extensions flag.
+_valid_extensions = set(['cc', 'h', 'cpp', 'cu', 'cuh'])
+
+# Treat all headers starting with 'h' equally: .h, .hpp, .hxx etc.
+# This is set by --headers flag.
+_hpp_headers = set(['h'])
+
+# {str, bool}: a map from error categories to booleans which indicate if the
+# category should be suppressed for every line.
+_global_error_suppressions = {}
+
+def ProcessHppHeadersOption(val):
+ global _hpp_headers
+ try:
+ _hpp_headers = set(val.split(','))
+ # Automatically append to extensions list so it does not have to be set 2 times
+ _valid_extensions.update(_hpp_headers)
+ except ValueError:
+ PrintUsage('Header extensions must be comma seperated list.')
+
+def IsHeaderExtension(file_extension):
+ return file_extension in _hpp_headers
+
+def ParseNolintSuppressions(filename, raw_line, linenum, error):
+ """Updates the global list of line error-suppressions.
+
+ Parses any NOLINT comments on the current line, updating the global
+ error_suppressions store. Reports an error if the NOLINT comment
+ was malformed.
+
+ Args:
+ filename: str, the name of the input file.
+ raw_line: str, the line of input text, with comments.
+ linenum: int, the number of the current line.
+ error: function, an error handler.
+ """
+ matched = Search(r'\bNOLINT(NEXTLINE)?\b(\([^)]+\))?', raw_line)
+ if matched:
+ if matched.group(1):
+ suppressed_line = linenum + 1
+ else:
+ suppressed_line = linenum
+ category = matched.group(2)
+ if category in (None, '(*)'): # => "suppress all"
+ _error_suppressions.setdefault(None, set()).add(suppressed_line)
+ else:
+ if category.startswith('(') and category.endswith(')'):
+ category = category[1:-1]
+ if category in _ERROR_CATEGORIES:
+ _error_suppressions.setdefault(category, set()).add(suppressed_line)
+ elif category not in _LEGACY_ERROR_CATEGORIES:
+ error(filename, linenum, 'readability/nolint', 5,
+ 'Unknown NOLINT error category: %s' % category)
+
+
+def ProcessGlobalSuppresions(lines):
+ """Updates the list of global error suppressions.
+
+ Parses any lint directives in the file that have global effect.
+
+ Args:
+ lines: An array of strings, each representing a line of the file, with the
+ last element being empty if the file is terminated with a newline.
+ """
+ for line in lines:
+ if _SEARCH_C_FILE.search(line):
+ for category in _DEFAULT_C_SUPPRESSED_CATEGORIES:
+ _global_error_suppressions[category] = True
+ if _SEARCH_KERNEL_FILE.search(line):
+ for category in _DEFAULT_KERNEL_SUPPRESSED_CATEGORIES:
+ _global_error_suppressions[category] = True
+
+
+def ResetNolintSuppressions():
+ """Resets the set of NOLINT suppressions to empty."""
+ _error_suppressions.clear()
+ _global_error_suppressions.clear()
+
+
+def IsErrorSuppressedByNolint(category, linenum):
+ """Returns true if the specified error category is suppressed on this line.
+
+ Consults the global error_suppressions map populated by
+ ParseNolintSuppressions/ProcessGlobalSuppresions/ResetNolintSuppressions.
+
+ Args:
+ category: str, the category of the error.
+ linenum: int, the current line number.
+ Returns:
+ bool, True iff the error should be suppressed due to a NOLINT comment or
+ global suppression.
+ """
+ return (_global_error_suppressions.get(category, False) or
+ linenum in _error_suppressions.get(category, set()) or
+ linenum in _error_suppressions.get(None, set()))
+
+
+def Match(pattern, s):
+ """Matches the string with the pattern, caching the compiled regexp."""
+ # The regexp compilation caching is inlined in both Match and Search for
+ # performance reasons; factoring it out into a separate function turns out
+ # to be noticeably expensive.
+ if pattern not in _regexp_compile_cache:
+ _regexp_compile_cache[pattern] = sre_compile.compile(pattern)
+ return _regexp_compile_cache[pattern].match(s)
+
+
+def ReplaceAll(pattern, rep, s):
+ """Replaces instances of pattern in a string with a replacement.
+
+ The compiled regex is kept in a cache shared by Match and Search.
+
+ Args:
+ pattern: regex pattern
+ rep: replacement text
+ s: search string
+
+ Returns:
+ string with replacements made (or original string if no replacements)
+ """
+ if pattern not in _regexp_compile_cache:
+ _regexp_compile_cache[pattern] = sre_compile.compile(pattern)
+ return _regexp_compile_cache[pattern].sub(rep, s)
+
+
+def Search(pattern, s):
+ """Searches the string for the pattern, caching the compiled regexp."""
+ if pattern not in _regexp_compile_cache:
+ _regexp_compile_cache[pattern] = sre_compile.compile(pattern)
+ return _regexp_compile_cache[pattern].search(s)
+
+
+def _IsSourceExtension(s):
+ """File extension (excluding dot) matches a source file extension."""
+ return s in ('c', 'cc', 'cpp', 'cxx', 'm', 'mm')
+
+
+class _IncludeState(object):
+ """Tracks line numbers for includes, and the order in which includes appear.
+
+ include_list contains list of lists of (header, line number) pairs.
+ It's a lists of lists rather than just one flat list to make it
+ easier to update across preprocessor boundaries.
+
+ Call CheckNextIncludeOrder() once for each header in the file, passing
+ in the type constants defined above. Calls in an illegal order will
+ raise an _IncludeError with an appropriate error message.
+
+ """
+ # self._section will move monotonically through this set. If it ever
+ # needs to move backwards, CheckNextIncludeOrder will raise an error.
+ _INITIAL_SECTION = 0
+ _MY_H_SECTION = 1
+ _C_SECTION = 2
+ _CPP_SECTION = 3
+ _OTHER_H_SECTION = 4
+
+ _TYPE_NAMES = {
+ _C_SYS_HEADER: 'C system header',
+ _CPP_SYS_HEADER: 'C++ system header',
+ _LIKELY_MY_HEADER: 'header this file implements',
+ _POSSIBLE_MY_HEADER: 'header this file may implement',
+ _OTHER_HEADER: 'other header',
+ }
+ _SECTION_NAMES = {
+ _INITIAL_SECTION: "... nothing. (This can't be an error.)",
+ _MY_H_SECTION: 'a header this file implements',
+ _C_SECTION: 'C system header',
+ _CPP_SECTION: 'C++ system header',
+ _OTHER_H_SECTION: 'other header',
+ }
+
+ def __init__(self):
+ self.include_list = [[]]
+ self.ResetSection('')
+
+ def FindHeader(self, header):
+ """Check if a header has already been included.
+
+ Args:
+ header: header to check.
+ Returns:
+ Line number of previous occurrence, or -1 if the header has not
+ been seen before.
+ """
+ for section_list in self.include_list:
+ for f in section_list:
+ if f[0] == header:
+ return f[1]
+ return -1
+
+ def ResetSection(self, directive):
+ """Reset section checking for preprocessor directive.
+
+ Args:
+ directive: preprocessor directive (e.g. "if", "else").
+ """
+ # The name of the current section.
+ self._section = self._INITIAL_SECTION
+ # The path of last found header.
+ self._last_header = ''
+
+ # Update list of includes. Note that we never pop from the
+ # include list.
+ if directive in ('if', 'ifdef', 'ifndef'):
+ self.include_list.append([])
+ elif directive in ('else', 'elif'):
+ self.include_list[-1] = []
+
+ def SetLastHeader(self, header_path):
+ self._last_header = header_path
+
+ def CanonicalizeAlphabeticalOrder(self, header_path):
+ """Returns a path canonicalized for alphabetical comparison.
+
+ - replaces "-" with "_" so they both cmp the same.
+ - removes '-inl' since we don't require them to be after the main header.
+ - lowercase everything, just in case.
+
+ Args:
+ header_path: Path to be canonicalized.
+
+ Returns:
+ Canonicalized path.
+ """
+ return header_path.replace('-inl.h', '.h').replace('-', '_').lower()
+
+ def IsInAlphabeticalOrder(self, clean_lines, linenum, header_path):
+ """Check if a header is in alphabetical order with the previous header.
+
+ Args:
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ header_path: Canonicalized header to be checked.
+
+ Returns:
+ Returns true if the header is in alphabetical order.
+ """
+ # If previous section is different from current section, _last_header will
+ # be reset to empty string, so it's always less than current header.
+ #
+ # If previous line was a blank line, assume that the headers are
+ # intentionally sorted the way they are.
+ if (self._last_header > header_path and
+ Match(r'^\s*#\s*include\b', clean_lines.elided[linenum - 1])):
+ return False
+ return True
+
+ def CheckNextIncludeOrder(self, header_type):
+ """Returns a non-empty error message if the next header is out of order.
+
+ This function also updates the internal state to be ready to check
+ the next include.
+
+ Args:
+ header_type: One of the _XXX_HEADER constants defined above.
+
+ Returns:
+ The empty string if the header is in the right order, or an
+ error message describing what's wrong.
+
+ """
+ error_message = ('Found %s after %s' %
+ (self._TYPE_NAMES[header_type],
+ self._SECTION_NAMES[self._section]))
+
+ last_section = self._section
+
+ if header_type == _C_SYS_HEADER:
+ if self._section <= self._C_SECTION:
+ self._section = self._C_SECTION
+ else:
+ self._last_header = ''
+ return error_message
+ elif header_type == _CPP_SYS_HEADER:
+ if self._section <= self._CPP_SECTION:
+ self._section = self._CPP_SECTION
+ else:
+ self._last_header = ''
+ return error_message
+ elif header_type == _LIKELY_MY_HEADER:
+ if self._section <= self._MY_H_SECTION:
+ self._section = self._MY_H_SECTION
+ else:
+ self._section = self._OTHER_H_SECTION
+ elif header_type == _POSSIBLE_MY_HEADER:
+ if self._section <= self._MY_H_SECTION:
+ self._section = self._MY_H_SECTION
+ else:
+ # This will always be the fallback because we're not sure
+ # enough that the header is associated with this file.
+ self._section = self._OTHER_H_SECTION
+ else:
+ assert header_type == _OTHER_HEADER
+ self._section = self._OTHER_H_SECTION
+
+ if last_section != self._section:
+ self._last_header = ''
+
+ return ''
+
+
+class _CppLintState(object):
+ """Maintains module-wide state.."""
+
+ def __init__(self):
+ self.verbose_level = 1 # global setting.
+ self.error_count = 0 # global count of reported errors
+ # filters to apply when emitting error messages
+ self.filters = _DEFAULT_FILTERS[:]
+ # backup of filter list. Used to restore the state after each file.
+ self._filters_backup = self.filters[:]
+ self.counting = 'total' # In what way are we counting errors?
+ self.errors_by_category = {} # string to int dict storing error counts
+ self.quiet = False # Suppress non-error messagess?
+
+ # output format:
+ # "emacs" - format that emacs can parse (default)
+ # "vs7" - format that Microsoft Visual Studio 7 can parse
+ self.output_format = 'emacs'
+
+ def SetOutputFormat(self, output_format):
+ """Sets the output format for errors."""
+ self.output_format = output_format
+
+ def SetQuiet(self, quiet):
+ """Sets the module's quiet settings, and returns the previous setting."""
+ last_quiet = self.quiet
+ self.quiet = quiet
+ return last_quiet
+
+ def SetVerboseLevel(self, level):
+ """Sets the module's verbosity, and returns the previous setting."""
+ last_verbose_level = self.verbose_level
+ self.verbose_level = level
+ return last_verbose_level
+
+ def SetCountingStyle(self, counting_style):
+ """Sets the module's counting options."""
+ self.counting = counting_style
+
+ def SetFilters(self, filters):
+ """Sets the error-message filters.
+
+ These filters are applied when deciding whether to emit a given
+ error message.
+
+ Args:
+ filters: A string of comma-separated filters (eg "+whitespace/indent").
+ Each filter should start with + or -; else we die.
+
+ Raises:
+ ValueError: The comma-separated filters did not all start with '+' or '-'.
+ E.g. "-,+whitespace,-whitespace/indent,whitespace/badfilter"
+ """
+ # Default filters always have less priority than the flag ones.
+ self.filters = _DEFAULT_FILTERS[:]
+ self.AddFilters(filters)
+
+ def AddFilters(self, filters):
+ """ Adds more filters to the existing list of error-message filters. """
+ for filt in filters.split(','):
+ clean_filt = filt.strip()
+ if clean_filt:
+ self.filters.append(clean_filt)
+ for filt in self.filters:
+ if not (filt.startswith('+') or filt.startswith('-')):
+ raise ValueError('Every filter in --filters must start with + or -'
+ ' (%s does not)' % filt)
+
+ def BackupFilters(self):
+ """ Saves the current filter list to backup storage."""
+ self._filters_backup = self.filters[:]
+
+ def RestoreFilters(self):
+ """ Restores filters previously backed up."""
+ self.filters = self._filters_backup[:]
+
+ def ResetErrorCounts(self):
+ """Sets the module's error statistic back to zero."""
+ self.error_count = 0
+ self.errors_by_category = {}
+
+ def IncrementErrorCount(self, category):
+ """Bumps the module's error statistic."""
+ self.error_count += 1
+ if self.counting in ('toplevel', 'detailed'):
+ if self.counting != 'detailed':
+ category = category.split('/')[0]
+ if category not in self.errors_by_category:
+ self.errors_by_category[category] = 0
+ self.errors_by_category[category] += 1
+
+ def PrintErrorCounts(self):
+ """Print a summary of errors by category, and the total."""
+ for category, count in self.errors_by_category.iteritems():
+ sys.stderr.write('Category \'%s\' errors found: %d\n' %
+ (category, count))
+ sys.stdout.write('Total errors found: %d\n' % self.error_count)
+
+_cpplint_state = _CppLintState()
+
+
+def _OutputFormat():
+ """Gets the module's output format."""
+ return _cpplint_state.output_format
+
+
+def _SetOutputFormat(output_format):
+ """Sets the module's output format."""
+ _cpplint_state.SetOutputFormat(output_format)
+
+def _Quiet():
+ """Return's the module's quiet setting."""
+ return _cpplint_state.quiet
+
+def _SetQuiet(quiet):
+ """Set the module's quiet status, and return previous setting."""
+ return _cpplint_state.SetQuiet(quiet)
+
+
+def _VerboseLevel():
+ """Returns the module's verbosity setting."""
+ return _cpplint_state.verbose_level
+
+
+def _SetVerboseLevel(level):
+ """Sets the module's verbosity, and returns the previous setting."""
+ return _cpplint_state.SetVerboseLevel(level)
+
+
+def _SetCountingStyle(level):
+ """Sets the module's counting options."""
+ _cpplint_state.SetCountingStyle(level)
+
+
+def _Filters():
+ """Returns the module's list of output filters, as a list."""
+ return _cpplint_state.filters
+
+
+def _SetFilters(filters):
+ """Sets the module's error-message filters.
+
+ These filters are applied when deciding whether to emit a given
+ error message.
+
+ Args:
+ filters: A string of comma-separated filters (eg "whitespace/indent").
+ Each filter should start with + or -; else we die.
+ """
+ _cpplint_state.SetFilters(filters)
+
+def _AddFilters(filters):
+ """Adds more filter overrides.
+
+ Unlike _SetFilters, this function does not reset the current list of filters
+ available.
+
+ Args:
+ filters: A string of comma-separated filters (eg "whitespace/indent").
+ Each filter should start with + or -; else we die.
+ """
+ _cpplint_state.AddFilters(filters)
+
+def _BackupFilters():
+ """ Saves the current filter list to backup storage."""
+ _cpplint_state.BackupFilters()
+
+def _RestoreFilters():
+ """ Restores filters previously backed up."""
+ _cpplint_state.RestoreFilters()
+
+class _FunctionState(object):
+ """Tracks current function name and the number of lines in its body."""
+
+ _NORMAL_TRIGGER = 250 # for --v=0, 500 for --v=1, etc.
+ _TEST_TRIGGER = 400 # about 50% more than _NORMAL_TRIGGER.
+
+ def __init__(self):
+ self.in_a_function = False
+ self.lines_in_function = 0
+ self.current_function = ''
+
+ def Begin(self, function_name):
+ """Start analyzing function body.
+
+ Args:
+ function_name: The name of the function being tracked.
+ """
+ self.in_a_function = True
+ self.lines_in_function = 0
+ self.current_function = function_name
+
+ def Count(self):
+ """Count line in current function body."""
+ if self.in_a_function:
+ self.lines_in_function += 1
+
+ def Check(self, error, filename, linenum):
+ """Report if too many lines in function body.
+
+ Args:
+ error: The function to call with any errors found.
+ filename: The name of the current file.
+ linenum: The number of the line to check.
+ """
+ if not self.in_a_function:
+ return
+
+ if Match(r'T(EST|est)', self.current_function):
+ base_trigger = self._TEST_TRIGGER
+ else:
+ base_trigger = self._NORMAL_TRIGGER
+ trigger = base_trigger * 2**_VerboseLevel()
+
+ if self.lines_in_function > trigger:
+ error_level = int(math.log(self.lines_in_function / base_trigger, 2))
+ # 50 => 0, 100 => 1, 200 => 2, 400 => 3, 800 => 4, 1600 => 5, ...
+ if error_level > 5:
+ error_level = 5
+ error(filename, linenum, 'readability/fn_size', error_level,
+ 'Small and focused functions are preferred:'
+ ' %s has %d non-comment lines'
+ ' (error triggered by exceeding %d lines).' % (
+ self.current_function, self.lines_in_function, trigger))
+
+ def End(self):
+ """Stop analyzing function body."""
+ self.in_a_function = False
+
+
+class _IncludeError(Exception):
+ """Indicates a problem with the include order in a file."""
+ pass
+
+
+class FileInfo(object):
+ """Provides utility functions for filenames.
+
+ FileInfo provides easy access to the components of a file's path
+ relative to the project root.
+ """
+
+ def __init__(self, filename):
+ self._filename = filename
+
+ def FullName(self):
+ """Make Windows paths like Unix."""
+ return os.path.abspath(self._filename).replace('\\', '/')
+
+ def RepositoryName(self):
+ """FullName after removing the local path to the repository.
+
+ If we have a real absolute path name here we can try to do something smart:
+ detecting the root of the checkout and truncating /path/to/checkout from
+ the name so that we get header guards that don't include things like
+ "C:\Documents and Settings\..." or "/home/username/..." in them and thus
+ people on different computers who have checked the source out to different
+ locations won't see bogus errors.
+ """
+ fullname = self.FullName()
+
+ if os.path.exists(fullname):
+ project_dir = os.path.dirname(fullname)
+
+ if os.path.exists(os.path.join(project_dir, ".svn")):
+ # If there's a .svn file in the current directory, we recursively look
+ # up the directory tree for the top of the SVN checkout
+ root_dir = project_dir
+ one_up_dir = os.path.dirname(root_dir)
+ while os.path.exists(os.path.join(one_up_dir, ".svn")):
+ root_dir = os.path.dirname(root_dir)
+ one_up_dir = os.path.dirname(one_up_dir)
+
+ prefix = os.path.commonprefix([root_dir, project_dir])
+ return fullname[len(prefix) + 1:]
+
+ # Not SVN <= 1.6? Try to find a git, hg, or svn top level directory by
+ # searching up from the current path.
+ root_dir = current_dir = os.path.dirname(fullname)
+ while current_dir != os.path.dirname(current_dir):
+ if (os.path.exists(os.path.join(current_dir, ".git")) or
+ os.path.exists(os.path.join(current_dir, ".hg")) or
+ os.path.exists(os.path.join(current_dir, ".svn"))):
+ root_dir = current_dir
+ current_dir = os.path.dirname(current_dir)
+
+ if (os.path.exists(os.path.join(root_dir, ".git")) or
+ os.path.exists(os.path.join(root_dir, ".hg")) or
+ os.path.exists(os.path.join(root_dir, ".svn"))):
+ prefix = os.path.commonprefix([root_dir, project_dir])
+ return fullname[len(prefix) + 1:]
+
+ # Don't know what to do; header guard warnings may be wrong...
+ return fullname
+
+ def Split(self):
+ """Splits the file into the directory, basename, and extension.
+
+ For 'chrome/browser/browser.cc', Split() would
+ return ('chrome/browser', 'browser', '.cc')
+
+ Returns:
+ A tuple of (directory, basename, extension).
+ """
+
+ googlename = self.RepositoryName()
+ project, rest = os.path.split(googlename)
+ return (project,) + os.path.splitext(rest)
+
+ def BaseName(self):
+ """File base name - text after the final slash, before the final period."""
+ return self.Split()[1]
+
+ def Extension(self):
+ """File extension - text following the final period."""
+ return self.Split()[2]
+
+ def NoExtension(self):
+ """File has no source file extension."""
+ return '/'.join(self.Split()[0:2])
+
+ def IsSource(self):
+ """File has a source file extension."""
+ return _IsSourceExtension(self.Extension()[1:])
+
+
+def _ShouldPrintError(category, confidence, linenum):
+ """If confidence >= verbose, category passes filter and is not suppressed."""
+
+ # There are three ways we might decide not to print an error message:
+ # a "NOLINT(category)" comment appears in the source,
+ # the verbosity level isn't high enough, or the filters filter it out.
+ if IsErrorSuppressedByNolint(category, linenum):
+ return False
+
+ if confidence < _cpplint_state.verbose_level:
+ return False
+
+ is_filtered = False
+ for one_filter in _Filters():
+ if one_filter.startswith('-'):
+ if category.startswith(one_filter[1:]):
+ is_filtered = True
+ elif one_filter.startswith('+'):
+ if category.startswith(one_filter[1:]):
+ is_filtered = False
+ else:
+ assert False # should have been checked for in SetFilter.
+ if is_filtered:
+ return False
+
+ return True
+
+
+def Error(filename, linenum, category, confidence, message):
+ """Logs the fact we've found a lint error.
+
+ We log where the error was found, and also our confidence in the error,
+ that is, how certain we are this is a legitimate style regression, and
+ not a misidentification or a use that's sometimes justified.
+
+ False positives can be suppressed by the use of
+ "cpplint(category)" comments on the offending line. These are
+ parsed into _error_suppressions.
+
+ Args:
+ filename: The name of the file containing the error.
+ linenum: The number of the line containing the error.
+ category: A string used to describe the "category" this bug
+ falls under: "whitespace", say, or "runtime". Categories
+ may have a hierarchy separated by slashes: "whitespace/indent".
+ confidence: A number from 1-5 representing a confidence score for
+ the error, with 5 meaning that we are certain of the problem,
+ and 1 meaning that it could be a legitimate construct.
+ message: The error message.
+ """
+ if _ShouldPrintError(category, confidence, linenum):
+ _cpplint_state.IncrementErrorCount(category)
+ if _cpplint_state.output_format == 'vs7':
+ sys.stderr.write('%s(%s): error cpplint: [%s] %s [%d]\n' % (
+ filename, linenum, category, message, confidence))
+ elif _cpplint_state.output_format == 'eclipse':
+ sys.stderr.write('%s:%s: warning: %s [%s] [%d]\n' % (
+ filename, linenum, message, category, confidence))
+ else:
+ sys.stderr.write('%s:%s: %s [%s] [%d]\n' % (
+ filename, linenum, message, category, confidence))
+
+
+# Matches standard C++ escape sequences per 2.13.2.3 of the C++ standard.
+_RE_PATTERN_CLEANSE_LINE_ESCAPES = re.compile(
+ r'\\([abfnrtv?"\\\']|\d+|x[0-9a-fA-F]+)')
+# Match a single C style comment on the same line.
+_RE_PATTERN_C_COMMENTS = r'/\*(?:[^*]|\*(?!/))*\*/'
+# Matches multi-line C style comments.
+# This RE is a little bit more complicated than one might expect, because we
+# have to take care of space removals tools so we can handle comments inside
+# statements better.
+# The current rule is: We only clear spaces from both sides when we're at the
+# end of the line. Otherwise, we try to remove spaces from the right side,
+# if this doesn't work we try on left side but only if there's a non-character
+# on the right.
+_RE_PATTERN_CLEANSE_LINE_C_COMMENTS = re.compile(
+ r'(\s*' + _RE_PATTERN_C_COMMENTS + r'\s*$|' +
+ _RE_PATTERN_C_COMMENTS + r'\s+|' +
+ r'\s+' + _RE_PATTERN_C_COMMENTS + r'(?=\W)|' +
+ _RE_PATTERN_C_COMMENTS + r')')
+
+
+def IsCppString(line):
+ """Does line terminate so, that the next symbol is in string constant.
+
+ This function does not consider single-line nor multi-line comments.
+
+ Args:
+ line: is a partial line of code starting from the 0..n.
+
+ Returns:
+ True, if next character appended to 'line' is inside a
+ string constant.
+ """
+
+ line = line.replace(r'\\', 'XX') # after this, \\" does not match to \"
+ return ((line.count('"') - line.count(r'\"') - line.count("'\"'")) & 1) == 1
+
+
+def CleanseRawStrings(raw_lines):
+ """Removes C++11 raw strings from lines.
+
+ Before:
+ static const char kData[] = R"(
+ multi-line string
+ )";
+
+ After:
+ static const char kData[] = ""
+ (replaced by blank line)
+ "";
+
+ Args:
+ raw_lines: list of raw lines.
+
+ Returns:
+ list of lines with C++11 raw strings replaced by empty strings.
+ """
+
+ delimiter = None
+ lines_without_raw_strings = []
+ for line in raw_lines:
+ if delimiter:
+ # Inside a raw string, look for the end
+ end = line.find(delimiter)
+ if end >= 0:
+ # Found the end of the string, match leading space for this
+ # line and resume copying the original lines, and also insert
+ # a "" on the last line.
+ leading_space = Match(r'^(\s*)\S', line)
+ line = leading_space.group(1) + '""' + line[end + len(delimiter):]
+ delimiter = None
+ else:
+ # Haven't found the end yet, append a blank line.
+ line = '""'
+
+ # Look for beginning of a raw string, and replace them with
+ # empty strings. This is done in a loop to handle multiple raw
+ # strings on the same line.
+ while delimiter is None:
+ # Look for beginning of a raw string.
+ # See 2.14.15 [lex.string] for syntax.
+ #
+ # Once we have matched a raw string, we check the prefix of the
+ # line to make sure that the line is not part of a single line
+ # comment. It's done this way because we remove raw strings
+ # before removing comments as opposed to removing comments
+ # before removing raw strings. This is because there are some
+ # cpplint checks that requires the comments to be preserved, but
+ # we don't want to check comments that are inside raw strings.
+ matched = Match(r'^(.*?)\b(?:R|u8R|uR|UR|LR)"([^\s\\()]*)\((.*)$', line)
+ if (matched and
+ not Match(r'^([^\'"]|\'(\\.|[^\'])*\'|"(\\.|[^"])*")*//',
+ matched.group(1))):
+ delimiter = ')' + matched.group(2) + '"'
+
+ end = matched.group(3).find(delimiter)
+ if end >= 0:
+ # Raw string ended on same line
+ line = (matched.group(1) + '""' +
+ matched.group(3)[end + len(delimiter):])
+ delimiter = None
+ else:
+ # Start of a multi-line raw string
+ line = matched.group(1) + '""'
+ else:
+ break
+
+ lines_without_raw_strings.append(line)
+
+ # TODO(unknown): if delimiter is not None here, we might want to
+ # emit a warning for unterminated string.
+ return lines_without_raw_strings
+
+
+def FindNextMultiLineCommentStart(lines, lineix):
+ """Find the beginning marker for a multiline comment."""
+ while lineix < len(lines):
+ if lines[lineix].strip().startswith('/*'):
+ # Only return this marker if the comment goes beyond this line
+ if lines[lineix].strip().find('*/', 2) < 0:
+ return lineix
+ lineix += 1
+ return len(lines)
+
+
+def FindNextMultiLineCommentEnd(lines, lineix):
+ """We are inside a comment, find the end marker."""
+ while lineix < len(lines):
+ if lines[lineix].strip().endswith('*/'):
+ return lineix
+ lineix += 1
+ return len(lines)
+
+
+def RemoveMultiLineCommentsFromRange(lines, begin, end):
+ """Clears a range of lines for multi-line comments."""
+ # Having // dummy comments makes the lines non-empty, so we will not get
+ # unnecessary blank line warnings later in the code.
+ for i in range(begin, end):
+ lines[i] = '/**/'
+
+
+def RemoveMultiLineComments(filename, lines, error):
+ """Removes multiline (c-style) comments from lines."""
+ lineix = 0
+ while lineix < len(lines):
+ lineix_begin = FindNextMultiLineCommentStart(lines, lineix)
+ if lineix_begin >= len(lines):
+ return
+ lineix_end = FindNextMultiLineCommentEnd(lines, lineix_begin)
+ if lineix_end >= len(lines):
+ error(filename, lineix_begin + 1, 'readability/multiline_comment', 5,
+ 'Could not find end of multi-line comment')
+ return
+ RemoveMultiLineCommentsFromRange(lines, lineix_begin, lineix_end + 1)
+ lineix = lineix_end + 1
+
+
+def CleanseComments(line):
+ """Removes //-comments and single-line C-style /* */ comments.
+
+ Args:
+ line: A line of C++ source.
+
+ Returns:
+ The line with single-line comments removed.
+ """
+ commentpos = line.find('//')
+ if commentpos != -1 and not IsCppString(line[:commentpos]):
+ line = line[:commentpos].rstrip()
+ # get rid of /* ... */
+ return _RE_PATTERN_CLEANSE_LINE_C_COMMENTS.sub('', line)
+
+
+class CleansedLines(object):
+ """Holds 4 copies of all lines with different preprocessing applied to them.
+
+ 1) elided member contains lines without strings and comments.
+ 2) lines member contains lines without comments.
+ 3) raw_lines member contains all the lines without processing.
+ 4) lines_without_raw_strings member is same as raw_lines, but with C++11 raw
+ strings removed.
+ All these members are of <type 'list'>, and of the same length.
+ """
+
+ def __init__(self, lines):
+ self.elided = []
+ self.lines = []
+ self.raw_lines = lines
+ self.num_lines = len(lines)
+ self.lines_without_raw_strings = CleanseRawStrings(lines)
+ for linenum in range(len(self.lines_without_raw_strings)):
+ self.lines.append(CleanseComments(
+ self.lines_without_raw_strings[linenum]))
+ elided = self._CollapseStrings(self.lines_without_raw_strings[linenum])
+ self.elided.append(CleanseComments(elided))
+
+ def NumLines(self):
+ """Returns the number of lines represented."""
+ return self.num_lines
+
+ @staticmethod
+ def _CollapseStrings(elided):
+ """Collapses strings and chars on a line to simple "" or '' blocks.
+
+ We nix strings first so we're not fooled by text like '"http://"'
+
+ Args:
+ elided: The line being processed.
+
+ Returns:
+ The line with collapsed strings.
+ """
+ if _RE_PATTERN_INCLUDE.match(elided):
+ return elided
+
+ # Remove escaped characters first to make quote/single quote collapsing
+ # basic. Things that look like escaped characters shouldn't occur
+ # outside of strings and chars.
+ elided = _RE_PATTERN_CLEANSE_LINE_ESCAPES.sub('', elided)
+
+ # Replace quoted strings and digit separators. Both single quotes
+ # and double quotes are processed in the same loop, otherwise
+ # nested quotes wouldn't work.
+ collapsed = ''
+ while True:
+ # Find the first quote character
+ match = Match(r'^([^\'"]*)([\'"])(.*)$', elided)
+ if not match:
+ collapsed += elided
+ break
+ head, quote, tail = match.groups()
+
+ if quote == '"':
+ # Collapse double quoted strings
+ second_quote = tail.find('"')
+ if second_quote >= 0:
+ collapsed += head + '""'
+ elided = tail[second_quote + 1:]
+ else:
+ # Unmatched double quote, don't bother processing the rest
+ # of the line since this is probably a multiline string.
+ collapsed += elided
+ break
+ else:
+ # Found single quote, check nearby text to eliminate digit separators.
+ #
+ # There is no special handling for floating point here, because
+ # the integer/fractional/exponent parts would all be parsed
+ # correctly as long as there are digits on both sides of the
+ # separator. So we are fine as long as we don't see something
+ # like "0.'3" (gcc 4.9.0 will not allow this literal).
+ if Search(r'\b(?:0[bBxX]?|[1-9])[0-9a-fA-F]*$', head):
+ match_literal = Match(r'^((?:\'?[0-9a-zA-Z_])*)(.*)$', "'" + tail)
+ collapsed += head + match_literal.group(1).replace("'", '')
+ elided = match_literal.group(2)
+ else:
+ second_quote = tail.find('\'')
+ if second_quote >= 0:
+ collapsed += head + "''"
+ elided = tail[second_quote + 1:]
+ else:
+ # Unmatched single quote
+ collapsed += elided
+ break
+
+ return collapsed
+
+
+def FindEndOfExpressionInLine(line, startpos, stack):
+ """Find the position just after the end of current parenthesized expression.
+
+ Args:
+ line: a CleansedLines line.
+ startpos: start searching at this position.
+ stack: nesting stack at startpos.
+
+ Returns:
+ On finding matching end: (index just after matching end, None)
+ On finding an unclosed expression: (-1, None)
+ Otherwise: (-1, new stack at end of this line)
+ """
+ for i in xrange(startpos, len(line)):
+ char = line[i]
+ if char in '([{':
+ # Found start of parenthesized expression, push to expression stack
+ stack.append(char)
+ elif char == '<':
+ # Found potential start of template argument list
+ if i > 0 and line[i - 1] == '<':
+ # Left shift operator
+ if stack and stack[-1] == '<':
+ stack.pop()
+ if not stack:
+ return (-1, None)
+ elif i > 0 and Search(r'\boperator\s*$', line[0:i]):
+ # operator<, don't add to stack
+ continue
+ else:
+ # Tentative start of template argument list
+ stack.append('<')
+ elif char in ')]}':
+ # Found end of parenthesized expression.
+ #
+ # If we are currently expecting a matching '>', the pending '<'
+ # must have been an operator. Remove them from expression stack.
+ while stack and stack[-1] == '<':
+ stack.pop()
+ if not stack:
+ return (-1, None)
+ if ((stack[-1] == '(' and char == ')') or
+ (stack[-1] == '[' and char == ']') or
+ (stack[-1] == '{' and char == '}')):
+ stack.pop()
+ if not stack:
+ return (i + 1, None)
+ else:
+ # Mismatched parentheses
+ return (-1, None)
+ elif char == '>':
+ # Found potential end of template argument list.
+
+ # Ignore "->" and operator functions
+ if (i > 0 and
+ (line[i - 1] == '-' or Search(r'\boperator\s*$', line[0:i - 1]))):
+ continue
+
+ # Pop the stack if there is a matching '<'. Otherwise, ignore
+ # this '>' since it must be an operator.
+ if stack:
+ if stack[-1] == '<':
+ stack.pop()
+ if not stack:
+ return (i + 1, None)
+ elif char == ';':
+ # Found something that look like end of statements. If we are currently
+ # expecting a '>', the matching '<' must have been an operator, since
+ # template argument list should not contain statements.
+ while stack and stack[-1] == '<':
+ stack.pop()
+ if not stack:
+ return (-1, None)
+
+ # Did not find end of expression or unbalanced parentheses on this line
+ return (-1, stack)
+
+
+def CloseExpression(clean_lines, linenum, pos):
+ """If input points to ( or { or [ or <, finds the position that closes it.
+
+ If lines[linenum][pos] points to a '(' or '{' or '[' or '<', finds the
+ linenum/pos that correspond to the closing of the expression.
+
+ TODO(unknown): cpplint spends a fair bit of time matching parentheses.
+ Ideally we would want to index all opening and closing parentheses once
+ and have CloseExpression be just a simple lookup, but due to preprocessor
+ tricks, this is not so easy.
+
+ Args:
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ pos: A position on the line.
+
+ Returns:
+ A tuple (line, linenum, pos) pointer *past* the closing brace, or
+ (line, len(lines), -1) if we never find a close. Note we ignore
+ strings and comments when matching; and the line we return is the
+ 'cleansed' line at linenum.
+ """
+
+ line = clean_lines.elided[linenum]
+ if (line[pos] not in '({[<') or Match(r'<[<=]', line[pos:]):
+ return (line, clean_lines.NumLines(), -1)
+
+ # Check first line
+ (end_pos, stack) = FindEndOfExpressionInLine(line, pos, [])
+ if end_pos > -1:
+ return (line, linenum, end_pos)
+
+ # Continue scanning forward
+ while stack and linenum < clean_lines.NumLines() - 1:
+ linenum += 1
+ line = clean_lines.elided[linenum]
+ (end_pos, stack) = FindEndOfExpressionInLine(line, 0, stack)
+ if end_pos > -1:
+ return (line, linenum, end_pos)
+
+ # Did not find end of expression before end of file, give up
+ return (line, clean_lines.NumLines(), -1)
+
+
+def FindStartOfExpressionInLine(line, endpos, stack):
+ """Find position at the matching start of current expression.
+
+ This is almost the reverse of FindEndOfExpressionInLine, but note
+ that the input position and returned position differs by 1.
+
+ Args:
+ line: a CleansedLines line.
+ endpos: start searching at this position.
+ stack: nesting stack at endpos.
+
+ Returns:
+ On finding matching start: (index at matching start, None)
+ On finding an unclosed expression: (-1, None)
+ Otherwise: (-1, new stack at beginning of this line)
+ """
+ i = endpos
+ while i >= 0:
+ char = line[i]
+ if char in ')]}':
+ # Found end of expression, push to expression stack
+ stack.append(char)
+ elif char == '>':
+ # Found potential end of template argument list.
+ #
+ # Ignore it if it's a "->" or ">=" or "operator>"
+ if (i > 0 and
+ (line[i - 1] == '-' or
+ Match(r'\s>=\s', line[i - 1:]) or
+ Search(r'\boperator\s*$', line[0:i]))):
+ i -= 1
+ else:
+ stack.append('>')
+ elif char == '<':
+ # Found potential start of template argument list
+ if i > 0 and line[i - 1] == '<':
+ # Left shift operator
+ i -= 1
+ else:
+ # If there is a matching '>', we can pop the expression stack.
+ # Otherwise, ignore this '<' since it must be an operator.
+ if stack and stack[-1] == '>':
+ stack.pop()
+ if not stack:
+ return (i, None)
+ elif char in '([{':
+ # Found start of expression.
+ #
+ # If there are any unmatched '>' on the stack, they must be
+ # operators. Remove those.
+ while stack and stack[-1] == '>':
+ stack.pop()
+ if not stack:
+ return (-1, None)
+ if ((char == '(' and stack[-1] == ')') or
+ (char == '[' and stack[-1] == ']') or
+ (char == '{' and stack[-1] == '}')):
+ stack.pop()
+ if not stack:
+ return (i, None)
+ else:
+ # Mismatched parentheses
+ return (-1, None)
+ elif char == ';':
+ # Found something that look like end of statements. If we are currently
+ # expecting a '<', the matching '>' must have been an operator, since
+ # template argument list should not contain statements.
+ while stack and stack[-1] == '>':
+ stack.pop()
+ if not stack:
+ return (-1, None)
+
+ i -= 1
+
+ return (-1, stack)
+
+
+def ReverseCloseExpression(clean_lines, linenum, pos):
+ """If input points to ) or } or ] or >, finds the position that opens it.
+
+ If lines[linenum][pos] points to a ')' or '}' or ']' or '>', finds the
+ linenum/pos that correspond to the opening of the expression.
+
+ Args:
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ pos: A position on the line.
+
+ Returns:
+ A tuple (line, linenum, pos) pointer *at* the opening brace, or
+ (line, 0, -1) if we never find the matching opening brace. Note
+ we ignore strings and comments when matching; and the line we
+ return is the 'cleansed' line at linenum.
+ """
+ line = clean_lines.elided[linenum]
+ if line[pos] not in ')}]>':
+ return (line, 0, -1)
+
+ # Check last line
+ (start_pos, stack) = FindStartOfExpressionInLine(line, pos, [])
+ if start_pos > -1:
+ return (line, linenum, start_pos)
+
+ # Continue scanning backward
+ while stack and linenum > 0:
+ linenum -= 1
+ line = clean_lines.elided[linenum]
+ (start_pos, stack) = FindStartOfExpressionInLine(line, len(line) - 1, stack)
+ if start_pos > -1:
+ return (line, linenum, start_pos)
+
+ # Did not find start of expression before beginning of file, give up
+ return (line, 0, -1)
+
+
+def CheckForCopyright(filename, lines, error):
+ """Logs an error if no Copyright message appears at the top of the file."""
+
+ # We'll say it should occur by line 10. Don't forget there's a
+ # dummy line at the front.
+ for line in xrange(1, min(len(lines), 11)):
+ if re.search(r'Copyright', lines[line], re.I): break
+ else: # means no copyright line was found
+ error(filename, 0, 'legal/copyright', 5,
+ 'No copyright message found. '
+ 'You should have a line: "Copyright [year] <Copyright Owner>"')
+
+
+def GetIndentLevel(line):
+ """Return the number of leading spaces in line.
+
+ Args:
+ line: A string to check.
+
+ Returns:
+ An integer count of leading spaces, possibly zero.
+ """
+ indent = Match(r'^( *)\S', line)
+ if indent:
+ return len(indent.group(1))
+ else:
+ return 0
+
+def PathSplitToList(path):
+ """Returns the path split into a list by the separator.
+
+ Args:
+ path: An absolute or relative path (e.g. '/a/b/c/' or '../a')
+
+ Returns:
+ A list of path components (e.g. ['a', 'b', 'c]).
+ """
+ lst = []
+ while True:
+ (head, tail) = os.path.split(path)
+ if head == path: # absolute paths end
+ lst.append(head)
+ break
+ if tail == path: # relative paths end
+ lst.append(tail)
+ break
+
+ path = head
+ lst.append(tail)
+
+ lst.reverse()
+ return lst
+
+def GetHeaderGuardCPPVariable(filename):
+ """Returns the CPP variable that should be used as a header guard.
+
+ Args:
+ filename: The name of a C++ header file.
+
+ Returns:
+ The CPP variable that should be used as a header guard in the
+ named file.
+
+ """
+
+ # Restores original filename in case that cpplint is invoked from Emacs's
+ # flymake.
+ filename = re.sub(r'_flymake\.h$', '.h', filename)
+ filename = re.sub(r'/\.flymake/([^/]*)$', r'/\1', filename)
+ # Replace 'c++' with 'cpp'.
+ filename = filename.replace('C++', 'cpp').replace('c++', 'cpp')
+
+ fileinfo = FileInfo(filename)
+ file_path_from_root = fileinfo.RepositoryName()
+
+ def FixupPathFromRoot():
+ if _root_debug:
+ sys.stderr.write("\n_root fixup, _root = '%s', repository name = '%s'\n"
+ %(_root, fileinfo.RepositoryName()))
+
+ # Process the file path with the --root flag if it was set.
+ if not _root:
+ if _root_debug:
+ sys.stderr.write("_root unspecified\n")
+ return file_path_from_root
+
+ def StripListPrefix(lst, prefix):
+ # f(['x', 'y'], ['w, z']) -> None (not a valid prefix)
+ if lst[:len(prefix)] != prefix:
+ return None
+ # f(['a, 'b', 'c', 'd'], ['a', 'b']) -> ['c', 'd']
+ return lst[(len(prefix)):]
+
+ # root behavior:
+ # --root=subdir , lstrips subdir from the header guard
+ maybe_path = StripListPrefix(PathSplitToList(file_path_from_root),
+ PathSplitToList(_root))
+
+ if _root_debug:
+ sys.stderr.write("_root lstrip (maybe_path=%s, file_path_from_root=%s," +
+ " _root=%s)\n" %(maybe_path, file_path_from_root, _root))
+
+ if maybe_path:
+ return os.path.join(*maybe_path)
+
+ # --root=.. , will prepend the outer directory to the header guard
+ full_path = fileinfo.FullName()
+ root_abspath = os.path.abspath(_root)
+
+ maybe_path = StripListPrefix(PathSplitToList(full_path),
+ PathSplitToList(root_abspath))
+
+ if _root_debug:
+ sys.stderr.write("_root prepend (maybe_path=%s, full_path=%s, " +
+ "root_abspath=%s)\n" %(maybe_path, full_path, root_abspath))
+
+ if maybe_path:
+ return os.path.join(*maybe_path)
+
+ if _root_debug:
+ sys.stderr.write("_root ignore, returning %s\n" %(file_path_from_root))
+
+ # --root=FAKE_DIR is ignored
+ return file_path_from_root
+
+ file_path_from_root = FixupPathFromRoot()
+ return re.sub(r'[^a-zA-Z0-9]', '_', file_path_from_root).upper() + '_'
+
+
+def CheckForHeaderGuard(filename, clean_lines, error):
+ """Checks that the file contains a header guard.
+
+ Logs an error if no #ifndef header guard is present. For other
+ headers, checks that the full pathname is used.
+
+ Args:
+ filename: The name of the C++ header file.
+ clean_lines: A CleansedLines instance containing the file.
+ error: The function to call with any errors found.
+ """
+
+ # Don't check for header guards if there are error suppression
+ # comments somewhere in this file.
+ #
+ # Because this is silencing a warning for a nonexistent line, we
+ # only support the very specific NOLINT(build/header_guard) syntax,
+ # and not the general NOLINT or NOLINT(*) syntax.
+ raw_lines = clean_lines.lines_without_raw_strings
+ for i in raw_lines:
+ if Search(r'//\s*NOLINT\(build/header_guard\)', i):
+ return
+
+ cppvar = GetHeaderGuardCPPVariable(filename)
+
+ ifndef = ''
+ ifndef_linenum = 0
+ define = ''
+ endif = ''
+ endif_linenum = 0
+ for linenum, line in enumerate(raw_lines):
+ linesplit = line.split()
+ if len(linesplit) >= 2:
+ # find the first occurrence of #ifndef and #define, save arg
+ if not ifndef and linesplit[0] == '#ifndef':
+ # set ifndef to the header guard presented on the #ifndef line.
+ ifndef = linesplit[1]
+ ifndef_linenum = linenum
+ if not define and linesplit[0] == '#define':
+ define = linesplit[1]
+ # find the last occurrence of #endif, save entire line
+ if line.startswith('#endif'):
+ endif = line
+ endif_linenum = linenum
+
+ if not ifndef or not define or ifndef != define:
+ error(filename, 0, 'build/header_guard', 5,
+ 'No #ifndef header guard found, suggested CPP variable is: %s' %
+ cppvar)
+ return
+
+ # The guard should be PATH_FILE_H_, but we also allow PATH_FILE_H__
+ # for backward compatibility.
+ if ifndef != cppvar:
+ error_level = 0
+ if ifndef != cppvar + '_':
+ error_level = 5
+
+ ParseNolintSuppressions(filename, raw_lines[ifndef_linenum], ifndef_linenum,
+ error)
+ error(filename, ifndef_linenum, 'build/header_guard', error_level,
+ '#ifndef header guard has wrong style, please use: %s' % cppvar)
+
+ # Check for "//" comments on endif line.
+ ParseNolintSuppressions(filename, raw_lines[endif_linenum], endif_linenum,
+ error)
+ match = Match(r'#endif\s*//\s*' + cppvar + r'(_)?\b', endif)
+ if match:
+ if match.group(1) == '_':
+ # Issue low severity warning for deprecated double trailing underscore
+ error(filename, endif_linenum, 'build/header_guard', 0,
+ '#endif line should be "#endif // %s"' % cppvar)
+ return
+
+ # Didn't find the corresponding "//" comment. If this file does not
+ # contain any "//" comments at all, it could be that the compiler
+ # only wants "/**/" comments, look for those instead.
+ no_single_line_comments = True
+ for i in xrange(1, len(raw_lines) - 1):
+ line = raw_lines[i]
+ if Match(r'^(?:(?:\'(?:\.|[^\'])*\')|(?:"(?:\.|[^"])*")|[^\'"])*//', line):
+ no_single_line_comments = False
+ break
+
+ if no_single_line_comments:
+ match = Match(r'#endif\s*/\*\s*' + cppvar + r'(_)?\s*\*/', endif)
+ if match:
+ if match.group(1) == '_':
+ # Low severity warning for double trailing underscore
+ error(filename, endif_linenum, 'build/header_guard', 0,
+ '#endif line should be "#endif /* %s */"' % cppvar)
+ return
+
+ # Didn't find anything
+ error(filename, endif_linenum, 'build/header_guard', 5,
+ '#endif line should be "#endif // %s"' % cppvar)
+
+
+def CheckHeaderFileIncluded(filename, include_state, error):
+ """Logs an error if a .cc file does not include its header."""
+
+ # Do not check test files
+ fileinfo = FileInfo(filename)
+ if Search(_TEST_FILE_SUFFIX, fileinfo.BaseName()):
+ return
+
+ headerfile = filename[0:len(filename) - len(fileinfo.Extension())] + '.h'
+ if not os.path.exists(headerfile):
+ return
+ headername = FileInfo(headerfile).RepositoryName()
+ first_include = 0
+ for section_list in include_state.include_list:
+ for f in section_list:
+ if headername in f[0] or f[0] in headername:
+ return
+ if not first_include:
+ first_include = f[1]
+
+ error(filename, first_include, 'build/include', 5,
+ '%s should include its header file %s' % (fileinfo.RepositoryName(),
+ headername))
+
+
+def CheckForBadCharacters(filename, lines, error):
+ """Logs an error for each line containing bad characters.
+
+ Two kinds of bad characters:
+
+ 1. Unicode replacement characters: These indicate that either the file
+ contained invalid UTF-8 (likely) or Unicode replacement characters (which
+ it shouldn't). Note that it's possible for this to throw off line
+ numbering if the invalid UTF-8 occurred adjacent to a newline.
+
+ 2. NUL bytes. These are problematic for some tools.
+
+ Args:
+ filename: The name of the current file.
+ lines: An array of strings, each representing a line of the file.
+ error: The function to call with any errors found.
+ """
+ for linenum, line in enumerate(lines):
+ if u'\ufffd' in line:
+ error(filename, linenum, 'readability/utf8', 5,
+ 'Line contains invalid UTF-8 (or Unicode replacement character).')
+ if '\0' in line:
+ error(filename, linenum, 'readability/nul', 5, 'Line contains NUL byte.')
+
+
+def CheckForNewlineAtEOF(filename, lines, error):
+ """Logs an error if there is no newline char at the end of the file.
+
+ Args:
+ filename: The name of the current file.
+ lines: An array of strings, each representing a line of the file.
+ error: The function to call with any errors found.
+ """
+
+ # The array lines() was created by adding two newlines to the
+ # original file (go figure), then splitting on \n.
+ # To verify that the file ends in \n, we just have to make sure the
+ # last-but-two element of lines() exists and is empty.
+ if len(lines) < 3 or lines[-2]:
+ error(filename, len(lines) - 2, 'whitespace/ending_newline', 5,
+ 'Could not find a newline character at the end of the file.')
+
+
+def CheckForMultilineCommentsAndStrings(filename, clean_lines, linenum, error):
+ """Logs an error if we see /* ... */ or "..." that extend past one line.
+
+ /* ... */ comments are legit inside macros, for one line.
+ Otherwise, we prefer // comments, so it's ok to warn about the
+ other. Likewise, it's ok for strings to extend across multiple
+ lines, as long as a line continuation character (backslash)
+ terminates each line. Although not currently prohibited by the C++
+ style guide, it's ugly and unnecessary. We don't do well with either
+ in this lint program, so we warn about both.
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ error: The function to call with any errors found.
+ """
+ line = clean_lines.elided[linenum]
+
+ # Remove all \\ (escaped backslashes) from the line. They are OK, and the
+ # second (escaped) slash may trigger later \" detection erroneously.
+ line = line.replace('\\\\', '')
+
+ if line.count('/*') > line.count('*/'):
+ error(filename, linenum, 'readability/multiline_comment', 5,
+ 'Complex multi-line /*...*/-style comment found. '
+ 'Lint may give bogus warnings. '
+ 'Consider replacing these with //-style comments, '
+ 'with #if 0...#endif, '
+ 'or with more clearly structured multi-line comments.')
+
+ if (line.count('"') - line.count('\\"')) % 2:
+ error(filename, linenum, 'readability/multiline_string', 5,
+ 'Multi-line string ("...") found. This lint script doesn\'t '
+ 'do well with such strings, and may give bogus warnings. '
+ 'Use C++11 raw strings or concatenation instead.')
+
+
+# (non-threadsafe name, thread-safe alternative, validation pattern)
+#
+# The validation pattern is used to eliminate false positives such as:
+# _rand(); // false positive due to substring match.
+# ->rand(); // some member function rand().
+# ACMRandom rand(seed); // some variable named rand.
+# ISAACRandom rand(); // another variable named rand.
+#
+# Basically we require the return value of these functions to be used
+# in some expression context on the same line by matching on some
+# operator before the function name. This eliminates constructors and
+# member function calls.
+_UNSAFE_FUNC_PREFIX = r'(?:[-+*/=%^&|(<]\s*|>\s+)'
+_THREADING_LIST = (
+ ('asctime(', 'asctime_r(', _UNSAFE_FUNC_PREFIX + r'asctime\([^)]+\)'),
+ ('ctime(', 'ctime_r(', _UNSAFE_FUNC_PREFIX + r'ctime\([^)]+\)'),
+ ('getgrgid(', 'getgrgid_r(', _UNSAFE_FUNC_PREFIX + r'getgrgid\([^)]+\)'),
+ ('getgrnam(', 'getgrnam_r(', _UNSAFE_FUNC_PREFIX + r'getgrnam\([^)]+\)'),
+ ('getlogin(', 'getlogin_r(', _UNSAFE_FUNC_PREFIX + r'getlogin\(\)'),
+ ('getpwnam(', 'getpwnam_r(', _UNSAFE_FUNC_PREFIX + r'getpwnam\([^)]+\)'),
+ ('getpwuid(', 'getpwuid_r(', _UNSAFE_FUNC_PREFIX + r'getpwuid\([^)]+\)'),
+ ('gmtime(', 'gmtime_r(', _UNSAFE_FUNC_PREFIX + r'gmtime\([^)]+\)'),
+ ('localtime(', 'localtime_r(', _UNSAFE_FUNC_PREFIX + r'localtime\([^)]+\)'),
+ ('rand(', 'rand_r(', _UNSAFE_FUNC_PREFIX + r'rand\(\)'),
+ ('strtok(', 'strtok_r(',
+ _UNSAFE_FUNC_PREFIX + r'strtok\([^)]+\)'),
+ ('ttyname(', 'ttyname_r(', _UNSAFE_FUNC_PREFIX + r'ttyname\([^)]+\)'),
+ )
+
+
+def CheckPosixThreading(filename, clean_lines, linenum, error):
+ """Checks for calls to thread-unsafe functions.
+
+ Much code has been originally written without consideration of
+ multi-threading. Also, engineers are relying on their old experience;
+ they have learned posix before threading extensions were added. These
+ tests guide the engineers to use thread-safe functions (when using
+ posix directly).
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ error: The function to call with any errors found.
+ """
+ line = clean_lines.elided[linenum]
+ for single_thread_func, multithread_safe_func, pattern in _THREADING_LIST:
+ # Additional pattern matching check to confirm that this is the
+ # function we are looking for
+ if Search(pattern, line):
+ error(filename, linenum, 'runtime/threadsafe_fn', 2,
+ 'Consider using ' + multithread_safe_func +
+ '...) instead of ' + single_thread_func +
+ '...) for improved thread safety.')
+
+
+def CheckVlogArguments(filename, clean_lines, linenum, error):
+ """Checks that VLOG() is only used for defining a logging level.
+
+ For example, VLOG(2) is correct. VLOG(INFO), VLOG(WARNING), VLOG(ERROR), and
+ VLOG(FATAL) are not.
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ error: The function to call with any errors found.
+ """
+ line = clean_lines.elided[linenum]
+ if Search(r'\bVLOG\((INFO|ERROR|WARNING|DFATAL|FATAL)\)', line):
+ error(filename, linenum, 'runtime/vlog', 5,
+ 'VLOG() should be used with numeric verbosity level. '
+ 'Use LOG() if you want symbolic severity levels.')
+
+# Matches invalid increment: *count++, which moves pointer instead of
+# incrementing a value.
+_RE_PATTERN_INVALID_INCREMENT = re.compile(
+ r'^\s*\*\w+(\+\+|--);')
+
+
+def CheckInvalidIncrement(filename, clean_lines, linenum, error):
+ """Checks for invalid increment *count++.
+
+ For example following function:
+ void increment_counter(int* count) {
+ *count++;
+ }
+ is invalid, because it effectively does count++, moving pointer, and should
+ be replaced with ++*count, (*count)++ or *count += 1.
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ error: The function to call with any errors found.
+ """
+ line = clean_lines.elided[linenum]
+ if _RE_PATTERN_INVALID_INCREMENT.match(line):
+ error(filename, linenum, 'runtime/invalid_increment', 5,
+ 'Changing pointer instead of value (or unused value of operator*).')
+
+
+def IsMacroDefinition(clean_lines, linenum):
+ if Search(r'^#define', clean_lines[linenum]):
+ return True
+
+ if linenum > 0 and Search(r'\\$', clean_lines[linenum - 1]):
+ return True
+
+ return False
+
+
+def IsForwardClassDeclaration(clean_lines, linenum):
+ return Match(r'^\s*(\btemplate\b)*.*class\s+\w+;\s*$', clean_lines[linenum])
+
+
+class _BlockInfo(object):
+ """Stores information about a generic block of code."""
+
+ def __init__(self, linenum, seen_open_brace):
+ self.starting_linenum = linenum
+ self.seen_open_brace = seen_open_brace
+ self.open_parentheses = 0
+ self.inline_asm = _NO_ASM
+ self.check_namespace_indentation = False
+
+ def CheckBegin(self, filename, clean_lines, linenum, error):
+ """Run checks that applies to text up to the opening brace.
+
+ This is mostly for checking the text after the class identifier
+ and the "{", usually where the base class is specified. For other
+ blocks, there isn't much to check, so we always pass.
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ error: The function to call with any errors found.
+ """
+ pass
+
+ def CheckEnd(self, filename, clean_lines, linenum, error):
+ """Run checks that applies to text after the closing brace.
+
+ This is mostly used for checking end of namespace comments.
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ error: The function to call with any errors found.
+ """
+ pass
+
+ def IsBlockInfo(self):
+ """Returns true if this block is a _BlockInfo.
+
+ This is convenient for verifying that an object is an instance of
+ a _BlockInfo, but not an instance of any of the derived classes.
+
+ Returns:
+ True for this class, False for derived classes.
+ """
+ return self.__class__ == _BlockInfo
+
+
+class _ExternCInfo(_BlockInfo):
+ """Stores information about an 'extern "C"' block."""
+
+ def __init__(self, linenum):
+ _BlockInfo.__init__(self, linenum, True)
+
+
+class _ClassInfo(_BlockInfo):
+ """Stores information about a class."""
+
+ def __init__(self, name, class_or_struct, clean_lines, linenum):
+ _BlockInfo.__init__(self, linenum, False)
+ self.name = name
+ self.is_derived = False
+ self.check_namespace_indentation = True
+ if class_or_struct == 'struct':
+ self.access = 'public'
+ self.is_struct = True
+ else:
+ self.access = 'private'
+ self.is_struct = False
+
+ # Remember initial indentation level for this class. Using raw_lines here
+ # instead of elided to account for leading comments.
+ self.class_indent = GetIndentLevel(clean_lines.raw_lines[linenum])
+
+ # Try to find the end of the class. This will be confused by things like:
+ # class A {
+ # } *x = { ...
+ #
+ # But it's still good enough for CheckSectionSpacing.
+ self.last_line = 0
+ depth = 0
+ for i in range(linenum, clean_lines.NumLines()):
+ line = clean_lines.elided[i]
+ depth += line.count('{') - line.count('}')
+ if not depth:
+ self.last_line = i
+ break
+
+ def CheckBegin(self, filename, clean_lines, linenum, error):
+ # Look for a bare ':'
+ if Search('(^|[^:]):($|[^:])', clean_lines.elided[linenum]):
+ self.is_derived = True
+
+ def CheckEnd(self, filename, clean_lines, linenum, error):
+ # If there is a DISALLOW macro, it should appear near the end of
+ # the class.
+ seen_last_thing_in_class = False
+ for i in xrange(linenum - 1, self.starting_linenum, -1):
+ match = Search(
+ r'\b(DISALLOW_COPY_AND_ASSIGN|DISALLOW_IMPLICIT_CONSTRUCTORS)\(' +
+ self.name + r'\)',
+ clean_lines.elided[i])
+ if match:
+ if seen_last_thing_in_class:
+ error(filename, i, 'readability/constructors', 3,
+ match.group(1) + ' should be the last thing in the class')
+ break
+
+ if not Match(r'^\s*$', clean_lines.elided[i]):
+ seen_last_thing_in_class = True
+
+ # Check that closing brace is aligned with beginning of the class.
+ # Only do this if the closing brace is indented by only whitespaces.
+ # This means we will not check single-line class definitions.
+ indent = Match(r'^( *)\}', clean_lines.elided[linenum])
+ if indent and len(indent.group(1)) != self.class_indent:
+ if self.is_struct:
+ parent = 'struct ' + self.name
+ else:
+ parent = 'class ' + self.name
+ error(filename, linenum, 'whitespace/indent', 3,
+ 'Closing brace should be aligned with beginning of %s' % parent)
+
+
+class _NamespaceInfo(_BlockInfo):
+ """Stores information about a namespace."""
+
+ def __init__(self, name, linenum):
+ _BlockInfo.__init__(self, linenum, False)
+ self.name = name or ''
+ self.check_namespace_indentation = True
+
+ def CheckEnd(self, filename, clean_lines, linenum, error):
+ """Check end of namespace comments."""
+ line = clean_lines.raw_lines[linenum]
+
+ # Check how many lines is enclosed in this namespace. Don't issue
+ # warning for missing namespace comments if there aren't enough
+ # lines. However, do apply checks if there is already an end of
+ # namespace comment and it's incorrect.
+ #
+ # TODO(unknown): We always want to check end of namespace comments
+ # if a namespace is large, but sometimes we also want to apply the
+ # check if a short namespace contained nontrivial things (something
+ # other than forward declarations). There is currently no logic on
+ # deciding what these nontrivial things are, so this check is
+ # triggered by namespace size only, which works most of the time.
+ if (linenum - self.starting_linenum < 10
+ and not Match(r'^\s*};*\s*(//|/\*).*\bnamespace\b', line)):
+ return
+
+ # Look for matching comment at end of namespace.
+ #
+ # Note that we accept C style "/* */" comments for terminating
+ # namespaces, so that code that terminate namespaces inside
+ # preprocessor macros can be cpplint clean.
+ #
+ # We also accept stuff like "// end of namespace <name>." with the
+ # period at the end.
+ #
+ # Besides these, we don't accept anything else, otherwise we might
+ # get false negatives when existing comment is a substring of the
+ # expected namespace.
+ if self.name:
+ # Named namespace
+ if not Match((r'^\s*};*\s*(//|/\*).*\bnamespace\s+' +
+ re.escape(self.name) + r'[\*/\.\\\s]*$'),
+ line):
+ error(filename, linenum, 'readability/namespace', 5,
+ 'Namespace should be terminated with "// namespace %s"' %
+ self.name)
+ else:
+ # Anonymous namespace
+ if not Match(r'^\s*};*\s*(//|/\*).*\bnamespace[\*/\.\\\s]*$', line):
+ # If "// namespace anonymous" or "// anonymous namespace (more text)",
+ # mention "// anonymous namespace" as an acceptable form
+ if Match(r'^\s*}.*\b(namespace anonymous|anonymous namespace)\b', line):
+ error(filename, linenum, 'readability/namespace', 5,
+ 'Anonymous namespace should be terminated with "// namespace"'
+ ' or "// anonymous namespace"')
+ else:
+ error(filename, linenum, 'readability/namespace', 5,
+ 'Anonymous namespace should be terminated with "// namespace"')
+
+
+class _PreprocessorInfo(object):
+ """Stores checkpoints of nesting stacks when #if/#else is seen."""
+
+ def __init__(self, stack_before_if):
+ # The entire nesting stack before #if
+ self.stack_before_if = stack_before_if
+
+ # The entire nesting stack up to #else
+ self.stack_before_else = []
+
+ # Whether we have already seen #else or #elif
+ self.seen_else = False
+
+
+class NestingState(object):
+ """Holds states related to parsing braces."""
+
+ def __init__(self):
+ # Stack for tracking all braces. An object is pushed whenever we
+ # see a "{", and popped when we see a "}". Only 3 types of
+ # objects are possible:
+ # - _ClassInfo: a class or struct.
+ # - _NamespaceInfo: a namespace.
+ # - _BlockInfo: some other type of block.
+ self.stack = []
+
+ # Top of the previous stack before each Update().
+ #
+ # Because the nesting_stack is updated at the end of each line, we
+ # had to do some convoluted checks to find out what is the current
+ # scope at the beginning of the line. This check is simplified by
+ # saving the previous top of nesting stack.
+ #
+ # We could save the full stack, but we only need the top. Copying
+ # the full nesting stack would slow down cpplint by ~10%.
+ self.previous_stack_top = []
+
+ # Stack of _PreprocessorInfo objects.
+ self.pp_stack = []
+
+ def SeenOpenBrace(self):
+ """Check if we have seen the opening brace for the innermost block.
+
+ Returns:
+ True if we have seen the opening brace, False if the innermost
+ block is still expecting an opening brace.
+ """
+ return (not self.stack) or self.stack[-1].seen_open_brace
+
+ def InNamespaceBody(self):
+ """Check if we are currently one level inside a namespace body.
+
+ Returns:
+ True if top of the stack is a namespace block, False otherwise.
+ """
+ return self.stack and isinstance(self.stack[-1], _NamespaceInfo)
+
+ def InExternC(self):
+ """Check if we are currently one level inside an 'extern "C"' block.
+
+ Returns:
+ True if top of the stack is an extern block, False otherwise.
+ """
+ return self.stack and isinstance(self.stack[-1], _ExternCInfo)
+
+ def InClassDeclaration(self):
+ """Check if we are currently one level inside a class or struct declaration.
+
+ Returns:
+ True if top of the stack is a class/struct, False otherwise.
+ """
+ return self.stack and isinstance(self.stack[-1], _ClassInfo)
+
+ def InAsmBlock(self):
+ """Check if we are currently one level inside an inline ASM block.
+
+ Returns:
+ True if the top of the stack is a block containing inline ASM.
+ """
+ return self.stack and self.stack[-1].inline_asm != _NO_ASM
+
+ def InTemplateArgumentList(self, clean_lines, linenum, pos):
+ """Check if current position is inside template argument list.
+
+ Args:
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ pos: position just after the suspected template argument.
+ Returns:
+ True if (linenum, pos) is inside template arguments.
+ """
+ while linenum < clean_lines.NumLines():
+ # Find the earliest character that might indicate a template argument
+ line = clean_lines.elided[linenum]
+ match = Match(r'^[^{};=\[\]\.<>]*(.)', line[pos:])
+ if not match:
+ linenum += 1
+ pos = 0
+ continue
+ token = match.group(1)
+ pos += len(match.group(0))
+
+ # These things do not look like template argument list:
+ # class Suspect {
+ # class Suspect x; }
+ if token in ('{', '}', ';'): return False
+
+ # These things look like template argument list:
+ # template <class Suspect>
+ # template <class Suspect = default_value>
+ # template <class Suspect[]>
+ # template <class Suspect...>
+ if token in ('>', '=', '[', ']', '.'): return True
+
+ # Check if token is an unmatched '<'.
+ # If not, move on to the next character.
+ if token != '<':
+ pos += 1
+ if pos >= len(line):
+ linenum += 1
+ pos = 0
+ continue
+
+ # We can't be sure if we just find a single '<', and need to
+ # find the matching '>'.
+ (_, end_line, end_pos) = CloseExpression(clean_lines, linenum, pos - 1)
+ if end_pos < 0:
+ # Not sure if template argument list or syntax error in file
+ return False
+ linenum = end_line
+ pos = end_pos
+ return False
+
+ def UpdatePreprocessor(self, line):
+ """Update preprocessor stack.
+
+ We need to handle preprocessors due to classes like this:
+ #ifdef SWIG
+ struct ResultDetailsPageElementExtensionPoint {
+ #else
+ struct ResultDetailsPageElementExtensionPoint : public Extension {
+ #endif
+
+ We make the following assumptions (good enough for most files):
+ - Preprocessor condition evaluates to true from #if up to first
+ #else/#elif/#endif.
+
+ - Preprocessor condition evaluates to false from #else/#elif up
+ to #endif. We still perform lint checks on these lines, but
+ these do not affect nesting stack.
+
+ Args:
+ line: current line to check.
+ """
+ if Match(r'^\s*#\s*(if|ifdef|ifndef)\b', line):
+ # Beginning of #if block, save the nesting stack here. The saved
+ # stack will allow us to restore the parsing state in the #else case.
+ self.pp_stack.append(_PreprocessorInfo(copy.deepcopy(self.stack)))
+ elif Match(r'^\s*#\s*(else|elif)\b', line):
+ # Beginning of #else block
+ if self.pp_stack:
+ if not self.pp_stack[-1].seen_else:
+ # This is the first #else or #elif block. Remember the
+ # whole nesting stack up to this point. This is what we
+ # keep after the #endif.
+ self.pp_stack[-1].seen_else = True
+ self.pp_stack[-1].stack_before_else = copy.deepcopy(self.stack)
+
+ # Restore the stack to how it was before the #if
+ self.stack = copy.deepcopy(self.pp_stack[-1].stack_before_if)
+ else:
+ # TODO(unknown): unexpected #else, issue warning?
+ pass
+ elif Match(r'^\s*#\s*endif\b', line):
+ # End of #if or #else blocks.
+ if self.pp_stack:
+ # If we saw an #else, we will need to restore the nesting
+ # stack to its former state before the #else, otherwise we
+ # will just continue from where we left off.
+ if self.pp_stack[-1].seen_else:
+ # Here we can just use a shallow copy since we are the last
+ # reference to it.
+ self.stack = self.pp_stack[-1].stack_before_else
+ # Drop the corresponding #if
+ self.pp_stack.pop()
+ else:
+ # TODO(unknown): unexpected #endif, issue warning?
+ pass
+
+ # TODO(unknown): Update() is too long, but we will refactor later.
+ def Update(self, filename, clean_lines, linenum, error):
+ """Update nesting state with current line.
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ error: The function to call with any errors found.
+ """
+ line = clean_lines.elided[linenum]
+
+ # Remember top of the previous nesting stack.
+ #
+ # The stack is always pushed/popped and not modified in place, so
+ # we can just do a shallow copy instead of copy.deepcopy. Using
+ # deepcopy would slow down cpplint by ~28%.
+ if self.stack:
+ self.previous_stack_top = self.stack[-1]
+ else:
+ self.previous_stack_top = None
+
+ # Update pp_stack
+ self.UpdatePreprocessor(line)
+
+ # Count parentheses. This is to avoid adding struct arguments to
+ # the nesting stack.
+ if self.stack:
+ inner_block = self.stack[-1]
+ depth_change = line.count('(') - line.count(')')
+ inner_block.open_parentheses += depth_change
+
+ # Also check if we are starting or ending an inline assembly block.
+ if inner_block.inline_asm in (_NO_ASM, _END_ASM):
+ if (depth_change != 0 and
+ inner_block.open_parentheses == 1 and
+ _MATCH_ASM.match(line)):
+ # Enter assembly block
+ inner_block.inline_asm = _INSIDE_ASM
+ else:
+ # Not entering assembly block. If previous line was _END_ASM,
+ # we will now shift to _NO_ASM state.
+ inner_block.inline_asm = _NO_ASM
+ elif (inner_block.inline_asm == _INSIDE_ASM and
+ inner_block.open_parentheses == 0):
+ # Exit assembly block
+ inner_block.inline_asm = _END_ASM
+
+ # Consume namespace declaration at the beginning of the line. Do
+ # this in a loop so that we catch same line declarations like this:
+ # namespace proto2 { namespace bridge { class MessageSet; } }
+ while True:
+ # Match start of namespace. The "\b\s*" below catches namespace
+ # declarations even if it weren't followed by a whitespace, this
+ # is so that we don't confuse our namespace checker. The
+ # missing spaces will be flagged by CheckSpacing.
+ namespace_decl_match = Match(r'^\s*namespace\b\s*([:\w]+)?(.*)$', line)
+ if not namespace_decl_match:
+ break
+
+ new_namespace = _NamespaceInfo(namespace_decl_match.group(1), linenum)
+ self.stack.append(new_namespace)
+
+ line = namespace_decl_match.group(2)
+ if line.find('{') != -1:
+ new_namespace.seen_open_brace = True
+ line = line[line.find('{') + 1:]
+
+ # Look for a class declaration in whatever is left of the line
+ # after parsing namespaces. The regexp accounts for decorated classes
+ # such as in:
+ # class LOCKABLE API Object {
+ # };
+ class_decl_match = Match(
+ r'^(\s*(?:template\s*<[\w\s<>,:]*>\s*)?'
+ r'(class|struct)\s+(?:[A-Z_]+\s+)*(\w+(?:::\w+)*))'
+ r'(.*)$', line)
+ if (class_decl_match and
+ (not self.stack or self.stack[-1].open_parentheses == 0)):
+ # We do not want to accept classes that are actually template arguments:
+ # template <class Ignore1,
+ # class Ignore2 = Default<Args>,
+ # template <Args> class Ignore3>
+ # void Function() {};
+ #
+ # To avoid template argument cases, we scan forward and look for
+ # an unmatched '>'. If we see one, assume we are inside a
+ # template argument list.
+ end_declaration = len(class_decl_match.group(1))
+ if not self.InTemplateArgumentList(clean_lines, linenum, end_declaration):
+ self.stack.append(_ClassInfo(
+ class_decl_match.group(3), class_decl_match.group(2),
+ clean_lines, linenum))
+ line = class_decl_match.group(4)
+
+ # If we have not yet seen the opening brace for the innermost block,
+ # run checks here.
+ if not self.SeenOpenBrace():
+ self.stack[-1].CheckBegin(filename, clean_lines, linenum, error)
+
+ # Update access control if we are inside a class/struct
+ if self.stack and isinstance(self.stack[-1], _ClassInfo):
+ classinfo = self.stack[-1]
+ access_match = Match(
+ r'^(.*)\b(public|private|protected|signals)(\s+(?:slots\s*)?)?'
+ r':(?:[^:]|$)',
+ line)
+ if access_match:
+ classinfo.access = access_match.group(2)
+
+ # Check that access keywords are indented +1 space. Skip this
+ # check if the keywords are not preceded by whitespaces.
+ indent = access_match.group(1)
+ if (len(indent) != classinfo.class_indent + 1 and
+ Match(r'^\s*$', indent)):
+ if classinfo.is_struct:
+ parent = 'struct ' + classinfo.name
+ else:
+ parent = 'class ' + classinfo.name
+ slots = ''
+ if access_match.group(3):
+ slots = access_match.group(3)
+ error(filename, linenum, 'whitespace/indent', 3,
+ '%s%s: should be indented +1 space inside %s' % (
+ access_match.group(2), slots, parent))
+
+ # Consume braces or semicolons from what's left of the line
+ while True:
+ # Match first brace, semicolon, or closed parenthesis.
+ matched = Match(r'^[^{;)}]*([{;)}])(.*)$', line)
+ if not matched:
+ break
+
+ token = matched.group(1)
+ if token == '{':
+ # If namespace or class hasn't seen a opening brace yet, mark
+ # namespace/class head as complete. Push a new block onto the
+ # stack otherwise.
+ if not self.SeenOpenBrace():
+ self.stack[-1].seen_open_brace = True
+ elif Match(r'^extern\s*"[^"]*"\s*\{', line):
+ self.stack.append(_ExternCInfo(linenum))
+ else:
+ self.stack.append(_BlockInfo(linenum, True))
+ if _MATCH_ASM.match(line):
+ self.stack[-1].inline_asm = _BLOCK_ASM
+
+ elif token == ';' or token == ')':
+ # If we haven't seen an opening brace yet, but we already saw
+ # a semicolon, this is probably a forward declaration. Pop
+ # the stack for these.
+ #
+ # Similarly, if we haven't seen an opening brace yet, but we
+ # already saw a closing parenthesis, then these are probably
+ # function arguments with extra "class" or "struct" keywords.
+ # Also pop these stack for these.
+ if not self.SeenOpenBrace():
+ self.stack.pop()
+ else: # token == '}'
+ # Perform end of block checks and pop the stack.
+ if self.stack:
+ self.stack[-1].CheckEnd(filename, clean_lines, linenum, error)
+ self.stack.pop()
+ line = matched.group(2)
+
+ def InnermostClass(self):
+ """Get class info on the top of the stack.
+
+ Returns:
+ A _ClassInfo object if we are inside a class, or None otherwise.
+ """
+ for i in range(len(self.stack), 0, -1):
+ classinfo = self.stack[i - 1]
+ if isinstance(classinfo, _ClassInfo):
+ return classinfo
+ return None
+
+ def CheckCompletedBlocks(self, filename, error):
+ """Checks that all classes and namespaces have been completely parsed.
+
+ Call this when all lines in a file have been processed.
+ Args:
+ filename: The name of the current file.
+ error: The function to call with any errors found.
+ """
+ # Note: This test can result in false positives if #ifdef constructs
+ # get in the way of brace matching. See the testBuildClass test in
+ # cpplint_unittest.py for an example of this.
+ for obj in self.stack:
+ if isinstance(obj, _ClassInfo):
+ error(filename, obj.starting_linenum, 'build/class', 5,
+ 'Failed to find complete declaration of class %s' %
+ obj.name)
+ elif isinstance(obj, _NamespaceInfo):
+ error(filename, obj.starting_linenum, 'build/namespaces', 5,
+ 'Failed to find complete declaration of namespace %s' %
+ obj.name)
+
+
+def CheckForNonStandardConstructs(filename, clean_lines, linenum,
+ nesting_state, error):
+ r"""Logs an error if we see certain non-ANSI constructs ignored by gcc-2.
+
+ Complain about several constructs which gcc-2 accepts, but which are
+ not standard C++. Warning about these in lint is one way to ease the
+ transition to new compilers.
+ - put storage class first (e.g. "static const" instead of "const static").
+ - "%lld" instead of %qd" in printf-type functions.
+ - "%1$d" is non-standard in printf-type functions.
+ - "\%" is an undefined character escape sequence.
+ - text after #endif is not allowed.
+ - invalid inner-style forward declaration.
+ - >? and <? operators, and their >?= and <?= cousins.
+
+ Additionally, check for constructor/destructor style violations and reference
+ members, as it is very convenient to do so while checking for
+ gcc-2 compliance.
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ nesting_state: A NestingState instance which maintains information about
+ the current stack of nested blocks being parsed.
+ error: A callable to which errors are reported, which takes 4 arguments:
+ filename, line number, error level, and message
+ """
+
+ # Remove comments from the line, but leave in strings for now.
+ line = clean_lines.lines[linenum]
+
+ if Search(r'printf\s*\(.*".*%[-+ ]?\d*q', line):
+ error(filename, linenum, 'runtime/printf_format', 3,
+ '%q in format strings is deprecated. Use %ll instead.')
+
+ if Search(r'printf\s*\(.*".*%\d+\$', line):
+ error(filename, linenum, 'runtime/printf_format', 2,
+ '%N$ formats are unconventional. Try rewriting to avoid them.')
+
+ # Remove escaped backslashes before looking for undefined escapes.
+ line = line.replace('\\\\', '')
+
+ if Search(r'("|\').*\\(%|\[|\(|{)', line):
+ error(filename, linenum, 'build/printf_format', 3,
+ '%, [, (, and { are undefined character escapes. Unescape them.')
+
+ # For the rest, work with both comments and strings removed.
+ line = clean_lines.elided[linenum]
+
+ if Search(r'\b(const|volatile|void|char|short|int|long'
+ r'|float|double|signed|unsigned'
+ r'|schar|u?int8|u?int16|u?int32|u?int64)'
+ r'\s+(register|static|extern|typedef)\b',
+ line):
+ error(filename, linenum, 'build/storage_class', 5,
+ 'Storage-class specifier (static, extern, typedef, etc) should be '
+ 'at the beginning of the declaration.')
+
+ if Match(r'\s*#\s*endif\s*[^/\s]+', line):
+ error(filename, linenum, 'build/endif_comment', 5,
+ 'Uncommented text after #endif is non-standard. Use a comment.')
+
+ if Match(r'\s*class\s+(\w+\s*::\s*)+\w+\s*;', line):
+ error(filename, linenum, 'build/forward_decl', 5,
+ 'Inner-style forward declarations are invalid. Remove this line.')
+
+ if Search(r'(\w+|[+-]?\d+(\.\d*)?)\s*(<|>)\?=?\s*(\w+|[+-]?\d+)(\.\d*)?',
+ line):
+ error(filename, linenum, 'build/deprecated', 3,
+ '>? and <? (max and min) operators are non-standard and deprecated.')
+
+ if Search(r'^\s*const\s*string\s*&\s*\w+\s*;', line):
+ # TODO(unknown): Could it be expanded safely to arbitrary references,
+ # without triggering too many false positives? The first
+ # attempt triggered 5 warnings for mostly benign code in the regtest, hence
+ # the restriction.
+ # Here's the original regexp, for the reference:
+ # type_name = r'\w+((\s*::\s*\w+)|(\s*<\s*\w+?\s*>))?'
+ # r'\s*const\s*' + type_name + '\s*&\s*\w+\s*;'
+ error(filename, linenum, 'runtime/member_string_references', 2,
+ 'const string& members are dangerous. It is much better to use '
+ 'alternatives, such as pointers or simple constants.')
+
+ # Everything else in this function operates on class declarations.
+ # Return early if the top of the nesting stack is not a class, or if
+ # the class head is not completed yet.
+ classinfo = nesting_state.InnermostClass()
+ if not classinfo or not classinfo.seen_open_brace:
+ return
+
+ # The class may have been declared with namespace or classname qualifiers.
+ # The constructor and destructor will not have those qualifiers.
+ base_classname = classinfo.name.split('::')[-1]
+
+ # Look for single-argument constructors that aren't marked explicit.
+ # Technically a valid construct, but against style.
+ explicit_constructor_match = Match(
+ r'\s+(?:(?:inline|constexpr)\s+)*(explicit\s+)?'
+ r'(?:(?:inline|constexpr)\s+)*%s\s*'
+ r'\(((?:[^()]|\([^()]*\))*)\)'
+ % re.escape(base_classname),
+ line)
+
+ if explicit_constructor_match:
+ is_marked_explicit = explicit_constructor_match.group(1)
+
+ if not explicit_constructor_match.group(2):
+ constructor_args = []
+ else:
+ constructor_args = explicit_constructor_match.group(2).split(',')
+
+ # collapse arguments so that commas in template parameter lists and function
+ # argument parameter lists don't split arguments in two
+ i = 0
+ while i < len(constructor_args):
+ constructor_arg = constructor_args[i]
+ while (constructor_arg.count('<') > constructor_arg.count('>') or
+ constructor_arg.count('(') > constructor_arg.count(')')):
+ constructor_arg += ',' + constructor_args[i + 1]
+ del constructor_args[i + 1]
+ constructor_args[i] = constructor_arg
+ i += 1
+
+ defaulted_args = [arg for arg in constructor_args if '=' in arg]
+ noarg_constructor = (not constructor_args or # empty arg list
+ # 'void' arg specifier
+ (len(constructor_args) == 1 and
+ constructor_args[0].strip() == 'void'))
+ onearg_constructor = ((len(constructor_args) == 1 and # exactly one arg
+ not noarg_constructor) or
+ # all but at most one arg defaulted
+ (len(constructor_args) >= 1 and
+ not noarg_constructor and
+ len(defaulted_args) >= len(constructor_args) - 1))
+ initializer_list_constructor = bool(
+ onearg_constructor and
+ Search(r'\bstd\s*::\s*initializer_list\b', constructor_args[0]))
+ copy_constructor = bool(
+ onearg_constructor and
+ Match(r'(const\s+)?%s(\s*<[^>]*>)?(\s+const)?\s*(?:<\w+>\s*)?&'
+ % re.escape(base_classname), constructor_args[0].strip()))
+
+ if (not is_marked_explicit and
+ onearg_constructor and
+ not initializer_list_constructor and
+ not copy_constructor):
+ if defaulted_args:
+ error(filename, linenum, 'runtime/explicit', 5,
+ 'Constructors callable with one argument '
+ 'should be marked explicit.')
+ else:
+ error(filename, linenum, 'runtime/explicit', 5,
+ 'Single-parameter constructors should be marked explicit.')
+ elif is_marked_explicit and not onearg_constructor:
+ if noarg_constructor:
+ error(filename, linenum, 'runtime/explicit', 5,
+ 'Zero-parameter constructors should not be marked explicit.')
+
+
+def CheckSpacingForFunctionCall(filename, clean_lines, linenum, error):
+ """Checks for the correctness of various spacing around function calls.
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ error: The function to call with any errors found.
+ """
+ line = clean_lines.elided[linenum]
+
+ # Since function calls often occur inside if/for/while/switch
+ # expressions - which have their own, more liberal conventions - we
+ # first see if we should be looking inside such an expression for a
+ # function call, to which we can apply more strict standards.
+ fncall = line # if there's no control flow construct, look at whole line
+ for pattern in (r'\bif\s*\((.*)\)\s*{',
+ r'\bfor\s*\((.*)\)\s*{',
+ r'\bwhile\s*\((.*)\)\s*[{;]',
+ r'\bswitch\s*\((.*)\)\s*{'):
+ match = Search(pattern, line)
+ if match:
+ fncall = match.group(1) # look inside the parens for function calls
+ break
+
+ # Except in if/for/while/switch, there should never be space
+ # immediately inside parens (eg "f( 3, 4 )"). We make an exception
+ # for nested parens ( (a+b) + c ). Likewise, there should never be
+ # a space before a ( when it's a function argument. I assume it's a
+ # function argument when the char before the whitespace is legal in
+ # a function name (alnum + _) and we're not starting a macro. Also ignore
+ # pointers and references to arrays and functions coz they're too tricky:
+ # we use a very simple way to recognize these:
+ # " (something)(maybe-something)" or
+ # " (something)(maybe-something," or
+ # " (something)[something]"
+ # Note that we assume the contents of [] to be short enough that
+ # they'll never need to wrap.
+ if ( # Ignore control structures.
+ not Search(r'\b(if|for|while|switch|return|new|delete|catch|sizeof)\b',
+ fncall) and
+ # Ignore pointers/references to functions.
+ not Search(r' \([^)]+\)\([^)]*(\)|,$)', fncall) and
+ # Ignore pointers/references to arrays.
+ not Search(r' \([^)]+\)\[[^\]]+\]', fncall)):
+ if Search(r'\w\s*\(\s(?!\s*\\$)', fncall): # a ( used for a fn call
+ error(filename, linenum, 'whitespace/parens', 4,
+ 'Extra space after ( in function call')
+ elif Search(r'\(\s+(?!(\s*\\)|\()', fncall):
+ error(filename, linenum, 'whitespace/parens', 2,
+ 'Extra space after (')
+ if (Search(r'\w\s+\(', fncall) and
+ not Search(r'_{0,2}asm_{0,2}\s+_{0,2}volatile_{0,2}\s+\(', fncall) and
+ not Search(r'#\s*define|typedef|using\s+\w+\s*=', fncall) and
+ not Search(r'\w\s+\((\w+::)*\*\w+\)\(', fncall) and
+ not Search(r'\bcase\s+\(', fncall)):
+ # TODO(unknown): Space after an operator function seem to be a common
+ # error, silence those for now by restricting them to highest verbosity.
+ if Search(r'\boperator_*\b', line):
+ error(filename, linenum, 'whitespace/parens', 0,
+ 'Extra space before ( in function call')
+ else:
+ error(filename, linenum, 'whitespace/parens', 4,
+ 'Extra space before ( in function call')
+ # If the ) is followed only by a newline or a { + newline, assume it's
+ # part of a control statement (if/while/etc), and don't complain
+ if Search(r'[^)]\s+\)\s*[^{\s]', fncall):
+ # If the closing parenthesis is preceded by only whitespaces,
+ # try to give a more descriptive error message.
+ if Search(r'^\s+\)', fncall):
+ error(filename, linenum, 'whitespace/parens', 2,
+ 'Closing ) should be moved to the previous line')
+ else:
+ error(filename, linenum, 'whitespace/parens', 2,
+ 'Extra space before )')
+
+
+def IsBlankLine(line):
+ """Returns true if the given line is blank.
+
+ We consider a line to be blank if the line is empty or consists of
+ only white spaces.
+
+ Args:
+ line: A line of a string.
+
+ Returns:
+ True, if the given line is blank.
+ """
+ return not line or line.isspace()
+
+
+def CheckForNamespaceIndentation(filename, nesting_state, clean_lines, line,
+ error):
+ is_namespace_indent_item = (
+ len(nesting_state.stack) > 1 and
+ nesting_state.stack[-1].check_namespace_indentation and
+ isinstance(nesting_state.previous_stack_top, _NamespaceInfo) and
+ nesting_state.previous_stack_top == nesting_state.stack[-2])
+
+ if ShouldCheckNamespaceIndentation(nesting_state, is_namespace_indent_item,
+ clean_lines.elided, line):
+ CheckItemIndentationInNamespace(filename, clean_lines.elided,
+ line, error)
+
+
+def CheckForFunctionLengths(filename, clean_lines, linenum,
+ function_state, error):
+ """Reports for long function bodies.
+
+ For an overview why this is done, see:
+ https://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Write_Short_Functions
+
+ Uses a simplistic algorithm assuming other style guidelines
+ (especially spacing) are followed.
+ Only checks unindented functions, so class members are unchecked.
+ Trivial bodies are unchecked, so constructors with huge initializer lists
+ may be missed.
+ Blank/comment lines are not counted so as to avoid encouraging the removal
+ of vertical space and comments just to get through a lint check.
+ NOLINT *on the last line of a function* disables this check.
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ function_state: Current function name and lines in body so far.
+ error: The function to call with any errors found.
+ """
+ lines = clean_lines.lines
+ line = lines[linenum]
+ joined_line = ''
+
+ starting_func = False
+ regexp = r'(\w(\w|::|\*|\&|\s)*)\(' # decls * & space::name( ...
+ match_result = Match(regexp, line)
+ if match_result:
+ # If the name is all caps and underscores, figure it's a macro and
+ # ignore it, unless it's TEST or TEST_F.
+ function_name = match_result.group(1).split()[-1]
+ if function_name == 'TEST' or function_name == 'TEST_F' or (
+ not Match(r'[A-Z_]+$', function_name)):
+ starting_func = True
+
+ if starting_func:
+ body_found = False
+ for start_linenum in xrange(linenum, clean_lines.NumLines()):
+ start_line = lines[start_linenum]
+ joined_line += ' ' + start_line.lstrip()
+ if Search(r'(;|})', start_line): # Declarations and trivial functions
+ body_found = True
+ break # ... ignore
+ elif Search(r'{', start_line):
+ body_found = True
+ function = Search(r'((\w|:)*)\(', line).group(1)
+ if Match(r'TEST', function): # Handle TEST... macros
+ parameter_regexp = Search(r'(\(.*\))', joined_line)
+ if parameter_regexp: # Ignore bad syntax
+ function += parameter_regexp.group(1)
+ else:
+ function += '()'
+ function_state.Begin(function)
+ break
+ if not body_found:
+ # No body for the function (or evidence of a non-function) was found.
+ error(filename, linenum, 'readability/fn_size', 5,
+ 'Lint failed to find start of function body.')
+ elif Match(r'^\}\s*$', line): # function end
+ function_state.Check(error, filename, linenum)
+ function_state.End()
+ elif not Match(r'^\s*$', line):
+ function_state.Count() # Count non-blank/non-comment lines.
+
+
+_RE_PATTERN_TODO = re.compile(r'^//(\s*)TODO(\(.+?\))?:?(\s|$)?')
+
+
+def CheckComment(line, filename, linenum, next_line_start, error):
+ """Checks for common mistakes in comments.
+
+ Args:
+ line: The line in question.
+ filename: The name of the current file.
+ linenum: The number of the line to check.
+ next_line_start: The first non-whitespace column of the next line.
+ error: The function to call with any errors found.
+ """
+ commentpos = line.find('//')
+ if commentpos != -1:
+ # Check if the // may be in quotes. If so, ignore it
+ if re.sub(r'\\.', '', line[0:commentpos]).count('"') % 2 == 0:
+ # Allow one space for new scopes, two spaces otherwise:
+ if (not (Match(r'^.*{ *//', line) and next_line_start == commentpos) and
+ ((commentpos >= 1 and
+ line[commentpos-1] not in string.whitespace) or
+ (commentpos >= 2 and
+ line[commentpos-2] not in string.whitespace))):
+ error(filename, linenum, 'whitespace/comments', 2,
+ 'At least two spaces is best between code and comments')
+
+ # Checks for common mistakes in TODO comments.
+ comment = line[commentpos:]
+ match = _RE_PATTERN_TODO.match(comment)
+ if match:
+ # One whitespace is correct; zero whitespace is handled elsewhere.
+ leading_whitespace = match.group(1)
+ if len(leading_whitespace) > 1:
+ error(filename, linenum, 'whitespace/todo', 2,
+ 'Too many spaces before TODO')
+
+ username = match.group(2)
+ if not username:
+ error(filename, linenum, 'readability/todo', 2,
+ 'Missing username in TODO; it should look like '
+ '"// TODO(my_username): Stuff."')
+
+ middle_whitespace = match.group(3)
+ # Comparisons made explicit for correctness -- pylint: disable=g-explicit-bool-comparison
+ if middle_whitespace != ' ' and middle_whitespace != '':
+ error(filename, linenum, 'whitespace/todo', 2,
+ 'TODO(my_username) should be followed by a space')
+
+ # If the comment contains an alphanumeric character, there
+ # should be a space somewhere between it and the // unless
+ # it's a /// or //! Doxygen comment.
+ if (Match(r'//[^ ]*\w', comment) and
+ not Match(r'(///|//\!)(\s+|$)', comment)):
+ error(filename, linenum, 'whitespace/comments', 4,
+ 'Should have a space between // and comment')
+
+
+def CheckSpacing(filename, clean_lines, linenum, nesting_state, error):
+ """Checks for the correctness of various spacing issues in the code.
+
+ Things we check for: spaces around operators, spaces after
+ if/for/while/switch, no spaces around parens in function calls, two
+ spaces between code and comment, don't start a block with a blank
+ line, don't end a function with a blank line, don't add a blank line
+ after public/protected/private, don't have too many blank lines in a row.
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ nesting_state: A NestingState instance which maintains information about
+ the current stack of nested blocks being parsed.
+ error: The function to call with any errors found.
+ """
+
+ # Don't use "elided" lines here, otherwise we can't check commented lines.
+ # Don't want to use "raw" either, because we don't want to check inside C++11
+ # raw strings,
+ raw = clean_lines.lines_without_raw_strings
+ line = raw[linenum]
+
+ # Before nixing comments, check if the line is blank for no good
+ # reason. This includes the first line after a block is opened, and
+ # blank lines at the end of a function (ie, right before a line like '}'
+ #
+ # Skip all the blank line checks if we are immediately inside a
+ # namespace body. In other words, don't issue blank line warnings
+ # for this block:
+ # namespace {
+ #
+ # }
+ #
+ # A warning about missing end of namespace comments will be issued instead.
+ #
+ # Also skip blank line checks for 'extern "C"' blocks, which are formatted
+ # like namespaces.
+ if (IsBlankLine(line) and
+ not nesting_state.InNamespaceBody() and
+ not nesting_state.InExternC()):
+ elided = clean_lines.elided
+ prev_line = elided[linenum - 1]
+ prevbrace = prev_line.rfind('{')
+ # TODO(unknown): Don't complain if line before blank line, and line after,
+ # both start with alnums and are indented the same amount.
+ # This ignores whitespace at the start of a namespace block
+ # because those are not usually indented.
+ if prevbrace != -1 and prev_line[prevbrace:].find('}') == -1:
+ # OK, we have a blank line at the start of a code block. Before we
+ # complain, we check if it is an exception to the rule: The previous
+ # non-empty line has the parameters of a function header that are indented
+ # 4 spaces (because they did not fit in a 80 column line when placed on
+ # the same line as the function name). We also check for the case where
+ # the previous line is indented 6 spaces, which may happen when the
+ # initializers of a constructor do not fit into a 80 column line.
+ exception = False
+ if Match(r' {6}\w', prev_line): # Initializer list?
+ # We are looking for the opening column of initializer list, which
+ # should be indented 4 spaces to cause 6 space indentation afterwards.
+ search_position = linenum-2
+ while (search_position >= 0
+ and Match(r' {6}\w', elided[search_position])):
+ search_position -= 1
+ exception = (search_position >= 0
+ and elided[search_position][:5] == ' :')
+ else:
+ # Search for the function arguments or an initializer list. We use a
+ # simple heuristic here: If the line is indented 4 spaces; and we have a
+ # closing paren, without the opening paren, followed by an opening brace
+ # or colon (for initializer lists) we assume that it is the last line of
+ # a function header. If we have a colon indented 4 spaces, it is an
+ # initializer list.
+ exception = (Match(r' {4}\w[^\(]*\)\s*(const\s*)?(\{\s*$|:)',
+ prev_line)
+ or Match(r' {4}:', prev_line))
+
+ if not exception:
+ error(filename, linenum, 'whitespace/blank_line', 2,
+ 'Redundant blank line at the start of a code block '
+ 'should be deleted.')
+ # Ignore blank lines at the end of a block in a long if-else
+ # chain, like this:
+ # if (condition1) {
+ # // Something followed by a blank line
+ #
+ # } else if (condition2) {
+ # // Something else
+ # }
+ if linenum + 1 < clean_lines.NumLines():
+ next_line = raw[linenum + 1]
+ if (next_line
+ and Match(r'\s*}', next_line)
+ and next_line.find('} else ') == -1):
+ error(filename, linenum, 'whitespace/blank_line', 3,
+ 'Redundant blank line at the end of a code block '
+ 'should be deleted.')
+
+ matched = Match(r'\s*(public|protected|private):', prev_line)
+ if matched:
+ error(filename, linenum, 'whitespace/blank_line', 3,
+ 'Do not leave a blank line after "%s:"' % matched.group(1))
+
+ # Next, check comments
+ next_line_start = 0
+ if linenum + 1 < clean_lines.NumLines():
+ next_line = raw[linenum + 1]
+ next_line_start = len(next_line) - len(next_line.lstrip())
+ CheckComment(line, filename, linenum, next_line_start, error)
+
+ # get rid of comments and strings
+ line = clean_lines.elided[linenum]
+
+ # You shouldn't have spaces before your brackets, except maybe after
+ # 'delete []' or 'return []() {};'
+ if Search(r'\w\s+\[', line) and not Search(r'(?:delete|return)\s+\[', line):
+ error(filename, linenum, 'whitespace/braces', 5,
+ 'Extra space before [')
+
+ # In range-based for, we wanted spaces before and after the colon, but
+ # not around "::" tokens that might appear.
+ if (Search(r'for *\(.*[^:]:[^: ]', line) or
+ Search(r'for *\(.*[^: ]:[^:]', line)):
+ error(filename, linenum, 'whitespace/forcolon', 2,
+ 'Missing space around colon in range-based for loop')
+
+
+def CheckOperatorSpacing(filename, clean_lines, linenum, error):
+ """Checks for horizontal spacing around operators.
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ error: The function to call with any errors found.
+ """
+ line = clean_lines.elided[linenum]
+
+ # Don't try to do spacing checks for operator methods. Do this by
+ # replacing the troublesome characters with something else,
+ # preserving column position for all other characters.
+ #
+ # The replacement is done repeatedly to avoid false positives from
+ # operators that call operators.
+ while True:
+ match = Match(r'^(.*\boperator\b)(\S+)(\s*\(.*)$', line)
+ if match:
+ line = match.group(1) + ('_' * len(match.group(2))) + match.group(3)
+ else:
+ break
+
+ # We allow no-spaces around = within an if: "if ( (a=Foo()) == 0 )".
+ # Otherwise not. Note we only check for non-spaces on *both* sides;
+ # sometimes people put non-spaces on one side when aligning ='s among
+ # many lines (not that this is behavior that I approve of...)
+ if ((Search(r'[\w.]=', line) or
+ Search(r'=[\w.]', line))
+ and not Search(r'\b(if|while|for) ', line)
+ # Operators taken from [lex.operators] in C++11 standard.
+ and not Search(r'(>=|<=|==|!=|&=|\^=|\|=|\+=|\*=|\/=|\%=)', line)
+ and not Search(r'operator=', line)):
+ error(filename, linenum, 'whitespace/operators', 4,
+ 'Missing spaces around =')
+
+ # It's ok not to have spaces around binary operators like + - * /, but if
+ # there's too little whitespace, we get concerned. It's hard to tell,
+ # though, so we punt on this one for now. TODO.
+
+ # You should always have whitespace around binary operators.
+ #
+ # Check <= and >= first to avoid false positives with < and >, then
+ # check non-include lines for spacing around < and >.
+ #
+ # If the operator is followed by a comma, assume it's be used in a
+ # macro context and don't do any checks. This avoids false
+ # positives.
+ #
+ # Note that && is not included here. This is because there are too
+ # many false positives due to RValue references.
+ match = Search(r'[^<>=!\s](==|!=|<=|>=|\|\|)[^<>=!\s,;\)]', line)
+ if match:
+ error(filename, linenum, 'whitespace/operators', 3,
+ 'Missing spaces around %s' % match.group(1))
+ elif not Match(r'#.*include', line):
+ # Look for < that is not surrounded by spaces. This is only
+ # triggered if both sides are missing spaces, even though
+ # technically should should flag if at least one side is missing a
+ # space. This is done to avoid some false positives with shifts.
+ match = Match(r'^(.*[^\s<])<[^\s=<,]', line)
+ if match:
+ (_, _, end_pos) = CloseExpression(
+ clean_lines, linenum, len(match.group(1)))
+ if end_pos <= -1:
+ error(filename, linenum, 'whitespace/operators', 3,
+ 'Missing spaces around <')
+
+ # Look for > that is not surrounded by spaces. Similar to the
+ # above, we only trigger if both sides are missing spaces to avoid
+ # false positives with shifts.
+ match = Match(r'^(.*[^-\s>])>[^\s=>,]', line)
+ if match:
+ (_, _, start_pos) = ReverseCloseExpression(
+ clean_lines, linenum, len(match.group(1)))
+ if start_pos <= -1:
+ error(filename, linenum, 'whitespace/operators', 3,
+ 'Missing spaces around >')
+
+ # We allow no-spaces around << when used like this: 10<<20, but
+ # not otherwise (particularly, not when used as streams)
+ #
+ # We also allow operators following an opening parenthesis, since
+ # those tend to be macros that deal with operators.
+ match = Search(r'(operator|[^\s(<])(?:L|UL|LL|ULL|l|ul|ll|ull)?<<([^\s,=<])', line)
+ if (match and not (match.group(1).isdigit() and match.group(2).isdigit()) and
+ not (match.group(1) == 'operator' and match.group(2) == ';')):
+ error(filename, linenum, 'whitespace/operators', 3,
+ 'Missing spaces around <<')
+
+ # We allow no-spaces around >> for almost anything. This is because
+ # C++11 allows ">>" to close nested templates, which accounts for
+ # most cases when ">>" is not followed by a space.
+ #
+ # We still warn on ">>" followed by alpha character, because that is
+ # likely due to ">>" being used for right shifts, e.g.:
+ # value >> alpha
+ #
+ # When ">>" is used to close templates, the alphanumeric letter that
+ # follows would be part of an identifier, and there should still be
+ # a space separating the template type and the identifier.
+ # type<type<type>> alpha
+ match = Search(r'>>[a-zA-Z_]', line)
+ if match:
+ error(filename, linenum, 'whitespace/operators', 3,
+ 'Missing spaces around >>')
+
+ # There shouldn't be space around unary operators
+ match = Search(r'(!\s|~\s|[\s]--[\s;]|[\s]\+\+[\s;])', line)
+ if match:
+ error(filename, linenum, 'whitespace/operators', 4,
+ 'Extra space for operator %s' % match.group(1))
+
+
+def CheckParenthesisSpacing(filename, clean_lines, linenum, error):
+ """Checks for horizontal spacing around parentheses.
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ error: The function to call with any errors found.
+ """
+ line = clean_lines.elided[linenum]
+
+ # No spaces after an if, while, switch, or for
+ match = Search(r' (if\(|for\(|while\(|switch\()', line)
+ if match:
+ error(filename, linenum, 'whitespace/parens', 5,
+ 'Missing space before ( in %s' % match.group(1))
+
+ # For if/for/while/switch, the left and right parens should be
+ # consistent about how many spaces are inside the parens, and
+ # there should either be zero or one spaces inside the parens.
+ # We don't want: "if ( foo)" or "if ( foo )".
+ # Exception: "for ( ; foo; bar)" and "for (foo; bar; )" are allowed.
+ match = Search(r'\b(if|for|while|switch)\s*'
+ r'\(([ ]*)(.).*[^ ]+([ ]*)\)\s*{\s*$',
+ line)
+ if match:
+ if len(match.group(2)) != len(match.group(4)):
+ if not (match.group(3) == ';' and
+ len(match.group(2)) == 1 + len(match.group(4)) or
+ not match.group(2) and Search(r'\bfor\s*\(.*; \)', line)):
+ error(filename, linenum, 'whitespace/parens', 5,
+ 'Mismatching spaces inside () in %s' % match.group(1))
+ if len(match.group(2)) not in [0, 1]:
+ error(filename, linenum, 'whitespace/parens', 5,
+ 'Should have zero or one spaces inside ( and ) in %s' %
+ match.group(1))
+
+
+def CheckCommaSpacing(filename, clean_lines, linenum, error):
+ """Checks for horizontal spacing near commas and semicolons.
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ error: The function to call with any errors found.
+ """
+ raw = clean_lines.lines_without_raw_strings
+ line = clean_lines.elided[linenum]
+
+ # You should always have a space after a comma (either as fn arg or operator)
+ #
+ # This does not apply when the non-space character following the
+ # comma is another comma, since the only time when that happens is
+ # for empty macro arguments.
+ #
+ # We run this check in two passes: first pass on elided lines to
+ # verify that lines contain missing whitespaces, second pass on raw
+ # lines to confirm that those missing whitespaces are not due to
+ # elided comments.
+ if (Search(r',[^,\s]', ReplaceAll(r'\boperator\s*,\s*\(', 'F(', line)) and
+ Search(r',[^,\s]', raw[linenum])):
+ error(filename, linenum, 'whitespace/comma', 3,
+ 'Missing space after ,')
+
+ # You should always have a space after a semicolon
+ # except for few corner cases
+ # TODO(unknown): clarify if 'if (1) { return 1;}' is requires one more
+ # space after ;
+ if Search(r';[^\s};\\)/]', line):
+ error(filename, linenum, 'whitespace/semicolon', 3,
+ 'Missing space after ;')
+
+
+def _IsType(clean_lines, nesting_state, expr):
+ """Check if expression looks like a type name, returns true if so.
+
+ Args:
+ clean_lines: A CleansedLines instance containing the file.
+ nesting_state: A NestingState instance which maintains information about
+ the current stack of nested blocks being parsed.
+ expr: The expression to check.
+ Returns:
+ True, if token looks like a type.
+ """
+ # Keep only the last token in the expression
+ last_word = Match(r'^.*(\b\S+)$', expr)
+ if last_word:
+ token = last_word.group(1)
+ else:
+ token = expr
+
+ # Match native types and stdint types
+ if _TYPES.match(token):
+ return True
+
+ # Try a bit harder to match templated types. Walk up the nesting
+ # stack until we find something that resembles a typename
+ # declaration for what we are looking for.
+ typename_pattern = (r'\b(?:typename|class|struct)\s+' + re.escape(token) +
+ r'\b')
+ block_index = len(nesting_state.stack) - 1
+ while block_index >= 0:
+ if isinstance(nesting_state.stack[block_index], _NamespaceInfo):
+ return False
+
+ # Found where the opening brace is. We want to scan from this
+ # line up to the beginning of the function, minus a few lines.
+ # template <typename Type1, // stop scanning here
+ # ...>
+ # class C
+ # : public ... { // start scanning here
+ last_line = nesting_state.stack[block_index].starting_linenum
+
+ next_block_start = 0
+ if block_index > 0:
+ next_block_start = nesting_state.stack[block_index - 1].starting_linenum
+ first_line = last_line
+ while first_line >= next_block_start:
+ if clean_lines.elided[first_line].find('template') >= 0:
+ break
+ first_line -= 1
+ if first_line < next_block_start:
+ # Didn't find any "template" keyword before reaching the next block,
+ # there are probably no template things to check for this block
+ block_index -= 1
+ continue
+
+ # Look for typename in the specified range
+ for i in xrange(first_line, last_line + 1, 1):
+ if Search(typename_pattern, clean_lines.elided[i]):
+ return True
+ block_index -= 1
+
+ return False
+
+
+def CheckBracesSpacing(filename, clean_lines, linenum, nesting_state, error):
+ """Checks for horizontal spacing near commas.
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ nesting_state: A NestingState instance which maintains information about
+ the current stack of nested blocks being parsed.
+ error: The function to call with any errors found.
+ """
+ line = clean_lines.elided[linenum]
+
+ # Except after an opening paren, or after another opening brace (in case of
+ # an initializer list, for instance), you should have spaces before your
+ # braces when they are delimiting blocks, classes, namespaces etc.
+ # And since you should never have braces at the beginning of a line,
+ # this is an easy test. Except that braces used for initialization don't
+ # follow the same rule; we often don't want spaces before those.
+ match = Match(r'^(.*[^ ({>]){', line)
+
+ if match:
+ # Try a bit harder to check for brace initialization. This
+ # happens in one of the following forms:
+ # Constructor() : initializer_list_{} { ... }
+ # Constructor{}.MemberFunction()
+ # Type variable{};
+ # FunctionCall(type{}, ...);
+ # LastArgument(..., type{});
+ # LOG(INFO) << type{} << " ...";
+ # map_of_type[{...}] = ...;
+ # ternary = expr ? new type{} : nullptr;
+ # OuterTemplate<InnerTemplateConstructor<Type>{}>
+ #
+ # We check for the character following the closing brace, and
+ # silence the warning if it's one of those listed above, i.e.
+ # "{.;,)<>]:".
+ #
+ # To account for nested initializer list, we allow any number of
+ # closing braces up to "{;,)<". We can't simply silence the
+ # warning on first sight of closing brace, because that would
+ # cause false negatives for things that are not initializer lists.
+ # Silence this: But not this:
+ # Outer{ if (...) {
+ # Inner{...} if (...){ // Missing space before {
+ # }; }
+ #
+ # There is a false negative with this approach if people inserted
+ # spurious semicolons, e.g. "if (cond){};", but we will catch the
+ # spurious semicolon with a separate check.
+ leading_text = match.group(1)
+ (endline, endlinenum, endpos) = CloseExpression(
+ clean_lines, linenum, len(match.group(1)))
+ trailing_text = ''
+ if endpos > -1:
+ trailing_text = endline[endpos:]
+ for offset in xrange(endlinenum + 1,
+ min(endlinenum + 3, clean_lines.NumLines() - 1)):
+ trailing_text += clean_lines.elided[offset]
+ # We also suppress warnings for `uint64_t{expression}` etc., as the style
+ # guide recommends brace initialization for integral types to avoid
+ # overflow/truncation.
+ if (not Match(r'^[\s}]*[{.;,)<>\]:]', trailing_text)
+ and not _IsType(clean_lines, nesting_state, leading_text)):
+ error(filename, linenum, 'whitespace/braces', 5,
+ 'Missing space before {')
+
+ # Make sure '} else {' has spaces.
+ if Search(r'}else', line):
+ error(filename, linenum, 'whitespace/braces', 5,
+ 'Missing space before else')
+
+ # You shouldn't have a space before a semicolon at the end of the line.
+ # There's a special case for "for" since the style guide allows space before
+ # the semicolon there.
+ if Search(r':\s*;\s*$', line):
+ error(filename, linenum, 'whitespace/semicolon', 5,
+ 'Semicolon defining empty statement. Use {} instead.')
+ elif Search(r'^\s*;\s*$', line):
+ error(filename, linenum, 'whitespace/semicolon', 5,
+ 'Line contains only semicolon. If this should be an empty statement, '
+ 'use {} instead.')
+ elif (Search(r'\s+;\s*$', line) and
+ not Search(r'\bfor\b', line)):
+ error(filename, linenum, 'whitespace/semicolon', 5,
+ 'Extra space before last semicolon. If this should be an empty '
+ 'statement, use {} instead.')
+
+
+def IsDecltype(clean_lines, linenum, column):
+ """Check if the token ending on (linenum, column) is decltype().
+
+ Args:
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: the number of the line to check.
+ column: end column of the token to check.
+ Returns:
+ True if this token is decltype() expression, False otherwise.
+ """
+ (text, _, start_col) = ReverseCloseExpression(clean_lines, linenum, column)
+ if start_col < 0:
+ return False
+ if Search(r'\bdecltype\s*$', text[0:start_col]):
+ return True
+ return False
+
+
+def CheckSectionSpacing(filename, clean_lines, class_info, linenum, error):
+ """Checks for additional blank line issues related to sections.
+
+ Currently the only thing checked here is blank line before protected/private.
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ class_info: A _ClassInfo objects.
+ linenum: The number of the line to check.
+ error: The function to call with any errors found.
+ """
+ # Skip checks if the class is small, where small means 25 lines or less.
+ # 25 lines seems like a good cutoff since that's the usual height of
+ # terminals, and any class that can't fit in one screen can't really
+ # be considered "small".
+ #
+ # Also skip checks if we are on the first line. This accounts for
+ # classes that look like
+ # class Foo { public: ... };
+ #
+ # If we didn't find the end of the class, last_line would be zero,
+ # and the check will be skipped by the first condition.
+ if (class_info.last_line - class_info.starting_linenum <= 24 or
+ linenum <= class_info.starting_linenum):
+ return
+
+ matched = Match(r'\s*(public|protected|private):', clean_lines.lines[linenum])
+ if matched:
+ # Issue warning if the line before public/protected/private was
+ # not a blank line, but don't do this if the previous line contains
+ # "class" or "struct". This can happen two ways:
+ # - We are at the beginning of the class.
+ # - We are forward-declaring an inner class that is semantically
+ # private, but needed to be public for implementation reasons.
+ # Also ignores cases where the previous line ends with a backslash as can be
+ # common when defining classes in C macros.
+ prev_line = clean_lines.lines[linenum - 1]
+ if (not IsBlankLine(prev_line) and
+ not Search(r'\b(class|struct)\b', prev_line) and
+ not Search(r'\\$', prev_line)):
+ # Try a bit harder to find the beginning of the class. This is to
+ # account for multi-line base-specifier lists, e.g.:
+ # class Derived
+ # : public Base {
+ end_class_head = class_info.starting_linenum
+ for i in range(class_info.starting_linenum, linenum):
+ if Search(r'\{\s*$', clean_lines.lines[i]):
+ end_class_head = i
+ break
+ if end_class_head < linenum - 1:
+ error(filename, linenum, 'whitespace/blank_line', 3,
+ '"%s:" should be preceded by a blank line' % matched.group(1))
+
+
+def GetPreviousNonBlankLine(clean_lines, linenum):
+ """Return the most recent non-blank line and its line number.
+
+ Args:
+ clean_lines: A CleansedLines instance containing the file contents.
+ linenum: The number of the line to check.
+
+ Returns:
+ A tuple with two elements. The first element is the contents of the last
+ non-blank line before the current line, or the empty string if this is the
+ first non-blank line. The second is the line number of that line, or -1
+ if this is the first non-blank line.
+ """
+
+ prevlinenum = linenum - 1
+ while prevlinenum >= 0:
+ prevline = clean_lines.elided[prevlinenum]
+ if not IsBlankLine(prevline): # if not a blank line...
+ return (prevline, prevlinenum)
+ prevlinenum -= 1
+ return ('', -1)
+
+
+def CheckBraces(filename, clean_lines, linenum, error):
+ """Looks for misplaced braces (e.g. at the end of line).
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ error: The function to call with any errors found.
+ """
+
+ line = clean_lines.elided[linenum] # get rid of comments and strings
+
+ if Match(r'\s*{\s*$', line):
+ # We allow an open brace to start a line in the case where someone is using
+ # braces in a block to explicitly create a new scope, which is commonly used
+ # to control the lifetime of stack-allocated variables. Braces are also
+ # used for brace initializers inside function calls. We don't detect this
+ # perfectly: we just don't complain if the last non-whitespace character on
+ # the previous non-blank line is ',', ';', ':', '(', '{', or '}', or if the
+ # previous line starts a preprocessor block. We also allow a brace on the
+ # following line if it is part of an array initialization and would not fit
+ # within the 80 character limit of the preceding line.
+ prevline = GetPreviousNonBlankLine(clean_lines, linenum)[0]
+ if (not Search(r'[,;:}{(]\s*$', prevline) and
+ not Match(r'\s*#', prevline) and
+ not (GetLineWidth(prevline) > _line_length - 2 and '[]' in prevline)):
+ error(filename, linenum, 'whitespace/braces', 4,
+ '{ should almost always be at the end of the previous line')
+
+ # An else clause should be on the same line as the preceding closing brace.
+ if Match(r'\s*else\b\s*(?:if\b|\{|$)', line):
+ prevline = GetPreviousNonBlankLine(clean_lines, linenum)[0]
+ if Match(r'\s*}\s*$', prevline):
+ error(filename, linenum, 'whitespace/newline', 4,
+ 'An else should appear on the same line as the preceding }')
+
+ # If braces come on one side of an else, they should be on both.
+ # However, we have to worry about "else if" that spans multiple lines!
+ if Search(r'else if\s*\(', line): # could be multi-line if
+ brace_on_left = bool(Search(r'}\s*else if\s*\(', line))
+ # find the ( after the if
+ pos = line.find('else if')
+ pos = line.find('(', pos)
+ if pos > 0:
+ (endline, _, endpos) = CloseExpression(clean_lines, linenum, pos)
+ brace_on_right = endline[endpos:].find('{') != -1
+ if brace_on_left != brace_on_right: # must be brace after if
+ error(filename, linenum, 'readability/braces', 5,
+ 'If an else has a brace on one side, it should have it on both')
+ elif Search(r'}\s*else[^{]*$', line) or Match(r'[^}]*else\s*{', line):
+ error(filename, linenum, 'readability/braces', 5,
+ 'If an else has a brace on one side, it should have it on both')
+
+ # Likewise, an else should never have the else clause on the same line
+ if Search(r'\belse [^\s{]', line) and not Search(r'\belse if\b', line):
+ error(filename, linenum, 'whitespace/newline', 4,
+ 'Else clause should never be on same line as else (use 2 lines)')
+
+ # In the same way, a do/while should never be on one line
+ if Match(r'\s*do [^\s{]', line):
+ error(filename, linenum, 'whitespace/newline', 4,
+ 'do/while clauses should not be on a single line')
+
+ # Check single-line if/else bodies. The style guide says 'curly braces are not
+ # required for single-line statements'. We additionally allow multi-line,
+ # single statements, but we reject anything with more than one semicolon in
+ # it. This means that the first semicolon after the if should be at the end of
+ # its line, and the line after that should have an indent level equal to or
+ # lower than the if. We also check for ambiguous if/else nesting without
+ # braces.
+ if_else_match = Search(r'\b(if\s*\(|else\b)', line)
+ if if_else_match and not Match(r'\s*#', line):
+ if_indent = GetIndentLevel(line)
+ endline, endlinenum, endpos = line, linenum, if_else_match.end()
+ if_match = Search(r'\bif\s*\(', line)
+ if if_match:
+ # This could be a multiline if condition, so find the end first.
+ pos = if_match.end() - 1
+ (endline, endlinenum, endpos) = CloseExpression(clean_lines, linenum, pos)
+ # Check for an opening brace, either directly after the if or on the next
+ # line. If found, this isn't a single-statement conditional.
+ if (not Match(r'\s*{', endline[endpos:])
+ and not (Match(r'\s*$', endline[endpos:])
+ and endlinenum < (len(clean_lines.elided) - 1)
+ and Match(r'\s*{', clean_lines.elided[endlinenum + 1]))):
+ while (endlinenum < len(clean_lines.elided)
+ and ';' not in clean_lines.elided[endlinenum][endpos:]):
+ endlinenum += 1
+ endpos = 0
+ if endlinenum < len(clean_lines.elided):
+ endline = clean_lines.elided[endlinenum]
+ # We allow a mix of whitespace and closing braces (e.g. for one-liner
+ # methods) and a single \ after the semicolon (for macros)
+ endpos = endline.find(';')
+ if not Match(r';[\s}]*(\\?)$', endline[endpos:]):
+ # Semicolon isn't the last character, there's something trailing.
+ # Output a warning if the semicolon is not contained inside
+ # a lambda expression.
+ if not Match(r'^[^{};]*\[[^\[\]]*\][^{}]*\{[^{}]*\}\s*\)*[;,]\s*$',
+ endline):
+ error(filename, linenum, 'readability/braces', 4,
+ 'If/else bodies with multiple statements require braces')
+ elif endlinenum < len(clean_lines.elided) - 1:
+ # Make sure the next line is dedented
+ next_line = clean_lines.elided[endlinenum + 1]
+ next_indent = GetIndentLevel(next_line)
+ # With ambiguous nested if statements, this will error out on the
+ # if that *doesn't* match the else, regardless of whether it's the
+ # inner one or outer one.
+ if (if_match and Match(r'\s*else\b', next_line)
+ and next_indent != if_indent):
+ error(filename, linenum, 'readability/braces', 4,
+ 'Else clause should be indented at the same level as if. '
+ 'Ambiguous nested if/else chains require braces.')
+ elif next_indent > if_indent:
+ error(filename, linenum, 'readability/braces', 4,
+ 'If/else bodies with multiple statements require braces')
+
+
+def CheckTrailingSemicolon(filename, clean_lines, linenum, error):
+ """Looks for redundant trailing semicolon.
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ error: The function to call with any errors found.
+ """
+
+ line = clean_lines.elided[linenum]
+
+ # Block bodies should not be followed by a semicolon. Due to C++11
+ # brace initialization, there are more places where semicolons are
+ # required than not, so we use a whitelist approach to check these
+ # rather than a blacklist. These are the places where "};" should
+ # be replaced by just "}":
+ # 1. Some flavor of block following closing parenthesis:
+ # for (;;) {};
+ # while (...) {};
+ # switch (...) {};
+ # Function(...) {};
+ # if (...) {};
+ # if (...) else if (...) {};
+ #
+ # 2. else block:
+ # if (...) else {};
+ #
+ # 3. const member function:
+ # Function(...) const {};
+ #
+ # 4. Block following some statement:
+ # x = 42;
+ # {};
+ #
+ # 5. Block at the beginning of a function:
+ # Function(...) {
+ # {};
+ # }
+ #
+ # Note that naively checking for the preceding "{" will also match
+ # braces inside multi-dimensional arrays, but this is fine since
+ # that expression will not contain semicolons.
+ #
+ # 6. Block following another block:
+ # while (true) {}
+ # {};
+ #
+ # 7. End of namespaces:
+ # namespace {};
+ #
+ # These semicolons seems far more common than other kinds of
+ # redundant semicolons, possibly due to people converting classes
+ # to namespaces. For now we do not warn for this case.
+ #
+ # Try matching case 1 first.
+ match = Match(r'^(.*\)\s*)\{', line)
+ if match:
+ # Matched closing parenthesis (case 1). Check the token before the
+ # matching opening parenthesis, and don't warn if it looks like a
+ # macro. This avoids these false positives:
+ # - macro that defines a base class
+ # - multi-line macro that defines a base class
+ # - macro that defines the whole class-head
+ #
+ # But we still issue warnings for macros that we know are safe to
+ # warn, specifically:
+ # - TEST, TEST_F, TEST_P, MATCHER, MATCHER_P
+ # - TYPED_TEST
+ # - INTERFACE_DEF
+ # - EXCLUSIVE_LOCKS_REQUIRED, SHARED_LOCKS_REQUIRED, LOCKS_EXCLUDED:
+ #
+ # We implement a whitelist of safe macros instead of a blacklist of
+ # unsafe macros, even though the latter appears less frequently in
+ # google code and would have been easier to implement. This is because
+ # the downside for getting the whitelist wrong means some extra
+ # semicolons, while the downside for getting the blacklist wrong
+ # would result in compile errors.
+ #
+ # In addition to macros, we also don't want to warn on
+ # - Compound literals
+ # - Lambdas
+ # - alignas specifier with anonymous structs
+ # - decltype
+ closing_brace_pos = match.group(1).rfind(')')
+ opening_parenthesis = ReverseCloseExpression(
+ clean_lines, linenum, closing_brace_pos)
+ if opening_parenthesis[2] > -1:
+ line_prefix = opening_parenthesis[0][0:opening_parenthesis[2]]
+ macro = Search(r'\b([A-Z_][A-Z0-9_]*)\s*$', line_prefix)
+ func = Match(r'^(.*\])\s*$', line_prefix)
+ if ((macro and
+ macro.group(1) not in (
+ 'TEST', 'TEST_F', 'MATCHER', 'MATCHER_P', 'TYPED_TEST',
+ 'EXCLUSIVE_LOCKS_REQUIRED', 'SHARED_LOCKS_REQUIRED',
+ 'LOCKS_EXCLUDED', 'INTERFACE_DEF')) or
+ (func and not Search(r'\boperator\s*\[\s*\]', func.group(1))) or
+ Search(r'\b(?:struct|union)\s+alignas\s*$', line_prefix) or
+ Search(r'\bdecltype$', line_prefix) or
+ Search(r'\s+=\s*$', line_prefix)):
+ match = None
+ if (match and
+ opening_parenthesis[1] > 1 and
+ Search(r'\]\s*$', clean_lines.elided[opening_parenthesis[1] - 1])):
+ # Multi-line lambda-expression
+ match = None
+
+ else:
+ # Try matching cases 2-3.
+ match = Match(r'^(.*(?:else|\)\s*const)\s*)\{', line)
+ if not match:
+ # Try matching cases 4-6. These are always matched on separate lines.
+ #
+ # Note that we can't simply concatenate the previous line to the
+ # current line and do a single match, otherwise we may output
+ # duplicate warnings for the blank line case:
+ # if (cond) {
+ # // blank line
+ # }
+ prevline = GetPreviousNonBlankLine(clean_lines, linenum)[0]
+ if prevline and Search(r'[;{}]\s*$', prevline):
+ match = Match(r'^(\s*)\{', line)
+
+ # Check matching closing brace
+ if match:
+ (endline, endlinenum, endpos) = CloseExpression(
+ clean_lines, linenum, len(match.group(1)))
+ if endpos > -1 and Match(r'^\s*;', endline[endpos:]):
+ # Current {} pair is eligible for semicolon check, and we have found
+ # the redundant semicolon, output warning here.
+ #
+ # Note: because we are scanning forward for opening braces, and
+ # outputting warnings for the matching closing brace, if there are
+ # nested blocks with trailing semicolons, we will get the error
+ # messages in reversed order.
+
+ # We need to check the line forward for NOLINT
+ raw_lines = clean_lines.raw_lines
+ ParseNolintSuppressions(filename, raw_lines[endlinenum-1], endlinenum-1,
+ error)
+ ParseNolintSuppressions(filename, raw_lines[endlinenum], endlinenum,
+ error)
+
+ error(filename, endlinenum, 'readability/braces', 4,
+ "You don't need a ; after a }")
+
+
+def CheckEmptyBlockBody(filename, clean_lines, linenum, error):
+ """Look for empty loop/conditional body with only a single semicolon.
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ error: The function to call with any errors found.
+ """
+
+ # Search for loop keywords at the beginning of the line. Because only
+ # whitespaces are allowed before the keywords, this will also ignore most
+ # do-while-loops, since those lines should start with closing brace.
+ #
+ # We also check "if" blocks here, since an empty conditional block
+ # is likely an error.
+ line = clean_lines.elided[linenum]
+ matched = Match(r'\s*(for|while|if)\s*\(', line)
+ if matched:
+ # Find the end of the conditional expression.
+ (end_line, end_linenum, end_pos) = CloseExpression(
+ clean_lines, linenum, line.find('('))
+
+ # Output warning if what follows the condition expression is a semicolon.
+ # No warning for all other cases, including whitespace or newline, since we
+ # have a separate check for semicolons preceded by whitespace.
+ if end_pos >= 0 and Match(r';', end_line[end_pos:]):
+ if matched.group(1) == 'if':
+ error(filename, end_linenum, 'whitespace/empty_conditional_body', 5,
+ 'Empty conditional bodies should use {}')
+ else:
+ error(filename, end_linenum, 'whitespace/empty_loop_body', 5,
+ 'Empty loop bodies should use {} or continue')
+
+ # Check for if statements that have completely empty bodies (no comments)
+ # and no else clauses.
+ if end_pos >= 0 and matched.group(1) == 'if':
+ # Find the position of the opening { for the if statement.
+ # Return without logging an error if it has no brackets.
+ opening_linenum = end_linenum
+ opening_line_fragment = end_line[end_pos:]
+ # Loop until EOF or find anything that's not whitespace or opening {.
+ while not Search(r'^\s*\{', opening_line_fragment):
+ if Search(r'^(?!\s*$)', opening_line_fragment):
+ # Conditional has no brackets.
+ return
+ opening_linenum += 1
+ if opening_linenum == len(clean_lines.elided):
+ # Couldn't find conditional's opening { or any code before EOF.
+ return
+ opening_line_fragment = clean_lines.elided[opening_linenum]
+ # Set opening_line (opening_line_fragment may not be entire opening line).
+ opening_line = clean_lines.elided[opening_linenum]
+
+ # Find the position of the closing }.
+ opening_pos = opening_line_fragment.find('{')
+ if opening_linenum == end_linenum:
+ # We need to make opening_pos relative to the start of the entire line.
+ opening_pos += end_pos
+ (closing_line, closing_linenum, closing_pos) = CloseExpression(
+ clean_lines, opening_linenum, opening_pos)
+ if closing_pos < 0:
+ return
+
+ # Now construct the body of the conditional. This consists of the portion
+ # of the opening line after the {, all lines until the closing line,
+ # and the portion of the closing line before the }.
+ if (clean_lines.raw_lines[opening_linenum] !=
+ CleanseComments(clean_lines.raw_lines[opening_linenum])):
+ # Opening line ends with a comment, so conditional isn't empty.
+ return
+ if closing_linenum > opening_linenum:
+ # Opening line after the {. Ignore comments here since we checked above.
+ body = list(opening_line[opening_pos+1:])
+ # All lines until closing line, excluding closing line, with comments.
+ body.extend(clean_lines.raw_lines[opening_linenum+1:closing_linenum])
+ # Closing line before the }. Won't (and can't) have comments.
+ body.append(clean_lines.elided[closing_linenum][:closing_pos-1])
+ body = '\n'.join(body)
+ else:
+ # If statement has brackets and fits on a single line.
+ body = opening_line[opening_pos+1:closing_pos-1]
+
+ # Check if the body is empty
+ if not _EMPTY_CONDITIONAL_BODY_PATTERN.search(body):
+ return
+ # The body is empty. Now make sure there's not an else clause.
+ current_linenum = closing_linenum
+ current_line_fragment = closing_line[closing_pos:]
+ # Loop until EOF or find anything that's not whitespace or else clause.
+ while Search(r'^\s*$|^(?=\s*else)', current_line_fragment):
+ if Search(r'^(?=\s*else)', current_line_fragment):
+ # Found an else clause, so don't log an error.
+ return
+ current_linenum += 1
+ if current_linenum == len(clean_lines.elided):
+ break
+ current_line_fragment = clean_lines.elided[current_linenum]
+
+ # The body is empty and there's no else clause until EOF or other code.
+ error(filename, end_linenum, 'whitespace/empty_if_body', 4,
+ ('If statement had no body and no else clause'))
+
+
+def FindCheckMacro(line):
+ """Find a replaceable CHECK-like macro.
+
+ Args:
+ line: line to search on.
+ Returns:
+ (macro name, start position), or (None, -1) if no replaceable
+ macro is found.
+ """
+ for macro in _CHECK_MACROS:
+ i = line.find(macro)
+ if i >= 0:
+ # Find opening parenthesis. Do a regular expression match here
+ # to make sure that we are matching the expected CHECK macro, as
+ # opposed to some other macro that happens to contain the CHECK
+ # substring.
+ matched = Match(r'^(.*\b' + macro + r'\s*)\(', line)
+ if not matched:
+ continue
+ return (macro, len(matched.group(1)))
+ return (None, -1)
+
+
+def CheckCheck(filename, clean_lines, linenum, error):
+ """Checks the use of CHECK and EXPECT macros.
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ error: The function to call with any errors found.
+ """
+
+ # Decide the set of replacement macros that should be suggested
+ lines = clean_lines.elided
+ (check_macro, start_pos) = FindCheckMacro(lines[linenum])
+ if not check_macro:
+ return
+
+ # Find end of the boolean expression by matching parentheses
+ (last_line, end_line, end_pos) = CloseExpression(
+ clean_lines, linenum, start_pos)
+ if end_pos < 0:
+ return
+
+ # If the check macro is followed by something other than a
+ # semicolon, assume users will log their own custom error messages
+ # and don't suggest any replacements.
+ if not Match(r'\s*;', last_line[end_pos:]):
+ return
+
+ if linenum == end_line:
+ expression = lines[linenum][start_pos + 1:end_pos - 1]
+ else:
+ expression = lines[linenum][start_pos + 1:]
+ for i in xrange(linenum + 1, end_line):
+ expression += lines[i]
+ expression += last_line[0:end_pos - 1]
+
+ # Parse expression so that we can take parentheses into account.
+ # This avoids false positives for inputs like "CHECK((a < 4) == b)",
+ # which is not replaceable by CHECK_LE.
+ lhs = ''
+ rhs = ''
+ operator = None
+ while expression:
+ matched = Match(r'^\s*(<<|<<=|>>|>>=|->\*|->|&&|\|\||'
+ r'==|!=|>=|>|<=|<|\()(.*)$', expression)
+ if matched:
+ token = matched.group(1)
+ if token == '(':
+ # Parenthesized operand
+ expression = matched.group(2)
+ (end, _) = FindEndOfExpressionInLine(expression, 0, ['('])
+ if end < 0:
+ return # Unmatched parenthesis
+ lhs += '(' + expression[0:end]
+ expression = expression[end:]
+ elif token in ('&&', '||'):
+ # Logical and/or operators. This means the expression
+ # contains more than one term, for example:
+ # CHECK(42 < a && a < b);
+ #
+ # These are not replaceable with CHECK_LE, so bail out early.
+ return
+ elif token in ('<<', '<<=', '>>', '>>=', '->*', '->'):
+ # Non-relational operator
+ lhs += token
+ expression = matched.group(2)
+ else:
+ # Relational operator
+ operator = token
+ rhs = matched.group(2)
+ break
+ else:
+ # Unparenthesized operand. Instead of appending to lhs one character
+ # at a time, we do another regular expression match to consume several
+ # characters at once if possible. Trivial benchmark shows that this
+ # is more efficient when the operands are longer than a single
+ # character, which is generally the case.
+ matched = Match(r'^([^-=!<>()&|]+)(.*)$', expression)
+ if not matched:
+ matched = Match(r'^(\s*\S)(.*)$', expression)
+ if not matched:
+ break
+ lhs += matched.group(1)
+ expression = matched.group(2)
+
+ # Only apply checks if we got all parts of the boolean expression
+ if not (lhs and operator and rhs):
+ return
+
+ # Check that rhs do not contain logical operators. We already know
+ # that lhs is fine since the loop above parses out && and ||.
+ if rhs.find('&&') > -1 or rhs.find('||') > -1:
+ return
+
+ # At least one of the operands must be a constant literal. This is
+ # to avoid suggesting replacements for unprintable things like
+ # CHECK(variable != iterator)
+ #
+ # The following pattern matches decimal, hex integers, strings, and
+ # characters (in that order).
+ lhs = lhs.strip()
+ rhs = rhs.strip()
+ match_constant = r'^([-+]?(\d+|0[xX][0-9a-fA-F]+)[lLuU]{0,3}|".*"|\'.*\')$'
+ if Match(match_constant, lhs) or Match(match_constant, rhs):
+ # Note: since we know both lhs and rhs, we can provide a more
+ # descriptive error message like:
+ # Consider using CHECK_EQ(x, 42) instead of CHECK(x == 42)
+ # Instead of:
+ # Consider using CHECK_EQ instead of CHECK(a == b)
+ #
+ # We are still keeping the less descriptive message because if lhs
+ # or rhs gets long, the error message might become unreadable.
+ error(filename, linenum, 'readability/check', 2,
+ 'Consider using %s instead of %s(a %s b)' % (
+ _CHECK_REPLACEMENT[check_macro][operator],
+ check_macro, operator))
+
+
+def CheckAltTokens(filename, clean_lines, linenum, error):
+ """Check alternative keywords being used in boolean expressions.
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ error: The function to call with any errors found.
+ """
+ line = clean_lines.elided[linenum]
+
+ # Avoid preprocessor lines
+ if Match(r'^\s*#', line):
+ return
+
+ # Last ditch effort to avoid multi-line comments. This will not help
+ # if the comment started before the current line or ended after the
+ # current line, but it catches most of the false positives. At least,
+ # it provides a way to workaround this warning for people who use
+ # multi-line comments in preprocessor macros.
+ #
+ # TODO(unknown): remove this once cpplint has better support for
+ # multi-line comments.
+ if line.find('/*') >= 0 or line.find('*/') >= 0:
+ return
+
+ for match in _ALT_TOKEN_REPLACEMENT_PATTERN.finditer(line):
+ error(filename, linenum, 'readability/alt_tokens', 2,
+ 'Use operator %s instead of %s' % (
+ _ALT_TOKEN_REPLACEMENT[match.group(1)], match.group(1)))
+
+
+def GetLineWidth(line):
+ """Determines the width of the line in column positions.
+
+ Args:
+ line: A string, which may be a Unicode string.
+
+ Returns:
+ The width of the line in column positions, accounting for Unicode
+ combining characters and wide characters.
+ """
+ if isinstance(line, unicode):
+ width = 0
+ for uc in unicodedata.normalize('NFC', line):
+ if unicodedata.east_asian_width(uc) in ('W', 'F'):
+ width += 2
+ elif not unicodedata.combining(uc):
+ width += 1
+ return width
+ else:
+ return len(line)
+
+
+def CheckStyle(filename, clean_lines, linenum, file_extension, nesting_state,
+ error):
+ """Checks rules from the 'C++ style rules' section of cppguide.html.
+
+ Most of these rules are hard to test (naming, comment style), but we
+ do what we can. In particular we check for 2-space indents, line lengths,
+ tab usage, spaces inside code, etc.
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ file_extension: The extension (without the dot) of the filename.
+ nesting_state: A NestingState instance which maintains information about
+ the current stack of nested blocks being parsed.
+ error: The function to call with any errors found.
+ """
+
+ # Don't use "elided" lines here, otherwise we can't check commented lines.
+ # Don't want to use "raw" either, because we don't want to check inside C++11
+ # raw strings,
+ raw_lines = clean_lines.lines_without_raw_strings
+ line = raw_lines[linenum]
+ prev = raw_lines[linenum - 1] if linenum > 0 else ''
+
+ if line.find('\t') != -1:
+ error(filename, linenum, 'whitespace/tab', 1,
+ 'Tab found; better to use spaces')
+
+ # One or three blank spaces at the beginning of the line is weird; it's
+ # hard to reconcile that with 2-space indents.
+ # NOTE: here are the conditions rob pike used for his tests. Mine aren't
+ # as sophisticated, but it may be worth becoming so: RLENGTH==initial_spaces
+ # if(RLENGTH > 20) complain = 0;
+ # if(match($0, " +(error|private|public|protected):")) complain = 0;
+ # if(match(prev, "&& *$")) complain = 0;
+ # if(match(prev, "\\|\\| *$")) complain = 0;
+ # if(match(prev, "[\",=><] *$")) complain = 0;
+ # if(match($0, " <<")) complain = 0;
+ # if(match(prev, " +for \\(")) complain = 0;
+ # if(prevodd && match(prevprev, " +for \\(")) complain = 0;
+ scope_or_label_pattern = r'\s*\w+\s*:\s*\\?$'
+ classinfo = nesting_state.InnermostClass()
+ initial_spaces = 0
+ cleansed_line = clean_lines.elided[linenum]
+ while initial_spaces < len(line) and line[initial_spaces] == ' ':
+ initial_spaces += 1
+ # There are certain situations we allow one space, notably for
+ # section labels, and also lines containing multi-line raw strings.
+ # We also don't check for lines that look like continuation lines
+ # (of lines ending in double quotes, commas, equals, or angle brackets)
+ # because the rules for how to indent those are non-trivial.
+ if (not Search(r'[",=><] *$', prev) and
+ (initial_spaces == 1 or initial_spaces == 3) and
+ not Match(scope_or_label_pattern, cleansed_line) and
+ not (clean_lines.raw_lines[linenum] != line and
+ Match(r'^\s*""', line))):
+ error(filename, linenum, 'whitespace/indent', 3,
+ 'Weird number of spaces at line-start. '
+ 'Are you using a 2-space indent?')
+
+ if line and line[-1].isspace():
+ error(filename, linenum, 'whitespace/end_of_line', 4,
+ 'Line ends in whitespace. Consider deleting these extra spaces.')
+
+ # Check if the line is a header guard.
+ is_header_guard = False
+ if IsHeaderExtension(file_extension):
+ cppvar = GetHeaderGuardCPPVariable(filename)
+ if (line.startswith('#ifndef %s' % cppvar) or
+ line.startswith('#define %s' % cppvar) or
+ line.startswith('#endif // %s' % cppvar)):
+ is_header_guard = True
+ # #include lines and header guards can be long, since there's no clean way to
+ # split them.
+ #
+ # URLs can be long too. It's possible to split these, but it makes them
+ # harder to cut&paste.
+ #
+ # The "$Id:...$" comment may also get very long without it being the
+ # developers fault.
+ if (not line.startswith('#include') and not is_header_guard and
+ not Match(r'^\s*//.*http(s?)://\S*$', line) and
+ not Match(r'^\s*//\s*[^\s]*$', line) and
+ not Match(r'^// \$Id:.*#[0-9]+ \$$', line)):
+ line_width = GetLineWidth(line)
+ if line_width > _line_length:
+ error(filename, linenum, 'whitespace/line_length', 2,
+ 'Lines should be <= %i characters long' % _line_length)
+
+ if (cleansed_line.count(';') > 1 and
+ # for loops are allowed two ;'s (and may run over two lines).
+ cleansed_line.find('for') == -1 and
+ (GetPreviousNonBlankLine(clean_lines, linenum)[0].find('for') == -1 or
+ GetPreviousNonBlankLine(clean_lines, linenum)[0].find(';') != -1) and
+ # It's ok to have many commands in a switch case that fits in 1 line
+ not ((cleansed_line.find('case ') != -1 or
+ cleansed_line.find('default:') != -1) and
+ cleansed_line.find('break;') != -1)):
+ error(filename, linenum, 'whitespace/newline', 0,
+ 'More than one command on the same line')
+
+ # Some more style checks
+ CheckBraces(filename, clean_lines, linenum, error)
+ CheckTrailingSemicolon(filename, clean_lines, linenum, error)
+ CheckEmptyBlockBody(filename, clean_lines, linenum, error)
+ CheckSpacing(filename, clean_lines, linenum, nesting_state, error)
+ CheckOperatorSpacing(filename, clean_lines, linenum, error)
+ CheckParenthesisSpacing(filename, clean_lines, linenum, error)
+ CheckCommaSpacing(filename, clean_lines, linenum, error)
+ CheckBracesSpacing(filename, clean_lines, linenum, nesting_state, error)
+ CheckSpacingForFunctionCall(filename, clean_lines, linenum, error)
+ CheckCheck(filename, clean_lines, linenum, error)
+ CheckAltTokens(filename, clean_lines, linenum, error)
+ classinfo = nesting_state.InnermostClass()
+ if classinfo:
+ CheckSectionSpacing(filename, clean_lines, classinfo, linenum, error)
+
+
+_RE_PATTERN_INCLUDE = re.compile(r'^\s*#\s*(?:include|import)\s*([<"])([^>"]*)[>"].*$')
+# Matches the first component of a filename delimited by -s and _s. That is:
+# _RE_FIRST_COMPONENT.match('foo').group(0) == 'foo'
+# _RE_FIRST_COMPONENT.match('foo.cc').group(0) == 'foo'
+# _RE_FIRST_COMPONENT.match('foo-bar_baz.cc').group(0) == 'foo'
+# _RE_FIRST_COMPONENT.match('foo_bar-baz.cc').group(0) == 'foo'
+_RE_FIRST_COMPONENT = re.compile(r'^[^-_.]+')
+
+
+def _DropCommonSuffixes(filename):
+ """Drops common suffixes like _test.cc or -inl.h from filename.
+
+ For example:
+ >>> _DropCommonSuffixes('foo/foo-inl.h')
+ 'foo/foo'
+ >>> _DropCommonSuffixes('foo/bar/foo.cc')
+ 'foo/bar/foo'
+ >>> _DropCommonSuffixes('foo/foo_internal.h')
+ 'foo/foo'
+ >>> _DropCommonSuffixes('foo/foo_unusualinternal.h')
+ 'foo/foo_unusualinternal'
+
+ Args:
+ filename: The input filename.
+
+ Returns:
+ The filename with the common suffix removed.
+ """
+ for suffix in ('test.cc', 'regtest.cc', 'unittest.cc',
+ 'inl.h', 'impl.h', 'internal.h'):
+ if (filename.endswith(suffix) and len(filename) > len(suffix) and
+ filename[-len(suffix) - 1] in ('-', '_')):
+ return filename[:-len(suffix) - 1]
+
+ for suffix in ['Tests.h', 'Test.m', 'Test.mm', 'Tests.m', 'Tests.mm']:
+ if (filename.endswith(suffix) and len(filename) > len(suffix)):
+ return filename[:-len(suffix)]
+
+ return os.path.splitext(filename)[0]
+
+
+def _ClassifyInclude(fileinfo, include, is_system):
+ """Figures out what kind of header 'include' is.
+
+ Args:
+ fileinfo: The current file cpplint is running over. A FileInfo instance.
+ include: The path to a #included file.
+ is_system: True if the #include used <> rather than "".
+
+ Returns:
+ One of the _XXX_HEADER constants.
+
+ For example:
+ >>> _ClassifyInclude(FileInfo('foo/foo.cc'), 'stdio.h', True)
+ _C_SYS_HEADER
+ >>> _ClassifyInclude(FileInfo('foo/foo.cc'), 'string', True)
+ _CPP_SYS_HEADER
+ >>> _ClassifyInclude(FileInfo('foo/foo.cc'), 'foo/foo.h', False)
+ _LIKELY_MY_HEADER
+ >>> _ClassifyInclude(FileInfo('foo/foo_unknown_extension.cc'),
+ ... 'bar/foo_other_ext.h', False)
+ _POSSIBLE_MY_HEADER
+ >>> _ClassifyInclude(FileInfo('foo/foo.cc'), 'foo/bar.h', False)
+ _OTHER_HEADER
+ """
+ # This is a list of all standard c++ header files, except
+ # those already checked for above.
+ is_cpp_h = include in _CPP_HEADERS
+
+ if is_system:
+ if is_cpp_h:
+ return _CPP_SYS_HEADER
+ else:
+ return _C_SYS_HEADER
+
+ # If the target file and the include we're checking share a
+ # basename when we drop common extensions, and the include
+ # lives in . , then it's likely to be owned by the target file.
+ target_dir, target_base = (
+ os.path.split(_DropCommonSuffixes(fileinfo.RepositoryName())))
+ include_dir, include_base = os.path.split(_DropCommonSuffixes(include))
+ if target_base == include_base and (
+ include_dir == target_dir or
+ include_dir == os.path.normpath(target_dir + '/../public')):
+ return _LIKELY_MY_HEADER
+
+ # If the target and include share some initial basename
+ # component, it's possible the target is implementing the
+ # include, so it's allowed to be first, but we'll never
+ # complain if it's not there.
+ target_first_component = _RE_FIRST_COMPONENT.match(target_base)
+ include_first_component = _RE_FIRST_COMPONENT.match(include_base)
+ if (target_first_component and include_first_component and
+ target_first_component.group(0) ==
+ include_first_component.group(0)):
+ return _POSSIBLE_MY_HEADER
+
+ return _OTHER_HEADER
+
+
+
+def CheckIncludeLine(filename, clean_lines, linenum, include_state, error):
+ """Check rules that are applicable to #include lines.
+
+ Strings on #include lines are NOT removed from elided line, to make
+ certain tasks easier. However, to prevent false positives, checks
+ applicable to #include lines in CheckLanguage must be put here.
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ include_state: An _IncludeState instance in which the headers are inserted.
+ error: The function to call with any errors found.
+ """
+ fileinfo = FileInfo(filename)
+ line = clean_lines.lines[linenum]
+
+ # system-style includes should not be used for project includes
+ match = Match(r'#include\s*<(([^/>]+)/[^>]+)', line)
+ if match:
+ if match.group(2) not in _C_SYSTEM_DIRECTORIES:
+ error(filename, linenum, 'build/include', 4,
+ '<%s> should be #include "%s" or #import <%s>' %
+ (match.group(1), match.group(1), match.group(1)))
+
+ # C++ system files should not be #imported
+ match = Match(r'#import\s*<([^/>.]+)>', line)
+ if match:
+ error(filename, linenum, 'build/include', 4,
+ 'C++ header <%s> was #imported. Should be #include <%s>' %
+ (match.group(1), match.group(1)))
+
+ # Prefer C++ wrappers for C headers
+ match = Match(r'#include\s*<(([^>]+).h)>', line)
+ if match:
+ wrapper = 'c' + match.group(2)
+ if wrapper in _CPP_HEADERS:
+ error(filename, linenum, 'build/include', 4,
+ 'Prefer C++ header <%s> for C system header %s' %
+ (wrapper, match.group(1)))
+
+ # "include" should use the new style "foo/bar.h" instead of just "bar.h"
+ # Only do this check if the included header follows google naming
+ # conventions. If not, assume that it's a 3rd party API that
+ # requires special include conventions.
+ #
+ # We also make an exception for Lua headers, which follow google
+ # naming convention but not the include convention.
+ match = Match(r'#include\s*"([^/]+\.h)"', line)
+ if match and not _THIRD_PARTY_HEADERS_PATTERN.match(match.group(1)):
+ error(filename, linenum, 'build/include', 4,
+ 'Include the directory when naming .h files')
+
+ # we shouldn't include a file more than once. actually, there are a
+ # handful of instances where doing so is okay, but in general it's
+ # not.
+ match = _RE_PATTERN_INCLUDE.search(line)
+ if match:
+ include = match.group(2)
+ is_system = (match.group(1) == '<')
+ duplicate_line = include_state.FindHeader(include)
+ if duplicate_line >= 0:
+ error(filename, linenum, 'build/include', 4,
+ '"%s" already included at %s:%s' %
+ (include, filename, duplicate_line))
+ elif (include.endswith('.cc') and
+ os.path.dirname(fileinfo.RepositoryName()) != os.path.dirname(include)):
+ error(filename, linenum, 'build/include', 4,
+ 'Do not include .cc files from other packages')
+ elif not _THIRD_PARTY_HEADERS_PATTERN.match(include):
+ include_state.include_list[-1].append((include, linenum))
+
+ # We want to ensure that headers appear in the right order:
+ # 1) for foo.cc, foo.h (preferred location)
+ # 2) c system files
+ # 3) cpp system files
+ # 4) for foo.cc, foo.h (deprecated location)
+ # 5) other google headers
+ #
+ # We classify each include statement as one of those 5 types
+ # using a number of techniques. The include_state object keeps
+ # track of the highest type seen, and complains if we see a
+ # lower type after that.
+ error_message = include_state.CheckNextIncludeOrder(
+ _ClassifyInclude(fileinfo, include, is_system))
+ if error_message:
+ error(filename, linenum, 'build/include_order', 4,
+ '%s. Should be: %s.h, c system, c++ system, other.' %
+ (error_message, fileinfo.BaseName()))
+ canonical_include = include_state.CanonicalizeAlphabeticalOrder(include)
+ if not include_state.IsInAlphabeticalOrder(
+ clean_lines, linenum, canonical_include):
+ error(filename, linenum, 'build/include_alpha', 4,
+ 'Include "%s" not in alphabetical order' % include)
+ include_state.SetLastHeader(canonical_include)
+
+
+
+def _GetTextInside(text, start_pattern):
+ r"""Retrieves all the text between matching open and close parentheses.
+
+ Given a string of lines and a regular expression string, retrieve all the text
+ following the expression and between opening punctuation symbols like
+ (, [, or {, and the matching close-punctuation symbol. This properly nested
+ occurrences of the punctuations, so for the text like
+ printf(a(), b(c()));
+ a call to _GetTextInside(text, r'printf\(') will return 'a(), b(c())'.
+ start_pattern must match string having an open punctuation symbol at the end.
+
+ Args:
+ text: The lines to extract text. Its comments and strings must be elided.
+ It can be single line and can span multiple lines.
+ start_pattern: The regexp string indicating where to start extracting
+ the text.
+ Returns:
+ The extracted text.
+ None if either the opening string or ending punctuation could not be found.
+ """
+ # TODO(unknown): Audit cpplint.py to see what places could be profitably
+ # rewritten to use _GetTextInside (and use inferior regexp matching today).
+
+ # Give opening punctuations to get the matching close-punctuations.
+ matching_punctuation = {'(': ')', '{': '}', '[': ']'}
+ closing_punctuation = set(matching_punctuation.itervalues())
+
+ # Find the position to start extracting text.
+ match = re.search(start_pattern, text, re.M)
+ if not match: # start_pattern not found in text.
+ return None
+ start_position = match.end(0)
+
+ assert start_position > 0, (
+ 'start_pattern must ends with an opening punctuation.')
+ assert text[start_position - 1] in matching_punctuation, (
+ 'start_pattern must ends with an opening punctuation.')
+ # Stack of closing punctuations we expect to have in text after position.
+ punctuation_stack = [matching_punctuation[text[start_position - 1]]]
+ position = start_position
+ while punctuation_stack and position < len(text):
+ if text[position] == punctuation_stack[-1]:
+ punctuation_stack.pop()
+ elif text[position] in closing_punctuation:
+ # A closing punctuation without matching opening punctuations.
+ return None
+ elif text[position] in matching_punctuation:
+ punctuation_stack.append(matching_punctuation[text[position]])
+ position += 1
+ if punctuation_stack:
+ # Opening punctuations left without matching close-punctuations.
+ return None
+ # punctuations match.
+ return text[start_position:position - 1]
+
+
+# Patterns for matching call-by-reference parameters.
+#
+# Supports nested templates up to 2 levels deep using this messy pattern:
+# < (?: < (?: < [^<>]*
+# >
+# | [^<>] )*
+# >
+# | [^<>] )*
+# >
+_RE_PATTERN_IDENT = r'[_a-zA-Z]\w*' # =~ [[:alpha:]][[:alnum:]]*
+_RE_PATTERN_TYPE = (
+ r'(?:const\s+)?(?:typename\s+|class\s+|struct\s+|union\s+|enum\s+)?'
+ r'(?:\w|'
+ r'\s*<(?:<(?:<[^<>]*>|[^<>])*>|[^<>])*>|'
+ r'::)+')
+# A call-by-reference parameter ends with '& identifier'.
+_RE_PATTERN_REF_PARAM = re.compile(
+ r'(' + _RE_PATTERN_TYPE + r'(?:\s*(?:\bconst\b|[*]))*\s*'
+ r'&\s*' + _RE_PATTERN_IDENT + r')\s*(?:=[^,()]+)?[,)]')
+# A call-by-const-reference parameter either ends with 'const& identifier'
+# or looks like 'const type& identifier' when 'type' is atomic.
+_RE_PATTERN_CONST_REF_PARAM = (
+ r'(?:.*\s*\bconst\s*&\s*' + _RE_PATTERN_IDENT +
+ r'|const\s+' + _RE_PATTERN_TYPE + r'\s*&\s*' + _RE_PATTERN_IDENT + r')')
+# Stream types.
+_RE_PATTERN_REF_STREAM_PARAM = (
+ r'(?:.*stream\s*&\s*' + _RE_PATTERN_IDENT + r')')
+
+
+def CheckLanguage(filename, clean_lines, linenum, file_extension,
+ include_state, nesting_state, error):
+ """Checks rules from the 'C++ language rules' section of cppguide.html.
+
+ Some of these rules are hard to test (function overloading, using
+ uint32 inappropriately), but we do the best we can.
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ file_extension: The extension (without the dot) of the filename.
+ include_state: An _IncludeState instance in which the headers are inserted.
+ nesting_state: A NestingState instance which maintains information about
+ the current stack of nested blocks being parsed.
+ error: The function to call with any errors found.
+ """
+ # If the line is empty or consists of entirely a comment, no need to
+ # check it.
+ line = clean_lines.elided[linenum]
+ if not line:
+ return
+
+ match = _RE_PATTERN_INCLUDE.search(line)
+ if match:
+ CheckIncludeLine(filename, clean_lines, linenum, include_state, error)
+ return
+
+ # Reset include state across preprocessor directives. This is meant
+ # to silence warnings for conditional includes.
+ match = Match(r'^\s*#\s*(if|ifdef|ifndef|elif|else|endif)\b', line)
+ if match:
+ include_state.ResetSection(match.group(1))
+
+ # Make Windows paths like Unix.
+ fullname = os.path.abspath(filename).replace('\\', '/')
+
+ # Perform other checks now that we are sure that this is not an include line
+ CheckCasts(filename, clean_lines, linenum, error)
+ CheckGlobalStatic(filename, clean_lines, linenum, error)
+ CheckPrintf(filename, clean_lines, linenum, error)
+
+ if IsHeaderExtension(file_extension):
+ # TODO(unknown): check that 1-arg constructors are explicit.
+ # How to tell it's a constructor?
+ # (handled in CheckForNonStandardConstructs for now)
+ # TODO(unknown): check that classes declare or disable copy/assign
+ # (level 1 error)
+ pass
+
+ # Check if people are using the verboten C basic types. The only exception
+ # we regularly allow is "unsigned short port" for port.
+ if Search(r'\bshort port\b', line):
+ if not Search(r'\bunsigned short port\b', line):
+ error(filename, linenum, 'runtime/int', 4,
+ 'Use "unsigned short" for ports, not "short"')
+ else:
+ match = Search(r'\b(short|long(?! +double)|long long)\b', line)
+ if match:
+ error(filename, linenum, 'runtime/int', 4,
+ 'Use int16/int64/etc, rather than the C type %s' % match.group(1))
+
+ # Check if some verboten operator overloading is going on
+ # TODO(unknown): catch out-of-line unary operator&:
+ # class X {};
+ # int operator&(const X& x) { return 42; } // unary operator&
+ # The trick is it's hard to tell apart from binary operator&:
+ # class Y { int operator&(const Y& x) { return 23; } }; // binary operator&
+ if Search(r'\boperator\s*&\s*\(\s*\)', line):
+ error(filename, linenum, 'runtime/operator', 4,
+ 'Unary operator& is dangerous. Do not use it.')
+
+ # Check for suspicious usage of "if" like
+ # } if (a == b) {
+ if Search(r'\}\s*if\s*\(', line):
+ error(filename, linenum, 'readability/braces', 4,
+ 'Did you mean "else if"? If not, start a new line for "if".')
+
+ # Check for potential format string bugs like printf(foo).
+ # We constrain the pattern not to pick things like DocidForPrintf(foo).
+ # Not perfect but it can catch printf(foo.c_str()) and printf(foo->c_str())
+ # TODO(unknown): Catch the following case. Need to change the calling
+ # convention of the whole function to process multiple line to handle it.
+ # printf(
+ # boy_this_is_a_really_long_variable_that_cannot_fit_on_the_prev_line);
+ printf_args = _GetTextInside(line, r'(?i)\b(string)?printf\s*\(')
+ if printf_args:
+ match = Match(r'([\w.\->()]+)$', printf_args)
+ if match and match.group(1) != '__VA_ARGS__':
+ function_name = re.search(r'\b((?:string)?printf)\s*\(',
+ line, re.I).group(1)
+ error(filename, linenum, 'runtime/printf', 4,
+ 'Potential format string bug. Do %s("%%s", %s) instead.'
+ % (function_name, match.group(1)))
+
+ # Check for potential memset bugs like memset(buf, sizeof(buf), 0).
+ match = Search(r'memset\s*\(([^,]*),\s*([^,]*),\s*0\s*\)', line)
+ if match and not Match(r"^''|-?[0-9]+|0x[0-9A-Fa-f]$", match.group(2)):
+ error(filename, linenum, 'runtime/memset', 4,
+ 'Did you mean "memset(%s, 0, %s)"?'
+ % (match.group(1), match.group(2)))
+
+ if Search(r'\busing namespace\b', line):
+ error(filename, linenum, 'build/namespaces', 5,
+ 'Do not use namespace using-directives. '
+ 'Use using-declarations instead.')
+
+ # Detect variable-length arrays.
+ match = Match(r'\s*(.+::)?(\w+) [a-z]\w*\[(.+)];', line)
+ if (match and match.group(2) != 'return' and match.group(2) != 'delete' and
+ match.group(3).find(']') == -1):
+ # Split the size using space and arithmetic operators as delimiters.
+ # If any of the resulting tokens are not compile time constants then
+ # report the error.
+ tokens = re.split(r'\s|\+|\-|\*|\/|<<|>>]', match.group(3))
+ is_const = True
+ skip_next = False
+ for tok in tokens:
+ if skip_next:
+ skip_next = False
+ continue
+
+ if Search(r'sizeof\(.+\)', tok): continue
+ if Search(r'arraysize\(\w+\)', tok): continue
+
+ tok = tok.lstrip('(')
+ tok = tok.rstrip(')')
+ if not tok: continue
+ if Match(r'\d+', tok): continue
+ if Match(r'0[xX][0-9a-fA-F]+', tok): continue
+ if Match(r'k[A-Z0-9]\w*', tok): continue
+ if Match(r'(.+::)?k[A-Z0-9]\w*', tok): continue
+ if Match(r'(.+::)?[A-Z][A-Z0-9_]*', tok): continue
+ # A catch all for tricky sizeof cases, including 'sizeof expression',
+ # 'sizeof(*type)', 'sizeof(const type)', 'sizeof(struct StructName)'
+ # requires skipping the next token because we split on ' ' and '*'.
+ if tok.startswith('sizeof'):
+ skip_next = True
+ continue
+ is_const = False
+ break
+ if not is_const:
+ error(filename, linenum, 'runtime/arrays', 1,
+ 'Do not use variable-length arrays. Use an appropriately named '
+ "('k' followed by CamelCase) compile-time constant for the size.")
+
+ # Check for use of unnamed namespaces in header files. Registration
+ # macros are typically OK, so we allow use of "namespace {" on lines
+ # that end with backslashes.
+ if (IsHeaderExtension(file_extension)
+ and Search(r'\bnamespace\s*{', line)
+ and line[-1] != '\\'):
+ error(filename, linenum, 'build/namespaces', 4,
+ 'Do not use unnamed namespaces in header files. See '
+ 'https://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Namespaces'
+ ' for more information.')
+
+
+def CheckGlobalStatic(filename, clean_lines, linenum, error):
+ """Check for unsafe global or static objects.
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ error: The function to call with any errors found.
+ """
+ line = clean_lines.elided[linenum]
+
+ # Match two lines at a time to support multiline declarations
+ if linenum + 1 < clean_lines.NumLines() and not Search(r'[;({]', line):
+ line += clean_lines.elided[linenum + 1].strip()
+
+ # Check for people declaring static/global STL strings at the top level.
+ # This is dangerous because the C++ language does not guarantee that
+ # globals with constructors are initialized before the first access, and
+ # also because globals can be destroyed when some threads are still running.
+ # TODO(unknown): Generalize this to also find static unique_ptr instances.
+ # TODO(unknown): File bugs for clang-tidy to find these.
+ match = Match(
+ r'((?:|static +)(?:|const +))(?::*std::)?string( +const)? +'
+ r'([a-zA-Z0-9_:]+)\b(.*)',
+ line)
+
+ # Remove false positives:
+ # - String pointers (as opposed to values).
+ # string *pointer
+ # const string *pointer
+ # string const *pointer
+ # string *const pointer
+ #
+ # - Functions and template specializations.
+ # string Function<Type>(...
+ # string Class<Type>::Method(...
+ #
+ # - Operators. These are matched separately because operator names
+ # cross non-word boundaries, and trying to match both operators
+ # and functions at the same time would decrease accuracy of
+ # matching identifiers.
+ # string Class::operator*()
+ if (match and
+ not Search(r'\bstring\b(\s+const)?\s*[\*\&]\s*(const\s+)?\w', line) and
+ not Search(r'\boperator\W', line) and
+ not Match(r'\s*(<.*>)?(::[a-zA-Z0-9_]+)*\s*\(([^"]|$)', match.group(4))):
+ if Search(r'\bconst\b', line):
+ error(filename, linenum, 'runtime/string', 4,
+ 'For a static/global string constant, use a C style string '
+ 'instead: "%schar%s %s[]".' %
+ (match.group(1), match.group(2) or '', match.group(3)))
+ else:
+ error(filename, linenum, 'runtime/string', 4,
+ 'Static/global string variables are not permitted.')
+
+ if (Search(r'\b([A-Za-z0-9_]*_)\(\1\)', line) or
+ Search(r'\b([A-Za-z0-9_]*_)\(CHECK_NOTNULL\(\1\)\)', line)):
+ error(filename, linenum, 'runtime/init', 4,
+ 'You seem to be initializing a member variable with itself.')
+
+
+def CheckPrintf(filename, clean_lines, linenum, error):
+ """Check for printf related issues.
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ error: The function to call with any errors found.
+ """
+ line = clean_lines.elided[linenum]
+
+ # When snprintf is used, the second argument shouldn't be a literal.
+ match = Search(r'snprintf\s*\(([^,]*),\s*([0-9]*)\s*,', line)
+ if match and match.group(2) != '0':
+ # If 2nd arg is zero, snprintf is used to calculate size.
+ error(filename, linenum, 'runtime/printf', 3,
+ 'If you can, use sizeof(%s) instead of %s as the 2nd arg '
+ 'to snprintf.' % (match.group(1), match.group(2)))
+
+ # Check if some verboten C functions are being used.
+ if Search(r'\bsprintf\s*\(', line):
+ error(filename, linenum, 'runtime/printf', 5,
+ 'Never use sprintf. Use snprintf instead.')
+ match = Search(r'\b(strcpy|strcat)\s*\(', line)
+ if match:
+ error(filename, linenum, 'runtime/printf', 4,
+ 'Almost always, snprintf is better than %s' % match.group(1))
+
+
+def IsDerivedFunction(clean_lines, linenum):
+ """Check if current line contains an inherited function.
+
+ Args:
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ Returns:
+ True if current line contains a function with "override"
+ virt-specifier.
+ """
+ # Scan back a few lines for start of current function
+ for i in xrange(linenum, max(-1, linenum - 10), -1):
+ match = Match(r'^([^()]*\w+)\(', clean_lines.elided[i])
+ if match:
+ # Look for "override" after the matching closing parenthesis
+ line, _, closing_paren = CloseExpression(
+ clean_lines, i, len(match.group(1)))
+ return (closing_paren >= 0 and
+ Search(r'\boverride\b', line[closing_paren:]))
+ return False
+
+
+def IsOutOfLineMethodDefinition(clean_lines, linenum):
+ """Check if current line contains an out-of-line method definition.
+
+ Args:
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ Returns:
+ True if current line contains an out-of-line method definition.
+ """
+ # Scan back a few lines for start of current function
+ for i in xrange(linenum, max(-1, linenum - 10), -1):
+ if Match(r'^([^()]*\w+)\(', clean_lines.elided[i]):
+ return Match(r'^[^()]*\w+::\w+\(', clean_lines.elided[i]) is not None
+ return False
+
+
+def IsInitializerList(clean_lines, linenum):
+ """Check if current line is inside constructor initializer list.
+
+ Args:
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ Returns:
+ True if current line appears to be inside constructor initializer
+ list, False otherwise.
+ """
+ for i in xrange(linenum, 1, -1):
+ line = clean_lines.elided[i]
+ if i == linenum:
+ remove_function_body = Match(r'^(.*)\{\s*$', line)
+ if remove_function_body:
+ line = remove_function_body.group(1)
+
+ if Search(r'\s:\s*\w+[({]', line):
+ # A lone colon tend to indicate the start of a constructor
+ # initializer list. It could also be a ternary operator, which
+ # also tend to appear in constructor initializer lists as
+ # opposed to parameter lists.
+ return True
+ if Search(r'\}\s*,\s*$', line):
+ # A closing brace followed by a comma is probably the end of a
+ # brace-initialized member in constructor initializer list.
+ return True
+ if Search(r'[{};]\s*$', line):
+ # Found one of the following:
+ # - A closing brace or semicolon, probably the end of the previous
+ # function.
+ # - An opening brace, probably the start of current class or namespace.
+ #
+ # Current line is probably not inside an initializer list since
+ # we saw one of those things without seeing the starting colon.
+ return False
+
+ # Got to the beginning of the file without seeing the start of
+ # constructor initializer list.
+ return False
+
+
+def CheckForNonConstReference(filename, clean_lines, linenum,
+ nesting_state, error):
+ """Check for non-const references.
+
+ Separate from CheckLanguage since it scans backwards from current
+ line, instead of scanning forward.
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ nesting_state: A NestingState instance which maintains information about
+ the current stack of nested blocks being parsed.
+ error: The function to call with any errors found.
+ """
+ # Do nothing if there is no '&' on current line.
+ line = clean_lines.elided[linenum]
+ if '&' not in line:
+ return
+
+ # If a function is inherited, current function doesn't have much of
+ # a choice, so any non-const references should not be blamed on
+ # derived function.
+ if IsDerivedFunction(clean_lines, linenum):
+ return
+
+ # Don't warn on out-of-line method definitions, as we would warn on the
+ # in-line declaration, if it isn't marked with 'override'.
+ if IsOutOfLineMethodDefinition(clean_lines, linenum):
+ return
+
+ # Long type names may be broken across multiple lines, usually in one
+ # of these forms:
+ # LongType
+ # ::LongTypeContinued &identifier
+ # LongType::
+ # LongTypeContinued &identifier
+ # LongType<
+ # ...>::LongTypeContinued &identifier
+ #
+ # If we detected a type split across two lines, join the previous
+ # line to current line so that we can match const references
+ # accordingly.
+ #
+ # Note that this only scans back one line, since scanning back
+ # arbitrary number of lines would be expensive. If you have a type
+ # that spans more than 2 lines, please use a typedef.
+ if linenum > 1:
+ previous = None
+ if Match(r'\s*::(?:[\w<>]|::)+\s*&\s*\S', line):
+ # previous_line\n + ::current_line
+ previous = Search(r'\b((?:const\s*)?(?:[\w<>]|::)+[\w<>])\s*$',
+ clean_lines.elided[linenum - 1])
+ elif Match(r'\s*[a-zA-Z_]([\w<>]|::)+\s*&\s*\S', line):
+ # previous_line::\n + current_line
+ previous = Search(r'\b((?:const\s*)?(?:[\w<>]|::)+::)\s*$',
+ clean_lines.elided[linenum - 1])
+ if previous:
+ line = previous.group(1) + line.lstrip()
+ else:
+ # Check for templated parameter that is split across multiple lines
+ endpos = line.rfind('>')
+ if endpos > -1:
+ (_, startline, startpos) = ReverseCloseExpression(
+ clean_lines, linenum, endpos)
+ if startpos > -1 and startline < linenum:
+ # Found the matching < on an earlier line, collect all
+ # pieces up to current line.
+ line = ''
+ for i in xrange(startline, linenum + 1):
+ line += clean_lines.elided[i].strip()
+
+ # Check for non-const references in function parameters. A single '&' may
+ # found in the following places:
+ # inside expression: binary & for bitwise AND
+ # inside expression: unary & for taking the address of something
+ # inside declarators: reference parameter
+ # We will exclude the first two cases by checking that we are not inside a
+ # function body, including one that was just introduced by a trailing '{'.
+ # TODO(unknown): Doesn't account for 'catch(Exception& e)' [rare].
+ if (nesting_state.previous_stack_top and
+ not (isinstance(nesting_state.previous_stack_top, _ClassInfo) or
+ isinstance(nesting_state.previous_stack_top, _NamespaceInfo))):
+ # Not at toplevel, not within a class, and not within a namespace
+ return
+
+ # Avoid initializer lists. We only need to scan back from the
+ # current line for something that starts with ':'.
+ #
+ # We don't need to check the current line, since the '&' would
+ # appear inside the second set of parentheses on the current line as
+ # opposed to the first set.
+ if linenum > 0:
+ for i in xrange(linenum - 1, max(0, linenum - 10), -1):
+ previous_line = clean_lines.elided[i]
+ if not Search(r'[),]\s*$', previous_line):
+ break
+ if Match(r'^\s*:\s+\S', previous_line):
+ return
+
+ # Avoid preprocessors
+ if Search(r'\\\s*$', line):
+ return
+
+ # Avoid constructor initializer lists
+ if IsInitializerList(clean_lines, linenum):
+ return
+
+ # We allow non-const references in a few standard places, like functions
+ # called "swap()" or iostream operators like "<<" or ">>". Do not check
+ # those function parameters.
+ #
+ # We also accept & in static_assert, which looks like a function but
+ # it's actually a declaration expression.
+ whitelisted_functions = (r'(?:[sS]wap(?:<\w:+>)?|'
+ r'operator\s*[<>][<>]|'
+ r'static_assert|COMPILE_ASSERT'
+ r')\s*\(')
+ if Search(whitelisted_functions, line):
+ return
+ elif not Search(r'\S+\([^)]*$', line):
+ # Don't see a whitelisted function on this line. Actually we
+ # didn't see any function name on this line, so this is likely a
+ # multi-line parameter list. Try a bit harder to catch this case.
+ for i in xrange(2):
+ if (linenum > i and
+ Search(whitelisted_functions, clean_lines.elided[linenum - i - 1])):
+ return
+
+ decls = ReplaceAll(r'{[^}]*}', ' ', line) # exclude function body
+ for parameter in re.findall(_RE_PATTERN_REF_PARAM, decls):
+ if (not Match(_RE_PATTERN_CONST_REF_PARAM, parameter) and
+ not Match(_RE_PATTERN_REF_STREAM_PARAM, parameter)):
+ error(filename, linenum, 'runtime/references', 2,
+ 'Is this a non-const reference? '
+ 'If so, make const or use a pointer: ' +
+ ReplaceAll(' *<', '<', parameter))
+
+
+def CheckCasts(filename, clean_lines, linenum, error):
+ """Various cast related checks.
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ error: The function to call with any errors found.
+ """
+ line = clean_lines.elided[linenum]
+
+ # Check to see if they're using an conversion function cast.
+ # I just try to capture the most common basic types, though there are more.
+ # Parameterless conversion functions, such as bool(), are allowed as they are
+ # probably a member operator declaration or default constructor.
+ match = Search(
+ r'(\bnew\s+(?:const\s+)?|\S<\s*(?:const\s+)?)?\b'
+ r'(int|float|double|bool|char|int32|uint32|int64|uint64)'
+ r'(\([^)].*)', line)
+ expecting_function = ExpectingFunctionArgs(clean_lines, linenum)
+ if match and not expecting_function:
+ matched_type = match.group(2)
+
+ # matched_new_or_template is used to silence two false positives:
+ # - New operators
+ # - Template arguments with function types
+ #
+ # For template arguments, we match on types immediately following
+ # an opening bracket without any spaces. This is a fast way to
+ # silence the common case where the function type is the first
+ # template argument. False negative with less-than comparison is
+ # avoided because those operators are usually followed by a space.
+ #
+ # function<double(double)> // bracket + no space = false positive
+ # value < double(42) // bracket + space = true positive
+ matched_new_or_template = match.group(1)
+
+ # Avoid arrays by looking for brackets that come after the closing
+ # parenthesis.
+ if Match(r'\([^()]+\)\s*\[', match.group(3)):
+ return
+
+ # Other things to ignore:
+ # - Function pointers
+ # - Casts to pointer types
+ # - Placement new
+ # - Alias declarations
+ matched_funcptr = match.group(3)
+ if (matched_new_or_template is None and
+ not (matched_funcptr and
+ (Match(r'\((?:[^() ]+::\s*\*\s*)?[^() ]+\)\s*\(',
+ matched_funcptr) or
+ matched_funcptr.startswith('(*)'))) and
+ not Match(r'\s*using\s+\S+\s*=\s*' + matched_type, line) and
+ not Search(r'new\(\S+\)\s*' + matched_type, line)):
+ error(filename, linenum, 'readability/casting', 4,
+ 'Using deprecated casting style. '
+ 'Use static_cast<%s>(...) instead' %
+ matched_type)
+
+ if not expecting_function:
+ CheckCStyleCast(filename, clean_lines, linenum, 'static_cast',
+ r'\((int|float|double|bool|char|u?int(16|32|64))\)', error)
+
+ # This doesn't catch all cases. Consider (const char * const)"hello".
+ #
+ # (char *) "foo" should always be a const_cast (reinterpret_cast won't
+ # compile).
+ if CheckCStyleCast(filename, clean_lines, linenum, 'const_cast',
+ r'\((char\s?\*+\s?)\)\s*"', error):
+ pass
+ else:
+ # Check pointer casts for other than string constants
+ CheckCStyleCast(filename, clean_lines, linenum, 'reinterpret_cast',
+ r'\((\w+\s?\*+\s?)\)', error)
+
+ # In addition, we look for people taking the address of a cast. This
+ # is dangerous -- casts can assign to temporaries, so the pointer doesn't
+ # point where you think.
+ #
+ # Some non-identifier character is required before the '&' for the
+ # expression to be recognized as a cast. These are casts:
+ # expression = &static_cast<int*>(temporary());
+ # function(&(int*)(temporary()));
+ #
+ # This is not a cast:
+ # reference_type&(int* function_param);
+ match = Search(
+ r'(?:[^\w]&\(([^)*][^)]*)\)[\w(])|'
+ r'(?:[^\w]&(static|dynamic|down|reinterpret)_cast\b)', line)
+ if match:
+ # Try a better error message when the & is bound to something
+ # dereferenced by the casted pointer, as opposed to the casted
+ # pointer itself.
+ parenthesis_error = False
+ match = Match(r'^(.*&(?:static|dynamic|down|reinterpret)_cast\b)<', line)
+ if match:
+ _, y1, x1 = CloseExpression(clean_lines, linenum, len(match.group(1)))
+ if x1 >= 0 and clean_lines.elided[y1][x1] == '(':
+ _, y2, x2 = CloseExpression(clean_lines, y1, x1)
+ if x2 >= 0:
+ extended_line = clean_lines.elided[y2][x2:]
+ if y2 < clean_lines.NumLines() - 1:
+ extended_line += clean_lines.elided[y2 + 1]
+ if Match(r'\s*(?:->|\[)', extended_line):
+ parenthesis_error = True
+
+ if parenthesis_error:
+ error(filename, linenum, 'readability/casting', 4,
+ ('Are you taking an address of something dereferenced '
+ 'from a cast? Wrapping the dereferenced expression in '
+ 'parentheses will make the binding more obvious'))
+ else:
+ error(filename, linenum, 'runtime/casting', 4,
+ ('Are you taking an address of a cast? '
+ 'This is dangerous: could be a temp var. '
+ 'Take the address before doing the cast, rather than after'))
+
+
+def CheckCStyleCast(filename, clean_lines, linenum, cast_type, pattern, error):
+ """Checks for a C-style cast by looking for the pattern.
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ cast_type: The string for the C++ cast to recommend. This is either
+ reinterpret_cast, static_cast, or const_cast, depending.
+ pattern: The regular expression used to find C-style casts.
+ error: The function to call with any errors found.
+
+ Returns:
+ True if an error was emitted.
+ False otherwise.
+ """
+ line = clean_lines.elided[linenum]
+ match = Search(pattern, line)
+ if not match:
+ return False
+
+ # Exclude lines with keywords that tend to look like casts
+ context = line[0:match.start(1) - 1]
+ if Match(r'.*\b(?:sizeof|alignof|alignas|[_A-Z][_A-Z0-9]*)\s*$', context):
+ return False
+
+ # Try expanding current context to see if we one level of
+ # parentheses inside a macro.
+ if linenum > 0:
+ for i in xrange(linenum - 1, max(0, linenum - 5), -1):
+ context = clean_lines.elided[i] + context
+ if Match(r'.*\b[_A-Z][_A-Z0-9]*\s*\((?:\([^()]*\)|[^()])*$', context):
+ return False
+
+ # operator++(int) and operator--(int)
+ if context.endswith(' operator++') or context.endswith(' operator--'):
+ return False
+
+ # A single unnamed argument for a function tends to look like old style cast.
+ # If we see those, don't issue warnings for deprecated casts.
+ remainder = line[match.end(0):]
+ if Match(r'^\s*(?:;|const\b|throw\b|final\b|override\b|[=>{),]|->)',
+ remainder):
+ return False
+
+ # At this point, all that should be left is actual casts.
+ error(filename, linenum, 'readability/casting', 4,
+ 'Using C-style cast. Use %s<%s>(...) instead' %
+ (cast_type, match.group(1)))
+
+ return True
+
+
+def ExpectingFunctionArgs(clean_lines, linenum):
+ """Checks whether where function type arguments are expected.
+
+ Args:
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+
+ Returns:
+ True if the line at 'linenum' is inside something that expects arguments
+ of function types.
+ """
+ line = clean_lines.elided[linenum]
+ return (Match(r'^\s*MOCK_(CONST_)?METHOD\d+(_T)?\(', line) or
+ (linenum >= 2 and
+ (Match(r'^\s*MOCK_(?:CONST_)?METHOD\d+(?:_T)?\((?:\S+,)?\s*$',
+ clean_lines.elided[linenum - 1]) or
+ Match(r'^\s*MOCK_(?:CONST_)?METHOD\d+(?:_T)?\(\s*$',
+ clean_lines.elided[linenum - 2]) or
+ Search(r'\bstd::m?function\s*\<\s*$',
+ clean_lines.elided[linenum - 1]))))
+
+
+_HEADERS_CONTAINING_TEMPLATES = (
+ ('<deque>', ('deque',)),
+ ('<functional>', ('unary_function', 'binary_function',
+ 'plus', 'minus', 'multiplies', 'divides', 'modulus',
+ 'negate',
+ 'equal_to', 'not_equal_to', 'greater', 'less',
+ 'greater_equal', 'less_equal',
+ 'logical_and', 'logical_or', 'logical_not',
+ 'unary_negate', 'not1', 'binary_negate', 'not2',
+ 'bind1st', 'bind2nd',
+ 'pointer_to_unary_function',
+ 'pointer_to_binary_function',
+ 'ptr_fun',
+ 'mem_fun_t', 'mem_fun', 'mem_fun1_t', 'mem_fun1_ref_t',
+ 'mem_fun_ref_t',
+ 'const_mem_fun_t', 'const_mem_fun1_t',
+ 'const_mem_fun_ref_t', 'const_mem_fun1_ref_t',
+ 'mem_fun_ref',
+ )),
+ ('<limits>', ('numeric_limits',)),
+ ('<list>', ('list',)),
+ ('<map>', ('map', 'multimap',)),
+ ('<memory>', ('allocator', 'make_shared', 'make_unique', 'shared_ptr',
+ 'unique_ptr', 'weak_ptr')),
+ ('<queue>', ('queue', 'priority_queue',)),
+ ('<set>', ('set', 'multiset',)),
+ ('<stack>', ('stack',)),
+ ('<string>', ('char_traits', 'basic_string',)),
+ ('<tuple>', ('tuple',)),
+ ('<unordered_map>', ('unordered_map', 'unordered_multimap')),
+ ('<unordered_set>', ('unordered_set', 'unordered_multiset')),
+ ('<utility>', ('pair',)),
+ ('<vector>', ('vector',)),
+
+ # gcc extensions.
+ # Note: std::hash is their hash, ::hash is our hash
+ ('<hash_map>', ('hash_map', 'hash_multimap',)),
+ ('<hash_set>', ('hash_set', 'hash_multiset',)),
+ ('<slist>', ('slist',)),
+ )
+
+_HEADERS_MAYBE_TEMPLATES = (
+ ('<algorithm>', ('copy', 'max', 'min', 'min_element', 'sort',
+ 'transform',
+ )),
+ ('<utility>', ('forward', 'make_pair', 'move', 'swap')),
+ )
+
+_RE_PATTERN_STRING = re.compile(r'\bstring\b')
+
+_re_pattern_headers_maybe_templates = []
+for _header, _templates in _HEADERS_MAYBE_TEMPLATES:
+ for _template in _templates:
+ # Match max<type>(..., ...), max(..., ...), but not foo->max, foo.max or
+ # type::max().
+ _re_pattern_headers_maybe_templates.append(
+ (re.compile(r'[^>.]\b' + _template + r'(<.*?>)?\([^\)]'),
+ _template,
+ _header))
+
+# Other scripts may reach in and modify this pattern.
+_re_pattern_templates = []
+for _header, _templates in _HEADERS_CONTAINING_TEMPLATES:
+ for _template in _templates:
+ _re_pattern_templates.append(
+ (re.compile(r'(\<|\b)' + _template + r'\s*\<'),
+ _template + '<>',
+ _header))
+
+
+def FilesBelongToSameModule(filename_cc, filename_h):
+ """Check if these two filenames belong to the same module.
+
+ The concept of a 'module' here is a as follows:
+ foo.h, foo-inl.h, foo.cc, foo_test.cc and foo_unittest.cc belong to the
+ same 'module' if they are in the same directory.
+ some/path/public/xyzzy and some/path/internal/xyzzy are also considered
+ to belong to the same module here.
+
+ If the filename_cc contains a longer path than the filename_h, for example,
+ '/absolute/path/to/base/sysinfo.cc', and this file would include
+ 'base/sysinfo.h', this function also produces the prefix needed to open the
+ header. This is used by the caller of this function to more robustly open the
+ header file. We don't have access to the real include paths in this context,
+ so we need this guesswork here.
+
+ Known bugs: tools/base/bar.cc and base/bar.h belong to the same module
+ according to this implementation. Because of this, this function gives
+ some false positives. This should be sufficiently rare in practice.
+
+ Args:
+ filename_cc: is the path for the .cc file
+ filename_h: is the path for the header path
+
+ Returns:
+ Tuple with a bool and a string:
+ bool: True if filename_cc and filename_h belong to the same module.
+ string: the additional prefix needed to open the header file.
+ """
+
+ fileinfo = FileInfo(filename_cc)
+ if not fileinfo.IsSource():
+ return (False, '')
+ filename_cc = filename_cc[:-len(fileinfo.Extension())]
+ matched_test_suffix = Search(_TEST_FILE_SUFFIX, fileinfo.BaseName())
+ if matched_test_suffix:
+ filename_cc = filename_cc[:-len(matched_test_suffix.group(1))]
+ filename_cc = filename_cc.replace('/public/', '/')
+ filename_cc = filename_cc.replace('/internal/', '/')
+
+ if not filename_h.endswith('.h'):
+ return (False, '')
+ filename_h = filename_h[:-len('.h')]
+ if filename_h.endswith('-inl'):
+ filename_h = filename_h[:-len('-inl')]
+ filename_h = filename_h.replace('/public/', '/')
+ filename_h = filename_h.replace('/internal/', '/')
+
+ files_belong_to_same_module = filename_cc.endswith(filename_h)
+ common_path = ''
+ if files_belong_to_same_module:
+ common_path = filename_cc[:-len(filename_h)]
+ return files_belong_to_same_module, common_path
+
+
+def UpdateIncludeState(filename, include_dict, io=codecs):
+ """Fill up the include_dict with new includes found from the file.
+
+ Args:
+ filename: the name of the header to read.
+ include_dict: a dictionary in which the headers are inserted.
+ io: The io factory to use to read the file. Provided for testability.
+
+ Returns:
+ True if a header was successfully added. False otherwise.
+ """
+ headerfile = None
+ try:
+ headerfile = io.open(filename, 'r', 'utf8', 'replace')
+ except IOError:
+ return False
+ linenum = 0
+ for line in headerfile:
+ linenum += 1
+ clean_line = CleanseComments(line)
+ match = _RE_PATTERN_INCLUDE.search(clean_line)
+ if match:
+ include = match.group(2)
+ include_dict.setdefault(include, linenum)
+ return True
+
+
+def CheckForIncludeWhatYouUse(filename, clean_lines, include_state, error,
+ io=codecs):
+ """Reports for missing stl includes.
+
+ This function will output warnings to make sure you are including the headers
+ necessary for the stl containers and functions that you use. We only give one
+ reason to include a header. For example, if you use both equal_to<> and
+ less<> in a .h file, only one (the latter in the file) of these will be
+ reported as a reason to include the <functional>.
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ include_state: An _IncludeState instance.
+ error: The function to call with any errors found.
+ io: The IO factory to use to read the header file. Provided for unittest
+ injection.
+ """
+ required = {} # A map of header name to linenumber and the template entity.
+ # Example of required: { '<functional>': (1219, 'less<>') }
+
+ for linenum in xrange(clean_lines.NumLines()):
+ line = clean_lines.elided[linenum]
+ if not line or line[0] == '#':
+ continue
+
+ # String is special -- it is a non-templatized type in STL.
+ matched = _RE_PATTERN_STRING.search(line)
+ if matched:
+ # Don't warn about strings in non-STL namespaces:
+ # (We check only the first match per line; good enough.)
+ prefix = line[:matched.start()]
+ if prefix.endswith('std::') or not prefix.endswith('::'):
+ required['<string>'] = (linenum, 'string')
+
+ for pattern, template, header in _re_pattern_headers_maybe_templates:
+ if pattern.search(line):
+ required[header] = (linenum, template)
+
+ # The following function is just a speed up, no semantics are changed.
+ if not '<' in line: # Reduces the cpu time usage by skipping lines.
+ continue
+
+ for pattern, template, header in _re_pattern_templates:
+ matched = pattern.search(line)
+ if matched:
+ # Don't warn about IWYU in non-STL namespaces:
+ # (We check only the first match per line; good enough.)
+ prefix = line[:matched.start()]
+ if prefix.endswith('std::') or not prefix.endswith('::'):
+ required[header] = (linenum, template)
+
+ # The policy is that if you #include something in foo.h you don't need to
+ # include it again in foo.cc. Here, we will look at possible includes.
+ # Let's flatten the include_state include_list and copy it into a dictionary.
+ include_dict = dict([item for sublist in include_state.include_list
+ for item in sublist])
+
+ # Did we find the header for this file (if any) and successfully load it?
+ header_found = False
+
+ # Use the absolute path so that matching works properly.
+ abs_filename = FileInfo(filename).FullName()
+
+ # For Emacs's flymake.
+ # If cpplint is invoked from Emacs's flymake, a temporary file is generated
+ # by flymake and that file name might end with '_flymake.cc'. In that case,
+ # restore original file name here so that the corresponding header file can be
+ # found.
+ # e.g. If the file name is 'foo_flymake.cc', we should search for 'foo.h'
+ # instead of 'foo_flymake.h'
+ abs_filename = re.sub(r'_flymake\.cc$', '.cc', abs_filename)
+
+ # include_dict is modified during iteration, so we iterate over a copy of
+ # the keys.
+ header_keys = include_dict.keys()
+ for header in header_keys:
+ (same_module, common_path) = FilesBelongToSameModule(abs_filename, header)
+ fullpath = common_path + header
+ if same_module and UpdateIncludeState(fullpath, include_dict, io):
+ header_found = True
+
+ # If we can't find the header file for a .cc, assume it's because we don't
+ # know where to look. In that case we'll give up as we're not sure they
+ # didn't include it in the .h file.
+ # TODO(unknown): Do a better job of finding .h files so we are confident that
+ # not having the .h file means there isn't one.
+ if filename.endswith('.cc') and not header_found:
+ return
+
+ # All the lines have been processed, report the errors found.
+ for required_header_unstripped in required:
+ template = required[required_header_unstripped][1]
+ if required_header_unstripped.strip('<>"') not in include_dict:
+ error(filename, required[required_header_unstripped][0],
+ 'build/include_what_you_use', 4,
+ 'Add #include ' + required_header_unstripped + ' for ' + template)
+
+
+_RE_PATTERN_EXPLICIT_MAKEPAIR = re.compile(r'\bmake_pair\s*<')
+
+
+def CheckMakePairUsesDeduction(filename, clean_lines, linenum, error):
+ """Check that make_pair's template arguments are deduced.
+
+ G++ 4.6 in C++11 mode fails badly if make_pair's template arguments are
+ specified explicitly, and such use isn't intended in any case.
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ error: The function to call with any errors found.
+ """
+ line = clean_lines.elided[linenum]
+ match = _RE_PATTERN_EXPLICIT_MAKEPAIR.search(line)
+ if match:
+ error(filename, linenum, 'build/explicit_make_pair',
+ 4, # 4 = high confidence
+ 'For C++11-compatibility, omit template arguments from make_pair'
+ ' OR use pair directly OR if appropriate, construct a pair directly')
+
+
+def CheckRedundantVirtual(filename, clean_lines, linenum, error):
+ """Check if line contains a redundant "virtual" function-specifier.
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ error: The function to call with any errors found.
+ """
+ # Look for "virtual" on current line.
+ line = clean_lines.elided[linenum]
+ virtual = Match(r'^(.*)(\bvirtual\b)(.*)$', line)
+ if not virtual: return
+
+ # Ignore "virtual" keywords that are near access-specifiers. These
+ # are only used in class base-specifier and do not apply to member
+ # functions.
+ if (Search(r'\b(public|protected|private)\s+$', virtual.group(1)) or
+ Match(r'^\s+(public|protected|private)\b', virtual.group(3))):
+ return
+
+ # Ignore the "virtual" keyword from virtual base classes. Usually
+ # there is a column on the same line in these cases (virtual base
+ # classes are rare in google3 because multiple inheritance is rare).
+ if Match(r'^.*[^:]:[^:].*$', line): return
+
+ # Look for the next opening parenthesis. This is the start of the
+ # parameter list (possibly on the next line shortly after virtual).
+ # TODO(unknown): doesn't work if there are virtual functions with
+ # decltype() or other things that use parentheses, but csearch suggests
+ # that this is rare.
+ end_col = -1
+ end_line = -1
+ start_col = len(virtual.group(2))
+ for start_line in xrange(linenum, min(linenum + 3, clean_lines.NumLines())):
+ line = clean_lines.elided[start_line][start_col:]
+ parameter_list = Match(r'^([^(]*)\(', line)
+ if parameter_list:
+ # Match parentheses to find the end of the parameter list
+ (_, end_line, end_col) = CloseExpression(
+ clean_lines, start_line, start_col + len(parameter_list.group(1)))
+ break
+ start_col = 0
+
+ if end_col < 0:
+ return # Couldn't find end of parameter list, give up
+
+ # Look for "override" or "final" after the parameter list
+ # (possibly on the next few lines).
+ for i in xrange(end_line, min(end_line + 3, clean_lines.NumLines())):
+ line = clean_lines.elided[i][end_col:]
+ match = Search(r'\b(override|final)\b', line)
+ if match:
+ error(filename, linenum, 'readability/inheritance', 4,
+ ('"virtual" is redundant since function is '
+ 'already declared as "%s"' % match.group(1)))
+
+ # Set end_col to check whole lines after we are done with the
+ # first line.
+ end_col = 0
+ if Search(r'[^\w]\s*$', line):
+ break
+
+
+def CheckRedundantOverrideOrFinal(filename, clean_lines, linenum, error):
+ """Check if line contains a redundant "override" or "final" virt-specifier.
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ error: The function to call with any errors found.
+ """
+ # Look for closing parenthesis nearby. We need one to confirm where
+ # the declarator ends and where the virt-specifier starts to avoid
+ # false positives.
+ line = clean_lines.elided[linenum]
+ declarator_end = line.rfind(')')
+ if declarator_end >= 0:
+ fragment = line[declarator_end:]
+ else:
+ if linenum > 1 and clean_lines.elided[linenum - 1].rfind(')') >= 0:
+ fragment = line
+ else:
+ return
+
+ # Check that at most one of "override" or "final" is present, not both
+ if Search(r'\boverride\b', fragment) and Search(r'\bfinal\b', fragment):
+ error(filename, linenum, 'readability/inheritance', 4,
+ ('"override" is redundant since function is '
+ 'already declared as "final"'))
+
+
+
+
+# Returns true if we are at a new block, and it is directly
+# inside of a namespace.
+def IsBlockInNameSpace(nesting_state, is_forward_declaration):
+ """Checks that the new block is directly in a namespace.
+
+ Args:
+ nesting_state: The _NestingState object that contains info about our state.
+ is_forward_declaration: If the class is a forward declared class.
+ Returns:
+ Whether or not the new block is directly in a namespace.
+ """
+ if is_forward_declaration:
+ if len(nesting_state.stack) >= 1 and (
+ isinstance(nesting_state.stack[-1], _NamespaceInfo)):
+ return True
+ else:
+ return False
+
+ return (len(nesting_state.stack) > 1 and
+ nesting_state.stack[-1].check_namespace_indentation and
+ isinstance(nesting_state.stack[-2], _NamespaceInfo))
+
+
+def ShouldCheckNamespaceIndentation(nesting_state, is_namespace_indent_item,
+ raw_lines_no_comments, linenum):
+ """This method determines if we should apply our namespace indentation check.
+
+ Args:
+ nesting_state: The current nesting state.
+ is_namespace_indent_item: If we just put a new class on the stack, True.
+ If the top of the stack is not a class, or we did not recently
+ add the class, False.
+ raw_lines_no_comments: The lines without the comments.
+ linenum: The current line number we are processing.
+
+ Returns:
+ True if we should apply our namespace indentation check. Currently, it
+ only works for classes and namespaces inside of a namespace.
+ """
+
+ is_forward_declaration = IsForwardClassDeclaration(raw_lines_no_comments,
+ linenum)
+
+ if not (is_namespace_indent_item or is_forward_declaration):
+ return False
+
+ # If we are in a macro, we do not want to check the namespace indentation.
+ if IsMacroDefinition(raw_lines_no_comments, linenum):
+ return False
+
+ return IsBlockInNameSpace(nesting_state, is_forward_declaration)
+
+
+# Call this method if the line is directly inside of a namespace.
+# If the line above is blank (excluding comments) or the start of
+# an inner namespace, it cannot be indented.
+def CheckItemIndentationInNamespace(filename, raw_lines_no_comments, linenum,
+ error):
+ line = raw_lines_no_comments[linenum]
+ if Match(r'^\s+', line):
+ error(filename, linenum, 'runtime/indentation_namespace', 4,
+ 'Do not indent within a namespace')
+
+
+def ProcessLine(filename, file_extension, clean_lines, line,
+ include_state, function_state, nesting_state, error,
+ extra_check_functions=[]):
+ """Processes a single line in the file.
+
+ Args:
+ filename: Filename of the file that is being processed.
+ file_extension: The extension (dot not included) of the file.
+ clean_lines: An array of strings, each representing a line of the file,
+ with comments stripped.
+ line: Number of line being processed.
+ include_state: An _IncludeState instance in which the headers are inserted.
+ function_state: A _FunctionState instance which counts function lines, etc.
+ nesting_state: A NestingState instance which maintains information about
+ the current stack of nested blocks being parsed.
+ error: A callable to which errors are reported, which takes 4 arguments:
+ filename, line number, error level, and message
+ extra_check_functions: An array of additional check functions that will be
+ run on each source line. Each function takes 4
+ arguments: filename, clean_lines, line, error
+ """
+ raw_lines = clean_lines.raw_lines
+ ParseNolintSuppressions(filename, raw_lines[line], line, error)
+ nesting_state.Update(filename, clean_lines, line, error)
+ CheckForNamespaceIndentation(filename, nesting_state, clean_lines, line,
+ error)
+ if nesting_state.InAsmBlock(): return
+ CheckForFunctionLengths(filename, clean_lines, line, function_state, error)
+ CheckForMultilineCommentsAndStrings(filename, clean_lines, line, error)
+ CheckStyle(filename, clean_lines, line, file_extension, nesting_state, error)
+ CheckLanguage(filename, clean_lines, line, file_extension, include_state,
+ nesting_state, error)
+ CheckForNonConstReference(filename, clean_lines, line, nesting_state, error)
+ CheckForNonStandardConstructs(filename, clean_lines, line,
+ nesting_state, error)
+ CheckVlogArguments(filename, clean_lines, line, error)
+ CheckPosixThreading(filename, clean_lines, line, error)
+ CheckInvalidIncrement(filename, clean_lines, line, error)
+ CheckMakePairUsesDeduction(filename, clean_lines, line, error)
+ CheckRedundantVirtual(filename, clean_lines, line, error)
+ CheckRedundantOverrideOrFinal(filename, clean_lines, line, error)
+ for check_fn in extra_check_functions:
+ check_fn(filename, clean_lines, line, error)
+
+def FlagCxx11Features(filename, clean_lines, linenum, error):
+ """Flag those c++11 features that we only allow in certain places.
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ error: The function to call with any errors found.
+ """
+ line = clean_lines.elided[linenum]
+
+ include = Match(r'\s*#\s*include\s+[<"]([^<"]+)[">]', line)
+
+ # Flag unapproved C++ TR1 headers.
+ if include and include.group(1).startswith('tr1/'):
+ error(filename, linenum, 'build/c++tr1', 5,
+ ('C++ TR1 headers such as <%s> are unapproved.') % include.group(1))
+
+ # Flag unapproved C++11 headers.
+ if include and include.group(1) in ('cfenv',
+ 'condition_variable',
+ 'fenv.h',
+ 'future',
+ 'mutex',
+ 'thread',
+ 'chrono',
+ 'ratio',
+ 'regex',
+ 'system_error',
+ ):
+ error(filename, linenum, 'build/c++11', 5,
+ ('<%s> is an unapproved C++11 header.') % include.group(1))
+
+ # The only place where we need to worry about C++11 keywords and library
+ # features in preprocessor directives is in macro definitions.
+ if Match(r'\s*#', line) and not Match(r'\s*#\s*define\b', line): return
+
+ # These are classes and free functions. The classes are always
+ # mentioned as std::*, but we only catch the free functions if
+ # they're not found by ADL. They're alphabetical by header.
+ for top_name in (
+ # type_traits
+ 'alignment_of',
+ 'aligned_union',
+ ):
+ if Search(r'\bstd::%s\b' % top_name, line):
+ error(filename, linenum, 'build/c++11', 5,
+ ('std::%s is an unapproved C++11 class or function. Send c-style '
+ 'an example of where it would make your code more readable, and '
+ 'they may let you use it.') % top_name)
+
+
+def FlagCxx14Features(filename, clean_lines, linenum, error):
+ """Flag those C++14 features that we restrict.
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ error: The function to call with any errors found.
+ """
+ line = clean_lines.elided[linenum]
+
+ include = Match(r'\s*#\s*include\s+[<"]([^<"]+)[">]', line)
+
+ # Flag unapproved C++14 headers.
+ if include and include.group(1) in ('scoped_allocator', 'shared_mutex'):
+ error(filename, linenum, 'build/c++14', 5,
+ ('<%s> is an unapproved C++14 header.') % include.group(1))
+
+ # These are classes and free functions with abeil equivalents
+ for top_name in (
+ # memory
+ 'make_unique',
+ ):
+ if Search(r'\bstd::%s\b' % top_name, line):
+ error(filename, linenum, 'build/c++14', 5,
+ 'std::%s does not exist in C++11. Use absl::%s instead.' %
+ (top_name, top_name))
+
+
+def ProcessFileData(filename, file_extension, lines, error,
+ extra_check_functions=[]):
+ """Performs lint checks and reports any errors to the given error function.
+
+ Args:
+ filename: Filename of the file that is being processed.
+ file_extension: The extension (dot not included) of the file.
+ lines: An array of strings, each representing a line of the file, with the
+ last element being empty if the file is terminated with a newline.
+ error: A callable to which errors are reported, which takes 4 arguments:
+ filename, line number, error level, and message
+ extra_check_functions: An array of additional check functions that will be
+ run on each source line. Each function takes 4
+ arguments: filename, clean_lines, line, error
+ """
+ lines = (['// marker so line numbers and indices both start at 1'] + lines +
+ ['// marker so line numbers end in a known way'])
+
+ include_state = _IncludeState()
+ function_state = _FunctionState()
+ nesting_state = NestingState()
+
+ ResetNolintSuppressions()
+
+ CheckForCopyright(filename, lines, error)
+ ProcessGlobalSuppresions(lines)
+ RemoveMultiLineComments(filename, lines, error)
+ clean_lines = CleansedLines(lines)
+
+ if IsHeaderExtension(file_extension):
+ CheckForHeaderGuard(filename, clean_lines, error)
+
+ for line in xrange(clean_lines.NumLines()):
+ ProcessLine(filename, file_extension, clean_lines, line,
+ include_state, function_state, nesting_state, error,
+ extra_check_functions)
+ FlagCxx11Features(filename, clean_lines, line, error)
+ FlagCxx14Features(filename, clean_lines, line, error)
+ nesting_state.CheckCompletedBlocks(filename, error)
+
+ CheckForIncludeWhatYouUse(filename, clean_lines, include_state, error)
+
+ # Check that the .cc file has included its header if it exists.
+ if _IsSourceExtension(file_extension):
+ CheckHeaderFileIncluded(filename, include_state, error)
+
+ # We check here rather than inside ProcessLine so that we see raw
+ # lines rather than "cleaned" lines.
+ CheckForBadCharacters(filename, lines, error)
+
+ CheckForNewlineAtEOF(filename, lines, error)
+
+def ProcessConfigOverrides(filename):
+ """ Loads the configuration files and processes the config overrides.
+
+ Args:
+ filename: The name of the file being processed by the linter.
+
+ Returns:
+ False if the current |filename| should not be processed further.
+ """
+
+ abs_filename = os.path.abspath(filename)
+ cfg_filters = []
+ keep_looking = True
+ while keep_looking:
+ abs_path, base_name = os.path.split(abs_filename)
+ if not base_name:
+ break # Reached the root directory.
+
+ cfg_file = os.path.join(abs_path, "CPPLINT.cfg")
+ abs_filename = abs_path
+ if not os.path.isfile(cfg_file):
+ continue
+
+ try:
+ with open(cfg_file) as file_handle:
+ for line in file_handle:
+ line, _, _ = line.partition('#') # Remove comments.
+ if not line.strip():
+ continue
+
+ name, _, val = line.partition('=')
+ name = name.strip()
+ val = val.strip()
+ if name == 'set noparent':
+ keep_looking = False
+ elif name == 'filter':
+ cfg_filters.append(val)
+ elif name == 'exclude_files':
+ # When matching exclude_files pattern, use the base_name of
+ # the current file name or the directory name we are processing.
+ # For example, if we are checking for lint errors in /foo/bar/baz.cc
+ # and we found the .cfg file at /foo/CPPLINT.cfg, then the config
+ # file's "exclude_files" filter is meant to be checked against "bar"
+ # and not "baz" nor "bar/baz.cc".
+ if base_name:
+ pattern = re.compile(val)
+ if pattern.match(base_name):
+ if _cpplint_state.quiet:
+ # Suppress "Ignoring file" warning when using --quiet.
+ return False
+ sys.stderr.write('Ignoring "%s": file excluded by "%s". '
+ 'File path component "%s" matches '
+ 'pattern "%s"\n' %
+ (filename, cfg_file, base_name, val))
+ return False
+ elif name == 'linelength':
+ global _line_length
+ try:
+ _line_length = int(val)
+ except ValueError:
+ sys.stderr.write('Line length must be numeric.')
+ elif name == 'root':
+ global _root
+ # root directories are specified relative to CPPLINT.cfg dir.
+ _root = os.path.join(os.path.dirname(cfg_file), val)
+ elif name == 'headers':
+ ProcessHppHeadersOption(val)
+ else:
+ sys.stderr.write(
+ 'Invalid configuration option (%s) in file %s\n' %
+ (name, cfg_file))
+
+ except IOError:
+ sys.stderr.write(
+ "Skipping config file '%s': Can't open for reading\n" % cfg_file)
+ keep_looking = False
+
+ # Apply all the accumulated filters in reverse order (top-level directory
+ # config options having the least priority).
+ for filter in reversed(cfg_filters):
+ _AddFilters(filter)
+
+ return True
+
+
+def ProcessFile(filename, vlevel, extra_check_functions=[]):
+ """Does google-lint on a single file.
+
+ Args:
+ filename: The name of the file to parse.
+
+ vlevel: The level of errors to report. Every error of confidence
+ >= verbose_level will be reported. 0 is a good default.
+
+ extra_check_functions: An array of additional check functions that will be
+ run on each source line. Each function takes 4
+ arguments: filename, clean_lines, line, error
+ """
+
+ _SetVerboseLevel(vlevel)
+ _BackupFilters()
+ old_errors = _cpplint_state.error_count
+
+ if not ProcessConfigOverrides(filename):
+ _RestoreFilters()
+ return
+
+ lf_lines = []
+ crlf_lines = []
+ try:
+ # Support the UNIX convention of using "-" for stdin. Note that
+ # we are not opening the file with universal newline support
+ # (which codecs doesn't support anyway), so the resulting lines do
+ # contain trailing '\r' characters if we are reading a file that
+ # has CRLF endings.
+ # If after the split a trailing '\r' is present, it is removed
+ # below.
+ if filename == '-':
+ lines = codecs.StreamReaderWriter(sys.stdin,
+ codecs.getreader('utf8'),
+ codecs.getwriter('utf8'),
+ 'replace').read().split('\n')
+ else:
+ lines = codecs.open(filename, 'r', 'utf8', 'replace').read().split('\n')
+
+ # Remove trailing '\r'.
+ # The -1 accounts for the extra trailing blank line we get from split()
+ for linenum in range(len(lines) - 1):
+ if lines[linenum].endswith('\r'):
+ lines[linenum] = lines[linenum].rstrip('\r')
+ crlf_lines.append(linenum + 1)
+ else:
+ lf_lines.append(linenum + 1)
+
+ except IOError:
+ sys.stderr.write(
+ "Skipping input '%s': Can't open for reading\n" % filename)
+ _RestoreFilters()
+ return
+
+ # Note, if no dot is found, this will give the entire filename as the ext.
+ file_extension = filename[filename.rfind('.') + 1:]
+
+ # When reading from stdin, the extension is unknown, so no cpplint tests
+ # should rely on the extension.
+ if filename != '-' and file_extension not in _valid_extensions:
+ sys.stderr.write('Ignoring %s; not a valid file name '
+ '(%s)\n' % (filename, ', '.join(_valid_extensions)))
+ else:
+ ProcessFileData(filename, file_extension, lines, Error,
+ extra_check_functions)
+
+ # If end-of-line sequences are a mix of LF and CR-LF, issue
+ # warnings on the lines with CR.
+ #
+ # Don't issue any warnings if all lines are uniformly LF or CR-LF,
+ # since critique can handle these just fine, and the style guide
+ # doesn't dictate a particular end of line sequence.
+ #
+ # We can't depend on os.linesep to determine what the desired
+ # end-of-line sequence should be, since that will return the
+ # server-side end-of-line sequence.
+ if lf_lines and crlf_lines:
+ # Warn on every line with CR. An alternative approach might be to
+ # check whether the file is mostly CRLF or just LF, and warn on the
+ # minority, we bias toward LF here since most tools prefer LF.
+ for linenum in crlf_lines:
+ Error(filename, linenum, 'whitespace/newline', 1,
+ 'Unexpected \\r (^M) found; better to use only \\n')
+
+ # Suppress printing anything if --quiet was passed unless the error
+ # count has increased after processing this file.
+ if not _cpplint_state.quiet or old_errors != _cpplint_state.error_count:
+ sys.stdout.write('Done processing %s\n' % filename)
+ _RestoreFilters()
+
+
+def PrintUsage(message):
+ """Prints a brief usage string and exits, optionally with an error message.
+
+ Args:
+ message: The optional error message.
+ """
+ sys.stderr.write(_USAGE)
+ if message:
+ sys.exit('\nFATAL ERROR: ' + message)
+ else:
+ sys.exit(1)
+
+
+def PrintCategories():
+ """Prints a list of all the error-categories used by error messages.
+
+ These are the categories used to filter messages via --filter.
+ """
+ sys.stderr.write(''.join(' %s\n' % cat for cat in _ERROR_CATEGORIES))
+ sys.exit(0)
+
+
+def ParseArguments(args):
+ """Parses the command line arguments.
+
+ This may set the output format and verbosity level as side-effects.
+
+ Args:
+ args: The command line arguments:
+
+ Returns:
+ The list of filenames to lint.
+ """
+ try:
+ (opts, filenames) = getopt.getopt(args, '', ['help', 'output=', 'verbose=',
+ 'counting=',
+ 'filter=',
+ 'root=',
+ 'linelength=',
+ 'extensions=',
+ 'headers=',
+ 'quiet'])
+ except getopt.GetoptError:
+ PrintUsage('Invalid arguments.')
+
+ verbosity = _VerboseLevel()
+ output_format = _OutputFormat()
+ filters = ''
+ quiet = _Quiet()
+ counting_style = ''
+
+ for (opt, val) in opts:
+ if opt == '--help':
+ PrintUsage(None)
+ elif opt == '--output':
+ if val not in ('emacs', 'vs7', 'eclipse'):
+ PrintUsage('The only allowed output formats are emacs, vs7 and eclipse.')
+ output_format = val
+ elif opt == '--quiet':
+ quiet = True
+ elif opt == '--verbose':
+ verbosity = int(val)
+ elif opt == '--filter':
+ filters = val
+ if not filters:
+ PrintCategories()
+ elif opt == '--counting':
+ if val not in ('total', 'toplevel', 'detailed'):
+ PrintUsage('Valid counting options are total, toplevel, and detailed')
+ counting_style = val
+ elif opt == '--root':
+ global _root
+ _root = val
+ elif opt == '--linelength':
+ global _line_length
+ try:
+ _line_length = int(val)
+ except ValueError:
+ PrintUsage('Line length must be digits.')
+ elif opt == '--extensions':
+ global _valid_extensions
+ try:
+ _valid_extensions = set(val.split(','))
+ except ValueError:
+ PrintUsage('Extensions must be comma seperated list.')
+ elif opt == '--headers':
+ ProcessHppHeadersOption(val)
+
+ if not filenames:
+ PrintUsage('No files were specified.')
+
+ _SetOutputFormat(output_format)
+ _SetQuiet(quiet)
+ _SetVerboseLevel(verbosity)
+ _SetFilters(filters)
+ _SetCountingStyle(counting_style)
+
+ return filenames
+
+
+def main():
+ filenames = ParseArguments(sys.argv[1:])
+
+ # Change stderr to write with replacement characters so we don't die
+ # if we try to print something containing non-ASCII characters.
+ sys.stderr = codecs.StreamReaderWriter(sys.stderr,
+ codecs.getreader('utf8'),
+ codecs.getwriter('utf8'),
+ 'replace')
+
+ _cpplint_state.ResetErrorCounts()
+ for filename in filenames:
+ ProcessFile(filename, _cpplint_state.verbose_level)
+ # If --quiet is passed, suppress printing error count unless there are errors.
+ if not _cpplint_state.quiet or _cpplint_state.error_count > 0:
+ _cpplint_state.PrintErrorCounts()
+
+ sys.exit(_cpplint_state.error_count > 0)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/scripts/if_changed.sh b/scripts/if_changed.sh
new file mode 100755
index 0000000..08ca96d
--- /dev/null
+++ b/scripts/if_changed.sh
@@ -0,0 +1,74 @@
+# Copyright 2018 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.
+
+# Within Travis, runs the given command if the current project has changes
+# worth building.
+#
+# Examines the following Travis-supplied environment variables:
+# - TRAVIS_PULL_REQUEST - the PR number or false for full build
+# - TRAVIS_COMMIT_RANGE - the range of commits under test; empty on a new
+# branch
+#
+# Also examines the following configured environment variables that should be
+# specified in an env: block
+# - PROJECT - Firebase or Firestore
+# - METHOD - xcodebuild or cmake
+
+function check_changes() {
+ if git diff --name-only "$TRAVIS_COMMIT_RANGE" | grep -Eq "$1"; then
+ run=true
+ fi
+}
+
+run=false
+
+# To force Travis to do a full run, change the "false" to "{PR number}" like
+# if [[ "$TRAVIS_PULL_REQUEST" == "904" ]]; then
+if [[ "$TRAVIS_PULL_REQUEST" == "false" ]]; then
+ # Full builds should run everything
+ run=true
+
+elif [[ -z "$TRAVIS_COMMIT_RANGE" ]]; then
+ # First builds on a branch should also run everything
+ run=true
+
+else
+ case "$PROJECT-$METHOD" in
+ Firebase-*)
+ check_changes '^(Firebase|Functions|Example)'
+ ;;
+
+ Firestore-xcodebuild)
+ check_changes '^Firestore/(core|third_party)'
+ ;;
+
+ Firestore-cmake)
+ check_changes '^Firestore'
+ ;;
+
+ *)
+ echo "Unknown project-method combo" 1>&2
+ echo " PROJECT=$PROJECT" 1>&2
+ echo " METHOD=$METHOD" 1>&2
+ exit 1
+ ;;
+ esac
+fi
+
+if [[ "$run" == true ]]; then
+ "$@"
+else
+ echo "skipped $*"
+fi
+
diff --git a/scripts/lint.sh b/scripts/lint.sh
new file mode 100755
index 0000000..9e33c87
--- /dev/null
+++ b/scripts/lint.sh
@@ -0,0 +1,87 @@
+# Copyright 2018 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.
+
+# Lints C++ files for conformance with the Google C++ style guide
+
+# Joins the given arguments with the separator given as the first argument.
+function join() {
+ local IFS="$1"
+ shift
+ echo "$*"
+}
+
+git_options=(
+ -z # \0 terminate output
+)
+
+objc_lint_filters=(
+ # Objective-C uses #import and does not use header guards
+ -build/header_guard
+
+ # Inline definitions of Objective-C blocks confuse
+ -readability/braces
+
+ # C-style casts are acceptable in Objective-C++
+ -readability/casting
+
+ # Objective-C needs use type 'long' for interop between types like NSInteger
+ # and printf-style functions.
+ -runtime/int
+
+ # cpplint is generally confused by Objective-C mixing with C++.
+ # * Objective-C method invocations in a for loop make it think its a
+ # range-for
+ # * Objective-C dictionary literals confuse brace spacing
+ # * Empty category declarations ("@interface Foo ()") look like function
+ # invocations
+ -whitespace
+)
+
+objc_lint_options=(
+ # cpplint normally excludes Objective-C++
+ --extensions=h,m,mm
+
+ # Objective-C style allows longer lines
+ --linelength=100
+
+ --filter=$(join , "${objc_lint_filters[@]}")
+)
+
+if [[ $# -gt 0 ]]; then
+ # Interpret any command-line argument as a revision range
+ command=(git diff --name-only)
+ git_options+=("$@")
+
+else
+ # Default to operating on all files that match the pattern
+ command=(git ls-files)
+fi
+
+# Straight C++ files get regular cpplint
+"${command[@]}" "${git_options[@]}" \
+ -- 'Firestore/core/**/*.'{h,cc} \
+ | xargs -0 python scripts/cpplint.py --quiet 2>&1
+CPP_STATUS=$?
+
+# Objective-C++ files get a looser cpplint
+"${command[@]}" "${git_options[@]}" \
+ -- 'Firestore/Source/**/*.'{h,m,mm} \
+ 'Firestore/Example/Tests/**/*.'{h,m,mm} \
+ 'Firestore/core/**/*.mm' \
+ | xargs -0 python scripts/cpplint.py "${objc_lint_options[@]}" --quiet 2>&1
+OBJC_STATUS=$?
+
+if [[ $CPP_STATUS != 0 || $OBJC_STATUS != 0 ]]; then
+ exit 1
+fi
diff --git a/scripts/style.sh b/scripts/style.sh
index 494a937..d8825f2 100755
--- a/scripts/style.sh
+++ b/scripts/style.sh
@@ -21,13 +21,73 @@
# Commonly
# ./scripts/style.sh master
-set -euo pipefail
+system=$(uname -s)
+version=$(clang-format --version)
+version="${version/*version /}"
+version="${version/.*/}"
+if [[ "$version" != 6 && "$version" != 7 ]]; then
+ # Allow an older clang-format to accommodate Travis version skew.
+ echo "Please upgrade to clang-format version 7."
+ echo "If it's installed via homebrew you can run: brew upgrade clang-format"
+ exit 1
+fi
+
+if [[ "$system" == "Darwin" ]]; then
+ version=$(swiftformat --version)
+ version="${version/*version /}"
+ # Allow an older swiftformat because travis isn't running High Sierra yet
+ # and the formula hasn't been updated in a while on Sierra :-/.
+ if [[ "$version" != "0.32.0" && "$version" != "0.33"* ]]; then
+ echo "Please upgrade to swiftformat 0.33.3"
+ echo "If it's installed via homebrew you can run: brew upgrade swiftformat"
+ exit 1
+ fi
+fi
+
+# Joins the given arguments with the separator given as the first argument.
+function join() {
+ local IFS="$1"
+ shift
+ echo "$*"
+}
+
+clang_options=(-style=file)
+
+# Rules to disable in swiftformat:
+swift_disable=(
+ # sortedImports is broken, sorting into the middle of the copyright notice.
+ sortedImports
+
+ # Too many of our swift files have simplistic examples. While technically
+ # it's correct to remove the unused argument labels, it makes our examples
+ # look wrong.
+ unusedArguments
+)
+
+swift_options=(
+ # Mimic Objective-C style.
+ --indent 2
+
+ --disable $(join , "${swift_disable[@]}")
+)
+
+if [[ $# -gt 0 && "$1" == "test-only" ]]; then
+ test_only=true
+ clang_options+=(-output-replacements-xml)
+ swift_options+=(--dryrun)
+ shift
+else
+ test_only=false
+ clang_options+=(-i)
+fi
+
+files=$(
(
if [[ $# -gt 0 ]]; then
if git rev-parse "$1" -- >& /dev/null; then
# Argument was a branch name show files changed since that branch
- git diff --name-only --relative
+ git diff --name-only --relative --diff-filter=ACMR "$1"
else
# Otherwise assume the passed things are files or directories
find "$@" -type f
@@ -37,20 +97,57 @@ set -euo pipefail
find . -type f
fi
) | sed -E -n '
+# find . includes a leading "./" that git does not include
+s%^./%%
+
# Build outputs
\%/Pods/% d
-\%^./build/% d
+\%^build/% d
+\%^Debug/% d
+\%^Release/% d
# Sources controlled outside this tree
\%/third_party/% d
\%/Firestore/Port/% d
+# Generated source
+\%/Firestore/core/src/firebase/firestore/util/config.h% d
+
+# Sources pulled in by travis bundler
+\%/vendor/bundle/% d
+
# Sources within the tree that are not subject to formatting
-\%^./(Example|Firebase)/(Auth|AuthSamples|Database|Messaging)/% d
+\%^(Example|Firebase)/(Auth|AuthSamples|Database|Messaging)/% d
# Checked-in generated code
\%\.pb(objc|rpc)\.% d
+\%\.pb\.% d
# Format C-ish sources only
-\%\.(h|m|mm|cc)$% p
-' | xargs clang-format -style=file -i
+\%\.(h|m|mm|cc|swift)$% p
+'
+)
+
+needs_formatting=false
+for f in $files; do
+ if [[ "${f: -6}" == '.swift' ]]; then
+ if [[ "$system" == 'Darwin' ]]; then
+ swiftformat "${swift_options[@]}" "$f" 2> /dev/null | grep 'would have updated' > /dev/null
+ else
+ false
+ fi
+ else
+ clang-format "${clang_options[@]}" "$f" | grep "<replacement " > /dev/null
+ fi
+
+ if [[ "$test_only" == true && $? -ne 1 ]]; then
+ echo "$f needs formatting."
+ needs_formatting=true
+ fi
+done
+
+if [[ "$needs_formatting" == true ]]; then
+ echo "Proposed commit is not style compliant."
+ echo "Run scripts/style.sh and git add the result."
+ exit 1
+fi
diff --git a/test.sh b/test.sh
index 627fbad..9b9a4bb 100755
--- a/test.sh
+++ b/test.sh
@@ -13,46 +13,9 @@
set -eo pipefail
-test_iOS() {
- xcodebuild \
- -workspace Example/Firebase.xcworkspace \
- -scheme AllUnitTests_iOS \
- -sdk iphonesimulator \
- -destination 'platform=iOS Simulator,OS=10.3.1,name=iPhone 7' \
- build \
- test \
- ONLY_ACTIVE_ARCH=YES \
- CODE_SIGNING_REQUIRED=NO \
- | xcpretty
-}
+top_dir=$(dirname "${BASH_SOURCE[0]}")
+scripts_dir="$top_dir/scripts"
-test_macOS() {
- xcodebuild \
- -workspace Example/Firebase.xcworkspace \
- -scheme AllUnitTests_macOS \
- -sdk macosx \
- -destination 'platform=OS X,arch=x86_64' \
- build \
- test \
- ONLY_ACTIVE_ARCH=YES \
- CODE_SIGNING_REQUIRED=NO \
- | xcpretty
-}
-
-test_iOS; RESULT=$?
-
-if [ $RESULT != 0 ]; then exit $RESULT; fi
-
-test_macOS; RESULT=$?
-
-if [ $RESULT == 65 ]; then
- echo "xcodebuild exited with 65, retrying"
- sleep 5
-
- test_macOS; RESULT=$?
-fi
-
-if [ $RESULT != 0 ]; then exit $RESULT; fi
-
-# Also test Firestore
-Firestore/test.sh
+$scripts_dir/build.sh Firebase iOS
+$scripts_dir/build.sh Firebase macOS
+$scripts_dir/build.sh Firebase tvOS