Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,7 @@ Node's `process.env` only stores strings, but sometimes you want to retrieve oth
URL, email address). To these ends, the following validation functions are available:

- `str()` - Passes string values through, will ensure a value is present unless a
`default` value is given. Note that an empty string is considered a valid value -
if this is undesirable you can easily create your own validator (see below)
`default` value is given. Note that an empty string is not considered a valid value
- `bool()` - Parses env var strings `"1", "0", "true", "false", "t", "f", "yes", "no", "on", "off"` into booleans
- `num()` - Parses an env var (eg. `"42", "0.23", "1e5"`) into a Number
- `email()` - Ensures an env var is an email address
Expand Down
15 changes: 12 additions & 3 deletions src/core.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { EnvError, EnvMissingError } from './errors'
import type { CleanOptions, SpecsOutput, Spec, ValidatorSpec } from './types'
import { defaultReporter } from './reporter'
import type { CleanOptions, Spec, SpecsOutput, ValidatorSpec } from './types'

/**
* Validate a single env var, given a spec object
Expand Down Expand Up @@ -40,8 +40,17 @@ export function formatSpecDescription<T>(spec: Spec<T>) {
return `${spec.desc}${egText}${docsText}`
}

const readRawEnvValue = <T>(env: unknown, k: keyof T | 'NODE_ENV'): string | T[keyof T] => {
return (env as any)[k]
const readRawEnvValue = <T>(
env: unknown,
k: keyof T | 'NODE_ENV',
): string | undefined | T[keyof T] => {
const result = (env as any)[k]

if (typeof result == 'string' && !result.trim()) {
return undefined
}

return result
}

/**
Expand Down
60 changes: 50 additions & 10 deletions tests/requiredWhen.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { bool, cleanEnv, defaultReporter, EnvMissingError, num, EnvError } from
import { formatSpecDescription } from '../src/core'

vi.mock('../src/reporter')
const mockedDefaultReporter: vi.Mock = <vi.Mock<typeof defaultReporter>>defaultReporter;
mockedDefaultReporter.mockImplementation(() => { })
const mockedDefaultReporter: vi.Mock = <vi.Mock<typeof defaultReporter>>defaultReporter
mockedDefaultReporter.mockImplementation(() => {})

describe('requiredWhen', () => {
beforeEach(() => {
Expand All @@ -13,7 +13,7 @@ describe('requiredWhen', () => {
test("isn't required", () => {
cleanEnv(
{
autoExtractId: "true",
autoExtractId: 'true',
},
{
autoExtractId: bool(),
Expand All @@ -29,14 +29,54 @@ describe('requiredWhen', () => {
autoExtractId: true,
id: undefined,
},
errors: {}
errors: {},
})
})

test("isn't required but empty string provided", () => {
cleanEnv(
{
id: '',
},
{
id: num({
default: undefined,
requiredWhen: () => false,
}),
},
)
expect(mockedDefaultReporter).toHaveBeenCalledTimes(1)
expect(mockedDefaultReporter).toHaveBeenCalledWith({
env: {
id: undefined,
},
errors: {},
})

cleanEnv(
{
autoExtractId: '',
},
{
autoExtractId: bool({
default: undefined,
requiredWhen: () => false,
}),
},
)
expect(mockedDefaultReporter).toHaveBeenCalledTimes(2)
expect(mockedDefaultReporter).toHaveBeenCalledWith({
env: {
autoExtractId: undefined,
},
errors: {},
})
})

test('required but not provided', () => {
cleanEnv(
{
autoExtractId: "false",
autoExtractId: 'false',
},
{
autoExtractId: bool(),
Expand Down Expand Up @@ -68,8 +108,8 @@ describe('requiredWhen', () => {
test('required and provided', () => {
cleanEnv(
{
autoExtractId: "false",
id: "123"
autoExtractId: 'false',
id: '123',
},
{
autoExtractId: bool(),
Expand All @@ -92,8 +132,8 @@ describe('requiredWhen', () => {
test('required but failed to parse', () => {
cleanEnv(
{
autoExtractId: "false",
id: "abc"
autoExtractId: 'false',
id: 'abc',
},
{
autoExtractId: bool(),
Expand All @@ -110,7 +150,7 @@ describe('requiredWhen', () => {
id: undefined,
},
errors: {
id: new EnvError(`Invalid number input: "abc"`)
id: new EnvError(`Invalid number input: "abc"`),
},
})
})
Expand Down
25 changes: 23 additions & 2 deletions tests/validators.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,19 @@ test('bool() works with various formats', () => {
const off = cleanEnv({ FOO: 'off' }, { FOO: bool() })
expect(off).toEqual({ FOO: false })

expect(function withEmpty() {
return cleanEnv({ FOO: '' }, { FOO: bool() }, makeSilent)
}).toThrow()

const defaultF = cleanEnv({}, { FOO: bool({ default: false }) })
expect(defaultF).toEqual({ FOO: false })

const defaultTWithWhitespace = cleanEnv(
{ FOO: ' ' },
{ FOO: bool({ default: true }) },
makeSilent,
)
expect(defaultTWithWhitespace).toEqual({ FOO: true })
Comment thread
svr93 marked this conversation as resolved.
})

test('num()', () => {
Expand Down Expand Up @@ -132,8 +143,18 @@ test('url()', () => {
})

test('str()', () => {
const withEmpty = cleanEnv({ FOO: '' }, { FOO: str() })
expect(withEmpty).toEqual({ FOO: '' })
expect(cleanEnv({ FOO: 'asdf' }, { FOO: str() })).toEqual({ FOO: 'asdf' })

expect(function withWhitespace() {
return cleanEnv({ FOO: ' ' }, { FOO: str() }, makeSilent)
}).toThrow()

const defaultWithWhitespace = cleanEnv(
{ FOO: ' ' },
{ FOO: str({ default: 'asdf' }) },
makeSilent,
)
expect(defaultWithWhitespace).toEqual({ FOO: 'asdf' })

expect(() => cleanEnv({ FOO: 42 }, { FOO: str() }, makeSilent)).toThrow()
})
Expand Down
Loading