Newer
Older
* (c) 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('json-schema-ref-parser'), require('jsonpath-plus')) :
typeof define === 'function' && define.amd ? define(['json-schema-ref-parser', 'jsonpath-plus'], factory) :
(global = global || self, global.JSONSchemaFaker = factory(global.$RefParser, global.jsonpathPlus));
}(this, function ($RefParser, jsonpathPlus) { 'use strict';
$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];
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 = /*@__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 );
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);
}
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;
/**
* 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[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),
});
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;
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) });
}
}
// Positionals.
case '^':
last.push(positions.begin());
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,
});
// 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,
};
// 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}`);
}
// 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');
// 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(', ') + ' ]';
}
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');
}
/**
* 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();
/**
* 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);
}
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));
/**
* 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));