Skip to content

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

Merged
merged 4 commits into from
Mar 21, 2018

Conversation

rsgowman
Copy link
Member

Rather than a token plus error code/msg.

This eliminates the concept of an "invalid" token, though an unauthenticated token is still possible.

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());
Copy link
Member Author

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.

@rsgowman
Copy link
Member Author

No manual testing yet (will start now), but I wanted to get this out early to ensure this is the right approach.

@rsgowman rsgowman requested review from zxu123 and var-const March 20, 2018 15:01
Copy link
Contributor

@wilhuff wilhuff left a 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());
Copy link
Contributor

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?

Copy link
Member Author

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.

Copy link
Contributor

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.

Copy link
Contributor

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.

Copy link
Member Author

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.)

Copy link
Member Author

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) {
Copy link
Contributor

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()));
}
// ...

Copy link
Member Author

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()) {
Copy link
Contributor

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.

Copy link
Member Author

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});
Copy link
Contributor

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.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Copy link
Contributor

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?

Copy link
Contributor

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.

Copy link
Contributor

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. */
Copy link
Contributor

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.

Copy link
Member Author

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();
Copy link
Contributor

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().

Copy link
Member Author

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});
Copy link
Contributor

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,
Copy link
Contributor

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?

Copy link
Member Author

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());
Copy link
Contributor

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());
Copy link
Contributor

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();
Copy link
Contributor

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.

Copy link
Member Author

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() {
Copy link
Contributor

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()?

Copy link
Member Author

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:

  1. a rename from invalid -> unauthenticated
  2. remove error conditions from this state (since we can now track that via Status/StatusOr)

Copy link
Contributor

@wilhuff wilhuff left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@rsgowman rsgowman merged commit 308acc0 into master Mar 21, 2018
@rsgowman rsgowman deleted the rsgowman/token_err_handling2 branch March 21, 2018 15:04
minafarid pushed a commit to minafarid/firebase-ios-sdk that referenced this pull request Jun 6, 2018
…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.
@firebase firebase locked and limited conversation to collaborators Nov 6, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants