Skip to content

Commit

Permalink
feat: add support for uuid format
Browse files Browse the repository at this point in the history
  • Loading branch information
gazoakley authored and RomanHotsiy committed May 14, 2020
1 parent 3c54217 commit 9d6c312
Show file tree
Hide file tree
Showing 7 changed files with 78 additions and 10 deletions.
1 change: 1 addition & 0 deletions README.md
Expand Up @@ -20,6 +20,7 @@ Tool for generation samples based on OpenAPI payload/response schema
- ipv6
- hostname
- uri
- uuid
- Infers schema type automatically following same rules as [json-schema-faker](https://www.npmjs.com/package/json-schema-faker#inferred-types)
- Support for `$ref` resolving

Expand Down
2 changes: 1 addition & 1 deletion src/samplers/object.js
Expand Up @@ -14,7 +14,7 @@ export function sampleObject(schema, options = {}, spec) {
return;
}

const sample = traverse(schema.properties[propertyName], options, spec);
const sample = traverse(schema.properties[propertyName], options, spec, { propertyName });
if (options.skipReadOnly && sample.readOnly) {
return;
}
Expand Down
12 changes: 9 additions & 3 deletions src/samplers/string.js
@@ -1,6 +1,6 @@
'use strict';

import { ensureMinLength, toRFCDateTime } from '../utils';
import { ensureMinLength, toRFCDateTime, uuid } from '../utils';

const passwordSymbols = 'qwerty!@#$%^123456';

Expand Down Expand Up @@ -60,6 +60,10 @@ function uriSample() {
return 'http://example.com';
}

function uuidSample(_min, _max, propertyName) {
return uuid(propertyName || 'id');
}

const stringFormats = {
'email': emailSample,
'password': passwordSample,
Expand All @@ -69,11 +73,13 @@ const stringFormats = {
'ipv6': ipv6Sample,
'hostname': hostnameSample,
'uri': uriSample,
'uuid': uuidSample,
'default': defaultSample
};

export function sampleString(schema) {
export function sampleString(schema, options, spec, context) {
let format = schema.format || 'default';
let sampler = stringFormats[format] || defaultSample;
return sampler(schema.minLength | 0, schema.maxLength);
let propertyName = context && context.propertyName;
return sampler(schema.minLength | 0, schema.maxLength, propertyName);
}
4 changes: 2 additions & 2 deletions src/traverse.js
Expand Up @@ -9,7 +9,7 @@ export function clearCache() {
$refCache = {};
}

export function traverse(schema, options, spec) {
export function traverse(schema, options, spec, context) {
if (schema.$ref) {
if (!spec) {
throw new Error('Your schema contains $ref. You must provide specification in the third parameter.');
Expand Down Expand Up @@ -85,7 +85,7 @@ export function traverse(schema, options, spec) {
}
let sampler = _samplers[type];
if (sampler) {
example = sampler(schema, options, spec);
example = sampler(schema, options, spec, context);
}
}

Expand Down
43 changes: 39 additions & 4 deletions src/utils.js
Expand Up @@ -13,10 +13,10 @@ export function toRFCDateTime(date, omitTime, milliseconds) {
'-' + pad(date.getUTCDate());
if (!omitTime) {
res += 'T' + pad(date.getUTCHours()) +
':' + pad(date.getUTCMinutes()) +
':' + pad(date.getUTCSeconds()) +
(milliseconds ? '.' + (date.getUTCMilliseconds() / 1000).toFixed(3).slice(2, 5) : '') +
'Z';
':' + pad(date.getUTCMinutes()) +
':' + pad(date.getUTCSeconds()) +
(milliseconds ? '.' + (date.getUTCMilliseconds() / 1000).toFixed(3).slice(2, 5) : '') +
'Z';
}
return res;
};
Expand Down Expand Up @@ -46,3 +46,38 @@ export function mergeDeep(...objects) {
return prev;
}, Array.isArray(objects[objects.length - 1]) ? [] : {});
}

// deterministic UUID sampler

export function uuid(str) {
var hash = hashCode(str);
var random = jsf32(hash, hash, hash, hash);
var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
var r = (random() * 16) % 16 | 0;
return (c == 'x' ? r : (r & 0x3 | 0x8)).toString(16);
});
return uuid;
}

function hashCode(str) {
var hash = 0;
if (str.length == 0) return hash;
for (var i = 0; i < str.length; i++) {
var char = str.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash;
}
return hash;
}

function jsf32(a, b, c, d) {
return function () {
a |= 0; b |= 0; c |= 0; d |= 0;
var t = a - (b << 27 | b >>> 5) | 0;
a = b ^ (c << 17 | c >>> 15);
b = c + d | 0;
c = d + t | 0;
d = a + t | 0;
return (d >>> 0) / 4294967296;
}
}
13 changes: 13 additions & 0 deletions test/unit/object.spec.js
Expand Up @@ -90,4 +90,17 @@ describe('sampleObject', () => {
a: 'string'
});
});

