var assert = require('./assert'); var isTypeName = require('./isTypeName'); var String = require('./String'); var Function = require('./Function'); var isBoolean = require('./isBoolean'); var isObject = require('./isObject'); var isNil = require('./isNil'); var create = require('./create'); var getTypeName = require('./getTypeName'); var dict = require('./dict'); var getDefaultInterfaceName = require('./getDefaultInterfaceName'); var isIdentity = require('./isIdentity'); var is = require('./is'); var extend = require('./extend'); var assign = require('./assign'); function extendInterface(mixins, name) { return extend(inter, mixins, name); } function getOptions(options) { if (!isObject(options)) { options = isNil(options) ? {} : { name: options }; } if (!options.hasOwnProperty('strict')) { options.strict = inter.strict; } return options; } function inter(props, options) { options = getOptions(options); var name = options.name; var strict = options.strict; if (process.env.NODE_ENV !== 'production') { assert(dict(String, Function).is(props), function () { return 'Invalid argument props ' + assert.stringify(props) + ' supplied to interface(props, [options]) combinator (expected a dictionary String -> Type)'; }); assert(isTypeName(name), function () { return 'Invalid argument name ' + assert.stringify(name) + ' supplied to interface(props, [options]) combinator (expected a string)'; }); assert(isBoolean(strict), function () { return 'Invalid argument strict ' + assert.stringify(strict) + ' supplied to struct(props, [options]) combinator (expected a boolean)'; }); } var displayName = name || getDefaultInterfaceName(props); var identity = Object.keys(props).map(function (prop) { return props[prop]; }).every(isIdentity); function Interface(value, path) { if (process.env.NODE_ENV === 'production') { if (identity) { return value; // just trust the input if elements must not be hydrated } } if (process.env.NODE_ENV !== 'production') { path = path || [displayName]; assert(!isNil(value), function () { return 'Invalid value ' + value + ' supplied to ' + path.join('/'); }); // strictness if (strict) { for (var k in value) { assert(props.hasOwnProperty(k), function () { return 'Invalid additional prop "' + k + '" supplied to ' + path.join('/'); }); } } } var idempotent = true; var ret = identity ? {} : assign({}, value); for (var prop in props) { var expected = props[prop]; var actual = value[prop]; var instance = create(expected, actual, ( process.env.NODE_ENV !== 'production' ? path.concat(prop + ': ' + getTypeName(expected)) : null )); idempotent = idempotent && ( actual === instance ); ret[prop] = instance; } if (idempotent) { // implements idempotency ret = value; } if (process.env.NODE_ENV !== 'production') { Object.freeze(ret); } return ret; } Interface.meta = { kind: 'interface', props: props, name: name, identity: identity, strict: strict }; Interface.displayName = displayName; Interface.is = function (x) { if (isNil(x)) { return false; } if (strict) { for (var k in x) { if (!props.hasOwnProperty(k)) { return false; } } } for (var prop in props) { if (!is(x[prop], props[prop])) { return false; } } return true; }; Interface.update = function (instance, patch) { return Interface(assert.update(instance, patch)); }; Interface.extend = function (xs, name) { return extendInterface([Interface].concat(xs), name); }; return Interface; } inter.strict = false; inter.getOptions = getOptions; inter.getDefaultName = getDefaultInterfaceName; inter.extend = extendInterface; module.exports = inter;