Skip to content

Commit

Permalink
feat!: Allow custom error types to be used.
Browse files Browse the repository at this point in the history
  • Loading branch information
nzakas committed Sep 4, 2023
1 parent 94d8244 commit b2715c9
Show file tree
Hide file tree
Showing 4 changed files with 4,739 additions and 261 deletions.
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,27 @@ const username = env.get("USERNAME");
const password = env.require("PASSWORD");
```

Last, you can specify custom error classes to throw for the two different types of errors: key not found and empty string. Each error constructor is passed the string key that caused the error. Here's an example:

```js
class MyKeyNotFoundError extends Error {
constructor(key) {
super(`Yo this key isn't here: ${key}.`);
}
}

class MyEmptyStringError extends Error {
constructor(key) {
super(`Hey! This key is empty: ${key}.`);
}
}

Env.KeyNotFoundError = MyKeyNotFoundError;
Env.EmptyStringError = MyEmptyStringError;
```

Note that changing the error classes affects all instances of `Env`, both those that are already created and those that will be created within the same lifetime.

## Developer Setup

1. Fork the repository
Expand Down
88 changes: 69 additions & 19 deletions src/env.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,24 +28,62 @@ const defaultEnvSource = (() => {

})();

//-----------------------------------------------------------------------------
// Errors
//-----------------------------------------------------------------------------

/**
* Throws an error saying that the key was not found.
* @param {PropertyKey} key The key to report as missing.
* @returns {void}
* @throws {Error} Always.
* The error thrown when a required key is not found.
*/
function keyNotFound(key) {
throw new Error(`Required environment variable '${String(key)}' not found.`);
export class EnvKeyNotFoundError extends Error {

/**
* Creates a new instance.
* @param {PropertyKey} key The key that wasn't found.
*/
constructor(key) {

super(`Required environment variable '${String(key)}' not found.`);

/**
* The key that wasn't found.
* @type {PropertyKey}
*/
this.key = key;

/**
* Easily identifiable name for this error type.
* @type {string}
*/
this.name = "EnvKeyNotFoundError";
}
}

/**
* Throws an error saying that the key was an empty string.
* @param {PropertyKey} key The key to report as an empty string.
* @returns {void}
* @throws {Error} Always.
* The error thrown when a required key is present but is an empty string.
*/
function emptyString(key) {
throw new Error(`Required environment variable '${String(key)}' is an empty string.`);
export class EnvEmptyStringError extends Error {

/**
* Creates a new instance.
* @param {PropertyKey} key The key that wasn't found.
*/
constructor(key) {

super(`Required environment variable '${String(key)}' is an empty string.`);

/**
* The key that wasn't found.
* @type {PropertyKey}
*/
this.key = key;

/**
* Easily identifiable name for this error type.
* @type {string}
*/
this.name = "EnvEmptyStringError";
}
}

//-----------------------------------------------------------------------------
Expand Down Expand Up @@ -143,9 +181,9 @@ export class Env {
require(key) {
const value = this.get(key);
if (typeof value === "undefined") {
keyNotFound(key);
throw new Env.KeyNotFoundError(key);
} else if (value === "") {
emptyString(key);
throw new Env.EmptyStringError(key);
} else {
return value;
}
Expand All @@ -164,9 +202,9 @@ export class Env {

const value = this.first(keys);
if (typeof value === "undefined") {
keyNotFound(`[${keys}]`);
throw new Env.KeyNotFoundError(`[${keys}]`);
} else if (value === "") {
emptyString(`[${keys}]`);
throw new Env.EmptyStringError(`[${keys}]`);
} else {
return value;
}
Expand All @@ -186,7 +224,7 @@ export class Env {
return target[key];
}

keyNotFound(key);
throw new Env.KeyNotFoundError(key);
}
});

Expand Down Expand Up @@ -214,13 +252,13 @@ export class Env {
get(target, key) {
if (key in target) {
if (target[key] === "") {
emptyString(key);
throw new Env.EmptyStringError(key);
}

return target[key];
}

keyNotFound(key);
throw new Env.KeyNotFoundError(key);
}
});

Expand All @@ -237,3 +275,15 @@ export class Env {
}

}

/**
* The error to use when a key isn't found.
* @type {new (key: PropertyKey) => Error}
*/
Env.KeyNotFoundError = EnvKeyNotFoundError;

/**
* The error to use when a key isn't found.
* @type {new (key: PropertyKey) => Error}
*/
Env.EmptyStringError = EnvEmptyStringError;
95 changes: 87 additions & 8 deletions tests/env.test.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
/**
* @fileoverview Tests for the Env class.
*/
/*global describe, it*/
/*global describe, it, beforeEach, afterEach*/

//-----------------------------------------------------------------------------
// Requirements
//-----------------------------------------------------------------------------

import { Env } from "../src/env.js";
import { Env, EnvKeyNotFoundError, EnvEmptyStringError } from "../src/env.js";
import { assert } from "chai";

//-----------------------------------------------------------------------------
Expand Down Expand Up @@ -158,15 +158,15 @@ describe("Env", () => {

assert.throws(() => {
env.require("PASSWORD");
}, /PASSWORD/);
}, Env.KeyNotFoundError, /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/);
}, Env.EmptyStringError, /OTHER/);
});

});
Expand Down Expand Up @@ -201,7 +201,7 @@ describe("Env", () => {

assert.throws(() => {
env.requireFirst(["foo", "bar"]);
}, /Required environment variable '\[foo,bar\]' not found/);
}, Env.KeyNotFoundError, /Required environment variable '\[foo,bar\]' not found/);

});

Expand Down Expand Up @@ -255,7 +255,7 @@ describe("Env", () => {

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

});
Expand All @@ -278,17 +278,96 @@ describe("Env", () => {

assert.throws(() => {
env.required.PASSWORD;
}, /PASSWORD/);
}, Env.KeyNotFoundError, /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/);
}, Env.EmptyStringError, /OTHER/);
});

});

describe("Custom Errors", () => {

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

class MyError1 extends Error {
constructor(key) {
super(key);
}
}

class MyError2 extends Error {
constructor(key) {
super(key);
}
}

beforeEach(() => {
Env.KeyNotFoundError = MyError1;
Env.EmptyStringError = MyError2;
});

afterEach(() => {
Env.KeyNotFoundError = EnvKeyNotFoundError;
Env.EmptyStringError = EnvEmptyStringError;
});

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

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

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

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

it("should throw an error when none of the arguments exist", () => {
const env = new Env(source);

assert.throws(() => {
env.requireFirst(["foo", "bar"]);
}, MyError1, /foo,bar/);

});

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

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

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

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

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

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

});
});
Loading

0 comments on commit b2715c9

Please sign in to comment.