-
Notifications
You must be signed in to change notification settings - Fork 532
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
Error handling improvements #2067
Conversation
Things that we can consider:
|
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.
Generally fine - a few nits, but my only real concern is whether the default ToString() is now too big.
} | ||
|
||
/// <summary>Creates an API Service exception.</summary> | ||
public GoogleApiException(string serviceName, string message) : this(serviceName, message, null) { } | ||
public GoogleApiException(string serviceName, string message) |
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 the formatting was actually clearer before, to be honest. not overly bothered though.
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 : this(...)
back to the previous line but left the { }
on the next line, so it's clearer that the constructor is not doing anything itself.
: this(serviceName, message, null) | ||
{ } | ||
|
||
/// <summary>Creates an API Service exception.</summary> |
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.
Expand the comment to say "with no message" and maybe explain when the Message property will still have useful information?
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.
Yep, done.
|
||
private string ServiceNameMessage => $"The service {serviceName} has thrown an exception."; | ||
|
||
private string DefaultMessage => |
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.
Unwrap to single line?
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.
And maybe rename to "ServiceHttpAndErrorMessage" or something - "DefaultMessage" sounds like "the message you'd get if you didn't override the Message property" which is the opposite of what it means :)
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.
Unwrapped done and renamed to CombinedMessage.
@@ -26,6 +26,7 @@ namespace Google | |||
public class GoogleApiException : Exception | |||
{ | |||
private readonly string serviceName; | |||
private readonly string message; |
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.
As discussed, this can probably be a boolean just to indicate whether a message was supplied.
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.
And let's give it a leading underscore, which won't be inconsistent if we take other suggestions...
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.
Yep, done
@@ -26,6 +26,7 @@ namespace Google | |||
public class GoogleApiException : Exception | |||
{ | |||
private readonly string serviceName; |
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.
Let's remove this and make ServiceName a read-only autoprop. That remove the unconventional field name and makes the code feel more modern :)
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.
Ah yes, done.
// Parse the error from the current response. | ||
error = await service.DeserializeError(responseMessage).ConfigureAwait(false); | ||
} | ||
catch(GoogleApiException ex) when (!(ex.Error is null)) |
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.
Nit: add space. Oh for later versions of C# with "is not null". (Or just use ex.Error is object
for now.)
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
{ | ||
internal static async Task<RequestError> DeserializeErrorAsync(this HttpResponseMessage response, string name, ISerializer serializer) | ||
{ | ||
if (response?.Content is null) |
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.
Add a comment 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.
Done
} | ||
catch (JsonException ex) | ||
{ | ||
throw new GoogleApiException(name, null, ex) |
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.
Maybe use a named argument for the second one (I assume it's message).
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, yes it's the message
Error = new RequestError { ErrorResponseContent = responseText } | ||
}; | ||
} | ||
if (error == null) |
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 instead of ==, if you've got the energy to change it
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
{ | ||
internal static class HttpResponseMessageExtensions | ||
{ | ||
internal static async Task<RequestError> DeserializeErrorAsync(this HttpResponseMessage response, string name, ISerializer serializer) |
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 it would be good to add a comment to explain the situations in which this throws an exception instead of returning an 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.
Done.
641310f
to
c244dd2
Compare
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.
Jon, all comments adressed on the latest commit.
For trying to reduce the size of the ToString() I'm only printing the raw content if we couldn't parse any of it as an ErrorResponse instead of always.
I'm taking a look at BQ and Storage now and see what direct improvements we can do there.
@@ -26,6 +26,7 @@ namespace Google | |||
public class GoogleApiException : Exception | |||
{ | |||
private readonly string serviceName; |
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.
Ah yes, done.
@@ -26,6 +26,7 @@ namespace Google | |||
public class GoogleApiException : Exception | |||
{ | |||
private readonly string serviceName; | |||
private readonly string message; |
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.
Yep, done
} | ||
|
||
/// <summary>Creates an API Service exception.</summary> | ||
public GoogleApiException(string serviceName, string message) : this(serviceName, message, null) { } | ||
public GoogleApiException(string serviceName, string message) |
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 : this(...)
back to the previous line but left the { }
on the next line, so it's clearer that the constructor is not doing anything itself.
: this(serviceName, message, null) | ||
{ } | ||
|
||
/// <summary>Creates an API Service exception.</summary> |
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.
Yep, done.
|
||
private string ServiceNameMessage => $"The service {serviceName} has thrown an exception."; | ||
|
||
private string DefaultMessage => |
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.
Unwrapped done and renamed to CombinedMessage.
// Parse the error from the current response. | ||
error = await service.DeserializeError(responseMessage).ConfigureAwait(false); | ||
} | ||
catch(GoogleApiException ex) when (!(ex.Error is null)) |
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 = new RequestError { ErrorResponseContent = responseText } | ||
}; | ||
} | ||
if (error == null) |
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
} | ||
catch (JsonException ex) | ||
{ | ||
throw new GoogleApiException(name, null, ex) |
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, yes it's the message
{ | ||
internal static class HttpResponseMessageExtensions | ||
{ | ||
internal static async Task<RequestError> DeserializeErrorAsync(this HttpResponseMessage response, string name, ISerializer serializer) |
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.
{ | ||
internal static async Task<RequestError> DeserializeErrorAsync(this HttpResponseMessage response, string name, ISerializer serializer) | ||
{ | ||
if (response?.Content is null) |
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
Will look tomorrow... |
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.
A few nits, but they could be dealt with separately if you'd prefer
serviceName.ThrowIfNull("serviceName"); | ||
this.serviceName = serviceName; | ||
this.message = message; | ||
ServiceName = serviceName.ThrowIfNull("serviceName"); |
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.
Nit: use nameof.
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
this.serviceName = serviceName; | ||
this.message = message; | ||
ServiceName = serviceName.ThrowIfNull("serviceName"); | ||
_hasMessage = !(message is null); |
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.
message is object
?
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
: $"Raw error details are: {Error.ErrorResponseContent}"; | ||
: (Error.IsOnlyRawContent | ||
? $"No JSON error details were specified.{Environment.NewLine}" + | ||
(Error.ErrorResponseContent.Trim() == "" |
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.
Use string.IsNullOrWhitespace
?
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.
Ha, yes, of course, done.
string exToString = exception.ToString(); | ||
Assert.Contains(ErrorMessageHanlder.ExpectedError, exToString); | ||
Assert.Contains(ErrorMessageHanlder.ErrorContent, exToString); | ||
Assert.Contains(ErrorMessageHanlder.ExpectedError, exception.ToString()); |
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.
Nit: at some point, maybe not in this PR, let's fix ErrorMessageHanlder
to ErrorMessageHandler
.
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 adding a commit for this.
/// </list> | ||
/// Any exception thrown while reading the <paramref name="response"/> <see cref="HttpResponseMessage.Content"/> | ||
/// will be bubbled up. | ||
/// Otherwie this method will returned the deserialized <see cref="RequestError"/>. |
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.
Nit: Otherwie => Otherwise
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, and also returned-> return
In particular improves GoogleApiExcetion error messages and string representation by including more information. In cases when then response does not contain a valid JSON body, the error message has changed entirely to be more explicit. This affects normal, batch, and media upload and download requests. Closes googleapis#2063. Towards googleapis/google-cloud-dotnet#8206 BREAKING CHANGE: If code was depending on exact exception message or string representation content.
c244dd2
to
449d1c1
Compare
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.
All latest nits addressed.
I've squashed all the commits into one, and added a new commit fixing the test class name typo.
serviceName.ThrowIfNull("serviceName"); | ||
this.serviceName = serviceName; | ||
this.message = message; | ||
ServiceName = serviceName.ThrowIfNull("serviceName"); |
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
this.serviceName = serviceName; | ||
this.message = message; | ||
ServiceName = serviceName.ThrowIfNull("serviceName"); | ||
_hasMessage = !(message is null); |
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
: $"Raw error details are: {Error.ErrorResponseContent}"; | ||
: (Error.IsOnlyRawContent | ||
? $"No JSON error details were specified.{Environment.NewLine}" + | ||
(Error.ErrorResponseContent.Trim() == "" |
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.
Ha, yes, of course, done.
string exToString = exception.ToString(); | ||
Assert.Contains(ErrorMessageHanlder.ExpectedError, exToString); | ||
Assert.Contains(ErrorMessageHanlder.ErrorContent, exToString); | ||
Assert.Contains(ErrorMessageHanlder.ExpectedError, exception.ToString()); |
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 adding a commit for this.
/// </list> | ||
/// Any exception thrown while reading the <paramref name="response"/> <see cref="HttpResponseMessage.Content"/> | ||
/// will be bubbled up. | ||
/// Otherwie this method will returned the deserialized <see cref="RequestError"/>. |
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, and also returned-> return
Features - [googleapis#2067] BREAKING. Improve error handling for failed requests. BREAKING CHANGE: If calling code depended on exact GoogleApiException message or string representation. - [googleapis#2086] Makes `LocalServerCodeReceiver` partially extendable.
As usual one commit at a time.