Artifact Content
Not logged in

Artifact ce6c2cd75c517d6c4b573296596b8aa23ccf0d9c:


import InvalidJsonBodyError from '../errors/invalid-json-body-error'
import { constantCase } from 'change-case'
import { create as createLogger } from '../common/log'
import { Config as ConfigSchemaTyping } from '../schemas/Config'
const log = createLogger('config')
const schema = require('../schemas/Config.json')
const {
  extractDefaultsFromSchema
} = require('../lib/utils')
import Ajv = require('ajv')

const ajv = new Ajv()

const ENV_PREFIX = 'CONNECTOR_'

const BOOLEAN_VALUES = {
  '1': true,
  'true': true,
  '0': false,
  'false': false,
  '': false
}

export default class Config extends ConfigSchemaTyping {
  // TODO: These fields are already all defined in the config schema, however
  //   they are defined as optional and as a result, TypeScript thinks that they
  //   may not be set. However, when we construct a new Config instance, we load
  //   the defaults from the schema, so these *will* always be set. These
  //   declarations make TypeScript happy.
  public broadcastCurves!: boolean
  public store!: string
  public quoteExpiry!: number
  public routeExpiry!: number
  public minMessageWindow!: number
  public maxHoldTime!: number
  public routeBroadcastInterval!: number

  protected _validate: Ajv.ValidateFunction
  protected _validateAccount: Ajv.ValidateFunction

  constructor () {
    super()

    this.loadDefaults()

    this._validate = ajv.compile(schema)
    this._validateAccount = ajv.compile(schema.properties.accounts.additionalProperties)
  }

  loadDefaults () {
    Object.assign(this, extractDefaultsFromSchema(schema))
  }

  loadFromEnv (env?: NodeJS.ProcessEnv) {
    if (!env) {
      env = process.env
    }

    // Copy all env vars starting with ENV_PREFIX into a set so we can check off
    // the ones we recognize and warn the user about any we don't recognize.
    const unrecognizedEnvKeys = new Set(
      Object.keys(env).filter(key => key.startsWith(ENV_PREFIX))
    )

    const config = {}
    for (let key of Object.keys(schema.properties)) {
      const envKey = ENV_PREFIX + constantCase(key)
      const envValue = env[envKey]

      unrecognizedEnvKeys.delete(envKey)

      if (typeof envValue === 'string') {
        switch (schema.properties[key].type) {
          case 'string':
            config[key] = envValue
            break
          case 'object':
          case 'array':
            try {
              config[key] = JSON.parse(envValue)
            } catch (err) {
              log.warn('unable to parse config. key=%s', envKey)
            }
            break
          case 'boolean':
            config[key] = BOOLEAN_VALUES[envValue] || false
            break
          case 'integer':
          case 'number':
            config[key] = Number(envValue)
            break
          default:
            throw new TypeError('Unknown JSON schema type: ' + schema.properties[key].type)
        }
      }
    }

    for (const key of unrecognizedEnvKeys) {
      log.warn('unrecognized environment variable. key=%s', key)
    }

    this.validate(config)

    Object.assign(this, config)
  }

  loadFromOpts (opts: object) {
    this.validate(opts)

    Object.assign(this, opts)
  }

  validate (config: object) {
    if (!this._validate(config)) {
      const firstError = this._validate.errors && this._validate.errors[0]
        ? this._validate.errors[0]
        : { message: 'unknown validation error', dataPath: '' }
      throw new InvalidJsonBodyError('config failed to validate. error=' + firstError.message + ' dataPath=' + firstError.dataPath, this._validate.errors || [])
    }
  }

  validateAccount (id: string, accountInfo: any) {
    if (!this._validateAccount(accountInfo)) {
      throw new InvalidJsonBodyError('account config failed to validate. id=' + id, this._validateAccount.errors || [])
    }
  }

  get (key: string) {
    return this[key]
  }
}