aboutsummaryrefslogtreecommitdiffhomepage
path: root/Firestore/Source
diff options
context:
space:
mode:
authorGravatar Michael Lehenbauer <mikelehen@gmail.com>2018-04-12 13:50:02 -0700
committerGravatar GitHub <noreply@github.com>2018-04-12 13:50:02 -0700
commitc2d4d3a9cf7be4d86efe6549bbdecad3d72b7088 (patch)
treed6848e11af4fdc6c854086517193e06bebd3842e /Firestore/Source
parent97844711be9a44aa086eccbee1ebc1d79b6db978 (diff)
Small cleanup to UserDataConverter / FieldValue sentinel code to make adding more FieldValue sentinels easier. (#1077)
Diffstat (limited to 'Firestore/Source')
-rw-r--r--Firestore/Source/API/FIRFieldValue+Internal.h8
-rw-r--r--Firestore/Source/API/FIRFieldValue.mm8
-rw-r--r--Firestore/Source/API/FSTUserDataConverter.mm125
3 files changed, 101 insertions, 40 deletions
diff --git a/Firestore/Source/API/FIRFieldValue+Internal.h b/Firestore/Source/API/FIRFieldValue+Internal.h
index 1b4a99c..2eb927e 100644
--- a/Firestore/Source/API/FIRFieldValue+Internal.h
+++ b/Firestore/Source/API/FIRFieldValue+Internal.h
@@ -18,6 +18,14 @@
NS_ASSUME_NONNULL_BEGIN
+@interface FIRFieldValue (Internal)
+/**
+ * The method name (e.g. "FieldValue.delete()") that was used to create this FIRFieldValue
+ * instance, for use in error messages, etc.
+ */
+@property(nonatomic, strong, readonly) NSString *methodName;
+@end
+
/**
* FIRFieldValue class for field deletes. Exposed internally so code can do isKindOfClass checks on
* it.
diff --git a/Firestore/Source/API/FIRFieldValue.mm b/Firestore/Source/API/FIRFieldValue.mm
index 7ae4fb0..0d0a9c4 100644
--- a/Firestore/Source/API/FIRFieldValue.mm
+++ b/Firestore/Source/API/FIRFieldValue.mm
@@ -46,6 +46,10 @@ NS_ASSUME_NONNULL_BEGIN
return sharedInstance;
}
+- (NSString *)methodName {
+ return @"FieldValue.delete()";
+}
+
@end
#pragma mark - FSTServerTimestampFieldValue
@@ -72,6 +76,10 @@ NS_ASSUME_NONNULL_BEGIN
return sharedInstance;
}
+- (NSString *)methodName {
+ return @"FieldValue.serverTimestamp()";
+}
+
@end
#pragma mark - FIRFieldValue
diff --git a/Firestore/Source/API/FSTUserDataConverter.mm b/Firestore/Source/API/FSTUserDataConverter.mm
index 2eee878..6320e83 100644
--- a/Firestore/Source/API/FSTUserDataConverter.mm
+++ b/Firestore/Source/API/FSTUserDataConverter.mm
@@ -507,64 +507,110 @@ typedef NS_ENUM(NSInteger, FSTUserDataSource) {
*/
- (nullable FSTFieldValue *)parseData:(id)input context:(FSTParseContext *)context {
input = self.preConverter(input);
- if ([input isKindOfClass:[NSArray class]]) {
- // TODO(b/34871131): Include the path containing the array in the error message.
- if (context.isArrayElement) {
- FSTThrowInvalidArgument(@"Nested arrays are not supported");
- }
- NSArray *array = input;
- NSMutableArray<FSTFieldValue *> *result = [NSMutableArray arrayWithCapacity:array.count];
- [array enumerateObjectsUsingBlock:^(id entry, NSUInteger idx, BOOL *stop) {
- FSTFieldValue *_Nullable parsedEntry =
- [self parseData:entry context:[context contextForArrayIndex:idx]];
- if (!parsedEntry) {
- // Just include nulls in the array for fields being replaced with a sentinel.
- parsedEntry = [FSTNullValue nullValue];
- }
- [result addObject:parsedEntry];
- }];
+ if ([input isKindOfClass:[NSDictionary class]]) {
+ return [self parseDictionary:(NSDictionary *)input context:context];
+ } else {
// If context.path is nil we are already inside an array and we don't support field mask paths
// more granular than the top-level array.
if (context.path) {
[context appendToFieldMaskWithFieldPath:*context.path];
}
- return [[FSTArrayValue alloc] initWithValueNoCopy:result];
- } else if ([input isKindOfClass:[NSDictionary class]]) {
- NSDictionary *dict = input;
- NSMutableDictionary<NSString *, FSTFieldValue *> *result =
- [NSMutableDictionary dictionaryWithCapacity:dict.count];
- [dict enumerateKeysAndObjectsUsingBlock:^(NSString *key, id value, BOOL *stop) {
- FSTFieldValue *_Nullable parsedValue =
- [self parseData:value context:[context contextForField:key]];
- if (parsedValue) {
- result[key] = parsedValue;
+ if ([input isKindOfClass:[NSArray class]]) {
+ // TODO(b/34871131): Include the path containing the array in the error message.
+ if (context.isArrayElement) {
+ FSTThrowInvalidArgument(@"Nested arrays are not supported");
}
- }];
- return [[FSTObjectValue alloc] initWithDictionary:result];
+ return [self parseArray:(NSArray *)input context:context];
+ } else if ([input isKindOfClass:[FIRFieldValue class]]) {
+ // parseSentinelFieldValue may add an FSTFieldTransform, but we return nil since nothing
+ // should be included in the actual parsed data.
+ [self parseSentinelFieldValue:(FIRFieldValue *)input context:context];
+ return nil;
+ } else {
+ return [self parseScalarValue:input context:context];
+ }
+ }
+}
- } else {
- // If context.path is null, we are inside an array and we should have already added the root of
- // the array to the field mask.
- if (context.path) {
- [context appendToFieldMaskWithFieldPath:*context.path];
+- (FSTFieldValue *)parseDictionary:(NSDictionary *)dict context:(FSTParseContext *)context {
+ NSMutableDictionary<NSString *, FSTFieldValue *> *result =
+ [NSMutableDictionary dictionaryWithCapacity:dict.count];
+ [dict enumerateKeysAndObjectsUsingBlock:^(NSString *key, id value, BOOL *stop) {
+ FSTFieldValue *_Nullable parsedValue =
+ [self parseData:value context:[context contextForField:key]];
+ if (parsedValue) {
+ result[key] = parsedValue;
+ }
+ }];
+ return [[FSTObjectValue alloc] initWithDictionary:result];
+}
+
+- (FSTFieldValue *)parseArray:(NSArray *)array context:(FSTParseContext *)context {
+ NSMutableArray<FSTFieldValue *> *result = [NSMutableArray arrayWithCapacity:array.count];
+ [array enumerateObjectsUsingBlock:^(id entry, NSUInteger idx, BOOL *stop) {
+ FSTFieldValue *_Nullable parsedEntry =
+ [self parseData:entry context:[context contextForArrayIndex:idx]];
+ if (!parsedEntry) {
+ // Just include nulls in the array for fields being replaced with a sentinel.
+ parsedEntry = [FSTNullValue nullValue];
}
- return [self parseScalarValue:input context:context];
+ [result addObject:parsedEntry];
+ }];
+ return [[FSTArrayValue alloc] initWithValueNoCopy:result];
+}
+
+/**
+ * "Parses" the provided FIRFieldValue, adding any necessary transforms to
+ * context.fieldTransforms.
+ */
+- (void)parseSentinelFieldValue:(FIRFieldValue *)fieldValue context:(FSTParseContext *)context {
+ // Sentinels are only supported with writes, and not within arrays.
+ if (![context isWrite]) {
+ FSTThrowInvalidArgument(@"%@ can only be used with updateData() and setData()%@",
+ fieldValue.methodName, [context fieldDescription]);
+ }
+ if (!context.path) {
+ FSTThrowInvalidArgument(@"%@ is not currently supported inside arrays", fieldValue.methodName);
+ }
+
+ if ([fieldValue isKindOfClass:[FSTDeleteFieldValue class]]) {
+ if (context.dataSource == FSTUserDataSourceMergeSet) {
+ // No transform to add for a delete, so we do nothing.
+ } else if (context.dataSource == FSTUserDataSourceUpdate) {
+ FSTAssert(context.path->size() > 0,
+ @"FieldValue.delete() at the top level should have already been handled.");
+ FSTThrowInvalidArgument(
+ @"FieldValue.delete() can only appear at the top level of your "
+ "update data%@",
+ [context fieldDescription]);
+ } else {
+ // We shouldn't encounter delete sentinels for queries or non-merge setData calls.
+ FSTThrowInvalidArgument(
+ @"FieldValue.delete() can only be used with updateData() and setData() with "
+ @"SetOptions.merge()%@",
+ [context fieldDescription]);
+ }
+ } else if ([fieldValue isKindOfClass:[FSTServerTimestampFieldValue class]]) {
+ [context appendToFieldTransformsWithFieldPath:*context.path
+ transformOperation:absl::make_unique<ServerTimestampTransform>(
+ ServerTimestampTransform::Get())];
+ } else {
+ FSTFail(@"Unknown FIRFieldValue type: %@", NSStringFromClass([fieldValue class]));
}
}
/**
- * Helper to parse a scalar value (i.e. not an NSDictionary or NSArray).
+ * Helper to parse a scalar value (i.e. not an NSDictionary, NSArray, or FIRFieldValue).
*
* Note that it handles all NSNumber values that are encodable as int64_t or doubles
* (depending on the underlying type of the NSNumber). Unsigned integer values are handled though
* any value outside what is representable by int64_t (a signed 64-bit value) will throw an
* exception.
*
- * @return The parsed value, or nil if the value was a FieldValue sentinel that should not be
- * included in the resulting parsed data.
+ * @return The parsed value.
*/
-- (nullable FSTFieldValue *)parseScalarValue:(nullable id)input context:(FSTParseContext *)context {
+- (FSTFieldValue *)parseScalarValue:(nullable id)input context:(FSTParseContext *)context {
if (!input || [input isMemberOfClass:[NSNull class]]) {
return [FSTNullValue nullValue];
@@ -671,8 +717,7 @@ typedef NS_ENUM(NSInteger, FSTUserDataSource) {
FSTAssert(context.path->size() > 0,
@"FieldValue.delete() at the top level should have already been handled.");
FSTThrowInvalidArgument(
- @"FieldValue.delete() can only appear at the top level of your "
- "update data%@",
+ @"FieldValue.delete() can only appear at the top level of your update data%@",
[context fieldDescription]);
} else {
// We shouldn't encounter delete sentinels for queries or non-merge setData calls.