Merge pull request #320 from postmanlabs/feature/other-bodytype-validation

Added support for urlencoded body validation.
This commit is contained in:
Vishal Shingala
2021-02-19 12:44:59 +05:30
committed by GitHub
5 changed files with 480 additions and 58 deletions

View File

@@ -1105,6 +1105,35 @@ module.exports = {
return helper;
},
/**
* Generates appropriate collection element based on parameter location
*
* @param {Object} param - Parameter object habing key, value and description (optional)
* @param {String} location - Parameter location ("in" property of OAS defined parameter object)
* @returns {Object} - SDK element
*/
generateSdkParam: function (param, location) {
const sdkElementMap = {
'query': sdk.QueryParam,
'header': sdk.Header,
'path': sdk.Variable
};
let generatedParam = {
key: param.key,
value: param.value
};
_.has(param, 'disabled') && (generatedParam.disabled = param.disabled);
// use appropriate sdk element based on location parmaeter is in for param generation
if (sdkElementMap[location]) {
generatedParam = new sdkElementMap[location](generatedParam);
}
param.description && (generatedParam.description = param.description);
return generatedParam;
},
/**
* Generates Auth helper for response, params (query, headers) in helper object is added in
* request (originalRequest) part of example.
@@ -1510,12 +1539,12 @@ module.exports = {
case 'form':
if (explode && _.isObject(paramValue)) {
_.forEach(paramValue, (value, key) => {
pmParams.push({
pmParams.push(this.generateSdkParam({
key: _.isArray(paramValue) ? paramName : key,
value: (value === undefined ? '' : value),
description,
disabled
});
}, _.get(param, 'in')));
});
return pmParams;
}
@@ -1528,12 +1557,12 @@ module.exports = {
case 'deepObject':
if (_.isObject(paramValue)) {
_.forOwn(paramValue, (value, key) => {
pmParams.push({
pmParams.push(this.generateSdkParam({
key: param.name + '[' + key + ']',
value: (value === undefined ? '' : value),
description,
disabled
});
}, _.get(param, 'in')));
});
}
return pmParams;
@@ -1559,12 +1588,12 @@ module.exports = {
// prepend starting value to serialised value (valid for empty value also)
serialisedValue = startValue + serialisedValue;
pmParams.push({
pmParams.push(this.generateSdkParam({
key: paramName,
value: serialisedValue,
description,
disabled
});
}, _.get(param, 'in')));
return pmParams;
},
@@ -1672,55 +1701,36 @@ module.exports = {
}
description = (required ? '(Required) ' : '') + description +
(enumValue ? ' (This can only be one of ' + enumValue + ')' : '');
if (encoding.hasOwnProperty(key)) {
encoding[key].name = key;
encoding[key].schema = {
type: typeof value
};
encoding[key].description = description;
params = this.convertParamsWithStyle(encoding[key], value, PARAMETER_SOURCE.REQUEST, components,
schemaCache, options);
// TODO: Show warning for incorrect schema if !params
params && params.forEach((element) => {
// Collection v2.1 schema allows urlencoded param value to be only string
if (typeof element.value !== 'string') {
try {
// convert other datatype to string (i.e. number, boolean etc)
element.value = JSON.stringify(element.value);
}
catch (e) {
// JSON.stringify can fail in few cases, suggest invalid type for such case
// eslint-disable-next-line max-len
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#Exceptions
element.value = 'INVALID_URLENCODED_PARAM_TYPE';
}
}
delete element.description;
});
paramArray.push(...params);
}
else {
!encoding[key] && (encoding[key] = {});
encoding[key].name = key;
encoding[key].schema = {
type: typeof value
};
// for urlencoded body serialisation is treated similar to query param
// reference https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#fixed-fields-13
encoding[key].in = 'query';
encoding[key].description = description;
params = this.convertParamsWithStyle(encoding[key], value, PARAMETER_SOURCE.REQUEST, components,
schemaCache, options);
// TODO: Show warning for incorrect schema if !params
params && params.forEach((element) => {
// Collection v2.1 schema allows urlencoded param value to be only string
if (typeof value !== 'string') {
if (typeof element.value !== 'string') {
try {
// convert other datatype to string (i.e. number, boolean etc)
value = JSON.stringify(value);
element.value = JSON.stringify(element.value);
}
catch (e) {
// JSON.stringify can fail in few cases, suggest invalid type for such case
// eslint-disable-next-line max-len
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#Exceptions
value = 'INVALID_URLENCODED_PARAM_TYPE';
element.value = 'INVALID_URLENCODED_PARAM_TYPE';
}
}
param = new sdk.QueryParam({
key: key,
value: value
});
param.description = description;
paramArray.push(param);
}
});
paramArray.push(...params);
});
updateOptions = {
mode: rDataMode,
@@ -2256,7 +2266,6 @@ module.exports = {
});
});
item.request.url.query.members.forEach((query) => {
query.description = _.get(query, 'description.content', '');
// Collection v2.1 schema allows query param value to be string/null
if (typeof query.value !== 'string') {
try {
@@ -3799,6 +3808,160 @@ module.exports = {
);
}, 0);
}
else if (requestBody && requestBody.mode === 'urlencoded') {
let urlencodedBodySchema = _.get(schemaPath, ['requestBody', 'content', URLENCODED, 'schema']),
resolvedSchemaParams = [],
pathPrefix = `${schemaPathPrefix}.requestBody.content[${URLENCODED}].schema`;
urlencodedBodySchema = deref.resolveRefs(urlencodedBodySchema, PARAMETER_SOURCE.REQUEST, components,
schemaCache.schemaResolutionCache, PROCESSING_TYPE.VALIDATION, 'example', 0, {}, options.stackLimit);
// resolve each property as separate param similar to query parmas
_.forEach(_.get(urlencodedBodySchema, 'properties'), (propSchema, propName) => {
let resolvedProp = {
name: propName,
schema: propSchema,
in: 'query' // serialization follows same behaviour as query params
},
encodingValue = _.get(schemaPath, ['requestBody', 'content', URLENCODED, 'encoding', propName]),
pSerialisationInfo,
isPropSeparable;
if (_.isObject(encodingValue)) {
_.has(encodingValue, 'style') && (resolvedProp.style = encodingValue.style);
_.has(encodingValue, 'explode') && (resolvedProp.explode = encodingValue.explode);
}
if (_.includes(_.get(urlencodedBodySchema, 'required'), propName)) {
resolvedProp.required = true;
}
pSerialisationInfo = this.getParamSerialisationInfo(resolvedProp, PARAMETER_SOURCE.REQUEST,
components, schemaCache);
isPropSeparable = _.includes(['form', 'deepObject'], pSerialisationInfo.style);
if (isPropSeparable && propSchema.type === 'array' && pSerialisationInfo.explode) {
// add schema of items and instead array
resolvedSchemaParams.push(_.assign({}, resolvedProp, {
schema: _.get(propSchema, 'items'),
isResolvedParam: true
}));
}
else if (isPropSeparable && propSchema.type === 'object' && pSerialisationInfo.explode) {
// add schema of all properties instead entire object
_.forEach(_.get(propSchema, 'properties', {}), (value, key) => {
resolvedSchemaParams.push({
name: key,
schema: value,
isResolvedParam: true
});
});
}
else {
resolvedSchemaParams.push(resolvedProp);
}
});
return async.map(requestBody.urlencoded, (uParam, cb) => {
let mismatches = [],
index = _.findIndex(requestBody.urlencoded, uParam),
resolvedParamValue = uParam.value;
const schemaParam = _.find(resolvedSchemaParams, (param) => { return param.name === uParam.key; });
if (!schemaParam) {
// no schema param found
if (options.showMissingInSchemaErrors) {
mismatches.push({
property: mismatchProperty,
transactionJsonPath: transactionPathPrefix + `.urlencoded[${index}]`,
schemaJsonPath: null,
reasonCode: 'MISSING_IN_SCHEMA',
reason: `The Url Encoded body param "${uParam.key}" was not found in the schema`
});
}
return cb(null, mismatches);
}
if (!schemaParam.isResolvedParam) {
resolvedParamValue = this.deserialiseParamValue(schemaParam, uParam.value, PARAMETER_SOURCE.REQUEST,
components, schemaCache);
}
// store value of transaction to use in mismatch object
schemaParam.actualValue = uParam.value;
// param found in spec. check param's schema
setTimeout(() => {
if (!schemaParam.schema) {
// no errors to show if there's no schema present in the spec
return cb(null, []);
}
this.checkValueAgainstSchema(mismatchProperty,
transactionPathPrefix + `.urlencoded[${index}].value`,
uParam.key,
resolvedParamValue,
pathPrefix + '.properties[' + schemaParam.name + ']',
schemaParam.schema,
PARAMETER_SOURCE.REQUEST,
components, options, schemaCache, cb
);
}, 0);
}, (err, res) => {
let mismatches = [],
mismatchObj,
// fetches property name from schem path
getPropNameFromSchemPath = (schemaPath) => {
let regex = /\.properties\[(.+)\]/gm;
return _.last(regex.exec(schemaPath));
};
// update actual value and suggested value from JSON to serialized strings
_.forEach(_.flatten(res), (mismatchObj) => {
if (!_.isEmpty(mismatchObj)) {
let propertyName = getPropNameFromSchemPath(mismatchObj.schemaJsonPath),
schemaParam = _.find(resolvedSchemaParams, (param) => { return param.name === propertyName; }),
serializedParamValue;
if (schemaParam) {
// serialize param value (to be used in suggested value)
serializedParamValue = _.get(this.convertParamsWithStyle(schemaParam, _.get(mismatchObj,
'suggestedFix.suggestedValue'), PARAMETER_SOURCE.REQUEST, components, schemaCache, options),
'[0].value');
_.set(mismatchObj, 'suggestedFix.actualValue', schemaParam.actualValue);
_.set(mismatchObj, 'suggestedFix.suggestedValue', serializedParamValue);
}
}
});
_.each(resolvedSchemaParams, (uParam) => {
// report mismatches only for reuired properties
if (!_.find(requestBody.urlencoded, (param) => { return param.key === uParam.name; }) && uParam.required) {
mismatchObj = {
property: mismatchProperty,
transactionJsonPath: transactionPathPrefix + '.urlencoded',
schemaJsonPath: pathPrefix + '.properties[' + uParam.name + ']',
reasonCode: 'MISSING_IN_REQUEST',
reason: `The Url Encoded body param "${uParam.name}" was not found in the transaction`
};
if (options.suggestAvailableFixes) {
mismatchObj.suggestedFix = {
key: uParam.name,
actualValue: null,
suggestedValue: {
key: uParam.name,
value: safeSchemaFaker(uParam.schema || {}, 'example', PROCESSING_TYPE.VALIDATION,
PARAMETER_SOURCE.REQUEST, components, SCHEMA_FORMATS.DEFAULT, options.indentCharacter, schemaCache,
options.stackLimit)
}
};
}
mismatches.push(mismatchObj);
}
});
return callback(null, _.concat(_.flatten(res), mismatches));
});
}
else {
return callback(null, []);
}