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

1.0.0 #1

Merged
merged 7 commits into from
Dec 16, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 13 additions & 8 deletions lib/ExceptionTransformer.d.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import {
CustomTransformers,
Exception,
ExceptionMap
} from "./ExceptionTransformerModel";
import { CustomTransformers, Exception, ExceptionMap, Options, OnUnexpectedException } from "./ExceptionTransformerModel";
interface ExceptionTransformerConfig {
customTransformers?: CustomTransformers;
onUnexpectedException?: OnUnexpectedException;
}
declare class ExceptionTransformer {
private readonly customTransformers?;
constructor(customTransformers?: CustomTransformers);
generateExceptionMap(exception: Exception): ExceptionMap;
private readonly customTransformers?;
private genericErrorMessage;
private readonly onUnexpectedException?;
constructor(genericErrorMessage: string, config?: ExceptionTransformerConfig);
changeGenericErrorMessage(newMessage: string): void;
generateExceptionMap(exception: Exception): ExceptionMap;
generateSpecificFieldError(errorInfo: Exception | null | undefined): (fieldName: string) => string[] | undefined;
generateErrorMessage(errorInfo: Exception, options?: Options): string;
}
export default ExceptionTransformer;
129 changes: 109 additions & 20 deletions lib/ExceptionTransformer.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,19 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const mapUtils_1 = require("./utils/mapUtils");
import { createMapFromObject } from "./utils/mapUtils";
import { getErrorDetail, isObjectEmpty, getStringMessage, removeKnownKeysFromErrorDetail, generateFieldErrorFromErrorDetail } from "./utils/errorUtils";
class ExceptionTransformer {
// Initialize the custom exception transformers to the instance
// If you need to handle custom logic according to exception.type, here is an example;
// const customTransformers = {
// ProfileCredentialError: (exception: Exception) => {
// let exceptionMap = new Map();
//
// exceptionMap = exceptionMap.set("email", "You shall not pass.");
//
// return exceptionMap;
// },
// ...
// };
constructor(customTransformers) {
if (customTransformers) {
this.customTransformers = customTransformers;
constructor(genericErrorMessage, config) {
if (config) {
if (config.customTransformers) {
this.customTransformers = config.customTransformers;
}
if (config.onUnexpectedException) {
this.onUnexpectedException = config.onUnexpectedException;
}
}
this.genericErrorMessage = genericErrorMessage;
}
changeGenericErrorMessage(newMessage) {
this.genericErrorMessage = newMessage;
}
// Generates a Map from the exception object that came from API
// Also, includes the `fallback_message` to the `exceptionMap`
Expand All @@ -30,12 +26,105 @@ class ExceptionTransformer {
}
else {
// Using a basic utility to create a Map from object
exceptionMap = mapUtils_1.createMapFromObject(exception.detail);
exceptionMap = createMapFromObject(exception.detail);
}
if (!exceptionMap.get("fallback_message")) {
exceptionMap = exceptionMap.set("fallback_message", exception.fallback_message);
}
return exceptionMap;
}
/* Returns `errorGenerator` function, which returns the field error if there is errorDetail.
Otherwise returns () => undefined

`errorGenerator` function returns value of given key if `fieldName` is string
`fieldName` can be "message.title.name" and this function can through
errorInfo = {
message: {
title: {
name: ["a logical message"];
}
}
}
edizcelik marked this conversation as resolved.
Show resolved Hide resolved
and return `["a logical message"]`

if value of the `fieldName` is not string[],
it will generate the first meaningful message and return it as string[]
*/
generateSpecificFieldError(errorInfo) {
const errorDetail = getErrorDetail(errorInfo);
try {
if (errorDetail) {
return function errorGenerator(fieldName) {
return generateFieldErrorFromErrorDetail(fieldName, errorDetail);
};
}
}
catch (error) {
if (this.onUnexpectedException) {
this.onUnexpectedException({
type: "CAUGHT_ERROR_WHEN_GENERATING_FIELD_ERROR_MESSAGE",
error,
errorInfo: errorInfo
});
}
}
if (errorInfo && this.onUnexpectedException) {
this.onUnexpectedException({
type: "NO_ERROR_DETAIL",
error: undefined,
errorInfo
});
}
return () => undefined;
}
/* Generates a printable error message.
* `options`:
* `skipTypes`: (array of strings) Error types to be skipped.
* `knownErrorKeys`: (array of strings) Error keys to be skipped inside `Exception["detail"]`.
*
* Returns ""; when all error keys in errorInfo.detail are known (that is they are included in `knownErrorKeys`) or errorInfo.type should skipped.
* Returns first meaningful string found in non_field_errors, if it exists.
* Returns `${key}: ${value}`; When there is no non_field_errors, returns the first unknown key and its value combined in a string.
* Returns fallback_message; when it couldn't generate any meaningful message using the methods above.
* Returns genericErrorMessage; when there is no fallback_message
*/
generateErrorMessage(errorInfo, options = {}) {
const { knownErrorKeys = [], skipTypes = [] } = options;
const shouldSkipError = skipTypes && skipTypes.includes(errorInfo.type);
let finalMessage = "";
try {
if (!shouldSkipError) {
let errorDetail = getErrorDetail(errorInfo);
let message = "";
if (errorDetail && !isObjectEmpty(errorDetail)) {
message = getStringMessage(removeKnownKeysFromErrorDetail(errorDetail, knownErrorKeys));
}
else {
message = errorInfo.fallback_message || this.genericErrorMessage;
// call `onUnexpectedException` if it fell to the fallback message
if (this.onUnexpectedException) {
this.onUnexpectedException({
type: "FELL_TO_FALLBACK",
error: undefined,
errorInfo
});
}
}
finalMessage = message;
}
}
catch (error) {
// log this if `onUnexpectedException` is provided
if (this.onUnexpectedException) {
this.onUnexpectedException({
type: "CAUGHT_ERROR_WHEN_GENERATING_ERROR_MESSAGE",
error,
errorInfo
});
}
finalMessage = this.genericErrorMessage;
}
return finalMessage;
}
}
exports.default = ExceptionTransformer;
export default ExceptionTransformer;
21 changes: 15 additions & 6 deletions lib/ExceptionTransformerModel.d.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
declare type ExceptionMap = Map<string, object>;
interface ExceptionDetail {
[fieldName: string]: object;
[key: string]: string[] | ExceptionDetail | ExceptionDetail[];
}
interface CustomTransformers {
[type: string]: (params: Exception) => ExceptionMap;
[type: string]: (params: Exception) => ExceptionMap;
}
interface Exception {
type: string;
detail: ExceptionDetail;
fallback_message: string;
type: string;
detail: ExceptionDetail;
fallback_message: string;
}
export { ExceptionMap, CustomTransformers, ExceptionDetail, Exception };
declare type Options = {
knownErrorKeys?: string[] | null;
skipTypes?: string[];
};
declare type OnUnexpectedException = (details: {
type: string;
error: any;
errorInfo: Exception;
}) => void;
export { ExceptionMap, CustomTransformers, ExceptionDetail, Exception, Options, OnUnexpectedException };
2 changes: 0 additions & 2 deletions lib/ExceptionTransformerModel.js
Original file line number Diff line number Diff line change
@@ -1,2 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
6 changes: 2 additions & 4 deletions lib/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const ExceptionTransformer_1 = require("./ExceptionTransformer");
exports.default = ExceptionTransformer_1.default;
import ExceptionTransformer from "./ExceptionTransformer";
export default ExceptionTransformer;
2 changes: 2 additions & 0 deletions lib/utils/errorConstants.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
declare const GENERIC_ERROR_MESSAGE = "Something went wrong, please try again later.";
export { GENERIC_ERROR_MESSAGE };
4 changes: 4 additions & 0 deletions lib/utils/errorConstants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const GENERIC_ERROR_MESSAGE = "Something went wrong, please try again later.";
exports.GENERIC_ERROR_MESSAGE = GENERIC_ERROR_MESSAGE;
13 changes: 13 additions & 0 deletions lib/utils/errorUtils.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Exception, ExceptionDetail } from "../ExceptionTransformerModel";
declare function getErrorDetail(errorInfo: Exception | null | undefined): ExceptionDetail | null;
declare function isArrayOfString(x: unknown): boolean;
declare function isObjectEmpty(obj: unknown): boolean;
declare function generateMessageFromStringArray(array: string[], key?: string): string;
declare function generateFieldErrorFromErrorDetail(fieldName: string, errorDetail: ExceptionDetail): string[] | undefined;
declare function getStringMessage(errorDetailValue: string[] | ExceptionDetail[] | ExceptionDetail, key?: string): string;
declare function deleteProperty(exceptionDetail: ExceptionDetail, path: string): {
[x: string]: string[] | ExceptionDetail | ExceptionDetail[];
};
declare function removeKnownKeysFromErrorDetail(errorDetail: ExceptionDetail, knownErrorKeys: string[] | null): ExceptionDetail;
declare function getValueFromPath(exceptionDetail: ExceptionDetail, path: string): string[] | ExceptionDetail | ExceptionDetail[] | undefined;
export { getErrorDetail, isArrayOfString, isObjectEmpty, generateMessageFromStringArray, generateFieldErrorFromErrorDetail, getStringMessage, deleteProperty, removeKnownKeysFromErrorDetail, getValueFromPath };
103 changes: 103 additions & 0 deletions lib/utils/errorUtils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
function getErrorDetail(errorInfo) {
return errorInfo && typeof errorInfo.detail === "object"
? errorInfo.detail
: null;
}
function isArrayOfString(x) {
return (Array.isArray(x) &&
x.every(item => Boolean(item && typeof item === "string")));
}
function isArrayOfObject(x) {
return (Array.isArray(x) &&
x.every(item => Boolean(item && typeof item === "object")));
}
function isObjectEmpty(obj) {
return typeof obj === "object" && obj ? Object.keys(obj).length === 0 : false;
}
function generateMessageFromStringArray(array, key) {
let message = array[0];
return key ? `${key}: ${message}` : message;
}
function generateFieldErrorFromErrorDetail(fieldName, errorDetail) {
let fieldError;
//fieldName can be string only
if (typeof fieldName === "string") {
const errorValue = getValueFromPath(errorDetail, fieldName);
// errorValue can be string[], ExceptionDetail[], ExceptionDetail or undefined
if (errorValue) {
if (isArrayOfString(errorValue)) {
fieldError = errorValue;
}
else {
fieldError = getStringMessage(errorValue)
? [getStringMessage(errorValue)]
: undefined;
}
}
}
return fieldError;
}
function getStringMessage(errorDetailValue, key) {
let message = "";
if (Array.isArray(errorDetailValue)) {
if (isArrayOfString(errorDetailValue)) {
// errorDetailValue = ["", ""]
message = generateMessageFromStringArray(errorDetailValue, key);
}
else if (isArrayOfObject(errorDetailValue)) {
// errorDetailValue = [ {}, {}, {..} ]
const firstNonEmptyErrorObject = errorDetailValue.find(x => !isObjectEmpty(x));
if (firstNonEmptyErrorObject) {
message = getStringMessage(firstNonEmptyErrorObject);
}
}
}
else if (typeof errorDetailValue === "object") {
// errorDetailValue = {..} || {}
const errorDetailKeys = Object.keys(errorDetailValue);
if (errorDetailKeys.length) {
// `non_field_errors` has priority
if (errorDetailKeys.includes("non_field_errors") &&
errorDetailValue.non_field_errors) {
message = getStringMessage(errorDetailValue.non_field_errors);
}
else {
// Generate message from the immediately found field error
message = getStringMessage(errorDetailValue[errorDetailKeys[0]], errorDetailKeys[0]);
}
}
}
else {
// If `errorDetailValue` is neither string[], ExceptionDetail[] nor ExceptionDetail
message = JSON.stringify(errorDetailValue);
}
return message;
}
function deleteProperty(exceptionDetail, path) {
const filteredObj = { ...exceptionDetail };
const keys = path.split(".");
keys.reduce((value, key, index) => {
if (value && !Array.isArray(value)) {
if (index === keys.length - 1) {
delete value[key];
}
return value[key];
}
}, filteredObj);
return filteredObj;
}
function removeKnownKeysFromErrorDetail(errorDetail, knownErrorKeys) {
if (knownErrorKeys && knownErrorKeys.length) {
// delete all `knownErrorKeys` from errorDetail
errorDetail = knownErrorKeys.reduce(deleteProperty, errorDetail);
}
return errorDetail;
}
function getValueFromPath(exceptionDetail, path) {
const filteredObj = { ...exceptionDetail };
const keys = path.split(".");
return keys.reduce((acc, key) => {
return acc && !Array.isArray(acc) ? acc[key] : undefined;
}, filteredObj);
}
export { getErrorDetail, isArrayOfString, isObjectEmpty, generateMessageFromStringArray, generateFieldErrorFromErrorDetail, getStringMessage, deleteProperty, removeKnownKeysFromErrorDetail, getValueFromPath };
4 changes: 1 addition & 3 deletions lib/utils/mapUtils.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
function createMapFromObject(obj) {
const map = new Map();
for (let key in obj) {
map.set(key, obj[key]);
}
return map;
}
exports.createMapFromObject = createMapFromObject;
export { createMapFromObject };
4 changes: 2 additions & 2 deletions package-lock.json

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

Loading