-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Make FSTTimestamp into a public class: #698
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
- rename to FIRTimestamp and move into Public/; - split out internal part of FIRTimestamp; - hide the fact that implementation uses nanoseconds - only show microseconds in interface and truncate sub-microsecond precision if FIRTimestamp is created from NSDate.
|
Gil, this is using microseconds, so I presume it's obsolete, but I'll still appreciate any feedback. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The changes to get from micros to nanos on top of this change would be pretty small, so let's press on.
Thanks for working through this change so far!
| @@ -0,0 +1,149 @@ | |||
| /* | |||
| * Copyright 2017 Google | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
New file, new year :-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
|
|
||
| #import <XCTest/XCTest.h> | ||
|
|
||
| #import "FirebaseFirestore/FIRTimestamp.h" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI framework imports should be in angle brackets.
However, this should be unnecessary since you've imported the whole FirebaseFirestore module above.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for pointing it out, my Objective-C is pretty weak.
|
|
||
| #import "Firestore/Example/Tests/Util/FSTIntegrationTestCase.h" | ||
|
|
||
| @interface FIRTimestampAsArgumentTests : FSTIntegrationTestCase |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mild nit: depending on how much these overlap with some of the other tests, as much as this arrangement makes it easier to write these tests, it could be harder to find them later when e.g. working on changing queries or user input validation.
To the extent we can do so, we should group these tests with the other tests with similar behavior for the other types. For example query limits are already explored in FIRQueryTests.
However it's also possible that there's just not much overlap, in which case, carry on.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I actually went back-and-forth on this once, so I don't have a strong stance here. Moved tests to existing files.
| } | ||
|
|
||
| - (nullable id)valueForField:(id)field options:(FIRSnapshotOptions *)options { | ||
| - (nullable FSTFieldValue*)fieldValueForKey:(id)key { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is confusingly similar to valueForField:. Also I'm not sure this name really works: "keys" generally refer to document keys but fields/field paths refer to fields within a document. This is using key to mean field.
If you renamed this fieldValueForField to match the behavior of the argument then I think the functional confusion is probably too much.
Really the most important thing to share here is the bit that converts a field argument (which is an NSString or FIRFieldPath) to an FSTFieldPath. An alternative arrangement that might be clearer would be to pull that part out into a separate fieldPathForFieldArgument: helper and then have both valueForField and your new method call that while leaving them both to actually index into [[self.internalDocument data] valueForPath:] for themselves.
Even after renaming this could be overcome instead with comments, perhaps contrasting how these behave or what their return types are, etc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added this as a private helper method before valueForPath was added. Implemented your suggestion.
| return result; | ||
| } | ||
|
|
||
| - (nullable FIRTimestamp*)getTimestamp:(id)key { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In general Objective-C naming is obtuse. Full guide here: https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CodingGuidelines/Articles/NamingMethods.html
Also, see Google-specific guidance here: http://google.github.io/styleguide/objcguide.html
Briefly, for a method that returns a value the label should start by describing that thing. Arguments get their own part of the label separated from the return value with a preposition. "get" as a prefix is almost never used.
Taken together with the feedback above that the argument is a field and not a key, this should likely be named something like timestampForField.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
| @interface FSTTimestamp : NSObject <NSCopying> | ||
|
|
||
| - (instancetype)init NS_UNAVAILABLE; | ||
| @interface FIRTimestamp () |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm sort of surprised declaring this separately like this works and generates no warnings. It's not something we've commonly done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I took inspiration from FIRFieldPath+Internal.h. It looks like I can't have a designated initializer anywhere apart from the class definition itself or in a class extension.
On a related note: can we use the same +Internal mechanism now that FIRTimestamp is to be a Firebase type?
EDIT: found examples in Firebase/Core showing that it's possible.
Firestore/Source/API/FIRTimestamp.mm
Outdated
| */ | ||
|
|
||
| #import "Firestore/Source/Core/FSTTimestamp.h" | ||
| #import "FIRTimestamp+Internal.h" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only headers in the Public folder should be unqualified. All the rest should be fully qualified.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
| * | ||
| * @return The timestamp contained in the field or `nil` if the field doesn't exist. | ||
| */ | ||
| - (nullable FIRTimestamp *)getTimestamp:(id)key; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm surprised I didn't catch this looking at the API doc, but this is a highly non-idiomatic name as I commented above. The Objective-C-ish way of naming this would be timestampForField.
Note that objectForKeyedSubscript is an externally specified API that allows subscript notation to work against a document snapshot. We're using "key" there because that's what the API is documented as taking, but otherwise these values are "fields", not "keys".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
| @@ -0,0 +1,60 @@ | |||
| /* | |||
| * Copyright 2017 Google | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Happy new year!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Two major changes left:
- move FIRTimestamp to Core;
- remove timestampForField, add new SnapshotOptions option.
Given the discussion on options today, should I add a new option to DocumentSnapshot as an overload of data/valueForField? With the existing SnapshotOptions, it's not clear how to extend it, and if we've decided to deprecate it, that doesn't seem worth the trouble.
| @@ -0,0 +1,149 @@ | |||
| /* | |||
| * Copyright 2017 Google | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
|
|
||
| #import <XCTest/XCTest.h> | ||
|
|
||
| #import "FirebaseFirestore/FIRTimestamp.h" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for pointing it out, my Objective-C is pretty weak.
|
|
||
| #import "Firestore/Example/Tests/Util/FSTIntegrationTestCase.h" | ||
|
|
||
| @interface FIRTimestampAsArgumentTests : FSTIntegrationTestCase |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I actually went back-and-forth on this once, so I don't have a strong stance here. Moved tests to existing files.
| } | ||
|
|
||
| - (nullable id)valueForField:(id)field options:(FIRSnapshotOptions *)options { | ||
| - (nullable FSTFieldValue*)fieldValueForKey:(id)key { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added this as a private helper method before valueForPath was added. Implemented your suggestion.
| * | ||
| * @return The timestamp contained in the field or `nil` if the field doesn't exist. | ||
| */ | ||
| - (nullable FIRTimestamp *)getTimestamp:(id)key; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
| @@ -0,0 +1,60 @@ | |||
| /* | |||
| * Copyright 2017 Google | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
| @interface FSTTimestamp : NSObject <NSCopying> | ||
|
|
||
| - (instancetype)init NS_UNAVAILABLE; | ||
| @interface FIRTimestamp () |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I took inspiration from FIRFieldPath+Internal.h. It looks like I can't have a designated initializer anywhere apart from the class definition itself or in a class extension.
On a related note: can we use the same +Internal mechanism now that FIRTimestamp is to be a Firebase type?
EDIT: found examples in Firebase/Core showing that it's possible.
Firestore/Source/API/FIRTimestamp.mm
Outdated
| */ | ||
|
|
||
| #import "Firestore/Source/Core/FSTTimestamp.h" | ||
| #import "FIRTimestamp+Internal.h" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
| return result; | ||
| } | ||
|
|
||
| - (nullable FIRTimestamp*)getTimestamp:(id)key { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PTAL.
Summary of changes:
- FIRTimestamp is now in FirebaseCore, including the test. Removed usage of some Firestore helper functions;
- as discussed today, reverted most Firestore-related changes. The only changes in Firestore are renamings, changes of import paths, and a test to check that DocumentSnapshot still returns NSDates (this test perhaps should also be split from this PR).
One thing I'd like feedback on in particular is the split of FIRTimestamp into public/private. I tried to imitate what other headers in Firestore were doing, but I don't know the full rationale. As it stands, most usages in Firestore require the private header, and I'm not sure whether it makes sense - perhaps the usages should be fixed, or some stuff moved from private to public header (it's mostly creation of timestamp with current date/time, and comparison).
| @interface FIRTimestampTest : XCTestCase | ||
| @end | ||
|
|
||
| NSDate *TestDate(int year, int month, int day, int hour, int minute, int second) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I extracted this helper function from FSTHelpers.
| @@ -0,0 +1,53 @@ | |||
| /* | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have a couple of questions regarding this file:
- In general, what is the logic behind what should be kept in an internal header? I basically split all but the most basic functionality.
- In the build process, how does the separation into public and private header work? I found out I could include <FirebaseCore/some_header_from_private_folder.h> anywhere in Firestore without any issue. Is there some special build step to make these headers available inside the SDK but unavailable for users, or are private headers always available, and it's just a convention that they shouldn't be used?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have a couple of questions regarding this file:
- In general, what is the logic behind what should be kept in an internal header? I basically split all but the most basic functionality.
The general rule is to reserve the public header for committed public APIs. Everything else goes in the private header.
Some classes within Firestore hide their initializers this way so that they must be constructed using other mechanisms. It's not universally required that initializers be hidden and indeed for plain old data types this makes the types unfriendly.
- In the build process, how does the separation into public and private header work? I found out I could include <FirebaseCore/some_header_from_private_folder.h> anywhere in Firestore without any issue. Is there some special build step to make these headers available inside the SDK but unavailable for users, or are private headers always available, and it's just a convention that they shouldn't be used?
The private headers are available at build time within the SDK but aren't published as a part of the built framework. Source-only builds of the SDK have access to these, but source only builds can do whatever they want.
| NS_ASSUME_NONNULL_BEGIN | ||
|
|
||
| /** | ||
| * An FSTTimestamp represents an absolute time from the backend at up to nanosecond precision. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll rework this comment.
|
|
||
| #import <Foundation/Foundation.h> | ||
|
|
||
| NS_ASSUME_NONNULL_BEGIN |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can NS_ASSUME_NONNULL be used in FirebaseCore?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes.
| [self awaitExpectations]; | ||
| } | ||
|
|
||
| - (NSDictionary<NSString *, id> *)testDataWithTimestamp:(FIRTimestamp *)timestamp { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test must break when we switch the default option later on.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does our today's discussion about Timestamp code location on Android affect iOS? (i.e., should I move FIRTimestamp to Firestore/Source/Public?)
| @implementation FIRTimestampTest | ||
|
|
||
| - (void)testFromDate { | ||
| // Very carefully construct an NSDate that won't lose precision with its milliseconds. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done (with a very minor modification to the wording). I think this comment was originally copied from Android.
Firebase/Core/FIRTimestamp.m
Outdated
| @@ -1,5 +1,5 @@ | |||
| /* | |||
| * Copyright 2017 Google | |||
| * Copyright 2018 Google | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
So the rationale seems to be:
- if a file is modified OR moved, don't touch the copyright;
- if the file is added, have a copyright with the current year;
- if a file is modified AND moved, whether to update the copyright depends on whether the file was modified in any significant way.
Does the above look correct?
| + (instancetype)timestampWithSeconds:(int64_t)seconds nanoseconds:(int32_t)nanoseconds; | ||
|
|
||
| /** Creates a new timestamp from the given date. */ | ||
| + (instancetype)timestampWithDate:(NSDate *)date; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@wilhuff, can/should I add some tests for Timestamp to Example/SwiftBuildTest/main.swift?
| * @param seconds the number of seconds since epoch. | ||
| * @param nanoseconds the number of nanoseconds after the seconds. | ||
| */ | ||
| - (instancetype)initWithSeconds:(int64_t)seconds |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved it to the public header.
I also moved timestamp factory function that returns a current timestamp to the public header, for consistency with Android (since we discussed today that `Timestamp#now' should exist on Android).
Let me know if you think compare should be moved to public as well. I'm less inclined towards ISOString - though it seems pretty cool, it doesn't currently exist on Android, so for consistency it would have to be implemented there as well.
Firebase/Core/Public/FIRTimestamp.h
Outdated
| /** | ||
| * An FSTTimestamp represents an absolute time from the backend at up to nanosecond precision. | ||
| * An FSTTimestamp is represented in terms of UTC and does not have an associated timezone. | ||
| * An FIRTimestamp represents an absolute time from the backend at up to microsecond precision. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does Objective C use the same Javadoc style as Android (i.e., should I modify the link into "@see <a.../>")?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Basically LGTM aside from source location and compare:.
Friday's discussion about Android's packaging difficulties does not apply as strongly here but it seems like we were agreed that Core is perhaps not the best place and that we needed to figure something else out.
I'm more inclined to get this committed already so this change isn't hanging outstanding forever. Let's commit this as part of Firestore now and then we can move it to its eventual home later.
Firebase/Core/FIRTimestamp.m
Outdated
| @@ -1,5 +1,5 @@ | |||
| /* | |||
| * Copyright 2017 Google | |||
| * Copyright 2018 Google | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If by "added" you mean the file is new, then yes. New files get the current year.
If you're searching for a standard consult git diff -C --find-copies-harder but if anyone could reasonably argue it's a copy of something else just leave it alone.
The standard in the law is "visually perceptible copies". Not "exact". You're not required to update the notice when making changes.
| * @param seconds the number of seconds since epoch. | ||
| * @param nanoseconds the number of nanoseconds after the seconds. | ||
| */ | ||
| - (instancetype)initWithSeconds:(int64_t)seconds |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Compare can be made public to parallel Android's Comparable<Timestamp>.
Don't bother with ISOString. The need is internal and making this consistently available is more painful than it's worth.
Firebase/Core/Public/FIRTimestamp.h
Outdated
| /** | ||
| * An FSTTimestamp represents an absolute time from the backend at up to nanosecond precision. | ||
| * An FSTTimestamp is represented in terms of UTC and does not have an associated timezone. | ||
| * An FIRTimestamp represents an absolute time from the backend at up to microsecond precision. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Our Swift and Objective-C docs are published using Jazzy which uses a subset of the published standard keywords, documented here:
https://github.com/realm/jazzy#supported-keywords
AFAICT you can't insert the markup around the contents of the @see. It's just @see URL, like this:
| * @see https://cloud.google.com/storage/docs/json_api/v1/objects#resource |
| + (instancetype)timestampWithSeconds:(int64_t)seconds nanoseconds:(int32_t)nanoseconds; | ||
|
|
||
| /** Creates a new timestamp from the given date. */ | ||
| + (instancetype)timestampWithDate:(NSDate *)date; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It wouldn't hurt, but probably isn't worth it. We're about to add a bunch of actual tests for these public types via changes like #815 and there it's an actual test of behavior in Swift which is even more than our "does it build" demonstration in main.swift.
|
Once we go to source pods in Firebase 5.0, we'll have the infrastructure to make this an optional FirebaseCore subspec. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved timestamp files back to Firestore/Source/{Public,API}.
| * @param seconds the number of seconds since epoch. | ||
| * @param nanoseconds the number of nanoseconds after the seconds. | ||
| */ | ||
| - (instancetype)initWithSeconds:(int64_t)seconds |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Made compare public.
Firebase/Core/Public/FIRTimestamp.h
Outdated
| /** | ||
| * An FSTTimestamp represents an absolute time from the backend at up to nanosecond precision. | ||
| * An FSTTimestamp is represented in terms of UTC and does not have an associated timezone. | ||
| * An FIRTimestamp represents an absolute time from the backend at up to microsecond precision. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
| #import <FirebaseFirestore/FIRGeoPoint.h> | ||
| #import <FirebaseFirestore/FIRTimestamp.h> | ||
| #import <XCTest/XCTest.h> | ||
| #import "FIRTimestamp.h" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From tests these should be framework imports, like #import <FirebaseFirestore/FIRGeoPoint.h> above.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed in all test files.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
microseconds in interface and truncate sub-microsecond precision if
FIRTimestamp is created from NSDate.