aboutsummaryrefslogtreecommitdiffhomepage
path: root/Firestore/Source/Model
diff options
context:
space:
mode:
authorGravatar zxu <zxu@google.com>2018-03-14 08:57:31 -0400
committerGravatar GitHub <noreply@github.com>2018-03-14 08:57:31 -0400
commit9e815620e9f7f43b42e03db4e5118d7ad03ddee7 (patch)
tree86f362bad99281325fca7ee3ae116b6a0f36d511 /Firestore/Source/Model
parentd4d73ea53ecdf1e8ade3d00921419645dd5d66f7 (diff)
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
Diffstat (limited to 'Firestore/Source/Model')
-rw-r--r--Firestore/Source/Model/FSTDocument.h5
-rw-r--r--Firestore/Source/Model/FSTDocument.mm9
-rw-r--r--Firestore/Source/Model/FSTFieldValue.h9
-rw-r--r--Firestore/Source/Model/FSTFieldValue.mm31
-rw-r--r--Firestore/Source/Model/FSTMutation.h14
-rw-r--r--Firestore/Source/Model/FSTMutation.mm45
-rw-r--r--Firestore/Source/Model/FSTPath.h167
-rw-r--r--Firestore/Source/Model/FSTPath.mm399
8 files changed, 72 insertions, 607 deletions
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 <Foundation/Foundation.h>
+#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<NSString *, FSTFieldValue *> *)internalValue;
/** Returns the value at the given path if it exists. Returns nil otherwise. */
-- (nullable FSTFieldValue *)valueForPath:(FSTFieldPath *)fieldPath;
+- (nullable FSTFieldValue *)valueForPath:(const firebase::firestore::model::FieldPath &)fieldPath;
/**
* Returns a new object where the field at the named path has its value set to the given value.
* This object remains unmodified.
*/
-- (FSTObjectValue *)objectBySettingValue:(FSTFieldValue *)value forPath:(FSTFieldPath *)fieldPath;
+- (FSTObjectValue *)objectBySettingValue:(FSTFieldValue *)value
+ forPath:(const firebase::firestore::model::FieldPath &)fieldPath;
/**
* Returns a new object where the field at the named path has been removed. If any segment of the
* path does not exist within this object's structure, no change is performed.
*/
-- (FSTObjectValue *)objectByDeletingPath:(FSTFieldPath *)fieldPath;
+- (FSTObjectValue *)objectByDeletingPath:(const firebase::firestore::model::FieldPath &)fieldPath;
@end
/**
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 <Foundation/Foundation.h>
+#include <vector>
+
+#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<FSTFieldPath *> *)fields NS_DESIGNATED_INITIALIZER;
+- (instancetype)initWithFields:(std::vector<firebase::firestore::model::FieldPath>)fields
+ NS_DESIGNATED_INITIALIZER;
-@property(nonatomic, strong, readonly) NSArray<FSTFieldPath *> *fields;
+- (const std::vector<firebase::firestore::model::FieldPath> &)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<FSTTransformOperation>)transform NS_DESIGNATED_INITIALIZER;
-@property(nonatomic, strong, readonly) FSTFieldPath *path;
+- (const firebase::firestore::model::FieldPath &)path;
@property(nonatomic, strong, readonly) id<FSTTransformOperation> 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<FieldPath> _fields;
+}
-- (instancetype)initWithFields:(NSArray<FSTFieldPath *> *)fields {
+- (instancetype)initWithFields:(std::vector<FieldPath>)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<FieldPath> &)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<FSTTransformOperation>)transform {
+- (instancetype)initWithPath:(FieldPath)path transform:(id<FSTTransformOperation>)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<FSTTransformOperation> 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 <Foundation/Foundation.h>
-
-#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 <SelfType> : NSObject
-
-/** Returns the path segment of the given index. */
-- (NSString *)segmentAtIndex:(int)index;
-- (id)objectAtIndexedSubscript:(int)index;
-
-- (BOOL)isEqual:(id)path;
-- (NSComparisonResult)compare:(SelfType)other;
-
-/**
- * Returns a new path whose segments are the current path's plus one more.
- *
- * @param segment The new segment to concatenate to the path.
- * @return A new path with this path's segment plus the new one.
- */
-- (instancetype)pathByAppendingSegment:(NSString *)segment;
-
-/**
- * Returns a new path whose segments are the current path's plus another's.
- *
- * @param path The new path whose segments should be concatenated to the path.
- * @return A new path with this path's segment plus the new ones.
- */
-- (instancetype)pathByAppendingPath:(SelfType)path;
-
-/** Returns a new path whose segments are the same as this one's minus the first one. */
-- (instancetype)pathByRemovingFirstSegment;
-
-/** Returns a new path whose segments are the same as this one's minus the first `count`. */
-- (instancetype)pathByRemovingFirstSegments:(int)count;
-
-/** Returns a new path whose segments are the same as this one's minus the last one. */
-- (instancetype)pathByRemovingLastSegment;
-
-/** Convenience method for getting the first segment of this path. */
-- (NSString *)firstSegment;
-
-/** Convenience method for getting the last segment of this path. */
-- (NSString *)lastSegment;
-
-/** Returns true if this path is a prefix of the given path. */
-- (BOOL)isPrefixOfPath:(SelfType)other;
-
-/** Returns a standardized string representation of this path. */
-- (NSString *)canonicalString;
-
-/** The number of segments in the path. */
-@property(nonatomic, readonly) int length;
-
-/** True if the path is empty. */
-@property(nonatomic, readonly, getter=isEmpty) BOOL empty;
-
-@end
-
-/** A dot-separated path for navigating sub-objects within a document. */
-@class FSTFieldPath;
-
-@interface FSTFieldPath : FSTPath <FSTFieldPath *>
-
-/**
- * Creates and returns a new path with the given segments. The array of segments is not copied, so
- * one should not mutate the array once it is passed in here.
- *
- * @param segments The underlying array of segments for the path.
- * @return A new instance of FSTPath.
- */
-+ (instancetype)pathWithSegments:(NSArray<NSString *> *)segments;
-
-/**
- * Creates and returns a new path from the server formatted field-path string, where path segments
- * are separated by a dot "." and optionally encoded using backticks.
- *
- * @param fieldPath A dot-separated string representing the path.
- */
-+ (instancetype)pathWithServerFormat:(NSString *)fieldPath;
-
-/** Returns a field path that represents a document key. */
-+ (instancetype)keyFieldPath;
-
-/** Returns a field path that represents an empty path. */
-+ (instancetype)emptyPath;
-
-/** Returns YES if this is the `FSTFieldPath.keyFieldPath` field path. */
-- (BOOL)isKeyFieldPath;
-
-/** 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 <FSTResourcePath *>
-
-/**
- * Creates and returns a new path with the given segments. The array of segments is not copied, so
- * one should not mutate the array once it is passed in here.
- *
- * @param segments The underlying array of segments for the path.
- * @return A new instance of FSTPath.
- */
-+ (instancetype)pathWithSegments:(NSArray<NSString *> *)segments;
-
-/**
- * Creates and returns a new path from the given resource-path string, where the path segments are
- * separated by a slash "/".
- *
- * @param resourcePath A slash-separated string representing the path.
- */
-+ (instancetype)pathWithString:(NSString *)resourcePath;
-
-/** 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 <string>
-
-#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<NSString *> *segments;
-/** The index into the segments array of the first segment in this path. */
-@property int offset;
-@end
-
-@implementation FSTPath
-
-/**
- * Designated initializer.
- *
- * @param segments The underlying array of segments for the path.
- * @param offset The starting index in the underlying array for the subarray to use.
- * @param length The length of the subarray to use.
- */
-- (instancetype)initWithSegments:(NSArray<NSString *> *)segments
- offset:(int)offset
- length:(int)length {
- FSTAssert(offset <= segments.count, @"offset %d out of range %d", offset, (int)segments.count);
- FSTAssert(length <= segments.count - offset, @"offset %d out of range %d", offset,
- (int)segments.count - offset);
-
- if (self = [super init]) {
- _segments = segments;
- _offset = offset;
- _length = length;
- }
- return self;
-}
-
-- (BOOL)isEqual:(id)object {
- if (self == object) {
- return YES;
- }
- if (![object isKindOfClass:[FSTPath class]]) {
- return NO;
- }
- FSTPath *path = object;
- return [self compare:path] == NSOrderedSame;
-}
-
-- (NSUInteger)hash {
- NSUInteger hash = 0;
- for (int i = 0; i < self.length; ++i) {
- hash += [self segmentAtIndex:i].hash;
- }
- return hash;
-}
-
-- (NSString *)description {
- return [self canonicalString];
-}
-
-- (id)objectAtIndexedSubscript:(int)index {
- return [self segmentAtIndex:index];
-}
-
-- (NSString *)segmentAtIndex:(int)index {
- FSTAssert(index < self.length, @"index %d out of range", index);
- return self.segments[self.offset + index];
-}
-
-- (NSString *)firstSegment {
- FSTAssert(!self.isEmpty, @"Cannot call firstSegment on empty path");
- return [self segmentAtIndex:0];
-}
-
-- (NSString *)lastSegment {
- FSTAssert(!self.isEmpty, @"Cannot call lastSegment on empty path");
- return [self segmentAtIndex:self.length - 1];
-}
-
-- (NSComparisonResult)compare:(FSTPath *)other {
- int length = MIN(self.length, other.length);
- for (int i = 0; i < length; ++i) {
- NSString *left = [self segmentAtIndex:i];
- NSString *right = [other segmentAtIndex:i];
- NSComparisonResult result = [left compare:right];
- if (result != NSOrderedSame) {
- return result;
- }
- }
- if (self.length < other.length) {
- return NSOrderedAscending;
- }
- if (self.length > other.length) {
- return NSOrderedDescending;
- }
- return NSOrderedSame;
-}
-
-- (instancetype)pathWithSegments:(NSArray<NSString *> *)segments
- offset:(int)offset
- length:(int)length {
- return [[[self class] alloc] initWithSegments:segments offset:offset length:length];
-}
-
-- (instancetype)pathByAppendingSegment:(NSString *)segment {
- int newLength = self.length + 1;
- NSMutableArray<NSString *> *segments = [NSMutableArray arrayWithCapacity:newLength];
- for (int i = 0; i < self.length; ++i) {
- [segments addObject:self[i]];
- }
- [segments addObject:segment];
- return [self pathWithSegments:segments offset:0 length:newLength];
-}
-
-- (instancetype)pathByAppendingPath:(FSTPath *)path {
- int newLength = self.length + path.length;
- NSMutableArray<NSString *> *segments = [NSMutableArray arrayWithCapacity:newLength];
- for (int i = 0; i < self.length; ++i) {
- [segments addObject:self[i]];
- }
- for (int i = 0; i < path.length; ++i) {
- [segments addObject:path[i]];
- }
- return [self pathWithSegments:segments offset:0 length:newLength];
-}
-
-- (BOOL)isEmpty {
- return self.length == 0;
-}
-
-- (instancetype)pathByRemovingFirstSegment {
- FSTAssert(!self.isEmpty, @"Cannot call pathByRemovingFirstSegment on empty path");
- return [self pathWithSegments:self.segments offset:self.offset + 1 length:self.length - 1];
-}
-
-- (instancetype)pathByRemovingFirstSegments:(int)count {
- FSTAssert(self.length >= count, @"pathByRemovingFirstSegments:%d on path of length %d", count,
- self.length);
- return
- [self pathWithSegments:self.segments offset:self.offset + count length:self.length - count];
-}
-
-- (instancetype)pathByRemovingLastSegment {
- FSTAssert(!self.isEmpty, @"Cannot call pathByRemovingLastSegment on empty path");
- return [self pathWithSegments:self.segments offset:self.offset length:self.length - 1];
-}
-
-- (BOOL)isPrefixOfPath:(FSTPath *)other {
- if (other.length < self.length) {
- return NO;
- }
- for (int i = 0; i < self.length; ++i) {
- if (![self[i] isEqual:other[i]]) {
- return NO;
- }
- }
- return YES;
-}
-
-/** Returns a standardized string representation of this path. */
-- (NSString *)canonicalString {
- @throw FSTAbstractMethodException(); // NOLINT
-}
-@end
-
-@implementation FSTFieldPath
-+ (instancetype)pathWithSegments:(NSArray<NSString *> *)segments {
- return [[FSTFieldPath alloc] initWithSegments:segments offset:0 length:(int)segments.count];
-}
-
-+ (instancetype)pathWithServerFormat:(NSString *)fieldPath {
- NSMutableArray<NSString *> *segments = [NSMutableArray array];
-
- // TODO(b/37244157): Once we move to v1beta1, we should make this more strict. Right now, it
- // allows non-identifier path components, even if they aren't escaped. Technically, this will
- // mangle paths with backticks in them used in v1alpha1, but that's fine.
-
- const char *source = [fieldPath UTF8String];
- char *segment = (char *)malloc(strlen(source) + 1);
- char *segmentEnd = segment;
-
- // If we're inside '`' backticks, then we should ignore '.' dots.
- BOOL inBackticks = NO;
-
- char c;
- do {
- // Examine current character. This is legit even on zero-length strings because there's always
- // a null terminator.
- c = *source++;
- switch (c) {
- case '\0': // Falls through
- case '.':
- if (!inBackticks) {
- // Segment is complete
- *segmentEnd = '\0';
- if (segment == segmentEnd) {
- FSTThrowInvalidArgument(
- @"Invalid field path (%@). Paths must not be empty, begin with "
- @"'.', end with '.', or contain '..'",
- fieldPath);
- }
-
- [segments addObject:[NSString stringWithUTF8String:segment]];
- segmentEnd = segment;
- } else {
- // copy into the current segment
- *segmentEnd++ = c;
- }
- break;
-
- case '`':
- if (inBackticks) {
- inBackticks = NO;
- } else {
- inBackticks = YES;
- }
- break;
-
- case '\\':
- // advance to escaped character
- c = *source++;
- // TODO(b/37244157): Make this a user-facing exception once we finalize field escaping.
- FSTAssert(c != '\0', @"Trailing escape characters not allowed in %@", fieldPath);
- // Fall through
-
- default:
- // copy into the current segment
- *segmentEnd++ = c;
- break;
- }
- } while (c);
-
- FSTAssert(!inBackticks, @"Unterminated ` in path %@", fieldPath);
-
- free(segment);
- return [FSTFieldPath pathWithSegments:segments];
-}
-
-+ (instancetype)keyFieldPath {
- static FSTFieldPath *keyFieldPath;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- keyFieldPath = [FSTFieldPath pathWithSegments:@[ kDocumentKeyPath ]];
- });
- return keyFieldPath;
-}
-
-+ (instancetype)emptyPath {
- static FSTFieldPath *emptyPath;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- emptyPath = [FSTFieldPath pathWithSegments:@[]];
- });
- return emptyPath;
-}
-
-/** Return YES if the string could be used as a segment in a field path without escaping. */
-+ (BOOL)isValidIdentifier:(NSString *)segment {
- if (segment.length == 0) {
- return NO;
- }
- unichar first = [segment characterAtIndex:0];
- if (first != '_' && (first < 'a' || first > 'z') && (first < 'A' || first > 'Z')) {
- return NO;
- }
- for (int i = 1; i < segment.length; i++) {
- unichar c = [segment characterAtIndex:i];
- if (c != '_' && (c < 'a' || c > 'z') && (c < 'A' || c > 'Z') && (c < '0' || c > '9')) {
- return NO;
- }
- }
- return YES;
-}
-
-- (BOOL)isKeyFieldPath {
- return [self isEqual:FSTFieldPath.keyFieldPath];
-}
-
-- (NSString *)canonicalString {
- NSMutableString *result = [NSMutableString string];
- for (int i = 0; i < self.length; i++) {
- if (i > 0) {
- [result appendString:@"."];
- }
-
- NSString *escaped = [self[i] stringByReplacingOccurrencesOfString:@"\\" withString:@"\\\\"];
- escaped = [escaped stringByReplacingOccurrencesOfString:@"`" withString:@"\\`"];
- if (![FSTFieldPath isValidIdentifier:escaped]) {
- escaped = [NSString stringWithFormat:@"`%@`", escaped];
- }
-
- [result appendString:escaped];
- }
- return result;
-}
-
-+ (instancetype)fieldPathWithCPPFieldPath:(const FieldPath &)fieldPath {
- NSMutableArray<NSString *> *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<std::string> 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<NSString *> *)segments {
- return [[FSTResourcePath alloc] initWithSegments:segments offset:0 length:(int)segments.count];
-}
-
-+ (instancetype)pathWithString:(NSString *)resourcePath {
- // NOTE: The client is ignorant of any path segments containing escape sequences (e.g. __id123__)
- // and just passes them through raw (they exist for legacy reasons and should not be used
- // frequently).
-
- if ([resourcePath rangeOfString:@"//"].location != NSNotFound) {
- FSTThrowInvalidArgument(@"Invalid path (%@). Paths must not contain // in them.", resourcePath);
- }
-
- NSMutableArray *segments = [[resourcePath componentsSeparatedByString:@"/"] mutableCopy];
- // We may still have an empty segment at the beginning or end if they had a leading or trailing
- // slash (which we allow).
- [segments removeObject:@""];
-
- return [self pathWithSegments:segments];
-}
-
-- (NSString *)canonicalString {
- // NOTE: The client is ignorant of any path segments containing escape sequences (e.g. __id123__)
- // and just passes them through raw (they exist for legacy reasons and should not be used
- // frequently).
-
- NSMutableString *result = [NSMutableString string];
- for (int i = 0; i < self.length; i++) {
- if (i > 0) {
- [result appendString:@"/"];
- }
- [result appendString:self[i]];
- }
- return result;
-}
-
-+ (instancetype)resourcePathWithCPPResourcePath:(const ResourcePath &)resourcePath {
- NSMutableArray<NSString *> *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<std::string> 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