Skip to content

Commit 36122fb

Browse files
committed
Refactor validation
1 parent 89bb18a commit 36122fb

File tree

12 files changed

+94
-93
lines changed

12 files changed

+94
-93
lines changed

src/date.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,12 @@
11
import type { Declaration, OptionalProps } from './types'
22

3-
type Params = {
4-
validator?: (value: Date) => void
5-
} & OptionalProps<Date>
6-
export function DateVar({
7-
validator,
8-
...props
9-
}: Params = {}): Declaration<Date> {
3+
type Params = OptionalProps<Date>
4+
export function DateVar(props: Params = {}): Declaration<Date> {
105
const parser = (value: string) => {
116
const dateValue = new Date(value)
127
if (dateValue.toString() === 'Invalid Date') {
138
throw new Error('Value did not match a valid date format!')
149
}
15-
if (validator) validator(dateValue)
1610
return dateValue
1711
}
1812
return {

src/env.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,13 @@ const parseValue = <T>(
1010
if (typeof declaration.defaultValue === 'undefined') {
1111
throw new Error(`No value specified for ${envVarName}`)
1212
}
13+
if (declaration.validator) declaration.validator(declaration.defaultValue)
1314
return [key, declaration.defaultValue]
1415
}
1516
try {
16-
return [key, declaration.parser(envValue)]
17+
const value: T = declaration.parser(envValue)
18+
if (declaration.validator) declaration.validator(value)
19+
return [key, value]
1720
} catch (error) {
1821
throw new Error(`Error while parsing env var ${envVarName}: ${error}`)
1922
}
@@ -22,11 +25,19 @@ const parseValue = <T>(
2225
export function optional<T>({
2326
parser,
2427
defaultValue,
28+
validator,
2529
...others
2630
}: Declaration<T>): Declaration<T | null> {
2731
return {
2832
parser,
2933
defaultValue: defaultValue || null,
34+
validator:
35+
validator &&
36+
((value: T | null) => {
37+
if (value !== null) {
38+
validator(value)
39+
}
40+
}),
3041
...others,
3142
}
3243
}

src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@ export { EnumVar } from './enum'
44
export { IntVar } from './integer'
55
export { StringVar } from './string'
66
export { optional, TypedEnv } from './env'
7-
export type { Declaration, Parser } from './types'
7+
export type { Declaration, Parser, Validator } from './types'

src/integer.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,17 @@
11
import type { Declaration, OptionalProps } from './types'
22

3-
type Params = {
4-
validator?: (intValue: number) => void
5-
} & OptionalProps<number>
3+
type Params = OptionalProps<number>
64

75
const invalidIntMessage = 'Value is not an integer!'
86

9-
export function IntVar({
10-
validator,
11-
...props
12-
}: Params = {}): Declaration<number> {
7+
export function IntVar(props: Params = {}): Declaration<number> {
138
const parser = (value: string) => {
149
if (value.trim() === '') {
1510
// Special case; whitespace-only string will parse but should not.
1611
throw new Error(invalidIntMessage)
1712
}
1813
const numberValue = Number(value)
1914
if (!Number.isInteger(numberValue)) throw new Error(invalidIntMessage)
20-
if (validator) validator(numberValue)
2115
return numberValue
2216
}
2317
return {

src/string.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,17 @@ import type { Declaration, OptionalProps } from './types'
22

33
type Params = {
44
pattern?: RegExp
5-
validator?: (value: string) => void
65
} & OptionalProps<string>
76

87
export function StringVar({
98
pattern,
10-
validator,
119
...props
1210
}: Params = {}): Declaration<string> {
1311
const parser = (value: string) => {
1412
if (pattern) {
1513
if (!pattern.exec(value))
1614
throw new Error(`Did not match pattern ${pattern}`)
1715
}
18-
if (validator) validator(value)
1916
return value
2017
}
2118
return {

src/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
export type Parser<T> = (value: string) => T
2+
export type Validator<T> = (value: T) => void
23

34
export type OptionalProps<T> = {
45
variable?: string
56
defaultValue?: T
7+
validator?: Validator<T>
68
}
79

810
export type Declaration<T> = {

test/date.test.ts

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,3 @@ test('Throws for invalid date formats', async () => {
1919
'Value did not match a valid date format!'
2020
)
2121
})
22-
23-
test('Supports custom validators', async () => {
24-
const datevar = DateVar({
25-
validator: (v) => {
26-
if (v < new Date('2011-11-11')) {
27-
throw new Error(
28-
'This date precedes the release of The Elder Scrolls V: Skyrim!'
29-
)
30-
}
31-
},
32-
})
33-
expect(() => datevar.parser('11/10/11')).toThrow('Skyrim')
34-
expect(() => datevar.parser('11/11/11')).not.toThrow('Skyrim')
35-
})

test/enum.test.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
import { EnumVar } from '../src/enum'
22

3-
test('Enums work', async () => {
3+
describe('EnumVar', () => {
44
const envOptions = ['dev', 'staging', 'prod'] as const
55
const enumVar = EnumVar({ options: envOptions })
6-
expect(enumVar.parser('dev')).toEqual('dev')
7-
expect(enumVar.parser('staging')).toEqual('staging')
8-
expect(enumVar.parser('prod')).toEqual('prod')
9-
expect(() => enumVar.parser('production')).toThrow(
10-
'Value did not match one of dev,staging,prod'
11-
)
6+
7+
test('can handle success cases', async () => {
8+
expect(enumVar.parser('dev')).toEqual('dev')
9+
expect(enumVar.parser('staging')).toEqual('staging')
10+
expect(enumVar.parser('prod')).toEqual('prod')
11+
})
12+
13+
test('can handle failure cases', async () => {
14+
expect(() => enumVar.parser('production')).toThrow(
15+
'Value did not match one of dev,staging,prod'
16+
)
17+
})
1218
})

test/env.test.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,19 @@ describe('TypedEnv', () => {
9797
expect(TypedEnv(schema, envWithB).A).toEqual('Real Value')
9898
})
9999

100+
test('runs validation on default values', async () => {
101+
expect(() =>
102+
TypedEnv({
103+
A: IntVar({
104+
defaultValue: 1,
105+
validator(v) {
106+
if (v < 2) throw new Error('too small')
107+
},
108+
}),
109+
})
110+
).toThrow('too small')
111+
})
112+
100113
test('handles optional variables', async () => {
101114
const rawEnv = {
102115
A: 'A',
@@ -123,4 +136,20 @@ describe('TypedEnv', () => {
123136
expect(TypedEnv(schema).SOME_RANDOM_VAR_NAME).toEqual('SOME_VALUE')
124137
delete process.env.SOME_RANDOM_VAR_NAME
125138
})
139+
140+
test("Validators don't fire for nulls on optionals", async () => {
141+
const schema = {
142+
A: optional(
143+
IntVar({
144+
validator() {
145+
throw new Error('nope')
146+
},
147+
})
148+
),
149+
}
150+
const rawEnv = { A: '1' }
151+
const emptyEnv = {}
152+
expect(() => TypedEnv(schema, rawEnv)).toThrow('nope')
153+
expect(TypedEnv(schema, emptyEnv).A).toBeNull()
154+
})
126155
})

test/integer.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { IntVar } from '../src/integer'
2+
3+
describe('IntVar', () => {
4+
test('can parse base 10 integers', async () => {
5+
const intvar = IntVar()
6+
expect(intvar.parser('12345')).toEqual(12345)
7+
expect(intvar.parser('-54321')).toEqual(-54321)
8+
expect(() => intvar.parser('1.5')).toThrow()
9+
})
10+
11+
test('can parse bases other than 10', async () => {
12+
const intvar = IntVar()
13+
expect(intvar.parser('0xffff')).toEqual(0xffff)
14+
expect(intvar.parser('0b101101101')).toEqual(365)
15+
expect(() => intvar.parser('0xfffg')).toThrow('not an integer')
16+
})
17+
18+
test('throws errors for random garbage', async () => {
19+
const intvar = IntVar()
20+
expect(() => intvar.parser('')).toThrow('not an integer')
21+
expect(() => intvar.parser(' ')).toThrow('not an integer')
22+
expect(() => intvar.parser('false')).toThrow('not an integer')
23+
})
24+
})

0 commit comments

Comments
 (0)