Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Adding OpenAPI v3.0 Support #57

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ html-report
xunit.xml
node_modules
npm-debug.log
package-lock.json

.project
.idea
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
28 changes: 28 additions & 0 deletions lib/generators/v3/format.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
'use strict';
const Moment = require('moment');
const Chance = require('chance').Chance();
const Randexp = require('randexp').randexp;

const date = () => Moment().format('YYYY-MM-DD');
const dataTime = () => Moment().toISOString();
const url = () => Chance.url();
const email = () => Chance.email();
const phone = () => Chance.phone();
const guid = () => Chance.guid();
const ipv4 = () => Chance.ip();
const ipv6 = () => Chance.ipv6();
const hostname = () => Randexp(/^(([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])$/gm);

module.exports = {
date,
'date-time': dataTime,
uri: url,
url,
email,
phone,
uuid: guid,
guid,
ipv4,
ipv6,
hostname
};
239 changes: 239 additions & 0 deletions lib/generators/v3/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
'use strict';
const Chance = require('chance').Chance();
const Format = require('./format');
const Randexp = require('randexp').randexp;

const mock = (node, useExample) => {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Notice I'm calling this node instead of schema, as the schema is actually node.schema now.

screen shot 2018-07-24 at 10 57 43 am

let mock;

// Parameters and responses live inside the schema keyword now
const schema = node.schema;

if (schema) {
let type = schema.type || findType(schema);
let example = schema.examples || schema.example || schema.default;
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added default as an example, which the spec says can be used if no examples exist.

This schema.example || ... might give funny results if example is 0 or false.

/**
* Get the mock generator from the `type` of the schema
*/
if (example && useExample) {
mock = example;
} else {
const generator = Generators[type];
if (generator) {
mock = generator.call(null, schema, useExample);
}
}
}
return mock;
};

const objectMock = ({ properties, additionalProperties }, useExample ) => {
let mockObj = {};
if (properties) {
Object.keys(properties).forEach(function (key) {
mockObj[key] = mock(properties[key], useExample);
});
/**
* In the absense of `properties`, check if `additionalProperties` is defined or not.
* (If additionalProperties is an object, that object is a schema that will be used to validate
* any additional properties not listed in properties.)
*
* If present, use this to generate mocks.
*/
} else if (additionalProperties) {
//Create a random property
mockObj[Chance.word()] = mock(additionalProperties, useExample);
}
return mockObj;
};
/**
* Generates a mock `array` data of `items`
* Supports: `minItems` and `maxItems`
* TODO: Implement `uniqueItems`
*/
const arrayMock = ({ items, minItems, maxItems }, useExample) => {
let min;
let max;
let numItems;
let arr = [];

if (items) {
//Use the min as the base
min = minItems || 1;
if (maxItems) {
//If min is greater than max, use min as max.
max = (maxItems < min) ? min : maxItems;
} else {
//If max is not defined, use min as max.
max = min;
}
//Find the number of items with min and max boundary parameters.
numItems = Chance.integer({
min: min,
max: max
});
for (let i = 0; i < numItems; i++) {
arr.push(mock(items, useExample));
}
}
return arr;
};
/**
* Generates a mock `integer` value
* Supports `minimum`, `maximum`, `exclusiveMinimum` and `exclusiveMaximum`
* TODO - Validate `minimum` and `maximum` values
*/
const integerMock = schema => {
let opts = {};
let intmock;

/**
* If `enum` is defined for the property
*/
if (schema.enum && schema.enum.length > 0) {
return enumMock(schema);
}

if (Number.isInteger(schema.minimum)) {
opts.min = (schema.exclusiveMinimum) ? schema.minimum + 1 : schema.minimum;
}
if (Number.isInteger(schema.maximum)) {
opts.max = (schema.exclusiveMaximum) ? schema.maximum - 1 : schema.maximum;
}
//Generate a number that is multiple of schema.multipleOf
if (Number.isInteger(schema.multipleOf) && schema.multipleOf > 0) {
//Use the min/muplilier as the min number
//Use default min as 1 if min is not properly set.
opts.min = (Number.isInteger(opts.min)) ? (Math.ceil(opts.min / schema.multipleOf)) : 1;
//Use the max/muplilier as the new max value
//Use a default - min + 10 - if max value is not properly set.
opts.max = (Number.isInteger(opts.max)) ? (Math.floor(opts.max / schema.multipleOf)) : (opts.min + 10);
intmock = Chance.integer(opts);
intmock = intmock * schema.multipleOf;
} else {
intmock = Chance.integer(opts);
}
return intmock;
};

/**
* Generates a mock `number` value
* Supports `minimum`, `maximum`, `exclusiveMinimum` and `exclusiveMaximum`
* TODO - Validate `minimum` and `maximum` values
*/
const numberMock = schema => {
let opts = {};
let nummock;

/**
* If `enum` is defined for the property
*/
if (schema.enum && schema.enum.length > 0) {
return enumMock(schema);
}

if (Number.isFinite(schema.minimum)) {
opts.min = (schema.exclusiveMinimum) ? schema.minimum + 0.1 : schema.minimum;
}
if (Number.isFinite(schema.maximum)) {
opts.max = (schema.exclusiveMaximum) ? schema.maximum - 0.1 : schema.maximum ;
}
//Generate a number that is multiple of schema.multipleOf
if (Number.isFinite(schema.multipleOf) && schema.multipleOf > 0) {
//Use the min/muplilier as the min number
//Use default min as 1 if min is not properly set
opts.min = (Number.isFinite(opts.min)) ? (Math.ceil(opts.min / schema.multipleOf)) : 1;
//Use the max/muplilier as the new max value
//Use a default - min + 10 - if max value is not properly set.
opts.max = (Number.isFinite(opts.max)) ? (Math.floor(opts.max / schema.multipleOf)) : (opts.min + 10);

nummock = Chance.integer(opts);
nummock = nummock * schema.multipleOf;
} else {
nummock = Chance.floating(opts);
}
return nummock;
};

const booleanMock = schema => {
/**
* If `enum` is defined for the property
*/
if (schema.enum && schema.enum.length > 0) {
return enumMock(schema);
}
return Chance.bool();
};
/**
* Geneartes a mock `string` value
* Supports: `minLength`, `maxLength`, `enum`, `date`, and `date-time`
*
*/
const stringMock = schema => {
let mockStr;
let opts = {};
let minLength = schema.minLength || 1;
let maxLength = schema.maxLength || minLength + 10;
opts.min = minLength;
opts.max = maxLength;

if (schema.enum && schema.enum.length > 0) {
/**
* If `enum` is defined for the property
*/
mockStr = enumMock(schema);
} else if (schema.pattern) {
/**
* If `pattern` is defined for the property
*/
mockStr = Randexp(schema.pattern);
} else if(Format[schema.format]) {
/**
* If a `format` is defined for the property
*/
mockStr = Format[schema.format].call(null, schema);
} else {
mockStr = Chance.string({
length: Chance.integer(opts),
alpha: true //Use only alpha characters
});
}

return mockStr;
};

const enumMock = schema => {
let len = schema.enum.length;
let opts = {
min: 0,
max: len - 1
};
return schema.enum[Chance.integer(opts)];
};

const fileMock = () => {
return Chance.file();
};

//Find out the type based on schema props
//(This is not a complete list or full proof solution)
const findType = schema => {
let type = 'object';// Use 'object' as the default type
if (schema.pattern) {
type = 'string';
} else if (schema.items) {
type = 'array';
}
return type;
};

const Generators = module.exports = {
object: objectMock,
array: arrayMock,
string: stringMock,
integer: integerMock,
number: numberMock,
boolean: booleanMock,
file: fileMock,
mock
};
74 changes: 74 additions & 0 deletions lib/generators/v3/paramtypes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
const Generators = require('./index');

const defaultStyle = {
query: 'form',
path: 'simple',
header: 'simple',
cookie: 'form'
};

const arrayStyles = {
// matrix: val => ';' + val.join(','),
// label: val => '.' + val.join('.'),
// form: val => val.join('\t'),
simple: val => val.join(','),
spaceDelimited: val => val.join('%20'),
pipeDelimited: val => val.join('|'),
// deepObject: val => val.join('|'),
};

/**
* TODO : Handle type `file`
*/
const queryMock = param => {
// Describes how the parameter value will be serialized depending on the type of the parameter
// value. Default values (based on value of in): for query - form; for path - simple; for header
// - simple; for cookie - form.
if (typeof param.style !== 'string') {
param.style = defaultStyle[param.in];
}

// When this is true, parameter values of type array or object generate separate parameters for
// each value of the array or key-value pair of the map. For other types of parameters this property
// has no effect. When style is form, the default value is true. For all other styles, the default
// value is false.
if (typeof param.explode === 'undefined') {
param.explode = param.style == 'form';
}

let value = Generators.mock(param);

if (param.type === 'array') {
const collector = arrayStyles[param.style] || arrayStyles.simple;

console.log({collector})
console.log({value})
value = collector(value, param.explode);
console.log({value})

}

// TODO Support object collection too https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.1.md#style-values
if (param.type === 'object') {
}

return {
name: param.name,
value: value
};
};

const bodyMock = param => {
return {
name: param.name,
value: Generators.mock(param.schema)
};
};

module.exports = {
query: queryMock,
path: queryMock,
formData: queryMock,
header: queryMock,
body: bodyMock
};
Loading