Skip to content
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

feathers rest client (fetch) doesn't convert errors when response body is not JSON #1940

Closed
cdelaorden opened this issue Apr 30, 2020 · 9 comments
Labels

Comments

@cdelaorden
Copy link

@cdelaorden cdelaorden commented Apr 30, 2020

Hi

The @feathers/client module which includes itself the @feathers/rest package is not catching correctly errors from the backend that don't return any JSON.

The problem is in the following line:

return response.json().then(error => {

The "checkStatus" function is checking if the fetch ok property is set to true, and assuming that if false the response will be valid JSON. When it isn't, and there's a myriad of possibilities that this may happen, it fails to convert and thus the proper NotFound error is never thrown, and you get instead an ugly JSON parse error.

All API calls to a service will fail silently with a JSON parsing error, so there's no way the client can access at least the status code. It's a really simple fix, do you consider accepting a PR for this?

@daffl
Copy link
Member

@daffl daffl commented Apr 30, 2020

What would the fix look like? Catch the JSON parsing error and throw a Feathers error based on the status code?

@cdelaorden
Copy link
Author

@cdelaorden cdelaorden commented Apr 30, 2020

I guess so, at least having the proper error constructor and specially the code (status) from fetch would be useful to handle in the UI (the resource does not exist, in case of a 404).

To be honest I don't really understand why the express error handler in the backend is not sending it, in my backend service (custom service) I'm catching the error and rethrowing as NotFoundError (albeit without data).

@daffl
Copy link
Member

@daffl daffl commented Apr 30, 2020

@cdelaorden
Copy link
Author

@cdelaorden cdelaorden commented Apr 30, 2020

Yes, I now I can fix it in the backend (I just did, I was returning "undefined" from a db call), but don't you think the client library should be a little more resilient? :) A JSON.parse error is quite a "bad" error to get from an API call, normally in 404 errors there's no need to have a JSON response. The error code has all the needed semantics.

In any case you can close this issue if you prefer to do so, at least someone having the same issue will find something to look at and I'll save them the journey through all packages included in @feathers/client. 😄

Thanks for your prompt response!

@daffl
Copy link
Member

@daffl daffl commented Apr 30, 2020

This should definitely still be fixed since it - like you said - still can happen in other instances (e.g. if the load balancer returns a 502 because the backend is down).

@cdelaorden
Copy link
Author

@cdelaorden cdelaorden commented Apr 30, 2020

I think the proper fix which I can PR for like tomorrow would be to check the content type headers sent in the response instead of assuming it is indeed JSON.
See example from MDN:
https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#Headers

fetch(myRequest)
  .then(response => {
     const contentType = response.headers.get('content-type');
     if (!contentType || !contentType.includes('application/json')) {
       throw new TypeError("Oops, we haven't got JSON!");
     }
     return response.json();
  })
  .then(data => {
      /* process your data further */
  })
  .catch(error => console.error(error));
@daffl
Copy link
Member

@daffl daffl commented Apr 30, 2020

I believe something like

  checkStatus (response) {
    if (response.ok) {
      return response;
    }

    return response.json().catch(error => {
      const toJsonError = new Error('JSON parsing error');

      toJsonError.code = response.status;

      return toJsonError;
    }).then(error => {
      error.response = response;
      throw error;
    });
  }

Might do it already as well.

@cdelaorden
Copy link
Author

@cdelaorden cdelaorden commented Apr 30, 2020

It would not be transformed to a Feathers error in that case, if you don't throw?

Since you already mapped most status code to custom Errors it would be great to keep that (if it's possible). Otherwise for a quick cheap fix that would be enough as well. I just prefer checking instead of try/catching. :) But I'm not sure if the other transports behave the same way.

@daffl
Copy link
Member

@daffl daffl commented Apr 30, 2020

Well, with websockets this isn't a problem since you'll always get an object. An improvement with the right error type would be

import * as errors from '@feathersjs/errors';

// ...

  checkStatus (response) {
    if (response.ok) {
      return response;
    }

    return response.json().catch(error => {
      const ErrorClass = errors[response.status] || Error;
      
      return new ErrorClass('JSON parsing error');
    }).then(error => {
      error.response = response;
      throw error;
    });
  }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

2 participants
You can’t perform that action at this time.