184 lines
5.4 KiB
JavaScript
184 lines
5.4 KiB
JavaScript
'use strict'
|
|
|
|
const EnvVarError = require('./env-error')
|
|
const base64Regex = /^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{4}|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)$/
|
|
|
|
/**
|
|
* Returns an Object that contains functions to read and specify the format of
|
|
* the variable you wish to have returned
|
|
* @param {Object} container Encapsulated container (e.g., `process.env`).
|
|
* @param {String} varName Name of the requested property from `container`.
|
|
* @param {*} defValue Default value to return if `varName` is invalid.
|
|
* @param {Object} extraAccessors Extra accessors to install.
|
|
* @return {Object}
|
|
*/
|
|
module.exports = function getVariableAccessors (container, varName, extraAccessors, logger) {
|
|
let isBase64 = false
|
|
let isRequired = false
|
|
let defValue
|
|
let example
|
|
|
|
const builtInAccessors = require('./accessors/index')
|
|
|
|
/**
|
|
* Logs the given string using the provided logger
|
|
* @param {String} str
|
|
* @param {String} str
|
|
*/
|
|
function log (str) {
|
|
logger(varName, str)
|
|
}
|
|
|
|
/**
|
|
* Throw an error with a consistent type/format.
|
|
* @param {String} value
|
|
*/
|
|
function raiseError (value, msg) {
|
|
let errMsg = `"${varName}" ${msg}`
|
|
|
|
if (value) {
|
|
errMsg = `${errMsg}`
|
|
}
|
|
|
|
if (example) {
|
|
errMsg = `${errMsg}. An example of a valid value would be: ${example}`
|
|
}
|
|
|
|
throw new EnvVarError(errMsg)
|
|
}
|
|
|
|
/**
|
|
* Returns an accessor wrapped by error handling and args passing logic
|
|
* @param {Function} accessor
|
|
*/
|
|
function generateAccessor (accessor) {
|
|
return function () {
|
|
let value = container[varName]
|
|
|
|
log(`will be read from the environment using "${accessor.name}" accessor`)
|
|
|
|
if (typeof value === 'undefined') {
|
|
if (typeof defValue === 'undefined' && isRequired) {
|
|
log('was not found in the environment, but is required to be set')
|
|
// Var is not set, nor is a default. Throw an error
|
|
raiseError(undefined, 'is a required variable, but it was not set')
|
|
} else if (typeof defValue !== 'undefined') {
|
|
log(`was not found in the environment, parsing default value "${defValue}" instead`)
|
|
value = defValue
|
|
} else {
|
|
log('was not found in the environment, but is not required. returning undefined')
|
|
// return undefined since variable is not required and
|
|
// there's no default value provided
|
|
return undefined
|
|
}
|
|
}
|
|
|
|
if (isRequired) {
|
|
log('verifying variable value is not an empty string')
|
|
// Need to verify that required variables aren't just whitespace
|
|
if (value.trim().length === 0) {
|
|
raiseError(undefined, 'is a required variable, but its value was empty')
|
|
}
|
|
}
|
|
|
|
if (isBase64) {
|
|
log('verifying variable is a valid base64 string')
|
|
if (!value.match(base64Regex)) {
|
|
raiseError(value, 'should be a valid base64 string if using convertFromBase64')
|
|
}
|
|
log('converting from base64 to utf8 string')
|
|
value = Buffer.from(value, 'base64').toString()
|
|
}
|
|
|
|
const args = [value].concat(Array.prototype.slice.call(arguments))
|
|
|
|
try {
|
|
log(`passing value "${value}" to "${accessor.name}" accessor`)
|
|
|
|
const result = accessor.apply(
|
|
accessor,
|
|
args
|
|
)
|
|
|
|
log(`parsed successfully, returning ${result}`)
|
|
return result
|
|
} catch (error) {
|
|
raiseError(value, error.message)
|
|
}
|
|
}
|
|
}
|
|
|
|
const accessors = {
|
|
/**
|
|
* Instructs env-var to first convert the value of the variable from base64
|
|
* when reading it using a function such as asString()
|
|
*/
|
|
convertFromBase64: function () {
|
|
log('marking for base64 conversion')
|
|
isBase64 = true
|
|
|
|
return accessors
|
|
},
|
|
|
|
/**
|
|
* Set a default value for the variable
|
|
* @param {String} value
|
|
*/
|
|
default: function (value) {
|
|
if (typeof value === 'number') {
|
|
defValue = value.toString()
|
|
} else if (Array.isArray(value) || (typeof value === 'object' && value !== null)) {
|
|
defValue = JSON.stringify(value)
|
|
} else if (typeof value !== 'string') {
|
|
throw new EnvVarError('values passed to default() must be of Number, String, Array, or Object type')
|
|
} else {
|
|
defValue = value
|
|
}
|
|
|
|
log(`setting default value to "${defValue}"`)
|
|
|
|
return accessors
|
|
},
|
|
|
|
/**
|
|
* Ensures a variable is set in the given environment container. Throws an
|
|
* EnvVarError if the variable is not set or a default is not provided
|
|
* @param {Boolean} required
|
|
*/
|
|
required: function (required) {
|
|
if (typeof required === 'undefined') {
|
|
log('marked as required')
|
|
// If no value is passed assume that developer means "true"
|
|
// This is to retain support legacy usage (and intuitive)
|
|
isRequired = true
|
|
} else {
|
|
log(`setting required flag to ${required}`)
|
|
isRequired = required
|
|
}
|
|
|
|
return accessors
|
|
},
|
|
|
|
/**
|
|
* Set an example value for this variable. If the variable value is not set
|
|
* or is set to an invalid value this example will be show in error output.
|
|
* @param {String} example
|
|
*/
|
|
example: function (ex) {
|
|
example = ex
|
|
|
|
return accessors
|
|
}
|
|
}
|
|
|
|
// Attach accessors, and extra accessors if provided.
|
|
Object.entries({
|
|
...builtInAccessors,
|
|
...extraAccessors
|
|
}).forEach(([name, accessor]) => {
|
|
accessors[name] = generateAccessor(accessor)
|
|
})
|
|
|
|
return accessors
|
|
}
|