UNPKG

json-schema-faker

Version:
1,888 lines (1,524 loc) 73.4 kB
/*! * json-schema-faker v0.5.0-rc16 * (c) 2018-present Alvaro Cabrera <pateketrueke@gmail.com> (https://soypache.co) * Released under the MIT License. */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('jsonpath'), require('json-schema-ref-parser')) : typeof define === 'function' && define.amd ? define(['jsonpath', 'json-schema-ref-parser'], factory) : (global.JSONSchemaFaker = factory(global.jsonpath,global.$RefParser)); }(this, (function (jsonpath,$RefParser) { 'use strict'; jsonpath = jsonpath && jsonpath.hasOwnProperty('default') ? jsonpath['default'] : jsonpath; $RefParser = $RefParser && $RefParser.hasOwnProperty('default') ? $RefParser['default'] : $RefParser; /** * 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 = null; 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 = (function (Registry$$1) { function OptionRegistry() { Registry$$1.call(this); this.data = Object.assign({}, defaults); this._defaults = defaults; } if ( Registry$$1 ) OptionRegistry.__proto__ = Registry$$1; OptionRegistry.prototype = Object.create( Registry$$1 && Registry$$1.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) { if (typeof nameOrOptionMap === 'string') { 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 createCommonjsModule(fn, module) { return module = { exports: {} }, fn(module, module.exports), module.exports; } var types = { ROOT : 0, GROUP : 1, POSITION : 2, SET : 3, RANGE : 4, REPETITION : 5, REFERENCE : 6, CHAR : 7, }; const INTS = () => [{ type: types.RANGE , from: 48, to: 57 }]; const WORDS = () => { return [ { type: types.CHAR, value: 95 }, { type: types.RANGE, from: 97, to: 122 }, { type: types.RANGE, from: 65, to: 90 } ].concat(INTS()); }; const WHITESPACE = () => { return [ { type: types.CHAR, value: 9 }, { type: types.CHAR, value: 10 }, { type: types.CHAR, value: 11 }, { type: types.CHAR, value: 12 }, { type: types.CHAR, value: 13 }, { type: types.CHAR, value: 32 }, { type: types.CHAR, value: 160 }, { type: types.CHAR, value: 5760 }, { type: types.RANGE, from: 8192, to: 8202 }, { type: types.CHAR, value: 8232 }, { type: types.CHAR, value: 8233 }, { type: types.CHAR, value: 8239 }, { type: types.CHAR, value: 8287 }, { type: types.CHAR, value: 12288 }, { type: types.CHAR, value: 65279 } ]; }; const NOTANYCHAR = () => { return [ { type: types.CHAR, value: 10 }, { type: types.CHAR, value: 13 }, { type: types.CHAR, value: 8232 }, { type: types.CHAR, value: 8233 }, ]; }; // Predefined class objects. var words = () => ({ type: types.SET, set: WORDS(), not: false }); var notWords = () => ({ type: types.SET, set: WORDS(), not: true }); var ints = () => ({ type: types.SET, set: INTS(), not: false }); var notInts = () => ({ type: types.SET, set: INTS(), not: true }); var whitespace = () => ({ type: types.SET, set: WHITESPACE(), not: false }); var notWhitespace = () => ({ type: types.SET, set: WHITESPACE(), not: true }); var anyChar = () => ({ type: types.SET, set: NOTANYCHAR(), not: true }); var sets = { words: words, notWords: notWords, ints: ints, notInts: notInts, whitespace: whitespace, notWhitespace: notWhitespace, anyChar: anyChar }; var util = createCommonjsModule(function (module, exports) { const CTRL = '@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^ ?'; const SLSH = { '0': 0, 't': 9, 'n': 10, 'v': 11, 'f': 12, 'r': 13 }; /** * Finds character representations in str and convert all to * their respective characters * * @param {String} str * @return {String} */ exports.strToChars = function(str) { /* jshint maxlen: false */ var chars_regex = /(\[\\b\])|(\\)?\\(?:u([A-F0-9]{4})|x([A-F0-9]{2})|(0?[0-7]{2})|c([@A-Z[\\\]^?])|([0tnvfr]))/g; str = str.replace(chars_regex, function(s, b, lbs, a16, b16, c8, dctrl, eslsh) { if (lbs) { return s; } var code = b ? 8 : a16 ? parseInt(a16, 16) : b16 ? parseInt(b16, 16) : c8 ? parseInt(c8, 8) : dctrl ? CTRL.indexOf(dctrl) : SLSH[eslsh]; var c = String.fromCharCode(code); // Escape special regex characters. if (/[[\]{}^$.|?*+()]/.test(c)) { c = '\\' + c; } return c; }); return str; }; /** * turns class into tokens * reads str until it encounters a ] not preceeded by a \ * * @param {String} str * @param {String} regexpStr * @return {Array.<Array.<Object>, Number>} */ exports.tokenizeClass = (str, regexpStr) => { /* jshint maxlen: false */ var tokens = []; var regexp = /\\(?:(w)|(d)|(s)|(W)|(D)|(S))|((?:(?:\\)(.)|([^\]\\]))-(?:\\)?([^\]]))|(\])|(?:\\)?([^])/g; var rs, c; while ((rs = regexp.exec(str)) != null) { if (rs[1]) { tokens.push(sets.words()); } else if (rs[2]) { tokens.push(sets.ints()); } else if (rs[3]) { tokens.push(sets.whitespace()); } else if (rs[4]) { tokens.push(sets.notWords()); } else if (rs[5]) { tokens.push(sets.notInts()); } else if (rs[6]) { tokens.push(sets.notWhitespace()); } else if (rs[7]) { tokens.push({ type: types.RANGE, from: (rs[8] || rs[9]).charCodeAt(0), to: rs[10].charCodeAt(0), }); } else if ((c = rs[12])) { tokens.push({ type: types.CHAR, value: c.charCodeAt(0), }); } else { return [tokens, regexp.lastIndex]; } } exports.error(regexpStr, 'Unterminated character class'); }; /** * Shortcut to throw errors. * * @param {String} regexp * @param {String} msg */ exports.error = (regexp, msg) => { throw new SyntaxError('Invalid regular expression: /' + regexp + '/: ' + msg); }; }); var util_1 = util.strToChars; var util_2 = util.tokenizeClass; var util_3 = util.error; var wordBoundary = () => ({ type: types.POSITION, value: 'b' }); var nonWordBoundary = () => ({ type: types.POSITION, value: 'B' }); var begin = () => ({ type: types.POSITION, value: '^' }); var end = () => ({ type: types.POSITION, value: '$' }); var positions = { wordBoundary: wordBoundary, nonWordBoundary: nonWordBoundary, begin: begin, end: end }; var lib = (regexpStr) => { var i = 0, l, c, start = { type: types.ROOT, stack: []}, // Keep track of last clause/group and stack. lastGroup = start, last = start.stack, groupStack = []; var repeatErr = (i) => { util.error(regexpStr, `Nothing to repeat at column ${i - 1}`); }; // Decode a few escaped characters. var str = util.strToChars(regexpStr); l = str.length; // Iterate through each character in string. while (i < l) { c = str[i++]; switch (c) { // Handle escaped characters, inclues a few sets. case '\\': c = str[i++]; switch (c) { case 'b': last.push(positions.wordBoundary()); break; case 'B': last.push(positions.nonWordBoundary()); break; case 'w': last.push(sets.words()); break; case 'W': last.push(sets.notWords()); break; case 'd': last.push(sets.ints()); break; case 'D': last.push(sets.notInts()); break; case 's': last.push(sets.whitespace()); break; case 'S': last.push(sets.notWhitespace()); break; default: // Check if c is integer. // In which case it's a reference. if (/\d/.test(c)) { last.push({ type: types.REFERENCE, value: parseInt(c, 10) }); // Escaped character. } else { last.push({ type: types.CHAR, value: c.charCodeAt(0) }); } } break; // Positionals. case '^': last.push(positions.begin()); break; case '$': last.push(positions.end()); break; // Handle custom sets. case '[': // Check if this class is 'anti' i.e. [^abc]. var not; if (str[i] === '^') { not = true; i++; } else { not = false; } // Get all the characters in class. var classTokens = util.tokenizeClass(str.slice(i), regexpStr); // Increase index by length of class. i += classTokens[1]; last.push({ type: types.SET, set: classTokens[0], not, }); break; // Class of any character except \n. case '.': last.push(sets.anyChar()); break; // Push group onto stack. case '(': // Create group. var group = { type: types.GROUP, stack: [], remember: true, }; c = str[i]; // If if this is a special kind of group. if (c === '?') { c = str[i + 1]; i += 2; // Match if followed by. if (c === '=') { group.followedBy = true; // Match if not followed by. } else if (c === '!') { group.notFollowedBy = true; } else if (c !== ':') { util.error(regexpStr, `Invalid group, character '${c}'` + ` after '?' at column ${i - 1}`); } group.remember = false; } // Insert subgroup into current group stack. last.push(group); // Remember the current group for when the group closes. groupStack.push(lastGroup); // Make this new group the current group. lastGroup = group; last = group.stack; break; // Pop group out of stack. case ')': if (groupStack.length === 0) { util.error(regexpStr, `Unmatched ) at column ${i - 1}`); } lastGroup = groupStack.pop(); // Check if this group has a PIPE. // To get back the correct last stack. last = lastGroup.options ? lastGroup.options[lastGroup.options.length - 1] : lastGroup.stack; break; // Use pipe character to give more choices. case '|': // Create array where options are if this is the first PIPE // in this clause. if (!lastGroup.options) { lastGroup.options = [lastGroup.stack]; delete lastGroup.stack; } // Create a new stack and add to options for rest of clause. var stack = []; lastGroup.options.push(stack); last = stack; break; // Repetition. // For every repetition, remove last element from last stack // then insert back a RANGE object. // This design is chosen because there could be more than // one repetition symbols in a regex i.e. `a?+{2,3}`. case '{': var rs = /^(\d+)(,(\d+)?)?\}/.exec(str.slice(i)), min, max; if (rs !== null) { if (last.length === 0) { repeatErr(i); } min = parseInt(rs[1], 10); max = rs[2] ? rs[3] ? parseInt(rs[3], 10) : Infinity : min; i += rs[0].length; last.push({ type: types.REPETITION, min, max, value: last.pop(), }); } else { last.push({ type: types.CHAR, value: 123, }); } break; case '?': if (last.length === 0) { repeatErr(i); } last.push({ type: types.REPETITION, min: 0, max: 1, value: last.pop(), }); break; case '+': if (last.length === 0) { repeatErr(i); } last.push({ type: types.REPETITION, min: 1, max: Infinity, value: last.pop(), }); break; case '*': if (last.length === 0) { repeatErr(i); } last.push({ type: types.REPETITION, min: 0, max: Infinity, value: last.pop(), }); break; // Default is a character that is not `\[](){}?+*^$`. default: last.push({ type: types.CHAR, value: c.charCodeAt(0), }); } } // Check if any groups have not been closed. if (groupStack.length !== 0) { util.error(regexpStr, 'Unterminated group'); } return start; }; var types_1$1 = types; lib.types = types_1$1; /* eslint indent: 4 */ // Private helper class class SubRange { constructor(low, high) { this.low = low; this.high = high; this.length = 1 + high - low; } overlaps(range) { return !(this.high < range.low || this.low > range.high); } touches(range) { return !(this.high + 1 < range.low || this.low - 1 > range.high); } // Returns inclusive combination of SubRanges as a SubRange. add(range) { return new SubRange( Math.min(this.low, range.low), Math.max(this.high, range.high) ); } // Returns subtraction of SubRanges as an array of SubRanges. // (There's a case where subtraction divides it in 2) subtract(range) { if (range.low <= this.low && range.high >= this.high) { return []; } else if (range.low > this.low && range.high < this.high) { return [ new SubRange(this.low, range.low - 1), new SubRange(range.high + 1, this.high) ]; } else if (range.low <= this.low) { return [new SubRange(range.high + 1, this.high)]; } else { return [new SubRange(this.low, range.low - 1)]; } } toString() { return this.low == this.high ? this.low.toString() : this.low + '-' + this.high; } } class DRange { constructor(a, b) { this.ranges = []; this.length = 0; if (a != null) this.add(a, b); } _update_length() { this.length = this.ranges.reduce((previous, range) => { return previous + range.length; }, 0); } add(a, b) { var _add = (subrange) => { var i = 0; while (i < this.ranges.length && !subrange.touches(this.ranges[i])) { i++; } var newRanges = this.ranges.slice(0, i); while (i < this.ranges.length && subrange.touches(this.ranges[i])) { subrange = subrange.add(this.ranges[i]); i++; } newRanges.push(subrange); this.ranges = newRanges.concat(this.ranges.slice(i)); this._update_length(); }; if (a instanceof DRange) { a.ranges.forEach(_add); } else { if (b == null) b = a; _add(new SubRange(a, b)); } return this; } subtract(a, b) { var _subtract = (subrange) => { var i = 0; while (i < this.ranges.length && !subrange.overlaps(this.ranges[i])) { i++; } var newRanges = this.ranges.slice(0, i); while (i < this.ranges.length && subrange.overlaps(this.ranges[i])) { newRanges = newRanges.concat(this.ranges[i].subtract(subrange)); i++; } this.ranges = newRanges.concat(this.ranges.slice(i)); this._update_length(); }; if (a instanceof DRange) { a.ranges.forEach(_subtract); } else { if (b == null) b = a; _subtract(new SubRange(a, b)); } return this; } intersect(a, b) { var newRanges = []; var _intersect = (subrange) => { var i = 0; while (i < this.ranges.length && !subrange.overlaps(this.ranges[i])) { i++; } while (i < this.ranges.length && subrange.overlaps(this.ranges[i])) { var low = Math.max(this.ranges[i].low, subrange.low); var high = Math.min(this.ranges[i].high, subrange.high); newRanges.push(new SubRange(low, high)); i++; } }; if (a instanceof DRange) { a.ranges.forEach(_intersect); } else { if (b == null) b = a; _intersect(new SubRange(a, b)); } this.ranges = newRanges; this._update_length(); return this; } index(index) { var i = 0; while (i < this.ranges.length && this.ranges[i].length <= index) { index -= this.ranges[i].length; i++; } return this.ranges[i].low + index; } toString() { return '[ ' + this.ranges.join(', ') + ' ]'; } clone() { return new DRange(this); } } var lib$1 = DRange; const types$1 = lib.types; var randexp = class RandExp { /** * @constructor * @param {RegExp|String} regexp * @param {String} m */ constructor(regexp, m) { this._setDefaults(regexp); if (regexp instanceof RegExp) { this.ignoreCase = regexp.ignoreCase; this.multiline = regexp.multiline; regexp = regexp.source; } else if (typeof regexp === 'string') { this.ignoreCase = m && m.indexOf('i') !== -1; this.multiline = m && m.indexOf('m') !== -1; } else { throw new Error('Expected a regexp or string'); } this.tokens = lib(regexp); } /** * Checks if some custom properties have been set for this regexp. * * @param {RandExp} randexp * @param {RegExp} regexp */ _setDefaults(regexp) { // When a repetitional token has its max set to Infinite, // randexp won't actually generate a random amount between min and Infinite // instead it will see Infinite as min + 100. this.max = regexp.max != null ? regexp.max : RandExp.prototype.max != null ? RandExp.prototype.max : 100; // This allows expanding to include additional characters // for instance: RandExp.defaultRange.add(0, 65535); this.defaultRange = regexp.defaultRange ? regexp.defaultRange : this.defaultRange.clone(); if (regexp.randInt) { this.randInt = regexp.randInt; } } /** * Generates the random string. * * @return {String} */ gen() { return this._gen(this.tokens, []); } /** * Generate random string modeled after given tokens. * * @param {Object} token * @param {Array.<String>} groups * @return {String} */ _gen(token, groups) { var stack, str, n, i, l; switch (token.type) { case types$1.ROOT: case types$1.GROUP: // Ignore lookaheads for now. if (token.followedBy || token.notFollowedBy) { return ''; } // Insert placeholder until group string is generated. if (token.remember && token.groupNumber === undefined) { token.groupNumber = groups.push(null) - 1; } stack = token.options ? this._randSelect(token.options) : token.stack; str = ''; for (i = 0, l = stack.length; i < l; i++) { str += this._gen(stack[i], groups); } if (token.remember) { groups[token.groupNumber] = str; } return str; case types$1.POSITION: // Do nothing for now. return ''; case types$1.SET: var expandedSet = this._expand(token); if (!expandedSet.length) { return ''; } return String.fromCharCode(this._randSelect(expandedSet)); case types$1.REPETITION: // Randomly generate number between min and max. n = this.randInt(token.min, token.max === Infinity ? token.min + this.max : token.max); str = ''; for (i = 0; i < n; i++) { str += this._gen(token.value, groups); } return str; case types$1.REFERENCE: return groups[token.value - 1] || ''; case types$1.CHAR: var code = this.ignoreCase && this._randBool() ? this._toOtherCase(token.value) : token.value; return String.fromCharCode(code); } } /** * If code is alphabetic, converts to other case. * If not alphabetic, returns back code. * * @param {Number} code * @return {Number} */ _toOtherCase(code) { return code + (97 <= code && code <= 122 ? -32 : 65 <= code && code <= 90 ? 32 : 0); } /** * Randomly returns a true or false value. * * @return {Boolean} */ _randBool() { return !this.randInt(0, 1); } /** * Randomly selects and returns a value from the array. * * @param {Array.<Object>} arr * @return {Object} */ _randSelect(arr) { if (arr instanceof lib$1) { return arr.index(this.randInt(0, arr.length - 1)); } return arr[this.randInt(0, arr.length - 1)]; } /** * expands a token to a DiscontinuousRange of characters which has a * length and an index function (for random selecting) * * @param {Object} token * @return {DiscontinuousRange} */ _expand(token) { if (token.type === lib.types.CHAR) { return new lib$1(token.value); } else if (token.type === lib.types.RANGE) { return new lib$1(token.from, token.to); } else { let drange = new lib$1(); for (let i = 0; i < token.set.length; i++) { let subrange = this._expand(token.set[i]); drange.add(subrange); if (this.ignoreCase) { for (let j = 0; j < subrange.length; j++) { let code = subrange.index(j); let otherCaseCode = this._toOtherCase(code); if (code !== otherCaseCode) { drange.add(otherCaseCode); } } } } if (token.not) { return this.defaultRange.clone().subtract(drange); } else { return this.defaultRange.clone().intersect(drange); } } } /** * Randomly generates and returns a number between a and b (inclusive). * * @param {Number} a * @param {Number} b * @return {Number} */ randInt(a, b) { return a + Math.floor(Math.random() * (1 + b - a)); } /** * Default range of characters to generate from. */ get defaultRange() { return this._range = this._range || new lib$1(32, 126); } set defaultRange(range) { this._range = range; } /** * * Enables use of randexp with a shorter call. * * @param {RegExp|String| regexp} * @param {String} m * @return {String} */ static randexp(regexp, m) { var randexp; if(typeof regexp === 'string') { regexp = new RegExp(regexp, m); } if (regexp._randexp === undefined) { randexp = new RandExp(regexp, m); regexp._randexp = randexp; } else { randexp = regexp._randexp; randexp._setDefaults(regexp); } return randexp.gen(); } /** * Enables sugary /regexp/.gen syntax. */ static sugar() { /* eshint freeze:false */ RegExp.prototype.gen = function() { return RandExp.randexp(this); }; } }; 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) { value += " " + value; } if (value.length > max$1) { value = value.substr(0, max$1); } 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 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, [], {}, 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) { 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] = typeof obj[k] === '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, 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(); } else 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 = (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'], 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); } } 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) { limit -= 1; 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('fixedProbabilities') || false; var length = random.number(minItems, maxItems, 1, 5); if (optionalsProbability !== false) { length = fixedProbabilities ? Math.round(maxItems * optionalsProbability) : random.number(minItems, maxItems * optionalsProbability); } // 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; } if (value.exclusiveMaximum && max === value.maximum) { max -= multipleOf || 1; } if (min > max) { return NaN; } if (multipleOf) { if (String(multipleOf).indexOf('.') === -1) { var base = random.number(Math.floor(min / multipleOf), Math.floor(max / multipleOf)) * multipleOf; while (base < min) { base += value.multipleOf; } return base; } var boundary = (max - min) / multipleOf; var num; var fix; do { num = random.number(0, boundary) * multipleOf; fix = num / multipleOf % 1; } while (fix !== 0); // FIXME: h