175 lines
5.0 KiB
JavaScript
175 lines
5.0 KiB
JavaScript
import deepCopy from "@stdlib/utils-copy";
|
|
import { GhostError } from "./GhostError";
|
|
import * as errors from "./errors";
|
|
const errorsWithBase = { ...errors, GhostError };
|
|
const _private = {
|
|
serialize(err) {
|
|
try {
|
|
return {
|
|
id: err.id,
|
|
status: err.statusCode,
|
|
code: err.code || err.errorType,
|
|
title: err.name,
|
|
detail: err.message,
|
|
meta: {
|
|
context: err.context,
|
|
help: err.help,
|
|
errorDetails: err.errorDetails,
|
|
level: err.level,
|
|
errorType: err.errorType
|
|
}
|
|
};
|
|
} catch (error) {
|
|
return {
|
|
detail: "Something went wrong."
|
|
};
|
|
}
|
|
},
|
|
deserialize(obj) {
|
|
return {
|
|
id: obj.id,
|
|
message: obj.detail || obj.error_description || obj.message,
|
|
statusCode: obj.status,
|
|
code: obj.code || obj.error,
|
|
level: obj.meta && obj.meta.level,
|
|
help: obj.meta && obj.meta.help,
|
|
context: obj.meta && obj.meta.context
|
|
};
|
|
},
|
|
/**
|
|
* @description Serialize error instance into oauth format.
|
|
*
|
|
* @see https://tools.ietf.org/html/rfc6749#page-45
|
|
*
|
|
* To not loose any error data when sending errors between internal services, we use the suggested OAuth properties and add ours as well.
|
|
*/
|
|
OAuthSerialize(err) {
|
|
const matchTable = {
|
|
[errors.NoPermissionError.name]: "access_denied",
|
|
[errors.MaintenanceError.name]: "temporarily_unavailable",
|
|
[errors.BadRequestError.name]: "invalid_request",
|
|
[errors.ValidationError.name]: "invalid_request",
|
|
default: "server_error"
|
|
};
|
|
const { detail, code, ...properties } = _private.serialize(err);
|
|
return {
|
|
error: err.code || matchTable[err.name] || "server_error",
|
|
error_description: err.message,
|
|
...properties
|
|
};
|
|
},
|
|
/**
|
|
* @description Deserialize oauth error format into GhostError instance.
|
|
* @constructor
|
|
*/
|
|
OAuthDeserialize(errorFormat) {
|
|
try {
|
|
return new errorsWithBase[errorFormat.title || errorFormat.name || errors.InternalServerError.name](_private.deserialize(errorFormat));
|
|
} catch (err) {
|
|
return new errors.InternalServerError({
|
|
errorType: errorFormat.title || errorFormat.name,
|
|
..._private.deserialize(errorFormat)
|
|
});
|
|
}
|
|
},
|
|
/**
|
|
* @description Serialize GhostError instance into jsonapi.org format.
|
|
* @param err
|
|
* @return {Object}
|
|
*/
|
|
JSONAPISerialize(err) {
|
|
const errorFormat = {
|
|
errors: [_private.serialize(err)]
|
|
};
|
|
errorFormat.errors[0].source = {};
|
|
if (err.property) {
|
|
errorFormat.errors[0].source.pointer = "/data/attributes/" + err.property;
|
|
}
|
|
return errorFormat;
|
|
},
|
|
/**
|
|
* @description Deserialize JSON api format into GhostError instance.
|
|
*/
|
|
JSONAPIDeserialize(errorFormat) {
|
|
errorFormat = errorFormat.errors && errorFormat.errors[0] || {};
|
|
let internalError;
|
|
try {
|
|
internalError = new errorsWithBase[errorFormat.title || errorFormat.name || errors.InternalServerError.name](_private.deserialize(errorFormat));
|
|
} catch (err) {
|
|
internalError = new errors.InternalServerError({
|
|
errorType: errorFormat.title || errorFormat.name,
|
|
..._private.deserialize(errorFormat)
|
|
});
|
|
}
|
|
if (errorFormat.source && errorFormat.source.pointer) {
|
|
internalError.property = errorFormat.source.pointer.split("/")[3];
|
|
}
|
|
return internalError;
|
|
}
|
|
};
|
|
function serialize(err, options) {
|
|
options = options || { format: "jsonapi" };
|
|
let errorFormat = {};
|
|
try {
|
|
if (options.format === "jsonapi") {
|
|
errorFormat = _private.JSONAPISerialize(err);
|
|
} else {
|
|
errorFormat = _private.OAuthSerialize(err);
|
|
}
|
|
} catch (error) {
|
|
errorFormat.message = "Something went wrong.";
|
|
}
|
|
return errorFormat;
|
|
}
|
|
;
|
|
function deserialize(errorFormat) {
|
|
let internalError = {};
|
|
if (errorFormat.errors) {
|
|
internalError = _private.JSONAPIDeserialize(errorFormat);
|
|
} else {
|
|
internalError = _private.OAuthDeserialize(errorFormat);
|
|
}
|
|
return internalError;
|
|
}
|
|
;
|
|
function prepareStackForUser(error) {
|
|
const stackbits = error.stack?.split(/\n/) || [];
|
|
const hideStack = "hideStack" in error && error.hideStack;
|
|
if (hideStack) {
|
|
stackbits.splice(1, stackbits.length - 1);
|
|
} else {
|
|
stackbits.splice(1, 0, `Stack Trace:`);
|
|
}
|
|
if ("help" in error && error.help) {
|
|
stackbits.splice(1, 0, `${error.help}`);
|
|
}
|
|
if ("context" in error && error.context) {
|
|
stackbits.splice(1, 0, `${error.context}`);
|
|
}
|
|
const errorClone = deepCopy(error);
|
|
errorClone.stack = stackbits.join("\n");
|
|
return errorClone;
|
|
}
|
|
;
|
|
function isGhostError(err) {
|
|
const errorName = GhostError.name;
|
|
const legacyErrorName = "IgnitionError";
|
|
const recursiveIsGhostError = function recursiveIsGhostError2(obj) {
|
|
if (!obj || !obj.name) {
|
|
return false;
|
|
}
|
|
if (obj.name === errorName || obj.name === legacyErrorName) {
|
|
return true;
|
|
}
|
|
return recursiveIsGhostError2(Object.getPrototypeOf(obj));
|
|
};
|
|
return recursiveIsGhostError(err.constructor);
|
|
}
|
|
;
|
|
export {
|
|
deserialize,
|
|
isGhostError,
|
|
prepareStackForUser,
|
|
serialize
|
|
};
|