-
-
Notifications
You must be signed in to change notification settings - Fork 651
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
Clean up API error-distinguishing logic #4896
Conversation
Plus a TODO comment for something the new jsdoc says that isn't yet true. We'll fix that later in this series.
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 this cleanup, @gnprice! See a few tiny comments below; otherwise, please merge at will.
src/api/apiFetch.js
Outdated
Sentry.addBreadcrumb({ | ||
category: 'api', | ||
level: 'info', | ||
data: { route, params, httpStatus: response.status, json }, | ||
data: { route, params, httpStatus, data }, |
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.
api errors [nfc]: Rely on error object in logging code.
nit: Breadcrumbs sent to Sentry will look different after this commit, so I think that makes it non-NFC.
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.
Mmm, indeed, good catch.
I think actually the name should ideally say something like "result" or "response" -- neither "json" nor "data" is real clear that this is the data the server sent back in the body, rather than say the data that we sent in the request. I'll pick a name like that.
@@ -37,12 +37,13 @@ export const apiFetch = async ( | |||
params: $Diff<$Exact<RequestOptions>, {| headers: mixed |}>, | |||
) => fetch(new URL(`/${apiVersion}/${route}`, auth.realm).toString(), getFetchParams(auth, params)); | |||
|
|||
/** (Caller beware! Return type is the magic `empty`.) */ |
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.
fetch
having a type ofany
in the libdef RN provides
Yeah; it'll be nice to get facebook/react-native@6651b7c59, looks like in RN v0.65.
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 indeed, neat!
src/api/apiErrors.js
Outdated
@@ -11,7 +21,7 @@ import * as logging from '../utils/logging'; | |||
* See docs: https://zulip.com/api/rest-error-handling | |||
*/ | |||
// TODO we currently raise these in more situations; fix that. | |||
export class ApiError extends Error { | |||
export class ApiError extends RequestError { | |||
code: ApiErrorCode; |
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.
Could also have a commit that makes ApiError
and ServerError
read-only, right?
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.
Sure, yeah.
I guess when I did that on the base class:
export class RequestError extends Error {
+httpStatus: number | void;
+data: mixed;
it was just because that's necessary in order for the subclasses to give more-specific types for those properties. But the same thing would be appropriate on the subclasses' properties too.
For one of these cases, there's a nice new thing the data can be instead. For the other, we could modify the test case to have some status code that's neither 5xx nor 4xx. But later in this series we're going to eliminate all the non-4xx cases of ApiError anyway, so we don't bother.
We got an error, but of that whole error what this number specifically is is the HTTP status code.
This sets up the next refactoring; we're going to use a try/catch at this callsite.
The high-order bit about this particular property, relative to the others in this blob, is that it represents the data the server sent back to us in reply to the request. So give it a name that says that. A variety of names are possible, but "response" is the term we use in the types ApiResponse, ApiResponseSuccess, and so on, which correspond exactly to the data we expect to see here. So use that.
This matches how the data will flow when we use a try/catch, coming up next.
And use it for the example that motivated adding it.
This is NFC in the case where we reach `makeErrorFromApi` and throw an exception it returns. If there's an exception somewhere else -- which would be a bug in this code -- then this change means that we now reach the logging code that got moved inside this new `catch` block. That seems all to the good. And in fact, one of our tests demonstrates that case! It does so by artificially making `fetch` itself fail. That does seem like a bug (an existing one) in this code; we'll be fixing it later in this series.
This is just the definition of the `ok` property, by spec: https://fetch.spec.whatwg.org/#dom-response-ok
This is the type Flow was already inferring here, as a result of, ultimately, `fetch` having a type of `any` in the libdef RN provides. Make it a little more explicit that that's happening.
This gives us a better shot at seeing all in one place how we interpret the server's response and what the possible cases are.
This was getting lumped in at the bottom with the case of a malformed error blob.
We now only throw an ApiError in the first place when the code is 4xx.
Saying `instanceof ApiError` seems like a cleaner interface for the API code to present, now that it means the same thing.
This can indeed have additional properties.
We've had this as an exact object type, but the real type is inexact. When spreading it, though, we always just want to incorporate the properties it specifically has. The way to spell that is `$Exact<ApiResponseSuccess>`. This has no effect at the moment because the type is exact in the first place, but it'll let us change it to inexact and keep getting the intended results.
This may indeed have additional properties.
Thanks for the review! Merged, with those changes. |
This is what I ended up with after my comment #4754 (review) :
It was kind of a tangle, and now I hope it's clearer. In particular, I think it's now a lot more straightforward to think of the set of possible errors on an API request as completely partitioned into meaningful subcategories, and to write conditionals on those.
The
isClientError
predicate goes away in this branch, replaced by a directinstanceof
check at its former callsite. I think the same thing can be done withisServerError
andisNetworkRequestFailedError
in #4754; they can becomeinstanceof
checks referring to the new error classes added here.The last few commits in this branch are some cleanups to the API result types, which aren't needed for anything else in this branch. I wrote those on the way toward trying to make
interpretApiResponse
return anApiSuccessResponse
instead ofmixed
, but the change to actually do that is still a draft.This inherits P1 priority from #4754 .