Supporting XML schemas as well, refactoring, moving tests around

This commit is contained in:
abhijitkane
2018-12-12 01:22:17 +05:30
parent 21f1fb53ab
commit 9eac512396
7 changed files with 1588 additions and 1071 deletions

View File

@@ -185,9 +185,10 @@ $ openapi2postmanv2 --test
| request.url.params | parameter (`in = query`) | - | {"key": param.name, "value": [here](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.1.md#style-examples)}|
| api_key in (query or header) | components.securitySchemes.api_key | - ||
>`option : 'schemaFaker'(boolean) whether to use json-schema-faker for schema conversion`
>`option : 'requestNameSource'(string) The strategy to use to generate request names. url: use the request's URL as the name, fallback: Use the summary/operationId/URL (in that order) Default: fallback`
Usage Options:
* `'schemaFaker'(boolean): whether to use json-schema-faker for schema conversion`
* `'requestNameSource'(string): The strategy to use to generate request names. url: use the request's URL as the name, fallback: Use the summary/operationId/URL (in that order) Default: fallback`
* `'indentCharacter' (string): The character to use per level of indentation for JSON/XML data. Default: ' ' (space)`
### Header/Path param conversion example

View File

@@ -58,7 +58,8 @@ var sdk = require('postman-collection'),
// set default options
util.options = {
requestNameSource: (options.hasOwnProperty('requestNameSource') ? options.requestNameSource : 'fallback'),
schemaFaker: (options.hasOwnProperty('schemaFaker') ? options.schemaFaker : true)
schemaFaker: (options.hasOwnProperty('schemaFaker') ? options.schemaFaker : true),
indentCharacter: (options.hasOwnProperty('indentCharacter') ? options.indentCharacter : ' ')
};
// @TODO - Have to handle global level security scheme

View File

@@ -3,8 +3,13 @@ const sdk = require('postman-collection'),
parse = require('./parse.js'),
deref = require('./deref.js'),
_ = require('lodash'),
xmlFaker = require('./xmlSchemaFaker.js'),
openApiErr = require('./error.js'),
{ Node, Trie } = require('./trie.js'),
SCHEMA_FORMATS = {
DEFAULT: 'default', // used for non-request-body data and json
XML: 'xml' // used for request-body XMLs
},
URLENCODED = 'application/x-www-form-urlencoded',
APP_JSON = 'application/json',
APP_JS = 'application/javascript',
@@ -35,9 +40,11 @@ schemaFaker.option({
* removes things that might make schemaFaker crash
* @param {*} oldSchema the schema to fake
* @param {*} components list of predefined components (with schemas)
* @param {string} schemaFormat default or xml
* @param {string} indentCharacter char for 1 unit of indentation
* @returns {object} fakedObject
*/
function safeSchemaFaker(oldSchema, components) {
function safeSchemaFaker(oldSchema, components, schemaFormat, indentCharacter) {
var prop,
schema = deref.resolveRefs(oldSchema, components);
@@ -54,6 +61,10 @@ function safeSchemaFaker(oldSchema, components) {
}
try {
if (schemaFormat === SCHEMA_FORMATS.XML) {
return xmlFaker(null, schema, indentCharacter);
}
// for JSON, the indentCharacter will be applied in the JSON.stringify step later on
return schemaFaker(schema);
}
catch (e) {
@@ -101,12 +112,11 @@ module.exports = {
* something that can be added to the collection
* TODO: Figure out better description
* @param {object} serverVariables - Object containing the server variables at the root/path-item level
* @param {string} level - root / path-item level
* @param {string} keyName
* @param {string} serverUrl - URL from the server object
*
* @returns {object} modified collection after the addition of the server variables
*/
convertToPmCollectionVariables: function(serverVariables, level, serverUrl = '') {
convertToPmCollectionVariables: function(serverVariables, keyName, serverUrl = '') {
var variables = [];
if (serverVariables) {
_.forOwn(serverVariables, (value, key) => {
@@ -117,9 +127,9 @@ module.exports = {
}));
});
}
else {
if (keyName) {
variables.push(new sdk.Variable({
id: level,
id: keyName,
value: serverUrl,
type: 'string'
}));
@@ -131,6 +141,7 @@ module.exports = {
* Parses an OAS string/object as a YAML or JSON
* @param {YAML/JSON} openApiSpec - The OAS 3.x specification specified in either YAML or JSON
* @returns {Object} - Contains the parsed JSON-version of the OAS spec, or an error
* @no-unit-test
*/
parseSpec: function (openApiSpec) {
var openApiObj = openApiSpec,
@@ -350,14 +361,16 @@ module.exports = {
* @param {string} type - Level at the tree root/path level. Can be method/root/param.
* method: request(operation)-level, root: spec-level, param: url-level
* @param {Array<object>} providedPathVars - Array of path variables
* @param {object} commonPathVariables - Object of path variables taken from the specification
* @param {object|array} commonPathVars - Object of path variables taken from the specification
* @returns {Array<object>} returns an array of sdk.Variable
*/
convertPathVariables: function(type, providedPathVars, commonPathVariables) {
convertPathVariables: function(type, providedPathVars, commonPathVars) {
var variables = providedPathVars;
// converting the base uri path variables, if any
// commonPathVars is an object for type = root/method
// array otherwise
if (type === 'root' || type === 'method') {
_.forOwn(commonPathVariables, (value, key) => {
_.forOwn(commonPathVars, (value, key) => {
variables.push({
key: key,
value: type === 'root' ? '{{' + key + '}}' : value.default,
@@ -367,11 +380,12 @@ module.exports = {
});
}
else {
_.forEach(commonPathVariables, (variable) => {
_.forEach(commonPathVars, (variable) => {
variables.push({
key: variable.name,
// we only fake the schema for param-level pathVars
value: this.options.schemaFaker ? safeSchemaFaker(variable.schema || {}, this.components) : '',
value: this.options.schemaFaker ?
safeSchemaFaker(variable.schema || {}, this.components, SCHEMA_FORMATS.DEFAULT) : '',
description: variable.description || ''
});
});
@@ -405,7 +419,7 @@ module.exports = {
* @returns {string} space-separated string
*/
insertSpacesInName: function (string) {
if (!string) {
if (!string || (typeof string !== 'string')) {
return '';
}
@@ -421,6 +435,7 @@ module.exports = {
* @param {*} openapi object with root-level data like pathVariables baseurl
* @param {*} child object is of type itemGroup or request
* @returns {*} Postman itemGroup or request
* @no-unit-test
*/
convertChildToItemGroup: function(openapi, child) {
var resource = child,
@@ -491,6 +506,7 @@ module.exports = {
* @returns {object} The authHelper to use while constructing the Postman Request. This is
* not directly supported in the SDK - the caller needs to determine the header/body based on the return
* value
* @no-unit-test
*/
getAuthHelper: function(openapi, securitySet) {
var securityDef,
@@ -572,13 +588,13 @@ module.exports = {
}
}
responseBody = this.convertToPmBodyData(contentObj[cTypeHeader]);
responseBody = this.convertToPmBodyData(contentObj[cTypeHeader], cTypeHeader, this.options.indentCharacter);
if (cTypeHeader === APP_JSON) {
responseBody = JSON.stringify(responseBody, null, this.options.indentCharacter);
}
return {
contentTypeHeader: cTypeHeader,
// what if it's not JSON?
// TODO: The indentation must be an option
responseBody: JSON.stringify(responseBody, null, 4)
responseBody: responseBody
};
},
@@ -586,6 +602,7 @@ module.exports = {
* Create parameters specific for a request
* @param {*} localParams parameters array
* @returns {Object} with three arrays of query, header and path as keys.
* @no-unit-test
*/
getParametersForPathItem: function(localParams) {
var tempParam,
@@ -637,14 +654,16 @@ module.exports = {
/**
* converts one of the eamples or schema in Media Type object to postman data
* @param {*} bodyObj is MediaTypeObject
* @param {string} indentCharacter is needed for XML/JSON bodies only
* @returns {*} postman body data
*/
// TODO: We also need to accept the content type
// and generate the body accordingly
// right now, even if the content-type was XML, we'll generate
// a JSON example/schema
convertToPmBodyData: function(bodyObj) {
var bodyData = '';
convertToPmBodyData: function(bodyObj, contentType, indentCharacter = '') {
var bodyData = '',
schemaType = SCHEMA_FORMATS.DEFAULT;
if (bodyObj.example) {
bodyData = bodyObj.example;
@@ -661,7 +680,16 @@ module.exports = {
if (bodyObj.schema.hasOwnProperty('$ref')) {
bodyObj.schema = this.getRefObject(bodyObj.schema.$ref);
}
bodyData = this.options.schemaFaker ? safeSchemaFaker(bodyObj.schema || {}, this.components) : '';
if (this.options.schemaFaker) {
if (contentType === APP_XML || contentType === TEXT_XML) {
schemaType = SCHEMA_FORMATS.XML;
}
bodyData = safeSchemaFaker(bodyObj.schema || {}, this.components, schemaType, indentCharacter);
}
else {
// do not fake if the option is false
bodyData = '';
}
}
return bodyData;
},
@@ -681,7 +709,8 @@ module.exports = {
// check for existence of schema
if (param.hasOwnProperty('schema')) {
// fake data generated
paramValue = this.options.schemaFaker ? safeSchemaFaker(param.schema, this.components) : '';
paramValue = this.options.schemaFaker ?
safeSchemaFaker(param.schema, this.components, SCHEMA_FORMATS.DEFAULT) : '';
// paramType = param.schema.type;
return this.convertParamsWithStyle(param, paramValue);
}
@@ -850,7 +879,8 @@ module.exports = {
reqHeader;
if (header.hasOwnProperty('schema')) {
fakeData = this.options.schemaFaker ? JSON.stringify(safeSchemaFaker(header.schema || {}, this.components)) : '';
fakeData = this.options.schemaFaker ?
JSON.stringify(safeSchemaFaker(header.schema || {}, this.components, SCHEMA_FORMATS.DEFAULT)) : '';
}
else {
fakeData = '';
@@ -893,7 +923,7 @@ module.exports = {
if (contentObj[URLENCODED].hasOwnProperty('schema') && contentObj[URLENCODED].schema.hasOwnProperty('$ref')) {
contentObj[URLENCODED].schema = this.getRefObject(contentObj[URLENCODED].schema.$ref);
}
bodyData = this.convertToPmBodyData(contentObj[URLENCODED]);
bodyData = this.convertToPmBodyData(contentObj[URLENCODED], URLENCODED);
encoding = contentObj[URLENCODED].encoding ? contentObj[URLENCODED].encoding : {};
// create query parameters and add it to the request body object
_.forOwn(bodyData, (value, key) => {
@@ -936,7 +966,7 @@ module.exports = {
}
else if (contentObj.hasOwnProperty(FORM_DATA)) {
rDataMode = 'formdata';
bodyData = this.convertToPmBodyData(contentObj[FORM_DATA]);
bodyData = this.convertToPmBodyData(contentObj[FORM_DATA], FORM_DATA);
encoding = contentObj[FORM_DATA].encoding ? contentObj[FORM_DATA].encoding : {};
// create the form parameters and add it to the request body object
_.forOwn(bodyData, (value, key) => {
@@ -992,7 +1022,7 @@ module.exports = {
}
}
bodyData = this.convertToPmBodyData(contentObj[bodyType]);
bodyData = this.convertToPmBodyData(contentObj[bodyType], bodyType, this.options.indentCharacter);
updateOptions = {
mode: rDataMode,
@@ -1075,6 +1105,7 @@ module.exports = {
/**
* @param {*} $ref reference object
* @returns {Object} reference object from the saved components
* @no-unit-tests
*/
getRefObject: function($ref) {
var refObj, savedSchema;
@@ -1104,7 +1135,8 @@ module.exports = {
* function to convert an openapi path item to postman item
* @param {*} openapi openapi object with root properties
* @param {*} operationItem path operationItem from tree structure
* @returns {Object} postman request Item
* @returns {Object} postman request Item
* @no-unit-test
*/
convertRequestToItem: function(openapi, operationItem) {
var reqName,

111
lib/xmlSchemaFaker.js Normal file
View File

@@ -0,0 +1,111 @@
/* eslint-disable */
const _ = require('lodash');
function convertSchemaToXML(name, schema, attribute, indentChar, inden) {
var tagPrefix = '',
cInden = _.times(inden, _.constant(indentChar)).join('');
name = _.get(schema, 'xml.name', name || 'element');
if (_.get(schema, 'xml.prefix')) {
tagPrefix = schema.xml.prefix + ':';
}
if (['integer','string'].includes(schema.type)) {
if (schema.type === 'integer') {
actualValue = '(integer)';
}
else if (schema.type === 'string') {
actualValue = '(string)';
}
if (attribute) {
return actualValue;
}
else {
var retVal = '\n' + cInden + '<' + tagPrefix+name;
if (_.get(schema, 'xml.namespace')) {
retVal += ' xmlns:' + tagPrefix.slice(0,-1) + '="'+schema.xml.namespace+'"'
}
retVal += '>' + actualValue + '</' + tagPrefix+name + '>';
}
}
else if (schema.type === 'object') {
// go through all properties
var retVal = '\n' + cInden + `<${tagPrefix+name}`, propVal, attributes = [], childNodes = '';
if (_.get(schema, 'xml.namespace')) {
retVal += ' xmlns:' + tagPrefix.slice(0,-1) + '="'+schema.xml.namespace+'"'
}
_.forOwn(schema.properties, (value, key) => {
propVal = convertSchemaToXML(key, value, _.get(value, 'xml.attribute'), indentChar, inden + 1);
if (_.get(value, 'xml.attribute')) {
attributes.push(key + '="' + propVal + '"');
}
else {
childNodes += propVal;
}
});
if (attributes.length > 0) {
retVal += ' ' + attributes.join(' ');
}
retVal += '>';
retVal += childNodes;
retVal += '\n</' + name + '>';
}
else if (schema.type === 'array') {
// schema.items must be an object
var isWrapped = _.get(schema, 'xml.wrapped'),
extraIndent = isWrapped ? 1 : 0;
var arrayElemName = _.get(schema, 'items.xml.name', name, 'arrayItem');
var contents = convertSchemaToXML(arrayElemName, schema.items, false, indentChar, inden + extraIndent) +
convertSchemaToXML(arrayElemName, schema.items, false, indentChar, inden + extraIndent);
if (isWrapped) {
return '\n' + cInden + '<' + name + '>' + contents + '\n' + cInden + '</' + name + '>';
}
else {
return contents;
}
}
return retVal;
}
module.exports = function(name, schema, indentCharacter) {
// substring(1) to trim the leading newline
return convertSchemaToXML(name, schema, false, indentCharacter, 0).substring(1);
};
/*
a = convertSchemaToXML('Person',{
"type": "object",
"properties": {
"id": {
"type": "integer",
"format": "int32",
"xml": {
"attribute": true
}
},
"name": {
"type": "string",
"xml": {
"namespace": "http://example.com/schema/sample",
"prefix": "sample"
}
},
"animals": {
"type": "array",
"items": {
"type": "string",
"xml": {
"name": "animal"
}
},
"xml": {
"name": "aliens",
"wrapped": true
}
}
},
xml: {
namespace: "www.kane.com",
"prefix": "M"
}
}, false, 0);
console.log(a);
*/

File diff suppressed because it is too large Load Diff

40
test/unit/deref.test.js Normal file
View File

@@ -0,0 +1,40 @@
var expect = require('chai').expect,
deref = require('../../lib/deref.js');
describe('DEREF FUNCTION TESTS ', function() {
it('resolveRefs Function should return schema with resolved references.', function(done) {
var schema = {
$ref: '#/components/schemas/schema1'
},
components = {
schemas: {
schema1: {
anyOf: [{
$ref: '#/components/schemas/schema2'
},
{
$ref: '#/components/schemas/schema3'
}
]
},
schema2: {
type: 'object',
required: [
'id'
],
properties: {
id: {
type: 'integer',
format: 'int64'
}
}
}
}
},
output = deref.resolveRefs(schema, components);
expect(output).to.deep.include({ type: 'object',
required: ['id'],
properties: { id: { default: '<long>', type: 'string' } } });
done();
});
});

1373
test/unit/util.test.js Normal file

File diff suppressed because it is too large Load Diff