Skip to content

Commit

Permalink
Improvements + solved PR comments
Browse files Browse the repository at this point in the history
  • Loading branch information
horstenwillem committed Apr 24, 2018
1 parent 76eb612 commit 5c6fb51
Show file tree
Hide file tree
Showing 8 changed files with 75 additions and 28 deletions.
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export { parseErrors } from './lib/errorParser';
export { errorConfig as errors } from './lib/errorConfig';
export { importTranslations, Options } from './lib/importTranslations';
export { importTranslations, TranslationOptions } from './lib/importTranslations';
export * from './lib/errors';
16 changes: 8 additions & 8 deletions src/lib/errorConfig.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@

export const errorConfig = {
INTERNAL_ERROR: { code: 'INTERNAL_ERROR', message: 'An unkown error occurred' },
INVALID_INPUT: { code: 'INVALID_INPUT', message: 'Invalid input provided' },
AUTHENTICATION_FAILED: { code: 'AUTHENTICATION_FAILED', message: 'Authentication failed' },
BAD_REQUEST: { code: 'BAD_REQUEST', message: 'Bad request' },
MISSING_HEADERS: { code: 'MISSING_HEADERS', message: 'Missing headers' },
UNAUTHORIZED: { code: 'UNAUTHORIZED', message: 'Unauthorized' },
FORBIDDEN: { code: 'FORBIDDEN', message: 'No access' },
RESOURCE_NOT_FOUND: { code: 'RESOURCE_NOT_FOUND', message: 'Resource not found' },
INTERNAL_ERROR: { code: 'INTERNAL_ERROR', i18n: 'internal_error', message: 'An unkown error occurred' },
INVALID_INPUT: { code: 'INVALID_INPUT', i18n: 'invalid_input', message: 'Invalid input provided' },
AUTHENTICATION_FAILED: { code: 'AUTHENTICATION_FAILED', i18n: 'authentication_failed', message: 'Authentication failed' },
BAD_REQUEST: { code: 'BAD_REQUEST', i18n: 'bad_request', message: 'Bad request' },
MISSING_HEADERS: { code: 'MISSING_HEADERS', i18n: 'missing_headers', message: 'Missing headers' },
UNAUTHORIZED: { code: 'UNAUTHORIZED', i18n: 'unauthorized', message: 'Unauthorized' },
FORBIDDEN: { code: 'FORBIDDEN', i18n: 'forbidden', message: 'No access' },
RESOURCE_NOT_FOUND: { code: 'RESOURCE_NOT_FOUND', i18n: 'resource_not_found', message: 'Resource not found' },
};
14 changes: 8 additions & 6 deletions src/lib/errorParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { errorConfig as errors } from './errorConfig';
import { errorDefaults } from './constants';
import i18n from './i18nConfig';

