Skip to content

Commit 82ced2d

Browse files
authored
Add FSTDelegateValue which delegates to model::FieldValue (#2681)
* Fix FSTFieldValue instanceof check to use FSTFieldValue.type Missed in the previous PR because this gets the class via [value class] rather than [value isKindOfClass]. * Add FSTDelegateValue which delegates to model::FieldValue Also migrates FSTBooleanValue to FSTDelegateValue as a proof-of-concept. Other FSTXValues still TODO.
1 parent b30d005 commit 82ced2d

File tree

9 files changed

+229
-131
lines changed

9 files changed

+229
-131
lines changed

Firestore/Example/Tests/Model/FSTFieldValueTests.mm

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ - (void)testWrapsBooleans {
124124
NSArray *values = @[ @YES, @NO ];
125125
for (id value in values) {
126126
FSTFieldValue *wrapped = FSTTestFieldValue(value);
127-
XCTAssertEqualObjects([wrapped class], [FSTBooleanValue class]);
127+
XCTAssertEqualObjects([wrapped class], [FSTDelegateValue class]);
128128
XCTAssertEqualObjects([wrapped value], value);
129129
XCTAssertEqual(wrapped.type, FieldValue::Type::Boolean);
130130
}
@@ -287,7 +287,7 @@ - (void)testWrapsSimpleObjects {
287287
FSTObjectValue *expected = [[FSTObjectValue alloc] initWithDictionary:@{
288288
@"a" : [FSTStringValue stringValue:@"foo"],
289289
@"b" : [FSTIntegerValue integerValue:1LL],
290-
@"c" : [FSTBooleanValue trueValue],
290+
@"c" : FieldValue::True().Wrap(),
291291
@"d" : [FSTNullValue nullValue]
292292
}];
293293
XCTAssertEqualObjects(actual, expected);
@@ -300,7 +300,7 @@ - (void)testWrapsNestedObjects {
300300
@"a" : [[FSTObjectValue alloc] initWithDictionary:@{
301301
@"b" :
302302
[[FSTObjectValue alloc] initWithDictionary:@{@"c" : [FSTStringValue stringValue:@"foo"]}],
303-
@"d" : [FSTBooleanValue booleanValue:YES]
303+
@"d" : FieldValue::True().Wrap()
304304
}]
305305
}];
306306
XCTAssertEqualObjects(actual, expected);
@@ -312,7 +312,7 @@ - (void)testExtractsFields {
312312
FSTAssertIsKindOfClass(obj, FSTObjectValue);
313313

314314
FSTAssertIsKindOfClass([obj valueForPath:testutil::Field("foo")], FSTObjectValue);
315-
XCTAssertEqualObjects([obj valueForPath:testutil::Field("foo.a")], [FSTBooleanValue trueValue]);
315+
XCTAssertEqualObjects([obj valueForPath:testutil::Field("foo.a")], FieldValue::True().Wrap());
316316
XCTAssertEqualObjects([obj valueForPath:testutil::Field("foo.b")],
317317
[FSTStringValue stringValue:@"string"]);
318318

@@ -424,7 +424,7 @@ - (void)testDeletesNestedKeys {
424424

425425
- (void)testArrays {
426426
FSTArrayValue *expected = [[FSTArrayValue alloc]
427-
initWithValueNoCopy:@[ [FSTStringValue stringValue:@"value"], [FSTBooleanValue trueValue] ]];
427+
initWithValueNoCopy:@[ [FSTStringValue stringValue:@"value"], FieldValue::True().Wrap() ]];
428428

429429
FSTArrayValue *actual = (FSTArrayValue *)FSTTestFieldValue(@[ @"value", @YES ]);
430430
XCTAssertEqualObjects(actual, expected);
@@ -434,8 +434,8 @@ - (void)testArrays {
434434
- (void)testValueEquality {
435435
DatabaseId database_id = DatabaseId("project", DatabaseId::kDefault);
436436
NSArray *groups = @[
437-
@[ FSTTestFieldValue(@YES), [FSTBooleanValue booleanValue:YES] ],
438-
@[ FSTTestFieldValue(@NO), [FSTBooleanValue booleanValue:NO] ],
437+
@[ FSTTestFieldValue(@YES), FieldValue::True().Wrap() ],
438+
@[ FSTTestFieldValue(@NO), FieldValue::False().Wrap() ],
439439
@[ FSTTestFieldValue([NSNull null]), [FSTNullValue nullValue] ],
440440
@[ FSTTestFieldValue(@(0.0 / 0.0)), FSTTestFieldValue(@(NAN)), [FSTDoubleValue nanValue] ],
441441
// -0.0 and 0.0 compare: the same (but are not isEqual:)

Firestore/Source/API/FSTUserDataConverter.mm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -426,7 +426,7 @@ - (nullable FSTFieldValue *)parseScalarValue:(nullable id)input context:(ParseCo
426426
// legitimate usage of signed chars is impossible, but this should be rare.
427427
//
428428
// Additionally, for consistency, map unsigned chars to bools in the same way.
429-
return [FSTBooleanValue booleanValue:[input boolValue]];
429+
return FieldValue::FromBoolean([input boolValue]).Wrap();
430430

431431
default:
432432
// All documented codes should be handled above, so this shouldn't happen.

Firestore/Source/Model/FSTFieldValue.h

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -125,15 +125,6 @@ enum class ServerTimestampBehavior { None, Estimate, Previous };
125125
+ (instancetype)nullValue;
126126
@end
127127

128-
/**
129-
* A boolean value stored in Firestore.
130-
*/
131-
@interface FSTBooleanValue : FSTFieldValue <NSNumber *>
132-
+ (instancetype)trueValue;
133-
+ (instancetype)falseValue;
134-
+ (instancetype)booleanValue:(BOOL)value;
135-
@end
136-
137128
/**
138129
* Base class inherited from by FSTIntegerValue and FSTDoubleValue. It implements proper number
139130
* comparisons between the two types.
@@ -288,4 +279,11 @@ enum class ServerTimestampBehavior { None, Estimate, Previous };
288279

289280
@end
290281

282+
/**
283+
* A value that delegates to the c++ model::FieldValue.
284+
*/
285+
@interface FSTDelegateValue : FSTFieldValue <id>
286+
+ (instancetype)delegateWithValue:(FieldValue &&)value;
287+
@end
288+
291289
NS_ASSUME_NONNULL_END

Firestore/Source/Model/FSTFieldValue.mm

Lines changed: 117 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616

1717
#import "Firestore/Source/Model/FSTFieldValue.h"
1818

19+
#include <functional>
20+
#include <utility>
21+
1922
#import "FIRDocumentSnapshot.h"
2023
#import "FIRTimestamp.h"
2124

@@ -161,77 +164,6 @@ - (NSComparisonResult)compare:(FSTFieldValue *)other {
161164

162165
@end
163166

164-
#pragma mark - FSTBooleanValue
165-
166-
@interface FSTBooleanValue ()
167-
@property(nonatomic, assign, readonly) BOOL internalValue;
168-
@end
169-
170-
@implementation FSTBooleanValue
171-
172-
+ (instancetype)trueValue {
173-
static FSTBooleanValue *sharedInstance = nil;
174-
static dispatch_once_t onceToken;
175-
176-
dispatch_once(&onceToken, ^{
177-
sharedInstance = [[FSTBooleanValue alloc] initWithValue:YES];
178-
});
179-
return sharedInstance;
180-
}
181-
182-
+ (instancetype)falseValue {
183-
static FSTBooleanValue *sharedInstance = nil;
184-
static dispatch_once_t onceToken;
185-
186-
dispatch_once(&onceToken, ^{
187-
sharedInstance = [[FSTBooleanValue alloc] initWithValue:NO];
188-
});
189-
return sharedInstance;
190-
}
191-
192-
+ (instancetype)booleanValue:(BOOL)value {
193-
return value ? [FSTBooleanValue trueValue] : [FSTBooleanValue falseValue];
194-
}
195-
196-
- (id)initWithValue:(BOOL)value {
197-
self = [super init];
198-
if (self) {
199-
_internalValue = value;
200-
}
201-
return self;
202-
}
203-
204-
- (FieldValue::Type)type {
205-
return FieldValue::Type::Boolean;
206-
}
207-
208-
- (FSTTypeOrder)typeOrder {
209-
return FSTTypeOrderBoolean;
210-
}
211-
212-
- (id)value {
213-
return self.internalValue ? @YES : @NO;
214-
}
215-
216-
- (BOOL)isEqual:(id)other {
217-
// Since we create shared instances for true / false, we can use reference equality.
218-
return self == other;
219-
}
220-
221-
- (NSUInteger)hash {
222-
return self.internalValue ? 1231 : 1237;
223-
}
224-
225-
- (NSComparisonResult)compare:(FSTFieldValue *)other {
226-
if (other.type == FieldValue::Type::Boolean) {
227-
return WrapCompare<bool>(self.internalValue, ((FSTBooleanValue *)other).internalValue);
228-
} else {
229-
return [self defaultCompare:other];
230-
}
231-
}
232-
233-
@end
234-
235167
#pragma mark - FSTNumberValue
236168

237169
@implementation FSTNumberValue
@@ -1027,4 +959,118 @@ - (NSComparisonResult)compare:(FSTFieldValue *)other {
1027959

1028960
@end
1029961

962+
@interface FSTDelegateValue ()
963+
@property(nonatomic, assign, readonly) FieldValue internalValue;
964+
@end
965+
966+
@implementation FSTDelegateValue
967+
+ (instancetype)delegateWithValue:(FieldValue &&)value {
968+
return [[FSTDelegateValue alloc] initWithValue:std::move(value)];
969+
}
970+
971+
- (id)initWithValue:(FieldValue &&)value {
972+
self = [super init];
973+
if (self) {
974+
_internalValue = std::move(value);
975+
}
976+
return self;
977+
}
978+
979+
- (FieldValue::Type)type {
980+
return self.internalValue.type();
981+
}
982+
983+
- (FSTTypeOrder)typeOrder {
984+
switch (self.internalValue.type()) {
985+
case FieldValue::Type::Null:
986+
return FSTTypeOrderNull;
987+
case FieldValue::Type::Boolean:
988+
return FSTTypeOrderBoolean;
989+
case FieldValue::Type::Integer:
990+
case FieldValue::Type::Double:
991+
return FSTTypeOrderNumber;
992+
case FieldValue::Type::Timestamp:
993+
case FieldValue::Type::ServerTimestamp:
994+
return FSTTypeOrderTimestamp;
995+
case FieldValue::Type::String:
996+
return FSTTypeOrderString;
997+
case FieldValue::Type::Blob:
998+
return FSTTypeOrderBlob;
999+
case FieldValue::Type::Reference:
1000+
return FSTTypeOrderReference;
1001+
case FieldValue::Type::GeoPoint:
1002+
return FSTTypeOrderGeoPoint;
1003+
case FieldValue::Type::Array:
1004+
return FSTTypeOrderArray;
1005+
case FieldValue::Type::Object:
1006+
return FSTTypeOrderObject;
1007+
}
1008+
UNREACHABLE();
1009+
}
1010+
1011+
- (BOOL)isEqual:(id)other {
1012+
// TODO(rsgowman): Port the other FST*Value's, and then remove this comment:
1013+
//
1014+
// Simplification: We'll assume that (eg) FSTBooleanValue(true) !=
1015+
// FSTDelegateValue(FieldValue::FromBoolean(true)). That's not great. We'll
1016+
// handle this by ensuring that we remove (eg) FSTBooleanValue at the same
1017+
// time that FSTDelegateValue handles (eg) booleans to ensure this case never
1018+
// occurs.
1019+
1020+
if (other == self) {
1021+
return YES;
1022+
}
1023+
if (![other isKindOfClass:[self class]]) {
1024+
return NO;
1025+
}
1026+
1027+
return self.internalValue == ((FSTDelegateValue *)other).internalValue;
1028+
}
1029+
1030+
- (id)value {
1031+
switch (self.internalValue.type()) {
1032+
case FieldValue::Type::Null:
1033+
HARD_FAIL("TODO(rsgowman): implement");
1034+
case FieldValue::Type::Boolean:
1035+
return self.internalValue.boolean_value() ? @YES : @NO;
1036+
case FieldValue::Type::Integer:
1037+
case FieldValue::Type::Double:
1038+
case FieldValue::Type::Timestamp:
1039+
case FieldValue::Type::ServerTimestamp:
1040+
case FieldValue::Type::String:
1041+
case FieldValue::Type::Blob:
1042+
case FieldValue::Type::Reference:
1043+
case FieldValue::Type::GeoPoint:
1044+
case FieldValue::Type::Array:
1045+
case FieldValue::Type::Object:
1046+
HARD_FAIL("TODO(rsgowman): implement");
1047+
}
1048+
UNREACHABLE();
1049+
}
1050+
1051+
- (NSComparisonResult)compare:(FSTFieldValue *)other {
1052+
// TODO(rsgowman): Port the other FST*Value's, and then remove this comment:
1053+
//
1054+
// Simplification: We'll assume that if Comparable(self.type, other.type),
1055+
// then other must be a FSTDelegateValue. That's not great. We'll handle this
1056+
// by ensuring that we remove (eg) FSTBooleanValue at the same time that
1057+
// FSTDelegateValue handles (eg) booleans to ensure this case never occurs.
1058+
1059+
if (FieldValue::Comparable(self.type, other.type)) {
1060+
HARD_ASSERT([other isKindOfClass:[FSTDelegateValue class]]);
1061+
return WrapCompare<FieldValue>(self.internalValue, ((FSTDelegateValue *)other).internalValue);
1062+
} else {
1063+
return [self defaultCompare:other];
1064+
}
1065+
}
1066+
1067+
- (NSUInteger)hash {
1068+
return self.internalValue.Hash();
1069+
}
1070+
1071+
@end
1072+
1073+
template <>
1074+
struct Comparator<FieldValue> : public std::less<FieldValue> {};
1075+
10301076
NS_ASSUME_NONNULL_END

Firestore/Source/Remote/FSTSerializerBeta.mm

Lines changed: 38 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -199,49 +199,45 @@ - (NSString *)encodedDatabaseID {
199199
#pragma mark - FSTFieldValue <=> Value proto
200200

201201
- (GCFSValue *)encodedFieldValue:(FSTFieldValue *)fieldValue {
202-
Class fieldClass = [fieldValue class];
203-
if (fieldClass == [FSTNullValue class]) {
204-
return [self encodedNull];
205-
206-
} else if (fieldClass == [FSTBooleanValue class]) {
207-
return [self encodedBool:[[fieldValue value] boolValue]];
208-
209-
} else if (fieldClass == [FSTIntegerValue class]) {
210-
return [self encodedInteger:[[fieldValue value] longLongValue]];
211-
212-
} else if (fieldClass == [FSTDoubleValue class]) {
213-
return [self encodedDouble:[[fieldValue value] doubleValue]];
214-
215-
} else if (fieldClass == [FSTStringValue class]) {
216-
return [self encodedString:[fieldValue value]];
217-
218-
} else if (fieldClass == [FSTTimestampValue class]) {
219-
FIRTimestamp *value = static_cast<FIRTimestamp *>([fieldValue value]);
220-
return [self encodedTimestampValue:Timestamp{value.seconds, value.nanoseconds}];
221-
} else if (fieldClass == [FSTGeoPointValue class]) {
222-
return [self encodedGeoPointValue:[fieldValue value]];
223-
224-
} else if (fieldClass == [FSTBlobValue class]) {
225-
return [self encodedBlobValue:[fieldValue value]];
226-
227-
} else if (fieldClass == [FSTReferenceValue class]) {
228-
FSTReferenceValue *ref = (FSTReferenceValue *)fieldValue;
229-
DocumentKey key = [[ref value] key];
230-
return [self encodedReferenceValueForDatabaseID:[ref databaseID] key:key];
231-
232-
} else if (fieldClass == [FSTObjectValue class]) {
233-
GCFSValue *result = [GCFSValue message];
234-
result.mapValue = [self encodedMapValue:(FSTObjectValue *)fieldValue];
235-
return result;
236-
237-
} else if (fieldClass == [FSTArrayValue class]) {
238-
GCFSValue *result = [GCFSValue message];
239-
result.arrayValue = [self encodedArrayValue:(FSTArrayValue *)fieldValue];
240-
return result;
202+
switch (fieldValue.type) {
203+
case FieldValue::Type::Null:
204+
return [self encodedNull];
205+
case FieldValue::Type::Boolean:
206+
return [self encodedBool:[[fieldValue value] boolValue]];
207+
case FieldValue::Type::Integer:
208+
return [self encodedInteger:[[fieldValue value] longLongValue]];
209+
case FieldValue::Type::Double:
210+
return [self encodedDouble:[[fieldValue value] doubleValue]];
211+
case FieldValue::Type::Timestamp: {
212+
FIRTimestamp *value = static_cast<FIRTimestamp *>([fieldValue value]);
213+
return [self encodedTimestampValue:Timestamp{value.seconds, value.nanoseconds}];
214+
}
215+
case FieldValue::Type::String:
216+
return [self encodedString:[fieldValue value]];
217+
case FieldValue::Type::Blob:
218+
return [self encodedBlobValue:[fieldValue value]];
219+
case FieldValue::Type::Reference: {
220+
FSTReferenceValue *ref = (FSTReferenceValue *)fieldValue;
221+
DocumentKey key = [[ref value] key];
222+
return [self encodedReferenceValueForDatabaseID:[ref databaseID] key:key];
223+
}
224+
case FieldValue::Type::GeoPoint:
225+
return [self encodedGeoPointValue:[fieldValue value]];
226+
case FieldValue::Type::Array: {
227+
GCFSValue *result = [GCFSValue message];
228+
result.arrayValue = [self encodedArrayValue:(FSTArrayValue *)fieldValue];
229+
return result;
230+
}
231+
case FieldValue::Type::Object: {
232+
GCFSValue *result = [GCFSValue message];
233+
result.mapValue = [self encodedMapValue:(FSTObjectValue *)fieldValue];
234+
return result;
235+
}
241236

242-
} else {
243-
HARD_FAIL("Unhandled type %s on %s", NSStringFromClass([fieldValue class]), fieldValue);
237+
case FieldValue::Type::ServerTimestamp:
238+
HARD_FAIL("Unhandled type %s on %s", NSStringFromClass([fieldValue class]), fieldValue);
244239
}
240+
UNREACHABLE();
245241
}
246242

247243
- (FSTFieldValue *)decodedFieldValue:(GCFSValue *)valueProto {
@@ -250,7 +246,7 @@ - (FSTFieldValue *)decodedFieldValue:(GCFSValue *)valueProto {
250246
return [FSTNullValue nullValue];
251247

252248
case GCFSValue_ValueType_OneOfCase_BooleanValue:
253-
return [FSTBooleanValue booleanValue:valueProto.booleanValue];
249+
return FieldValue::FromBoolean(valueProto.booleanValue).Wrap();
254250

255251
case GCFSValue_ValueType_OneOfCase_IntegerValue:
256252
return [FSTIntegerValue integerValue:valueProto.integerValue];

0 commit comments

Comments
 (0)