Skip to content

Commit

Permalink
0.6.0 (#6)
Browse files Browse the repository at this point in the history
  • Loading branch information
zoontek authored Feb 23, 2024
1 parent f5d98ec commit ac03d11
Show file tree
Hide file tree
Showing 6 changed files with 1,956 additions and 1,701 deletions.
18 changes: 8 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ _⚠️  The values set has to be correctly typed but are **not** validated._

### Custom validators

By default, `valienv` only exports 3 validators: `string`, `number` and `boolean`. It also offers `oneOf`, a helper to create validators for union of string literals.
By default, `valienv` exports 6 validators: `string`, `number`, `boolean`, `url`, `port` and `email`. It also offers `oneOf`, a helper to create validators for union of string literals.

It's very easy to write your own:

Expand All @@ -89,28 +89,26 @@ import { validate, Validator } from "valienv";

// A validator take raw input, try to parse it and
// returns the result in case of valid value:
const port: Validator<number> = (
value: string | undefined = "",
): number | undefined => {
const number = Number.parseInt(value);
const buffer: Validator<Buffer> = (value: string = "") => {
const valid = /^[A-F\d]+$/i.test(value);

if (number > 0 && number < 65536) {
return number;
if (valid) {
return Buffer.from(value);
}
};

// with process.env = {
// PORT: "3000",
// COOKIE_KEY: "aba4a6fb2222ef28d81e4be445a51fba",
// }

export const env = validate({
env: process.env,
validators: {
PORT: port,
COOKIE_KEY: buffer,
},
});

// -> typeof env = Readonly<{ PORT: number }>
// -> typeof env = Readonly<{ COOKIE_KEY: Buffer }>
```

You can even go wild by using stricter types, complex parsing, your favorite validation library, etc! 🔥
Expand Down
40 changes: 13 additions & 27 deletions __tests__/customValidators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ const nodeEnv: Validator<"development" | "test" | "production"> = (value) => {
}
};

const email: Validator<string> = (value = "") => {
if (/.+@.+\..+/.test(value)) {
const hex: Validator<string> = (value = "") => {
if (/^[A-F\d]+$/i.test(value)) {
return value;
}
};
Expand All @@ -19,95 +19,81 @@ const url: Validator<URL> = (value = "") => {
} catch {} // eslint-disable-line no-empty
};

const port: Validator<number> = (value = "") => {
const number = Number.parseInt(value);

if (number > 0 && number < 65536) {
return number;
}
};

test("with valid input", () => {
const input = {
NODE_ENV: "test",
USER_EMAIL: "[email protected]",
COOKIE_KEY: "aba4a6fb2222ef28d81e4be445a51fba",
SERVER_URL: "https://github.com",
SERVER_PORT: "3000",
};

const output = validate({
env: input,
validators: {
NODE_ENV: nodeEnv,
USER_EMAIL: email,
COOKIE_KEY: hex,
SERVER_URL: url,
SERVER_PORT: port,
},
});

expect(output).toStrictEqual({
NODE_ENV: "test",
USER_EMAIL: "[email protected]",
COOKIE_KEY: "aba4a6fb2222ef28d81e4be445a51fba",
SERVER_URL: new URL("https://github.com"),
SERVER_PORT: 3000,
});
});

test("with invalid input", () => {
const input = {
NODE_ENV: "staging",
USER_EMAIL: "mathieu",
SERVER_URL: "youtube",
SERVER_PORT: "three thousand",
COOKIE_KEY: "invalid hex",
};

try {
validate({
env: input,
validators: {
NODE_ENV: nodeEnv,
USER_EMAIL: email,
SERVER_URL: url,
SERVER_PORT: port,
COOKIE_KEY: hex,
},
});
} catch (error) {
expect(error).toBeInstanceOf(EnvValidationError);

expect((error as EnvValidationError).message).toBe(
"Some environment variables cannot be validated: NODE_ENV, USER_EMAIL, SERVER_URL, SERVER_PORT",
"Some environment variables cannot be validated: NODE_ENV, SERVER_URL, COOKIE_KEY",
);

expect((error as EnvValidationError).variables).toStrictEqual([
"NODE_ENV",
"USER_EMAIL",
"SERVER_URL",
"SERVER_PORT",
"COOKIE_KEY",
]);
}
});

test("with invalid overrides", () => {
// overrides are not validated, it will not throw
const input = {
COOKIE_KEY: "aba4a6fb2222ef28d81e4be445a51fba",
SERVER_URL: "https://github.com",
SERVER_PORT: "3000",
};

const output = validate({
env: input,
validators: {
COOKIE_KEY: hex,
SERVER_URL: url,
SERVER_PORT: port,
},
overrides: {
COOKIE_KEY: "invalid hex",
SERVER_URL: new URL("https://github.com"),
SERVER_PORT: 0,
},
});

expect(output).toStrictEqual({
COOKIE_KEY: "invalid hex",
SERVER_URL: new URL("https://github.com"),
SERVER_PORT: 0,
});
});
30 changes: 28 additions & 2 deletions __tests__/validate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@ import { expect, test } from "vitest";
import {
EnvValidationError,
boolean,
email,
number,
oneOf,
optional,
port,
string,
url,
validate,
} from "../src";

Expand All @@ -15,6 +18,9 @@ test("with valid input", () => {
BAR: "42",
BAZ: "true",
QUX: "a",
QUUX: "https://swan.io",
FRED: "8080",
THUD: "[email protected]",
};

const output = validate({
Expand All @@ -24,6 +30,9 @@ test("with valid input", () => {
BAR: number,
BAZ: boolean,
QUX: oneOf("a", "b"),
QUUX: url,
FRED: port,
THUD: email,
},
});

Expand All @@ -32,6 +41,9 @@ test("with valid input", () => {
BAR: 42,
BAZ: true,
QUX: "a",
QUUX: "https://swan.io",
FRED: 8080,
THUD: "[email protected]",
});
});

Expand Down Expand Up @@ -60,6 +72,9 @@ test("with invalid env variables", () => {
BAR: "bar",
BAZ: "baz",
QUX: "c",
QUUX: "swan.io",
FRED: 72000,
THUD: "john-doe.com",
};

try {
Expand All @@ -70,12 +85,23 @@ test("with invalid env variables", () => {
BAR: number,
BAZ: boolean,
QUX: oneOf("a", "b"),
QUUX: url,
FRED: port,
THUD: email,
},
});
} catch (e) {
expect(e).toBeInstanceOf(EnvValidationError);
const error = e as EnvValidationError;
expect(error.variables).toStrictEqual(["BAR", "BAZ", "QUX"]);

expect(error.variables).toStrictEqual([
"BAR",
"BAZ",
"QUX",
"QUUX",
"FRED",
"THUD",
]);
}
});

Expand Down Expand Up @@ -130,7 +156,7 @@ test("with overrides", () => {
const input = {
FOO: "foo",
BAR: "42",
BAZ: "true",
BAZ: "1",
};

const output = validate({
Expand Down
22 changes: 12 additions & 10 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "valienv",
"version": "0.5.0",
"version": "0.6.0",
"license": "MIT",
"description": "A simple environment variables validator for Node.js, web browsers and React Native",
"author": "Mathieu Acthernoene <[email protected]>",
Expand Down Expand Up @@ -41,17 +41,19 @@
"not dead"
],
"prettier": {
"trailingComma": "all"
"plugins": [
"prettier-plugin-organize-imports"
]
},
"devDependencies": {
"@types/node": "^20.2.5",
"@typescript-eslint/eslint-plugin": "^5.59.9",
"@typescript-eslint/parser": "^5.59.9",
"eslint": "^8.42.0",
"@types/node": "^20.11.20",
"@typescript-eslint/eslint-plugin": "^7.0.2",
"@typescript-eslint/parser": "^7.0.2",
"eslint": "^8.56.0",
"microbundle": "^0.15.1",
"prettier": "^2.8.8",
"prettier-plugin-organize-imports": "^3.2.2",
"typescript": "^5.1.3",
"vitest": "^0.32.0"
"prettier": "^3.2.5",
"prettier-plugin-organize-imports": "^3.2.4",
"typescript": "^5.3.3",
"vitest": "^1.3.1"
}
}
32 changes: 30 additions & 2 deletions src/validators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,22 @@ export type OptionalEnvValue<T> =
| { defined: false };

export const boolean: Validator<boolean> = (value = "") => {
if (value === "true") {
if (value === "true" || value === "1") {
return true;
}
if (value === "false") {
if (value === "false" || value === "0") {
return false;
}
};

const EMAIL_REGEX = /^[^@\s]+@[^@\s]+\.[^@\s]+$/;

export const email: Validator<string> = (value = "") => {
if (EMAIL_REGEX.test(value)) {
return value;
}
};

export const number: Validator<number> = (value = "") => {
const number = Number.parseFloat(value);

Expand All @@ -21,12 +29,32 @@ export const number: Validator<number> = (value = "") => {
}
};

export const port: Validator<number> = (value = "") => {
const number = Number.parseFloat(value);

if (
!Number.isNaN(number) &&
number % 1 === 0 &&
number > 0 &&
number < 65536
) {
return number;
}
};

export const string: Validator<string> = (value = "") => {
if (value !== "") {
return value;
}
};

export const url: Validator<string> = (value = "") => {
try {
new URL(value);
return value;
} catch {} // eslint-disable-line no-empty
};

export const oneOf =
<T extends string>(...values: Readonly<T[]>): Validator<T> =>
(value = "") => {
Expand Down
Loading

0 comments on commit ac03d11

Please sign in to comment.