Skip to content

Commit

Permalink
Add extra proprty to check for instanceof ValidationError across vers…
Browse files Browse the repository at this point in the history
…ions
  • Loading branch information
knor-el-snor committed Jan 20, 2020
1 parent 87fe5b9 commit 18ef3c0
Show file tree
Hide file tree
Showing 6 changed files with 69 additions and 29 deletions.
22 changes: 18 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,20 +131,34 @@ throw new ApiError(400, errors.BAD_REQUEST);

## Error parsing

### isApiError(object)
### isApiError(apiError)

Will return boolean indicating whether object has all required properties to be an `ApiError`.
Will return boolean indicating whether error is instance of `ApiError`.

```javascript
// Will return true
isApiError({ status: 200, code: 'MY_CODE', title: 'MY_ERROR', detail: {} })
isApiError(new BadRequestError())

// Will return false
isApiError({ status: 200, code: 'MY_CODE' })
isApiError(new Error('Something'))
```

> Will automatically cast to ApiError if succeeds and using Typescript
### isJsonApiError(object)

Will return boolean indicating whether object has all required properties to be a parsed `ApiError`.

```javascript
// Will return true
isJsonApiError({ status: 200, code: 'MY_CODE', title: 'MY_ERROR', detail: {} })

// Will return false
isJsonApiError({ status: 200, code: 'MY_CODE' })
```

> Will automatically cast to ParsedError if succeeds and using Typescript
### parseErrors(error, i18nOptions (optional))

