@tryghost/errors
Advanced tools
+319
| "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]; | ||
| }); | ||
| } | ||
| } | ||
| } |
+45
| "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 | ||
| }; |
+204
| "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"); | ||
| } | ||
| ; |
+299
| 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 | ||
| }; |
+14
| 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 | ||
| }; |
+174
| 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 | ||
| }; |
+301
| 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]; | ||
| }); | ||
| } | ||
| } | ||
| } |
+14
| 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 | ||
| }; |
+238
| 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" | ||
| } |
-1
| module.exports = require('./lib/errors'); |
-339
| 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 | ||
| }; |
-237
| 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); | ||
| }; |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
73308
231.01%2
-33.33%28
366.67%1973
271.56%10
150%2
Infinity%- Removed
- Removed