it('should pass propertyName context to samplers', () => {
res = sampleObject({
properties: {
fooId: {type: 'string', format: 'uuid'},
barId: {type: 'string', format: 'uuid'},
}
});
expect(res).to.deep.equal({
fooId: 'fb4274c7-4fcd-4035-8958-a680548957ff',
barId: '3c966637-4898-4972-9a9d-baefa6cd6c89'
});
})
});
13 changes: 13 additions & 0 deletions test/unit/string.spec.js
Expand Up @@ -4,6 +4,7 @@ const IPV4_REGEXP = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5
const IPV6_REGEXP = /^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/;
const HOSTNAME_REGEXP = /^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/;
const URI_REGEXP = new RegExp('([A-Za-z][A-Za-z0-9+\\-.]*):(?:(//)(?:((?:[A-Za-z0-9\\-._~!$&\'()*+,;=:]|%[0-9A-Fa-f]{2})*)@)?((?:\\[(?:(?:(?:(?:[0-9A-Fa-f]{1,4}:){6}|::(?:[0-9A-Fa-f]{1,4}:){5}|(?:[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){4}|(?:(?:[0-9A-Fa-f]{1,4}:){0,1}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){3}|(?:(?:[0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){2}|(?:(?:[0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}:|(?:(?:[0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})?::)(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))|(?:(?:[0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}|(?:(?:[0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})?::)|[Vv][0-9A-Fa-f]+\\.[A-Za-z0-9\\-._~!$&\'()*+,;=:]+)\\]|(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)|(?:[A-Za-z0-9\\-._~!$&\'()*+,;=]|%[0-9A-Fa-f]{2})*))(?::([0-9]*))?((?:/(?:[A-Za-z0-9\\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})*)*)|/((?:(?:[A-Za-z0-9\\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})+(?:/(?:[A-Za-z0-9\\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})*)*)?)|((?:[A-Za-z0-9\\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})+(?:/(?:[A-Za-z0-9\\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})*)*)|)(?:\\?((?:[A-Za-z0-9\\-._~!$&\'()*+,;=:@/?]|%[0-9A-Fa-f]{2})*))?(?:\\#((?:[A-Za-z0-9\\-._~!$&\'()*+,;=:@/?]|%[0-9A-Fa-f]{2})*))?');
const UUID_REGEXP = /^[0-9A-Fa-f]{8}(?:-[0-9A-Fa-f]{4}){3}-[0-9A-Fa-f]{12}$/;

describe('sampleString', () => {
let res;
Expand Down Expand Up @@ -82,4 +83,16 @@ describe('sampleString', () => {
res = sampleString({format: 'uri'});
expect(res).to.match(URI_REGEXP);
});

it('should return valid uuid for uuid format without propertyName context', () => {
res = sampleString({format: 'uuid'});
expect(res).to.match(UUID_REGEXP);
expect(res).to.equal('497f6eca-6276-4993-bfeb-53cbbbba6f08');
});

it('should return valid uuid for uuid format with propertyName context', () => {
res = sampleString({format: 'uuid'}, null, null, {propertyName: 'fooId'});
expect(res).to.match(UUID_REGEXP);
expect(res).to.equal('fb4274c7-4fcd-4035-8958-a680548957ff');
});
});

0 comments on commit 9d6c312

Please sign in to comment.