-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Change CredentialsProvider::TokenListener to use StatusOr<Token> #945
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
Rather than a token plus error code/msg. Note that this is a direct conversion. In particular, it is still possible to have a token that isn't valid, i.e. this is still possible: ``` void foo(StatusOr<Token> token) { assert(token.ok()); Token& t = token.ValueOrDie(); assert(!t.is_valid()); } ```
Instead, we'll just use StatusOr<Token>. Note that unauthenticated tokens are handled as a special case; they're created via: Token::Unauthenticated() and are otherwise "valid", though attempting to retrieve the raw token on one of these tokens will cause an assertion failure.
@@ -46,7 +46,7 @@ class Token { | |||
|
|||
/** The actual raw token. */ | |||
const std::string& token() const { | |||
FIREBASE_ASSERT(is_valid_); | |||
FIREBASE_ASSERT(user_.is_authenticated()); |
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 should prevent us from retrieving the token for an unauthenticated user. However, there's no reason we couldn't return it anyways (it's just the empty string).
We could change this to a dev assert, or eliminate it altogether.
No manual testing yet (will start now), but I wanted to get this out early to ensure this is the right approach. |
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.
Basic idea LGTM. A few minor notes about details.
const absl::string_view error_msg) { | ||
NSError *error = util::WrapNSError(error_code, error_msg); | ||
/*force_refresh=*/false, [self, rpcFactory, errorHandler](util::StatusOr<Token> result) { | ||
NSError *error = util::WrapNSError(result.status()); |
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.
We've been calling these utilities two different things: "wrap parameter" or "make return type". We have a few cases where we did "wrap return type" but these seem to have been in error (mostly stemming from WrapNSStringNoCopy
really wanting to convey that it was well and truly wrapping the underlying string.
I don't feel terribly strongly about this, but it seems like we could move toward a standard naming for this. Without expressing too strong a preference either way, how about MakeNSError
?
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 like that. (I like WrapParameter
less, as WrapNSError can now take either a Status or an int/msg). 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 agree that WrapNSError
sounds as if an existing NSError
were wrapped in some object. Since this is a conversion, I can also suggest ToNSError
or AsNSError
in addition to MakeNSError
.
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.
Any chance, error_msg
could be populated through? error code might be enough but error message comes handy to provide debug info.
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 we've been doing 'make return type', then I prefer that for consistencies sake (and have done that here.)
(Though I like 'to return type' slightly better.)
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.
error_msg should already be populated by the WrapNSError/MakeNSError call. (StatusOr contains both a code and an error message.)
const absl::string_view error_msg) { | ||
NSError *error = util::WrapNSError(error_code, error_msg); | ||
/*force_refresh=*/false, [self, rpcFactory, errorHandler](util::StatusOr<Token> result) { | ||
NSError *error = util::WrapNSError(result.status()); | ||
[self.workerDispatchQueue dispatchAsyncAllowingSameQueue:^{ | ||
if (error) { |
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.
We should defer wrapping the error until we need it for interoperating so that we can get closer to the end state of just using status. This should be:
if (!result.ok()) {
errorHandler(MakeNSError(result.status()));
}
// ...
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.
} | ||
|
||
Token::Token() : token_(), user_(User::Unauthenticated()), is_valid_(false) { | ||
Token::Token() : token_(), user_(User::Unauthenticated()) { |
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.
Is it reasonable to just wholesale avoid having this constructor?
We want callers to use Token::Unauthenticated
when they mean this.
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's private, so callers can't use it anyways. But it also no longer adds any value. Removed.
error == nil ? FirestoreErrorCode::Ok : error.code, | ||
error == nil ? "" : util::MakeStringView(error.localizedDescription)); | ||
if (error == nil) { | ||
completion(Token{util::MakeStringView(token), contents->current_user}); |
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 creates a semi-broken string view, wherein the contents are actually the nullptr
. If token
is nil
, MakeStringView
doesn't do anything special to ensure the string view is the empty string.
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.
What does [str lengthOfBytesUsingEncoding:NSUTF8StringEncoding]
return if str
is nil
?
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.
Objective-C has a notion of nil dispatch. Essentially if you call a method on nil
the result will be some zero value appropriate to the type: a nil pointer for NSObject-derived pointer types (including NSString), nullptr for unmanaged pointer types, zero for integral types, etc.
I believe MakeStringView would construct an absl::string_view{nullptr, 0}
if passed a nil token.
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 the explanation. I think it's a valid way to create an empty string_view
(doc), unless some code relies on the assumption that empty string view is == ""
.
|
||
/** Returns an invalid token. */ | ||
static const Token& Invalid(); | ||
/** Returns a token for an unauthenticated user. */ |
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.
Please get in the habit of double-checking whether or not there are equivalents on the other platforms and either:
- avoid the new concept,
- port use concepts over,
- make PORTABILITY NOTES indicating the deviation and why, or
- file bugs to sort this out as a follow-up
There is no equivalent concept on Android and the production code path in the credential provider doesn't actually use this to create the unauthenticated token.
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.
EXPECT_TRUE(result.ok()); | ||
const Token& token = result.ValueOrDie(); | ||
EXPECT_ANY_THROW(token.token()); | ||
const User& user = result.ValueOrDie().user(); |
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.
You can make this token.user()
.
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.
error == nil ? FirestoreErrorCode::Ok : error.code, | ||
error == nil ? "" : util::MakeStringView(error.localizedDescription)); | ||
if (error == nil) { | ||
completion(Token{util::MakeStringView(token), contents->current_user}); |
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.
What does [str lengthOfBytesUsingEncoding:NSUTF8StringEncoding]
return if str
is nil
?
error_code = static_cast<FirestoreErrorCode>(error.code); | ||
} | ||
completion( | ||
util::Status(FirestoreErrorCode::Unknown, |
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.
Should you use error_code
instead of Unknown
here?
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.
Yup (good catch).
const absl::string_view error_msg) { | ||
NSError *error = util::WrapNSError(error_code, error_msg); | ||
/*force_refresh=*/false, [self, rpcFactory, errorHandler](util::StatusOr<Token> result) { | ||
NSError *error = util::WrapNSError(result.status()); |
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 agree that WrapNSError
sounds as if an existing NSError
were wrapped in some object. Since this is a conversion, I can also suggest ToNSError
or AsNSError
in addition to MakeNSError
.
const absl::string_view error_msg) { | ||
NSError *error = util::WrapNSError(error_code, error_msg); | ||
/*force_refresh=*/false, [self, rpcFactory, errorHandler](util::StatusOr<Token> result) { | ||
NSError *error = util::WrapNSError(result.status()); |
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.
Any chance, error_msg
could be populated through? error code might be enough but error message comes handy to provide debug info.
[self.workerDispatchQueue dispatchAsyncAllowingSameQueue:^{ | ||
if (error) { | ||
errorHandler(error); | ||
} else { | ||
GRPCProtoCall *rpc = rpcFactory(); | ||
const Token &token = result.ValueOrDie(); |
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.
Just my 1 cent, check result
rather than error
in the condition is safer to call result.ValueOrDie()
. Then we only assume statusor
's logic rather than also depending on MakeNSError
's.
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.
(See Gil's comment / done.)
const Token& Token::Invalid() { | ||
static const Token kInvalidToken; | ||
return kInvalidToken; | ||
const Token& Token::Unauthenticated() { |
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.
Invalid()
is added for nil
. Now we don't need it. Do we want to add Unauthenticated()
?
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.
Previously, "invalid" meant either: (a) error condition, or (b) nil.
Now, "unauthenticated" means only (b) nil.
So really, you could view this PR as:
- a rename from invalid -> unauthenticated
- remove error conditions from this state (since we can now track that via Status/StatusOr)
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
…ebase#945) * Change CredentialsProvider::TokenListener to use StatusOr Rather than a token plus error code/msg. * Eliminate the concept of an invalid Token Instead, we'll just use StatusOr<Token>. Note that unauthenticated tokens are handled as a special case; they're created via: Token::Unauthenticated() and are otherwise "valid", though attempting to retrieve the raw token on one of these tokens will cause an assertion failure.
Rather than a token plus error code/msg.
This eliminates the concept of an "invalid" token, though an unauthenticated token is still possible.