Skip to content

Instantly share code, notes, and snippets.

@jhsware
Created October 26, 2021 09:45
Show Gist options
  • Select an option

  • Save jhsware/d11bfde94b02297426d637e9b55a64b2 to your computer and use it in GitHub Desktop.

Select an option

Save jhsware/d11bfde94b02297426d637e9b55a64b2 to your computer and use it in GitHub Desktop.
/**
* Common ancestor of all operational errors allowing
* for more catch all checks.
*/
class OperationalError extends Error {
constructor(name, statusCode, type, message, details, err) {
super(message);
this.name = name;
this.statusCode = statusCode;
this.type = type;
this.details = details;
this.err = err;
}
}
/**
* Error for recoverable programmer errors. This tells the consuming
* code that it shouldn't crash the application.
*/
class RecoverableError extends Error {
constructor({ message = "We encountered an error in our code.", err }) {
super(message);
this.name = "RecoverableError";
this.err = err;
}
}
/**
* AuthError should be handled by the frontend
* api client.
*/
class AuthError extends OperationalError {
constructor({ type, message, details, err }) {
super("AuthError", 401, type, message, details, err);
}
}
/**
* All errors of type EndpointError must be
* handled by the frontend code that calls the
* actual endpoint.
*/
class EndpointError extends OperationalError {
// Errors that must be handled by frontend
constructor({ type, statusCode, message, details, err }) {
super("EndpointError", statusCode, type, message, details, err);
}
}
/**
* All errors of type "TheExternalApiError" are known problems that happened when
* calling out external api
*/
class TheExternalApiError extends OperationalError {
constructor({ type, message, details, err }) {
super("TheExternalApiError", 503, type, message, details, err);
}
}
function isOperationalOrRecoverableError(err) {
return err instanceof OperationalError || err instanceof RecoverableError;
}
// Find a wrapped programmer error
function getOrigProgrammerError(error) {
const { err } = error;
let result;
if (isOperationalOrRecoverableError(err)) {
// .err is an OperationalError so need to check if
// that in turn constains a wrapped error
result = getOrigProgrammerError(err);
} else if (err instanceof Error) {
result = err;
}
return result;
}
// Find the innermost wrapped operational or recoverable error
function getMostSignificantError(error) {
const { err } = error;
if (isOperationalOrRecoverableError(err)) {
// If we have wrapped an Operational or Recoverable error
// that is the cause and what should be logged
return getMostSignificantError(err);
}
return error;
}
function _formatErrorMsg(name, type, message) {
return `(${name}${type ? "/" + type : ""}) ${message}`;
}
function errorHandler(err, req, res, next) {
if (err instanceof AuthError) {
// Simple auth errors
console.warn(err);
// Add error details if provided for debugging
if (err.details) console.debug(err.details);
} else if (err instanceof EndpointError) {
/**
* An EndpointError can be caused in four ways:
* 1. Thrown by endpoint handler but there was no error in the code
* 2. Thrown by endpoint handler due to an error in the code
* 3. An [Name]ApiError was caught which wasn't caused by an error in the code
* 4. An [Name]ApiError was caught which in turn was caused by an error in the code
*
* Alternative 2 & 4 will have a wrapped programmer error.
*/
// Since EndpointErrors can wrap other errors we want to make sure we console the most significant error
// and the underlying programmer error if one exists
const logErr = getMostSignificantError(err);
const progErr = getOrigProgrammerError(err);
if (progErr === undefined) {
// If the EndpointError wasn't caused by a programmer error we only need to inform about it.
console.info(logErr);
} else {
// If it was a programmer error it needs to be logged and fixed.
console.error(
progErr, // this provides a stack trace for the original error that needs to be fixed
_formatErrorMsg(logErr.name, logErr.type, logErr.message)
);
}
// Add error details if provided for debugging.
if (logErr.details) console.debug(logErr.details);
} else if (isOperationalOrRecoverableError(err)) {
// These errors should always be wrapped in EndpointError so we console the
// outer most error to know what we have missed in our endpoint code
console.warn(
"This error should be wrapped in an EndpointError for consistency"
);
console.error(
getOrigProgrammerError(err), // this provides a stack trace for the original error that needs to be fixed
_formatErrorMsg(err.name, err.type, err.message)
);
// Add error details if provided for debugging
if (err.details) console.debug(err.details);
} else {
// All other passed errors should always be logged as error
console.error(err);
}
if (res.headersSent) {
return next(err);
}
const error = {
type: err.type || "error",
message:
err.message || "An unknown error occured. Please contact IT-support.",
statusCode: err.statusCode,
};
return res.status(err.statusCode !== undefined ? err.statusCode : 500).send({
error,
});
}
module.exports = {
errorHandler,
OperationalError,
RecoverableError,
AuthError,
EndpointError,
TheExternalApiError,
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment