From 85b1639e9555029c173caabfdf7cb80d5cbe3412 Mon Sep 17 00:00:00 2001 From: Evan Sherwood Date: Mon, 7 May 2018 23:50:47 -0700 Subject: [PATCH] Add requiredWhen to validator spec closes #78 --- README.md | 1 + envalid.d.ts | 5 +++++ example/env.js | 8 +++++++- index.js | 6 ++++++ tests/envalid.ts | 8 ++++++-- tests/test_basics.js | 19 ++++++++++++++++++- 6 files changed, 43 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 0de1ef1..699742a 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,7 @@ Each validation function accepts an (optional) object with the following attribu * `desc` - A string that describes the env var. * `example` - An example value for the env var. * `docs` - A url that leads to more detailed documentation about the env var. +* `requiredWhen` - A function that receives the raw env vars and must return a boolean. If true, this env var will be required. If false, this env var will be optional. ## Custom validators diff --git a/envalid.d.ts b/envalid.d.ts index 0ddd33e..1e54d97 100644 --- a/envalid.d.ts +++ b/envalid.d.ts @@ -24,6 +24,11 @@ interface Spec { * A url that leads to more detailed documentation about the env var. */ docs?: string + /** + * A function that receives the full raw environment and returns a boolean + * indicating if this field is required or optional. + */ + requiredWhen?: (env: any) => boolean } interface ValidatorSpec extends Spec { diff --git a/example/env.js b/example/env.js index 9afd901..3b2db15 100644 --- a/example/env.js +++ b/example/env.js @@ -6,5 +6,11 @@ module.exports = envalid.cleanEnv(process.env, { // For this example, the MESSAGE env var will be read from the .env // file in this directory (so the default value won't be used): - MESSAGE: envalid.str({ default: 'Hello, world' }) + MESSAGE: envalid.str({ default: 'Hello, world' }), + + USE_TLS: envalid.bool({ defualt: false }), + + // If USE_TLS is true, this will throw if no value is provided (required) + // If USE_TLS is false, this will not throw if no value is provided (optional) + TLS_KEY_PATH: envalid.str({ requiredWhen: env => !!env.USE_TLS }) }) diff --git a/index.js b/index.js index 8589b9f..fd563b8 100644 --- a/index.js +++ b/index.js @@ -105,6 +105,12 @@ function cleanEnv(inputEnv, specs = {}, options = {}) { throw new EnvMissingError(formatSpecDescription(spec)) } + if (typeof spec.requiredWhen === 'function') { + if (!spec.requiredWhen(env)) { + continue + } + } + if (rawValue === undefined) { if (!usingFalsyDefault) { throw new EnvMissingError(formatSpecDescription(spec)) diff --git a/tests/envalid.ts b/tests/envalid.ts index 47fc9f2..968c881 100644 --- a/tests/envalid.ts +++ b/tests/envalid.ts @@ -38,7 +38,9 @@ const spec = { json: json({ devDefault: { foo: 'bar' } }), - url: url(), + url: url({ + requiredWhen: () => true + }), email: email({ example: 'example', docs: 'http://example.com' @@ -62,7 +64,9 @@ const inferredEnv = cleanEnv( json: json({ devDefault: { foo: 'bar' } }), - url: url(), + url: url({ + requiredWhen: () => true + }), email: email({ example: 'example', docs: 'http://example.com' diff --git a/tests/test_basics.js b/tests/test_basics.js index 0f93984..6de9bd1 100644 --- a/tests/test_basics.js +++ b/tests/test_basics.js @@ -1,5 +1,5 @@ const { createGroup, assert } = require('painless') -const { cleanEnv, EnvError, EnvMissingError, str, num, testOnly } = require('..') +const { cleanEnv, EnvError, EnvMissingError, str, num, bool, testOnly } = require('..') const { assertPassthrough } = require('./utils') const test = createGroup() const makeSilent = { reporter: null } @@ -205,3 +205,20 @@ test('testOnly', () => { EnvMissingError ) }) + +test('conditionally required env', () => { + // Throws when the env var isn't in the given choices: + const spec = { + FOO: str({ requiredWhen: rawEnv => !!rawEnv.REQUIRE_FOO }), + REQUIRE_FOO: bool({ default: false }) + } + + // FOO not required when REQUIRE_FOO is false + assert.deepEqual(cleanEnv({}, spec, makeSilent), { REQUIRE_FOO: false }) + + // FOO is required when REQUIRE_FOO is true and FOO is not provided + assert.throws(() => cleanEnv({ REQUIRE_FOO: true }, spec, makeSilent), EnvMissingError) + + // Works fine when REQUIRE_FOO is true and FOO is satisfied + assertPassthrough({ REQUIRE_FOO: true, FOO: 'bar' }, spec) +})