-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
25faf7a
commit 984075b
Showing
16 changed files
with
279 additions
and
235 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,59 +1,11 @@ | ||
import { expect } from 'chai' | ||
import { EnvConfig, envParser } from './EnvParser' | ||
import { envParser } from './EnvParser' | ||
import { test } from 'mocha' | ||
|
||
describe('EnvParser', () => { | ||
it('Custom env value', () => { | ||
expect(envParser({ port: { type: 'number', env: 'FOO' } }, { FOO: '80' })).eql({ port: 80 }) | ||
expect(envParser({ port: { type: 'string', env: 'FOO' } }, { FOO: '80' })).eql({ port: '80' }) | ||
expect(envParser({ port: { type: 'boolean', env: 'FOO' } }, { FOO: 'true' })).eql({ port: true }) | ||
expect(envParser({ port: { type: 'number', env: 'FOO' } }, { FOO: '80', port: '81' })).eql({ port: 80 }) | ||
expect(envParser({ port: { type: 'number' } }, { FOO: '80', port: '81' })).eql({ port: 81 }) | ||
}) | ||
|
||
describe('Number', () => { | ||
it('Parse Number', () => { | ||
expect(envParser({ port: { type: 'number' } }, { port: '80' })).eql({ port: 80 }) | ||
}) | ||
|
||
it('Optional Number', () => { | ||
expect(envParser({ port: { type: 'number', optional: true } }, {})).eql({ port: null }) | ||
}) | ||
}) | ||
|
||
describe('String', () => { | ||
it('Parse String', () => { | ||
expect(envParser({ folder: { type: 'string' } }, { folder: 'images' })).eql({ folder: 'images' }) | ||
}) | ||
|
||
it('Optional String', () => { | ||
expect(envParser({ folder: { type: 'string', optional: true } }, {})).eql({ folder: null }) | ||
}) | ||
}) | ||
|
||
describe('Boolean', () => { | ||
it('Parse Boolean', () => { | ||
const config: EnvConfig = { force: { type: 'boolean' } } | ||
|
||
expect(envParser(config, { force: 'true' })).eql({ force: true }) | ||
expect(envParser(config, { force: 'false' })).eql({ force: false }) | ||
expect(envParser(config, { force: '1' })).eql({ force: true }) | ||
expect(envParser(config, { force: '0' })).eql({ force: false }) | ||
expect(envParser(config, { force: 'TRUE' })).eql({ force: true }) | ||
expect(envParser(config, { force: 'FALSE' })).eql({ force: false }) | ||
}) | ||
|
||
it('Optional Boolean', () => { | ||
expect(envParser({ force: { type: 'boolean', optional: true } }, {})).eql({ force: null }) | ||
}) | ||
}) | ||
|
||
describe('Const', () => { | ||
it('Parse Const', () => { | ||
// | ||
}) | ||
|
||
it('Optional Const', () => { | ||
// | ||
}) | ||
test('it uses the correct enviroment key', () => { | ||
expect(envParser({ port: { type: 'string' } }, { port: '80' })).eql({ port: '80' }) | ||
expect(envParser({ port: { type: 'string', env: 'PORT' } }, { PORT: '80' })).eql({ port: '80' }) | ||
expect(() => envParser({ port: { type: 'string', env: 'PORT' } }, { port: '80' })).throw | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,80 +1,24 @@ | ||
import { parseBoolean, parseConst, parseNumber, parseString } from './TypeParser' | ||
|
||
type EnvNumber = { type: 'number'; defaultValue?: number; env?: string; optional?: boolean } | ||
type EnvString = { type: 'string'; defaultValue?: string; env?: string; optional?: boolean } | ||
type EnvBoolean = { type: 'boolean'; defaultValue?: boolean; env?: string; optional?: boolean } | ||
type EnvConst<T = readonly string[]> = { | ||
type: 'const' | ||
options: T | ||
defaultValue?: string | ||
env?: string | ||
optional?: boolean | ||
} | ||
|
||
type EnvInput = EnvNumber | EnvString | EnvBoolean | EnvConst | ||
|
||
type ReturnType<T extends EnvInput> = T extends EnvNumber | ||
? T extends Omit<EnvBoolean, 'optional'> & { optional: true } | ||
? number | null | ||
: number | ||
: T extends EnvBoolean | ||
? T extends Omit<EnvBoolean, 'optional'> & { optional: true } | ||
? boolean | null | ||
: boolean | ||
: T extends EnvString | ||
? T extends Omit<EnvString, 'optional'> & { optional: true } | ||
? string | null | ||
: string | ||
: T extends EnvConst | ||
? T extends Omit<EnvConst, 'optional'> & { optional: true } | ||
? T['options'][number] | null | ||
: T['options'][number] | ||
: unknown | ||
|
||
export type EnvConfig = Record<string, EnvInput> | ||
type Output<T extends EnvConfig> = { [k in keyof T]: ReturnType<T[k]> } | ||
|
||
export function envParser<T extends EnvConfig>(config: T, args?: NodeJS.ProcessEnv): Output<T> { | ||
import { parseEnvironmentVariable } from './ParseVariable' | ||
import { EnvInput, EnvResult } from './Types' | ||
|
||
export type EnvConfig<T extends readonly string[]> = Record<string, EnvInput<T>> | ||
type Output<T extends EnvConfig<I>, I extends readonly string[]> = { [k in keyof T]: EnvResult<T[k], I> } | ||
|
||
// Prettier is ignored because of the usage of const here. | ||
// prettier-ignore | ||
export function envParser<T extends EnvConfig<I>, const I extends readonly string[]>( | ||
config: T, | ||
args?: NodeJS.ProcessEnv | ||
): Output<T, I> { | ||
const env = { ...process.env, ...args } | ||
const parsedEnv: { [k: string]: string | number | boolean | null } = {} | ||
|
||
for (const [entry, options] of Object.entries(config)) { | ||
const key = options.env || entry | ||
const value = env[key] | ||
|
||
if (options.type === 'number') { | ||
parsedEnv[entry] = parseNumber({ | ||
key, | ||
value, | ||
defaultValue: options.defaultValue, | ||
optional: options.optional, | ||
}) | ||
} else if (options.type === 'string') { | ||
parsedEnv[entry] = parseString({ | ||
key, | ||
value, | ||
defaultValue: options.defaultValue, | ||
optional: options.optional, | ||
}) | ||
} else if (options.type === 'boolean') { | ||
parsedEnv[entry] = parseBoolean({ | ||
key, | ||
value, | ||
defaultValue: options.defaultValue, | ||
optional: options.optional, | ||
}) | ||
} else if (options.type === 'const') { | ||
parsedEnv[entry] = parseConst({ | ||
key, | ||
value, | ||
defaultValue: options.defaultValue, | ||
options: options.options, | ||
optional: options.optional, | ||
}) | ||
} else { | ||
throw new Error(`Env type not implemented`) | ||
} | ||
parsedEnv[entry] = parseEnvironmentVariable(key, value, options) | ||
} | ||
|
||
return parsedEnv as Output<T> | ||
return parsedEnv as Output<T, I> | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import { parseBoolean, parseConst, parseNumber, parseString } from './Parsers' | ||
import { EnvInput } from './Types' | ||
|
||
export function parseEnvironmentVariable<T extends readonly string[]>( | ||
argKey: string, | ||
argValue: string | undefined, | ||
config: EnvInput<T> | ||
) { | ||
try { | ||
if (config.type === 'boolean') { | ||
return parseBoolean({ value: argValue, defaultValue: config.defaultValue, optional: config.optional }) | ||
} else if (config.type === 'number') { | ||
return parseNumber({ value: argValue, defaultValue: config.defaultValue, optional: config.optional }) | ||
} else if (config.type === 'const') { | ||
return parseConst({ | ||
value: argValue, | ||
options: config.options, | ||
defaultValue: config.defaultValue, | ||
optional: config.optional, | ||
}) | ||
} else if (config.type === 'string') { | ||
return parseString({ value: argValue, defaultValue: config.defaultValue, optional: config.optional }) | ||
} | ||
} catch (error) { | ||
if (error instanceof Error) { | ||
throw new Error(`Unable to parse variable "${argKey}"`, { cause: error }) | ||
} else { | ||
throw new Error('An invalid error happened') | ||
} | ||
} | ||
|
||
throw new Error('Option type not implemented') | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import { describe, test } from 'mocha' | ||
import { parseBoolean } from './ParseBoolean' | ||
import { expect } from 'chai' | ||
|
||
describe('ParseBoolean', () => { | ||
test('errors out when value is not defined and the value is required', () => { | ||
expect(() => parseBoolean({ value: undefined })).to.throw | ||
expect(parseBoolean({ value: undefined, optional: true })).to.null | ||
}) | ||
|
||
test('True values', () => { | ||
expect(parseBoolean({ value: '' })).to.be.true | ||
expect(parseBoolean({ value: '1' })).to.be.true | ||
expect(parseBoolean({ value: 'true' })).to.be.true | ||
expect(parseBoolean({ value: 'TRUE' })).to.be.true | ||
}) | ||
|
||
test('False values', () => { | ||
expect(parseBoolean({ value: '0' })).to.be.false | ||
expect(parseBoolean({ value: 'false' })).to.be.false | ||
expect(parseBoolean({ value: 'FALSE' })).to.be.false | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
export function parseBoolean(args: { | ||
value: string | undefined | ||
defaultValue?: boolean | ||
optional?: boolean | ||
}): boolean | null { | ||
if (typeof args.value === 'undefined') { | ||
if (typeof args.defaultValue !== 'undefined') { | ||
return args.defaultValue | ||
} | ||
|
||
if (args.optional === true) { | ||
return null | ||
} | ||
|
||
throw new Error(`Value was not defined`) | ||
} | ||
|
||
if (args.value.trim().length === 0) { | ||
return true // If the value is defined but not set to anything specific we treat it as true. | ||
} | ||
|
||
switch (args.value.trim().toLowerCase()) { | ||
case 'true': | ||
case '1': | ||
return true | ||
|
||
case 'false': | ||
case '0': | ||
return false | ||
|
||
default: | ||
throw new Error(`Unable to parse value`) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import { describe, test } from 'mocha' | ||
import { parseConst } from './ParseConst' | ||
import { expect } from 'chai' | ||
|
||
describe('ParseConst', () => { | ||
test('Empty value', () => { | ||
expect(parseConst({ value: '', options: ['foo', 'bar'], optional: true })).to.eql(null) | ||
expect(() => parseConst({ value: '', options: ['foo', 'bar'] })).to.throw | ||
}) | ||
|
||
test('Invalid value', () => { | ||
expect(() => parseConst({ value: 'baz', options: ['FOO', 'BAR'] })).to.throw | ||
expect(() => parseConst({ value: 'baz', options: ['FOO', 'BAR'], optional: true })).to.throw | ||
}) | ||
|
||
test('Valid value', () => { | ||
expect(parseConst({ value: 'foo', options: ['foo', 'bar'] })).to.eql('foo') | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
export function parseConst<T extends readonly string[]>(args: { | ||
value: string | undefined | ||
options: T | ||
defaultValue?: T[number] | ||
optional?: boolean | ||
}): string | null { | ||
if (typeof args.value === 'undefined' || args.value.trim().length === 0) { | ||
if (typeof args.defaultValue !== 'undefined') { | ||
if (!args.options.includes(args.defaultValue)) { | ||
throw new Error(`Invalid defaultValue "${args.defaultValue}"`) | ||
} | ||
|
||
return args.defaultValue | ||
} | ||
|
||
if (args.optional) { | ||
return null | ||
} | ||
|
||
throw new Error(`Missing value`) | ||
} | ||
|
||
if (!args.options.includes(args.value)) { | ||
throw new Error(`Invalid value "${args.value}"`) | ||
} | ||
|
||
return args.value | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { describe, test } from 'mocha' | ||
import { parseNumber } from './ParseNumber' | ||
import { expect } from 'chai' | ||
|
||
describe('ParseNumber', () => { | ||
test('True values', () => { | ||
expect(parseNumber({ value: '80' })).to.eql(80) | ||
expect(parseNumber({ value: '', optional: true })).to.eql(null) | ||
expect(parseNumber({ value: undefined, optional: true })).to.eql(null) | ||
}) | ||
|
||
test('False values', () => { | ||
expect(() => parseNumber({ value: '' })).to.throw | ||
expect(() => parseNumber({ value: undefined })).to.throw | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
export function parseNumber(args: { | ||
value: string | undefined | ||
defaultValue?: number | ||
optional?: boolean | ||
}): number | null { | ||
const value = args.value?.trim() | ||
|
||
if (typeof value === 'undefined' || value.length === 0) { | ||
if (typeof args.defaultValue !== 'undefined') { | ||
return args.defaultValue | ||
} | ||
|
||
if (args.optional) { | ||
return null | ||
} | ||
|
||
throw new Error(`Missing value"`) | ||
} | ||
|
||
const data = parseFloat(value) | ||
|
||
if (isNaN(data)) { | ||
throw new Error(`Unable to parse value`) | ||
} | ||
|
||
return data | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import { describe, test } from 'mocha' | ||
import { parseString } from './ParseString' | ||
import { expect } from 'chai' | ||
|
||
describe('ParseString', () => { | ||
test('True values', () => { | ||
expect(parseString({ value: 'foo' })).to.eql('foo') | ||
expect(parseString({ value: 'foo ' })).to.eql('foo ') | ||
expect(parseString({ value: '', optional: true })).to.eql(null) | ||
expect(parseString({ value: undefined, optional: true })).to.eql(null) | ||
}) | ||
|
||
test('False values', () => { | ||
expect(() => parseString({ value: '' })).to.throw | ||
expect(() => parseString({ value: undefined })).to.throw | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
export function parseString(args: { | ||
value: string | undefined | ||
defaultValue?: string | ||
optional?: boolean | ||
}): string | null { | ||
if (typeof args.value === 'undefined' || args.value?.trim().length === 0) { | ||
if (typeof args.defaultValue !== 'undefined' && args.defaultValue.trim().length > 0) { | ||
return args.defaultValue | ||
} | ||
|
||
if (args.optional) { | ||
return null | ||
} | ||
|
||
throw new Error(`Missing value`) | ||
} | ||
|
||
return args.value | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
export * from './ParseBoolean' | ||
export * from './ParseConst' | ||
export * from './ParseNumber' | ||
export * from './ParseString' |
Oops, something went wrong.