UNPKG

envalid

Version:

Validation for your environment variables

132 lines (119 loc) 4.19 kB
import { Spec, ValidatorSpec } from './types' import { EnvError } from './errors' // Simplified adaptation of https://github.com/validatorjs/validator.js/blob/master/src/lib/isFQDN.js const isFQDN = (input: string) => { if (!input.length) return false const parts = input.split('.') for (let part, i = 0; i < parts.length; i++) { part = parts[i] if (!/^[a-z\u00a1-\uffff0-9-]+$/i.test(part)) return false if (/[\uff01-\uff5e]/.test(part)) return false // disallow full-width chars if (part[0] === '-' || part[part.length - 1] === '-') return false } return true } // "best effort" regex-based IP address check // If you want a more exhaustive check, create your own custom validator, perhaps wrapping this // implementation (the source of the ipv4 regex below): https://github.com/validatorjs/validator.js/blob/master/src/lib/isIP.js const ipv4Regex = /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/ const ipv6Regex = /([a-f0-9]+:+)+[a-f0-9]+/ const isIP = (input: string) => { if (!input.length) return false return ipv4Regex.test(input) || ipv6Regex.test(input) } const EMAIL_REGEX = /^[^@\s]+@[^@\s]+\.[^@\s]+$/ // intentionally non-exhaustive export const makeValidator = <T>(parseFn: (input: string) => T) => { return function (spec?: Spec<T>): ValidatorSpec<T> { return { ...spec, _parse: parseFn } } } // The reason for the function wrapper is to enable the <T extends boolean = boolean> type parameter // that enables better type inference. For more context, check out the following PR: // https://github.com/af/envalid/pull/118 export function bool<T extends boolean = boolean>(spec?: Spec<T>) { return makeValidator((input: string | boolean) => { switch (input) { case true: case 'true': case 't': case '1': return true case false: case 'false': case 'f': case '0': return false default: throw new EnvError(`Invalid bool input: "${input}"`) } })(spec) } export function num<T extends number = number>(spec?: Spec<T>) { return makeValidator((input: string) => { const coerced = parseFloat(input) if (Number.isNaN(coerced)) throw new EnvError(`Invalid number input: "${input}"`) return coerced })(spec) } export function str<T extends string = string>(spec?: Spec<T>) { return makeValidator((input: string) => { if (typeof input === 'string') return input throw new EnvError(`Not a string: "${input}"`) })(spec) } export function email<T extends string = string>(spec?: Spec<T>) { return makeValidator((x: string) => { if (EMAIL_REGEX.test(x)) return x throw new EnvError(`Invalid email address: "${x}"`) })(spec) } export function host<T extends string = string>(spec?: Spec<T>) { return makeValidator((input: string) => { if (!isFQDN(input) && !isIP(input)) { throw new EnvError(`Invalid host (domain or ip): "${input}"`) } return input })(spec) } export function port<T extends number = number>(spec?: Spec<T>) { return makeValidator((input: string) => { const coerced = +input if ( Number.isNaN(coerced) || `${coerced}` !== `${input}` || coerced % 1 !== 0 || coerced < 1 || coerced > 65535 ) { throw new EnvError(`Invalid port input: "${input}"`) } return coerced })(spec) } export function url<T extends string = string>(spec?: Spec<T>) { return makeValidator((x: string) => { try { // @ts-expect-error TS doesn't acknowledge this API by default yet new URL(x) return x } catch (e) { throw new EnvError(`Invalid url: "${x}"`) } })(spec) } // It's recommended that you provide an explicit type parameter for json validation // if you're using TypeScript. Otherwise the output will be typed as `any`. For example: // // cleanEnv({ // MY_VAR: json<{ foo: number }>({ default: { foo: 123 } }), // }) export function json<T = any>(spec?: Spec<T>) { return makeValidator<T>((x: string) => { try { return JSON.parse(x) } catch (e) { throw new EnvError(`Invalid json: "${x}"`) } })(spec) }