diff options
102 files changed, 4174 insertions, 152 deletions
diff --git a/.travis.yml b/.travis.yml index 2f8f497..042fab1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,6 +13,8 @@ before_install: - bundle exec pod install --project-directory=Example --repo-update - bundle exec pod install --project-directory=Firestore/Example --no-repo-update - brew install clang-format + - brew install cmake + - brew install go # Somehow the build for Abseil requires this. - echo "$TRAVIS_COMMIT_RANGE" - echo "$TRAVIS_PULL_REQUEST" - | @@ -37,6 +39,10 @@ script: fi - | if [ $SKIP_FIRESTORE != 1 ]; then + ./scripts/lint.sh # Google C++ style compliance + fi + - | + if [ $SKIP_FIRESTORE != 1 ]; then ./Firestore/test.sh fi diff --git a/Example/Core/Tests/FIROptionsTest.m b/Example/Core/Tests/FIROptionsTest.m index 8170405..5d66ca9 100644 --- a/Example/Core/Tests/FIROptionsTest.m +++ b/Example/Core/Tests/FIROptionsTest.m @@ -128,7 +128,7 @@ extern NSString *const kFIRLibraryVersionID; #pragma clang diagnostic pop } -- (void)testinitWithContentsOfFile { +- (void)testInitWithContentsOfFile { NSString *filePath = [[NSBundle mainBundle] pathForResource:@"GoogleService-Info" ofType:@"plist"]; FIROptions *options = [[FIROptions alloc] initWithContentsOfFile:filePath]; 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 9431ae0..c8400d6 100644 --- a/Example/Firebase.xcodeproj/project.pbxproj +++ b/Example/Firebase.xcodeproj/project.pbxproj @@ -8111,6 +8111,7 @@ 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"; @@ -8129,6 +8130,7 @@ 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 = ""; @@ -8650,6 +8652,7 @@ baseConfigurationReference = 06F3D16439F061DE9973902D /* 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; @@ -8671,6 +8674,7 @@ baseConfigurationReference = D440FB786B320FCF836B508F /* 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; diff --git a/Example/Storage/Tests/Integration/FIRStorageIntegrationTests.m b/Example/Storage/Tests/Integration/FIRStorageIntegrationTests.m index 703c2c6..8e826c9 100644 --- a/Example/Storage/Tests/Integration/FIRStorageIntegrationTests.m +++ b/Example/Storage/Tests/Integration/FIRStorageIntegrationTests.m @@ -33,7 +33,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=**} { 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/FIRNetworkURLSession.m b/Firebase/Core/FIRNetworkURLSession.m index c3da674..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 @@ -651,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 6d52173..75f2ed2 100644 --- a/Firebase/Core/FIROptions.m +++ b/Firebase/Core/FIROptions.m @@ -367,7 +367,7 @@ static NSDictionary *sDefaultOptionsDictionary = nil; } tempAnalyticsOptions[key] = value; } - _analyticsOptionsDictionary = tempAnalyticsOptions; + self->_analyticsOptionsDictionary = tempAnalyticsOptions; }); return _analyticsOptionsDictionary; } @@ -386,7 +386,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]; @@ -397,7 +397,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]; @@ -405,7 +405,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/Database/Persistence/FLevelDBStorageEngine.m b/Firebase/Database/Persistence/FLevelDBStorageEngine.m index 7de9ebf..e49d6bc 100644 --- a/Firebase/Database/Persistence/FLevelDBStorageEngine.m +++ b/Firebase/Database/Persistence/FLevelDBStorageEngine.m @@ -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/third_party/SocketRocket/FSRWebSocket.m b/Firebase/Database/third_party/SocketRocket/FSRWebSocket.m index c80dbb0..9b3dad0 100644 --- a/Firebase/Database/third_party/SocketRocket/FSRWebSocket.m +++ b/Firebase/Database/third_party/SocketRocket/FSRWebSocket.m @@ -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/Public/FIRMessaging.h b/Firebase/Messaging/Public/FIRMessaging.h index 33dd596..31e8625 100644 --- a/Firebase/Messaging/Public/FIRMessaging.h +++ b/Firebase/Messaging/Public/FIRMessaging.h @@ -356,6 +356,11 @@ NS_SWIFT_NAME(Messaging) * 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. 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/FIRStorageGetMetadataTask.m b/Firebase/Storage/FIRStorageGetMetadataTask.m index 78d8a16..2623652 100644 --- a/Firebase/Storage/FIRStorageGetMetadataTask.m +++ b/Firebase/Storage/FIRStorageGetMetadataTask.m @@ -65,7 +65,7 @@ if (callback) { callback(nil, self.error); } - _fetcherCompletion = nil; + self->_fetcherCompletion = nil; return; } @@ -90,7 +90,7 @@ callback(nil, 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..ab25076 100644 --- a/Firebase/Storage/FIRStorageMetadata.m +++ b/Firebase/Storage/FIRStorageMetadata.m @@ -60,8 +60,9 @@ NSURLComponents *components = [[NSURLComponents alloc] init]; components.scheme = kFIRStorageScheme; components.host = kFIRStorageHost; - NSString *path = [FIRStorageUtils GCSEscapedString:_path]; - NSString *fullPath = [NSString stringWithFormat:kFIRStorageFullPathFormat, _bucket, path]; + NSString *path = [FIRStorageUtils GCSEscapedString:self->_path]; + NSString *fullPath = + [NSString stringWithFormat:kFIRStorageFullPathFormat, self->_bucket, path]; components.percentEncodedPath = fullPath; components.query = [NSString stringWithFormat:@"alt=media&token=%@", token]; diff --git a/Firebase/Storage/FIRStorageUpdateMetadataTask.m b/Firebase/Storage/FIRStorageUpdateMetadataTask.m index cf1bf93..fa5955a 100644 --- a/Firebase/Storage/FIRStorageUpdateMetadataTask.m +++ b/Firebase/Storage/FIRStorageUpdateMetadataTask.m @@ -71,7 +71,7 @@ if (callback) { callback(nil, self.error); } - _fetcherCompletion = nil; + self->_fetcherCompletion = nil; return; } @@ -96,7 +96,7 @@ callback(nil, 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..f84c2c7 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; } @@ -166,7 +166,7 @@ [self fireHandlersForStatus:FIRStorageTaskStatusSuccess snapshot:self.snapshot]; [self removeAllObservers]; - _fetcherCompletion = nil; + self->_fetcherCompletion = nil; }; #pragma clang diagnostic pop diff --git a/Firestore/CMakeLists.txt b/Firestore/CMakeLists.txt index 25f27fa..28ac08f 100644 --- a/Firestore/CMakeLists.txt +++ b/Firestore/CMakeLists.txt @@ -68,3 +68,4 @@ include_directories(${FIREBASE_SOURCE_DIR}) include_directories(${FIREBASE_SOURCE_DIR}/Firestore/Protos/nanopb) add_subdirectory(core) +add_subdirectory(Protos) diff --git a/Firestore/Example/Firestore.xcodeproj/project.pbxproj b/Firestore/Example/Firestore.xcodeproj/project.pbxproj index 8d99eb6..3b91c76 100644 --- a/Firestore/Example/Firestore.xcodeproj/project.pbxproj +++ b/Firestore/Example/Firestore.xcodeproj/project.pbxproj @@ -140,10 +140,20 @@ 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 */; }; 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 */; }; @@ -336,10 +346,20 @@ 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; path = 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>"; }; 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>"; }; 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>"; }; @@ -430,6 +450,7 @@ 54764FAC1FAA0C390085E60A /* GoogleTests */ = { isa = PBXGroup; children = ( + AB38D9312023962A000A432D /* auth */, AB380CF7201937B800D97691 /* core */, 54EB764B202277970088B8F3 /* immutable */, AB356EF5200E9D1A0089B766 /* model */, @@ -569,10 +590,15 @@ 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 */, ABF6506B201131F8005F2C74 /* timestamp_test.cc */, ); name = model; @@ -589,6 +615,19 @@ 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 = ( @@ -1252,7 +1291,9 @@ 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 */, B686F2AF2023DDEE0028D6BE /* field_path_test.cc in Sources */, 5492E03120213FFC00B64F25 /* FSTLevelDBSpecTests.mm in Sources */, @@ -1272,11 +1313,13 @@ 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 */, @@ -1284,22 +1327,28 @@ 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 */, 5492E0C82021557E00B64F25 /* FSTDatastoreTests.mm in Sources */, 5492E065202154B900B64F25 /* FSTViewTests.mm in Sources */, 5492E03C2021401F00B64F25 /* XCTestCase+Await.mm in Sources */, + B6152AD7202A53CB000E5744 /* document_key_test.cc in Sources */, 54764FAF1FAA21B90085E60A /* FSTGoogleTestTests.mm 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 */, diff --git a/Firestore/Example/Tests/Local/FSTLevelDBMigrationsTests.mm b/Firestore/Example/Tests/Local/FSTLevelDBMigrationsTests.mm index 8ef0e94..3559d5d 100644 --- a/Firestore/Example/Tests/Local/FSTLevelDBMigrationsTests.mm +++ b/Firestore/Example/Tests/Local/FSTLevelDBMigrationsTests.mm @@ -18,13 +18,18 @@ #include <leveldb/db.h> #import "Firestore/Protos/objc/firestore/local/Target.pbobjc.h" +#import "Firestore/Source/Local/FSTLevelDBKey.h" #import "Firestore/Source/Local/FSTLevelDBMigrations.h" #import "Firestore/Source/Local/FSTLevelDBQueryCache.h" +#import "Firestore/Source/Local/FSTWriteGroup.h" + +#include "Firestore/core/src/firebase/firestore/util/ordered_code.h" #import "Firestore/Example/Tests/Local/FSTPersistenceTestHelpers.h" NS_ASSUME_NONNULL_BEGIN +using firebase::firestore::util::OrderedCode; using leveldb::DB; using leveldb::Options; using leveldb::Status; @@ -70,6 +75,30 @@ using leveldb::Status; XCTAssertGreaterThan(actual, 0, @"Expected to migrate to a schema version > 0"); } +- (void)testCountsQueries { + NSUInteger expected = 50; + FSTWriteGroup *group = [FSTWriteGroup groupWithAction:@"Setup"]; + for (int i = 0; i < expected; i++) { + std::string key = [FSTLevelDBTargetKey keyWithTargetID:i]; + [group setData:"dummy" forKey:key]; + } + // 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"); + [group setData:"dummy" forKey:dummyKey]; + + Status status = [group writeToDB:_db]; + XCTAssertTrue(status.ok(), @"Failed to write targets"); + + [FSTLevelDBMigrations runMigrationsOnDB:_db]; + 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/FSTQueryCacheTests.mm b/Firestore/Example/Tests/Local/FSTQueryCacheTests.mm index 0c6a2a4..6ab655a 100644 --- a/Firestore/Example/Tests/Local/FSTQueryCacheTests.mm +++ b/Firestore/Example/Tests/Local/FSTQueryCacheTests.mm @@ -89,6 +89,7 @@ NS_ASSUME_NONNULL_BEGIN FSTQueryData *data2 = [self queryDataWithQuery:q2]; [self addQueryData:data2]; + XCTAssertEqual(2, [self.queryCache count]); XCTAssertEqualObjects([self.queryCache queryDataForQuery:q1], data1); XCTAssertEqualObjects([self.queryCache queryDataForQuery:q2], data2); @@ -96,10 +97,12 @@ NS_ASSUME_NONNULL_BEGIN [self removeQueryData:data1]; XCTAssertNil([self.queryCache queryDataForQuery:q1]); XCTAssertEqualObjects([self.queryCache queryDataForQuery:q2], data2); + XCTAssertEqual(1, [self.queryCache count]); [self removeQueryData:data2]; XCTAssertNil([self.queryCache queryDataForQuery:q1]); XCTAssertNil([self.queryCache queryDataForQuery:q2]); + XCTAssertEqual(0, [self.queryCache count]); } - (void)testSetQueryToNewValue { diff --git a/Firestore/Example/Tests/Util/FSTHelpers.mm b/Firestore/Example/Tests/Util/FSTHelpers.mm index 406d4dd..452e9a6 100644 --- a/Firestore/Example/Tests/Util/FSTHelpers.mm +++ b/Firestore/Example/Tests/Util/FSTHelpers.mm @@ -17,7 +17,7 @@ #import "Firestore/Example/Tests/Util/FSTHelpers.h" #include <inttypes.h> -#include <vector> +#include <list> #import <FirebaseFirestore/FIRFieldPath.h> #import <FirebaseFirestore/FIRGeoPoint.h> @@ -174,7 +174,7 @@ FSTResourcePath *FSTTestPath(NSString *path) { FSTDocumentKeyReference *FSTTestRef(NSString *projectID, NSString *database, NSString *path) { // This owns the DatabaseIds since we do not have FirestoreClient instance to own them. - static std::vector<DatabaseId> database_ids; + static std::list<DatabaseId> database_ids; database_ids.emplace_back(util::MakeStringView(projectID), util::MakeStringView(database)); return [[FSTDocumentKeyReference alloc] initWithKey:FSTTestDocKey(path) databaseID:&database_ids.back()]; diff --git a/Firestore/Protos/CMakeLists.txt b/Firestore/Protos/CMakeLists.txt new file mode 100644 index 0000000..f20f702 --- /dev/null +++ b/Firestore/Protos/CMakeLists.txt @@ -0,0 +1,45 @@ +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 +) diff --git a/Firestore/Protos/FrameworkMaker.xcodeproj/project.pbxproj b/Firestore/Protos/FrameworkMaker.xcodeproj/project.pbxproj index 51a61b8..2efcb21 100644 --- a/Firestore/Protos/FrameworkMaker.xcodeproj/project.pbxproj +++ b/Firestore/Protos/FrameworkMaker.xcodeproj/project.pbxproj @@ -201,7 +201,7 @@ ); 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 = ( 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/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/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/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/protobuf/any.proto b/Firestore/Protos/protos/google/protobuf/any.proto new file mode 100644 index 0000000..c748667 --- /dev/null +++ b/Firestore/Protos/protos/google/protobuf/any.proto @@ -0,0 +1,149 @@ +// 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 whose content describes the type of the + // serialized protocol buffer message. + // + // For URLs which use the scheme `http`, `https`, or no scheme, the + // following restrictions and interpretations apply: + // + // * If no scheme is provided, `https` is assumed. + // * 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). + // * 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.) + // + // 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/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/Local/FSTLevelDBMigrations.mm b/Firestore/Source/Local/FSTLevelDBMigrations.mm index 49af893..7595c53 100644 --- a/Firestore/Source/Local/FSTLevelDBMigrations.mm +++ b/Firestore/Source/Local/FSTLevelDBMigrations.mm @@ -16,21 +16,22 @@ #include "Firestore/Source/Local/FSTLevelDBMigrations.h" -#include <leveldb/db.h> -#include <leveldb/write_batch.h> +#include "leveldb/write_batch.h" #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/FSTLevelDBQueryCache.h" #import "Firestore/Source/Local/FSTWriteGroup.h" +#import "Firestore/Source/Util/FSTAssert.h" NS_ASSUME_NONNULL_BEGIN // Current version of the schema defined in this file. -static FSTLevelDBSchemaVersion kSchemaVersion = 1; +static FSTLevelDBSchemaVersion kSchemaVersion = 2; using leveldb::DB; +using leveldb::Iterator; using leveldb::Status; using leveldb::Slice; using leveldb::WriteOptions; @@ -57,6 +58,31 @@ static void SaveVersion(FSTLevelDBSchemaVersion version, FSTWriteGroup *group) { [group setData:version_string forKey:key]; } +/** + * 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(std::shared_ptr<DB> db, FSTWriteGroup *group) { + std::unique_ptr<Iterator> it(db->NewIterator([FSTLevelDB standardReadOptions])); + Slice start_key = [FSTLevelDBTargetKey keyPrefix]; + it->Seek(start_key); + + int32_t count = 0; + while (it->Valid() && it->key().starts_with(start_key)) { + count++; + it->Next(); + } + + FSTPBTargetGlobal *targetGlobal = [FSTLevelDBQueryCache readTargetMetadataFromDB:db]; + FSTCAssert(targetGlobal != nil, + @"We should have a metadata row as it was added in an earlier migration"); + targetGlobal.targetCount = count; + [group setMessage:targetGlobal forKey:[FSTLevelDBTargetGlobalKey key]]; +} + @implementation FSTLevelDBMigrations + (FSTLevelDBSchemaVersion)schemaVersionForDB:(std::shared_ptr<DB>)db { @@ -80,6 +106,16 @@ static void SaveVersion(FSTLevelDBSchemaVersion version, FSTWriteGroup *group) { case 0: EnsureTargetGlobal(db, group); // Fallthrough + case 1: + // We need to make sure we have metadata, since we're going to read and modify it + // in this migration. Commit the current transaction and start a new one. Since we're + // committing, we need to save a version. It's safe to save this one, if we crash + // after saving we'll resume from this step when we try to migrate. + SaveVersion(1, group); + [group writeToDB:db]; + group = [FSTWriteGroup groupWithAction:@"Migrations"]; + AddTargetCount(db, group); + // Fallthrough default: if (currentVersion < kSchemaVersion) { SaveVersion(kSchemaVersion, group); diff --git a/Firestore/Source/Local/FSTLevelDBQueryCache.mm b/Firestore/Source/Local/FSTLevelDBQueryCache.mm index b3f4822..fe1bf19 100644 --- a/Firestore/Source/Local/FSTLevelDBQueryCache.mm +++ b/Firestore/Source/Local/FSTLevelDBQueryCache.mm @@ -18,7 +18,6 @@ #include <leveldb/db.h> #include <leveldb/write_batch.h> -#include <string> #import "Firestore/Protos/objc/firestore/local/Target.pbobjc.h" #import "Firestore/Source/Core/FSTQuery.h" @@ -127,31 +126,50 @@ using leveldb::WriteOptions; _db.reset(); } -- (void)addQueryData:(FSTQueryData *)queryData group:(FSTWriteGroup *)group { +- (void)saveQueryData:(FSTQueryData *)queryData group:(FSTWriteGroup *)group { FSTTargetID targetID = queryData.targetID; std::string key = [FSTLevelDBTargetKey keyWithTargetID:targetID]; [group setMessage:[self.serializer encodedQueryData:queryData] forKey:key]; +} + +- (void)saveMetadataInGroup:(FSTWriteGroup *)group { + [group setMessage:self.metadata forKey:[FSTLevelDBTargetGlobalKey key]]; +} + +- (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 group:(FSTWriteGroup *)group { + [self saveQueryData:queryData group:group]; 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]; - BOOL saveMetadata = NO; - FSTPBTargetGlobal *metadata = self.metadata; - if (targetID > metadata.highestTargetId) { - metadata.highestTargetId = targetID; - saveMetadata = YES; - } + self.metadata.targetCount += 1; + [self updateMetadataForQueryData:queryData]; + [self saveMetadataInGroup:group]; +} - if (queryData.sequenceNumber > metadata.highestListenSequenceNumber) { - metadata.highestListenSequenceNumber = queryData.sequenceNumber; - saveMetadata = YES; - } +- (void)updateQueryData:(FSTQueryData *)queryData group:(FSTWriteGroup *)group { + [self saveQueryData:queryData group:group]; - if (saveMetadata) { - [group setMessage:metadata forKey:[FSTLevelDBTargetGlobalKey key]]; + if ([self updateMetadataForQueryData:queryData]) { + [self saveMetadataInGroup:group]; } } @@ -166,6 +184,12 @@ using leveldb::WriteOptions; std::string indexKey = [FSTLevelDBQueryTargetKey keyWithCanonicalID:queryData.query.canonicalID targetID:targetID]; [group removeMessageForKey:indexKey]; + self.metadata.targetCount -= 1; + [self saveMetadataInGroup:group]; +} + +- (int32_t)count { + return self.metadata.targetCount; } /** diff --git a/Firestore/Source/Local/FSTLocalStore.mm b/Firestore/Source/Local/FSTLocalStore.mm index d30177a..8a383e5 100644 --- a/Firestore/Source/Local/FSTLocalStore.mm +++ b/Firestore/Source/Local/FSTLocalStore.mm @@ -312,7 +312,7 @@ NS_ASSUME_NONNULL_BEGIN queryData = [queryData queryDataByReplacingSnapshotVersion:change.snapshotVersion resumeToken:resumeToken]; self.targetIDs[targetIDNumber] = queryData; - [self.queryCache addQueryData:queryData group:group]; + [self.queryCache updateQueryData:queryData group:group]; } }]; diff --git a/Firestore/Source/Local/FSTMemoryQueryCache.mm b/Firestore/Source/Local/FSTMemoryQueryCache.mm index bcab174..56d5699 100644 --- a/Firestore/Source/Local/FSTMemoryQueryCache.mm +++ b/Firestore/Source/Local/FSTMemoryQueryCache.mm @@ -90,6 +90,20 @@ NS_ASSUME_NONNULL_BEGIN } } +- (void)updateQueryData:(FSTQueryData *)queryData group:(FSTWriteGroup *)group { + 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 { [self.queries removeObjectForKey:queryData.query]; [self.references removeReferencesForID:queryData.targetID]; diff --git a/Firestore/Source/Local/FSTQueryCache.h b/Firestore/Source/Local/FSTQueryCache.h index 88c9df9..5c43de4 100644 --- a/Firestore/Source/Local/FSTQueryCache.h +++ b/Firestore/Source/Local/FSTQueryCache.h @@ -78,18 +78,29 @@ NS_ASSUME_NONNULL_BEGIN group:(FSTWriteGroup *)group; /** - * 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; +/** + * 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 group:(FSTWriteGroup *)group; + /** Removes the cached entry for the given query data (no-op if no entry exists). */ - (void)removeQueryData:(FSTQueryData *)queryData group:(FSTWriteGroup *)group; +/** Returns the number of targets cached. */ +- (int32_t)count; + /** * Looks up an FSTQueryData entry in the cache. * diff --git a/Firestore/Source/Remote/FSTStream.mm b/Firestore/Source/Remote/FSTStream.mm index e5cbf87..c1479c5 100644 --- a/Firestore/Source/Remote/FSTStream.mm +++ b/Firestore/Source/Remote/FSTStream.mm @@ -548,6 +548,7 @@ static const NSTimeInterval kIdleTimeout = 60.0; FSTStrongify(self); if (![self isStarted]) { FSTLog(@"%@ Ignoring stream message from inactive stream.", NSStringFromClass([self class])); + return; } if (!self.messageReceived) { diff --git a/Firestore/core/CMakeLists.txt b/Firestore/core/CMakeLists.txt index 2fc88c6..e70647d 100644 --- a/Firestore/core/CMakeLists.txt +++ b/Firestore/core/CMakeLists.txt @@ -13,6 +13,7 @@ # limitations under the License. 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/model) @@ -20,6 +21,7 @@ add_subdirectory(src/firebase/firestore/remote) add_subdirectory(src/firebase/firestore/util) 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/model) 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..58310b5 --- /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 decl's 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 decl's 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 straightfoward 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/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..2a52c99 --- /dev/null +++ b/Firestore/core/src/firebase/firestore/auth/credentials_provider.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_CREDENTIALS_PROVIDER_H_ +#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_AUTH_CREDENTIALS_PROVIDER_H_ + +#include <functional> +#include <string> + +#include "Firestore/core/src/firebase/firestore/auth/token.h" +#include "Firestore/core/src/firebase/firestore/auth/user.h" +#include "absl/strings/string_view.h" + +namespace firebase { +namespace firestore { +namespace auth { + +// `TokenErrorListener` is a listener that gets a token or an error. +// token: An auth token as a string, or nullptr if error occurred. +// error: The error if one occurred, or else nullptr. +typedef std::function<void(const Token& token, const absl::string_view error)> + TokenListener; + +// Listener notified with a User change. +typedef std::function<void(const User& user)> UserChangeListener; + +/** + * 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..6ee7f61 --- /dev/null +++ b/Firestore/core/src/firebase/firestore/auth/empty_credentials_provider.cc @@ -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. + */ + +#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) { + completion({"", User::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..65c4c65 --- /dev/null +++ b/Firestore/core/src/firebase/firestore/auth/firebase_credentials_provider_apple.h @@ -0,0 +1,112 @@ +/* + * 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 "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, const absl::string_view uid) + : app(app), current_user(uid), mutex() { + } + + 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..f463958 --- /dev/null +++ b/Firestore/core/src/firebase/firestore/auth/firebase_credentials_provider_apple.mm @@ -0,0 +1,134 @@ +/* + * 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" + +namespace firebase { +namespace firestore { +namespace auth { + +FirebaseCredentialsProvider::FirebaseCredentialsProvider(FIRApp* app) + : contents_( + std::make_shared<Contents>(app, util::MakeStringView([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 notifiying 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(util::MakeStringView(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({"", User::Unauthenticated()}, + "getToken aborted due to user change."); + } else { + completion( + {util::MakeStringView(token), contents->current_user}, + error == nil ? "" : 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..0618ddb --- /dev/null +++ b/Firestore/core/src/firebase/firestore/auth/token.cc @@ -0,0 +1,29 @@ +/* + * 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) { +} + +} // 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..f3b7363 --- /dev/null +++ b/Firestore/core/src/firebase/firestore/auth/token.h @@ -0,0 +1,68 @@ +/* + * 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 "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(const absl::string_view token, const User& user); + + /** The actual raw token. */ + const std::string& token() const { + return token_; + } + + /** + * The user with which the token is associated (used for persisting user + * state on disk, etc.). + */ + const User& user() const { + return user_; + } + + 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..f442d7b --- /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() : uid_(), 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..58b8b55 --- /dev/null +++ b/Firestore/core/src/firebase/firestore/auth/user.h @@ -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. + */ + +#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_AUTH_USER_H_ +#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_AUTH_USER_H_ + +#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(const 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(); + + 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); +} + +} // namespace auth +} // namespace firestore +} // namespace firebase + +#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_AUTH_USER_H_ diff --git a/Firestore/core/src/firebase/firestore/model/CMakeLists.txt b/Firestore/core/src/firebase/firestore/model/CMakeLists.txt index 8bdbe18..1b0e6a4 100644 --- a/Firestore/core/src/firebase/firestore/model/CMakeLists.txt +++ b/Firestore/core/src/firebase/firestore/model/CMakeLists.txt @@ -18,10 +18,22 @@ cc_library( base_path.h database_id.cc database_id.h + document.cc + document.h + document_key.cc + document_key.h field_path.cc field_path.h field_value.cc field_value.h + maybe_document.cc + maybe_document.h + no_document.cc + no_document.h + resource_path.cc + resource_path.h + snapshot_version.cc + snapshot_version.h timestamp.cc timestamp.h DEPENDS diff --git a/Firestore/core/src/firebase/firestore/model/base_path.h b/Firestore/core/src/firebase/firestore/model/base_path.h index f5a8ab7..accce27 100644 --- a/Firestore/core/src/firebase/firestore/model/base_path.h +++ b/Firestore/core/src/firebase/firestore/model/base_path.h @@ -166,7 +166,7 @@ class BasePath { } BasePath(std::initializer_list<std::string> list) : segments_{list} { } - BasePath(SegmentsT&& segments) : segments_{std::move(segments)} { + explicit BasePath(SegmentsT&& segments) : segments_{std::move(segments)} { } private: 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..16548cd --- /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; + } + const Document& 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..6eb1e18 --- /dev/null +++ b/Firestore/core/src/firebase/firestore/model/document_key.h @@ -0,0 +1,101 @@ +/* + * 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> + +#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); + + /** + * 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_path.cc b/Firestore/core/src/firebase/firestore/model/field_path.cc index 6c40600..bc0e97c 100644 --- a/Firestore/core/src/firebase/firestore/model/field_path.cc +++ b/Firestore/core/src/firebase/firestore/model/field_path.cc @@ -30,9 +30,6 @@ namespace model { namespace { -// TODO(varconst): move to C++ equivalent of FSTDocumentKey.{h,cc} -const char* const kDocumentKeyPath = "__name__"; - /** * 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_]* @@ -51,7 +48,7 @@ bool IsValidIdentifier(const std::string& segment) { (first < 'A' || first > 'Z')) { return false; } - for (int i = 1; i != segment.size(); ++i) { + 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')) { @@ -93,7 +90,7 @@ FieldPath FieldPath::FromServerFormat(const absl::string_view path) { // Inside backticks, dots are treated literally. bool inside_backticks = false; - int i = 0; + size_t i = 0; while (i < path.size()) { const char c = path[i]; // std::string (and string_view) may contain embedded nulls. For full @@ -146,12 +143,12 @@ const FieldPath& FieldPath::EmptyPath() { } const FieldPath& FieldPath::KeyFieldPath() { - static const FieldPath key_field_path{kDocumentKeyPath}; + static const FieldPath key_field_path{FieldPath::kDocumentKeyPath}; return key_field_path; } bool FieldPath::IsKeyFieldPath() const { - return size() == 1 && first_segment() == kDocumentKeyPath; + return size() == 1 && first_segment() == FieldPath::kDocumentKeyPath; } std::string FieldPath::CanonicalString() const { diff --git a/Firestore/core/src/firebase/firestore/model/field_path.h b/Firestore/core/src/firebase/firestore/model/field_path.h index 00b658a..fdf4918 100644 --- a/Firestore/core/src/firebase/firestore/model/field_path.h +++ b/Firestore/core/src/firebase/firestore/model/field_path.h @@ -35,6 +35,9 @@ namespace model { */ 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>() { } @@ -82,7 +85,7 @@ class FieldPath : public impl::BasePath<FieldPath> { } private: - FieldPath(SegmentsT&& segments) : BasePath{std::move(segments)} { + explicit FieldPath(SegmentsT&& segments) : BasePath{std::move(segments)} { } // So that methods of base can construct FieldPath using the private diff --git a/Firestore/core/src/firebase/firestore/model/field_value.cc b/Firestore/core/src/firebase/firestore/model/field_value.cc index 570226e..012a90d 100644 --- a/Firestore/core/src/firebase/firestore/model/field_value.cc +++ b/Firestore/core/src/firebase/firestore/model/field_value.cc @@ -99,6 +99,9 @@ FieldValue& FieldValue::operator=(const FieldValue& 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; @@ -131,6 +134,11 @@ FieldValue& FieldValue::operator=(FieldValue&& value) { 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_); @@ -233,6 +241,26 @@ FieldValue FieldValue::BlobValue(const uint8_t* source, size_t size) { 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); @@ -309,6 +337,12 @@ bool operator<(const FieldValue& lhs, const FieldValue& rhs) { 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: @@ -343,6 +377,9 @@ void FieldValue::SwitchTo(const Type type) { case Type::Blob: blob_value_.~vector(); break; + case Type::Reference: + reference_value_.~ReferenceValue(); + break; case Type::GeoPoint: geo_point_value_.~GeoPoint(); break; @@ -370,6 +407,10 @@ void FieldValue::SwitchTo(const Type type) { // 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; diff --git a/Firestore/core/src/firebase/firestore/model/field_value.h b/Firestore/core/src/firebase/firestore/model/field_value.h index 4cd0b3d..e547be3 100644 --- a/Firestore/core/src/firebase/firestore/model/field_value.h +++ b/Firestore/core/src/firebase/firestore/model/field_value.h @@ -25,6 +25,8 @@ #include <vector> #include "Firestore/core/include/firebase/firestore/geo_point.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/timestamp.h" namespace firebase { @@ -38,6 +40,12 @@ struct ServerTimestamp { bool has_previous_value_; }; +struct ReferenceValue { + DocumentKey reference; + // Does not own the DatabaseId instance. + const DatabaseId* database_id; +}; + /** * tagged-union class representing an immutable data value as stored in * Firestore. FieldValue represents all the different kinds of values @@ -69,7 +77,7 @@ class FieldValue { // position instead, see the doc comment above. }; - FieldValue() : tag_(Type::Null) { + FieldValue() { } // Do not inline these ctor/dtor below, which contain call to non-trivial @@ -103,7 +111,10 @@ class FieldValue { 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(); + 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); @@ -123,7 +134,7 @@ class FieldValue { */ void SwitchTo(const Type type); - Type tag_; + Type tag_ = Type::Null; union { // There is no null type as tag_ alone is enough for Null FieldValue. bool boolean_value_; @@ -133,6 +144,8 @@ class FieldValue { 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_; std::map<const std::string, const FieldValue> object_value_; 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/resource_path.cc b/Firestore/core/src/firebase/firestore/model/resource_path.cc index 36218e9..c95aa63 100644 --- a/Firestore/core/src/firebase/firestore/model/resource_path.cc +++ b/Firestore/core/src/firebase/firestore/model/resource_path.cc @@ -18,6 +18,7 @@ #include <algorithm> #include <utility> +#include <vector> #include "Firestore/core/src/firebase/firestore/util/firebase_assert.h" #include "absl/strings/str_join.h" @@ -27,7 +28,7 @@ namespace firebase { namespace firestore { namespace model { -ResourcePath ResourcePath::Parse(const absl::string_view path) { +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). diff --git a/Firestore/core/src/firebase/firestore/model/resource_path.h b/Firestore/core/src/firebase/firestore/model/resource_path.h index 481d32f..53c1951 100644 --- a/Firestore/core/src/firebase/firestore/model/resource_path.h +++ b/Firestore/core/src/firebase/firestore/model/resource_path.h @@ -19,6 +19,7 @@ #include <initializer_list> #include <string> +#include <utility> #include "Firestore/core/src/firebase/firestore/model/base_path.h" #include "absl/strings/string_view.h" @@ -44,7 +45,7 @@ class ResourcePath : public impl::BasePath<ResourcePath> { * Creates and returns a new path from the given resource-path string, where * the path segments are separated by a slash "/". */ - static ResourcePath Parse(absl::string_view path); + static ResourcePath FromString(absl::string_view path); /** Returns a standardized string representation of this path. */ std::string CanonicalString() const; @@ -69,7 +70,7 @@ class ResourcePath : public impl::BasePath<ResourcePath> { } private: - ResourcePath(SegmentsT&& segments) : BasePath{std::move(segments)} { + explicit ResourcePath(SegmentsT&& segments) : BasePath{std::move(segments)} { } // So that methods of base can construct ResourcePath using the private @@ -81,4 +82,4 @@ class ResourcePath : public impl::BasePath<ResourcePath> { } // namespace firestore } // namespace firebase -#endif +#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..70f6f4a --- /dev/null +++ b/Firestore/core/src/firebase/firestore/model/snapshot_version.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_MODEL_SNAPSHOT_VERSION_H_ +#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_SNAPSHOT_VERSION_H_ + +#include "Firestore/core/src/firebase/firestore/model/timestamp.h" + +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(); + + 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/remote/CMakeLists.txt b/Firestore/core/src/firebase/firestore/remote/CMakeLists.txt index a218e3b..7f528fb 100644 --- a/Firestore/core/src/firebase/firestore/remote/CMakeLists.txt +++ b/Firestore/core/src/firebase/firestore/remote/CMakeLists.txt @@ -20,6 +20,8 @@ cc_library( serializer.h serializer.cc DEPENDS + firebase_firestore_model + firebase_firestore_protos_nanopb grpc::grpc nanopb ) diff --git a/Firestore/core/src/firebase/firestore/remote/serializer.cc b/Firestore/core/src/firebase/firestore/remote/serializer.cc index d3cdd3f..e503d09 100644 --- a/Firestore/core/src/firebase/firestore/remote/serializer.cc +++ b/Firestore/core/src/firebase/firestore/remote/serializer.cc @@ -16,16 +16,94 @@ #include "Firestore/core/src/firebase/firestore/remote/serializer.h" -// TODO(rsgowman): These are (currently!) unnecessary includes. Adding for now -// to ensure we can find nanopb's generated header files. -#include "Firestore/Protos/nanopb/google/firestore/v1beta1/document.pb.h" -#include "Firestore/Protos/nanopb/google/protobuf/timestamp.pb.h" +#include <pb_decode.h> +#include <pb_encode.h> namespace firebase { namespace firestore { namespace remote { -Serializer::Serializer() { +using firebase::firestore::model::FieldValue; + +Serializer::TypedValue Serializer::EncodeFieldValue( + const FieldValue& field_value) { + Serializer::TypedValue proto_value{ + field_value.type(), google_firestore_v1beta1_Value_init_default}; + switch (field_value.type()) { + case FieldValue::Type::Null: + proto_value.value.null_value = google_protobuf_NullValue_NULL_VALUE; + break; + default: + // TODO(rsgowman): implement the other types + abort(); + } + return proto_value; +} + +void Serializer::EncodeTypedValue(const TypedValue& value, + std::vector<uint8_t>* out_bytes) { + bool status; + // TODO(rsgowman): how large should the output buffer be? Do some + // investigation to see if we can get nanopb to tell us how much space it's + // going to need. + uint8_t buf[1024]; + pb_ostream_t stream = pb_ostream_from_buffer(buf, sizeof(buf)); + switch (value.type) { + case FieldValue::Type::Null: + status = pb_encode_tag(&stream, PB_WT_VARINT, + google_firestore_v1beta1_Value_null_value_tag); + if (!status) { + // TODO(rsgowman): figure out error handling + abort(); + } + + status = pb_encode_varint(&stream, value.value.null_value); + if (!status) { + // TODO(rsgowman): figure out error handling + abort(); + } + + out_bytes->insert(out_bytes->end(), buf, buf + stream.bytes_written); + + break; + + default: + // TODO(rsgowman): implement the other types + abort(); + } +} + +FieldValue Serializer::DecodeFieldValue( + const Serializer::TypedValue& value_proto) { + switch (value_proto.type) { + case FieldValue::Type::Null: + return FieldValue::NullValue(); + default: + // TODO(rsgowman): implement the other types + abort(); + } +} + +Serializer::TypedValue Serializer::DecodeTypedValue(const uint8_t* bytes, + size_t length) { + pb_istream_t stream = pb_istream_from_buffer(bytes, length); + pb_wire_type_t wire_type; + uint32_t tag; + bool eof; + bool status = pb_decode_tag(&stream, &wire_type, &tag, &eof); + if (!status || wire_type != PB_WT_VARINT) { + // TODO(rsgowman): figure out error handling + abort(); + } + + switch (tag) { + case google_firestore_v1beta1_Value_null_value_tag: + return Serializer::TypedValue{ + FieldValue::Type::Null, google_firestore_v1beta1_Value_init_default}; + default: + // TODO(rsgowman): figure out error handling + abort(); + } } } // namespace remote diff --git a/Firestore/core/src/firebase/firestore/remote/serializer.h b/Firestore/core/src/firebase/firestore/remote/serializer.h index 4dc6b9e..518cff4 100644 --- a/Firestore/core/src/firebase/firestore/remote/serializer.h +++ b/Firestore/core/src/firebase/firestore/remote/serializer.h @@ -17,16 +17,26 @@ #ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_REMOTE_SERIALIZER_H_ #define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_REMOTE_SERIALIZER_H_ +#include <stdint.h> +#include <stdlib.h> +#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" + namespace firebase { namespace firestore { namespace remote { /** * @brief Converts internal model objects to their equivalent protocol buffer - * form. + * form, and protocol buffer objects to their equivalent bytes. * - * Methods starting with "Encode" convert to a protocol buffer and methods - * starting with "Decode" convert from a protocol buffer. + * Methods starting with "Encode" either convert from a model object to a + * protocol buffer or from a protocol buffer to bytes, and methods starting with + * "Decode" either convert from a protocol buffer to a model object or from + * bytes to a protocol buffer. * */ // TODO(rsgowman): Original docs also has this: "Throws an exception if a @@ -34,10 +44,102 @@ namespace remote { // interpret." Adjust for C++. class Serializer { public: - // TODO(rsgowman): Adjust ctor to accept a DatabaseID... once that exists. - Serializer(/*DatabaseID databaseId*/); + /** + * @brief Wraps (nanopb) google_firestore_v1beta1_Value with type information. + */ + struct TypedValue { + firebase::firestore::model::FieldValue::Type type; + google_firestore_v1beta1_Value value; + }; + + 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 the Value proto equivalent. + * + * @param field_value the model to convert. + * @return the proto representation of the model. + */ + static Serializer::TypedValue EncodeFieldValue( + const firebase::firestore::model::FieldValue& field_value); + + /** + * @brief Converts the value proto passed into bytes. + * + * @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. + static void EncodeTypedValue(const TypedValue& value, + std::vector<uint8_t>* out_bytes); + + /** + * Converts from the proto Value format to the model FieldValue format + * + * @return The model equivalent of the proto data. + */ + static firebase::firestore::model::FieldValue DecodeFieldValue( + const Serializer::TypedValue& value_proto); + + /** + * @brief Converts from bytes to the nanopb proto. + * + * @param bytes The bytes to convert. It's assumed that exactly all of the + * bytes will be used by this conversion. + * @return The (nanopb) proto equivalent of the bytes. + */ + // TODO(rsgowman): error handling. + static TypedValue DecodeTypedValue(const uint8_t* bytes, size_t length); + + /** + * @brief Converts from bytes to the nanopb proto. + * + * @param bytes The bytes to convert. It's assumed that exactly all of the + * bytes will be used by this conversion. + * @return The (nanopb) proto equivalent of the bytes. + */ + // TODO(rsgowman): error handling. + static TypedValue DecodeTypedValue(const std::vector<uint8_t>& bytes) { + return DecodeTypedValue(bytes.data(), bytes.size()); + } + + private: + // TODO(rsgowman): We don't need the database_id_ yet (but will eventually). + // const firebase::firestore::model::DatabaseId& database_id_; }; +inline bool operator==(const Serializer::TypedValue& lhs, + const Serializer::TypedValue& rhs) { + if (lhs.type != rhs.type) { + return false; + } + + switch (lhs.type) { + case firebase::firestore::model::FieldValue::Type::Null: + FIREBASE_DEV_ASSERT(lhs.value.null_value == + google_protobuf_NullValue_NULL_VALUE); + FIREBASE_DEV_ASSERT(rhs.value.null_value == + google_protobuf_NullValue_NULL_VALUE); + return true; + default: + // TODO(rsgowman): implement the other types + abort(); + } +} + } // namespace remote } // namespace firestore } // namespace firebase diff --git a/Firestore/core/src/firebase/firestore/util/firebase_assert.h b/Firestore/core/src/firebase/firestore/util/firebase_assert.h index 993f27a..76768e6 100644 --- a/Firestore/core/src/firebase/firestore/util/firebase_assert.h +++ b/Firestore/core/src/firebase/firestore/util/firebase_assert.h @@ -91,6 +91,17 @@ 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__) + namespace firebase { namespace firestore { namespace util { 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..f470bd7 --- /dev/null +++ b/Firestore/core/test/firebase/firestore/auth/CMakeLists.txt @@ -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. + +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 + ) +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..1748422 --- /dev/null +++ b/Firestore/core/test/firebase/firestore/auth/credentials_provider_test.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 "Firestore/core/src/firebase/firestore/auth/credentials_provider.h" + +#include "gtest/gtest.h" + +namespace firebase { +namespace firestore { +namespace auth { + +#define UNUSED(x) (void)(x) + +TEST(CredentialsProvider, Typedef) { + TokenListener token_listener = [](const Token& token, + const absl::string_view error) { + UNUSED(token); + UNUSED(error); + }; + 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 = [](const 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..123f952 --- /dev/null +++ b/Firestore/core/test/firebase/firestore/auth/empty_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/empty_credentials_provider.h" + +#include "gtest/gtest.h" + +namespace firebase { +namespace firestore { +namespace auth { + +TEST(EmptyCredentialsProvider, GetToken) { + EmptyCredentialsProvider credentials_provider; + credentials_provider.GetToken( + /*force_refresh=*/true, + [](const Token& token, const absl::string_view error) { + EXPECT_EQ("", token.token()); + const User& user = token.user(); + EXPECT_EQ("", user.uid()); + EXPECT_FALSE(user.is_authenticated()); + EXPECT_EQ("", error); + }); +} + +TEST(EmptyCredentialsProvider, SetListener) { + EmptyCredentialsProvider credentials_provider; + credentials_provider.SetUserChangeListener([](const 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..8d2b361 --- /dev/null +++ b/Firestore/core/test/firebase/firestore/auth/firebase_credentials_provider_test.mm @@ -0,0 +1,98 @@ +/* + * 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/string_apple.h" + +#include "gtest/gtest.h" + +namespace firebase { +namespace firestore { +namespace auth { + +// TODO(zxu123): Make this an integration test and get infos from environment. +// Set a .plist file here to enable the test-case. +static NSString* const kPlist = @""; + +class FirebaseCredentialsProviderTest : public ::testing::Test { + protected: + void SetUp() override { + app_ready_ = false; + if (![kPlist hasSuffix:@".plist"]) { + return; + } + + static dispatch_once_t once_token; + dispatch_once(&once_token, ^{ + FIROptions* options = [[FIROptions alloc] initWithContentsOfFile:kPlist]; + [FIRApp configureWithOptions:options]; + }); + + // Set getUID implementation. + FIRApp* default_app = [FIRApp defaultApp]; + default_app.getUIDImplementation = ^NSString* { + return @"I'm a fake uid."; + }; + app_ready_ = true; + } + + bool app_ready_; +}; + +// Set kPlist above before enable. +TEST_F(FirebaseCredentialsProviderTest, GetToken) { + if (!app_ready_) { + return; + } + + FirebaseCredentialsProvider credentials_provider([FIRApp defaultApp]); + credentials_provider.GetToken( + /*force_refresh=*/true, + [](const Token& token, const absl::string_view error) { + EXPECT_EQ("", token.token()); + const User& user = token.user(); + EXPECT_EQ("I'm a fake uid.", user.uid()); + EXPECT_TRUE(user.is_authenticated()); + EXPECT_EQ("", error) << error; + }); +} + +// Set kPlist above before enable. +TEST_F(FirebaseCredentialsProviderTest, SetListener) { + if (!app_ready_) { + return; + } + + FirebaseCredentialsProvider credentials_provider([FIRApp defaultApp]); + credentials_provider.SetUserChangeListener([](const User& user) { + EXPECT_EQ("I'm a fake uid.", user.uid()); + EXPECT_TRUE(user.is_authenticated()); + }); + + // TODO(wilhuff): We should wait for the above expectations to actually happen + // before continuing. + + 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..a0f2c48 --- /dev/null +++ b/Firestore/core/test/firebase/firestore/auth/token_test.cc @@ -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. + */ + +#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()); +} + +} // 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..a9f764d --- /dev/null +++ b/Firestore/core/test/firebase/firestore/auth/user_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/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")); +} + +} // namespace auth +} // namespace firestore +} // namespace firebase diff --git a/Firestore/core/test/firebase/firestore/model/CMakeLists.txt b/Firestore/core/test/firebase/firestore/model/CMakeLists.txt index 63ed813..0d581bc 100644 --- a/Firestore/core/test/firebase/firestore/model/CMakeLists.txt +++ b/Firestore/core/test/firebase/firestore/model/CMakeLists.txt @@ -16,10 +16,15 @@ cc_test( firebase_firestore_model_test SOURCES database_id_test.cc + document_key_test.cc + document_test.cc field_path_test.cc field_value_test.cc - timestamp_test.cc + maybe_document_test.cc + no_document_test.cc resource_path_test.cc + snapshot_version_test.cc + timestamp_test.cc DEPENDS firebase_firestore_model ) 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..0e0df2d --- /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()); + 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); + 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); + 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..6b72360 --- /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::ObjectValue( + {{"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::ObjectValue({{"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::ObjectValue({}), + 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_path_test.cc b/Firestore/core/test/firebase/firestore/model/field_path_test.cc index 7c7e0a3..a5ae3b2 100644 --- a/Firestore/core/test/firebase/firestore/model/field_path_test.cc +++ b/Firestore/core/test/firebase/firestore/model/field_path_test.cc @@ -29,18 +29,18 @@ namespace model { TEST(FieldPath, Constructors) { const FieldPath empty_path; EXPECT_TRUE(empty_path.empty()); - EXPECT_EQ(0, empty_path.size()); + 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(3, path_from_list.size()); + 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(3, path_from_segments.size()); + EXPECT_EQ(3u, path_from_segments.size()); EXPECT_TRUE(path_from_segments.begin() + 3 == path_from_segments.end()); FieldPath copied = path_from_list; @@ -168,13 +168,13 @@ TEST(FieldPath, IsPrefixOf) { TEST(FieldPath, AccessFailures) { const FieldPath path; - ASSERT_DEATH_IF_SUPPORTED(path.first_segment(), ""); - ASSERT_DEATH_IF_SUPPORTED(path.last_segment(), ""); - ASSERT_DEATH_IF_SUPPORTED(path[0], ""); - ASSERT_DEATH_IF_SUPPORTED(path[1], ""); - ASSERT_DEATH_IF_SUPPORTED(path.PopFirst(), ""); - ASSERT_DEATH_IF_SUPPORTED(path.PopFirst(2), ""); - ASSERT_DEATH_IF_SUPPORTED(path.PopLast(), ""); + 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) { @@ -201,7 +201,7 @@ TEST(FieldPath, Parsing) { const auto path_with_dot = FieldPath::FromServerFormat(R"(foo\.bar)"); EXPECT_EQ(path_with_dot.CanonicalString(), "`foo.bar`"); - EXPECT_EQ(path_with_dot.size(), 1); + EXPECT_EQ(path_with_dot.size(), 1u); } // This is a special case in C++: std::string may contain embedded nulls. To @@ -213,22 +213,22 @@ TEST(FieldPath, ParseEmbeddedNull) { str += ".bar"; const auto path = FieldPath::FromServerFormat(str); - EXPECT_EQ(path.size(), 1); + EXPECT_EQ(path.size(), 1u); EXPECT_EQ(path.CanonicalString(), "foo"); } TEST(FieldPath, ParseFailures) { - ASSERT_DEATH_IF_SUPPORTED(FieldPath::FromServerFormat(""), ""); - ASSERT_DEATH_IF_SUPPORTED(FieldPath::FromServerFormat("."), ""); - ASSERT_DEATH_IF_SUPPORTED(FieldPath::FromServerFormat(".."), ""); - ASSERT_DEATH_IF_SUPPORTED(FieldPath::FromServerFormat("foo."), ""); - ASSERT_DEATH_IF_SUPPORTED(FieldPath::FromServerFormat(".bar"), ""); - ASSERT_DEATH_IF_SUPPORTED(FieldPath::FromServerFormat("foo..bar"), ""); - ASSERT_DEATH_IF_SUPPORTED(FieldPath::FromServerFormat(R"(foo\)"), ""); - ASSERT_DEATH_IF_SUPPORTED(FieldPath::FromServerFormat(R"(foo.\)"), ""); - ASSERT_DEATH_IF_SUPPORTED(FieldPath::FromServerFormat("foo`"), ""); - ASSERT_DEATH_IF_SUPPORTED(FieldPath::FromServerFormat("foo```"), ""); - ASSERT_DEATH_IF_SUPPORTED(FieldPath::FromServerFormat("`foo"), ""); + 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) { diff --git a/Firestore/core/test/firebase/firestore/model/field_value_test.cc b/Firestore/core/test/firebase/firestore/model/field_value_test.cc index 702c0f6..86eb804 100644 --- a/Firestore/core/test/firebase/firestore/model/field_value_test.cc +++ b/Firestore/core/test/firebase/firestore/model/field_value_test.cc @@ -144,6 +144,20 @@ TEST(FieldValue, BlobType) { 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}); @@ -280,6 +294,23 @@ TEST(FieldValue, Copy) { 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); @@ -361,7 +392,7 @@ TEST(FieldValue, Move) { clone = FieldValue::NullValue(); EXPECT_EQ(FieldValue::NullValue(), clone); - const FieldValue timestamp_value = FieldValue::TimestampValue({100, 200}); + FieldValue timestamp_value = FieldValue::TimestampValue({100, 200}); clone = std::move(timestamp_value); EXPECT_EQ(FieldValue::TimestampValue({100, 200}), clone); clone = FieldValue::NullValue(); @@ -373,13 +404,23 @@ TEST(FieldValue, Move) { clone = FieldValue::NullValue(); EXPECT_EQ(FieldValue::NullValue(), clone); - const FieldValue blob_value = FieldValue::BlobValue(Bytes("abc"), 4); + 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 FieldValue geo_point_value = FieldValue::GeoPointValue({1, 2}); + 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; + 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; @@ -415,6 +456,9 @@ TEST(FieldValue, CompareMixedType) { 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>()); @@ -425,7 +469,8 @@ TEST(FieldValue, CompareMixedType) { EXPECT_TRUE(number_value < timestamp_value); EXPECT_TRUE(timestamp_value < string_value); EXPECT_TRUE(string_value < blob_value); - EXPECT_TRUE(blob_value < geo_point_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); } 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..005798a --- /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 comparision 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/resource_path_test.cc b/Firestore/core/test/firebase/firestore/model/resource_path_test.cc index 317a1db..637e78e 100644 --- a/Firestore/core/test/firebase/firestore/model/resource_path_test.cc +++ b/Firestore/core/test/firebase/firestore/model/resource_path_test.cc @@ -29,18 +29,18 @@ namespace model { TEST(ResourcePath, Constructor) { const ResourcePath empty_path; EXPECT_TRUE(empty_path.empty()); - EXPECT_EQ(0, empty_path.size()); + 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(3, path_from_list.size()); + 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(3, path_from_segments.size()); + EXPECT_EQ(3u, path_from_segments.size()); EXPECT_TRUE(path_from_segments.begin() + 3 == path_from_segments.end()); ResourcePath copied = path_from_list; @@ -74,7 +74,7 @@ TEST(ResourcePath, Comparison) { TEST(ResourcePath, Parsing) { const auto parse = [](const std::pair<std::string, size_t> expected) { - const auto path = ResourcePath::Parse(expected.first); + 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) { @@ -92,12 +92,12 @@ TEST(ResourcePath, Parsing) { expected = make_expected(R"(foo/__!?#@..`..\`/baz)", 3); EXPECT_EQ(expected, parse(expected)); - EXPECT_EQ(ResourcePath::Parse("/foo/").CanonicalString(), "foo"); + EXPECT_EQ(ResourcePath::FromString("/foo/").CanonicalString(), "foo"); } TEST(ResourcePath, ParseFailures) { - ASSERT_DEATH_IF_SUPPORTED(ResourcePath::Parse("//"), ""); - ASSERT_DEATH_IF_SUPPORTED(ResourcePath::Parse("foo//bar"), ""); + ASSERT_ANY_THROW(ResourcePath::FromString("//")); + ASSERT_ANY_THROW(ResourcePath::FromString("foo//bar")); } } // namespace model 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/remote/serializer_test.cc b/Firestore/core/test/firebase/firestore/remote/serializer_test.cc index 1be5a87..35f417e 100644 --- a/Firestore/core/test/firebase/firestore/remote/serializer_test.cc +++ b/Firestore/core/test/firebase/firestore/remote/serializer_test.cc @@ -16,8 +16,15 @@ #include "Firestore/core/src/firebase/firestore/remote/serializer.h" -#include <gtest/gtest.h> +#include <pb.h> #include <pb_encode.h> +#include <vector> + +#include "Firestore/core/src/firebase/firestore/model/field_value.h" +#include "gtest/gtest.h" + +using firebase::firestore::model::FieldValue; +using firebase::firestore::remote::Serializer; TEST(Serializer, CanLinkToNanopb) { // This test doesn't actually do anything interesting as far as actually using @@ -26,3 +33,65 @@ TEST(Serializer, CanLinkToNanopb) { // the test. pb_ostream_from_buffer(NULL, 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 Serializer::TypedValue& proto, + FieldValue::Type type) { + EXPECT_EQ(type, model.type()); + EXPECT_EQ(type, proto.type); + Serializer::TypedValue actual_proto = serializer.EncodeFieldValue(model); + EXPECT_EQ(type, actual_proto.type); + EXPECT_EQ(proto, actual_proto); + EXPECT_EQ(model, serializer.DecodeFieldValue(proto)); + } + + void ExpectRoundTrip(const Serializer::TypedValue& proto, + std::vector<uint8_t> bytes, + FieldValue::Type type) { + EXPECT_EQ(type, proto.type); + std::vector<uint8_t> actual_bytes; + Serializer::EncodeTypedValue(proto, &actual_bytes); + EXPECT_EQ(bytes, actual_bytes); + Serializer::TypedValue actual_proto = Serializer::DecodeTypedValue(bytes); + EXPECT_EQ(type, actual_proto.type); + EXPECT_EQ(proto, actual_proto); + } +}; + +TEST_F(SerializerTest, EncodesNullModelToProto) { + FieldValue model = FieldValue::NullValue(); + Serializer::TypedValue proto{FieldValue::Type::Null, + google_firestore_v1beta1_Value_init_default}; + // sanity check (the _init_default above should set this to _NULL_VALUE) + EXPECT_EQ(google_protobuf_NullValue_NULL_VALUE, proto.value.null_value); + ExpectRoundTrip(model, proto, FieldValue::Type::Null); +} + +TEST_F(SerializerTest, EncodesNullProtoToBytes) { + Serializer::TypedValue proto{FieldValue::Type::Null, + google_firestore_v1beta1_Value_init_default}; + // sanity check (the _init_default above should set this to _NULL_VALUE) + EXPECT_EQ(google_protobuf_NullValue_NULL_VALUE, proto.value.null_value); + + /* NB: proto bytes were created via: + echo 'null_value: NULL_VALUE' \ + | ./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 \ + > output.bin + */ + std::vector<uint8_t> bytes{0x58, 0x00}; + ExpectRoundTrip(proto, bytes, FieldValue::Type::Null); +} + +// TODO(rsgowman): Test [en|de]coding multiple protos into the same output +// vector. diff --git a/Firestore/test.sh b/Firestore/test.sh index 7e26e3f..b211f46 100755 --- a/Firestore/test.sh +++ b/Firestore/test.sh @@ -38,6 +38,23 @@ test_iOS() { | xcpretty } +test_CMake() { + echo "cpu core: $(sysctl -n hw.ncpu)" + echo "set cmake build" && \ + mkdir build && \ + cd build && \ + cmake .. || \ + exit 1 + + echo "initial cmake build" && \ + make -j $(sysctl -n hw.ncpu) all || \ + exit 2 + + echo "test Firestore cmake build" && \ + cd Firestore && \ + make test +} + test_iOS; RESULT=$? if [[ $RESULT == 65 ]]; then echo "xcodebuild exited with 65, retrying" @@ -46,4 +63,8 @@ if [[ $RESULT == 65 ]]; then test_iOS; RESULT=$? fi -exit $RESULT +if [ $RESULT != 0 ]; then exit $RESULT; fi + +test_CMake; RESULT=$? + +if [ $RESULT != 0 ]; then exit $RESULT; fi @@ -54,10 +54,10 @@ pod 'FirebaseCore', :git => 'https://github.com/firebase/firebase-ios-sdk.git', 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`. diff --git a/cmake/FindNanopb.cmake b/cmake/FindNanopb.cmake index fb22aef..43c2886 100644 --- a/cmake/FindNanopb.cmake +++ b/cmake/FindNanopb.cmake @@ -10,7 +10,7 @@ find_path( find_library( NANOPB_LIBRARY NAMES protobuf-nanopb protobuf-nanopbd - HINTS ${BINARY_DIR}/src/nanopb-build + HINTS ${BINARY_DIR}/src/nanopb ) find_package_handle_standard_args( diff --git a/cmake/external/grpc.cmake b/cmake/external/grpc.cmake index 0af6b31..d545087 100644 --- a/cmake/external/grpc.cmake +++ b/cmake/external/grpc.cmake @@ -68,7 +68,15 @@ else() PREFIX ${PROJECT_BINARY_DIR}/external/grpc - CMAKE_ARGS ${CMAKE_ARGS} + # 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 diff --git a/cmake/external/nanopb.cmake b/cmake/external/nanopb.cmake index 5df0cf5..d09c668 100644 --- a/cmake/external/nanopb.cmake +++ b/cmake/external/nanopb.cmake @@ -55,9 +55,14 @@ ExternalProject_Add( # 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 - perl -i -pe s,protoc,${NANOPB_PROTOC_BIN},g - ./CMakeLists.txt ./generator/proto/Makefile + 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 "" |