Parse any data into an error object with all properties needed for jsonade parser. Also parses [`express-validation`](https://github.com/andrewkeig/express-validation) and [`celebrate`](https://github.com/arb/celebrate) errors.
Expand Down
20 changes: 10 additions & 10 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,14 @@
"http-status": "~1.4.2",
"i18n": "~0.8.4",
"joi": "~14.3.1",
"safe-json-stringify": "1.2.0",
"safe-json-stringify": "~1.2.0",
"tree-house-translations": "~1.2.2",
"uuid": "~3.3.3"
"uuid": "~3.4.0"
},
"devDependencies": {
"@types/http-status": "~1.1.2",
"@types/i18n": "~0.8.6",
"@types/jest": "~24.0.25",
"@types/jest": "24.9.0",
"@types/uuid": "~3.4.6",
"coveralls": "~3.0.9",
"jest": "~24.9.0",
Expand Down
2 changes: 2 additions & 0 deletions src/lib/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ export class ApiError extends Error {
i18n?: string;
id?: string;
detail?: any;
isApiError: boolean;

constructor(status: number, error: ErrorType, args: { message?: string, detail?: any, stack?: any } = {}) {
const { message, detail, stack } = args;
super(message || error.message);
this.name = 'ApiError';
this.isApiError = true;
this.id = uuid.v1();
this.code = error.code;
this.i18n = error.i18n;
Expand Down
9 changes: 6 additions & 3 deletions src/lib/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,15 @@ import { getTranslator } from './translator';
* Check if object has all required properties to be an ApiError
* @param {Object} obj
*/
export const isApiError = (obj: ParsedError | any = {}): obj is ParsedError =>
export const isJsonApiError = (obj: ParsedError | any = {}): obj is ParsedError =>
_.has(obj, 'status')
&& _.has(obj, 'code')
&& _.has(obj, 'title')
&& _.has(obj, 'detail');

export const isApiError = (err: ApiError | any = {}): err is ApiError =>
(err || {}).hasOwnProperty('isApiError') && err.isApiError === true;

/**
* Parse errors
* @param {String} error
Expand Down Expand Up @@ -54,7 +57,7 @@ export function parseErrors(error: any = {}, translatorOptions?: TranslatorOptio
}

// Own thrown ApiErrors
if (error instanceof ApiError) {
if (isApiError(error)) {
let translatedMessage = error.message;

if (translatorOptions) {
Expand Down Expand Up @@ -94,7 +97,7 @@ export function parseErrors(error: any = {}, translatorOptions?: TranslatorOptio
export function parseJsonErrors(response: any): ApiError[] {
if ((response || {}).hasOwnProperty('errors') && Array.isArray(response.errors)) {
return response.errors.reduce((acc: ApiError[], error: any) => {
if (isApiError(error)) {
if (isJsonApiError(error)) {
const { status, code, title, detail, meta = {} } = error;
return [...acc, new ApiError(status, { code, message: title }, { detail, stack: (meta || {}).stack })];
}
Expand Down
39 changes: 30 additions & 9 deletions tests/parser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import * as httpStatus from 'http-status';
import { ValidationError } from 'express-validation';

import * as translator from '../src/lib/translator';
import { ApiError, errors, parseErrors, parseJsonErrors, isApiError, InternalServerError } from '../src';
import {
ApiError, errors, parseErrors, parseJsonErrors, isJsonApiError, InternalServerError, BadRequestError, isApiError, UnauthorizedError,
ForbiddenError,
} from '../src';
import { errorDefaults } from '../src/config/defaults.config';

describe('errorParser', () => {
Expand Down Expand Up @@ -111,7 +114,7 @@ describe('errorParser', () => {
it('Should succesfully parse default ApiError with default message when language is not available', () => {
expect.assertions(1);
try {
throw new ApiError(httpStatus.BAD_REQUEST, errors.INVALID_INPUT);
throw new BadRequestError(errors.INVALID_INPUT);
} catch (err) {
const parsedError = parseErrors(err, { path: '', language: 'du' });
expect(parsedError).toMatchObject({
Expand Down Expand Up @@ -182,7 +185,7 @@ describe('errorParser', () => {

it('Should parse celebrate joi error without ValidationError', () => {
const celebrateError = {
joi: {},
joi: null,
};

const parsedError = parseErrors(celebrateError);
Expand All @@ -197,9 +200,9 @@ describe('errorParser', () => {
});
});

describe('isApiError', () => {
describe('isJsonApiError', () => {
it('Should return true when all properties are available', () => {
const output = isApiError({
const output = isJsonApiError({
status: httpStatus.BAD_REQUEST,
code: 'RANDOM_CODE',
title: 'My title',
Expand All @@ -212,11 +215,29 @@ describe('errorParser', () => {
});

it('Should return false when some properties are missing', () => {
expect(isJsonApiError()).toEqual(false);
expect(isJsonApiError({})).toEqual(false);
expect(isJsonApiError({ status: httpStatus.NOT_ACCEPTABLE, code: '120', title: 'any Title' })).toEqual(false);
expect(isJsonApiError({ status: httpStatus.INTERNAL_SERVER_ERROR, code: '120', detail: 'any Title' })).toEqual(false);
expect(isJsonApiError({ code: '120', detail: 'any Title' })).toEqual(false);
expect(isJsonApiError({ status: httpStatus.BAD_REQUEST, detail: 'any Title' })).toEqual(false);
});
});

describe('isApiError', () => {
it('Should return true when all properties are available', () => {
expect(isApiError(new BadRequestError())).toEqual(true);
expect(isApiError(new ApiError(401, errors.AUTHENTICATION_FAILED))).toEqual(true);
expect(isApiError(new InternalServerError())).toEqual(true);
expect(isApiError(new UnauthorizedError())).toEqual(true);
expect(isApiError(new ForbiddenError())).toEqual(true);
});

it('Should return false when some properties are missing', () => {
expect(isApiError()).toEqual(false);
expect(isApiError({})).toEqual(false);
expect(isApiError({ status: httpStatus.NOT_ACCEPTABLE, code: '120', title: 'any Title' })).toEqual(false);
expect(isApiError({ status: httpStatus.INTERNAL_SERVER_ERROR, code: '120', detail: 'any Title' })).toEqual(false);
expect(isApiError({ code: '120', detail: 'any Title' })).toEqual(false);
expect(isApiError({ status: httpStatus.BAD_REQUEST, detail: 'any Title' })).toEqual(false);
expect(isApiError(null)).toEqual(false);
expect(isApiError(new Error('SOMETHING WRONG'))).toEqual(false);
});
});

Expand Down

0 comments on commit 18ef3c0

Please sign in to comment.