UNPKG

19.4 kBJavaScriptView Raw
1(function (global, factory) {
2 typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
3 typeof define === 'function' && define.amd ? define(['exports'], factory) :
4 (global = global || self, factory(global.Superstruct = {}));
5}(this, (function (exports) { 'use strict';
6
7 function _defineProperty(obj, key, value) {
8 if (key in obj) {
9 Object.defineProperty(obj, key, {
10 value: value,
11 enumerable: true,
12 configurable: true,
13 writable: true
14 });
15 } else {
16 obj[key] = value;
17 }
18
19 return obj;
20 }
21
22 function ownKeys(object, enumerableOnly) {
23 var keys = Object.keys(object);
24
25 if (Object.getOwnPropertySymbols) {
26 var symbols = Object.getOwnPropertySymbols(object);
27 if (enumerableOnly) symbols = symbols.filter(function (sym) {
28 return Object.getOwnPropertyDescriptor(object, sym).enumerable;
29 });
30 keys.push.apply(keys, symbols);
31 }
32
33 return keys;
34 }
35
36 function _objectSpread2(target) {
37 for (var i = 1; i < arguments.length; i++) {
38 var source = arguments[i] != null ? arguments[i] : {};
39
40 if (i % 2) {
41 ownKeys(Object(source), true).forEach(function (key) {
42 _defineProperty(target, key, source[key]);
43 });
44 } else if (Object.getOwnPropertyDescriptors) {
45 Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
46 } else {
47 ownKeys(Object(source)).forEach(function (key) {
48 Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
49 });
50 }
51 }
52
53 return target;
54 }
55
56 function _objectWithoutPropertiesLoose(source, excluded) {
57 if (source == null) return {};
58 var target = {};
59 var sourceKeys = Object.keys(source);
60 var key, i;
61
62 for (i = 0; i < sourceKeys.length; i++) {
63 key = sourceKeys[i];
64 if (excluded.indexOf(key) >= 0) continue;
65 target[key] = source[key];
66 }
67
68 return target;
69 }
70
71 function _objectWithoutProperties(source, excluded) {
72 if (source == null) return {};
73
74 var target = _objectWithoutPropertiesLoose(source, excluded);
75
76 var key, i;
77
78 if (Object.getOwnPropertySymbols) {
79 var sourceSymbolKeys = Object.getOwnPropertySymbols(source);
80
81 for (i = 0; i < sourceSymbolKeys.length; i++) {
82 key = sourceSymbolKeys[i];
83 if (excluded.indexOf(key) >= 0) continue;
84 if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue;
85 target[key] = source[key];
86 }
87 }
88
89 return target;
90 }
91
92 /**
93 * Convert a validation result to an iterable of failures.
94 */
95 function toFailures(result, context) {
96 if (result === true) {
97 return [];
98 } else if (result === false) {
99 return [context.fail()];
100 } else {
101 return result;
102 }
103 }
104
105 /**
106 * `Struct` objects encapsulate the schema for a specific data type (with
107 * optional coercion). You can then use the `assert`, `is` or `validate` helpers
108 * to validate unknown data against a struct.
109 */
110
111 class Struct {
112 constructor(props) {
113 const {
114 type,
115 schema,
116 coercer = value => value,
117 validator = () => [],
118 refiner = () => []
119 } = props;
120 this.type = type;
121 this.schema = schema;
122 this.coercer = coercer;
123 this.validator = validator;
124 this.refiner = refiner;
125 }
126
127 }
128 /**
129 * `StructError` objects are thrown (or returned) by Superstruct when its
130 * validation fails. The error represents the first error encountered during
131 * validation. But they also have an `error.failures` property that holds
132 * information for all of the failures encountered.
133 */
134
135 class StructError extends TypeError {
136 constructor(failure, iterable) {
137 const {
138 path,
139 value,
140 type,
141 branch
142 } = failure,
143 rest = _objectWithoutProperties(failure, ["path", "value", "type", "branch"]);
144
145 const message = `Expected a value of type \`${type}\`${path.length ? ` for \`${path.join('.')}\`` : ''} but received \`${JSON.stringify(value)}\`.`;
146
147 function* failures() {
148 yield failure;
149 yield* iterable;
150 }
151
152 super(message);
153 this.value = value;
154 Object.assign(this, rest);
155 this.type = type;
156 this.path = path;
157 this.branch = branch;
158 this.failures = failures;
159 this.stack = new Error().stack;
160 this.__proto__ = StructError.prototype;
161 }
162
163 }
164 /**
165 * Assert that a value passes a `Struct`, throwing if it doesn't.
166 */
167
168 function assert(value, struct) {
169 const result = validate(value, struct);
170
171 if (result[0]) {
172 throw result[0];
173 }
174 }
175 /**
176 * Coerce a value with the coercion logic of `Struct` and validate it.
177 */
178
179 function coerce(value, struct) {
180 const ret = struct.coercer(value);
181 assert(ret, struct);
182 return ret;
183 }
184 /**
185 * Check if a value passes a `Struct`.
186 */
187
188 function is(value, struct) {
189 const result = validate(value, struct);
190 return !result[0];
191 }
192 /**
193 * Validate a value against a `Struct`, returning an error if invalid.
194 */
195
196 function validate(value, struct, coercing = false) {
197 if (coercing) {
198 value = struct.coercer(value);
199 }
200
201 const iterable = check(value, struct);
202 const [failure] = iterable;
203
204 if (failure) {
205 const error = new StructError(failure, iterable);
206 return [error, undefined];
207 } else {
208 return [undefined, value];
209 }
210 }
211 /**
212 * Check a value against a `Struct`, returning an iterable of failures.
213 */
214
215 function* check(value, struct, path = [], branch = []) {
216 const {
217 type
218 } = struct;
219 const ctx = {
220 value,
221 type,
222 branch,
223 path,
224
225 fail(props = {}) {
226 return _objectSpread2({
227 value,
228 type,
229 path,
230 branch: [...branch, value]
231 }, props);
232 },
233
234 check(v, s, parent, key) {
235 const p = parent !== undefined ? [...path, key] : path;
236 const b = parent !== undefined ? [...branch, parent] : branch;
237 return check(v, s, p, b);
238 }
239
240 };
241 const failures = toFailures(struct.validator(value, ctx), ctx);
242 const [failure] = failures;
243
244 if (failure) {
245 yield failure;
246 yield* failures;
247 } else {
248 yield* toFailures(struct.refiner(value, ctx), ctx);
249 }
250 }
251
252 /**
253 * Augment a `Struct` to add an additional coercion step to its input.
254 */
255
256 function coercion(struct, coercer) {
257 const fn = struct.coercer;
258 return new Struct(_objectSpread2(_objectSpread2({}, struct), {}, {
259 coercer: value => {
260 return fn(coercer(value));
261 }
262 }));
263 }
264 /**
265 * Augment a struct to coerce a default value for missing values.
266 *
267 * Note: You must use `coerce(value, Struct)` on the value before validating it
268 * to have the value defaulted!
269 */
270
271 function defaulted(S, fallback, strict) {
272 return coercion(S, x => {
273 const f = typeof fallback === 'function' ? fallback() : fallback;
274
275 if (x === undefined) {
276 return f;
277 }
278
279 if (strict !== true && isPlainObject(x) && isPlainObject(f)) {
280 const ret = _objectSpread2({}, x);
281
282 let changed = false;
283
284 for (const key in f) {
285 if (ret[key] === undefined) {
286 ret[key] = f[key];
287 changed = true;
288 }
289 }
290
291 if (changed) {
292 return ret;
293 }
294 }
295
296 return x;
297 });
298 }
299 /**
300 * Coerce a value to mask its properties to only that defined in the struct.
301 */
302
303 function masked(S) {
304 return coercion(S, x => {
305 if (!isPlainObject(x)) {
306 return x;
307 }
308
309 const ret = {};
310
311 for (const key in S.schema) {
312 ret[key] = x[key];
313 }
314
315 return ret;
316 });
317 }
318 /**
319 * Check if a value is a plain object.
320 */
321
322 function isPlainObject(value) {
323 if (Object.prototype.toString.call(value) !== '[object Object]') {
324 return false;
325 }
326
327 const prototype = Object.getPrototypeOf(value);
328 return prototype === null || prototype === Object.prototype;
329 }
330
331 /**
332 * Augment a string or array struct to constrain its length to being between a
333 * minimum and maximum size.
334 */
335
336 function length(S, min, max) {
337 return refinement(S, `${S.type} & Length<${min},${max}>`, value => {
338 return min < value.length && value.length < max;
339 });
340 }
341 /**
342 * Refine a string struct to match a specific regexp pattern.
343 */
344
345 function pattern(S, regexp) {
346 return refinement(S, `${S.type} & Pattern<${regexp.source}>`, value => {
347 return regexp.test(value);
348 });
349 }
350 /**
351 * Augment a `Struct` to add an additional refinement to the validation.
352 */
353
354 function refinement(struct, type, refiner) {
355 const fn = struct.refiner;
356 return new Struct(_objectSpread2(_objectSpread2({}, struct), {}, {
357 type,
358
359 *refiner(value, fail) {
360 yield* toFailures(fn(value, fail), fail);
361 yield* toFailures(refiner(value, fail), fail);
362 }
363
364 }));
365 }
366
367 /**
368 * Validate any value.
369 */
370
371 function any() {
372 return struct('any', () => true);
373 }
374 /**
375 * Validate that an array of values of a specific type.
376 */
377
378 function array(Element) {
379 return new Struct({
380 type: `Array<${Element.type}>`,
381 schema: Element,
382 coercer: value => {
383 return Array.isArray(value) ? value.map(v => coerce(v, Element)) : value;
384 },
385
386 *validator(value, ctx) {
387 if (!Array.isArray(value)) {
388 yield ctx.fail();
389 return;
390 }
391
392 for (const [i, v] of value.entries()) {
393 yield* ctx.check(v, Element, value, i);
394 }
395 }
396
397 });
398 }
399 /**
400 * Validate that boolean values.
401 */
402
403 function boolean() {
404 return struct('boolean', value => {
405 return typeof value === 'boolean';
406 });
407 }
408 /**
409 * Validate that `Date` values.
410 *
411 * Note: this also ensures that the value is *not* an invalid `Date` object,
412 * which can occur when parsing a date fails but still returns a `Date`.
413 */
414
415 function date() {
416 return struct('Date', value => {
417 return value instanceof Date && !isNaN(value.getTime());
418 });
419 }
420 /**
421 * Validate that a value dynamically, determing which struct to use at runtime.
422 */
423
424 function dynamic(fn) {
425 return struct('Dynamic<...>', (value, ctx) => {
426 return ctx.check(value, fn(value, ctx));
427 });
428 }
429 /**
430 * Validate that a value against a set of potential values.
431 */
432
433 function enums(values) {
434 return struct(`Enum<${values.map(toLiteralString)}>`, value => {
435 return values.includes(value);
436 });
437 }
438 /**
439 * Validate that a value is a function.
440 */
441
442 function func() {
443 return struct('Function', value => {
444 return typeof value === 'function';
445 });
446 }
447 /**
448 * Validate that a value is an instance of a class.
449 */
450
451 function instance(Class) {
452 return struct(`InstanceOf<${Class.name}>`, value => {
453 return value instanceof Class;
454 });
455 }
456 function intersection(Structs) {
457 return struct(Structs.map(s => s.type).join(' & '), function* (value, ctx) {
458 for (const S of Structs) {
459 yield* ctx.check(value, S);
460 }
461 });
462 }
463 /**
464 * Validate a value lazily, by constructing the struct right before the first
465 * validation. This is useful for cases where you want to have self-referential
466 * structs for nested data structures.
467 */
468
469 function lazy(fn) {
470 let S;
471 return struct('Lazy<...>', (value, ctx) => {
472 if (!S) {
473 S = fn();
474 }
475
476 return ctx.check(value, S);
477 });
478 }
479 /**
480 * Validate that a value is a specific constant.
481 */
482
483 function literal(constant) {
484 return struct(`Literal<${toLiteralString(constant)}>`, value => {
485 return value === constant;
486 });
487 }
488 /**
489 * Validate that a value is a map with specific key and value entries.
490 */
491
492 function map(Key, Value) {
493 return struct(`Map<${Key.type},${Value.type}>`, function* (value, ctx) {
494 if (!(value instanceof Map)) {
495 yield ctx.fail();
496 return;
497 }
498
499 for (const [k, v] of value.entries()) {
500 yield* ctx.check(k, Key, value, k);
501 yield* ctx.check(v, Value, value, k);
502 }
503 });
504 }
505 /**
506 * Validate that a value always fails.
507 */
508
509 function never() {
510 return struct('never', () => false);
511 }
512 /**
513 * Validate that a value is a number.
514 */
515
516 function number() {
517 return struct(`number`, value => {
518 return typeof value === 'number' && !isNaN(value);
519 });
520 }
521 /**
522 * Validate that an object with specific entry values.
523 */
524
525 function object(Structs) {
526 const knowns = Object.keys(Structs);
527 const Never = never();
528 return new Struct({
529 type: `Object<{${knowns.join(',')}}>`,
530 schema: Structs,
531 coercer: createObjectCoercer(Structs),
532
533 *validator(value, ctx) {
534 if (typeof value !== 'object' || value == null) {
535 yield ctx.fail();
536 return;
537 }
538
539 const unknowns = new Set(Object.keys(value));
540
541 for (const key of knowns) {
542 unknowns.delete(key);
543 const Value = Structs[key];
544 const v = value[key];
545 yield* ctx.check(v, Value, value, key);
546 }
547
548 for (const key of unknowns) {
549 const v = value[key];
550 yield* ctx.check(v, Never, value, key);
551 }
552 }
553
554 });
555 }
556 /**
557 * Augment a struct to make it accept optionally accept `undefined` values.
558 */
559
560 function optional(S) {
561 return new Struct({
562 type: `${S.type}?`,
563 schema: S.schema,
564 validator: (value, ctx) => {
565 return value === undefined || ctx.check(value, S);
566 }
567 });
568 }
569 /**
570 * Validate that a partial object with specific entry values.
571 */
572
573 function partial(Structs) {
574 if (Structs instanceof Struct) {
575 Structs = Structs.schema;
576 }
577
578 const knowns = Object.keys(Structs);
579 const Never = never();
580 return new Struct({
581 type: `Partial<{${knowns.join(',')}}>`,
582 schema: Structs,
583 coercer: createObjectCoercer(Structs),
584
585 *validator(value, ctx) {
586 if (typeof value !== 'object' || value == null) {
587 yield ctx.fail();
588 return;
589 }
590
591 const unknowns = new Set(Object.keys(value));
592
593 for (const key of knowns) {
594 unknowns.delete(key);
595
596 if (!(key in value)) {
597 continue;
598 }
599
600 const Value = Structs[key];
601 const v = value[key];
602 yield* ctx.check(v, Value, value, key);
603 }
604
605 for (const key of unknowns) {
606 const v = value[key];
607 yield* ctx.check(v, Never, value, key);
608 }
609 }
610
611 });
612 }
613 /**
614 * Validate that a value is a record with specific key and
615 * value entries.
616 */
617
618 function record(Key, Value) {
619 return struct(`Record<${Key.type},${Value.type}>`, function* (value, ctx) {
620 if (typeof value !== 'object' || value == null) {
621 yield ctx.fail();
622 return;
623 }
624
625 for (const k in value) {
626 const v = value[k];
627 yield* ctx.check(k, Key, value, k);
628 yield* ctx.check(v, Value, value, k);
629 }
630 });
631 }
632 /**
633 * Validate that a set of values matches a specific type.
634 */
635
636 function set(Element) {
637 return struct(`Set<${Element.type}>`, (value, ctx) => {
638 if (!(value instanceof Set)) {
639 return false;
640 }
641
642 for (const val of value) {
643 const [failure] = ctx.check(val, Element);
644
645 if (failure) {
646 return false;
647 }
648 }
649
650 return true;
651 });
652 }
653 /**
654 * Validate that a value is a string.
655 */
656
657 function string() {
658 return struct('string', value => {
659 return typeof value === 'string';
660 });
661 }
662 /**
663 * Define a `Struct` instance with a type and validation function.
664 */
665
666 function struct(name, validator) {
667 return new Struct({
668 type: name,
669 validator,
670 schema: null
671 });
672 }
673 function tuple(Elements) {
674 const Never = never();
675 return struct(`[${Elements.map(s => s.type).join(',')}]`, function* (value, ctx) {
676 if (!Array.isArray(value)) {
677 yield ctx.fail();
678 return;
679 }
680
681 for (const [index, Element] of Elements.entries()) {
682 const v = value[index];
683 yield* ctx.check(v, Element, value, index);
684 }
685
686 if (value.length > Elements.length) {
687 const index = Elements.length;
688 const v = value[index];
689 yield* ctx.check(v, Never, value, index);
690 }
691 });
692 }
693 /**
694 * Validate that a value matches a specific strutural interface, like the
695 * structural typing that TypeScript uses.
696 */
697
698 function type(Structs) {
699 const keys = Object.keys(Structs);
700 return struct(`Type<{${keys.join(',')}}>`, function* (value, ctx) {
701 if (typeof value !== 'object' || value == null) {
702 yield ctx.fail();
703 return;
704 }
705
706 for (const key of keys) {
707 const Value = Structs[key];
708 const v = value[key];
709 yield* ctx.check(v, Value, value, key);
710 }
711 });
712 }
713 function union(Structs) {
714 return struct(`${Structs.map(s => s.type).join(' | ')}`, function* (value, ctx) {
715 for (const S of Structs) {
716 const [...failures] = ctx.check(value, S);
717
718 if (failures.length === 0) {
719 return;
720 }
721 }
722
723 yield ctx.fail();
724 });
725 }
726 /**
727 * Convert a value to a literal string.
728 */
729
730 function toLiteralString(value) {
731 return typeof value === 'string' ? `"${value.replace(/"/g, '"')}"` : `${value}`;
732 }
733 /**
734 * Coerce the values of an object-like struct.
735 */
736
737
738 function createObjectCoercer(Structs) {
739 const knowns = Object.keys(Structs);
740 return value => {
741 if (typeof value !== 'object' || value == null) {
742 return value;
743 }
744
745 const ret = {};
746 const unknowns = new Set(Object.keys(value));
747
748 for (const key of knowns) {
749 unknowns.delete(key);
750 const Value = Structs[key];
751 const v = value[key];
752 ret[key] = coerce(v, Value);
753 }
754
755 for (const key of unknowns) {
756 ret[key] = value[key];
757 }
758
759 return ret;
760 };
761 }
762
763 exports.Struct = Struct;
764 exports.StructError = StructError;
765 exports.any = any;
766 exports.array = array;
767 exports.assert = assert;
768 exports.boolean = boolean;
769 exports.coerce = coerce;
770 exports.coercion = coercion;
771 exports.date = date;
772 exports.defaulted = defaulted;
773 exports.dynamic = dynamic;
774 exports.enums = enums;
775 exports.func = func;
776 exports.instance = instance;
777 exports.intersection = intersection;
778 exports.is = is;
779 exports.lazy = lazy;
780 exports.length = length;
781 exports.literal = literal;
782 exports.map = map;
783 exports.masked = masked;
784 exports.never = never;
785 exports.number = number;
786 exports.object = object;
787 exports.optional = optional;
788 exports.partial = partial;
789 exports.pattern = pattern;
790 exports.record = record;
791 exports.refinement = refinement;
792 exports.set = set;
793 exports.string = string;
794 exports.struct = struct;
795 exports.tuple = tuple;
796 exports.type = type;
797 exports.union = union;
798 exports.validate = validate;
799
800 Object.defineProperty(exports, '__esModule', { value: true });
801
802})));