Trava stands for TRAnsform and VAlidate. Inspired by struct.
32 |The main goal of the library is to provide highly extendable and customizable way of JavaScript entities validation. Validation often goes along with parsing or transforming values. So coercion feature is included by design into Trava.
33 | 34 | 35 |Features
36 |-
37 |
- easy to use 38 |
- no external dependencies 39 |
- supports all major browsers and IE11+ 40 |
- customizable and extendable 41 |
Install
45 |Install from npm:
46 |npm install trava
47 | And import or require:
48 |import Trava from 'trava';
49 | or use CDN:
50 |<script src="https://unpkg.com/trava"></script>
51 | For modern browsers es201X builds are available (trava.es.js
and trava.es.min.js
).
Getting Started
55 | Let's imaging the most obvious and simple validator. Probably it will look like: 56 |function validate (value) {
57 | // any checking...
58 | if (!check(value)) return false;
59 | return true;
60 | }
61 | But in real scenarios we'd also like to get some error details, e.g.:
62 | function validate (value) {
63 | if (!check1(value)) return 'ERROR_1';
64 | if (!check2(value)) return 'ERROR_2';
65 | return true;
66 | }
67 | We may stop here and probably you don't need any library to do validation this way, may be just some primitives for common cases. But there is one more feature we might want to get and when Trava could help. While using JSON it's often needed to parse or convert values after validation. Code for parsing looks pretty similar to validation: its just validate
being replaced with parse
. In Trava these steps are united together. To support both validation and transformation we need to distinguish error from transformed value returned from validator. Probably we could use js Error
, but it works only with string messages. Fortunately Trava has own ValidationError
to support complex errors.
68 |
So Trava validator looks like:
70 |function validate (value) {
71 | if (!check1(value)) return new Trava.ValidationError({ code: 401 });
72 | if (!check2(value)) return new Trava.ValidationError({ code: 405 });
73 | return parseOrTransform(value); // apply some parse or transform
74 | }
75 | Then use validator:
76 |const result = validate(data);
77 | if (result instanceof Trava.ValidationError) {
78 | // note using `data` property to extract error data
79 | console.log('This is error!', result.data);
80 | } else {
81 | const goodTransformedValue = result;
82 | }
83 |
84 | That's all you have to know to start using Trava. It is very simple. The second advantage why to use Trava is a collection of helpful operators out of the box. See examples to learn how to use them.
85 | 86 | 87 |Operators
88 | 102 |Despite operators itself are just functions, it's convinient to use Trava
to build validators or validate data directly:
import Trava from 'trava';
104 | let validator = Trava(validators);
105 | let values = validator(data);
106 |
107 | // or validate directly
108 | values = Trava(validators, data);
109 |
110 |
111 | Compose
112 |Compose is used to combine several validators into one. E.g.:
113 |const validator1 = n => n < 10 ? n : new ValidationError('BAD NUMBER');
114 | const validator2 = ...;
115 | const validator3 = ...;
116 | const composedValidator = Trava.Compose([validator1, validator2, validator3]);
117 | // then `composedValidator` could be used just like simple validator
118 |
119 | Validation goes consequently from left to right. When an error occurs, validation stops and an error is returned immediately.
120 |When used inside other operators explicit call Trava.Compose
could be omitted. E.g.:
const composedValidator = Trava.Required(Trava.Compose([v1, v2, v3]));
122 | // is same as
123 | const composedValidator = Trava.Required([v1, v2, v3]);
124 |
125 |
126 | Required
127 |Required
is a guard to check if a value is defined (!== undefined
):
const validator = ...;
129 | const requiredValidator = Trava.Required(validator);
130 |
131 | let value;
132 | console.log(requiredValidator(value)); // ValidationError('Value is required')
133 |
134 | value = 'any';
135 | console.log(requiredValidator(value)); // `Required` is bypassed
136 |
137 | Custom error message can be set by providing it as a second argument:
138 |const requiredValidator = Trava.Required(validator, 'My custom error message');
139 | Or set the default for all validators:
140 |Trava.Required.ErrorMessage = 'My default required error';
141 | Error message also could be a function which should return error data and will be called with same arguments as validator when error occurs.
142 |const validator = Trava.Check(v => v > 0, v => `${v} is not positive number!`);
143 |
144 | Optional
145 |Optional
checks if value is not defined then returns value or default value which can be provided as a second argument:
const optionalValidator = Trava.Optional(validator, 'default value');
147 |
148 | Nullable
149 |Nullable
is just like Optional except it also checks if value is not null
.
Check
152 |Check
is helper to reuse validators which return boolean:
153 | const myExistingValidator = (v) => v < 10;
154 | const travaValidator = Trava.Check(myExistingValidator);
155 |
156 | console.log(travaValidator(20)); // ValidationError('Incorrect value')
157 |
158 | Custom error message can be set by providing it as a second argument:
159 |const checkValidator = Trava.Check(n => Boolean(n), 'My custom error message');
160 | Or use the following to set the default one:
161 |Trava.Check.ErrorMessage = 'My default check error';
162 |
163 | Enum
164 |Enum
checks if value exists in enum:
165 | const enumValidator = Trava.Enum(['a', 'b']);
166 | console.log(enumValidator('c')); // ValidationError('Incorrect value')
167 |
168 | Just like Check
it accepts an error message as a second argument.
Const
171 |Const
checks if value equals:
172 | const constValidator = Trava.Const('a');
173 | console.log(constValidator('c')); // ValidationError('Incorrect value')
174 |
175 | Just like Check
it accepts an error message as a second argument.
Each
178 |Each
is usefull for validating uniform data structures (so far works only with arrays). Errors are aggregated in object by keys (or indices):
const elementValidator = n => n < 10 ? n : new ValidationError('BAD NUMBER');
180 | const arrayValidator = Trava.Each(elementValidator);
181 |
182 | console.log(arrayValidator([1, 15, 7, 5, 20]));
183 | // ValidationError({2: 'BAD NUMBER', 4: 'BAD NUMBER'})
184 |
185 |
188 |
189 | Keys
190 |Keys
is used to validate Objects
:
const objectValidator = Trava.Keys({
192 | a: Trava.Check(a => a >= 0),
193 | b: Trava.Required(Trava.Check(b => b.startsWith('nice'), 'HEY, think positive!')),
194 | });
195 |
196 | console.log(objectValidator({
197 | a: -1,
198 | b: 'bad wrong error'
199 | }));
200 | // ValidationError({
201 | // a: 'Incorrect value'.
202 | // b: 'HEY, think positive!',
203 | // })
204 |
205 | When used inside other operators explicit call Trava.Keys
could be omitted. E.g.:
const objectValidator = Trava.Required({
207 | a: Trava.Check(a => a >= 0),
208 | b: Trava.Check(b => b < 0),
209 | });
210 |
211 |
214 |
215 | Some
216 |Some
is like Compose but tries to find first non error.
const someValidator = Trava.Some([
218 | Trava.Check(isString, 'Not a string'),
219 | Trava.Check(isNumber, 'Not a number'),
220 | ]);
221 |
222 | console.log(someValidator(1)); // 1
223 | console.log(someValidator('a')); // 'a'
224 | console.log(someValidator({})); // ValidationError('Not a number') <-- latest error
225 |
226 |
227 | Examples
228 |const t = require('trava');
229 | const { Required, Optional, Each, Enum, Check, Keys, Some, ValidationError } = t;
230 |
231 | const isString = s => typeof s === 'string';
232 | const isEmail = s => /^\S+@\S+\.\S+$/.test(s);
233 |
234 | const validateForm = t({
235 | username: Check(un => isString(un) && /^\w{3,30}$/.test(un), 'CUSTOM ERROR: INVALID USERNAME!'), // required by default
236 | password: Optional(Check(pwd => isString(pwd) && pwd.length >= 6)),
237 | access_token: Optional(Some([isString, Number.isInteger]), 'default token'),
238 | birthyear: Optional(Check(b => Number.isInteger(b) && 1900 <= b && b <= 2018)),
239 | email: Optional(Check(isEmail)),
240 | contacts: Optional(Each({
241 | name: Enum(['phone', 'email']),
242 | value: Check(isString),
243 | }))
244 | });
245 |
246 | const values = validateForm({
247 | username: 'HelloTrava',
248 | password: 'secretoversecret',
249 | birthyear: 1990,
250 | });
251 | if (values instanceof ValidationError) {
252 | console.error('FORM ERRORS', values.data);
253 | } else {
254 | console.log('FORM VALUES', values);
255 | }
256 |
257 |