Skip to content
Snippets Groups Projects
index.js 48.2 KiB
Newer Older
  • Learn to ignore specific revisions
  • 'use strict';
    
    function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
    
    
    var $RefParser = _interopDefault(require('json-schema-ref-parser'));
    
    var RandExp = _interopDefault(require('randexp'));
    
    var jsonpathPlus = require('jsonpath-plus');
    
    
    /**
     * This class defines a registry for custom formats used within JSF.
     */
    var Registry = function Registry() {
      // empty by default
      this.data = {};
    };
    /**
     * Unregisters custom format(s)
     * @param name
     */
    
    
    Registry.prototype.unregister = function unregister (name) {
      if (!name) {
        this.data = {};
      } else {
        delete this.data[name];
      }
    };
    /**
     * Registers custom format
     */
    
    
    Registry.prototype.register = function register (name, callback) {
      this.data[name] = callback;
    };
    /**
     * Register many formats at one shot
     */
    
    
    Registry.prototype.registerMany = function registerMany (formats) {
        var this$1 = this;
    
      Object.keys(formats).forEach(function (name) {
        this$1.data[name] = formats[name];
      });
    };
    /**
     * Returns element by registry key
     */
    
    
    Registry.prototype.get = function get (name) {
      var format = this.data[name];
      return format;
    };
    /**
     * Returns the whole registry content
     */
    
    
    Registry.prototype.list = function list () {
      return this.data;
    };
    
    var defaults = {};
    defaults.defaultInvalidTypeProduct = null;
    defaults.defaultRandExpMax = 10;
    defaults.ignoreProperties = [];
    defaults.ignoreMissingRefs = false;
    defaults.failOnInvalidTypes = true;
    defaults.failOnInvalidFormat = true;
    defaults.alwaysFakeOptionals = false;
    defaults.optionalsProbability = false;
    defaults.fixedProbabilities = false;
    defaults.useExamplesValue = false;
    defaults.useDefaultValue = false;
    defaults.requiredOnly = false;
    defaults.minItems = 0;
    
    defaults.maxItems = 1;
    
    defaults.minLength = 0;
    defaults.maxLength = null;
    defaults.resolveJsonPath = false;
    defaults.reuseProperties = false;
    defaults.fillProperties = true;
    defaults.random = Math.random;
    /**
     * This class defines a registry for custom settings used within JSF.
     */
    
    
    var OptionRegistry = /*@__PURE__*/(function (Registry) {
    
      function OptionRegistry() {
    
        Registry.call(this);
    
        this.data = Object.assign({}, defaults);
        this._defaults = defaults;
      }
    
    
      if ( Registry ) OptionRegistry.__proto__ = Registry;
      OptionRegistry.prototype = Object.create( Registry && Registry.prototype );
    
      OptionRegistry.prototype.constructor = OptionRegistry;
    
      var prototypeAccessors = { defaults: { configurable: true } };
    
      prototypeAccessors.defaults.get = function () {
        return Object.assign({}, this._defaults);
      };
    
      Object.defineProperties( OptionRegistry.prototype, prototypeAccessors );
    
      return OptionRegistry;
    }(Registry));
    
    var registry = new OptionRegistry();
    /**
     * Custom option API
     *
     * @param nameOrOptionMap
     * @returns {any}
     */
    
    
    function optionAPI(nameOrOptionMap, optionalValue) {
    
      if (typeof nameOrOptionMap === 'string') {
    
        if (typeof optionalValue !== 'undefined') {
          return registry.register(nameOrOptionMap, optionalValue);
        }
    
    
        return registry.get(nameOrOptionMap);
      }
    
      return registry.registerMany(nameOrOptionMap);
    }
    
    optionAPI.getDefaults = function () { return registry.defaults; };
    
    var ALL_TYPES = ['array', 'object', 'integer', 'number', 'string', 'boolean', 'null'];
    var MOST_NEAR_DATETIME = 2524608000000;
    var MIN_INTEGER = -100000000;
    var MAX_INTEGER = 100000000;
    var MIN_NUMBER = -100;
    var MAX_NUMBER = 100;
    var env = {
      ALL_TYPES: ALL_TYPES,
      MIN_NUMBER: MIN_NUMBER,
      MAX_NUMBER: MAX_NUMBER,
      MIN_INTEGER: MIN_INTEGER,
      MAX_INTEGER: MAX_INTEGER,
      MOST_NEAR_DATETIME: MOST_NEAR_DATETIME
    };
    
    function getRandomInteger(min, max) {
      min = typeof min === 'undefined' ? env.MIN_INTEGER : min;
      max = typeof max === 'undefined' ? env.MAX_INTEGER : max;
      return Math.floor(optionAPI('random')() * (max - min + 1)) + min;
    }
    
    function _randexp(value) {
      // set maximum default, see #193
      RandExp.prototype.max = optionAPI('defaultRandExpMax'); // same implementation as the original except using our random
    
      RandExp.prototype.randInt = function (a, b) { return a + Math.floor(optionAPI('random')() * (1 + (b - a))); };
    
      var re = new RandExp(value);
      return re.gen();
    }
    /**
     * Returns random element of a collection
     *
     * @param collection
     * @returns {T}
     */
    
    
    function pick(collection) {
      return collection[Math.floor(optionAPI('random')() * collection.length)];
    }
    /**
     * Returns shuffled collection of elements
     *
     * @param collection
     * @returns {T[]}
     */
    
    
    function shuffle(collection) {
      var tmp;
      var key;
      var length = collection.length;
      var copy = collection.slice();
    
      for (; length > 0;) {
        key = Math.floor(optionAPI('random')() * length); // swap
    
        length -= 1;
        tmp = copy[length];
        copy[length] = copy[key];
        copy[key] = tmp;
      }
    
      return copy;
    }
    /**
     * 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
     */
    
    
    function getRandom(min, max) {
      return optionAPI('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' ? env.MIN_NUMBER : defMin;
      defMax = typeof defMax === 'undefined' ? env.MAX_NUMBER : defMax;
      min = typeof min === 'undefined' ? defMin : min;
      max = typeof max === 'undefined' ? defMax : max;
    
      if (max < min) {
        max += min;
      }
    
      if (hasPrecision) {
        return getRandom(min, max);
      }
    
      return getRandomInteger(min, max);
    }
    
    function by(type) {
      switch (type) {
        case 'seconds':
          return number(0, 60) * 60;
    
        case 'minutes':
          return number(15, 50) * 612;
    
        case 'hours':
          return number(12, 72) * 36123;
    
        case 'days':
          return number(7, 30) * 86412345;
    
        case 'weeks':
          return number(4, 52) * 604812345;
    
        case 'months':
          return number(2, 13) * 2592012345;
    
        case 'years':
          return number(1, 20) * 31104012345;
    
        default:
          break;
      }
    }
    
    function date(step) {
      if (step) {
        return by(step);
      }
    
      var now = new Date();
      var days = number(-1000, env.MOST_NEAR_DATETIME);
      now.setTime(now.getTime() - days);
      return now;
    }
    
    var random = {
      pick: pick,
      date: date,
      shuffle: shuffle,
      number: number,
      randexp: _randexp
    };
    
    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 = [], len = arguments.length - 1;
      while ( len-- > 0 ) properties[ len ] = arguments[ len + 1 ];
    
      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. This is the base formatter for all result values.
     *
     * @param type
     * @param schema
     * @param callback
     * @returns {any}
     */
    
    
    function typecast(type, schema, callback) {
      var params = {}; // normalize constraints
    
      switch (type || schema.type) {
        case 'integer':
        case 'number':
          if (typeof schema.minimum !== 'undefined') {
            params.minimum = schema.minimum;
          }
    
          if (typeof schema.maximum !== 'undefined') {
            params.maximum = schema.maximum;
          }
    
          if (schema.enum) {
            var min = Math.max(params.minimum || 0, 0);
            var max = Math.min(params.maximum || Infinity, Infinity);
    
            if (schema.exclusiveMinimum && min === schema.minimum) {
              min += schema.multipleOf || 1;
            }
    
            if (schema.exclusiveMaximum && max === schema.maximum) {
              max -= schema.multipleOf || 1;
            } // discard out-of-bounds enumerations
    
    
            if (min || max !== Infinity) {
              schema.enum = schema.enum.filter(function (x) {
                if (x >= min && x <= max) {
                  return true;
                }
    
                return false;
              });
            }
          }
    
          break;
    
        case 'string':
          {
            if (typeof schema.minLength !== 'undefined') {
              params.minLength = schema.minLength;
            }
    
            if (typeof schema.maxLength !== 'undefined') {
              params.maxLength = schema.maxLength;
            }
    
            var _maxLength = optionAPI('maxLength');
    
            var _minLength = optionAPI('minLength'); // Don't allow user to set max length above our maximum
    
    
            if (_maxLength && params.maxLength > _maxLength) {
              params.maxLength = _maxLength;
            } // Don't allow user to set min length above our maximum
    
    
            if (_minLength && params.minLength < _minLength) {
              params.minLength = _minLength;
            }
    
            break;
          }
    
        default:
          break;
      } // execute generator
    
    
      var value = callback(params); // normalize output value
    
      switch (type || schema.type) {
        case 'number':
          value = parseFloat(value);
          break;
    
        case 'integer':
          value = parseInt(value, 10);
          break;
    
        case 'boolean':
          value = !!value;
          break;
    
        case 'string':
          {
            value = String(value);
            var min$1 = Math.max(params.minLength || 0, 0);
            var max$1 = Math.min(params.maxLength || Infinity, Infinity);
    
            while (value.length < min$1) {
    
              if (!schema.pattern) {
                value += "" + (random.pick([' ', '/', '_', '-', '+', '=', '@', '^'])) + value;
              } else {
                value += random.randexp(schema.pattern);
              }
    
            }
    
            if (value.length > max$1) {
              value = value.substr(0, max$1);
            }
    
    
            switch (schema.format) {
              case 'date-time':
              case 'datetime':
                value = new Date(value).toISOString().replace(/([0-9])0+Z$/, '$1Z');
                break;
    
              case 'date':
                value = new Date(value).toISOString().substr(0, 10);
                break;
    
              case 'time':
                value = new Date(("1969-01-01 " + value)).toISOString().substr(11);
                break;
    
              default:
                break;
            }
    
    
            break;
          }
    
        default:
          break;
      }
    
      return value;
    }
    
    function merge(a, b) {
      Object.keys(b).forEach(function (key) {
        if (typeof b[key] !== 'object' || b[key] === null) {
          a[key] = b[key];
        } else if (Array.isArray(b[key])) {
          a[key] = a[key] || []; // fix #292 - skip duplicated values from merge object (b)
    
          b[key].forEach(function (value) {
            if (a[key].indexOf(value) === -1) {
              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;
    }
    
    
    function clone(obj) {
    
    Alvaro Cabrera Durán's avatar
    Alvaro Cabrera Durán committed
      return JSON.parse(JSON.stringify(obj));
    
    function short(schema) {
      var s = JSON.stringify(schema);
      var l = JSON.stringify(schema, null, 2);
      return s.length > 400 ? ((l.substr(0, 400)) + "...") : l;
    }
    
    function anyValue() {
    
      return random.pick([false, true, null, -1, NaN, Math.PI, Infinity, undefined, [], {}, // FIXME: use built-in random?
      Math.random(), Math.random().toString(36).substr(2)]);
    
    }
    
    function notValue(schema, parent) {
      var copy = merge({}, parent);
    
      if (typeof schema.minimum !== 'undefined') {
        copy.maximum = schema.minimum;
        copy.exclusiveMaximum = true;
      }
    
      if (typeof schema.maximum !== 'undefined') {
        copy.minimum = schema.maximum > copy.maximum ? 0 : schema.maximum;
        copy.exclusiveMinimum = true;
      }
    
      if (typeof schema.minLength !== 'undefined') {
        copy.maxLength = schema.minLength;
      }
    
      if (typeof schema.maxLength !== 'undefined') {
        copy.minLength = schema.maxLength > copy.maxLength ? 0 : schema.maxLength;
      }
    
      if (schema.type) {
        copy.type = random.pick(env.ALL_TYPES.filter(function (x) {
          var types = Array.isArray(schema.type) ? schema.type : [schema.type];
          return types.every(function (type) {
            // treat both types as _similar enough_ to be skipped equal
            if (x === 'number' || x === 'integer') {
              return type !== 'number' && type !== 'integer';
            }
    
            return x !== type;
          });
        }));
      } else if (schema.enum) {
        var value;
    
        do {
          value = anyValue();
        } while (schema.enum.indexOf(value) !== -1);
    
        copy.enum = [value];
      }
    
      if (schema.required && copy.properties) {
        schema.required.forEach(function (prop) {
          delete copy.properties[prop];
        });
      } // TODO: explore more scenarios
    
    
      return copy;
    } // FIXME: evaluate more constraints?
    
    
    function validate(value, schemas) {
      return !schemas.every(function (x) {
        if (typeof x.minimum !== 'undefined' && value >= x.minimum) {
          return true;
        }
    
        if (typeof x.maximum !== 'undefined' && value <= x.maximum) {
          return true;
        }
    
        return false;
      });
    }
    
    function isKey(prop) {
    
    Alvaro Cabrera Durán's avatar
    Alvaro Cabrera Durán committed
      return ['enum', 'const', 'default', 'examples', 'required', 'definitions'].indexOf(prop) !== -1;
    
    }
    
    function omitProps(obj, props) {
      var copy = {};
      Object.keys(obj).forEach(function (k) {
        if (props.indexOf(k) === -1) {
          if (Array.isArray(obj[k])) {
            copy[k] = obj[k].slice();
          } else {
    
            copy[k] = obj[k] instanceof Object ? merge({}, obj[k]) : obj[k];
    
          }
        }
      });
      return copy;
    }
    
    function template(value, schema) {
      if (Array.isArray(value)) {
        return value.map(function (x) { return template(x, schema); });
      }
    
      if (typeof value === 'string') {
        value = value.replace(/#\{([\w.-]+)\}/g, function (_, $1) { return schema[$1]; });
      }
    
      return value;
    }
    
    var utils = {
      getSubAttribute: getSubAttribute,
      hasProperties: hasProperties,
      omitProps: omitProps,
      typecast: typecast,
      merge: merge,
    
      clone: clone,
    
      short: short,
      notValue: notValue,
      anyValue: anyValue,
      validate: validate,
      isKey: isKey,
      template: template
    };
    
    function proxy(gen) {
      return function (value, schema, property, rootSchema) {
        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.map(function (x) { return utils.template(x, rootSchema); }));
        } // test for pending callbacks
    
    
        if (Object.prototype.toString.call(value) === '[object Object]') {
          Object.keys(value).forEach(function (key) {
            if (typeof value[key] === 'function') {
              throw new Error(("Cannot resolve value for '" + property + ": " + fn + "', given: " + value));
            }
          });
        }
    
        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 Container() {
      // dynamic requires - handle all dependencies
      // they will NOT be included on the bundle
      this.registry = {};
      this.support = {};
    };
    /**
     * Unregister extensions
     * @param name
     */
    
    
    Container.prototype.reset = function reset (name) {
      if (!name) {
        this.registry = {};
        this.support = {};
      } else {
        delete this.registry[name];
        delete this.support[name];
      }
    };
    /**
     * Override dependency given by name
     * @param name
     * @param callback
     */
    
    
    Container.prototype.extend = function extend (name, callback) {
        var this$1 = 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$1.registry[name]; });
      }
    };
    /**
     * Set keyword support by name
     * @param name
     * @param callback
     */
    
    
    Container.prototype.define = function define (name, callback) {
      this.support[name] = callback;
    };
    /**
     * Returns dependency given by name
     * @param name
     * @returns {Dependency}
     */
    
    
    Container.prototype.get = function get (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 wrap (schema) {
        var this$1 = this;
    
      var keys = Object.keys(schema);
      var context = {};
      var length = keys.length;
    
      var loop = function () {
        // eslint-disable-line
        var fn = keys[length].replace(/^x-/, '');
        var gen = this$1.support[fn];
    
        if (typeof gen === 'function') {
          Object.defineProperty(schema, 'generate', {
            configurable: false,
            enumerable: false,
            writable: false,
            value: function (rootSchema) { return gen.call(context, schema[keys[length]], schema, keys[length], rootSchema); } // eslint-disable-line
    
          });
          return 'break';
        }
      };
    
        while (length--) {
          var returned = loop();
    
          if ( returned === 'break' ) break;
        }
    
      return schema;
    };
    
    var registry$1 = new Registry();
    /**
     * 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$1.list();
    
      }
    
      if (typeof nameOrFormatMap === 'string') {
    
        if (typeof callback === 'function') {
          registry$1.register(nameOrFormatMap, callback);
        } else if (callback === null || callback === false) {
          registry$1.unregister(nameOrFormatMap);
        } else {
          return registry$1.get(nameOrFormatMap);
        }
      } else {
        registry$1.registerMany(nameOrFormatMap);
      }
    }
    
    
    var ParseError = /*@__PURE__*/(function (Error) {
    
      function ParseError(message, path) {
        Error.call(this);
    
        if (Error.captureStackTrace) {
          Error.captureStackTrace(this, this.constructor);
        }
    
        this.name = 'ParseError';
        this.message = message;
        this.path = path;
      }
    
      if ( Error ) ParseError.__proto__ = Error;
      ParseError.prototype = Object.create( Error && Error.prototype );
      ParseError.prototype.constructor = ParseError;
    
      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'],
    
    Alvaro Cabrera Durán's avatar
    Alvaro Cabrera Durán committed
      string: ['maxLength', 'minLength', 'pattern', 'format']
    
    };
    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;
        var inferredPropertyFound = inferredTypeProperties.indexOf(prop) > -1;
    
        if (inferredPropertyFound && !isSubschema) {
          return true;
        }
    
        return false;
      }).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) {
      var keys = Object.keys(inferredProperties);
    
      for (var i = 0; i < keys.length; i += 1) {
        var typeName = keys[i];
        var lastElementInPath = schemaPath[schemaPath.length - 1];
    
        if (matchesType(obj, lastElementInPath, inferredProperties[typeName])) {
          return typeName;
        }
      }
    }
    
    /**
     * Generates randomized boolean value.
     *
     * @returns {boolean}
     */
    
    function booleanGenerator() {
      return optionAPI('random')() > 0.5;
    }
    
    var booleanType = booleanGenerator;
    
    /**
     * Generates null value.
     *
     * @returns {null}
     */
    function nullGenerator() {
      return null;
    }
    
    var nullType = nullGenerator;
    
    function unique(path, items, value, sample, resolve, traverseCallback) {
      var tmp = [];
      var seen = [];
    
      function walk(obj) {
        var json = JSON.stringify(obj);
    
        if (seen.indexOf(json) === -1) {
          seen.push(json);
          tmp.push(obj);
    
          return true;
    
    
        return false;
    
      }
    
      items.forEach(walk); // TODO: find a better solution?
    
      var limit = 100;
    
      while (tmp.length !== items.length) {
    
        if (!walk(traverseCallback(value.items || sample, path, resolve))) {
          limit -= 1;
        }
    
    
        if (!limit) {
          break;
        }
      }
    
      return tmp;
    } // TODO provide types
    
    
    function arrayType(value, path, resolve, traverseCallback) {
      var items = [];
    
      if (!(value.items || value.additionalItems)) {
        if (utils.hasProperties(value, 'minItems', 'maxItems', 'uniqueItems')) {
          throw new ParseError(("missing items for " + (utils.short(value))), path);
        }
    
        return items;
      }
    
      if (Array.isArray(value.items)) {
        return value.items.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('minItems')) {
        // fix boundaries
        minItems = !maxItems ? optionAPI('minItems') : Math.min(optionAPI('minItems'), 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;
        }
      }
    
      var optionalsProbability = optionAPI('alwaysFakeOptionals') === true ? 1.0 : optionAPI('optionalsProbability');
    
      var fixedProbabilities = optionAPI('alwaysFakeOptionals') || optionAPI('fixedProbabilities') || false;
    
      var length = random.number(minItems, maxItems, 1, 5);
    
      if (optionalsProbability !== false) {
    
        length = Math.max(fixedProbabilities ? Math.round((maxItems || length) * optionalsProbability) : Math.abs(random.number(minItems, maxItems) * optionalsProbability), minItems || 0);
    
      } // TODO below looks bad. Should additionalItems be copied as-is?
    
    
      var sample = typeof value.additionalItems === 'object' ? value.additionalItems : {};
    
      for (var current = items.length; current < length; current += 1) {
        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;
    }
    
    function numberType(value) {
      var min = typeof value.minimum === 'undefined' ? env.MIN_INTEGER : value.minimum;
      var max = typeof value.maximum === 'undefined' ? env.MAX_INTEGER : value.maximum;
      var multipleOf = value.multipleOf;
    
      if (multipleOf) {
        max = Math.floor(max / multipleOf) * multipleOf;
        min = Math.ceil(min / multipleOf) * multipleOf;
      }
    
      if (value.exclusiveMinimum && min === value.minimum) {
        min += multipleOf || 1;
      }