Skip to content

Commit

Permalink
Merge pull request #236 from postmanlabs/release/1.1.20
Browse files Browse the repository at this point in the history
Release v1.1.20
  • Loading branch information
VShingala authored Jun 14, 2020
2 parents b339869 + ca85073 commit 26209d4
Show file tree
Hide file tree
Showing 15 changed files with 861 additions and 218 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
# OpenAPI-Postman Changelog
#### v1.1.20 (June 14, 2020)
* Added support for stricter request matching via option for validation.
* Added missing endpoints from collection info in result of validation.
* Suggest fixes in collection for violated properties in validation.
* Introduced option to validate metadata for validation.
* Use faked value instead of fallback to schema for parameter resolution when set to example.
* Use faked value instead of invalid schema defined example.
* Introduced option to ignore unresolved postman variable mismatches.
* Fixed invalid generated collection for body type formdata.

#### v1.1.19 (June 12, 2020)
* Fix for [#232](https://github.com/postmanlabs/openapi-to-postman/issues/232) - Changes default auth of requests to null conforming to the JSON schema.

Expand Down
45 changes: 42 additions & 3 deletions assets/json-schema-faker.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
* Date: 2018-04-09 17:23:23.954Z
*/

var validateSchema = require('../lib/ajvValidation').validateSchema;

(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
Expand Down Expand Up @@ -24430,6 +24432,15 @@ function extend() {
return dateTimeGenerator().slice(11);
}

/**
* Added few formats from latest json-schema-faker. see below for source
* https://github.com/json-schema-faker/json-schema-faker/blob/master/src/lib/generators/coreFormat.js
*
*/
const FRAGMENT = '[a-zA-Z][a-zA-Z0-9+-.]*';
const URI_PATTERN = `https?://{hostname}(?:${FRAGMENT})+`;
const PARAM_PATTERN = '(?:\\?([a-z]{1,7}(=\\w{1,5})?&){0,3})?';

/**
* Predefined core formats
* @type {[key: string]: string}
Expand All @@ -24438,9 +24449,24 @@ function extend() {
email: '[a-zA-Z\\d][a-zA-Z\\d-]{1,13}[a-zA-Z\\d]@{hostname}',
hostname: '[a-zA-Z]{1,33}\\.[a-z]{2,4}',
ipv6: '[a-f\\d]{4}(:[a-f\\d]{4}){7}',
uri: 'https?://[a-zA-Z][a-zA-Z0-9+-.]*',
'uri-reference': '(https?://|#|/|)[a-zA-Z][a-zA-Z0-9+-.]*',
uri: URI_PATTERN,
slug: '[a-zA-Z\\d_-]+',

// types from draft-0[67] (?)
'uri-reference': `${URI_PATTERN}${PARAM_PATTERN}`,
'uri-template': URI_PATTERN.replace('(?:', '(?:/\\{[a-z][:a-zA-Z0-9-]*\\}|'),
'json-pointer': `(/(?:${FRAGMENT.replace(']*', '/]*')}|~[01]))+`,

// some types from https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.1.md#data-types (?)
uuid: '^(?:urn:uuid:)?[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$',
};

regexps.iri = regexps['uri-reference'];
regexps['iri-reference'] = regexps['uri-reference'];

regexps['idn-email'] = regexps.email;
regexps['idn-hostname'] = regexps.hostname;

/**
* Generates randomized string basing on a built-in regex format
*
Expand Down Expand Up @@ -24476,6 +24502,14 @@ function extend() {
case 'ipv6':
case 'uri':
case 'uri-reference':
case 'iri':
case 'iri-reference':
case 'idn-email':
case 'idn-hostname':
case 'json-pointer':
case 'slug':
case 'uri-template':
case 'uuid':
return coreFormatGenerator(value.format);
default:
if (typeof callback === 'undefined') {
Expand Down Expand Up @@ -24520,7 +24554,12 @@ function extend() {
return;
}
if (optionAPI('useExamplesValue') && 'example' in schema) {
return schema.example;
var result = validateSchema(schema, schema.example);

// Use example only if valid
if (result && result.length === 0) {
return schema.example;
}
}
if (optionAPI('useDefaultValue') && 'default' in schema) {
return schema.default;
Expand Down
89 changes: 89 additions & 0 deletions lib/ajvValidation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
var _ = require('lodash'),
Ajv = require('ajv');

// Following keyword are supoorted for Ajv but not by OAS
const IGNORED_KEYWORDS = ['propertyNames', 'const', 'additionalItems', 'dependencies'];

/**
* Checks if value is postman variable or not
*
* @param {*} value - Value to check for
* @returns {Boolean} postman variable or not
*/
function isPmVariable (value) {
// collection/environment variables are in format - {{var}}
return _.isString(value) && _.startsWith(value, '{{') && _.endsWith(value, '}}');
}

/**
* Used to validate schema against a value.
* NOTE: Used in assets/json-schema-faker.js to validate schema example
*
* @param {*} schema - schema to validate
* @param {*} valueToUse - value to validate schema against
* @param {*} options - a standard list of options that's globally passed around. Check options.js for more.
* @returns {*} - Found Validation Errors
*/
function validateSchema (schema, valueToUse, options = {}) {
var ajv,
validate,
filteredValidationError;

try {
// add Ajv options to support validation of OpenAPI schema.
// For more details see https://ajv.js.org/#options
ajv = new Ajv({
// the unknown formats are ones that are allowed in OAS, but not JSON schema.
unknownFormats: ['int32', 'int64'],

// check all rules collecting all errors. instead returning after the first error.
allErrors: true,

// supports keyword "nullable" from Open API 3 specification.
nullable: true
});
validate = ajv.compile(schema);
validate(valueToUse);
}
catch (e) {
// something went wrong validating the schema
// input was invalid. Don't throw mismatch
return filteredValidationError;
}

// Filter validation errors for following cases
filteredValidationError = _.filter(_.get(validate, 'errors', []), (validationError) => {
let dataPath = _.get(validationError, 'dataPath', '');

// discard the leading '.' if it exists
if (dataPath[0] === '.') {
dataPath = dataPath.slice(1);
}

// for invalid `propertyNames` two error are thrown by Ajv, which include error with `pattern` keyword
if (validationError.keyword === 'pattern') {
return !_.has(validationError, 'propertyName');
}

// As OAS only supports some of Json Schema keywords, and Ajv is supporting all keywords from Draft 7
// Remove keyword currently not supported in OAS to make both compatible with each other
else if (_.includes(IGNORED_KEYWORDS, validationError.keyword)) {
return false;
}

// Ignore unresolved variables from mismatch if option is set
else if (options.ignoreUnresolvedVariables && isPmVariable(_.get(valueToUse, dataPath))) {
return false;
}
return true;
});

// sort errors based on dataPath, as this will ensure no overriding later
filteredValidationError = _.sortBy(filteredValidationError, ['dataPath']);

return filteredValidationError;
}

module.exports = {
validateSchema
};
40 changes: 25 additions & 15 deletions lib/ajvValidationError.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,35 +9,45 @@ var _ = require('lodash');
* @returns {Object} mismatchObj with reason and reasonCode properties
*/
function ajvValidationError(ajvValidationErrorObj, data) {
var mismatchPropName = _.get(ajvValidationErrorObj, 'dataPath', '').slice(1),
mismatchObj = {
reason: `The ${data.humanPropName} property "${mismatchPropName}" ` +
`${ajvValidationErrorObj.message}`,
reasonCode: 'INVALID_TYPE'
};
var mismatchPropName = _.get(ajvValidationErrorObj, 'dataPath', ''),
mismatchObj;

// discard the leading '.' if it exists
if (mismatchPropName[0] === '.') {
mismatchPropName = mismatchPropName.slice(1);
}

mismatchObj = {
reason: `The ${data.humanPropName} property "${mismatchPropName}" ` +
`${ajvValidationErrorObj.message}`,
reasonCode: 'INVALID_TYPE'
};

switch (ajvValidationErrorObj.keyword) {

case 'additionalProperties':
mismatchObj.reasonCode = 'MISSING_IN_SCHEMA';
break;

case 'dependencies':
mismatchObj.reason = `The ${data.humanPropName} property "${mismatchPropName}" ` +
`should have property "${_.get(ajvValidationErrorObj, 'params.missingProperty')}" when property ` +
`"${_.get(ajvValidationErrorObj, 'params.property')}" is present`;
break;
// currently not supported as OAS doesn't support this keyword
// case 'dependencies':
// mismatchObj.reasonCode = 'MISSING_IN_REQUEST';
// mismatchObj.reason = `The ${data.humanPropName} property "${mismatchPropName}" ` +
// `should have property "${_.get(ajvValidationErrorObj, 'params.missingProperty')}" when property ` +
// `"${_.get(ajvValidationErrorObj, 'params.property')}" is present`;
// break;

case 'required':
mismatchObj.reasonCode = 'MISSING_IN_REQUEST';
mismatchObj.reason = `The ${data.humanPropName} property "${mismatchPropName}" should have required ` +
`property "${_.get(ajvValidationErrorObj, 'params.missingProperty')}"`;
break;

case 'propertyNames':
mismatchObj.reason = `The ${data.humanPropName} property "${mismatchPropName}" contains invalid ` +
`property named "${_.get(ajvValidationErrorObj, 'params.propertyName')}"`;
break;
// currently not supported as OAS doesn't support this keyword
// case 'propertyNames':
// mismatchObj.reason = `The ${data.humanPropName} property "${mismatchPropName}" contains invalid ` +
// `property named "${_.get(ajvValidationErrorObj, 'params.propertyName')}"`;
// break;`

default:
break;
Expand Down
59 changes: 35 additions & 24 deletions lib/deref.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,14 @@ module.exports = {
* @param {*} components components in openapi spec.
* @param {object} schemaResolutionCache stores already resolved references
* @param {*} resolveFor - resolve refs for validation/conversion (value to be one of VALIDATION/CONVERSION)
* @param {string} resolveTo The desired JSON-generation mechanism (schema: prefer using the JSONschema to
generate a fake object, example: use specified examples as-is). Default: schema
* @param {*} stack counter which keeps a tab on nested schemas
* @param {*} seenRef References that are repeated. Used to identify circular references.
* @returns {*} schema - schema that adheres to all individual schemas in schemaArr
*/
resolveAllOf: function (schemaArr, parameterSourceOption, components, schemaResolutionCache,
resolveFor = 'CONVERSION', stack = 0, seenRef) {
resolveFor = 'CONVERSION', resolveTo = 'schema', stack = 0, seenRef) {

if (!(schemaArr instanceof Array)) {
return null;
Expand All @@ -69,13 +71,13 @@ module.exports = {
if (schemaArr.length === 1) {
// for just one entry in allOf, don't need to enforce type: object restriction
return this.resolveRefs(schemaArr[0], parameterSourceOption, components, schemaResolutionCache, resolveFor,
stack, seenRef);
resolveTo, stack, seenRef);
}

// generate one object for each schema
let indivObjects = schemaArr.map((schema) => {
return this.resolveRefs(schema, parameterSourceOption, components, schemaResolutionCache, resolveFor,
stack, seenRef);
resolveTo, stack, seenRef);
}).filter((schema) => {
return schema.type === 'object';
}),
Expand Down Expand Up @@ -113,13 +115,15 @@ module.exports = {
* @param {*} components components in openapi spec.
* @param {object} schemaResolutionCache stores already resolved references
* @param {*} resolveFor - resolve refs for validation/conversion (value to be one of VALIDATION/CONVERSION)
* @param {string} resolveTo The desired JSON-generation mechanism (schema: prefer using the JSONschema to
generate a fake object, example: use specified examples as-is). Default: schema
* @param {*} stack counter which keeps a tab on nested schemas
* @param {*} seenRef - References that are repeated. Used to identify circular references.
* @returns {*} schema satisfying JSON-schema-faker.
*/

resolveRefs: function (schema, parameterSourceOption, components, schemaResolutionCache,
resolveFor = 'CONVERSION', stack = 0, seenRef = {}) {
resolveFor = 'CONVERSION', resolveTo = 'schema', stack = 0, seenRef = {}) {
var resolvedSchema, prop, splitRef,
ERR_TOO_MANY_LEVELS = '<Error: Too many levels of nesting to fake this schema>';

Expand All @@ -136,15 +140,15 @@ module.exports = {

if (schema.anyOf) {
return this.resolveRefs(schema.anyOf[0], parameterSourceOption, components, schemaResolutionCache, resolveFor,
stack, _.cloneDeep(seenRef));
resolveTo, stack, _.cloneDeep(seenRef));
}
if (schema.oneOf) {
return this.resolveRefs(schema.oneOf[0], parameterSourceOption, components, schemaResolutionCache, resolveFor,
stack, _.cloneDeep(seenRef));
resolveTo, stack, _.cloneDeep(seenRef));
}
if (schema.allOf) {
return this.resolveAllOf(schema.allOf, parameterSourceOption, components, schemaResolutionCache, resolveFor,
stack, _.cloneDeep(seenRef));
resolveTo, stack, _.cloneDeep(seenRef));
}
if (schema.$ref && _.isFunction(schema.$ref.split)) {
let refKey = schema.$ref;
Expand Down Expand Up @@ -186,7 +190,7 @@ module.exports = {

if (resolvedSchema) {
let refResolvedSchema = this.resolveRefs(resolvedSchema, parameterSourceOption,
components, schemaResolutionCache, resolveFor, stack, _.cloneDeep(seenRef));
components, schemaResolutionCache, resolveFor, resolveTo, stack, _.cloneDeep(seenRef));

if (refResolvedSchema && refResolvedSchema.value !== ERR_TOO_MANY_LEVELS) {
schemaResolutionCache[refKey] = refResolvedSchema;
Expand Down Expand Up @@ -221,15 +225,19 @@ module.exports = {
continue;
}
/* eslint-enable */
tempSchema.properties[prop] = this.resolveRefs(property,
parameterSourceOption, components, schemaResolutionCache, resolveFor, stack, _.cloneDeep(seenRef));
tempSchema.properties[prop] = this.resolveRefs(property, parameterSourceOption, components,
schemaResolutionCache, resolveFor, resolveTo, stack, _.cloneDeep(seenRef));
}
}
return tempSchema;
}

schema.type = 'string';
schema.default = '<object>';

// Override deefault value to appropriate type representation for parameter resolution to schema
if (resolveFor === 'CONVERSION' && resolveTo === 'schema') {
schema.default = '<object>';
}
}
else if (schema.type === 'array' && schema.items) {
/*
Expand Down Expand Up @@ -258,25 +266,28 @@ module.exports = {
// without this, schemas with circular references aren't faked correctly
let tempSchema = _.omit(schema, 'items');
tempSchema.items = this.resolveRefs(schema.items, parameterSourceOption,
components, schemaResolutionCache, resolveFor, stack, _.cloneDeep(seenRef));
components, schemaResolutionCache, resolveFor, resolveTo, stack, _.cloneDeep(seenRef));
return tempSchema;
}
else if (!schema.hasOwnProperty('default')) {
if (schema.hasOwnProperty('type')) {
if (!schema.hasOwnProperty('format')) {
schema.default = '<' + schema.type + '>';
}
else if (type.hasOwnProperty(schema.type)) {
schema.default = type[schema.type][schema.format];
// Override default value to schema for CONVERSION only for parmeter resolution set to schema
if (resolveFor === 'CONVERSION' && resolveTo === 'schema') {
if (!schema.hasOwnProperty('format')) {
schema.default = '<' + schema.type + '>';
}
else if (type.hasOwnProperty(schema.type)) {
schema.default = type[schema.type][schema.format];

// in case the format is a custom format (email, hostname etc.)
// https://swagger.io/docs/specification/data-models/data-types/#string
if (!schema.default && schema.format) {
schema.default = '<' + schema.format + '>';
// in case the format is a custom format (email, hostname etc.)
// https://swagger.io/docs/specification/data-models/data-types/#string
if (!schema.default && schema.format) {
schema.default = '<' + schema.format + '>';
}
}
else {
schema.default = '<' + schema.type + (schema.format ? ('-' + schema.format) : '') + '>';
}
}
else {
schema.default = '<' + schema.type + (schema.format ? ('-' + schema.format) : '') + '>';
}
}
else if (schema.enum && schema.enum.length > 0) {
Expand Down
Loading

0 comments on commit 26209d4

Please sign in to comment.