Skip to content

Commit

Permalink
fix: parse api errors
Browse files Browse the repository at this point in the history
  • Loading branch information
ErnoW committed Sep 21, 2022
1 parent 67a9984 commit d79800a
Show file tree
Hide file tree
Showing 3 changed files with 207 additions and 7 deletions.
10 changes: 10 additions & 0 deletions .changeset/nine-donuts-doubt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
'@moralisweb3/core': patch
'@moralisweb3/auth': patch
'@moralisweb3/evm-api': patch
'moralis': patch
'@moralisweb3/sol-api': patch
'@moralisweb3/streams': patch
---

Fix parsing of API error messages, now the MoralisError will show the `message` that is returned from the api.
48 changes: 41 additions & 7 deletions packages/core/src/controllers/RequestController.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import axios, { AxiosError, AxiosRequestConfig } from 'axios';
import { AxiosError, AxiosRequestConfig } from 'axios';
import { CoreErrorCode, MoralisCoreError } from '../Error';
import { AxiosRetry, AxiosRetryConfig } from './AxiosRetry';
import { isTest } from '../environment/isTest';
Expand All @@ -10,6 +10,38 @@ export interface RequestOptions {
headers?: { [name: string]: string };
}

type AxiosApiError = AxiosError<{ message?: string | string[] }> & {
response: NonNullable<AxiosError<{ message: string | string[] }>['response']>;
};

const isAxiosApiError = (error: unknown): error is AxiosApiError => {
// Check if the error is an axios error
if (!(error instanceof AxiosError)) {
return false;
}

// Check if the error is a result of a 400 or 500 response
if (error.code !== AxiosError.ERR_BAD_REQUEST && error.code !== AxiosError.ERR_BAD_RESPONSE) {
return false;
}

return true;
};

const getApiMessageFromError = (error: AxiosApiError) => {
const { message } = error.response.data;

if (Array.isArray(message)) {
return message.join(', ');
}

if(typeof message === 'string'){
return message;
}

return 'Unknown error (no error info returned from API)';
};

/**
* A controller responsible to handle all requests in Moralis,
* compatible with browser, nodejJs and react-native
Expand Down Expand Up @@ -73,21 +105,23 @@ export class RequestController {
}

private makeError(error: unknown): MoralisCoreError {
if (axios.isAxiosError(error)) {
const axiosError = error as AxiosError;
if (isAxiosApiError(error)) {
const { status } = error.response;
const apiMessage = getApiMessageFromError(error);

return new MoralisCoreError({
code: CoreErrorCode.REQUEST_ERROR,
message: `Request failed with status ${axiosError.response?.status}: ${axiosError.message}`,
message: `Request failed with status ${status}: ${apiMessage}`,
cause: error,
details: {
status: axiosError.response?.status,
request: axiosError.request,
response: axiosError.response,
status,
response: error.response,
},
});
}

const err = error instanceof Error ? error : new Error(`${error}`);

return new MoralisCoreError({
code: CoreErrorCode.REQUEST_ERROR,
message: `Request failed: ${err.message}`,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import { MoralisCore, RequestController } from '@moralisweb3/core';
import { setupServer, SetupServerApi } from 'msw/node';
import { rest } from 'msw';

const getJsonMock = rest.get(`http://example.com/getJson`, (req, res, ctx) => {
return res(
ctx.status(200),
ctx.json({
iAm: 'Batman',
}),
);
});

const getTextMock = rest.get(`http://example.com/getText`, (req, res, ctx) => {
return res(ctx.status(200), ctx.text('I am Batman'));
});

const get400ErrorMock = rest.get(`http://example.com/get400Error`, (req, res, ctx) => {
return res(
ctx.status(400),
ctx.json({
message: 'I am not Batman',
}),
);
});

const get404ErrorMock = rest.get(`http://example.com/get404Error`, (req, res, ctx) => {
return res(
ctx.status(404),
ctx.json({
message: 'I am not Batman',
}),
);
});

const get500ErrorMock = rest.get(`http://example.com/get500Error`, (req, res, ctx) => {
return res(
ctx.status(500),
ctx.json({
message: 'I am not Batman',
}),
);
});

const get503ErrorMock = rest.get(`http://example.com/get503Error`, (req, res, ctx) => {
return res(
ctx.status(503),
ctx.json({
message: 'I am not Batman',
}),
);
});

const get400ErrorMultiMessageMock = rest.get(`http://example.com/get400MultiMessageError`, (req, res, ctx) => {
return res(
ctx.status(400),
ctx.json({
message: ['I am not Batman', 'I am not superman'],
}),
);
});

const get400ErrorEmptyJsonMock = rest.get(`http://example.com/get400ErrorEmptyJson`, (req, res, ctx) => {
return res(ctx.status(400), ctx.json({}));
});

const get400ErrorEmptyMock = rest.get(`http://example.com/get400ErrorEmpty`, (req, res, ctx) => {
return res(ctx.status(400));
});

const handlers = [
getJsonMock,
getTextMock,
get400ErrorMock,
get404ErrorMock,
get500ErrorMock,
get503ErrorMock,
get400ErrorMultiMessageMock,
get400ErrorEmptyJsonMock,
get400ErrorEmptyMock,
];

describe('RequestControllerGet', () => {
let requestController: RequestController;
let mockServer: SetupServerApi;

beforeAll(() => {
const core = MoralisCore.create();
requestController = RequestController.create(core);

mockServer = setupServer(...handlers);
mockServer.listen({
onUnhandledRequest: 'warn',
});
});

afterAll(() => {
mockServer.close();
});

beforeEach(() => {});

it('should get a valid Json response', async () => {
const result = await requestController.get('http://example.com/getJson');

expect(result).toStrictEqual({ iAm: 'Batman' });
});

it('should get a valid text response', async () => {
const result = await requestController.get('http://example.com/getText');

expect(result).toStrictEqual('I am Batman');
});

it('should throw an error on 400 response', async () => {
expect(requestController.get('http://example.com/get400Error')).rejects.toThrowError(
'[C0006] Request failed with status 400: I am not Batman',
);
});

it('should throw an error on 404 response', async () => {
expect(requestController.get('http://example.com/get404Error')).rejects.toThrowError(
'[C0006] Request failed with status 404: I am not Batman',
);
});

it('should throw an error on 500 response', async () => {
expect(requestController.get('http://example.com/get500Error')).rejects.toThrowError(
'[C0006] Request failed with status 500: I am not Batman',
);
});

it('should throw an error on 503 response', async () => {
expect(requestController.get('http://example.com/get503Error')).rejects.toThrowError(
'[C0006] Request failed with status 503: I am not Batman',
);
});

it('should handle multiple messages in an error response', async () => {
expect(requestController.get('http://example.com/get400MultiMessageError')).rejects.toThrowError(
'[C0006] Request failed with status 400: I am not Batman, I am not superman',
);
});

it('should handle empty error response', async () => {
expect(requestController.get('http://example.com/get400ErrorEmptyJson')).rejects.toThrowError(
'[C0006] Request failed with status 400: Unknown error (no error info returned from API)',
);
});

it('should handle empty error response', async () => {
expect(requestController.get('http://example.com/get400ErrorEmpty')).rejects.toThrowError(
'[C0006] Request failed with status 400: Unknown error (no error info returned from API)',
);
});
});

0 comments on commit d79800a

Please sign in to comment.