garn-validator: Create validations with ease
Features
- Supports checking primitives or objects with schemas
- Apply default value if validation fails.
- Easy to use and learn but powerful
- It's totally composable
- Fast and without dependencies
- Six behaviors:
mustBe
returns the value evaluated or it throws. (default export)isValid
returnstrue
orfalse
, never throwsisValidOrLog
returnstrue
orfalse
and log error, never throwshasErrors
returns null or Array of errors, never throwsmustBeOrThrowAll
returns the value evaluated or throws AggregateErrorisValidOrLogAll
returnstrue
orfalse
and log all errors, never throws
- Works with ESModules or CommonJS from Node 10.x or Deno
- Works in all modern browsers
- Works in all frontend frameworks: React, Angular, Vue, etc...
DEPRECATION WARNING:
isValidOrThrow
behavior has been deprecated in favor ofmustBe
. Learn more at mustBe
Example
; const isValidPassword = ; ; // returns true const isValidName = ; // will auto correct to 'anonymous' if fails const isValidAge = ; ; // false, and logs 15 do not match validator (age) => age > 18 // Compositionconst isValidUser = ; const newUser = ; // returns { name: 'anonymous', age: 38, password: '12345zZ?', country: 'ES' } const anotherUser = ; // it throws --> TypeValidationError: At path /password "1234" do not match validator (str) => str.length >= 8
Contents
- Get started
- In depth
- Roadmap
Get started
Node
npm install garn-validator
Import with ES Modules
// default export is mustBe;// or use named export;
Require with CommonJs
const mustBe = ;// or use de default exportconst mustBe = default;
Deno
The library can be used as is in typescript
Import from deno third party modules: deno.land/x/garn_validator
// mod.ts;
To have type definitions you can do:
;;garnValidator as typeof ValidatorTypes; const mustBe = garnValidator;
Basic Usage
; // default export is mustBe const isValidUser = ; ; // returns { name: "garn", age: 38 }; // it throws
Check against constructor
2; // returns 22; // it throws1 2; // returns [1, 2]1 2; // it throws
Learn more in depth at Constructors
Check against primitive
"a"; // returns "a"false; // it throws
Learn more in depth at Primitives
Check string against regex
"a"; // returns "a""b"; // it throws
Learn more in depth at RegExp
Check against custom function
33; // returns 33-1; // wil throwNaN; // returns NaN11; // wil throw
Learn more in depth at Custom function
Check against enums (OR operator)
"a"; // returns "a""c"; // it throws"18"; // returns "18"18; // it throws
Learn more in depth at Enums
Check multiple validations (AND operator)
1 2; // returns [1, 2]100; // it throws
Learn more in depth at Validations in serie (AND operator)
Check object against an schema
const schema = a: Number b: Number ; // a and b are requiredconst obj = a: 1 b: 2 ;obj; // returns obj a: 1 b: 2 ; // returns { a: 1, b: 2 }, because b is not checked a: 1 b: 2 ; // it throws (c is missing) // Optional keys{}; // returns {}
Learn more in depth at Schema
Behaviors
All behaviors run the same algorithm but differs in what returns and how behaves.
There are six behaviors that can be divided in two categories:
-
It stops in first error (bail):
mustBe
returns the value evaluated or it throws. (default export)isValid
returnstrue
orfalse
, never throwsisValidOrLog
returnstrue
orfalse
and log error, never throws
-
It collects all Errors:
hasErrors
returns null or Array of errors, never throwsmustBeOrThrowAll
returns the value evaluated or throws AggregateErrorisValidOrLogAll
returnstrue
orfalse
and log all errors, never throws
mustBe
mustBe
returns the value evaluated or it throws.
let input = "Garn";let userName = input; // return 'Garn'
let input = "Garn";let userName;let isValidName = ;try userName = ; // it throws catch err userName = "anonymous";
mustBe
may have attached an .or() to apply a default value if the validation fail.
let input = "Garn";let isValidName = ;let userName = ; // returns 'anonymous'
The .or() can receive a function to apply a transformation to the original value.
let input = "42";let asNumber = ;let number = ; // returns 42
If you need to apply a function as default, you can use a function the returns a function.
let input = "i am not a function";let {};let mustBeFunction = ;let fn = ; // returns () => {}
It can work nested in a schema
let input = name: "Garn" ;let isValidName = ; let user = input; // { name:'anonymous' }
If the .or() fails the whole validation will fail
let input = "not a valid number";let { let number = NumbermaybeNumber; if number == maybeNumber return number; else throw "not valid number";};let asNumber = ;let number = ; // it throws CastError (aggregateError) with TypeError: not valid number within its array of errors
isValid, isValidOrLog and isValidOrLogAll
isValid
returns true
or false
never throws, so if it fails for any reason you should know it won't tell you anything but false.
; // stops in first Error"g"; // returns true"G"; // returns false, doesn't throws
isValidOrLog
is the same as isValid
but log first error found and stops validating.
isValidOrLogAll
returns true
or false
and log all errors, never throws
; // stops in first Error"g"; // do nothing (but also returns true)"G"; // logs error and return false
hasErrors
hasErrors
returns null or Array with all errors found, never throws.
It's very useful to show the user all errors that need to be fixed.
; // return null or array or errors// checks until the end"g"; // null"G"; // [TypeValidationError, TypeValidationError]
mustBe vs mustBeOrThrowAll
mustBe
returns the value evaluated or it throws the first error found.
try a: null b: null ; catch error error instanceof TypeValidationError; // true errormessage; // At path /a null do not match constructor Number
mustBeOrThrowAll
returns the value evaluated or it throws an AggregateError
with all errors found.
try a: null b: null ; catch error error instanceof AggregateError; // true error instanceof SchemaValidationError; // true console; /* SchemaValidationError: value {"a":null,"b":null} do not match schema {"a":Number,"b":String} */ console; /* [ TypeValidationError: At path /a null do not match constructor Number , TypeValidationError: At path /b null do not match constructor String , ] */
But if it finds only one error, it will throw TypeValidationError
, no AggregateError
try a: null b: "str" ; catch error console; /* TypeValidationError: At path /a null do not match constructor Number , */ error instanceof TypeValidationError; // true error instanceof SchemaValidationError; // false
Learn more at Errors
Utils
The library has a bunch of pre-made validations (and growing), which makes easier validations of stings, numbers, objects or dates.
Learn more at utils test
; let Schema = name: Lowercase birthday: height: creditCard: Numeric books_id: ; let data = name: "garn" birthday: "1982-03-16" height: 170 creditCard: "42424242424242" books_id: "123" "321"; let user = data;
Logical utils
or(...validations)
Is just a shortcut to an enum.
Not to be confused with
mustBe().or()
; ;// same as:;
and(...validations)
is a shortcut to mustBe(...args)
, but semantically useful.
; ;// same as:;
not(...validations)
it negates the validations passed
; // anything but Number"qwerty"; // valid, return 'qwerty'
Object utils
arrayOf()
As we use the array []
as enum, if you need to check the items of an array, you should treat it as an object and check against an schema.
; 1 2 3; // returns [1, 2, 3]1 2 "3"; // throws
In order to not be so ugly you can import arrayOf
from garn-validator as a shortcut to:
export const arrayOf = type => isValid(Array, {[/^\d$/]: type})
; 1 2 3; // returns [1, 2, 3]1 2 "3"; // throws
objectOf
You can import objectOf
from garn-validator as a shortcut to:
export const objectOf = type => isValid(Object, {[/./]: type})
; a: 1 b: 2 ; // returns { a: 1, b: 2 } a: 1 b: "2" ; // throws
In depth
Types of validations
There are six types of validations: Primitives, Constructors, RegExp, Enums, Schemas and Custom functions
Primitives
Checking a primitive is a === comparison
Anything that is not and object in JS is a primitive: Number
, String
, undefined
, null
and Symbol
1; // returns 1 --> 1 === 1 1; // throws, '1' !== 1 1; // throws, 1n !== 1 null; // throws, undefined !== null // keep in mind than a symbol is only equal to itselflet s = Symbol;s; // returns sSymbol; // throws
Constructors
Checking against a constructor means to know if the value evaluated has been created from that constructor
2; // (2).constructor === Number --> trueSymbol; // ok
A valid constructor is a class
or any built-in constructor.
{}let honda = ;honda; // honda.constructor === Car --> true
You can't use a normal function used as constructor from the old JS times.
{ thisname = name;}let honda = "honda";honda; // throws. Car is detected as custom validator function
All built in Constructors are supported
Proxy detection
NOT YET WORKING IN DENO
In order to detect any Object (or Array) is a Proxy we intercept the creation of Proxies.
To have that functionality you must import "garn-validator/src/proxyDetection.js"
before any creation of Proxies you need to detect;
; const target = a: 1 ;const proxy = target 33; proxy; // returns proxy
without running garn-validator/src/proxyDetection.js
// NO IMPORTconst target = a: 1 ;const proxy = target 33; proxy; // fails
RegExp
The perfect validator to check strings. It does what you expect:
let isLowerCased = ; ; // /^[a-z]+$/.test('honda') --> true// or building a regexp with the constructor RexExp;"honda"; // true
Custom function
Any function that is not a constructor is treated as custom validator.
It must return any truthy value in order to pass the validation.
10; // returns 10-10; // throws 10; // returns 1010; // returns 10
To fail a validation may return a falsy value or throw an error.
If it returns a falsy value, the default error will be thrown: TypeValidationError
If it throws an error, that error will be thrown.
10; // throws TypeValidationError10; // throws TypeValidationError 10; // throws RangeError 10; // throws 'ups'
Enums
Enums works as OR operator. Must be an array which represent all options.
let cities = "valencia" "new york" "salzburg";"valencia"; // returns "valencia""madrid"; // throws
But it's much more powerful than checking against primitives. It can contain any type of validator.
It checks every item until one passes.
let isNumberOrBigInt = Number BigInt; // must be Number or BigInt1n; // returns 1n1; // returns 1 let isFalsy = 0 "" null undefined false;""; // returns "" let isNumberAlike = Number val === Numberval;1n; // returns 1n1; // returns 1"1"; // returns "1"
Schema
An Schema is just a plain object telling in each key which validation must pass.
let schema = name: String // required and be a Number age > 18 // required and be a greater than 18 tel: Number String // required and be Number or String role: "admin" "user" // required and be 'admin' or 'user' credentials: // must be and object and will be validate with this "subSchema" pass: String email: String ;let obj = name: "garn" age: 20 tel: "+34617819234" role: "admin" credentials: pass: "1234" email: "email@example.com" ;obj; // returns obj
Only the keys in the schema will be checked. Any key not present in the schema won't be checked (under consideration to be changed)
a: 1 ; // returns { a: 1 } , a is not in the schema
Optional Keys
And optional key must be undefined
, null
, or pass the validation
x: 1 ; // returns { x: 1 }, x is present and is Number x: 1 ; // it throws, x is present but is not String {}; // returns {}, x is undefined x: undefined ; // returns { x: undefined }, x is undefined x: null ; // returns { x: null }, x is null
You can use key$
or 'key?'
. It would be nicer to have key?
without quotes but is not valid JS
{}; // returns {}
Regexp keys
You can validate multiple keys at once using a regexp key
a: "a" b: "b"; // ok // or write it as plain string a: "a" b: 1 // fails; // throws // only checks the keys that matches regex x: 1 y: 2 z: 3 CONSTANT: "foo" // not checked; // ok, all lowercased keys are numbers
The required keys and optional won't be check against a regexp key
x: "x" // not checked as Number, checked as String; // ok, x is String x: "x" // not checked as Number, checked as String y: "y" // checked as Number, fails; // throw x: "x" // not checked as Number, checked as String y: "y" // checked as String z: "z" // checked as Number, fails; // throw
This feature is perfect to note that any key not specified in schema is not allowed
x: "x" y: "y" // fails;
Custom validation used in schemas
When using a custom validator inside an schema will be run with 3 arguments: (value, root, keyName) => {}
- value: the value present in that key from the object
- root: the whole object, not matter how deep the validation occurs
- keyName: the name of the key to be checked.
// against root obj max: 1 min: -1; // ok max: 10 min: 50; // it throws // all key must be at least 3 characters max: 1 // key too short longKey: 1 // valid key; // it throws, max key is too short
Validations in serie (AND operator)
The validator constructor can receive as many validations as needed.
All will be checked until one fails
const isArrayOfLength2 = ;; // returns [1, 2] 100; // it throws
const isValidPassword = ; ; // returns "12345wW-"; // fails
Errors
If a validation fails it will throw new TypeValidationError(meaningfulMessage)
which inherits from TypeError
. It can be imported.
If it throws an error from a custom validator, that error will be thrown.
; try 33; catch error error instanceof TypeValidationError; // true error instanceof TypeError; // true try 33; catch error error === "ups"; // true try 33; catch error error instanceof RangeError; // true error instanceof TypeError; // false
AggregateError
There are 3 types a AggregateError
that can be thrown:
SchemaValidationError
: thrown when more than one key fails checking an schemaEnumValidationError
: thrown when all validations fails checking an enumSerieValidationError
: thrown when more than one validation fails checking an Serie
All of them inherits from AggregateError
and has a property errors with an array of all errors collected
try null; catch error error instanceof AggregateError; // true console; /* [ TypeValidationError: value null do not match constructor Number , TypeValidationError: value null do not match constructor String , ] */
SchemaValidationError
If using mustBeOrThrowAll more than one key fails checking an Schema , it will throw a SchemaValidationError with all Errors aggregated in error.errors.
If only one key fail it will throw only that Error (not an AggregateError)
SchemaValidationError inherits from AggregateError,
But if using
mustBe
only the first Error will be thrown.
// more than 2 keys failstry {}; catch error console; // true console; // true console; // 2 // only 1 key failstry {}; catch error console; // true console; // false
EnumValidationError
If all validations of an enum fails, it will throw a EnumValidationError with all Errors aggregated in error.errors
But if the length of the enum is 1, it will throw only that error.
EnumValidationError inherits from AggregateError.
try 1; catch error console; // true console; // true try 1; catch error console; // false console; // true
SerieValidationError
If using mustBeOrThrowAll fails all validations of a serie , it will throw a SerieValidationError with all Errors aggregated in error.errors
But if the length of the enum is 1. it will throw only this error.
SerieValidationError inherits from AggregateError.
try 1; catch error console; // true console; // true try 1; catch error console; // false console; // true
hasErrors
hasErrors
will flatMap all errors found. No AggregateError will be in the array returned.
"g"; // null"G";/*[ TypeValidationError: value "G" do not match regex /[a-z]/, TypeValidationError: value "G" do not match constructor Number ,]*/ a: null b: null ;/*[ TypeValidationError: At path /a null do not match constructor Number, TypeValidationError: At path /b null do not match constructor String]*/
Raw Error data
All errors the library throws has the raw data collected in a property called raw
.
try a: null ; catch error console;/*{ // the validation failing type: [Function: Number], // the value evaluated value: null, // the root object root: { a: null }, // the key failing keyName: 'a', // the whole path where the evaluation happens as an array path: [ 'a' ], // the error message message: 'At path /a null do not match constructor Number', // the error constructor '$Error': [class TypeValidationError extends TypeError], // the behavior applied behavior: { collectAllErrors: false, onValid: [Function: onValidDefault], onInvalid: [Function: onInvalidDefault] },} */
Composition in depth
You can create your own validators and use them as custom validation creating new ones.
const isPositive = ;const isNotBig = ;const isNumber = ; "10"; // returns "10"200; // it throws
When used inside another kind of behavior, it will inherit the behavior from where it has been used.
const isNotBig = ;// its normal behavior; // false, logs '200 do not match validator (v) => v < 100' 200; // false , and won't log200; // fails , and won't log200; // array, won't log/*[ new TypeValidationError('200 do not match validator (v) => v < 100')] */
Actually, it's not treated as a custom validation function. No matter is your are using hasErrors
which return null when nothing fails, and it's just works.
const isBigNumber = ; // its normal behavior;/* [ new TypeValidationError(""a12" do not match validator (num) => num == Number(num)"), new TypeValidationError(""a12" do not match validator num => num > 1000"),]; */ // inherit behavior"a12"; // false, and log only one error value "a10" do not match validator (num) => num == Number(num)
Especial cases
AsyncFunction & GeneratorFunction
AsyncFunction
and GeneratorFunction
constructors are not in the global scope of any of the three JS environments (node, browser or deno). If you need to check an async function or a generator you can import them from garn-validator.
Note: Async functions and generators are not normal function, so it will fail against Function constructor
; async {}; // ok {}; // ok {}; // throws {}; // throws
Roadmap
- Check value by constructor
- Enum type
- Shape type
- Custom validation with a function (value, root, keyName)
- Check RegEx
- Match object key by RegEx
- Multiples behaviors
- ArrayOf & objectOf
- Multiples validations
isValid(String, val => val.length > 3, /^[a-z]+$/ )('foo')
- Schema with optionals key
{ 'optionalKey?': Number }
or{ optionalKey$: Number }
- Setting for check all keys (no matter if it fails) and return (or throw) an array of errors
- Support for deno
- Support for browser
- Behavior applyDefaultsOnError. (syntax
mustBe(Number).or(0)
) - Async validation support
- More built-in utils functions