adapt-authoring-errors/lib/ErrorsModule.js

import { AbstractModule } from 'adapt-authoring-core'
import AdaptError from './AdaptError.js'
import fs from 'fs/promises'
import { glob } from 'glob'

/**
 * Module to store a global reference to all errors defined in the system. Errors are accessed from here and thrown appropriately elsewhere.
 * @memberof errors
 * @extends {AbstractModule}
 */
class ErrorModule extends AbstractModule {
  /** @override */
  async init () {
    /**
     * A key/value store of all errors defined in the system. Errors are accessed via the human-readable error code for better readability when thrown in code.
     * @type {object}
     * @see {AdaptError}
     */
    this.errors = await this.loadErrors()
    this.app.errors = this.errors
  }

  /**
   * Loads all errors defined in Adapt module dependencies
   * @returns {Promise}
   */
  async loadErrors () {
    const errorDefs = {}
    await Promise.all(Object.values(this.app.dependencies).map(async d => {
      const files = await glob('errors/*.json', { cwd: d.rootDir, absolute: true })
      await Promise.all(files.map(async f => {
        try {
          const contents = JSON.parse(await fs.readFile(f))
          Object.entries(contents).forEach(([k, v]) => {
            if (errorDefs[k]) return this.log('warn', `error code '${k}' already defined`)
            errorDefs[k] = v
          })
        } catch (e) {
          this.log('error', e.message)
        }
      }))
    }))
    return Object.entries(errorDefs)
      .sort()
      .reduce((m, [k, { description, statusCode, data }]) => {
        return Object.defineProperty(m, k, {
          get: () => {
            const metadata = { description }
            if (data) metadata.data = data
            return new AdaptError(k, statusCode, metadata)
          },
          enumerable: true
        })
      }, {})
  }
}

export default ErrorModule