validata
Version:
Type safe data validation and sanitization
555 lines (429 loc) • 15.2 kB
Markdown
# Validata
Type safe data validation and sanitization.
See also
- [validata-koa](https://www.npmjs.com/package/validata-koa) for more usage in [Koa](https://www.npmjs.com/package/koa)
- [validata-express](https://www.npmjs.com/package/validata-express) for more usage in [Express](https://www.npmjs.com/package/express)
## Getting started
```bash
npm i validata
```
## Basic usage
```typescript
import { asString, isObject, isString, maybeString } from 'validata';
interface Sample {
maybeString: string | undefined;
myString: string;
numericString: string;
}
const sample = isObject<Sample>({
maybeString: maybeString(), // will allow string data type or sanitize to undefined
myString: isString(), // will allow only string data type
numericString: asString(), // will allow string or attempt to convert to string
});
console.log(
JSON.stringify(
sample.process({
maybeString: 123,
myString: 123,
numericString: 123,
})
)
);
/*
FAIL: Outputs:
{"issues":[{"path":["maybeString"],"value":123,"reason":"incorrect-type","info":{"expectedType":"string"}},{"path":["myString"],"value":123,"reason":"incorrect-type","info":{"expectedType":"string"}}]}
*/
console.log(
JSON.stringify(
sample.process({
myString: '123',
numericString: 123,
})
)
);
/*
SUCCESS: Outputs:
{"value":{"myString":"123","numericString":"123"}}
*/
```
## API
Checks:
- isAny
- Array
- isArray
- maybeArray
- asArray
- maybeAsArray
- Boolean
- isBoolean
- maybeBoolean
- asBoolean
- maybeAsBoolean
- Date
- isDate
- maybeDate
- asDate
- maybeAsDate
- Enum
- isEnum
- maybeEnum
- asEnum
- maybeAsEnum
- Number
- isNumber
- maybeNumber
- asNumber
- maybeAsNumber
- Object
- isObject
- maybeObject
- asObject
- maybeAsObject
- Record
- isRecord
- maybeRecord
- asRecord
- maybeAsRecord
- String
- isString
- maybeString
- asString
- maybeAsString
- Tuple
- isTuple
- maybeTuple
- Url
- isUrl
- maybeUrl
- asUrl
- maybeAsUrl
- isNullable
- asNullable
Types
- TypeOf
Work is done by a typed `ValueProcessor`, as returned by`isObject<T>()` or `asNumber()`.
```typescript
interface ValueProcessor<T> {
process(value: unknown): Result<T>;
}
```
The `process()` method returns a `Result<T>`.The `Result` is either a list of issues
(meaning validation failures) or the accepted value (it may be coerced/altered from the original).
```typescript
type Result<T> = ValueResult<T> | IssueResult;
interface ValueResult<T> {
value: T;
}
interface IssueResult {
issues: Issue[];
}
```
## Naming conventions
### `is...` e.g. `isNumber`
- if the value is of the type it will be accepted
- `null` or `undefined` cause an issue
- otherwise it will cause an issue
### `maybe...` e.g. `maybeNumber`
- if the value is of the type it will be accepted
- `null` or `undefined` it will sanitized to undefined
- otherwise it will cause an issue
### `as...` e.g. `asNumber`
- if the value is of the type it will be accepted
- `null` or `undefined` converted to default, if provided, or cause an issue
- if the value can be converted to the type, it will be converted and used
- if the value is cannot be converted the default will be used if provided
- otherwise it will cause an issue
### `maybeAs...` e.g. `maybeAsNumber`
- if the value is of the type it will be accepted
- `null` or `undefined` converted to default, if provided, or sanitized to undefined
- if the value can be converted to the type it will be converted and used
- if the value is cannot be converted the default will be used if provided
- otherwise it will cause an issue
// \* otherwise it will be sanitized to undefined
## Checks
### `isArray`, `maybeArray`, `asArray`, `maybeAsArray`
Usage:
```typescript
isArray(itemProcessor, options);
maybeArray(itemProcessor, options);
asArray(itemProcessor, options);
maybeAsArray(itemProcessor, options);
```
Options:
- `converter?: (value: unknown, options?: any) => T | undefined` - custom converter function, if not defined or `undefined` is returned then built in conversions will be run
- `convertOptions` - options to pass to the _converter_
- `coerceMaxLength? number` - if there are more items than this, some will be removed
- `maxLength?: number` - if there are more items than this, it's an error `max-length`
- `minLength?: number` - if there are less items than this, it's an error `min-length`
- `validator?: (value: T, options?: any, path?: Path[]) => boolean | Issue[]` - custom validation function; if false or Issue[] is returned it's an error
- `validatorOptions?: any` - options to pass to the _validator_
Example:
```typescript
isArray<number>(isNumber({ max: 20, min: 10 }), { coerceMaxLength: 7 });
```
### `isBoolean`, `maybeBoolean`, `asBoolean`, `maybeAsBoolean`
Usage:
```typescript
isBoolean(options);
maybeBoolean(options);
asBoolean(options);
maybeAsBoolean(options);
```
Options:
- `converter?: (value: unknown, options?: any) => T | undefined` - custom converter function, if not defined or `undefined` is returned then built in conversions will be run
- `convertOptions` - options to pass to the _converter_
- `validator?: (value: T, options?: any, path?: Path[]) => boolean | Issue[]` - custom validation function; if false or Issue[] is returned it's an error
- `validatorOptions?: any` - options to pass to the _validator_
### `isDate`, `maybeDate`, `asDate`, `maybeAsDate`
Usage:
```typescript
isDate(options);
maybeDate(options);
asDate(options);
maybeAsDate(options);
```
Options:
- `converter?: (value: unknown, options?: any) => T | undefined` - custom converter function, if not defined or `undefined` is returned then built in conversions will be run
- `convertOptions` - options to pass to the _converter_
- `format` - custom date format used in conversion from `string` to `Date` see [Luxon formatting](https://moment.github.io/luxon/docs/manual/formatting)
- `maxFuture?: Duration` - if the value is after this duration into the future, it's an error `max-future`
- `maxPast?: Duration` - if the value is before this duration into the past, it's an error `max-past`
- `validator?: (value: T, options?: any, path?: Path[]) => boolean | Issue[]` - custom validation function; if false or Issue[] is returned it's an error
- `validatorOptions?: any` - options to pass to the _validator_
### `isEnum`, `maybeEnum`, `asEnum`, `maybeAsEnum`
Usage:
```typescript
isEnum(Enum);
maybeNumber(Enum);
asNumber(Enum);
maybeAsNumber(Enum);
```
Example:
```typescript
// String based Enum
enum EnumOne {
A = 'A',
B = 'B',
C = 'C',
}
isEnum(EnumOne); // Allows "A", "B", "C"
// Number based Enum
enum EnumTwo {
A,
B,
C,
}
isEnum(EnumTwo); // Allows 0, 1, 2
// Converting to an Enum using it's key or value
asEnum(EnumTwo); // Allows 1, 2, 3, "A", "B", "C"
asEnum(EnumTwo).process('A')); // { value: 0 }
asEnum(EnumTwo).process(0)); // { value: 0 }
asEnum(EnumTwo).process(EnumOne.A)); // { value: 0 }
```
### `isNumber`, `maybeNumber`, `asNumber`, `maybeAsNumber`
Usage:
```typescript
isNumber(options);
maybeNumber(options);
asNumber(options);
maybeAsNumber(options);
```
Options:
- `converter?: (value: unknown, options?: any) => T | undefined` - custom converter function, if not defined or `undefined` is returned then built in conversions will be run
- `convertOptions` - options to pass to the _converter_
- `coerceMin?: number` - if the value is less than this, it will be set to this value
- `coerceMax?: number` - if the value is more than this, it will be set to this value
- `max?: number` - if the value is than this, it's an error `max`
- `min?: number` - if the value is than this, it's an error `min`
- `validator?: (value: T, options?: any, path?: Path[]) => boolean | Issue[]` - custom validation function; if false or Issue[] is returned it's an error
- `validatorOptions?: any` - options to pass to the _validator_
### `isObject`, `maybeObject`, `asObject`, `maybeAsObject`
Usage:
```typescript
isObject(contract, options);
maybeObject(contract, options);
asObject(contract, options); // will parse string JSON as object
maybeAsObject(contract, options); // will parse string JSON as object
// where `contract` is Record<string, ValueProcessor>
```
Options:
- `converter?: (value: unknown, options?: any) => T | undefined` - custom converter function, if not defined or `undefined` is returned then built in conversions will be run
- `convertOptions` - options to pass to the _converter_
- `validator?: (value: T, options?: any, path?: Path[]) => boolean | Issue[]` - custom validation function; if false or Issue[] is returned it's an error
- `validatorOptions?: any` - options to pass to the _validator_
Example:
```typescript
interface Sample {
myString: string;
maybeString: string | undefined;
numericString: string;
}
const check = isObject<Sample>({
maybeString: maybeString(), // if these don't match the interface TypeScript will error
myString: isString(),
numericString: asString(),
});
```
### `isRecord`, `maybeRecord`, `asRecord`, `maybeAsRecord`
Usage:
```typescript
isRecord<V>(check, options);
maybeRecord<V>(check, options);
asRecord<V>(check, options);
maybeAsRecord<V>(check, options);
// where `check` is ValueProcessor<V>, and Record<string, V> is the type to be processed
```
Options:
- `keyRegex?: RegExp` - regular expression to check each key name, or it's an error `key-regex`
- `maxKeys?: number` - if the number of keys in the object is more than this, it's an error `max-keys`
- `minKeys?: number` - if the number of keys in the object is more than this, it's an error `max-keys`
- `validator?: (value: Record<string, V>, options?: any, path?: Path[]) => boolean | Issue[]` - custom validation function; if false or Issue[] is returned it's an error
- `validatorOptions?: any` - options to pass to the _validator_
Example:
```typescript
const check = isRecord(isString());
check.process({ foo: 'bar' });
```
### `isString`, `maybeString`, `asString`, `maybeAsString`
Usage:
```typescript
isString(options);
maybeString(options);
asString(options);
maybeAsString(options);
```
Options:
- `converter?: (value: unknown, options?: any) => T | undefined` - custom converter function, if not defined or `undefined` is returned then built in conversions will be run
- `convertOptions` - options to pass to the _converter_
- `limitLength?: number` - if the length of the string is more than this, it will be truncated to this length
- `padStart?: StringPadding` - pad the start of the string up to given value
- `padEnd?: StringPadding` - pad the end of the string up to given value
- `trim?: 'start' | 'end' | 'both' | 'none'` - removes the leading and/or trailing white space and line terminator characters from the string
- `regex?: RegExp` - regular expression that must be matched, or it's an error `regex`
- `maxLength?: number` - if the length of the string is more than this, it's an error `max-length`
- `minLength?: number` - if the length of the string is less than this, it's an error `min-length`
- `format:? StringFormatCheck` - extension point for string format checking, if check fails it's an issue `format` with `info.expectedFormat` set
- `validator?: (value: T, options?: any, path?: Path[]) => boolean | Issue[]` - custom validation function; if false or Issue[] is returned it's an error
- `validatorOptions?: any` - options to pass to the _validator_
StringPadding:
- `length: number` - will pad up until this length
- `padWith: string` - the value to pad with
StringFormat:
- `StringFormat.ULID()` - https://github.com/ulid/spec
- `StringFormat.UUID()` - https://www.ietf.org/rfc/rfc4122.txt
- `StringFormat.password(requirements: PasswordRequirements)` - Password format with minimum requirements
Example:
```typescript
const check = isString({
limitLength: 6,
padStart: { length: 6, padWith: '-' },
});
```
```typescript
const check = isString({
format: StringFormat.ULID(),
});
```
```typescript
const check = isString({
format: StringFormat.password({
minLength: 10, // default=8
numberChars: 2, // default=1
lowerCaseChars: 2, // default=1
upperCaseChars: 2, // default=1
specialChars: 0, // default=1
}),
});
```
```typescript
// change case
import { pascalCase } from 'change-case';
const check = isString({
transform: pascalCase,
});
```
```typescript
const check = isString({
maxLength: 10,
minLength: 8,
regex: /^[A-Z]+$/,
});
```
```typescript
import validator from 'validator';
const check = isString({
validator: validator.isEmail,
validatorOptions: { allow_display_name: true },
});
```
### `isTuple`, `maybeTuple`
Usage:
```typescript
isTuple(options);
maybeTuple(options);
```
Options:
- `validator?: (value: T, options?: any, path?: Path[]) => boolean | Issue[]` - custom validation function; if false or Issue[] is returned it's an error
- `validatorOptions?: any` - options to pass to the _validator_
Example:
```typescript
type MyTuple = [number, string];
const check = isTuple([isNumber({ max: 9, min: 3 }), isString({ regex: /^\w+$/ })]);
```
### `isUrl`, `maybeUrl`, `asUrl`, `maybeAsUrl`
Working with Node's URL object
Usage:
```typescript
isUrl(options);
maybeUrl(options);
asUrl(options);
maybeAsUrl(options);
```
Options:
- `converter?: (value: unknown, options?: any) => T | undefined` - custom converter function, if not defined or `undefined` is returned then built in conversions will be run
- `convertOptions` - options to pass to the _converter_
- `setProtocol?: string` - will coerce the protocol to the given value, if present
- `protocol?: string` - given URL must have this protocol, or it's an error `invalid-protocol`
- `validator?: (value: URL, options?: any, path?: Path[]) => boolean | Issue[]` - custom validation function; if false or Issue[] is returned it's an error
- `validatorOptions?: any` - options to pass to the _validator_
Example:
```typescript
const check = asUrl({
protocol: 'https',
});
```
### `isNullable`
Any other check can be wrapped into `isNullable` to accept `null`.
Example:
```typescript
const check = isNullable(isString({ min: 3 }));
```
### `asNullable`
Any other check can be wrapped into `asNullable` to accept `null`.
Options:
- `default` - can be `null` or return type or a function with return type of the wrapped check
Example:
```typescript
const check = asNullable(isString({ min: 3 }));
const check = asNullable(isString({ min: 3 }), { default: null });
const check = asNullable(isString({ min: 3 }), { default: 'text' });
const check = asNullable(isString({ min: 3 }), { default: () => 'text' });
```
## Types
Types can be extracted from a `ValueProcessor` or a `Contract`-like pure object.
```typescript
const sampleContract = {
maybeString: maybeString(),
myString: isString(),
numericString: asString(),
};
const sample = isObject(sampleContract);
// both are same as
export type SampleContract = TypeOf<typeof sample>;
export type Sample = TypeOf<typeof sample>;
// interface Sample {
// myString: string;
// maybeString: string | undefined;
// numericString: string;
// }
```