@har-sdk/openapi-sampler
Version:
[](https://codeclimate.com/github/NeuraLegion/har-sdk/maintainability) [ {
// ADHOC: 500 seems enough to protect sampler in case of patterns with infinite
// quantifiers as the complexity may reach O(n^x) for the x quantifier nesting
this.MAX_PATTERN_SAMPLE_LENGTH = 500;
this.stringFormats = {
'email': () => 'jon.snow@targaryen.com',
'idn-email': () => 'джон.сноу@таргариен.укр',
'password': (min, max) => this.adjustLength('p@$$w0rd', min, max),
'date-time': () => '2021-12-31T23:34:00Z',
'date': () => '2021-12-31',
'time': () => '23:34:00Z',
'duration': () => 'P3D',
'ipv4': () => '208.67.222.222',
'ipv6': () => '0000:0000:0000:0000:0000:ffff:d043:dcdc',
'hostname': () => 'brokencrystals.com',
'idn-hostname': () => 'сломанные-кристаллы.бел',
'iri': () => 'https://be.wikipedia.org/wiki/%D0%9A%D1%80%D1%8B%D1%88%D1%82%D0%B0%D0%BB%D1%96',
'iri-reference': () => '/wiki/%D0%9A%D1%80%D1%8B%D1%88%D1%82%D0%B0%D0%BB%D1%96',
'uri': () => 'https://github.com/NeuraLegion/brokencrystals',
'uri-reference': () => '../brokencrystals',
'uri-template': () => 'https://brokencrystals.com/api/file/{provider}',
'byte': () => 'ZHVtbXkgYmluYXJ5IHNhbXBsZQA=',
'binary': () => '\x01\x02\x03\x04\x05',
'base64': () => 'ZHVtbXkgYmluYXJ5IHNhbXBsZQA=',
'base64url': () => 'bG9yZW0',
'uuid': () => 'fbdf5a53-161e-4460-98ad-0e39408d8689',
'json-pointer': () => '/json/pointer',
'relative-json-pointer': () => '1/relative/json/pointer',
'regex': () => '/regex/',
'pattern': (min, max, { pattern }) => this.patternSample(pattern, min, max),
'default': (min, max) => this.adjustLength('lorem', min, max)
};
}
sample(schema) {
const encoding = ['base64', 'base64url'].includes(schema.contentEncoding)
? schema.contentEncoding
: schema.contentMediaType === 'application/octet-stream'
? 'binary'
: schema.format;
const format = schema.pattern ? 'pattern' : encoding || 'default';
const sampler = this.stringFormats[format] || this.stringFormats['default'];
const { minLength: min, maxLength: max } = schema;
return this.checkLength(sampler(min, max, schema), format, min, max);
}
patternSample(pattern, min, max) {
this.assertLength(min, max);
const randExp = new RandExp(pattern);
randExp.randInt = (a, b) => Math.floor((a + b) / 2);
if (min !== undefined) {
return this.sampleWithMinLength(randExp, min, max);
}
randExp.max = max !== null && max !== void 0 ? max : randExp.max;
const result = randExp.gen();
return max !== undefined &&
result.length > max &&
this.hasInfiniteQuantifier(pattern)
? this.sampleWithMaxLength(randExp, max)
: result;
}
hasInfiniteQuantifier(pattern) {
const pat = typeof pattern === 'string' ? pattern : pattern.source;
return /(\+|\*|\{\d*,\})/.test(pat);
}
sampleWithMaxLength(randExp, max) {
let result = '';
for (let i = 1; i <= Math.min(max, 20); i++) {
randExp.max = Math.floor(max / i);
result = randExp.gen();
if (result.length <= max) {
break;
}
}
return result;
}
sampleWithMinLength(randExp, min, max) {
// ADHOC: make a probe for regex using min quantifier value
// e.g. ^[a]+[b]+$ expect 'ab', ^[a-z]*$ expect ''
const randInt = randExp.randInt;
randExp.max = min;
randExp.randInt = (a, _) => a;
let result = randExp.gen();
randExp.randInt = randInt;
if (result.length < min) {
// ADHOC: fallback for failed min quantifier probe with doubled min length
randExp.max = 2 * min;
result = this.adjustMaxLength(randExp.gen(), max);
}
return result;
}
checkLength(value, format, min, max) {
if ((min && value.length < min) || (max && value.length > max)) {
const pairs = [
{ key: 'minLength', value: min },
{ key: 'maxLength', value: max },
{ key: 'format', value: format }
];
const boundariesStr = pairs
.filter((p) => p.value !== undefined)
.map((p) => `${p.key}=${p.value}`)
.join(', ');
throw new Error(`Sample string cannot be generated by boundaries: ${boundariesStr}`);
}
return value;
}
assertLength(min, max) {
const pairs = [
{ key: 'minLength', value: min },
{ key: 'maxLength', value: max }
];
const boundariesStr = pairs
.filter((p) => !this.checkBoundary(p.value))
.map((p) => `${p.key}=${p.value}`)
.join(', ');
if (boundariesStr) {
throw new Error(`Sample string cannot be generated by boundaries: ${boundariesStr}. Both minLength and maxLength must not exceed ${this.MAX_PATTERN_SAMPLE_LENGTH}`);
}
}
checkBoundary(boundary) {
return boundary === undefined || boundary <= this.MAX_PATTERN_SAMPLE_LENGTH;
}
adjustLength(sample, min, max) {
const minLength = min ? min : 0;
const maxLength = max ? max : sample.length;
return minLength > sample.length
? sample
.repeat(Math.trunc(minLength / sample.length) + 1)
.substring(0, minLength)
: sample.substr(0, Math.min(Math.max(sample.length, minLength), maxLength));
}
adjustMaxLength(sample, max) {
return max && sample.length >= max ? sample.substring(0, max) : sample;
}
}
//# sourceMappingURL=StringSampler.js.map