New Research: Supply Chain Attack on Axios Pulls Malicious Dependency from npm.Details
Socket
Book a DemoSign in
Socket

@tryghost/errors

Package Overview
Dependencies
Maintainers
30
Versions
79
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@tryghost/errors - npm Package Compare versions

Comparing version
1.2.27
to
1.3.0
+319
cjs/errors.js
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var errors_exports = {};
__export(errors_exports, {
BadRequestError: () => BadRequestError,
ConflictError: () => ConflictError,
DataExportError: () => DataExportError,
DataImportError: () => DataImportError,
DisabledFeatureError: () => DisabledFeatureError,
EmailError: () => EmailError,
HelperWarning: () => HelperWarning,
HostLimitError: () => HostLimitError,
IncorrectUsageError: () => IncorrectUsageError,
InternalServerError: () => InternalServerError,
MaintenanceError: () => MaintenanceError,
MethodNotAllowedError: () => MethodNotAllowedError,
MigrationError: () => MigrationError,
NoContentError: () => NoContentError,
NoPermissionError: () => NoPermissionError,
NotFoundError: () => NotFoundError,
PasswordResetRequiredError: () => PasswordResetRequiredError,
RequestEntityTooLargeError: () => RequestEntityTooLargeError,
RequestNotAcceptableError: () => RequestNotAcceptableError,
ThemeValidationError: () => ThemeValidationError,
TokenRevocationError: () => TokenRevocationError,
TooManyRequestsError: () => TooManyRequestsError,
UnauthorizedError: () => UnauthorizedError,
UnhandledJobError: () => UnhandledJobError,
UnsupportedMediaTypeError: () => UnsupportedMediaTypeError,
UpdateCollisionError: () => UpdateCollisionError,
ValidationError: () => ValidationError,
VersionMismatchError: () => VersionMismatchError
});
module.exports = __toCommonJS(errors_exports);
var import_GhostError = require("./GhostError");
const mergeOptions = (options, defaults) => {
const result = { ...defaults };
Object.keys(options).forEach((key) => {
if (options[key] !== void 0) {
result[key] = options[key];
}
});
return result;
};
class InternalServerError extends import_GhostError.GhostError {
constructor(options = {}) {
super(mergeOptions(options, {
statusCode: 500,
level: "critical",
errorType: "InternalServerError",
message: "The server has encountered an error."
}));
}
}
class IncorrectUsageError extends import_GhostError.GhostError {
constructor(options = {}) {
super(mergeOptions(options, {
statusCode: 400,
level: "critical",
errorType: "IncorrectUsageError",
message: "We detected a misuse. Please read the stack trace."
}));
}
}
class NotFoundError extends import_GhostError.GhostError {
constructor(options = {}) {
super(mergeOptions(options, {
statusCode: 404,
errorType: "NotFoundError",
message: "Resource could not be found.",
hideStack: true
}));
}
}
class BadRequestError extends import_GhostError.GhostError {
constructor(options = {}) {
super(mergeOptions(options, {
statusCode: 400,
errorType: "BadRequestError",
message: "The request could not be understood."
}));
}
}
class UnauthorizedError extends import_GhostError.GhostError {
constructor(options = {}) {
super(mergeOptions(options, {
statusCode: 401,
errorType: "UnauthorizedError",
message: "You are not authorised to make this request."
}));
}
}
class NoPermissionError extends import_GhostError.GhostError {
constructor(options = {}) {
super(mergeOptions(options, {
statusCode: 403,
errorType: "NoPermissionError",
message: "You do not have permission to perform this request."
}));
}
}
class ValidationError extends import_GhostError.GhostError {
constructor(options = {}) {
super(mergeOptions(options, {
statusCode: 422,
errorType: "ValidationError",
message: "The request failed validation."
}));
}
}
class UnsupportedMediaTypeError extends import_GhostError.GhostError {
constructor(options = {}) {
super(mergeOptions(options, {
statusCode: 415,
errorType: "UnsupportedMediaTypeError",
message: "The media in the request is not supported by the server."
}));
}
}
class TooManyRequestsError extends import_GhostError.GhostError {
constructor(options = {}) {
super(mergeOptions(options, {
statusCode: 429,
errorType: "TooManyRequestsError",
message: "Server has received too many similar requests in a short space of time."
}));
}
}
class MaintenanceError extends import_GhostError.GhostError {
constructor(options = {}) {
super(mergeOptions(options, {
statusCode: 503,
errorType: "MaintenanceError",
message: "The server is temporarily down for maintenance."
}));
}
}
class MethodNotAllowedError extends import_GhostError.GhostError {
constructor(options = {}) {
super(mergeOptions(options, {
statusCode: 405,
errorType: "MethodNotAllowedError",
message: "Method not allowed for resource."
}));
}
}
class RequestNotAcceptableError extends import_GhostError.GhostError {
constructor(options = {}) {
super(mergeOptions(options, {
statusCode: 406,
errorType: "RequestNotAcceptableError",
message: "Request not acceptable for provided Accept-Version header.",
hideStack: true
}));
}
}
class RequestEntityTooLargeError extends import_GhostError.GhostError {
constructor(options = {}) {
super(mergeOptions(options, {
statusCode: 413,
errorType: "RequestEntityTooLargeError",
message: "Request was too big for the server to handle."
}));
}
}
class TokenRevocationError extends import_GhostError.GhostError {
constructor(options = {}) {
super(mergeOptions(options, {
statusCode: 503,
errorType: "TokenRevocationError",
message: "Token is no longer available."
}));
}
}
class VersionMismatchError extends import_GhostError.GhostError {
constructor(options = {}) {
super(mergeOptions(options, {
statusCode: 400,
errorType: "VersionMismatchError",
message: "Requested version does not match server version."
}));
}
}
class DataExportError extends import_GhostError.GhostError {
constructor(options = {}) {
super(mergeOptions(options, {
statusCode: 500,
errorType: "DataExportError",
message: "The server encountered an error whilst exporting data."
}));
}
}
class DataImportError extends import_GhostError.GhostError {
constructor(options = {}) {
super(mergeOptions(options, {
statusCode: 500,
errorType: "DataImportError",
message: "The server encountered an error whilst importing data."
}));
}
}
class EmailError extends import_GhostError.GhostError {
constructor(options = {}) {
super(mergeOptions(options, {
statusCode: 500,
errorType: "EmailError",
message: "The server encountered an error whilst sending email."
}));
}
}
class ThemeValidationError extends import_GhostError.GhostError {
constructor(options = {}) {
super(mergeOptions(options, {
statusCode: 422,
errorType: "ThemeValidationError",
message: "The theme has a validation error.",
errorDetails: {}
}));
}
}
class DisabledFeatureError extends import_GhostError.GhostError {
constructor(options = {}) {
super(mergeOptions(options, {
statusCode: 409,
errorType: "DisabledFeatureError",
message: "Unable to complete the request, this feature is disabled."
}));
}
}
class UpdateCollisionError extends import_GhostError.GhostError {
constructor(options = {}) {
super(mergeOptions(options, {
statusCode: 409,
errorType: "UpdateCollisionError",
message: "Unable to complete the request, there was a conflict."
}));
}
}
class HostLimitError extends import_GhostError.GhostError {
constructor(options = {}) {
super(mergeOptions(options, {
errorType: "HostLimitError",
hideStack: true,
statusCode: 403,
message: "Unable to complete the request, this resource is limited."
}));
}
}
class HelperWarning extends import_GhostError.GhostError {
constructor(options = {}) {
super(mergeOptions(options, {
errorType: "HelperWarning",
hideStack: true,
statusCode: 400,
message: "A theme helper has done something unexpected."
}));
}
}
class PasswordResetRequiredError extends import_GhostError.GhostError {
constructor(options = {}) {
super(mergeOptions(options, {
errorType: "PasswordResetRequiredError",
statusCode: 401,
message: "For security, you need to create a new password. An email has been sent to you with instructions!"
}));
}
}
class UnhandledJobError extends import_GhostError.GhostError {
constructor(options = {}) {
super(mergeOptions(options, {
errorType: "UnhandledJobError",
message: "Processed job threw an unhandled error",
level: "critical"
}));
}
}
class NoContentError extends import_GhostError.GhostError {
constructor(options = {}) {
super(mergeOptions(options, {
errorType: "NoContentError",
statusCode: 204,
hideStack: true
}));
}
}
class ConflictError extends import_GhostError.GhostError {
constructor(options = {}) {
super(mergeOptions(options, {
errorType: "ConflictError",
statusCode: 409,
message: "The server has encountered an conflict."
}));
}
}
class MigrationError extends import_GhostError.GhostError {
constructor(options = {}) {
super(mergeOptions(options, {
errorType: "MigrationError",
message: "An error has occurred applying a database migration.",
level: "critical"
}));
}
}
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var __publicField = (obj, key, value) => {
__defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
return value;
};
var GhostError_exports = {};
__export(GhostError_exports, {
GhostError: () => GhostError
});
module.exports = __toCommonJS(GhostError_exports);
var import_uuid = require("uuid");
var import_wrap_stack = require("./wrap-stack");
class GhostError extends Error {
constructor(options = {}) {
super();
__publicField(this, "statusCode");
__publicField(this, "errorType");
__publicField(this, "level");
__publicField(this, "id");
__publicField(this, "context");
__publicField(this, "help");
// eslint-disable-next-line @typescript-eslint/no-explicit-any
__publicField(this, "errorDetails");
__publicField(this, "code");
__publicField(this, "property");
__publicField(this, "redirect");
__publicField(this, "hideStack");
this.statusCode = 500;
this.errorType = "InternalServerError";
this.level = "normal";
this.message = "The server has encountered an error.";
this.id = (0, import_uuid.v1)();
this.id = options.id || this.id;
this.statusCode = options.statusCode || this.statusCode;
this.level = options.level || this.level;
this.context = options.context;
this.help = options.help;
this.errorType = this.name = options.errorType || this.errorType;
this.errorDetails = options.errorDetails;
this.code = options.code || null;
this.property = options.property || null;
this.redirect = options.redirect || null;
this.message = options.message || this.message;
this.hideStack = options.hideStack || false;
if (options.err) {
if (typeof options.err === "string") {
options.err = new Error(options.err);
}
Object.getOwnPropertyNames(options.err).forEach((property) => {
if (["errorType", "name", "statusCode", "message", "level"].indexOf(property) !== -1) {
return;
}
if (property === "code") {
this[property] = this[property] || options.err[property];
return;
}
if (property === "stack" && !this.hideStack) {
this[property] = (0, import_wrap_stack.wrapStack)(this, options.err);
return;
}
this[property] = options.err[property] || this[property];
});
}
}
}
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var src_exports = {};
__export(src_exports, {
default: () => src_default,
utils: () => utils
});
module.exports = __toCommonJS(src_exports);
var ghostErrors = __toESM(require("./errors"));
var import_utils = require("./utils");
__reExport(src_exports, require("./errors"), module.exports);
var src_default = ghostErrors;
const utils = {
serialize: import_utils.serialize,
deserialize: import_utils.deserialize,
isGhostError: import_utils.isGhostError,
prepareStackForUser: import_utils.prepareStackForUser
};
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var utils_exports = {};
__export(utils_exports, {
deserialize: () => deserialize,
isGhostError: () => isGhostError,
prepareStackForUser: () => prepareStackForUser,
serialize: () => serialize
});
module.exports = __toCommonJS(utils_exports);
var import_utils_copy = __toESM(require("@stdlib/utils-copy"));
var import_GhostError = require("./GhostError");
var errors = __toESM(require("./errors"));
const errorsWithBase = { ...errors, GhostError: import_GhostError.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 = (0, import_utils_copy.default)(error);
errorClone.stack = stackbits.join("\n");
return errorClone;
}
;
function isGhostError(err) {
const errorName = import_GhostError.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);
}
;
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var wrap_stack_exports = {};
__export(wrap_stack_exports, {
wrapStack: () => wrapStack
});
module.exports = __toCommonJS(wrap_stack_exports);
function wrapStack(err, internalErr) {
const extraLine = (err.stack?.split(/\n/g) || [])[1];
const [firstLine, ...rest] = internalErr.stack?.split(/\n/g) || [];
return [firstLine, extraLine, ...rest].join("\n");
}
;
import { GhostError } from "./GhostError";
const mergeOptions = (options, defaults) => {
const result = { ...defaults };
Object.keys(options).forEach((key) => {
if (options[key] !== void 0) {
result[key] = options[key];
}
});
return result;
};
class InternalServerError extends GhostError {
constructor(options = {}) {
super(mergeOptions(options, {
statusCode: 500,
level: "critical",
errorType: "InternalServerError",
message: "The server has encountered an error."
}));
}
}
class IncorrectUsageError extends GhostError {
constructor(options = {}) {
super(mergeOptions(options, {
statusCode: 400,
level: "critical",
errorType: "IncorrectUsageError",
message: "We detected a misuse. Please read the stack trace."
}));
}
}
class NotFoundError extends GhostError {
constructor(options = {}) {
super(mergeOptions(options, {
statusCode: 404,
errorType: "NotFoundError",
message: "Resource could not be found.",
hideStack: true
}));
}
}
class BadRequestError extends GhostError {
constructor(options = {}) {
super(mergeOptions(options, {
statusCode: 400,
errorType: "BadRequestError",
message: "The request could not be understood."
}));
}
}
class UnauthorizedError extends GhostError {
constructor(options = {}) {
super(mergeOptions(options, {
statusCode: 401,
errorType: "UnauthorizedError",
message: "You are not authorised to make this request."
}));
}
}
class NoPermissionError extends GhostError {
constructor(options = {}) {
super(mergeOptions(options, {
statusCode: 403,
errorType: "NoPermissionError",
message: "You do not have permission to perform this request."
}));
}
}
class ValidationError extends GhostError {
constructor(options = {}) {
super(mergeOptions(options, {
statusCode: 422,
errorType: "ValidationError",
message: "The request failed validation."
}));
}
}
class UnsupportedMediaTypeError extends GhostError {
constructor(options = {}) {
super(mergeOptions(options, {
statusCode: 415,
errorType: "UnsupportedMediaTypeError",
message: "The media in the request is not supported by the server."
}));
}
}
class TooManyRequestsError extends GhostError {
constructor(options = {}) {
super(mergeOptions(options, {
statusCode: 429,
errorType: "TooManyRequestsError",
message: "Server has received too many similar requests in a short space of time."
}));
}
}
class MaintenanceError extends GhostError {
constructor(options = {}) {
super(mergeOptions(options, {
statusCode: 503,
errorType: "MaintenanceError",
message: "The server is temporarily down for maintenance."
}));
}
}
class MethodNotAllowedError extends GhostError {
constructor(options = {}) {
super(mergeOptions(options, {
statusCode: 405,
errorType: "MethodNotAllowedError",
message: "Method not allowed for resource."
}));
}
}
class RequestNotAcceptableError extends GhostError {
constructor(options = {}) {
super(mergeOptions(options, {
statusCode: 406,
errorType: "RequestNotAcceptableError",
message: "Request not acceptable for provided Accept-Version header.",
hideStack: true
}));
}
}
class RequestEntityTooLargeError extends GhostError {
constructor(options = {}) {
super(mergeOptions(options, {
statusCode: 413,
errorType: "RequestEntityTooLargeError",
message: "Request was too big for the server to handle."
}));
}
}
class TokenRevocationError extends GhostError {
constructor(options = {}) {
super(mergeOptions(options, {
statusCode: 503,
errorType: "TokenRevocationError",
message: "Token is no longer available."
}));
}
}
class VersionMismatchError extends GhostError {
constructor(options = {}) {
super(mergeOptions(options, {
statusCode: 400,
errorType: "VersionMismatchError",
message: "Requested version does not match server version."
}));
}
}
class DataExportError extends GhostError {
constructor(options = {}) {
super(mergeOptions(options, {
statusCode: 500,
errorType: "DataExportError",
message: "The server encountered an error whilst exporting data."
}));
}
}
class DataImportError extends GhostError {
constructor(options = {}) {
super(mergeOptions(options, {
statusCode: 500,
errorType: "DataImportError",
message: "The server encountered an error whilst importing data."
}));
}
}
class EmailError extends GhostError {
constructor(options = {}) {
super(mergeOptions(options, {
statusCode: 500,
errorType: "EmailError",
message: "The server encountered an error whilst sending email."
}));
}
}
class ThemeValidationError extends GhostError {
constructor(options = {}) {
super(mergeOptions(options, {
statusCode: 422,
errorType: "ThemeValidationError",
message: "The theme has a validation error.",
errorDetails: {}
}));
}
}
class DisabledFeatureError extends GhostError {
constructor(options = {}) {
super(mergeOptions(options, {
statusCode: 409,
errorType: "DisabledFeatureError",
message: "Unable to complete the request, this feature is disabled."
}));
}
}
class UpdateCollisionError extends GhostError {
constructor(options = {}) {
super(mergeOptions(options, {
statusCode: 409,
errorType: "UpdateCollisionError",
message: "Unable to complete the request, there was a conflict."
}));
}
}
class HostLimitError extends GhostError {
constructor(options = {}) {
super(mergeOptions(options, {
errorType: "HostLimitError",
hideStack: true,
statusCode: 403,
message: "Unable to complete the request, this resource is limited."
}));
}
}
class HelperWarning extends GhostError {
constructor(options = {}) {
super(mergeOptions(options, {
errorType: "HelperWarning",
hideStack: true,
statusCode: 400,
message: "A theme helper has done something unexpected."
}));
}
}
class PasswordResetRequiredError extends GhostError {
constructor(options = {}) {
super(mergeOptions(options, {
errorType: "PasswordResetRequiredError",
statusCode: 401,
message: "For security, you need to create a new password. An email has been sent to you with instructions!"
}));
}
}
class UnhandledJobError extends GhostError {
constructor(options = {}) {
super(mergeOptions(options, {
errorType: "UnhandledJobError",
message: "Processed job threw an unhandled error",
level: "critical"
}));
}
}
class NoContentError extends GhostError {
constructor(options = {}) {
super(mergeOptions(options, {
errorType: "NoContentError",
statusCode: 204,
hideStack: true
}));
}
}
class ConflictError extends GhostError {
constructor(options = {}) {
super(mergeOptions(options, {
errorType: "ConflictError",
statusCode: 409,
message: "The server has encountered an conflict."
}));
}
}
class MigrationError extends GhostError {
constructor(options = {}) {
super(mergeOptions(options, {
errorType: "MigrationError",
message: "An error has occurred applying a database migration.",
level: "critical"
}));
}
}
export {
BadRequestError,
ConflictError,
DataExportError,
DataImportError,
DisabledFeatureError,
EmailError,
HelperWarning,
HostLimitError,
IncorrectUsageError,
InternalServerError,
MaintenanceError,
MethodNotAllowedError,
MigrationError,
NoContentError,
NoPermissionError,
NotFoundError,
PasswordResetRequiredError,
RequestEntityTooLargeError,
RequestNotAcceptableError,
ThemeValidationError,
TokenRevocationError,
TooManyRequestsError,
UnauthorizedError,
UnhandledJobError,
UnsupportedMediaTypeError,
UpdateCollisionError,
ValidationError,
VersionMismatchError
};
var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField = (obj, key, value) => {
__defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
return value;
};
import { v1 as uuidv1 } from "uuid";
import { wrapStack } from "./wrap-stack";
class GhostError extends Error {
constructor(options = {}) {
super();
__publicField(this, "statusCode");
__publicField(this, "errorType");
__publicField(this, "level");
__publicField(this, "id");
__publicField(this, "context");
__publicField(this, "help");
// eslint-disable-next-line @typescript-eslint/no-explicit-any
__publicField(this, "errorDetails");
__publicField(this, "code");
__publicField(this, "property");
__publicField(this, "redirect");
__publicField(this, "hideStack");
this.statusCode = 500;
this.errorType = "InternalServerError";
this.level = "normal";
this.message = "The server has encountered an error.";
this.id = uuidv1();
this.id = options.id || this.id;
this.statusCode = options.statusCode || this.statusCode;
this.level = options.level || this.level;
this.context = options.context;
this.help = options.help;
this.errorType = this.name = options.errorType || this.errorType;
this.errorDetails = options.errorDetails;
this.code = options.code || null;
this.property = options.property || null;
this.redirect = options.redirect || null;
this.message = options.message || this.message;
this.hideStack = options.hideStack || false;
if (options.err) {
if (typeof options.err === "string") {
options.err = new Error(options.err);
}
Object.getOwnPropertyNames(options.err).forEach((property) => {
if (["errorType", "name", "statusCode", "message", "level"].indexOf(property) !== -1) {
return;
}
if (property === "code") {
this[property] = this[property] || options.err[property];
return;
}
if (property === "stack" && !this.hideStack) {
this[property] = wrapStack(this, options.err);
return;
}
this[property] = options.err[property] || this[property];
});
}
}
}
export {
GhostError
};
import * as ghostErrors from "./errors";
import { deserialize, isGhostError, prepareStackForUser, serialize } from "./utils";
export * from "./errors";
var src_default = ghostErrors;
const utils = {
serialize,
deserialize,
isGhostError,
prepareStackForUser
};
export {
src_default as default,
utils
};
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
};
function wrapStack(err, internalErr) {
const extraLine = (err.stack?.split(/\n/g) || [])[1];
const [firstLine, ...rest] = internalErr.stack?.split(/\n/g) || [];
return [firstLine, extraLine, ...rest].join("\n");
}
;
export {
wrapStack
};
import {GhostError, GhostErrorOptions} from './GhostError';
const mergeOptions = (options: GhostErrorOptions, defaults: GhostErrorOptions) => {
const result = {...defaults};
// Ignore undefined options - for example passing statusCode: undefined should not override the default
(Object.keys(options) as (keyof GhostErrorOptions)[]).forEach((key) => {
if (options[key] !== undefined) {
result[key] = options[key];
}
});
return result;
};
export class InternalServerError extends GhostError {
constructor(options: GhostErrorOptions = {}) {
super(mergeOptions(options, {
statusCode: 500,
level: 'critical',
errorType: 'InternalServerError',
message: 'The server has encountered an error.'
}));
}
}
export class IncorrectUsageError extends GhostError {
constructor(options: GhostErrorOptions = {}) {
super(mergeOptions(options, {
statusCode: 400,
level: 'critical',
errorType: 'IncorrectUsageError',
message: 'We detected a misuse. Please read the stack trace.'
}));
}
}
export class NotFoundError extends GhostError {
constructor(options: GhostErrorOptions = {}) {
super(mergeOptions(options, {
statusCode: 404,
errorType: 'NotFoundError',
message: 'Resource could not be found.',
hideStack: true
}));
}
}
export class BadRequestError extends GhostError {
constructor(options: GhostErrorOptions = {}) {
super(mergeOptions(options, {
statusCode: 400,
errorType: 'BadRequestError',
message: 'The request could not be understood.'
}));
}
}
export class UnauthorizedError extends GhostError {
constructor(options: GhostErrorOptions = {}) {
super(mergeOptions(options, {
statusCode: 401,
errorType: 'UnauthorizedError',
message: 'You are not authorised to make this request.'
}));
}
}
export class NoPermissionError extends GhostError {
constructor(options: GhostErrorOptions = {}) {
super(mergeOptions(options, {
statusCode: 403,
errorType: 'NoPermissionError',
message: 'You do not have permission to perform this request.'
}));
}
}
export class ValidationError extends GhostError {
constructor(options: GhostErrorOptions = {}) {
super(mergeOptions(options, {
statusCode: 422,
errorType: 'ValidationError',
message: 'The request failed validation.'
}));
}
}
export class UnsupportedMediaTypeError extends GhostError {
constructor(options: GhostErrorOptions = {}) {
super(mergeOptions(options, {
statusCode: 415,
errorType: 'UnsupportedMediaTypeError',
message: 'The media in the request is not supported by the server.'
}));
}
}
export class TooManyRequestsError extends GhostError {
constructor(options: GhostErrorOptions = {}) {
super(mergeOptions(options, {
statusCode: 429,
errorType: 'TooManyRequestsError',
message: 'Server has received too many similar requests in a short space of time.'
}));
}
}
export class MaintenanceError extends GhostError {
constructor(options: GhostErrorOptions = {}) {
super(mergeOptions(options, {
statusCode: 503,
errorType: 'MaintenanceError',
message: 'The server is temporarily down for maintenance.'
}));
}
}
export class MethodNotAllowedError extends GhostError {
constructor(options: GhostErrorOptions = {}) {
super(mergeOptions(options, {
statusCode: 405,
errorType: 'MethodNotAllowedError',
message: 'Method not allowed for resource.'
}));
}
}
export class RequestNotAcceptableError extends GhostError {
constructor(options: GhostErrorOptions = {}) {
super(mergeOptions(options, {
statusCode: 406,
errorType: 'RequestNotAcceptableError',
message: 'Request not acceptable for provided Accept-Version header.',
hideStack: true
}));
}
}
export class RequestEntityTooLargeError extends GhostError {
constructor(options: GhostErrorOptions = {}) {
super(mergeOptions(options, {
statusCode: 413,
errorType: 'RequestEntityTooLargeError',
message: 'Request was too big for the server to handle.'
}));
}
}
export class TokenRevocationError extends GhostError {
constructor(options: GhostErrorOptions = {}) {
super(mergeOptions(options, {
statusCode: 503,
errorType: 'TokenRevocationError',
message: 'Token is no longer available.'
}));
}
}
export class VersionMismatchError extends GhostError {
constructor(options: GhostErrorOptions = {}) {
super(mergeOptions(options, {
statusCode: 400,
errorType: 'VersionMismatchError',
message: 'Requested version does not match server version.'
}));
}
}
export class DataExportError extends GhostError {
constructor(options: GhostErrorOptions = {}) {
super(mergeOptions(options, {
statusCode: 500,
errorType: 'DataExportError',
message: 'The server encountered an error whilst exporting data.'
}));
}
}
export class DataImportError extends GhostError {
constructor(options: GhostErrorOptions = {}) {
super(mergeOptions(options, {
statusCode: 500,
errorType: 'DataImportError',
message: 'The server encountered an error whilst importing data.'
}));
}
}
export class EmailError extends GhostError {
constructor(options: GhostErrorOptions = {}) {
super(mergeOptions(options, {
statusCode: 500,
errorType: 'EmailError',
message: 'The server encountered an error whilst sending email.'
}));
}
}
export class ThemeValidationError extends GhostError {
constructor(options: GhostErrorOptions = {}) {
super(mergeOptions(options, {
statusCode: 422,
errorType: 'ThemeValidationError',
message: 'The theme has a validation error.',
errorDetails: {}
}));
}
}
export class DisabledFeatureError extends GhostError {
constructor(options: GhostErrorOptions = {}) {
super(mergeOptions(options, {
statusCode: 409,
errorType: 'DisabledFeatureError',
message: 'Unable to complete the request, this feature is disabled.'
}));
}
}
export class UpdateCollisionError extends GhostError {
constructor(options: GhostErrorOptions = {}) {
super(mergeOptions(options, {
statusCode: 409,
errorType: 'UpdateCollisionError',
message: 'Unable to complete the request, there was a conflict.'
}));
}
}
export class HostLimitError extends GhostError {
constructor(options: GhostErrorOptions = {}) {
super(mergeOptions(options, {
errorType: 'HostLimitError',
hideStack: true,
statusCode: 403,
message: 'Unable to complete the request, this resource is limited.'
}));
}
}
export class HelperWarning extends GhostError {
constructor(options: GhostErrorOptions = {}) {
super(mergeOptions(options, {
errorType: 'HelperWarning',
hideStack: true,
statusCode: 400,
message: 'A theme helper has done something unexpected.'
}));
}
}
export class PasswordResetRequiredError extends GhostError {
constructor(options: GhostErrorOptions = {}) {
super(mergeOptions(options, {
errorType: 'PasswordResetRequiredError',
statusCode: 401,
message: 'For security, you need to create a new password. An email has been sent to you with instructions!'
}));
}
}
export class UnhandledJobError extends GhostError {
constructor(options: GhostErrorOptions = {}) {
super(mergeOptions(options, {
errorType: 'UnhandledJobError',
message: 'Processed job threw an unhandled error',
level: 'critical'
}));
}
}
export class NoContentError extends GhostError {
constructor(options: GhostErrorOptions = {}) {
super(mergeOptions(options, {
errorType: 'NoContentError',
statusCode: 204,
hideStack: true
}));
}
}
export class ConflictError extends GhostError {
constructor(options: GhostErrorOptions = {}) {
super(mergeOptions(options, {
errorType: 'ConflictError',
statusCode: 409,
message: 'The server has encountered an conflict.'
}));
}
}
export class MigrationError extends GhostError {
constructor(options: GhostErrorOptions = {}) {
super(mergeOptions(options, {
errorType: 'MigrationError',
message: 'An error has occurred applying a database migration.',
level: 'critical'
}));
}
}
import {v1 as uuidv1} from 'uuid';
import {wrapStack} from './wrap-stack';
export interface GhostErrorOptions {
message?: string;
statusCode?: number;
level?: string;
id?: string;
context?: string;
help?: string;
errorType?: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
errorDetails?: any;
code?: string;
property?: string;
redirect?: string;
hideStack?: boolean;
err?: Error | string;
}
export class GhostError extends Error {
statusCode: number;
errorType: string;
level: string;
id: string;
context?: string;
help?: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
errorDetails: any;
code: string | null;
property: string | null;
redirect: string | null;
hideStack: boolean;
constructor(options: GhostErrorOptions = {}) {
super();
/**
* defaults
*/
this.statusCode = 500;
this.errorType = 'InternalServerError';
this.level = 'normal';
this.message = 'The server has encountered an error.';
this.id = uuidv1();
/**
* custom overrides
*/
this.id = options.id || this.id;
this.statusCode = options.statusCode || this.statusCode;
this.level = options.level || this.level;
this.context = options.context;
this.help = options.help;
this.errorType = this.name = options.errorType || this.errorType;
this.errorDetails = options.errorDetails;
this.code = options.code || null;
this.property = options.property || null;
this.redirect = options.redirect || null;
this.message = options.message || this.message;
this.hideStack = options.hideStack || false;
// NOTE: Error to inherit from, override!
// Nested objects are getting copied over in one piece (can be changed, but not needed right now)
if (options.err) {
// CASE: Support err as string (it happens that third party libs return a string instead of an error instance)
if (typeof options.err === 'string') {
/* eslint-disable no-restricted-syntax */
options.err = new Error(options.err);
/* eslint-enable no-restricted-syntax */
}
Object.getOwnPropertyNames(options.err).forEach((property) => {
if (['errorType', 'name', 'statusCode', 'message', 'level'].indexOf(property) !== -1) {
return;
}
// CASE: `code` should put options as priority over err
if (property === 'code') {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
this[property] = this[property] || (options.err as any)[property];
return;
}
if (property === 'stack' && !this.hideStack) {
this[property] = wrapStack(this, options.err as Error);
return;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(this as any)[property] = (options.err as any)[property] || (this as any)[property];
});
}
}
}
import {GhostError} from './GhostError';
import * as ghostErrors from './errors';
import {deserialize, isGhostError, prepareStackForUser, serialize} from './utils';
export * from './errors';
export type {GhostError};
export default ghostErrors;
export const utils = {
serialize,
deserialize,
isGhostError,
prepareStackForUser
};
import deepCopy from '@stdlib/utils-copy';
import {GhostError} from './GhostError';
import * as errors from './errors';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type AnyObject = Record<string, any>
const errorsWithBase: Record<string, typeof GhostError> = {...errors, GhostError};
const _private = {
serialize(err: GhostError) {
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: AnyObject) {
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: GhostError) {
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'
};
// eslint-disable-next-line @typescript-eslint/no-unused-vars
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: AnyObject): GhostError {
try {
return new errorsWithBase[errorFormat.title || errorFormat.name || errors.InternalServerError.name](_private.deserialize(errorFormat));
} catch (err) {
// CASE: you receive an OAuth formatted error, but the error prototype is unknown
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: GhostError): AnyObject {
const errorFormat: AnyObject = {
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: AnyObject): GhostError {
errorFormat = errorFormat.errors && errorFormat.errors[0] || {};
let internalError;
try {
internalError = new errorsWithBase[errorFormat.title || errorFormat.name || errors.InternalServerError.name](_private.deserialize(errorFormat));
} catch (err) {
// CASE: you receive a JSON format error, but the error prototype is unknown
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;
}
};
/**
* @description Serialize GhostError instance to error JSON format
*
* jsonapi.org error format:
*
* source: {
* parameter: URL query parameter (no support yet)
* pointer: HTTP body attribute
* }
*
* @see http://jsonapi.org/format/#errors
*/
export function serialize(err: GhostError, options?: {format: 'jsonapi' | 'oauth'}) {
options = options || {format: 'jsonapi'};
let errorFormat: AnyObject = {};
try {
if (options.format === 'jsonapi') {
errorFormat = _private.JSONAPISerialize(err);
} else {
errorFormat = _private.OAuthSerialize(err);
}
} catch (error) {
errorFormat.message = 'Something went wrong.';
}
// no need to sanitize the undefined values, on response send JSON.stringify get's called
return errorFormat;
};
/**
* @description Deserialize from error JSON format to GhostError instance
*/
export function deserialize(errorFormat: AnyObject): AnyObject {
let internalError = {};
if (errorFormat.errors) {
internalError = _private.JSONAPIDeserialize(errorFormat);
} else {
internalError = _private.OAuthDeserialize(errorFormat);
}
return internalError;
};
/**
* @description Replace the stack with a user-facing one
* @returns Clone of the original error with a user-facing stack
*/
export function prepareStackForUser(error: GhostError): GhostError
export function prepareStackForUser(error: Error): Error
export function prepareStackForUser(error: Error): Error {
const stackbits = error.stack?.split(/\n/) || [];
// We build this up backwards, so we always insert at position 1
const hideStack = 'hideStack' in error && error.hideStack;
if (process.env.NODE_ENV === 'production' || hideStack) {
stackbits.splice(1, stackbits.length - 1);
} else {
// Clearly mark the strack trace
stackbits.splice(1, 0, `Stack Trace:`);
}
// Add in our custom context and help methods
if ('help' in error && error.help) {
stackbits.splice(1, 0, `${error.help}`);
}
if ('context' in error && error.context) {
stackbits.splice(1, 0, `${error.context}`);
}
// @NOTE: would be a good idea to swap out the cloning implementation with native
// `structuredClone` one once we use Node v17 or higher. Before making an
// upgrade make sure structuredClone does a full copy of all properties
// present on a custom error (see issue: https://github.com/ungap/structured-clone/issues/12)
const errorClone = deepCopy(error);
errorClone.stack = stackbits.join('\n');
return errorClone;
};
/**
* @description Check whether an error instance is a GhostError.
*/
export function isGhostError(err: Error) {
const errorName = GhostError.name;
const legacyErrorName = 'IgnitionError';
const recursiveIsGhostError = function recursiveIsGhostError(obj: AnyObject): boolean {
// no super constructor available anymore
if (!obj || !obj.name) {
return false;
}
if (obj.name === errorName || obj.name === legacyErrorName) {
return true;
}
return recursiveIsGhostError(Object.getPrototypeOf(obj));
};
return recursiveIsGhostError(err.constructor);
};
export function wrapStack(err: Error, internalErr: Error) {
const extraLine = (err.stack?.split(/\n/g) || [])[1];
const [firstLine, ...rest] = internalErr.stack?.split(/\n/g) || [];
return [firstLine, extraLine, ...rest].join('\n');
};
import { GhostError, GhostErrorOptions } from './GhostError';
export declare class InternalServerError extends GhostError {
constructor(options?: GhostErrorOptions);
}
export declare class IncorrectUsageError extends GhostError {
constructor(options?: GhostErrorOptions);
}
export declare class NotFoundError extends GhostError {
constructor(options?: GhostErrorOptions);
}
export declare class BadRequestError extends GhostError {
constructor(options?: GhostErrorOptions);
}
export declare class UnauthorizedError extends GhostError {
constructor(options?: GhostErrorOptions);
}
export declare class NoPermissionError extends GhostError {
constructor(options?: GhostErrorOptions);
}
export declare class ValidationError extends GhostError {
constructor(options?: GhostErrorOptions);
}
export declare class UnsupportedMediaTypeError extends GhostError {
constructor(options?: GhostErrorOptions);
}
export declare class TooManyRequestsError extends GhostError {
constructor(options?: GhostErrorOptions);
}
export declare class MaintenanceError extends GhostError {
constructor(options?: GhostErrorOptions);
}
export declare class MethodNotAllowedError extends GhostError {
constructor(options?: GhostErrorOptions);
}
export declare class RequestNotAcceptableError extends GhostError {
constructor(options?: GhostErrorOptions);
}
export declare class RequestEntityTooLargeError extends GhostError {
constructor(options?: GhostErrorOptions);
}
export declare class TokenRevocationError extends GhostError {
constructor(options?: GhostErrorOptions);
}
export declare class VersionMismatchError extends GhostError {
constructor(options?: GhostErrorOptions);
}
export declare class DataExportError extends GhostError {
constructor(options?: GhostErrorOptions);
}
export declare class DataImportError extends GhostError {
constructor(options?: GhostErrorOptions);
}
export declare class EmailError extends GhostError {
constructor(options?: GhostErrorOptions);
}
export declare class ThemeValidationError extends GhostError {
constructor(options?: GhostErrorOptions);
}
export declare class DisabledFeatureError extends GhostError {
constructor(options?: GhostErrorOptions);
}
export declare class UpdateCollisionError extends GhostError {
constructor(options?: GhostErrorOptions);
}
export declare class HostLimitError extends GhostError {
constructor(options?: GhostErrorOptions);
}
export declare class HelperWarning extends GhostError {
constructor(options?: GhostErrorOptions);
}
export declare class PasswordResetRequiredError extends GhostError {
constructor(options?: GhostErrorOptions);
}
export declare class UnhandledJobError extends GhostError {
constructor(options?: GhostErrorOptions);
}
export declare class NoContentError extends GhostError {
constructor(options?: GhostErrorOptions);
}
export declare class ConflictError extends GhostError {
constructor(options?: GhostErrorOptions);
}
export declare class MigrationError extends GhostError {
constructor(options?: GhostErrorOptions);
}
//# sourceMappingURL=errors.d.ts.map
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,UAAU,EAAE,iBAAiB,EAAC,MAAM,cAAc,CAAC;AAe3D,qBAAa,mBAAoB,SAAQ,UAAU;gBACnC,OAAO,GAAE,iBAAsB;CAQ9C;AAED,qBAAa,mBAAoB,SAAQ,UAAU;gBACnC,OAAO,GAAE,iBAAsB;CAQ9C;AAED,qBAAa,aAAc,SAAQ,UAAU;gBAC7B,OAAO,GAAE,iBAAsB;CAQ9C;AAED,qBAAa,eAAgB,SAAQ,UAAU;gBAC/B,OAAO,GAAE,iBAAsB;CAO9C;AAED,qBAAa,iBAAkB,SAAQ,UAAU;gBACjC,OAAO,GAAE,iBAAsB;CAO9C;AAED,qBAAa,iBAAkB,SAAQ,UAAU;gBACjC,OAAO,GAAE,iBAAsB;CAO9C;AAED,qBAAa,eAAgB,SAAQ,UAAU;gBAC/B,OAAO,GAAE,iBAAsB;CAO9C;AAED,qBAAa,yBAA0B,SAAQ,UAAU;gBACzC,OAAO,GAAE,iBAAsB;CAO9C;AAED,qBAAa,oBAAqB,SAAQ,UAAU;gBACpC,OAAO,GAAE,iBAAsB;CAO9C;AAED,qBAAa,gBAAiB,SAAQ,UAAU;gBAChC,OAAO,GAAE,iBAAsB;CAO9C;AAED,qBAAa,qBAAsB,SAAQ,UAAU;gBACrC,OAAO,GAAE,iBAAsB;CAO9C;AAED,qBAAa,yBAA0B,SAAQ,UAAU;gBACzC,OAAO,GAAE,iBAAsB;CAQ9C;AAED,qBAAa,0BAA2B,SAAQ,UAAU;gBAC1C,OAAO,GAAE,iBAAsB;CAO9C;AAED,qBAAa,oBAAqB,SAAQ,UAAU;gBACpC,OAAO,GAAE,iBAAsB;CAO9C;AAED,qBAAa,oBAAqB,SAAQ,UAAU;gBACpC,OAAO,GAAE,iBAAsB;CAO9C;AAED,qBAAa,eAAgB,SAAQ,UAAU;gBAC/B,OAAO,GAAE,iBAAsB;CAO9C;AAED,qBAAa,eAAgB,SAAQ,UAAU;gBAC/B,OAAO,GAAE,iBAAsB;CAO9C;AAED,qBAAa,UAAW,SAAQ,UAAU;gBAC1B,OAAO,GAAE,iBAAsB;CAO9C;AAED,qBAAa,oBAAqB,SAAQ,UAAU;gBACpC,OAAO,GAAE,iBAAsB;CAQ9C;AAED,qBAAa,oBAAqB,SAAQ,UAAU;gBACpC,OAAO,GAAE,iBAAsB;CAO9C;AAED,qBAAa,oBAAqB,SAAQ,UAAU;gBACpC,OAAO,GAAE,iBAAsB;CAO9C;AAED,qBAAa,cAAe,SAAQ,UAAU;gBAC9B,OAAO,GAAE,iBAAsB;CAQ9C;AAED,qBAAa,aAAc,SAAQ,UAAU;gBAC7B,OAAO,GAAE,iBAAsB;CAQ9C;AAED,qBAAa,0BAA2B,SAAQ,UAAU;gBAC1C,OAAO,GAAE,iBAAsB;CAO9C;AAED,qBAAa,iBAAkB,SAAQ,UAAU;gBACjC,OAAO,GAAE,iBAAsB;CAO9C;AAED,qBAAa,cAAe,SAAQ,UAAU;gBAC9B,OAAO,GAAE,iBAAsB;CAO9C;AAED,qBAAa,aAAc,SAAQ,UAAU;gBAC7B,OAAO,GAAE,iBAAsB;CAO9C;AAED,qBAAa,cAAe,SAAQ,UAAU;gBAC9B,OAAO,GAAE,iBAAsB;CAO9C"}
export interface GhostErrorOptions {
message?: string;
statusCode?: number;
level?: string;
id?: string;
context?: string;
help?: string;
errorType?: string;
errorDetails?: any;
code?: string;
property?: string;
redirect?: string;
hideStack?: boolean;
err?: Error | string;
}
export declare class GhostError extends Error {
statusCode: number;
errorType: string;
level: string;
id: string;
context?: string;
help?: string;
errorDetails: any;
code: string | null;
property: string | null;
redirect: string | null;
hideStack: boolean;
constructor(options?: GhostErrorOptions);
}
//# sourceMappingURL=GhostError.d.ts.map
{"version":3,"file":"GhostError.d.ts","sourceRoot":"","sources":["../src/GhostError.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,iBAAiB;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,YAAY,CAAC,EAAE,GAAG,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,GAAG,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;CACxB;AAED,qBAAa,UAAW,SAAQ,KAAK;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd,YAAY,EAAE,GAAG,CAAC;IAClB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,EAAE,OAAO,CAAC;gBAEP,OAAO,GAAE,iBAAsB;CA6D9C"}
import { GhostError } from './GhostError';
import * as ghostErrors from './errors';
import { deserialize, isGhostError, prepareStackForUser, serialize } from './utils';
export * from './errors';
export type { GhostError };
export default ghostErrors;
export declare const utils: {
serialize: typeof serialize;
deserialize: typeof deserialize;
isGhostError: typeof isGhostError;
prepareStackForUser: typeof prepareStackForUser;
};
//# sourceMappingURL=index.d.ts.map
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,UAAU,EAAC,MAAM,cAAc,CAAC;AACxC,OAAO,KAAK,WAAW,MAAM,UAAU,CAAC;AACxC,OAAO,EAAC,WAAW,EAAE,YAAY,EAAE,mBAAmB,EAAE,SAAS,EAAC,MAAM,SAAS,CAAC;AAElF,cAAc,UAAU,CAAC;AACzB,YAAY,EAAC,UAAU,EAAC,CAAC;AACzB,eAAe,WAAW,CAAC;AAE3B,eAAO,MAAM,KAAK;;;;;CAKjB,CAAC"}
import { GhostError } from './GhostError';
type AnyObject = Record<string, any>;
/**
* @description Serialize GhostError instance to error JSON format
*
* jsonapi.org error format:
*
* source: {
* parameter: URL query parameter (no support yet)
* pointer: HTTP body attribute
* }
*
* @see http://jsonapi.org/format/#errors
*/
export declare function serialize(err: GhostError, options?: {
format: 'jsonapi' | 'oauth';
}): AnyObject;
/**
* @description Deserialize from error JSON format to GhostError instance
*/
export declare function deserialize(errorFormat: AnyObject): AnyObject;
/**
* @description Replace the stack with a user-facing one
* @returns Clone of the original error with a user-facing stack
*/
export declare function prepareStackForUser(error: GhostError): GhostError;
export declare function prepareStackForUser(error: Error): Error;
/**
* @description Check whether an error instance is a GhostError.
*/
export declare function isGhostError(err: Error): boolean;
export {};
//# sourceMappingURL=utils.d.ts.map
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,UAAU,EAAC,MAAM,cAAc,CAAC;AAIxC,KAAK,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;AA+HpC;;;;;;;;;;;GAWG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE;IAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAAA;CAAC,aAiBjF;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,WAAW,EAAE,SAAS,GAAG,SAAS,CAU7D;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,UAAU,GAAG,UAAU,CAAA;AAClE,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,KAAK,GAAG,KAAK,CAAA;AAiCxD;;GAEG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,KAAK,WAkBtC"}
export declare function wrapStack(err: Error, internalErr: Error): string;
//# sourceMappingURL=wrap-stack.d.ts.map
{"version":3,"file":"wrap-stack.d.ts","sourceRoot":"","sources":["../src/wrap-stack.ts"],"names":[],"mappings":"AAAA,wBAAgB,SAAS,CAAC,GAAG,EAAE,KAAK,EAAE,WAAW,EAAE,KAAK,UAIvD"}
+25
-8
{
"name": "@tryghost/errors",
"version": "1.2.27",
"version": "1.3.0",
"repository": "https://github.com/TryGhost/framework/tree/main/packages/errors",
"author": "Ghost Foundation",
"license": "MIT",
"main": "index.js",
"main": "cjs/index.js",
"module": "es/index.js",
"types": "types/index.d.ts",
"source": "src/index.ts",
"sideEffects": false,
"scripts": {
"dev": "echo \"Implement me!\"",
"test": "NODE_ENV=testing c8 --check-coverage --all --reporter text --reporter cobertura mocha './test/**/*.test.js'",
"prepare": "NODE_ENV=production yarn build",
"pretest": "NODE_ENV=production yarn build",
"build": "yarn build:cjs && yarn build:es && yarn build:types",
"build:cjs": "esbuild src/*.ts --target=es2020 --outdir=cjs --format=cjs",
"build:es": "esbuild src/*.ts --target=es2020 --outdir=es --format=esm",
"build:types": "tsc --emitDeclarationOnly --declaration --declarationMap --outDir types",
"test": "TS_NODE_COMPILER_OPTIONS='{\"module\": \"commonjs\" }' NODE_ENV=testing c8 --check-coverage --all -n src --reporter text --reporter cobertura mocha -r ts-node/register './test/**/*.test.ts'",
"lint": "eslint . --ext .js --cache",

@@ -15,4 +25,6 @@ "posttest": "yarn lint"

"files": [
"index.js",
"lib"
"cjs",
"es",
"types",
"src"
],

@@ -23,13 +35,18 @@ "publishConfig": {

"devDependencies": {
"@types/lodash": "^4.14.200",
"@types/mocha": "^10.0.3",
"@types/uuid": "^9.0.6",
"c8": "8.0.1",
"esbuild": "^0.19.5",
"lodash": "^4.17.21",
"mocha": "10.2.0",
"should": "13.2.3",
"sinon": "17.0.1"
"ts-node": "^10.9.1",
"typescript": "5.2.2"
},
"dependencies": {
"@stdlib/utils-copy": "^0.0.7",
"lodash": "^4.17.21",
"uuid": "^9.0.0"
},
"gitHead": "a655bcfba6531944b8fd997bf53ccefb9dcf1685"
"gitHead": "2a91c8a1f54eef339c90e381252a1c8bbb7aeee4"
}
module.exports = require('./lib/errors');
const uuid = require('uuid');
const merge = require('lodash/merge');
const isString = require('lodash/isString');
const utils = require('./utils');
class GhostError extends Error {
constructor(options = {}) {
super();
/**
* defaults
*/
this.statusCode = 500;
this.errorType = 'InternalServerError';
this.level = 'normal';
this.message = 'The server has encountered an error.';
this.id = uuid.v1();
/**
* custom overrides
*/
this.id = options.id || this.id;
this.statusCode = options.statusCode || this.statusCode;
this.level = options.level || this.level;
this.context = options.context || this.context;
this.help = options.help || this.help;
this.errorType = this.name = options.errorType || this.errorType;
this.errorDetails = options.errorDetails;
// @ts-ignore
this.code = options.code || null;
this.property = options.property || null;
this.redirect = options.redirect || null;
this.message = options.message || this.message;
this.hideStack = options.hideStack || false;
// NOTE: Error to inherit from, override!
// Nested objects are getting copied over in one piece (can be changed, but not needed right now)
if (options.err) {
// CASE: Support err as string (it happens that third party libs return a string instead of an error instance)
if (isString(options.err)) {
// eslint-disable-next-line ghost/ghost-custom/no-native-error
options.err = new Error(options.err);
}
Object.getOwnPropertyNames(options.err).forEach((property) => {
if (['errorType', 'name', 'statusCode', 'message', 'level'].indexOf(property) !== -1) {
return;
}
// CASE: `code` should put options as priority over err
if (property === 'code') {
// @ts-ignore
this[property] = this[property] || options.err[property];
return;
}
if (property === 'stack' && !this.hideStack) {
this[property] = utils.wrapStack(this, options.err);
return;
}
this[property] = options.err[property] || this[property];
});
}
}
}
const ghostErrors = {
InternalServerError: class InternalServerError extends GhostError {
constructor(options) {
super(merge({
statusCode: 500,
level: 'critical',
errorType: 'InternalServerError',
message: 'The server has encountered an error.'
}, options));
}
},
IncorrectUsageError: class IncorrectUsageError extends GhostError {
constructor(options) {
super(merge({
statusCode: 400,
level: 'critical',
errorType: 'IncorrectUsageError',
message: 'We detected a misuse. Please read the stack trace.'
}, options));
}
},
NotFoundError: class NotFoundError extends GhostError {
constructor(options) {
super(merge({
statusCode: 404,
errorType: 'NotFoundError',
message: 'Resource could not be found.',
hideStack: true
}, options));
}
},
BadRequestError: class BadRequestError extends GhostError {
constructor(options) {
super(merge({
statusCode: 400,
errorType: 'BadRequestError',
message: 'The request could not be understood.'
}, options));
}
},
UnauthorizedError: class UnauthorizedError extends GhostError {
constructor(options) {
super(merge({
statusCode: 401,
errorType: 'UnauthorizedError',
message: 'You are not authorised to make this request.'
}, options));
}
},
NoPermissionError: class NoPermissionError extends GhostError {
constructor(options) {
super(merge({
statusCode: 403,
errorType: 'NoPermissionError',
message: 'You do not have permission to perform this request.'
}, options));
}
},
ValidationError: class ValidationError extends GhostError {
constructor(options) {
super(merge({
statusCode: 422,
errorType: 'ValidationError',
message: 'The request failed validation.'
}, options));
}
},
UnsupportedMediaTypeError: class UnsupportedMediaTypeError extends GhostError {
constructor(options) {
super(merge({
statusCode: 415,
errorType: 'UnsupportedMediaTypeError',
message: 'The media in the request is not supported by the server.'
}, options));
}
},
TooManyRequestsError: class TooManyRequestsError extends GhostError {
constructor(options) {
super(merge({
statusCode: 429,
errorType: 'TooManyRequestsError',
message: 'Server has received too many similar requests in a short space of time.'
}, options));
}
},
MaintenanceError: class MaintenanceError extends GhostError {
constructor(options) {
super(merge({
statusCode: 503,
errorType: 'MaintenanceError',
message: 'The server is temporarily down for maintenance.'
}, options));
}
},
MethodNotAllowedError: class MethodNotAllowedError extends GhostError {
constructor(options) {
super(merge({
statusCode: 405,
errorType: 'MethodNotAllowedError',
message: 'Method not allowed for resource.'
}, options));
}
},
RequestNotAcceptableError: class RequestNotAcceptableError extends GhostError {
constructor(options) {
super(merge({
statusCode: 406,
errorType: 'RequestNotAcceptableError',
message: 'Request not acceptable for provided Accept-Version header.',
hideStack: true
}, options));
}
},
RequestEntityTooLargeError: class RequestEntityTooLargeError extends GhostError {
constructor(options) {
super(merge({
statusCode: 413,
errorType: 'RequestEntityTooLargeError',
message: 'Request was too big for the server to handle.'
}, options));
}
},
TokenRevocationError: class TokenRevocationError extends GhostError {
constructor(options) {
super(merge({
statusCode: 503,
errorType: 'TokenRevocationError',
message: 'Token is no longer available.'
}, options));
}
},
VersionMismatchError: class VersionMismatchError extends GhostError {
constructor(options) {
super(merge({
statusCode: 400,
errorType: 'VersionMismatchError',
message: 'Requested version does not match server version.'
}, options));
}
},
DataExportError: class DataExportError extends GhostError {
constructor(options) {
super(merge({
statusCode: 500,
errorType: 'DataExportError',
message: 'The server encountered an error whilst exporting data.'
}, options));
}
},
DataImportError: class DataImportError extends GhostError {
constructor(options) {
super(merge({
statusCode: 500,
errorType: 'DataImportError',
message: 'The server encountered an error whilst importing data.'
}, options));
}
},
EmailError: class EmailError extends GhostError {
constructor(options) {
super(merge({
statusCode: 500,
errorType: 'EmailError',
message: 'The server encountered an error whilst sending email.'
}, options));
}
},
ThemeValidationError: class ThemeValidationError extends GhostError {
constructor(options) {
super(merge({
statusCode: 422,
errorType: 'ThemeValidationError',
message: 'The theme has a validation error.',
errorDetails: {}
}, options));
}
},
DisabledFeatureError: class DisabledFeatureError extends GhostError {
constructor(options) {
super(merge({
statusCode: 409,
errorType: 'DisabledFeatureError',
message: 'Unable to complete the request, this feature is disabled.'
}, options));
}
},
UpdateCollisionError: class UpdateCollisionError extends GhostError {
constructor(options) {
super(merge({
statusCode: 409,
errorType: 'UpdateCollisionError',
message: 'Unable to complete the request, there was a conflict.'
}, options));
}
},
HostLimitError: class HostLimitError extends GhostError {
constructor(options) {
super(merge({
errorType: 'HostLimitError',
hideStack: true,
statusCode: 403,
message: 'Unable to complete the request, this resource is limited.'
}, options));
}
},
HelperWarning: class HelperWarning extends GhostError {
constructor(options) {
super(merge({
errorType: 'HelperWarning',
hideStack: true,
statusCode: 400,
message: 'A theme helper has done something unexpected.'
}, options));
}
},
PasswordResetRequiredError: class PasswordResetRequiredError extends GhostError {
constructor(options) {
super(merge({
errorType: 'PasswordResetRequiredError',
statusCode: 401,
message: 'For security, you need to create a new password. An email has been sent to you with instructions!'
}, options));
}
},
UnhandledJobError: class UnhandledJobError extends GhostError {
constructor(options) {
super(merge({
errorType: 'UnhandledJobError',
message: 'Processed job threw an unhandled error',
level: 'critical'
}, options));
}
},
NoContentError: class NoContentError extends GhostError {
constructor(options) {
super(merge({
errorType: 'NoContentError',
statusCode: 204,
hideStack: true
}, options));
}
},
ConflictError: class ConflictError extends GhostError {
constructor(options) {
super(merge({
errorType: 'ConflictError',
statusCode: 409,
message: 'The server has encountered an conflict.'
}, options));
}
},
MigrationError: class MigrationError extends GhostError {
constructor(options) {
super(merge({
errorType: 'MigrationError',
message: 'An error has occurred applying a database migration.',
level: 'critical'
}, options));
}
}
};
module.exports = ghostErrors;
const ghostErrorsWithBase = Object.assign({}, ghostErrors, {GhostError});
module.exports.utils = {
serialize: utils.serialize.bind(ghostErrorsWithBase),
deserialize: utils.deserialize.bind(ghostErrorsWithBase),
isGhostError: utils.isGhostError.bind(ghostErrorsWithBase),
prepareStackForUser: utils.prepareStackForUser
};
const omit = require('lodash/omit');
const merge = require('lodash/merge');
const extend = require('lodash/extend');
const deepCopy = require('@stdlib/utils-copy');
const _private = {};
_private.serialize = function 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.'
};
}
};
_private.deserialize = function 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.
*/
_private.OAuthSerialize = function OAuthSerialize(err) {
const matchTable = {};
matchTable[this.NoPermissionError.name] = 'access_denied';
matchTable[this.MaintenanceError.name] = 'temporarily_unavailable';
matchTable[this.BadRequestError.name] = matchTable[this.ValidationError.name] = 'invalid_request';
matchTable.default = 'server_error';
return merge({
error: err.code || matchTable[err.name] || 'server_error',
error_description: err.message
}, omit(_private.serialize(err), ['detail', 'code']));
};
/**
* @description Deserialize oauth error format into GhostError instance.
* @param {Object} errorFormat
* @return {Error}
* @constructor
*/
_private.OAuthDeserialize = function OAuthDeserialize(errorFormat) {
try {
return new this[errorFormat.title || errorFormat.name || this.InternalServerError.name](_private.deserialize(errorFormat));
} catch (err) {
// CASE: you receive an OAuth formatted error, but the error prototype is unknown
return new this.InternalServerError(extend({
errorType: errorFormat.title || errorFormat.name
}, _private.deserialize(errorFormat)));
}
};
/**
* @description Serialize GhostError instance into jsonapi.org format.
* @param {Error} err
* @return {Object}
*/
_private.JSONAPISerialize = function 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.
* @param {Object} errorFormat
* @return {Error}
*/
_private.JSONAPIDeserialize = function JSONAPIDeserialize(errorFormat) {
errorFormat = errorFormat.errors && errorFormat.errors[0] || {};
let internalError;
try {
internalError = new this[errorFormat.title || errorFormat.name || this.InternalServerError.name](_private.deserialize(errorFormat));
} catch (err) {
// CASE: you receive a JSON format error, but the error prototype is unknown
internalError = new this.InternalServerError(extend({
errorType: errorFormat.title || errorFormat.name
}, _private.deserialize(errorFormat)));
}
if (errorFormat.source && errorFormat.source.pointer) {
internalError.property = errorFormat.source.pointer.split('/')[3];
}
return internalError;
};
exports.wrapStack = function wrapStack(err, internalErr) {
const extraLine = err.stack.split(/\n/g)[1];
const [firstLine, ...rest] = internalErr.stack.split(/\n/g);
return [firstLine, extraLine, ...rest].join('\n');
};
/**
* @description Serialize GhostError instance to error JSON format
*
* jsonapi.org error format:
*
* source: {
* parameter: URL query parameter (no support yet)
* pointer: HTTP body attribute
* }
*
* @see http://jsonapi.org/format/#errors
*
* @param {Error} err
* @param {Object} options { format: [String] (jsonapi || oauth) }
*/
exports.serialize = function serialize(err, options) {
options = options || {format: 'jsonapi'};
let errorFormat = {};
try {
if (options.format === 'jsonapi') {
errorFormat = _private.JSONAPISerialize.bind(this)(err);
} else {
errorFormat = _private.OAuthSerialize.bind(this)(err);
}
} catch (error) {
errorFormat.message = 'Something went wrong.';
}
// no need to sanitize the undefined values, on response send JSON.stringify get's called
return errorFormat;
};
/**
* @description Deserialize from error JSON format to GhostError instance
* @param {Object} errorFormat
*/
exports.deserialize = function deserialize(errorFormat) {
let internalError = {};
if (errorFormat.errors) {
internalError = _private.JSONAPIDeserialize.bind(this)(errorFormat);
} else {
internalError = _private.OAuthDeserialize.bind(this)(errorFormat);
}
return internalError;
};
/**
* @description Replace the stack with a user-facing one
* @params {Error} err
* @returns {Error} Clone of the original error with a user-facing stack
*/
exports.prepareStackForUser = function prepareStackForUser(error) {
let stackbits = error.stack.split(/\n/);
// We build this up backwards, so we always insert at position 1
if (process.env.NODE_ENV === 'production' || error.hideStack) {
stackbits.splice(1, stackbits.length - 1);
} else {
// Clearly mark the strack trace
stackbits.splice(1, 0, `Stack Trace:`);
}
// Add in our custom context and help methods
if (error.help) {
stackbits.splice(1, 0, `${error.help}`);
}
if (error.context) {
stackbits.splice(1, 0, `${error.context}`);
}
// @NOTE: would be a good idea to swap out the cloning implementation with native
// `structuredClone` one once we use Node v17 or higher. Before making an
// upgrade make sure structuredClone does a full copy of all properties
// present on a custom error (see issue: https://github.com/ungap/structured-clone/issues/12)
const errorClone = deepCopy(error);
errorClone.stack = stackbits.join('\n');
return errorClone;
};
/**
* @description Check whether an error instance is a GhostError.
*/
exports.isGhostError = function isGhostError(err) {
const errorName = this.GhostError.name;
const legacyErrorName = 'IgnitionError';
const recursiveIsGhostError = function recursiveIsGhostError(obj) {
// no super constructor available anymore
if (!obj || !obj.name) {
return false;
}
if (obj.name === errorName || obj.name === legacyErrorName) {
return true;
}
return recursiveIsGhostError(Object.getPrototypeOf(obj));
};
return recursiveIsGhostError(err.constructor);
};