Skip to content

Commit

Permalink
Merge pull request #5 from zoontek/0.5.0
Browse files Browse the repository at this point in the history
0.5.0
  • Loading branch information
zoontek authored Jun 7, 2023
2 parents 2c2eb9f + 725d384 commit f5d98ec
Show file tree
Hide file tree
Showing 8 changed files with 1,089 additions and 1,069 deletions.
39 changes: 14 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export const env = validate({
// }>
```

_⚠️  In case of incorrect environment variables, the function will throw an `EnvValidationError` exposing `invalidVariables` and `missingVariables` names (not their values) to prevent your application from starting._
_⚠️  In case of incorrect environment variables, the function will throw an `EnvValidationError` exposing `variables` names (not their values) to prevent your application from starting._

#### overrides

Expand Down Expand Up @@ -89,11 +89,13 @@ 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*/) => {
const parsed = parseInt(value);
const port: Validator<number> = (
value: string | undefined = "",
): number | undefined => {
const number = Number.parseInt(value);

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

Expand Down Expand Up @@ -126,12 +128,12 @@ export const env = validate({
env: process.env,
validators: {
// inlined validators return types are correctly inferred
ETHEREUM_ADDRESS: (value) => {
ETHEREUM_ADDRESS: (value = "") => {
if (validator.isEthereumAddress(value)) {
return value;
}
},
OPENED_COUNTRIES: (value) => {
OPENED_COUNTRIES: (value = "") => {
const array = value.split(",");

if (array.every(validator.isISO31661Alpha2)) {
Expand All @@ -149,23 +151,10 @@ export const env = validate({

### Optional values

As it's a common pattern to have some optional environment values, you can write a small helper to wrap every validator with:
As it's a common pattern to have some optional environment values, we provide `optional`, a small helper to wrap every validator with:

```ts
import { string, validate } from "valienv";

// Here's we are using a simple TS discriminating union:
type OptionalEnvValue<T> = { isSet: true; value: T } | { isSet: false };

const optional =
<T>(validator: Validator<T>): Validator<OptionalEnvValue<T>> =>
(value) => {
const result = validator(value);

return typeof result !== "undefined"
? { isSet: true, value: result }
: { isSet: false };
};
import { optional, string, validate } from "valienv";

const env = validate({
env: process.env,
Expand All @@ -174,12 +163,12 @@ const env = validate({
},
});

if (env.FOO.isSet) {
console.log(env.FOO.value); // FOO.value can only be accessed when isSet is true
if (env.FOO.defined) {
console.log(env.FOO.value); // FOO.value can only be accessed when defined is true
}
```

But you can also wrap them using a library of your choice. Here's an example with [`@swan-io/boxed`](https://github.com/swan-io/boxed):
You can also wrap validators using a library of your choice. Here's an example with [`@swan-io/boxed`](https://github.com/swan-io/boxed):

```ts
import { string, validate } from "valienv";
Expand Down
42 changes: 18 additions & 24 deletions __tests__/customValidators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,23 @@ const nodeEnv: Validator<"development" | "test" | "production"> = (value) => {
}
};

const email: Validator<string> = (value) => {
const email: Validator<string> = (value = "") => {
if (/.+@.+\..+/.test(value)) {
return value;
}
};

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

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

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

Expand All @@ -46,17 +45,19 @@ test("with valid input", () => {
},
});

expect(output).toEqual({
expect(output).toStrictEqual({
NODE_ENV: "test",
USER_EMAIL: "[email protected]",
SERVER_URL: "https://github.com",
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",
};

Expand All @@ -74,21 +75,14 @@ test("with invalid input", () => {
expect(error).toBeInstanceOf(EnvValidationError);

expect((error as EnvValidationError).message).toBe(
[
"Some environment variables cannot be validated",
"Invalid variables: NODE_ENV, SERVER_PORT",
"Missing variables: USER_EMAIL, SERVER_URL",
].join("\n"),
"Some environment variables cannot be validated: NODE_ENV, USER_EMAIL, SERVER_URL, SERVER_PORT",
);

expect((error as EnvValidationError).invalidVariables).toEqual([
expect((error as EnvValidationError).variables).toStrictEqual([
"NODE_ENV",
"SERVER_PORT",
]);

expect((error as EnvValidationError).missingVariables).toEqual([
"USER_EMAIL",
"SERVER_URL",
"SERVER_PORT",
]);
}
});
Expand All @@ -107,13 +101,13 @@ test("with invalid overrides", () => {
SERVER_PORT: port,
},
overrides: {
SERVER_URL: "",
SERVER_URL: new URL("https://github.com"),
SERVER_PORT: 0,
},
});

expect(output).toEqual({
SERVER_URL: "",
expect(output).toStrictEqual({
SERVER_URL: new URL("https://github.com"),
SERVER_PORT: 0,
});
});
77 changes: 38 additions & 39 deletions __tests__/validate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
boolean,
number,
oneOf,
optional,
string,
validate,
} from "../src";
Expand All @@ -26,33 +27,7 @@ test("with valid input", () => {
},
});

expect(output).toEqual({
FOO: "foo",
BAR: 42,
BAZ: true,
QUX: "a",
});
});

test("with valid input (as mixed literals)", () => {
const input = {
FOO: "foo",
BAR: 42,
BAZ: true,
QUX: "a",
};

const output = validate({
env: input,
validators: {
FOO: string,
BAR: number,
BAZ: boolean,
QUX: oneOf("a", "b"),
},
});

expect(output).toEqual({
expect(output).toStrictEqual({
FOO: "foo",
BAR: 42,
BAZ: true,
Expand All @@ -74,7 +49,7 @@ test("with extra env variables", () => {
},
});

expect(output).toEqual({
expect(output).toStrictEqual({
FOO: "foo",
});
});
Expand All @@ -100,32 +75,29 @@ test("with invalid env variables", () => {
} catch (e) {
expect(e).toBeInstanceOf(EnvValidationError);
const error = e as EnvValidationError;

expect(error.invalidVariables).toEqual(["BAR", "BAZ", "QUX"]);
expect(error.missingVariables).toEqual([]);
expect(error.variables).toStrictEqual(["BAR", "BAZ", "QUX"]);
}
});

test("with missing env variables", () => {
const input = {
FOO: "foo",
BAR: "", // empty strings means not set
};

try {
validate({
env: input,
validators: {
FOO: string,
BAR: number,
BAR: string,
BAZ: boolean,
},
});
} catch (e) {
expect(e).toBeInstanceOf(EnvValidationError);
const error = e as EnvValidationError;

expect(error.invalidVariables).toEqual([]);
expect(error.missingVariables).toEqual(["BAR", "BAZ"]);
expect(error.variables).toStrictEqual(["BAR", "BAZ"]);
}
});

Expand All @@ -147,8 +119,10 @@ test("with invalid and missing env variables", () => {
} catch (error) {
expect(error).toBeInstanceOf(EnvValidationError);

expect((error as EnvValidationError).invalidVariables).toEqual(["BAR"]);
expect((error as EnvValidationError).missingVariables).toEqual(["BAZ"]);
expect((error as EnvValidationError).variables).toStrictEqual([
"BAR",
"BAZ",
]);
}
});

Expand All @@ -171,7 +145,7 @@ test("with overrides", () => {
},
});

expect(output).toEqual({
expect(output).toStrictEqual({
FOO: "overriddenFoo",
BAR: 42,
BAZ: true,
Expand All @@ -196,9 +170,34 @@ test("with missing env variables and overrides", () => {
},
});

expect(output).toEqual({
expect(output).toStrictEqual({
FOO: "foo",
BAR: 42,
BAZ: true,
});
});

test("with optional values", () => {
const input = {
FOO: "foo",
BAR: "", // empty strings means not set
QUX: "a",
};

const output = validate({
env: input,
validators: {
FOO: string,
BAR: optional(string),
BAZ: optional(number),
QUX: optional(oneOf("a", "b")),
},
});

expect(output).toStrictEqual({
FOO: "foo",
BAR: { defined: false },
BAZ: { defined: false },
QUX: { defined: true, value: "a" },
});
});
18 changes: 9 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"name": "valienv",
"version": "0.4.0",
"version": "0.5.0",
"license": "MIT",
"description": "A simple environment variables validator for Node.js and web browsers.",
"description": "A simple environment variables validator for Node.js, web browsers and React Native",
"author": "Mathieu Acthernoene <[email protected]>",
"homepage": "https://github.com/zoontek/valienv#readme",
"repository": {
Expand Down Expand Up @@ -44,14 +44,14 @@
"trailingComma": "all"
},
"devDependencies": {
"@types/node": "^18.15.10",
"@typescript-eslint/eslint-plugin": "^5.56.0",
"@typescript-eslint/parser": "^5.56.0",
"eslint": "^8.36.0",
"@types/node": "^20.2.5",
"@typescript-eslint/eslint-plugin": "^5.59.9",
"@typescript-eslint/parser": "^5.59.9",
"eslint": "^8.42.0",
"microbundle": "^0.15.1",
"prettier": "^2.8.7",
"prettier": "^2.8.8",
"prettier-plugin-organize-imports": "^3.2.2",
"typescript": "^5.0.2",
"vitest": "^0.29.7"
"typescript": "^5.1.3",
"vitest": "^0.32.0"
}
}
Loading

0 comments on commit f5d98ec

Please sign in to comment.