Skip to content
Snippets Groups Projects
index.js 33.4 KiB
Newer Older
  • Learn to ignore specific revisions
  • Alvaro Cabrera's avatar
    Alvaro Cabrera committed
    function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
    var $RefParser = _interopDefault(require('json-schema-ref-parser'));
    
    var deref = _interopDefault(require('deref'));
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
    var tslib_1 = require('tslib');
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
    // dynamic proxy for custom generators
    function proxy(gen) {
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
        return function (value, schema, property) {
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
            var fn = value;
            var args = [];
            // support for nested object, first-key is the generator
            if (typeof value === 'object') {
                fn = Object.keys(value)[0];
                // treat the given array as arguments,
                if (Array.isArray(value[fn])) {
                    // if the generator is expecting arrays they should be nested, e.g. `[[1, 2, 3], true, ...]`
                    args = value[fn];
                }
                else {
                    args.push(value[fn]);
                }
            }
            // support for keypaths, e.g. "internet.email"
            var props = fn.split('.');
            // retrieve a fresh dependency
            var ctx = gen();
            while (props.length > 1) {
                ctx = ctx[props.shift()];
            }
            // retrieve last value from context object
            value = typeof ctx === 'object' ? ctx[props[0]] : ctx;
            // invoke dynamic generators
            if (typeof value === 'function') {
                value = value.apply(ctx, args);
            }
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
            // test for pending callbacks
            if (Object.prototype.toString.call(value) === '[object Object]') {
                for (var key in value) {
                    if (typeof value[key] === 'function') {
                        throw new Error('Cannot resolve value for "' + property + ': ' + fn + '", given: ' + value);
                    }
                }
            }
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
            return value;
        };
    }
    /**
     * Container is used to wrap external generators (faker, chance, casual, etc.) and its dependencies.
     *
     * - `jsf.extend('faker')` will enhance or define the given dependency.
     * - `jsf.define('faker')` will provide the "faker" keyword support.
     *
     * RandExp is not longer considered an "extension".
     */
    var Container = (function () {
        function Container() {
            // dynamic requires - handle all dependencies
            // they will NOT be included on the bundle
            this.registry = {};
            this.support = {};
        }
        /**
         * Override dependency given by name
         * @param name
         * @param callback
         */
        Container.prototype.extend = function (name, callback) {
            var _this = this;
            this.registry[name] = callback(this.registry[name]);
            // built-in proxy (can be overridden)
            if (!this.support[name]) {
                this.support[name] = proxy(function () { return _this.registry[name]; });
            }
        };
        /**
         * Set keyword support by name
         * @param name
         * @param callback
         */
        Container.prototype.define = function (name, callback) {
            this.support[name] = callback;
        };
        /**
         * Returns dependency given by name
         * @param name
         * @returns {Dependency}
         */
        Container.prototype.get = function (name) {
            if (typeof this.registry[name] === 'undefined') {
                throw new ReferenceError('"' + name + '" dependency doesn\'t exist.');
            }
            return this.registry[name];
        };
        /**
         * Apply a custom keyword
         * @param schema
         */
        Container.prototype.wrap = function (schema) {
            var keys = Object.keys(schema);
            var length = keys.length;
            while (length--) {
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
                var fn = keys[length].replace(/^x-/, '');
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
                var gen = this.support[fn];
                if (typeof gen === 'function') {
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
                    schema.generate = function () { return gen(schema[keys[length]], schema, keys[length]); };
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
                    break;
                }
            }
            return schema;
        };
        return Container;
    }());
    
    
    /**
     * This class defines a registry for custom formats used within JSF.
     */
    var Registry = (function () {
        function Registry() {
            // empty by default
            this.data = {};
        }
        /**
         * Registers custom format
         */
        Registry.prototype.register = function (name, callback) {
            this.data[name] = callback;
        };
        /**
         * Register many formats at one shot
         */
        Registry.prototype.registerMany = function (formats) {
            for (var name in formats) {
                this.data[name] = formats[name];
            }
        };
        /**
         * Returns element by registry key
         */
        Registry.prototype.get = function (name) {
            var format = this.data[name];
            return format;
        };
        /**
         * Returns the whole registry content
         */
        Registry.prototype.list = function () {
            return this.data;
        };
        return Registry;
    }());
    
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
    // instantiate
    var registry = new Registry();
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
     * Custom format API
     *
     * @see https://github.com/json-schema-faker/json-schema-faker#custom-formats
     * @param nameOrFormatMap
     * @param callback
     * @returns {any}
     */
    function formatAPI(nameOrFormatMap, callback) {
        if (typeof nameOrFormatMap === 'undefined') {
            return registry.list();
        }
        else if (typeof nameOrFormatMap === 'string') {
            if (typeof callback === 'function') {
                registry.register(nameOrFormatMap, callback);
            }
            else {
                return registry.get(nameOrFormatMap);
            }
        }
        else {
            registry.registerMany(nameOrFormatMap);
        }
    }
    
    /**
     * This class defines a registry for custom settings used within JSF.
    
     */
    var OptionRegistry = (function (_super) {
        tslib_1.__extends(OptionRegistry, _super);
        function OptionRegistry() {
            var _this = _super.call(this) || this;
            _this.data['failOnInvalidTypes'] = true;
            _this.data['defaultInvalidTypeProduct'] = null;
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
            _this.data['failOnInvalidFormat'] = true;
    
            _this.data['useDefaultValue'] = false;
            _this.data['requiredOnly'] = false;
            _this.data['maxItems'] = null;
            _this.data['maxLength'] = null;
            _this.data['defaultMinItems'] = 0;
            _this.data['defaultRandExpMax'] = 10;
            _this.data['alwaysFakeOptionals'] = false;
            return _this;
        }
        return OptionRegistry;
    }(Registry));
    
    // instantiate
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
    var registry$1 = new OptionRegistry();
    
    /**
     * Custom option API
     *
     * @param nameOrOptionMap
     * @returns {any}
     */
    function optionAPI(nameOrOptionMap) {
        if (typeof nameOrOptionMap === 'string') {
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
            return registry$1.get(nameOrOptionMap);
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
            return registry$1.registerMany(nameOrOptionMap);
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
    var RandExp = require('randexp');
    
    // set maximum default, see #193
    RandExp.prototype.max = 10;
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
    function _randexp(value) {
        var re = new RandExp(value);
        // apply given setting
        re.max = optionAPI('defaultRandExpMax');
        return re.gen();
    
    function getSubAttribute(obj, dotSeparatedKey) {
        var keyElements = dotSeparatedKey.split('.');
        while (keyElements.length) {
            var prop = keyElements.shift();
            if (!obj[prop]) {
                break;
            }
            obj = obj[prop];
        }
        return obj;
    }
    /**
     * Returns true/false whether the object parameter has its own properties defined
     *
     * @param obj
     * @param properties
     * @returns {boolean}
     */
    function hasProperties(obj) {
        var properties = [];
        for (var _i = 1; _i < arguments.length; _i++) {
            properties[_i - 1] = arguments[_i];
        }
        return properties.filter(function (key) {
            return typeof obj[key] !== 'undefined';
        }).length > 0;
    }
    /**
     * Returns typecasted value.
     * External generators (faker, chance, casual) may return data in non-expected formats, such as string, when you might expect an
     * integer. This function is used to force the typecast.
     *
     * @param value
     * @param targetType
     * @returns {any}
     */
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
    function typecast(value, schema) {
        // FIXME this function should cover most cases and should be reused within generators
        switch (schema.type) {
    
            case 'integer':
                return parseInt(value, 10);
            case 'number':
                return parseFloat(value);
            case 'string':
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
                value = String(value);
                var min = Math.max(schema.minLength || 0, 0);
                var max = Math.min(schema.maxLength || Infinity, Infinity);
                while (value.length < min) {
                    value += ' ' + value;
                }
                if (value.length > max) {
                    value = value.substr(0, max);
                }
                return value;
    
            case 'boolean':
                return !!value;
            default:
                return value;
        }
    }
    function merge(a, b) {
        for (var key in b) {
            if (typeof b[key] !== 'object' || b[key] === null) {
                a[key] = b[key];
            }
            else if (Array.isArray(b[key])) {
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
                a[key] = a[key] || [];
                // fix #292 - skip duplicated values from merge object (b)
                b[key].forEach(function (value) {
                    if (a[key].indexOf(value)) {
                        a[key].push(value);
                    }
                });
    
            }
            else if (typeof a[key] !== 'object' || a[key] === null || Array.isArray(a[key])) {
                a[key] = merge({}, b[key]);
            }
            else {
                a[key] = merge(a[key], b[key]);
            }
        }
        return a;
    }
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
    function clean(obj, isArray, requiredProps) {
    
        if (!obj || typeof obj !== 'object') {
            return obj;
        }
        if (Array.isArray(obj)) {
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
            obj = obj
    
                .map(function (value) { return clean(value, true); })
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
                .filter(function (value) { return typeof value !== 'undefined'; });
            return obj;
    
        }
        Object.keys(obj).forEach(function (k) {
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
            if (!requiredProps || requiredProps.indexOf(k) === -1) {
                if (Array.isArray(obj[k]) && !obj[k].length) {
                    delete obj[k];
                }
            }
            else {
                obj[k] = clean(obj[k]);
            }
    
        });
        if (!Object.keys(obj).length && isArray) {
            return undefined;
        }
        return obj;
    }
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
    function short(schema) {
        var s = JSON.stringify(schema);
        var l = JSON.stringify(schema, null, 2);
        return s.length > 400 ? l.substr(0, 400) + '...' : l;
    }
    
    var utils = {
        getSubAttribute: getSubAttribute,
        hasProperties: hasProperties,
        typecast: typecast,
        merge: merge,
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
        clean: clean,
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
        short: short,
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
        randexp: _randexp
    
    /// <reference path="../index.d.ts" />
    /**
     * Returns random element of a collection
     *
     * @param collection
     * @returns {T}
     */
    function pick(collection) {
        return collection[Math.floor(Math.random() * collection.length)];
    }
    /**
     * Returns shuffled collection of elements
     *
     * @param collection
     * @returns {T[]}
     */
    function shuffle(collection) {
        var tmp, key, copy = collection.slice(), length = collection.length;
        for (; length > 0;) {
            key = Math.floor(Math.random() * length);
            // swap
            tmp = copy[--length];
            copy[length] = copy[key];
            copy[key] = tmp;
        }
        return copy;
    }
    /**
     * These values determine default range for random.number function
     *
     * @type {number}
     */
    var MIN_NUMBER = -100;
    var MAX_NUMBER = 100;
    /**
     * Returns a random integer between min (inclusive) and max (inclusive)
     * Using Math.round() will give you a non-uniform distribution!
     * @see http://stackoverflow.com/a/1527820/769384
     */
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
    function getRandom(min, max) {
        return Math.random() * (max - min) + min;
    
    }
    /**
     * Generates random number according to parameters passed
     *
     * @param min
     * @param max
     * @param defMin
     * @param defMax
     * @param hasPrecision
     * @returns {number}
     */
    function number(min, max, defMin, defMax, hasPrecision) {
        if (hasPrecision === void 0) { hasPrecision = false; }
        defMin = typeof defMin === 'undefined' ? MIN_NUMBER : defMin;
        defMax = typeof defMax === 'undefined' ? MAX_NUMBER : defMax;
        min = typeof min === 'undefined' ? defMin : min;
        max = typeof max === 'undefined' ? defMax : max;
        if (max < min) {
            max += min;
        }
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
        var result = getRandom(min, max);
    
        if (!hasPrecision) {
            return parseInt(result + '', 10);
        }
        return result;
    }
    var random = {
        pick: pick,
        shuffle: shuffle,
        number: number,
    };
    
    var ParseError = (function (_super) {
        tslib_1.__extends(ParseError, _super);
        function ParseError(message, path) {
            var _this = _super.call(this) || this;
            _this.path = path;
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
            if (Error.captureStackTrace) {
                Error.captureStackTrace(_this, _this.constructor);
            }
    
            _this.name = 'ParseError';
            _this.message = message;
            _this.path = path;
            return _this;
        }
        return ParseError;
    }(Error));
    
    var inferredProperties = {
        array: [
            'additionalItems',
            'items',
            'maxItems',
            'minItems',
            'uniqueItems'
        ],
        integer: [
            'exclusiveMaximum',
            'exclusiveMinimum',
            'maximum',
            'minimum',
            'multipleOf'
        ],
        object: [
            'additionalProperties',
            'dependencies',
            'maxProperties',
            'minProperties',
            'patternProperties',
            'properties',
            'required'
        ],
        string: [
            'maxLength',
            'minLength',
            'pattern'
        ]
    };
    inferredProperties.number = inferredProperties.integer;
    var subschemaProperties = [
        'additionalItems',
        'items',
        'additionalProperties',
        'dependencies',
        'patternProperties',
        'properties'
    ];
    /**
     * Iterates through all keys of `obj` and:
     * - checks whether those keys match properties of a given inferred type
     * - makes sure that `obj` is not a subschema; _Do not attempt to infer properties named as subschema containers. The
     * reason for this is that any property name within those containers that matches one of the properties used for
     * inferring missing type values causes the container itself to get processed which leads to invalid output. (Issue 62)_
     *
     * @returns {boolean}
     */
    function matchesType(obj, lastElementInPath, inferredTypeProperties) {
        return Object.keys(obj).filter(function (prop) {
            var isSubschema = subschemaProperties.indexOf(lastElementInPath) > -1, inferredPropertyFound = inferredTypeProperties.indexOf(prop) > -1;
            if (inferredPropertyFound && !isSubschema) {
                return true;
            }
        }).length > 0;
    }
    /**
     * Checks whether given `obj` type might be inferred. The mechanism iterates through all inferred types definitions,
     * tries to match allowed properties with properties of given `obj`. Returns type name, if inferred, or null.
     *
     * @returns {string|null}
     */
    function inferType(obj, schemaPath) {
        for (var typeName in inferredProperties) {
            var lastElementInPath = schemaPath[schemaPath.length - 1];
            if (matchesType(obj, lastElementInPath, inferredProperties[typeName])) {
                return typeName;
            }
        }
    }
    
    /**
     * Generates randomized boolean value.
     *
     * @returns {boolean}
     */
    function booleanGenerator() {
        return Math.random() > 0.5;
    }
    
    var booleanType = booleanGenerator;
    
    /**
     * Generates null value.
     *
     * @returns {null}
     */
    function nullGenerator() {
        return null;
    }
    
    var nullType = nullGenerator;
    
    // TODO provide types
    function unique(path, items, value, sample, resolve, traverseCallback) {
        var tmp = [], seen = [];
        function walk(obj) {
            var json = JSON.stringify(obj);
            if (seen.indexOf(json) === -1) {
                seen.push(json);
                tmp.push(obj);
            }
        }
        items.forEach(walk);
        // TODO: find a better solution?
        var limit = 100;
        while (tmp.length !== items.length) {
            walk(traverseCallback(value.items || sample, path, resolve));
            if (!limit--) {
                break;
            }
        }
        return tmp;
    }
    // TODO provide types
    var arrayType = function arrayType(value, path, resolve, traverseCallback) {
        var items = [];
        if (!(value.items || value.additionalItems)) {
            if (utils.hasProperties(value, 'minItems', 'maxItems', 'uniqueItems')) {
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
                throw new ParseError('missing items for ' + utils.short(value), path);
    
            }
            return items;
        }
        // see http://stackoverflow.com/a/38355228/769384
        // after type guards support subproperties (in TS 2.0) we can simplify below to (value.items instanceof Array)
        // so that value.items.map becomes recognized for typescript compiler
        var tmpItems = value.items;
        if (tmpItems instanceof Array) {
    
            return Array.prototype.concat.call(items, tmpItems.map(function (item, key) {
    
                var itemSubpath = path.concat(['items', key + '']);
                return traverseCallback(item, itemSubpath, resolve);
            }));
        }
        var minItems = value.minItems;
        var maxItems = value.maxItems;
        if (optionAPI('defaultMinItems') && minItems === undefined) {
            // fix boundaries
            minItems = !maxItems
                ? optionAPI('defaultMinItems')
                : Math.min(optionAPI('defaultMinItems'), maxItems);
        }
        if (optionAPI('maxItems')) {
            // Don't allow user to set max items above our maximum
            if (maxItems && maxItems > optionAPI('maxItems')) {
                maxItems = optionAPI('maxItems');
            }
            // Don't allow user to set min items above our maximum
            if (minItems && minItems > optionAPI('maxItems')) {
                minItems = maxItems;
            }
        }
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
        var length = random.number(minItems, maxItems, 1, 5), 
    
        // TODO below looks bad. Should additionalItems be copied as-is?
        sample = typeof value.additionalItems === 'object' ? value.additionalItems : {};
        for (var current = items.length; current < length; current++) {
            var itemSubpath = path.concat(['items', current + '']);
            var element = traverseCallback(value.items || sample, itemSubpath, resolve);
            items.push(element);
        }
        if (value.uniqueItems) {
            return unique(path.concat(['items']), items, value, sample, resolve, traverseCallback);
        }
        return items;
    };
    
    var MIN_INTEGER = -100000000;
    var MAX_INTEGER = 100000000;
    var numberType = function numberType(value) {
        var min = typeof value.minimum === 'undefined' ? MIN_INTEGER : value.minimum, max = typeof value.maximum === 'undefined' ? MAX_INTEGER : value.maximum, multipleOf = value.multipleOf;
        if (multipleOf) {
            max = Math.floor(max / multipleOf) * multipleOf;
            min = Math.ceil(min / multipleOf) * multipleOf;
        }
        if (value.exclusiveMinimum && value.minimum && min === value.minimum) {
            min += multipleOf || 1;
        }
        if (value.exclusiveMaximum && value.maximum && max === value.maximum) {
            max -= multipleOf || 1;
        }
        if (min > max) {
            return NaN;
        }
        if (multipleOf) {
            return Math.floor(random.number(min, max) / multipleOf) * multipleOf;
        }
        return random.number(min, max, undefined, undefined, true);
    };
    
    // The `integer` type is just a wrapper for the `number` type. The `number` type
    // returns floating point numbers, and `integer` type truncates the fraction
    // part, leaving the result as an integer.
    var integerType = function integerType(value) {
        var generated = numberType(value);
        // whether the generated number is positive or negative, need to use either
        // floor (positive) or ceil (negative) function to get rid of the fraction
        return generated > 0 ? Math.floor(generated) : Math.ceil(generated);
    };
    
    var LIPSUM_WORDS = ('Lorem ipsum dolor sit amet consectetur adipisicing elit sed do eiusmod tempor incididunt ut labore'
        + ' et dolore magna aliqua Ut enim ad minim veniam quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea'
        + ' commodo consequat Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla'
        + ' pariatur Excepteur sint occaecat cupidatat non proident sunt in culpa qui officia deserunt mollit anim id est'
        + ' laborum').split(' ');
    /**
     * Generates randomized array of single lorem ipsum words.
     *
     * @param length
     * @returns {Array.<string>}
     */
    function wordsGenerator(length) {
        var words = random.shuffle(LIPSUM_WORDS);
        return words.slice(0, length);
    }
    
    // fallback generator
    var anyType = { type: ['string', 'number', 'integer', 'boolean'] };
    // TODO provide types
    var objectType = function objectType(value, path, resolve, traverseCallback) {
        var props = {};
        var properties = value.properties || {};
        var patternProperties = value.patternProperties || {};
        var requiredProperties = (value.required || []).slice();
        var allowsAdditional = value.additionalProperties === false ? false : true;
        var propertyKeys = Object.keys(properties);
        var patternPropertyKeys = Object.keys(patternProperties);
        var additionalProperties = allowsAdditional
            ? (value.additionalProperties === true ? {} : value.additionalProperties)
            : null;
        if (!allowsAdditional &&
            propertyKeys.length === 0 &&
            patternPropertyKeys.length === 0 &&
            utils.hasProperties(value, 'minProperties', 'maxProperties', 'dependencies', 'required')) {
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
            throw new ParseError('missing properties for:\n' + utils.short(value), path);
    
        }
        if (optionAPI('requiredOnly') === true) {
            requiredProperties.forEach(function (key) {
                if (properties[key]) {
                    props[key] = properties[key];
                }
            });
            return traverseCallback(props, path.concat(['properties']), resolve);
        }
        var min = Math.max(value.minProperties || 0, requiredProperties.length);
        var max = Math.max(value.maxProperties || random.number(min, min + 5));
        random.shuffle(patternPropertyKeys.concat(propertyKeys)).forEach(function (_key) {
            if (requiredProperties.indexOf(_key) === -1) {
                requiredProperties.push(_key);
            }
        });
        // properties are read from right-to-left
        var _props = optionAPI('alwaysFakeOptionals') ? requiredProperties
            : requiredProperties.slice(0, random.number(min, max));
        _props.forEach(function (key) {
            // first ones are the required properies
            if (properties[key]) {
                props[key] = properties[key];
            }
            else {
                var found;
                // then try patternProperties
                patternPropertyKeys.forEach(function (_key) {
                    if (key.match(new RegExp(_key))) {
                        found = true;
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
                        props[utils.randexp(key)] = patternProperties[_key];
    
                    }
                });
                if (!found) {
                    // try patternProperties again,
                    var subschema = patternProperties[key] || additionalProperties;
                    if (subschema) {
                        // otherwise we can use additionalProperties?
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
                        props[patternProperties[key] ? utils.randexp(key) : key] = subschema;
    
                    }
                }
            }
        });
        var current = Object.keys(props).length;
        while (true) {
            if (!(patternPropertyKeys.length || allowsAdditional)) {
                break;
            }
            if (current >= min) {
                break;
            }
            if (allowsAdditional) {
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
                var word = wordsGenerator(1) + utils.randexp('[a-f\\d]{1,3}');
    
                if (!props[word]) {
                    props[word] = additionalProperties || anyType;
                    current += 1;
                }
            }
            patternPropertyKeys.forEach(function (_key) {
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
                var word = utils.randexp(_key);
    
                if (!props[word]) {
                    props[word] = patternProperties[_key];
                    current += 1;
                }
            });
        }
        if (!allowsAdditional && current < min) {
            throw new ParseError('properties constraints were too strong to successfully generate a valid object for:\n' +
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
                utils.short(value), path);
    
        }
        return traverseCallback(props, path.concat(['properties']), resolve);
    };
    
    /**
     * Helper function used by thunkGenerator to produce some words for the final result.
     *
     * @returns {string}
     */
    function produce() {
        var length = random.number(1, 5);
        return wordsGenerator(length).join(' ');
    }
    /**
     * Generates randomized concatenated string based on words generator.
     *
     * @returns {string}
     */
    function thunkGenerator(min, max) {
        if (min === void 0) { min = 0; }
        if (max === void 0) { max = 140; }
        var min = Math.max(0, min), max = random.number(min, max), result = produce();
        // append until length is reached
        while (result.length < min) {
            result += produce();
        }
        // cut if needed
        if (result.length > max) {
            result = result.substr(0, max);
        }
        return result;
    }
    
    /**
     * Generates randomized ipv4 address.
     *
     * @returns {string}
     */
    function ipv4Generator() {
        return [0, 0, 0, 0].map(function () {
            return random.number(0, 255);
        }).join('.');
    }
    
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
    var MOST_NEAR_DATETIME = 2524608000000;
    
    /**
     * Generates randomized date time ISO format string.
     *
     * @returns {string}
     */
    function dateTimeGenerator() {
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
        var date = new Date();
        var days = random.number(-1000, MOST_NEAR_DATETIME);
        date.setTime(date.getTime() - days);
        return date.toISOString();
    
    }
    
    /**
     * Predefined core formats
     * @type {[key: string]: string}
     */
    var regexps = {
        email: '[a-zA-Z\\d][a-zA-Z\\d-]{1,13}[a-zA-Z\\d]@{hostname}',
        hostname: '[a-zA-Z]{1,33}\\.[a-z]{2,4}',
        ipv6: '[a-f\\d]{4}(:[a-f\\d]{4}){7}',
        uri: '[a-zA-Z][a-zA-Z0-9+-.]*'
    };
    /**
     * Generates randomized string basing on a built-in regex format
     *
     * @param coreFormat
     * @returns {string}
     */
    function coreFormatGenerator(coreFormat) {
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
        return utils.randexp(regexps[coreFormat]).replace(/\{(\w+)\}/, function (match, key) {
            return utils.randexp(regexps[key]);
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
    function generateFormat(value, invalid) {
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
        var callback = formatAPI(value.format);
        if (typeof callback === 'function') {
            return callback(value);
        }
    
        switch (value.format) {
            case 'date-time':
                return dateTimeGenerator();
            case 'ipv4':
                return ipv4Generator();
            case 'regex':
                // TODO: discuss
                return '.+?';
            case 'email':
            case 'hostname':
            case 'ipv6':
            case 'uri':
                return coreFormatGenerator(value.format);
            default:
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
                if (typeof callback === 'undefined') {
                    if (optionAPI('failOnInvalidFormat')) {
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
                        throw new Error('unknown registry key ' + utils.short(value.format));
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
                    }
                    else {
                        return invalid();
                    }
                }
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
                throw new Error('unsupported format "' + value.format + '"');
    
        }
    }
    var stringType = function stringType(value) {
        var output;
        var minLength = value.minLength;
        var maxLength = value.maxLength;
        if (optionAPI('maxLength')) {
            // Don't allow user to set max length above our maximum
            if (maxLength && maxLength > optionAPI('maxLength')) {
                maxLength = optionAPI('maxLength');
            }
            // Don't allow user to set min length above our maximum
            if (minLength && minLength > optionAPI('maxLength')) {
                minLength = optionAPI('maxLength');
            }
        }
        if (value.format) {
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
            output = generateFormat(value, function () { return thunkGenerator(minLength, maxLength); });
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
            output = utils.randexp(value.pattern);
    
        }
        else {
            output = thunkGenerator(minLength, maxLength);
        }
        while (output.length < minLength) {
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
            output += Math.random() > 0.7 ? thunkGenerator() : utils.randexp('.+');
    
        }
        if (output.length > maxLength) {
            output = output.substr(0, maxLength);
        }
        return output;
    };
    
    var typeMap = {
        boolean: booleanType,
        null: nullType,
        array: arrayType,
        integer: integerType,
        number: numberType,
        object: objectType,
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
        string: stringType
    
    };
    
    // TODO provide types
    function traverse(schema, path, resolve) {
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
        schema = resolve(schema);
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
        if (!schema) {
            return;
        }
    
        if (Array.isArray(schema.enum)) {
            return random.pick(schema.enum);
        }
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
        // thunks can return sub-schemas
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
        if (typeof schema.thunk === 'function') {
            return traverse(schema.thunk(), path, resolve);
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
        }
        if (typeof schema.generate === 'function') {
            return utils.typecast(schema.generate(), schema);
        }
    
        if (optionAPI('useDefaultValue') && 'default' in schema) {
            return schema.default;
        }
        // TODO remove the ugly overcome
        var type = schema.type;
        if (Array.isArray(type)) {
            type = random.pick(type);
        }
        else if (typeof type === 'undefined') {
            // Attempt to infer the type
            type = inferType(schema, path) || type;
        }
        if (typeof type === 'string') {
            if (!typeMap[type]) {
                if (optionAPI('failOnInvalidTypes')) {
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
                    throw new ParseError('unknown primitive ' + utils.short(type), path.concat(['type']));
    
                }
                else {
                    return optionAPI('defaultInvalidTypeProduct');
                }
            }
            else {
                try {
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
                    return utils.clean(typeMap[type](schema, path, resolve, traverse), null, schema.required);
    
                }
                catch (e) {
                    if (typeof e.path === 'undefined') {
                        throw new ParseError(e.message, path);
                    }
                    throw e;
                }
            }
        }
        var copy = {};
        if (Array.isArray(schema)) {
            copy = [];
        }
        for (var prop in schema) {
            if (typeof schema[prop] === 'object' && prop !== 'definitions') {
                copy[prop] = traverse(schema[prop], path.concat([prop]), resolve);
            }
            else {
                copy[prop] = schema[prop];
            }
        }
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
        return copy;
    
    }
    
    function isKey(prop) {
        return prop === 'enum' || prop === 'default' || prop === 'required' || prop === 'definitions';
    }
    // TODO provide types
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
    function run(refs, schema, container) {
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
        try {
            return traverse(schema, [], function reduce(sub, maxReduceDepth) {
                if (typeof maxReduceDepth === 'undefined') {
                    maxReduceDepth = random.number(1, 3);
                }
                if (!sub) {
                    return null;
                }
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
                if (sub.id && typeof sub.id === 'string') {
                    delete sub.id;
                    delete sub.$schema;
                }
                if (typeof sub.$ref === 'string') {
                    if (sub.$ref.indexOf('#/') === -1) {
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
                        var ref = deref.util.findByRef(sub.$ref, refs);
                        if (!ref) {
                            throw new Error('Reference not found: ' + sub.$ref);
                        }
                        return ref;
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
                    }
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
                    // just remove the reference
                    delete sub.$ref;
                    return sub;
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
                if (Array.isArray(sub.allOf)) {
                    var schemas = sub.allOf;
                    delete sub.allOf;
                    // this is the only case where all sub-schemas
                    // must be resolved before any merge
                    schemas.forEach(function (subSchema) {
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
                        var _sub = reduce(subSchema, maxReduceDepth + 1);
                        // call given thunks if present
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
                        utils.merge(sub, typeof _sub.thunk === 'function'
                            ? _sub.thunk()
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
                            : _sub);
    
    Alvaro Cabrera's avatar
    Alvaro Cabrera committed
                    });
                }
                if (Array.isArray(sub.oneOf || sub.anyOf)) {