UNPKG

@small-tech/node-pebble

Version:

A Node.js wrapper for Let’s Encrypt’s Pebble (“a small RFC 8555 ACME test server not suited for a production certificate authority”).

169 lines (142 loc) 5.18 kB
/** Throws Extensible basic error creation and throwing methods that use symbols and predefined yet configurable lists of errors to make working with errors safer (fewer magic strings) and DRYer (Don't Repeat Yourself). Copyright © 2020-2021 Aral Balkan, Small Technology Foundation. License: AGPLv3 or later. */ import fs from 'fs' /** Extends basic Error class with a symbol representing the error. */ class SymbolicError extends Error { symbol = null } /** @typedef { ( objectName: string , hint?: string ) => string } hintedErrorString */ /** Composes an error string that includes an optional hint. @type { hintedErrorString } */ const hintedErrorString = (str, hint) => hint ? `${str} (${hint}).` : `${str}.` export default class Throws { // Define common global errors. These are mixed into a local ERRORS object // if it exists on the object the mixin() method is called with a reference to. static ERRORS = { /** @type { hintedErrorString } */ [Symbol.for('UndefinedOrNullError')]: (object, hint) => hintedErrorString(`${object} must not be undefined or null`, hint), /** @type { hintedErrorString } */ [Symbol.for('UndefinedError')]: (object, hint) => hintedErrorString(`${object} must not be undefined`, hint), /** @type { hintedErrorString } */ [Symbol.for('NullError')]: (object, hint) => hintedErrorString(`${object} must not be null`, hint), /** @type { hintedErrorString } */ [Symbol.for('ArgumentError')]: (object, hint) => hintedErrorString(`incorrect value for argument ${object}`, hint), /** @type { hintedErrorString } */ [Symbol.for('MustBeInstantiatedViaAsyncFactoryMethodError')]: (object, _hint) => hintedErrorString(`Use the static getInstanceAsync() method to initialise ${object}`), /** @type { hintedErrorString } */ [Symbol.for('SingletonConstructorIsPrivateError')]: (object, _hint) => hintedErrorString(`{object} is a singleton; its constructor is private. Do not instantiate using new ${object}()`), /** @type { hintedErrorString } */ [Symbol.for('StaticClassCannotBeInstantiatedError')]: (object, _hint) => hintedErrorString(`${object} is a static class. You cannot instantiate it using new ${object}()`), /** @type { hintedErrorString } */ [Symbol.for('ReadOnlyAccessorError')]: (object, _hint) => hintedErrorString(`${object} is a read-only property`) } /** @param { Object } [customErrors={}] */ constructor (customErrors = {}) { this.errors = Object.assign(customErrors, Throws.ERRORS) } /** @param { symbol } symbol @param { ...any } args @returns { string } */ messageFor (symbol, ...args) { return this.errors[symbol](...args) } /** @param { symbol } symbol @param { ...any } args @returns { SymbolicError } */ createError (symbol, ...args) { const errorMessage = this.messageFor(symbol, ...args) const error = new SymbolicError(errorMessage) Error.captureStackTrace(error, this.error) error.name = symbol.description error.symbol = symbol return error } /** @param { boolean } condition @param { symbol } errorSymbol @param { ...any } args */ if (condition, errorSymbol, ...args) { if (condition) { this.error(errorSymbol, ...args) } } /** @param { string } [parameterName] */ ifMissing (parameterName) { // Attempt to automatically get the argument name even if parameterName was not // manually specified in the code. // // Thanks to Christian Bundy (https://social.coop/@christianbundy/) for the // technique (https://gist.github.com/christianbundy/4c3c29b9f1a52384d7e8a51a956227c2) // TODO: Generalise and use in the other parameter errors if it holds up to testing. const copy = Error.prepareStackTrace; Error.prepareStackTrace = (_, stack) => stack; const stack = /** @type { NodeJS.CallSite[] } */ (/** @type { unknown } */ (new Error().stack)) Error.prepareStackTrace = copy const fileName = stack[1].getFileName() const lineNumber = stack[1].getLineNumber() - 1 const columnNumber = stack[1].getColumnNumber() - 1 const line = fs.readFileSync(fileName, "utf8").split("\n")[lineNumber] const partialLine = line.slice(0, columnNumber) const groups = partialLine.match(/(\)|,)\s*?(([a-z]|[A-Z]|[0-9])+?)\s?=\s?throws\.$/) let argumentName = null if (groups !== null) { argumentName = groups[2] } this.error(Symbol.for('UndefinedOrNullError'), parameterName || argumentName || '<unknown>') } /** @param { any } object @param { string } objectName */ ifUndefinedOrNull (object, objectName) { if (object == undefined) { this.error(Symbol.for('UndefinedOrNullError'), objectName) } } /** @param { any } object @param { string } objectName */ ifUndefined (object, objectName) { if (object === undefined) { this.error(Symbol.for('UndefinedError'), objectName) } } /** @param { symbol } symbol @param { ...any } args */ error (symbol, ...args) { throw this.createError(symbol, ...args) } }