export function parseErrors(error: any, language: string) {
export function parseErrors(error: any, language?: string) {
const metaData: any = {};
let parsedError = new ApiError(errorDefaults.DEFAULT_HTTP_CODE, errorDefaults.DEFAULT_ERROR); // Default error

Expand All @@ -28,12 +28,14 @@ export function parseErrors(error: any, language: string) {
if (error instanceof ApiError) {
i18n.setLocale(language);

let correctMessage = i18n.__(error.code);
// if translation is not found, return default English message
if (correctMessage === error.code) {
correctMessage = error.message;
let translatedMessage = i18n.__(error.i18n);
// if the translatedMessage equals the error code or is undefined
// no translation is found
// fallback to default error message from ErrorConfig
if (translatedMessage === error.i18n || translatedMessage === undefined) {
translatedMessage = error.message;
}
parsedError = Object.assign({}, error, { message: correctMessage });
parsedError = Object.assign({}, error, { message: translatedMessage });
}

// Return object easy to use for serialisation
Expand Down
3 changes: 3 additions & 0 deletions src/lib/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { errorConfig as errors } from './errorConfig';
export class ApiError extends Error {
code: string;
status: number;
i18n?: string;
id?: string;
detail?: string;

Expand All @@ -14,6 +15,7 @@ export class ApiError extends Error {
this.name = 'ApiError';
this.id = uuid.v1();
this.code = error.code;
this.i18n = error.i18n;
this.status = status;
this.detail = detail;
if (stack) this.stack = stack;
Expand Down Expand Up @@ -73,6 +75,7 @@ export class AuthenticationError extends ApiError {
export interface ErrorType {
code: string;
message: string;
i18n?: string;
}

export interface ErrorArgs {
Expand Down
2 changes: 2 additions & 0 deletions src/lib/i18nConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import * as i18n from 'i18n';

i18n.configure({
directory: __dirname + '/locales',
defaultLocale: 'en',
updateFiles: false,
});

export default i18n;
12 changes: 4 additions & 8 deletions src/lib/importTranslations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,12 @@ const defaultOptions = {
destination: './lib/locales',
};

export function importTranslations(token: string, options?: Options) {
try {
const allOptions = Object.assign({}, defaultOptions, options);
return icappsTranslation.import(url, token, allOptions);
} catch (ex) {
throw ex;
}
export function importTranslations(token: string, options?: TranslationOptions) {
const allOptions = Object.assign({}, defaultOptions, options);
return icappsTranslation.import(url, token, allOptions);
}

export interface Options {
export interface TranslationOptions {
destination?: string;
clean?: boolean;
verbose?: boolean;
Expand Down
50 changes: 47 additions & 3 deletions tests/errorParser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,23 @@ import * as httpStatus from 'http-status';
import { ValidationError } from 'express-validation';
import { parseErrors, ApiError, errors } from '../src';
import { errorDefaults } from '../src/lib/constants';
import * as i18n from 'i18n';

describe('errorParser', () => {
const defaultError = new ApiError(errorDefaults.DEFAULT_HTTP_CODE, errorDefaults.DEFAULT_ERROR);
let i18nMock;

beforeEach(() => {
i18nMock = jest.spyOn(i18n, '__');
});

afterEach(() => {
jest.resetAllMocks();
});

describe('Unkown errors', () => {
it('Should return the default error when unknown object is passed', () => {
const parsedError = parseErrors({ myProp: 'iDunno' }, 'en');
const parsedError = parseErrors({ myProp: 'iDunno' });
expect(parsedError).toMatchObject({
id: expect.any(String),
status: defaultError.status,
Expand All @@ -27,7 +37,7 @@ describe('errorParser', () => {
const error = new Error('Some error...');
Object.assign(error, { stack: 'myStack', detail: 'some details', schema: 'public' });

const parsedError = parseErrors(error, 'en');
const parsedError = parseErrors(error);
expect(parsedError).toMatchObject({
id: expect.any(String),
status: defaultError.status,
Expand All @@ -51,7 +61,7 @@ describe('errorParser', () => {
};

const expressValidationError = new ValidationError([{ name: 'myField' }], options);
const parsedError = parseErrors(expressValidationError, 'en');
const parsedError = parseErrors(expressValidationError);
expect(parsedError).toMatchObject({
id: expect.any(String),
status: httpStatus.BAD_REQUEST,
Expand All @@ -63,10 +73,27 @@ describe('errorParser', () => {

describe('Predefined Api errors', () => {
it('Should succesfully parse default ApiError', () => {
const errorTranslation = 'English translation';
i18nMock.mockImplementation(() => errorTranslation);

try {
throw new ApiError(httpStatus.BAD_REQUEST, errors.INVALID_INPUT);
} catch (err) {
const parsedError = parseErrors(err, 'en');
expect(parsedError).toMatchObject({
id: expect.any(String),
status: httpStatus.BAD_REQUEST,
code: errors.INVALID_INPUT.code,
title: errorTranslation,
detail: errorTranslation,
});
}
});
it('Should succesfully parse default ApiError with default message when language is not available', () => {
try {
throw new ApiError(httpStatus.BAD_REQUEST, errors.INVALID_INPUT);
} catch (err) {
const parsedError = parseErrors(err, 'du');
expect(parsedError).toMatchObject({
id: expect.any(String),
status: httpStatus.BAD_REQUEST,
Expand All @@ -76,6 +103,23 @@ describe('errorParser', () => {
});
}
});
it('Should succesfully parse default ApiError for Dutch translation', () => {
const errorTranslation = 'Nederlands vertaling';
i18nMock.mockImplementation(() => errorTranslation);

try {
throw new ApiError(httpStatus.BAD_REQUEST, errors.INVALID_INPUT);
} catch (err) {
const parsedError = parseErrors(err, 'nl');
expect(parsedError).toMatchObject({
id: expect.any(String),
status: httpStatus.BAD_REQUEST,
code: errors.INVALID_INPUT.code,
title: errorTranslation,
detail: errorTranslation,
});
}
});

// TODO: Custom cases
});
Expand Down
4 changes: 2 additions & 2 deletions tests/translations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ import { importTranslations } from '../src';
import * as icappsTranslation from 'icapps-translations';

describe('importTranslations', () => {
const mock = jest.spyOn(icappsTranslation, 'import').mockImplementation(() => { });
const icappsTranslationMock = jest.spyOn(icappsTranslation, 'import').mockImplementation(() => { });
afterAll(() => {
jest.clearAllMocks();
});

it('should import the translations', async () => {
await importTranslations('randomToken');
expect(mock).toHaveBeenCalledTimes(1);
expect(icappsTranslationMock).toHaveBeenCalledTimes(1);
});
});

0 comments on commit 5c6fb51

Please sign in to comment.