123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312 |
- 'use strict';
- const Assert = require('@hapi/hoek/lib/assert');
- const Clone = require('@hapi/hoek/lib/clone');
- const Common = require('./common');
- const Messages = require('./messages');
- const internals = {};
- exports.type = function (from, options) {
- const base = Object.getPrototypeOf(from);
- const prototype = Clone(base);
- const schema = from._assign(Object.create(prototype));
- const def = Object.assign({}, options); // Shallow cloned
- delete def.base;
- prototype._definition = def;
- const parent = base._definition || {};
- def.messages = Messages.merge(parent.messages, def.messages);
- def.properties = Object.assign({}, parent.properties, def.properties);
- // Type
- schema.type = def.type;
- // Flags
- def.flags = Object.assign({}, parent.flags, def.flags);
- // Terms
- const terms = Object.assign({}, parent.terms);
- if (def.terms) {
- for (const name in def.terms) { // Only apply own terms
- const term = def.terms[name];
- Assert(schema.$_terms[name] === undefined, 'Invalid term override for', def.type, name);
- schema.$_terms[name] = term.init;
- terms[name] = term;
- }
- }
- def.terms = terms;
- // Constructor arguments
- if (!def.args) {
- def.args = parent.args;
- }
- // Prepare
- def.prepare = internals.prepare(def.prepare, parent.prepare);
- // Coerce
- if (def.coerce) {
- if (typeof def.coerce === 'function') {
- def.coerce = { method: def.coerce };
- }
- if (def.coerce.from &&
- !Array.isArray(def.coerce.from)) {
- def.coerce = { method: def.coerce.method, from: [].concat(def.coerce.from) };
- }
- }
- def.coerce = internals.coerce(def.coerce, parent.coerce);
- // Validate
- def.validate = internals.validate(def.validate, parent.validate);
- // Rules
- const rules = Object.assign({}, parent.rules);
- if (def.rules) {
- for (const name in def.rules) {
- const rule = def.rules[name];
- Assert(typeof rule === 'object', 'Invalid rule definition for', def.type, name);
- let method = rule.method;
- if (method === undefined) {
- method = function () {
- return this.$_addRule(name);
- };
- }
- if (method) {
- Assert(!prototype[name], 'Rule conflict in', def.type, name);
- prototype[name] = method;
- }
- Assert(!rules[name], 'Rule conflict in', def.type, name);
- rules[name] = rule;
- if (rule.alias) {
- const aliases = [].concat(rule.alias);
- for (const alias of aliases) {
- prototype[alias] = rule.method;
- }
- }
- if (rule.args) {
- rule.argsByName = new Map();
- rule.args = rule.args.map((arg) => {
- if (typeof arg === 'string') {
- arg = { name: arg };
- }
- Assert(!rule.argsByName.has(arg.name), 'Duplicated argument name', arg.name);
- if (Common.isSchema(arg.assert)) {
- arg.assert = arg.assert.strict().label(arg.name);
- }
- rule.argsByName.set(arg.name, arg);
- return arg;
- });
- }
- }
- }
- def.rules = rules;
- // Modifiers
- const modifiers = Object.assign({}, parent.modifiers);
- if (def.modifiers) {
- for (const name in def.modifiers) {
- Assert(!prototype[name], 'Rule conflict in', def.type, name);
- const modifier = def.modifiers[name];
- Assert(typeof modifier === 'function', 'Invalid modifier definition for', def.type, name);
- const method = function (arg) {
- return this.rule({ [name]: arg });
- };
- prototype[name] = method;
- modifiers[name] = modifier;
- }
- }
- def.modifiers = modifiers;
- // Overrides
- if (def.overrides) {
- prototype._super = base;
- schema.$_super = {}; // Backwards compatibility
- for (const override in def.overrides) {
- Assert(base[override], 'Cannot override missing', override);
- def.overrides[override][Common.symbols.parent] = base[override];
- schema.$_super[override] = base[override].bind(schema); // Backwards compatibility
- }
- Object.assign(prototype, def.overrides);
- }
- // Casts
- def.cast = Object.assign({}, parent.cast, def.cast);
- // Manifest
- const manifest = Object.assign({}, parent.manifest, def.manifest);
- manifest.build = internals.build(def.manifest && def.manifest.build, parent.manifest && parent.manifest.build);
- def.manifest = manifest;
- // Rebuild
- def.rebuild = internals.rebuild(def.rebuild, parent.rebuild);
- return schema;
- };
- // Helpers
- internals.build = function (child, parent) {
- if (!child ||
- !parent) {
- return child || parent;
- }
- return function (obj, desc) {
- return parent(child(obj, desc), desc);
- };
- };
- internals.coerce = function (child, parent) {
- if (!child ||
- !parent) {
- return child || parent;
- }
- return {
- from: child.from && parent.from ? [...new Set([...child.from, ...parent.from])] : null,
- method(value, helpers) {
- let coerced;
- if (!parent.from ||
- parent.from.includes(typeof value)) {
- coerced = parent.method(value, helpers);
- if (coerced) {
- if (coerced.errors ||
- coerced.value === undefined) {
- return coerced;
- }
- value = coerced.value;
- }
- }
- if (!child.from ||
- child.from.includes(typeof value)) {
- const own = child.method(value, helpers);
- if (own) {
- return own;
- }
- }
- return coerced;
- }
- };
- };
- internals.prepare = function (child, parent) {
- if (!child ||
- !parent) {
- return child || parent;
- }
- return function (value, helpers) {
- const prepared = child(value, helpers);
- if (prepared) {
- if (prepared.errors ||
- prepared.value === undefined) {
- return prepared;
- }
- value = prepared.value;
- }
- return parent(value, helpers) || prepared;
- };
- };
- internals.rebuild = function (child, parent) {
- if (!child ||
- !parent) {
- return child || parent;
- }
- return function (schema) {
- parent(schema);
- child(schema);
- };
- };
- internals.validate = function (child, parent) {
- if (!child ||
- !parent) {
- return child || parent;
- }
- return function (value, helpers) {
- const result = parent(value, helpers);
- if (result) {
- if (result.errors &&
- (!Array.isArray(result.errors) || result.errors.length)) {
- return result;
- }
- value = result.value;
- }
- return child(value, helpers) || result;
- };
- };
|