Security News
Supply Chain Attack Detected in Solana's web3.js Library
A supply chain attack has been detected in versions 1.95.6 and 1.95.7 of the popular @solana/web3.js library.
Light, expressive and type-safe data validation in typescript.
As you can see, you DON'T NEED to write your schema twice (for validation and for typescript). Just use validator functions, which return well typed results. So typescript is acknowledged about shape of your data and checks its usages on compilation stage.
Because I wanted to have type validation which:
Super light and easy
Library is small. No core with tons of unnecessary functionality. Each validator is exported separately. So if you use tree shaking in your project, only validators used by you will be in your bundle.
Written with typescript in mind
It always returns typed results
Support of transformations
E.g. I can convert strings to numbers on the fly, if I want to do so
Easy extensible with custom validators
Validator is a function. Adding new one is as simple, as writing new function.
Although, it's really easy to create your own validators, library exports few ready to go ones.
Checks if value is an integer.
Possible options:
const validator1 = int()
validator1(5, 'myValue'); // returns: 5
const validator2 = int() // returns: 5
validator2('5', 'myValue'); // throws: [myValue] should be an integer
const validator3 = int({min: 0, max: 4});
validator3(5, 'myValue'); // throws: [myValue] is larger than the allowed maximum (4)
The same as int but also tries to convert string to integer, if value is string.
It's convenient for validating/parsing url params, which initially are strings.
Checks if value is a finite number.
Possible options:
const validator1 = float()
validator1(5.2, 'myValue'); // returns: 5
const validator2 = float();
validator2('5.2', 'myValue'); // throws: [myValue] should be a float
const validator3 = float({min: 0, max: 4});
validator3(5.2, 'myValue'); // throws: [myValue] is larger than the allowed maximum (4)
The same as float but also tries to convert string to float, if value is string.
It's convenient for validating/parsing url params, which initially are strings.
Checks if value is a string.
Possible options:
const validator1 = string()
validator1('aaa', 'myValue'); // returns: "aaa"
const validator2 = string()
validator2({}, 'myValue'); // throws: [myValue] should be a string
const validator3 = string({max: 3});
validator3('aaaa', 'myValue'); // throws: [length(myValue)] is larger than the allowed maximum (4)
const validator4 = string({patten: /^[a-z]{3}$/i});
validator4('aaaa', 'myValue'); // throws: [myValue] doesn't match the pattern
Checks if value is a boolean.
const validator1 = boolean()
validator1(true, 'myValue'); // returns: true
const validator2 = boolean()
validator2('true', 'myValue'); // throws: [myValue] should be a boolean
The same as boolean but also converts strings ("1", "true", "yes") to true, and ("0", "false", "no") to false.
It's convenient for validating/parsing url params, which initially are strings.
Check if a value is an object and runs validation on all of its properties by given shape.
Receives 1 or 2 parameters.
First one should be an object. Values of this object should be validators, which will be called to check properties.
const validator = object({
a: int(),
b: optional(string({max: 3})),
c: v => v,
});
validator({a: 5, c: 'anystring'}, 'myValue'); // returns: {a: 5, b: undefined, c: 'anystring'}
validator({a: 5, b: 25, c: 'anystring'}, 'myValue'); // throws: [myValue.b] should be a string
validator('something totally different', 'myValue'); // throws: [myValue] should be an object
There are additional options can be passed with second param:
const validator = object({
a: int(),
b: optional(string({max: 3})),
c: v => v,
}, {
ignoreUnknown: true,
min: 2,
max: 3,
});
Checks if value is an array and checks each of its elements with given validator.
You can also pass options in second parameter:
const validator1 = arrayOf(string());
validator1(['1', '2', '3'], 'myValue').toEqual(['1', '2', '3']); // returns: ['1', '2', '3']
const validator2 = arrayOf(int());
validator2({a: 2}, 'myValue'); // throws: [myValue] should be an array
const validator3 = arrayOf(int());
validator3([1, 2, '3'], 'myValue'); // throws: [myValue[3]] should be an integer
const validator4 = arrayOf(int(), {max: 2});
validator4([1, 2, 3, 'abc'], 'myValue'); // throws: [length(myValue)] is larger than the allowed maximum (2)
const validator5 = arrayOf(
object({
a: string(),
}),
);
validator5([{a: 'aa'}], 'myValue'); // returns: [{a: 'aa'}]
validator5([{a: 2}], 'myValue'); // throws: [myValue[0].a] should be a string
Checks if value strictly equals one of the given values.
const validator1 = exact(1);
validator1(1, 'myValue'); // returns: 1
const validator2 = exact(1)
validator2('1', 'myValue'); // throws: [myValue] isn't equal to the expected value
const validator3 = exact({a: 1});
validator3({a: 1}, 'myValue'); // throws: [myValue] isn't equal to the expected value
// it's because it doesn't call deepEqual, but uses with strict equality
Checks if value strictly equals one of the given values.
const validator1 = oneOf([1, 2, '3'] as const);
validator1('3', 'myValue'); // returns: 3
const validator2 = oneOf([1, 2, '3'] as const)
validator2(3, 'myValue'); // throws: [myValue] isn't equal to any of the expected values
const validator3 = oneOf([1, 2, {a: 1}] as const);
validator3({a: 1}, 'myValue'); // throws: [myValue] isn't equal to any of the expected values
// it's because it doesn't call deepEqual, but uses with strict equality
If you want typescript to make a type with exactly your values, add "as const" after array, to let typescript know - your array is readonly:
const validator1 = oneOf([1, 2, '3'] as const);
const value1 = validator1(1, 'myValue');
// here type of value1 = 1 | 2 | "3"
const validator2 = oneOf([1, 2, '3']);
const value2 = validator1(1, 'myValue');
// but here type of value2 = number | string
// as typescript recognizes type of your values array as Array<string | number> in this case
Checks a value with the help of every of passed validators, one by one. Returns value from first validator, which doesn't fail.
If all validators failed, throws an error.
const validator = alternatives([
string(),
object({
a: string(),
}),
]);
validator({a: '5'}, 'myValue'); // returns: {a: '5'}
validator({a: 5}, 'myValue');
// throws: [myValue] doesn't match any of allowed alternatives:
// 0: [*] should be a string
// 1: [*.a] should be a string
Add undefined as a possible value to given validator. Although, it can be used itself, it's very helpful for optional values in objects.
const validator = object({
a: optional(string()),
});
validator({}, 'myValue'); // returns: {a: undefined}
validator({a: '123'}, 'myValue'); // returns {a: '123'}
validator({a: 456}, 'myValue'); // throws: [myValue.a] should be a string
validator({a: null}, 'myValue'); // throws: [myValue.a] should be a string
// (null is not the same as undefined)
Don't forget to enable typescript strictNullChecks config, if you want typescript to check null and undefined in resulting types.
Similar to optional. But instead of undefined, it allows value be null.
Don't forget to enable typescript strictNullChecks config, if you want typescript to check null and undefined in resulting types.
If you want to have optional and nullable value (so null and undefined will be allowed), you can make a composition of optional(nullable(...))
.
If value is undefined, returns some default value passed as first parameter.
Otherwise, runs validator given in second parameter.
const validator = object({
a: defaultValue('123', string()),
});
validator({}, 'myValue'); // returns: {a: '123'}
validator({a: 'uuu'}, 'myValue'); // retunrs: {a: 'uuu'}
validator({a: ''}, 'myValue'); // returns: {a: ''}
validator({a: null}, 'myValue'); // throws: [myValue.a] should be a string
Checks if value is a valid uuid string.
Checks if value is a valid email string.
There isn't "any" type, but you can easily emulate it using v => v
.
const validator = object({
stringProp: strint(),
anyProp: v => v,
});
Sometimes you may want to transform value after some validation and pass it to next validator.
// Let's imagine we want to validate some id, which can be represented by string like "user:1" or by object
// like {type: 'user', id: '1'}
// we can make one validator for that, which returns a string for both cases
const validator = alternatives([
string({pattern: /^[a-z0-9]+\:[a-z0-9]+$/}),
transform(
object({
id: string({min: 1}),
type: string({min: 1}),
}),
obj => `${obj.type}:${obj.id}`,
),
]);
validator('user:1', 'myValue'); // returns: "user:1"
validator({type: 'user', id: '1'}, 'myValue'); // returns "user:1"
validator({type: 'user'}, 'myValue');
// throws: [myValue] doesn't match any of allowed alternatives:
// 0: [*] should be a string
// 1: [*.id] should be a string
validator('asd', 'myValue');
// throws: [myValue] doesn't match any of allowed alternatives:
// 0: [*] doesn't match the pattern
// 1: [*] should be an object
In the case of validation failed, validator throws instance of ValidationError. So, it's easy to catch it.
import {ValidationError, int} from './index';
const validator = int();
try {
validator('a', 'myValue');
} catch (err) {
if (err instanceof ValidationError) {
//...
}
// OR
if (err.name === 'ValidationError') {
//...
}
}
The idea here is simple. To check any type of value you should to create a validator.
Validator is a function which receives 2 parameters: value to validate and a path.
type Validator<T> = (v: any, path: string) => T;
In case of validation error, validator should throw a ValidationError, using path to point at a place of shape, where validation failed.
In case of success validator should return a value. Validator can return the same value which was received. Or some another value, if it should be modified on the fly.
You can make inline validators. E.g.:
const myValidator = object({
id: string(),
type: (v, path): string => {
if (typeof v !== 'string' || !v.startsWith('TYPE__')) {
throw new ValidationError(`[${path}] is not correct type`);
}
return v;
}
});
If you want to crate reusable validator, the convention is to wrap it into one more function, which can additionally receive options as parameters. E.g. int validator can look like:
export const int = ({min, max}: {min?: number; max?: number} = {}): Validator<number> => (v, path) => {
if (!Number.isInteger(v)) {
throw new ValidationError(`[${path}] should be an integer`);
}
if ((min !== undefined && v < min) || (max !== undefined && v > max)) {
throw new ValidationError(`[${path}] isn\'t in allowed range`);
}
return v;
};
Let's create one more validator, which checks, does value exactly match given example:
const exactMatch = <T>(exactValue: T): Validator<T> => (v, path) => {
if (v !== exactValue) {
throw new Error(`[${path}] isn't the same as allowed value`);
}
return v;
};
We can use it itself:
// let's check value
const validator = exactMatch(5);
validator(10, 'myValue'); // throws: [myValue] isn't the same as allowed value
... or let's combine it with other validators
import {object, optional} from 'checkeasy';
const validator = object({
a: exactMatch(5),
b: optional(exactMatch(7)),
c: exactMatch(10),
});
validator({a: 5, c: 10}, 'myValue'); // returns: { a: 5, b: undefined, c: 10 }
validator({a: 5, c: 12}, 'myValue'); // throws: [myValue.c] isn't the same as allowed value
Thank you joshuafcole for some changes here
ISC
Copyright 2021 Roman Ditchuk
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
FAQs
Light, expressive and type-safe data validation in typescript
We found that checkeasy demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 0 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
A supply chain attack has been detected in versions 1.95.6 and 1.95.7 of the popular @solana/web3.js library.
Research
Security News
A malicious npm package targets Solana developers, rerouting funds in 2% of transactions to a hardcoded address.
Security News
Research
Socket researchers have discovered malicious npm packages targeting crypto developers, stealing credentials and wallet data using spyware delivered through typosquats of popular cryptographic libraries.