Comparing version 1.0.0-beta.1 to 1.0.0-beta.2
@@ -6,4 +6,4 @@ "use strict"; | ||
const _1 = require("."); | ||
describe('validate integration tests', () => { | ||
describe('Person', () => { | ||
describe('Integration tests', () => { | ||
describe('Basic validation', () => { | ||
const validatePerson = _1.validate.object({ | ||
@@ -28,9 +28,10 @@ name: _1.validate.string(), | ||
}; | ||
it('should validate a person', () => { | ||
it('validates a person', () => { | ||
const person = validatePerson(validPerson); | ||
chai_1.expect(person).to.eql(validPerson); | ||
}); | ||
it('should throw with an invalid person', () => { | ||
it('throws with an invalid person', () => { | ||
const invalidPerson = Object.assign({}, validPerson, { address: { street: 'Ackerstr', zip: 'foo' } }); | ||
chai_1.expect(() => validatePerson(invalidPerson)).to.throw(_1.FefeError, 'address.zip: Not a number.') | ||
chai_1.expect(() => validatePerson(invalidPerson)) | ||
.to.throw(_1.FefeError, 'address.zip: Not a number.') | ||
.that.deep.include({ value: invalidPerson, path: ['address', 'zip'] }) | ||
@@ -40,5 +41,26 @@ .and.has.property('originalError').that.include({ value: 'foo' }); | ||
}); | ||
}); | ||
describe('Complex example', () => { | ||
describe('Config', () => { | ||
describe('Basic transformation (sanitization)', () => { | ||
const sanitizeMovie = _1.validate.object({ | ||
title: _1.validate.string(), | ||
releasedAt: ramda_1.pipe(_1.validate.string(), _1.transform.parseDate()) | ||
}); | ||
it('validates a movie and parses the date string', () => { | ||
const movie = sanitizeMovie({ | ||
title: 'Star Wars', | ||
releasedAt: '1977-05-25T12:00:00.000Z' | ||
}); | ||
chai_1.expect(movie).to.eql({ | ||
title: 'Star Wars', | ||
releasedAt: new Date('1977-05-25T12:00:00.000Z') | ||
}); | ||
}); | ||
it('throws with an invalid date', () => { | ||
const invalidMovie = { title: 'Star Wars', releasedAt: 'foo' }; | ||
chai_1.expect(() => sanitizeMovie(invalidMovie)) | ||
.to.throw(_1.FefeError, 'releasedAt: Not a date.') | ||
.that.deep.include({ value: invalidMovie, path: ['releasedAt'] }) | ||
.and.has.property('originalError').that.include({ value: 'foo' }); | ||
}); | ||
}); | ||
describe('Complex transformation and validation', () => { | ||
const parseConfig = _1.validate.object({ | ||
@@ -56,9 +78,9 @@ gcloudCredentials: ramda_1.pipe(_1.validate.string(), _1.transform.parseJson(), _1.validate.object({ key: _1.validate.string() })), | ||
}; | ||
it('should parse a config', () => { | ||
it('parses a config', () => { | ||
const config = parseConfig(validConfigInput); | ||
chai_1.expect(config).to.eql(validConfig); | ||
}); | ||
it('should throw with an invalid config', () => { | ||
it('throws with an invalid config', () => { | ||
const invalidConfigInput = Object.assign({}, validConfigInput, { gcloudCredentials: '{ "key": "secret", "foo": "bar" }' }); | ||
chai_1.expect(() => parseConfig(invalidConfigInput)).to.throw(_1.FefeError, 'gcloudCredentials: Key(s) not allowed: foo') | ||
chai_1.expect(() => parseConfig(invalidConfigInput)).to.throw(_1.FefeError, 'gcloudCredentials: Properties not allowed: foo') | ||
.that.deep.include({ value: invalidConfigInput, path: ['gcloudCredentials'] }) | ||
@@ -65,0 +87,0 @@ .and.has.property('originalError').that.include({ value: { key: 'secret', foo: 'bar' } }); |
import { Validate } from './validate'; | ||
export interface ValidateArrayOptions<R> { | ||
elementValidate: Validate<R>; | ||
minLength?: number; | ||
@@ -8,2 +7,2 @@ maxLength?: number; | ||
export declare type ValidateArrayValue<R> = Validate<R> | ValidateArrayOptions<R>; | ||
export declare function validateArray<R>(_options: ValidateArrayOptions<R> | Validate<R>): (value: unknown) => R[]; | ||
export declare function validateArray<R>(elementValidate: Validate<R>, { minLength, maxLength }?: ValidateArrayOptions<R>): (value: unknown) => R[]; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const errors_1 = require("../errors"); | ||
function validateArray(_options) { | ||
const { elementValidate, minLength, maxLength } = typeof _options === 'object' | ||
? _options | ||
: { elementValidate: _options }; | ||
function validateArray(elementValidate, { minLength, maxLength } = {}) { | ||
return (value) => { | ||
@@ -9,0 +6,0 @@ if (!Array.isArray(value)) |
@@ -9,3 +9,3 @@ "use strict"; | ||
it('should throw if not a array', () => { | ||
const validate = validate_array_1.validateArray({ elementValidate: validate_string_1.validateString() }); | ||
const validate = validate_array_1.validateArray(validate_string_1.validateString()); | ||
chai_1.expect(() => validate('foo')) | ||
@@ -16,3 +16,3 @@ .to.throw(errors_1.FefeError, 'Not an array.') | ||
it('should throw if nested validation fails', () => { | ||
const validate = validate_array_1.validateArray({ elementValidate: validate_string_1.validateString() }); | ||
const validate = validate_array_1.validateArray(validate_string_1.validateString()); | ||
const value = ['foo', 1]; | ||
@@ -24,3 +24,3 @@ chai_1.expect(() => validate(value)) | ||
it('should return a valid array', () => { | ||
const validate = validate_array_1.validateArray({ elementValidate: validate_string_1.validateString() }); | ||
const validate = validate_array_1.validateArray(validate_string_1.validateString()); | ||
const value = ['foo', 'bar']; | ||
@@ -30,5 +30,3 @@ chai_1.expect(validate(value)).to.eql(value); | ||
it('should return a valid array with transformed values', () => { | ||
const validate = validate_array_1.validateArray({ | ||
elementValidate: value => `transformed: ${validate_string_1.validateString()(value)}` | ||
}); | ||
const validate = validate_array_1.validateArray(value => `transformed: ${validate_string_1.validateString()(value)}`); | ||
chai_1.expect(validate(['foo', 'bar'])).to.eql(['transformed: foo', 'transformed: bar']); | ||
@@ -35,0 +33,0 @@ }); |
@@ -12,5 +12,5 @@ import { Validate } from './validate'; | ||
} ? undefined : never) : never; | ||
export declare function validateObject<D extends ValidateObjectDefinition>(definition: D, { noExcessKeys }?: { | ||
noExcessKeys?: boolean; | ||
export declare function validateObject<D extends ValidateObjectDefinition>(definition: D, { allowExcessProperties }?: { | ||
allowExcessProperties?: boolean; | ||
}): (value: unknown) => { [k in keyof D]: ValidateObjectReturnType<D[k]>; }; | ||
export {}; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const errors_1 = require("../errors"); | ||
function validateObject(definition, { noExcessKeys = true } = {}) { | ||
function validateObject(definition, { allowExcessProperties = false } = {}) { | ||
Object.entries(definition).forEach(([key, definitionValue]) => { | ||
@@ -17,6 +17,6 @@ if (typeof definitionValue !== 'object') | ||
throw new errors_1.FefeError(value, 'Not an object.'); | ||
if (noExcessKeys) { | ||
const excessKeys = Object.keys(value).filter(key => !definition[key]); | ||
if (excessKeys.length > 0) | ||
throw new errors_1.FefeError(value, `Key(s) not allowed: ${excessKeys.join(', ')}`); | ||
if (!allowExcessProperties) { | ||
const excessProperties = Object.keys(value).filter(key => !definition[key]); | ||
if (excessProperties.length > 0) | ||
throw new errors_1.FefeError(value, `Properties not allowed: ${excessProperties.join(', ')}`); | ||
} | ||
@@ -23,0 +23,0 @@ const validated = {}; |
{ | ||
"name": "fefe", | ||
"version": "1.0.0-beta.1", | ||
"version": "1.0.0-beta.2", | ||
"description": "Validate, sanitize and transform values with proper types.", | ||
@@ -5,0 +5,0 @@ "main": "dist/index.js", |
106
README.md
@@ -14,4 +14,12 @@ # fefe | ||
## ๐ Validation example | ||
## Installation | ||
```bash | ||
npm install fefe | ||
``` | ||
## Usage | ||
### ๐ Validation example | ||
Validation only checks the provided value and returns it with proper types. | ||
@@ -31,3 +39,3 @@ | ||
You can also use `fefe` to define your types easily: | ||
โ๏ธ You can also use `fefe` to define your types easily: | ||
@@ -38,10 +46,13 @@ ```typescript | ||
## โ๏ธ Sanitization example | ||
### โ๏ธ Basic transformation example (sanitization/parsing) | ||
In this example a `string` needs to be parsed as a `Date`. Note how easy it is to apply a chain of functions to validate and transform a value (here we use `ramda`). | ||
```typescript | ||
import { sanitize, validate } from 'fefe' | ||
import { transform, validate } from 'fefe' | ||
import { pipe } from 'ramda' | ||
const sanitizeMovie = validate.object({ | ||
title: validate.string(), | ||
releasedAt: sanitize.date() | ||
releasedAt: pipe(validate.string(), transform.parseDate()) | ||
}) | ||
@@ -52,3 +63,3 @@ | ||
const book: Book = sanitizeMovie({ | ||
const movie: Movie = sanitizeMovie({ | ||
title: 'Star Wars', | ||
@@ -59,7 +70,7 @@ releasedAt: '1977-05-25T12:00:00.000Z' | ||
Then `book` equals `{ title: 'Star Wars', releasedAt: Date(1977-05-25T12:00:00.000Z) }` (`releasedAt` now is a date). | ||
Then `movie` equals `{ title: 'Star Wars', releasedAt: Date(1977-05-25T12:00:00.000Z) }` (`releasedAt` now is a date). | ||
## ๐ ๏ธ Transformation example | ||
### ๐ ๏ธ Complex transformation example | ||
This is an example that can be applied to parsing environment variables or query string parameters. Note how easy it is to apply a chain of functions to transform and validate a value (here we use `ramda`). | ||
This is a more complex example that can be applied to parsing environment variables or query string parameters. | ||
@@ -92,1 +103,78 @@ ```typescript | ||
## Documentation | ||
### `FefeError` | ||
`fefe` throws a `FefeError` if a value can't be validated/transformed. A `FefeError` has the following properties: | ||
* `reason`: the reason for the error. | ||
* `value`: the value that was passed. | ||
* `path`: the path in `value` to where the error occured. | ||
### `validate.array(elementValidate, options?)` | ||
Returns a function `(value: unknown) => T[]` that checks that the given value is an array and that runs `elementValidate` on all elements. A new array with the results is returned. | ||
Options: | ||
* `elementValidate`: validator function `(value: unknown) => T` that is applied to each element. The return values are returned as a new array. | ||
* `options.minLength?`, `options.maxLength?`: restrict length of array | ||
### `validate.boolean()` | ||
Returns a function `(value: unknown) => boolean` that checks that whether `value` is a boolean. | ||
### `validate.date(options?)` | ||
Returns a function `(value: unknown) => Date` that checks that whether `value` is a Date. | ||
Options: | ||
* `options.min?`, `options.max?`: restrict date | ||
### `validate.number(options?)` | ||
Returns a function `(value: unknown) => number` that checks that whether `value` is a number. | ||
Options: | ||
* `options.min?`, `options.max?`: restrict number | ||
* `options.integer?`: require number to be an integer (default: `false`) | ||
* `options.allowNaN?`, `options.allowInfinity?`: allow `NaN` or `infinity` (default: `false`) | ||
### `validate.object(definition, options?)` | ||
Returns a function `(value: unknown) => {...}` that checks that whether `value` is an object and all values pass the validation as specified in `definition`. A new object is returned that has the results of the validator functions as values. | ||
Options: | ||
* `definition`: an object where each value is either: | ||
* a validator functions `(value: unknown) => T` or | ||
* an object with the following properties: | ||
* `validate`: validator function `(value: unknown) => T` | ||
* `optional?`: allow undefined values (default: `false`) | ||
* `default?`: default value of type `T` or function `() => T` that returns a default value | ||
* `allowExcessProperties?`: allow excess properties (default: `false`) | ||
### `validate.string(options?)` | ||
Returns a function `(value: unknown) => string` that checks that whether `value` is a string. | ||
Options: | ||
* `options.minLength?`, `options.maxLength?`: restrict length of string | ||
* `options.regex?`: require string to match regex | ||
### `transform.parseBoolean()` | ||
Returns a function `(value: string) => boolean` that parses a string as a boolean. | ||
### `transform.parseDate(options?)` | ||
Returns a function `(value: string) => Date` that parses a string as a date. | ||
Options: | ||
* `options.iso?`: require value to be an ISO 8601 string. | ||
### `transform.parseJson()` | ||
Returns a function `(value: string) => any` that parses JSON. | ||
### `transform.parseNumber()` | ||
Returns a function `(value: string) => number` that parses a number. |
@@ -6,4 +6,4 @@ import { expect } from 'chai' | ||
describe('validate integration tests', () => { | ||
describe('Person', () => { | ||
describe('Integration tests', () => { | ||
describe('Basic validation', () => { | ||
const validatePerson = validate.object({ | ||
@@ -32,3 +32,3 @@ name: validate.string(), | ||
it('should validate a person', () => { | ||
it('validates a person', () => { | ||
const person = validatePerson(validPerson) | ||
@@ -38,5 +38,6 @@ expect(person).to.eql(validPerson) | ||
it('should throw with an invalid person', () => { | ||
it('throws with an invalid person', () => { | ||
const invalidPerson = { ...validPerson, address: { street: 'Ackerstr', zip: 'foo' } } | ||
expect(() => validatePerson(invalidPerson)).to.throw(FefeError, 'address.zip: Not a number.') | ||
expect(() => validatePerson(invalidPerson)) | ||
.to.throw(FefeError, 'address.zip: Not a number.') | ||
.that.deep.include({ value: invalidPerson, path: ['address', 'zip'] }) | ||
@@ -46,6 +47,33 @@ .and.has.property('originalError').that.include({ value: 'foo' }) | ||
}) | ||
}) | ||
describe('Complex example', () => { | ||
describe('Config', () => { | ||
describe('Basic transformation (sanitization)', () => { | ||
const sanitizeMovie = validate.object({ | ||
title: validate.string(), | ||
releasedAt: pipe(validate.string(), transform.parseDate()) | ||
}) | ||
// { title: string, releasedAt: Date } | ||
type Movie = ReturnType<typeof sanitizeMovie> | ||
it('validates a movie and parses the date string', () => { | ||
const movie = sanitizeMovie({ | ||
title: 'Star Wars', | ||
releasedAt: '1977-05-25T12:00:00.000Z' | ||
}) | ||
expect(movie).to.eql({ | ||
title: 'Star Wars', | ||
releasedAt: new Date('1977-05-25T12:00:00.000Z') | ||
}) | ||
}) | ||
it('throws with an invalid date', () => { | ||
const invalidMovie = { title: 'Star Wars', releasedAt: 'foo' } | ||
expect(() => sanitizeMovie(invalidMovie)) | ||
.to.throw(FefeError, 'releasedAt: Not a date.') | ||
.that.deep.include({ value: invalidMovie, path: ['releasedAt'] }) | ||
.and.has.property('originalError').that.include({ value: 'foo' }) | ||
}) | ||
}) | ||
describe('Complex transformation and validation', () => { | ||
const parseConfig = validate.object({ | ||
@@ -68,3 +96,3 @@ gcloudCredentials: pipe(validate.string(), transform.parseJson(), validate.object({ key: validate.string() })), | ||
it('should parse a config', () => { | ||
it('parses a config', () => { | ||
const config = parseConfig(validConfigInput) | ||
@@ -74,5 +102,5 @@ expect(config).to.eql(validConfig) | ||
it('should throw with an invalid config', () => { | ||
it('throws with an invalid config', () => { | ||
const invalidConfigInput = { ...validConfigInput, gcloudCredentials: '{ "key": "secret", "foo": "bar" }' } | ||
expect(() => parseConfig(invalidConfigInput)).to.throw(FefeError, 'gcloudCredentials: Key(s) not allowed: foo') | ||
expect(() => parseConfig(invalidConfigInput)).to.throw(FefeError, 'gcloudCredentials: Properties not allowed: foo') | ||
.that.deep.include({ value: invalidConfigInput, path: ['gcloudCredentials'] }) | ||
@@ -79,0 +107,0 @@ .and.has.property('originalError').that.include({ value: { key: 'secret', foo: 'bar' } }) |
@@ -9,3 +9,3 @@ import { expect } from 'chai' | ||
it('should throw if not a array', () => { | ||
const validate = validateArray({ elementValidate: validateString() }) | ||
const validate = validateArray(validateString()) | ||
expect(() => validate('foo')) | ||
@@ -17,3 +17,3 @@ .to.throw(FefeError, 'Not an array.') | ||
it('should throw if nested validation fails', () => { | ||
const validate = validateArray({ elementValidate: validateString() }) | ||
const validate = validateArray(validateString()) | ||
const value = ['foo', 1] | ||
@@ -26,3 +26,3 @@ expect(() => validate(value)) | ||
it('should return a valid array', () => { | ||
const validate = validateArray({ elementValidate: validateString() }) | ||
const validate = validateArray(validateString()) | ||
const value = ['foo', 'bar'] | ||
@@ -33,7 +33,5 @@ expect(validate(value)).to.eql(value) | ||
it('should return a valid array with transformed values', () => { | ||
const validate = validateArray({ | ||
elementValidate: value => `transformed: ${validateString()(value)}` | ||
}) | ||
const validate = validateArray(value => `transformed: ${validateString()(value)}`) | ||
expect(validate(['foo', 'bar'])).to.eql(['transformed: foo', 'transformed: bar']) | ||
}) | ||
}) |
@@ -5,3 +5,2 @@ import { FefeError } from '../errors' | ||
export interface ValidateArrayOptions<R> { | ||
elementValidate: Validate<R> | ||
minLength?: number | ||
@@ -13,6 +12,5 @@ maxLength?: number | ||
export function validateArray<R> (_options: ValidateArrayOptions<R> | Validate<R>): (value: unknown) => R[] { | ||
const { elementValidate, minLength, maxLength }: ValidateArrayOptions<R> = typeof _options === 'object' | ||
? _options | ||
: { elementValidate: _options } | ||
export function validateArray<R> ( | ||
elementValidate: Validate<R>, | ||
{ minLength, maxLength }: ValidateArrayOptions<R> = {}): (value: unknown) => R[] { | ||
@@ -19,0 +17,0 @@ return (value: unknown) => { |
@@ -20,3 +20,3 @@ import { FefeError } from '../errors' | ||
definition: D, | ||
{ noExcessKeys = true }: { noExcessKeys?: boolean } = {} | ||
{ allowExcessProperties = false }: { allowExcessProperties?: boolean } = {} | ||
) { | ||
@@ -35,5 +35,5 @@ Object.entries(definition).forEach(([key, definitionValue]) => { | ||
if (noExcessKeys) { | ||
const excessKeys = Object.keys(value).filter(key => !definition[key]) | ||
if (excessKeys.length > 0) throw new FefeError(value, `Key(s) not allowed: ${excessKeys.join(', ')}`) | ||
if (!allowExcessProperties) { | ||
const excessProperties = Object.keys(value).filter(key => !definition[key]) | ||
if (excessProperties.length > 0) throw new FefeError(value, `Properties not allowed: ${excessProperties.join(', ')}`) | ||
} | ||
@@ -40,0 +40,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
86776
1249
175