From 9e815620e9f7f43b42e03db4e5118d7ad03ddee7 Mon Sep 17 00:00:00 2001 From: zxu Date: Wed, 14 Mar 2018 08:57:31 -0400 Subject: grand PR to port the remaining paths (FieldPath and ResourcePath). (#865) * naively remove FSTPath import and source/test files. * port FieldPath, part I * port FieldPath, part II * port ResourcePath, part I * port ResourcePath, part II * the grand commit to fix build errors * use testutil:: helper instead of those from FSTHelpers * fix test and lint * use c_str in errmsg directly * fix * fix * make code clean * fix integration test I missed * fix to avoid naming collision in preprocessor * address changes * address changes * address changes * fix: fieldMask are actually shared with different context. * address changes * address changes --- Firestore/Source/Model/FSTDocument.h | 5 +- Firestore/Source/Model/FSTDocument.mm | 9 +- Firestore/Source/Model/FSTFieldValue.h | 9 +- Firestore/Source/Model/FSTFieldValue.mm | 31 +-- Firestore/Source/Model/FSTMutation.h | 14 +- Firestore/Source/Model/FSTMutation.mm | 45 ++-- Firestore/Source/Model/FSTPath.h | 167 ------------- Firestore/Source/Model/FSTPath.mm | 399 -------------------------------- 8 files changed, 72 insertions(+), 607 deletions(-) delete mode 100644 Firestore/Source/Model/FSTPath.h delete mode 100644 Firestore/Source/Model/FSTPath.mm (limited to 'Firestore/Source/Model') diff --git a/Firestore/Source/Model/FSTDocument.h b/Firestore/Source/Model/FSTDocument.h index 100f553..36237fd 100644 --- a/Firestore/Source/Model/FSTDocument.h +++ b/Firestore/Source/Model/FSTDocument.h @@ -16,8 +16,9 @@ #import +#include "Firestore/core/src/firebase/firestore/model/field_path.h" + @class FSTDocumentKey; -@class FSTFieldPath; @class FSTFieldValue; @class FSTObjectValue; @class FSTSnapshotVersion; @@ -41,7 +42,7 @@ NS_ASSUME_NONNULL_BEGIN version:(FSTSnapshotVersion *)version hasLocalMutations:(BOOL)mutations; -- (nullable FSTFieldValue *)fieldForPath:(FSTFieldPath *)path; +- (nullable FSTFieldValue *)fieldForPath:(const firebase::firestore::model::FieldPath &)path; @property(nonatomic, strong, readonly) FSTObjectValue *data; @property(nonatomic, readonly, getter=hasLocalMutations) BOOL localMutations; diff --git a/Firestore/Source/Model/FSTDocument.mm b/Firestore/Source/Model/FSTDocument.mm index 58d3629..ca66da1 100644 --- a/Firestore/Source/Model/FSTDocument.mm +++ b/Firestore/Source/Model/FSTDocument.mm @@ -19,9 +19,14 @@ #import "Firestore/Source/Core/FSTSnapshotVersion.h" #import "Firestore/Source/Model/FSTDocumentKey.h" #import "Firestore/Source/Model/FSTFieldValue.h" -#import "Firestore/Source/Model/FSTPath.h" #import "Firestore/Source/Util/FSTAssert.h" +#include "Firestore/core/src/firebase/firestore/model/field_path.h" +#include "Firestore/core/src/firebase/firestore/util/string_apple.h" + +namespace util = firebase::firestore::util; +using firebase::firestore::model::FieldPath; + NS_ASSUME_NONNULL_BEGIN @interface FSTMaybeDocument () @@ -99,7 +104,7 @@ NS_ASSUME_NONNULL_BEGIN self.localMutations ? @"YES" : @"NO", self.data]; } -- (nullable FSTFieldValue *)fieldForPath:(FSTFieldPath *)path { +- (nullable FSTFieldValue *)fieldForPath:(const FieldPath &)path { return [_data valueForPath:path]; } diff --git a/Firestore/Source/Model/FSTFieldValue.h b/Firestore/Source/Model/FSTFieldValue.h index be8ba45..7d72138 100644 --- a/Firestore/Source/Model/FSTFieldValue.h +++ b/Firestore/Source/Model/FSTFieldValue.h @@ -19,9 +19,9 @@ #import "Firestore/third_party/Immutable/FSTImmutableSortedDictionary.h" #include "Firestore/core/src/firebase/firestore/model/database_id.h" +#include "Firestore/core/src/firebase/firestore/model/field_path.h" @class FSTDocumentKey; -@class FSTFieldPath; @class FIRTimestamp; @class FSTFieldValueOptions; @class FIRGeoPoint; @@ -243,19 +243,20 @@ typedef NS_ENUM(NSInteger, FSTServerTimestampBehavior) { - (FSTImmutableSortedDictionary *)internalValue; /** Returns the value at the given path if it exists. Returns nil otherwise. */ -- (nullable FSTFieldValue *)valueForPath:(FSTFieldPath *)fieldPath; +- (nullable FSTFieldValue *)valueForPath:(const firebase::firestore::model::FieldPath &)fieldPath; /** * Returns a new object where the field at the named path has its value set to the given value. * This object remains unmodified. */ -- (FSTObjectValue *)objectBySettingValue:(FSTFieldValue *)value forPath:(FSTFieldPath *)fieldPath; +- (FSTObjectValue *)objectBySettingValue:(FSTFieldValue *)value + forPath:(const firebase::firestore::model::FieldPath &)fieldPath; /** * Returns a new object where the field at the named path has been removed. If any segment of the * path does not exist within this object's structure, no change is performed. */ -- (FSTObjectValue *)objectByDeletingPath:(FSTFieldPath *)fieldPath; +- (FSTObjectValue *)objectByDeletingPath:(const firebase::firestore::model::FieldPath &)fieldPath; @end /** diff --git a/Firestore/Source/Model/FSTFieldValue.mm b/Firestore/Source/Model/FSTFieldValue.mm index 5ef64e1..2f013c3 100644 --- a/Firestore/Source/Model/FSTFieldValue.mm +++ b/Firestore/Source/Model/FSTFieldValue.mm @@ -24,15 +24,16 @@ #import "Firestore/Source/API/FIRGeoPoint+Internal.h" #import "Firestore/Source/API/FIRSnapshotOptions+Internal.h" #import "Firestore/Source/Model/FSTDocumentKey.h" -#import "Firestore/Source/Model/FSTPath.h" #import "Firestore/Source/Util/FSTAssert.h" #import "Firestore/Source/Util/FSTClasses.h" #include "Firestore/core/src/firebase/firestore/model/database_id.h" +#include "Firestore/core/src/firebase/firestore/model/field_path.h" #include "Firestore/core/src/firebase/firestore/util/string_apple.h" namespace util = firebase::firestore::util; using firebase::firestore::model::DatabaseId; +using firebase::firestore::model::FieldPath; using firebase::firestore::util::Comparator; using firebase::firestore::util::CompareMixedNumber; using firebase::firestore::util::DoubleBitwiseEquals; @@ -808,25 +809,26 @@ static const NSComparator StringComparator = ^NSComparisonResult(NSString *left, } } -- (nullable FSTFieldValue *)valueForPath:(FSTFieldPath *)fieldPath { +- (nullable FSTFieldValue *)valueForPath:(const FieldPath &)fieldPath { FSTFieldValue *value = self; - for (int i = 0, max = fieldPath.length; value && i < max; i++) { + for (size_t i = 0, max = fieldPath.size(); value && i < max; i++) { if (![value isMemberOfClass:[FSTObjectValue class]]) { return nil; } - NSString *fieldName = fieldPath[i]; + NSString *fieldName = util::WrapNSStringNoCopy(fieldPath[i]); value = ((FSTObjectValue *)value).internalValue[fieldName]; } return value; } -- (FSTObjectValue *)objectBySettingValue:(FSTFieldValue *)value forPath:(FSTFieldPath *)fieldPath { - FSTAssert([fieldPath length] > 0, @"Cannot set value with an empty path"); +- (FSTObjectValue *)objectBySettingValue:(FSTFieldValue *)value + forPath:(const FieldPath &)fieldPath { + FSTAssert(fieldPath.size() > 0, @"Cannot set value with an empty path"); - NSString *childName = [fieldPath firstSegment]; - if ([fieldPath length] == 1) { + NSString *childName = util::WrapNSString(fieldPath.first_segment()); + if (fieldPath.size() == 1) { // Recursive base case: return [self objectBySettingValue:value forField:childName]; } else { @@ -841,23 +843,22 @@ static const NSComparator StringComparator = ^NSComparisonResult(NSString *left, // there. childObject = [FSTObjectValue objectValue]; } - FSTFieldValue *newChild = - [childObject objectBySettingValue:value forPath:[fieldPath pathByRemovingFirstSegment]]; + FSTFieldValue *newChild = [childObject objectBySettingValue:value forPath:fieldPath.PopFirst()]; return [self objectBySettingValue:newChild forField:childName]; } } -- (FSTObjectValue *)objectByDeletingPath:(FSTFieldPath *)fieldPath { - FSTAssert([fieldPath length] > 0, @"Cannot delete an empty path"); - NSString *childName = [fieldPath firstSegment]; - if ([fieldPath length] == 1) { +- (FSTObjectValue *)objectByDeletingPath:(const FieldPath &)fieldPath { + FSTAssert(fieldPath.size() > 0, @"Cannot delete an empty path"); + NSString *childName = util::WrapNSString(fieldPath.first_segment()); + if (fieldPath.size() == 1) { return [[FSTObjectValue alloc] initWithImmutableDictionary:[_internalValue dictionaryByRemovingObjectForKey:childName]]; } else { FSTFieldValue *child = _internalValue[childName]; if ([child isKindOfClass:[FSTObjectValue class]]) { FSTObjectValue *newChild = - [((FSTObjectValue *)child) objectByDeletingPath:[fieldPath pathByRemovingFirstSegment]]; + [((FSTObjectValue *)child) objectByDeletingPath:fieldPath.PopFirst()]; return [self objectBySettingValue:newChild forField:childName]; } else { // If the child is not found or is a primitive type, make no modifications diff --git a/Firestore/Source/Model/FSTMutation.h b/Firestore/Source/Model/FSTMutation.h index 72f6a25..2d29dc9 100644 --- a/Firestore/Source/Model/FSTMutation.h +++ b/Firestore/Source/Model/FSTMutation.h @@ -16,9 +16,12 @@ #import +#include + +#include "Firestore/core/src/firebase/firestore/model/field_path.h" + @class FSTDocument; @class FSTDocumentKey; -@class FSTFieldPath; @class FSTFieldValue; @class FSTMaybeDocument; @class FSTObjectValue; @@ -46,9 +49,10 @@ NS_ASSUME_NONNULL_BEGIN * Initializes the field mask with the given field paths. Caller is expected to either copy or * or release the array of fields. */ -- (instancetype)initWithFields:(NSArray *)fields NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithFields:(std::vector)fields + NS_DESIGNATED_INITIALIZER; -@property(nonatomic, strong, readonly) NSArray *fields; +- (const std::vector &)fields; @end #pragma mark - FSTFieldTransform @@ -65,9 +69,9 @@ NS_ASSUME_NONNULL_BEGIN /** A field path and the FSTTransformOperation to perform upon it. */ @interface FSTFieldTransform : NSObject - (instancetype)init NS_UNAVAILABLE; -- (instancetype)initWithPath:(FSTFieldPath *)path +- (instancetype)initWithPath:(firebase::firestore::model::FieldPath)path transform:(id)transform NS_DESIGNATED_INITIALIZER; -@property(nonatomic, strong, readonly) FSTFieldPath *path; +- (const firebase::firestore::model::FieldPath &)path; @property(nonatomic, strong, readonly) id transform; @end diff --git a/Firestore/Source/Model/FSTMutation.mm b/Firestore/Source/Model/FSTMutation.mm index e702644..a61ee84 100644 --- a/Firestore/Source/Model/FSTMutation.mm +++ b/Firestore/Source/Model/FSTMutation.mm @@ -22,19 +22,24 @@ #import "Firestore/Source/Model/FSTDocument.h" #import "Firestore/Source/Model/FSTDocumentKey.h" #import "Firestore/Source/Model/FSTFieldValue.h" -#import "Firestore/Source/Model/FSTPath.h" #import "Firestore/Source/Util/FSTAssert.h" #import "Firestore/Source/Util/FSTClasses.h" +#include "Firestore/core/src/firebase/firestore/model/field_path.h" + +using firebase::firestore::model::FieldPath; + NS_ASSUME_NONNULL_BEGIN #pragma mark - FSTFieldMask -@implementation FSTFieldMask +@implementation FSTFieldMask { + std::vector _fields; +} -- (instancetype)initWithFields:(NSArray *)fields { +- (instancetype)initWithFields:(std::vector)fields { if (self = [super init]) { - _fields = fields; + _fields = std::move(fields); } return self; } @@ -48,11 +53,19 @@ NS_ASSUME_NONNULL_BEGIN } FSTFieldMask *otherMask = (FSTFieldMask *)other; - return [self.fields isEqual:otherMask.fields]; + return _fields == otherMask->_fields; } - (NSUInteger)hash { - return self.fields.hash; + NSUInteger hashResult = 0; + for (const FieldPath &field : _fields) { + hashResult = hashResult * 31u + field.Hash(); + } + return hashResult; +} + +- (const std::vector &)fields { + return _fields; } @end @@ -85,12 +98,14 @@ NS_ASSUME_NONNULL_BEGIN #pragma mark - FSTFieldTransform -@implementation FSTFieldTransform +@implementation FSTFieldTransform { + FieldPath _path; +} -- (instancetype)initWithPath:(FSTFieldPath *)path transform:(id)transform { +- (instancetype)initWithPath:(FieldPath)path transform:(id)transform { self = [super init]; if (self) { - _path = path; + _path = std::move(path); _transform = transform; } return self; @@ -100,16 +115,20 @@ NS_ASSUME_NONNULL_BEGIN if (other == self) return YES; if (![[other class] isEqual:[self class]]) return NO; FSTFieldTransform *otherFieldTransform = other; - return [self.path isEqual:otherFieldTransform.path] && + return self.path == otherFieldTransform.path && [self.transform isEqual:otherFieldTransform.transform]; } - (NSUInteger)hash { - NSUInteger hash = [self.path hash]; + NSUInteger hash = self.path.Hash(); hash = hash * 31 + [self.transform hash]; return hash; } +- (const firebase::firestore::model::FieldPath &)path { + return _path; +} + @end #pragma mark - FSTPrecondition @@ -405,7 +424,7 @@ NS_ASSUME_NONNULL_BEGIN - (FSTObjectValue *)patchObjectValue:(FSTObjectValue *)objectValue { FSTObjectValue *result = objectValue; - for (FSTFieldPath *fieldPath in self.fieldMask.fields) { + for (const FieldPath &fieldPath : self.fieldMask.fields) { FSTFieldValue *newValue = [self.value valueForPath:fieldPath]; if (newValue) { result = [result objectBySettingValue:newValue forPath:fieldPath]; @@ -529,7 +548,7 @@ NS_ASSUME_NONNULL_BEGIN for (NSUInteger i = 0; i < self.fieldTransforms.count; i++) { FSTFieldTransform *fieldTransform = self.fieldTransforms[i]; id transform = fieldTransform.transform; - FSTFieldPath *fieldPath = fieldTransform.path; + FieldPath fieldPath = fieldTransform.path; if ([transform isKindOfClass:[FSTServerTimestampTransform class]]) { objectValue = [objectValue objectBySettingValue:transformResults[i] forPath:fieldPath]; } else { diff --git a/Firestore/Source/Model/FSTPath.h b/Firestore/Source/Model/FSTPath.h deleted file mode 100644 index f127156..0000000 --- a/Firestore/Source/Model/FSTPath.h +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright 2017 Google - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import - -#include "Firestore/core/src/firebase/firestore/model/field_path.h" -#include "Firestore/core/src/firebase/firestore/model/resource_path.h" - -NS_ASSUME_NONNULL_BEGIN - -/** - * FSTPath represents a path sequence in the Firestore database. It is composed of an ordered - * sequence of string segments. - * - * ## Subclassing Notes - * - * FSTPath itself is an abstract class that must be specialized by subclasses. Subclasses should - * implement constructors for common string-based representations of the path and also override - * -canonicalString which converts back to the canonical string-based representation of the path. - */ -@interface FSTPath : NSObject - -/** Returns the path segment of the given index. */ -- (NSString *)segmentAtIndex:(int)index; -- (id)objectAtIndexedSubscript:(int)index; - -- (BOOL)isEqual:(id)path; -- (NSComparisonResult)compare:(SelfType)other; - -/** - * Returns a new path whose segments are the current path's plus one more. - * - * @param segment The new segment to concatenate to the path. - * @return A new path with this path's segment plus the new one. - */ -- (instancetype)pathByAppendingSegment:(NSString *)segment; - -/** - * Returns a new path whose segments are the current path's plus another's. - * - * @param path The new path whose segments should be concatenated to the path. - * @return A new path with this path's segment plus the new ones. - */ -- (instancetype)pathByAppendingPath:(SelfType)path; - -/** Returns a new path whose segments are the same as this one's minus the first one. */ -- (instancetype)pathByRemovingFirstSegment; - -/** Returns a new path whose segments are the same as this one's minus the first `count`. */ -- (instancetype)pathByRemovingFirstSegments:(int)count; - -/** Returns a new path whose segments are the same as this one's minus the last one. */ -- (instancetype)pathByRemovingLastSegment; - -/** Convenience method for getting the first segment of this path. */ -- (NSString *)firstSegment; - -/** Convenience method for getting the last segment of this path. */ -- (NSString *)lastSegment; - -/** Returns true if this path is a prefix of the given path. */ -- (BOOL)isPrefixOfPath:(SelfType)other; - -/** Returns a standardized string representation of this path. */ -- (NSString *)canonicalString; - -/** The number of segments in the path. */ -@property(nonatomic, readonly) int length; - -/** True if the path is empty. */ -@property(nonatomic, readonly, getter=isEmpty) BOOL empty; - -@end - -/** A dot-separated path for navigating sub-objects within a document. */ -@class FSTFieldPath; - -@interface FSTFieldPath : FSTPath - -/** - * Creates and returns a new path with the given segments. The array of segments is not copied, so - * one should not mutate the array once it is passed in here. - * - * @param segments The underlying array of segments for the path. - * @return A new instance of FSTPath. - */ -+ (instancetype)pathWithSegments:(NSArray *)segments; - -/** - * Creates and returns a new path from the server formatted field-path string, where path segments - * are separated by a dot "." and optionally encoded using backticks. - * - * @param fieldPath A dot-separated string representing the path. - */ -+ (instancetype)pathWithServerFormat:(NSString *)fieldPath; - -/** Returns a field path that represents a document key. */ -+ (instancetype)keyFieldPath; - -/** Returns a field path that represents an empty path. */ -+ (instancetype)emptyPath; - -/** Returns YES if this is the `FSTFieldPath.keyFieldPath` field path. */ -- (BOOL)isKeyFieldPath; - -/** Creates and returns a new path from C++ FieldPath. - * - * @param fieldPath A C++ FieldPath. - */ -+ (instancetype)fieldPathWithCPPFieldPath:(const firebase::firestore::model::FieldPath &)fieldPath; - -/** - * Creates and returns a new C++ FieldPath. - */ -- (firebase::firestore::model::FieldPath)toCPPFieldPath; - -@end - -/** A slash-separated path for navigating resources (documents and collections) within Firestore. */ -@class FSTResourcePath; - -@interface FSTResourcePath : FSTPath - -/** - * Creates and returns a new path with the given segments. The array of segments is not copied, so - * one should not mutate the array once it is passed in here. - * - * @param segments The underlying array of segments for the path. - * @return A new instance of FSTPath. - */ -+ (instancetype)pathWithSegments:(NSArray *)segments; - -/** - * Creates and returns a new path from the given resource-path string, where the path segments are - * separated by a slash "/". - * - * @param resourcePath A slash-separated string representing the path. - */ -+ (instancetype)pathWithString:(NSString *)resourcePath; - -/** Creates and returns a new path from C++ ResourcePath. - * - * @param resourcePath A C++ ResourcePath. - */ -+ (instancetype)resourcePathWithCPPResourcePath: - (const firebase::firestore::model::ResourcePath &)resourcePath; - -/** - * Creates and returns a new C++ ResourcePath. - */ -- (firebase::firestore::model::ResourcePath)toCPPResourcePath; -@end - -NS_ASSUME_NONNULL_END diff --git a/Firestore/Source/Model/FSTPath.mm b/Firestore/Source/Model/FSTPath.mm deleted file mode 100644 index b91e428..0000000 --- a/Firestore/Source/Model/FSTPath.mm +++ /dev/null @@ -1,399 +0,0 @@ -/* - * Copyright 2017 Google - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import "Firestore/Source/Model/FSTPath.h" - -#include - -#import "Firestore/Source/Model/FSTDocumentKey.h" -#import "Firestore/Source/Util/FSTAssert.h" -#import "Firestore/Source/Util/FSTClasses.h" -#import "Firestore/Source/Util/FSTUsageValidation.h" - -#include "Firestore/core/src/firebase/firestore/model/field_path.h" -#include "Firestore/core/src/firebase/firestore/model/resource_path.h" -#include "Firestore/core/src/firebase/firestore/util/string_apple.h" - -namespace util = firebase::firestore::util; -using firebase::firestore::model::FieldPath; -using firebase::firestore::model::ResourcePath; - -NS_ASSUME_NONNULL_BEGIN - -@interface FSTPath () -/** An underlying array of which a subset of elements are the segments of the path. */ -@property(strong, nonatomic) NSArray *segments; -/** The index into the segments array of the first segment in this path. */ -@property int offset; -@end - -@implementation FSTPath - -/** - * Designated initializer. - * - * @param segments The underlying array of segments for the path. - * @param offset The starting index in the underlying array for the subarray to use. - * @param length The length of the subarray to use. - */ -- (instancetype)initWithSegments:(NSArray *)segments - offset:(int)offset - length:(int)length { - FSTAssert(offset <= segments.count, @"offset %d out of range %d", offset, (int)segments.count); - FSTAssert(length <= segments.count - offset, @"offset %d out of range %d", offset, - (int)segments.count - offset); - - if (self = [super init]) { - _segments = segments; - _offset = offset; - _length = length; - } - return self; -} - -- (BOOL)isEqual:(id)object { - if (self == object) { - return YES; - } - if (![object isKindOfClass:[FSTPath class]]) { - return NO; - } - FSTPath *path = object; - return [self compare:path] == NSOrderedSame; -} - -- (NSUInteger)hash { - NSUInteger hash = 0; - for (int i = 0; i < self.length; ++i) { - hash += [self segmentAtIndex:i].hash; - } - return hash; -} - -- (NSString *)description { - return [self canonicalString]; -} - -- (id)objectAtIndexedSubscript:(int)index { - return [self segmentAtIndex:index]; -} - -- (NSString *)segmentAtIndex:(int)index { - FSTAssert(index < self.length, @"index %d out of range", index); - return self.segments[self.offset + index]; -} - -- (NSString *)firstSegment { - FSTAssert(!self.isEmpty, @"Cannot call firstSegment on empty path"); - return [self segmentAtIndex:0]; -} - -- (NSString *)lastSegment { - FSTAssert(!self.isEmpty, @"Cannot call lastSegment on empty path"); - return [self segmentAtIndex:self.length - 1]; -} - -- (NSComparisonResult)compare:(FSTPath *)other { - int length = MIN(self.length, other.length); - for (int i = 0; i < length; ++i) { - NSString *left = [self segmentAtIndex:i]; - NSString *right = [other segmentAtIndex:i]; - NSComparisonResult result = [left compare:right]; - if (result != NSOrderedSame) { - return result; - } - } - if (self.length < other.length) { - return NSOrderedAscending; - } - if (self.length > other.length) { - return NSOrderedDescending; - } - return NSOrderedSame; -} - -- (instancetype)pathWithSegments:(NSArray *)segments - offset:(int)offset - length:(int)length { - return [[[self class] alloc] initWithSegments:segments offset:offset length:length]; -} - -- (instancetype)pathByAppendingSegment:(NSString *)segment { - int newLength = self.length + 1; - NSMutableArray *segments = [NSMutableArray arrayWithCapacity:newLength]; - for (int i = 0; i < self.length; ++i) { - [segments addObject:self[i]]; - } - [segments addObject:segment]; - return [self pathWithSegments:segments offset:0 length:newLength]; -} - -- (instancetype)pathByAppendingPath:(FSTPath *)path { - int newLength = self.length + path.length; - NSMutableArray *segments = [NSMutableArray arrayWithCapacity:newLength]; - for (int i = 0; i < self.length; ++i) { - [segments addObject:self[i]]; - } - for (int i = 0; i < path.length; ++i) { - [segments addObject:path[i]]; - } - return [self pathWithSegments:segments offset:0 length:newLength]; -} - -- (BOOL)isEmpty { - return self.length == 0; -} - -- (instancetype)pathByRemovingFirstSegment { - FSTAssert(!self.isEmpty, @"Cannot call pathByRemovingFirstSegment on empty path"); - return [self pathWithSegments:self.segments offset:self.offset + 1 length:self.length - 1]; -} - -- (instancetype)pathByRemovingFirstSegments:(int)count { - FSTAssert(self.length >= count, @"pathByRemovingFirstSegments:%d on path of length %d", count, - self.length); - return - [self pathWithSegments:self.segments offset:self.offset + count length:self.length - count]; -} - -- (instancetype)pathByRemovingLastSegment { - FSTAssert(!self.isEmpty, @"Cannot call pathByRemovingLastSegment on empty path"); - return [self pathWithSegments:self.segments offset:self.offset length:self.length - 1]; -} - -- (BOOL)isPrefixOfPath:(FSTPath *)other { - if (other.length < self.length) { - return NO; - } - for (int i = 0; i < self.length; ++i) { - if (![self[i] isEqual:other[i]]) { - return NO; - } - } - return YES; -} - -/** Returns a standardized string representation of this path. */ -- (NSString *)canonicalString { - @throw FSTAbstractMethodException(); // NOLINT -} -@end - -@implementation FSTFieldPath -+ (instancetype)pathWithSegments:(NSArray *)segments { - return [[FSTFieldPath alloc] initWithSegments:segments offset:0 length:(int)segments.count]; -} - -+ (instancetype)pathWithServerFormat:(NSString *)fieldPath { - NSMutableArray *segments = [NSMutableArray array]; - - // TODO(b/37244157): Once we move to v1beta1, we should make this more strict. Right now, it - // allows non-identifier path components, even if they aren't escaped. Technically, this will - // mangle paths with backticks in them used in v1alpha1, but that's fine. - - const char *source = [fieldPath UTF8String]; - char *segment = (char *)malloc(strlen(source) + 1); - char *segmentEnd = segment; - - // If we're inside '`' backticks, then we should ignore '.' dots. - BOOL inBackticks = NO; - - char c; - do { - // Examine current character. This is legit even on zero-length strings because there's always - // a null terminator. - c = *source++; - switch (c) { - case '\0': // Falls through - case '.': - if (!inBackticks) { - // Segment is complete - *segmentEnd = '\0'; - if (segment == segmentEnd) { - FSTThrowInvalidArgument( - @"Invalid field path (%@). Paths must not be empty, begin with " - @"'.', end with '.', or contain '..'", - fieldPath); - } - - [segments addObject:[NSString stringWithUTF8String:segment]]; - segmentEnd = segment; - } else { - // copy into the current segment - *segmentEnd++ = c; - } - break; - - case '`': - if (inBackticks) { - inBackticks = NO; - } else { - inBackticks = YES; - } - break; - - case '\\': - // advance to escaped character - c = *source++; - // TODO(b/37244157): Make this a user-facing exception once we finalize field escaping. - FSTAssert(c != '\0', @"Trailing escape characters not allowed in %@", fieldPath); - // Fall through - - default: - // copy into the current segment - *segmentEnd++ = c; - break; - } - } while (c); - - FSTAssert(!inBackticks, @"Unterminated ` in path %@", fieldPath); - - free(segment); - return [FSTFieldPath pathWithSegments:segments]; -} - -+ (instancetype)keyFieldPath { - static FSTFieldPath *keyFieldPath; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - keyFieldPath = [FSTFieldPath pathWithSegments:@[ kDocumentKeyPath ]]; - }); - return keyFieldPath; -} - -+ (instancetype)emptyPath { - static FSTFieldPath *emptyPath; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - emptyPath = [FSTFieldPath pathWithSegments:@[]]; - }); - return emptyPath; -} - -/** Return YES if the string could be used as a segment in a field path without escaping. */ -+ (BOOL)isValidIdentifier:(NSString *)segment { - if (segment.length == 0) { - return NO; - } - unichar first = [segment characterAtIndex:0]; - if (first != '_' && (first < 'a' || first > 'z') && (first < 'A' || first > 'Z')) { - return NO; - } - for (int i = 1; i < segment.length; i++) { - unichar c = [segment characterAtIndex:i]; - if (c != '_' && (c < 'a' || c > 'z') && (c < 'A' || c > 'Z') && (c < '0' || c > '9')) { - return NO; - } - } - return YES; -} - -- (BOOL)isKeyFieldPath { - return [self isEqual:FSTFieldPath.keyFieldPath]; -} - -- (NSString *)canonicalString { - NSMutableString *result = [NSMutableString string]; - for (int i = 0; i < self.length; i++) { - if (i > 0) { - [result appendString:@"."]; - } - - NSString *escaped = [self[i] stringByReplacingOccurrencesOfString:@"\\" withString:@"\\\\"]; - escaped = [escaped stringByReplacingOccurrencesOfString:@"`" withString:@"\\`"]; - if (![FSTFieldPath isValidIdentifier:escaped]) { - escaped = [NSString stringWithFormat:@"`%@`", escaped]; - } - - [result appendString:escaped]; - } - return result; -} - -+ (instancetype)fieldPathWithCPPFieldPath:(const FieldPath &)fieldPath { - NSMutableArray *segments = [NSMutableArray arrayWithCapacity:fieldPath.size()]; - for (int i = 0; i < fieldPath.size(); i++) { - segments[i] = util::WrapNSString(fieldPath[i]); - } - return [FSTFieldPath pathWithSegments:segments]; -} - -- (FieldPath)toCPPFieldPath { - std::vector segments(self.length); - for (int i = 0; i < self.length; i++) { - segments[i] = [[self segmentAtIndex:i] UTF8String]; - } - return FieldPath(segments.begin(), segments.end()); -} - -@end - -@implementation FSTResourcePath -+ (instancetype)pathWithSegments:(NSArray *)segments { - return [[FSTResourcePath alloc] initWithSegments:segments offset:0 length:(int)segments.count]; -} - -+ (instancetype)pathWithString:(NSString *)resourcePath { - // NOTE: The client is ignorant of any path segments containing escape sequences (e.g. __id123__) - // and just passes them through raw (they exist for legacy reasons and should not be used - // frequently). - - if ([resourcePath rangeOfString:@"//"].location != NSNotFound) { - FSTThrowInvalidArgument(@"Invalid path (%@). Paths must not contain // in them.", resourcePath); - } - - NSMutableArray *segments = [[resourcePath componentsSeparatedByString:@"/"] mutableCopy]; - // We may still have an empty segment at the beginning or end if they had a leading or trailing - // slash (which we allow). - [segments removeObject:@""]; - - return [self pathWithSegments:segments]; -} - -- (NSString *)canonicalString { - // NOTE: The client is ignorant of any path segments containing escape sequences (e.g. __id123__) - // and just passes them through raw (they exist for legacy reasons and should not be used - // frequently). - - NSMutableString *result = [NSMutableString string]; - for (int i = 0; i < self.length; i++) { - if (i > 0) { - [result appendString:@"/"]; - } - [result appendString:self[i]]; - } - return result; -} - -+ (instancetype)resourcePathWithCPPResourcePath:(const ResourcePath &)resourcePath { - NSMutableArray *segments = [NSMutableArray arrayWithCapacity:resourcePath.size()]; - for (int i = 0; i < resourcePath.size(); i++) { - segments[i] = util::WrapNSString(resourcePath[i]); - } - return [FSTResourcePath pathWithSegments:segments]; -} - -- (ResourcePath)toCPPResourcePath { - std::vector segments(self.length); - for (int i = 0; i < self.length; i++) { - segments[i] = [[self segmentAtIndex:i] UTF8String]; - } - return ResourcePath(segments.begin(), segments.end()); -} - -@end - -NS_ASSUME_NONNULL_END -- cgit v1.2.3