Skip to content

Commit

Permalink
Breaking: require()/required checks empty strings
Browse files Browse the repository at this point in the history
  • Loading branch information
nzakas committed Jul 24, 2020
1 parent 08abc44 commit 856c8b8
Show file tree
Hide file tree
Showing 3 changed files with 145 additions and 8 deletions.
19 changes: 18 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,14 @@ const username = env.get("USERNAME");
// read a variable and use a default if empty
const username = env.get("USERNAME", "humanwhocodes");

// determine if a variable exists
const username = env.has("USERNAME");

// read the first found variable and use a default is empty
const username = env.first(["USERNAME", "USERNAME2"], "humanwhocodes");

// read a variable and throw an error if it doesn't exist
// or is an empty string
const username = env.require("USERNAME");
```

Expand All @@ -85,13 +89,26 @@ To retrieve more than one required environment variable at one time, you can use
```js
const env = new Env();

// throws if variables are undefined or an empty string
const {
CLIENT_ID,
CLIENT_SECRET
} = env.required;
```

In this example, an error is thrown if either `CLIENT_ID` or `CLIENT_SECRET` is missing. The `required` property is a proxy object that throws an error whenever you attempt to access a property that doesn't exist.
In this example, an error is thrown if either `CLIENT_ID` or `CLIENT_SECRET` is missing or an empty string. The `required` property is a proxy object that throws an error whenever you attempt to access a property that doesn't exist.

If you don't want to throw an error for environment variables containing an empty string, use the `exists` property:

```js
const env = new Env();

// throws only if variables are not defined
const {
CLIENT_ID,
CLIENT_SECRET
} = env.exists;
```

You can also specify an alternate object to read variables from. This can be useful for testing or in the browser (where there is no environment variable to read from by default):

Expand Down
65 changes: 60 additions & 5 deletions src/env.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,16 @@ function keyNotFound(key) {
throw new Error(`Required environment variable '${key}' not found.`);
}

/**
* Throws an error saying that the key was an empty string.
* @param {string} key The key to report as an empty string.
* @returns {void}
* @throws {Error} Always.
*/
function emptyString(key) {
throw new Error(`Required environment variable '${key}' is an empty string.`);
}

//-----------------------------------------------------------------------------
// Main
//-----------------------------------------------------------------------------
Expand Down Expand Up @@ -66,6 +76,16 @@ export class Env {
return (key in this.source) ? this.source[key] : defaultValue;
}

/**
* Determines if a given environment variable exists.
* @param {string} key The environment variable name to check.
* @returns {boolean} True if the environment variable exists,
* false if not.
*/
has(key) {
return key in this.source;
}

/**
* Retrieves the first environment variable found in a list of environment
* variable names.
Expand Down Expand Up @@ -93,15 +113,18 @@ export class Env {

/**
* Retrieves an environment variable. If the environment variable does
* not exist, then it throws an error.
* not exist or is an empty string, then it throws an error.
* @param {string} key The environment variable name to retrieve.
* @returns {string?} The environment variable value.
* @throws {Error} When the environment variable doesn't exist.
* @throws {Error} When the environment variable doesn't exist or is an
* empty string.
*/
require(key) {
const value = this.get(key);
if (value === null) {
keyNotFound(key);
} else if (value === "") {
throw emptyString(key);
} else {
return value;
}
Expand All @@ -112,11 +135,43 @@ export class Env {
* automatically throw errors when an undefined environment variable
* is accessed.
*/
get exists() {

const existsProxy = new Proxy(this.source, {
get(target, key) {
if (key in target) {
return target[key];
}

keyNotFound(key);
}
});

// redefine this property as a data attribute
Object.defineProperty(this, "exists", {
value: existsProxy,
writable: false,
enumerable: false,
configurable: false
});

return existsProxy;
}

/**
* Lazy-loading property containing a proxy that can be used to
* automatically throw errors when an undefined or empty string
* environment variable is accessed.
*/
get required() {

const proxy = new Proxy(this.source, {
const requiredProxy = new Proxy(this.source, {
get(target, key) {
if (key in target) {
if (target[key] === "") {
emptyString(key);
}

return target[key];
}

Expand All @@ -126,13 +181,13 @@ export class Env {

// redefine this property as a data attribute
Object.defineProperty(this, "required", {
value: proxy,
value: requiredProxy,
writable: false,
enumerable: false,
configurable: false
});

return proxy;
return requiredProxy;
}

}
69 changes: 67 additions & 2 deletions tests/env.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,24 @@ describe("Env", () => {

});

describe("has()", () => {

const source = {
USERNAME: "humanwhocodes"
};

it("should return true when the environment variable exists", () => {
const env = new Env(source);
assert.isTrue(env.has("USERNAME"));
});

it("should return false when the environment variable doesn't exist", () => {
const env = new Env(source);
assert.isFalse(env.has("OTHER"));
});

});

describe("first()", () => {

const source = {
Expand Down Expand Up @@ -119,7 +137,8 @@ describe("Env", () => {
describe("require()", () => {

const source = {
USERNAME: "humanwhocodes"
USERNAME: "humanwhocodes",
OTHER: ""
};

it("should get an environment variable when it exists", () => {
Expand All @@ -136,12 +155,50 @@ describe("Env", () => {
}, /PASSWORD/);
});

it("should throw an error when the environment variable is an empty string", () => {
const env = new Env(source);

assert.throws(() => {
env.require("OTHER");
}, /OTHER/);
});

});

describe("exists", () => {

const source = {
USERNAME: "humanwhocodes",
OTHER: ""
};

it("should get an environment variable when it exists", () => {
const env = new Env(source);
const { USERNAME: value } = env.exists;
assert.strictEqual(value, source.USERNAME);
});

it("should get an environment variable when it is an empty string", () => {
const env = new Env(source);
const { OTHER: value } = env.exists;
assert.strictEqual(value, source.OTHER);
});

it("should throw an error when the environment variable doesn't exist", () => {
const env = new Env(source);

assert.throws(() => {
env.exists.PASSWORD;
}, /PASSWORD/);
});

});

describe("required", () => {

const source = {
USERNAME: "humanwhocodes"
USERNAME: "humanwhocodes",
OTHER: ""
};

it("should get an environment variable when it exists", () => {
Expand All @@ -158,6 +215,14 @@ describe("Env", () => {
}, /PASSWORD/);
});

it("should throw an error when the environment variable is an empty string", () => {
const env = new Env(source);

assert.throws(() => {
env.required.OTHER;
}, /OTHER/);
});

});

});

0 comments on commit 856c8b8

Please sign in to comment.