UNPKG

@har-sdk/openapi-sampler

Version:

[![Maintainability](https://api.codeclimate.com/v1/badges/4acaec95c82465cb2c3d/maintainability)](https://codeclimate.com/github/NeuraLegion/har-sdk/maintainability) [![Test Coverage](https://api.codeclimate.com/v1/badges/4acaec95c82465cb2c3d/test_coverage

136 lines 6.1 kB
import RandExp from 'randexp'; export class StringSampler { constructor() { // 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