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

Support for recognizing complex MIME definitions in OAS3 content type #191

Merged
merged 3 commits into from
Oct 9, 2023
Merged
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
17 changes: 17 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
"@stryker-mutator/mocha-runner": "^6.4.2",
"babel-loader": "^9.1.2",
"chai": "^4.3.6",
"chai-string": "^1.5.0",
"core-js-pure": "^3.30.2",
"coveralls": "^3.1.1",
"eslint": "^8.41.0",
Expand Down
52 changes: 15 additions & 37 deletions src/impl/v2/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,20 @@
* Contains validation-logic that is specific to V2 of the OpenAPI-spec
*/

const { JSONPath: jsonPath } = require('jsonpath-plus'),
cloneDeep = require('lodash.clonedeep'),
const cloneDeep = require('lodash.clonedeep'),
{ setAllPropertiesRequired } = require('../service/all-properties-required'),
{ setNoAdditionalProperties } = require('../service/no-additional-properties');

// CONSTANTS

const PATH__EXAMPLES = '$..examples.application/json',
const PATH__EXAMPLES = '$..examples[?(@property.match(/[\/+]json/))]',
PROP__SCHEMA = 'schema',
PROP__EXAMPLES = 'examples';

module.exports = {
buildValidationMap,
escapeExampleName,
getJsonPathsToExamples,
prepare,
unescapeExampleNames
prepare
};

// IMPLEMENTATION DETAILS
Expand All @@ -30,16 +27,18 @@ module.exports = {
function getJsonPathsToExamples() { return [PATH__EXAMPLES]; }



/**
* Builds a map with the path to the repsonse-schema as key and the paths to the examples, as value. The path of the
* schema is derived from the path to the example and doesn't necessarily mean that the schema actually exists.
* Builds a map with the json-pointers to the response-schema as key and the json-pointers to the examples, as value.
* The pointer of the schema is derived from the pointer to the example and doesn't necessarily mean
* that the schema actually exists.
* @param {Array.<String>} pathsExamples Paths to the examples
* @returns {Object.<String, String>} Map with schema-path as key and example-paths as value
* @returns {Object.<String, String>} Map with schema-pointers as key and example-pointers as value
* @private
*/
function buildValidationMap(pathsExamples) {
return pathsExamples.reduce((validationMap, pathExample) => {
const pathSchema = _getSchemaPathOfExample(pathExample);
const pathSchema = _getSchemaPointerOfExample(pathExample);
validationMap[pathSchema] = (validationMap[pathSchema] || new Set())
.add(pathExample);
return validationMap;
Expand All @@ -62,35 +61,14 @@ function prepare(openapiSpec, { noAdditionalProperties, allPropertiesRequired }
}

/**
* Escapes the name of the example.
* @param {string} rawPath Unescaped path
* @returns {string} Escaped path
* @private
*/
function escapeExampleName(rawPath) {
// No escaping necessary in v2, as there are no named-examples
return rawPath;
}

/**
* Escaped example-names reflect in the result (where they shouldn't). This function reverts it.
* @param {string} rawPath Escaped path
* @returns {string} Unescaped path
*/
function unescapeExampleNames(rawPath) {
// No unescaping necessary in v2, as there are no named-examples
return rawPath;
}

/**
* Gets a JSON-path to the corresponding response-schema, based on a JSON-path to an example.
* @param {String} pathExample JSON-path to example
* @returns {String} JSON-path to the corresponding response-schema
* Gets a JSON-pointer to the corresponding response-schema, based on a JSON-pointer to an example.
* @param {String} examplePointer JSON-pointer to example
* @returns {String} JSON-pointer to the corresponding response-schema
* @private
*/
function _getSchemaPathOfExample(pathExample) {
const pathSegs = jsonPath.toPathArray(pathExample).slice(),
function _getSchemaPointerOfExample(examplePointer) {
const pathSegs = examplePointer.split('/'),
idxExamples = pathSegs.lastIndexOf(PROP__EXAMPLES);
pathSegs.splice(idxExamples, pathSegs.length - idxExamples, PROP__SCHEMA);
return jsonPath.toPathString(pathSegs);
return pathSegs.join('/');
}
63 changes: 24 additions & 39 deletions src/impl/v3/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,24 @@
* Contains validation-logic that is specific to V3 of the OpenAPI-spec
*/

const { JSONPath: jsonPath } = require('jsonpath-plus'),
cloneDeep = require('lodash.clonedeep'),
const cloneDeep = require('lodash.clonedeep'),
{ ApplicationError, ErrorType } = require('../../application-error'),
{ setAllPropertiesRequired } = require('../service/all-properties-required'),
{ setNoAdditionalProperties } = require('../service/no-additional-properties');

// CONSTANTS

const PATH__EXAMPLE = '$..responses..content.application/json.example',
PATH__EXAMPLES = '$..responses..content.application/json.examples.*.value',
const RESPONSES = '$..responses..content[?(@property.match(/[\/+]json/))]';
const REQUEST = '$..requestBody.content[?(@property.match(/[\/+]json/))]';
const SINGLE_EXAMPLE = '.example';
const MANY_EXAMPLES = '.examples.*.value';

const PATH__EXAMPLE = `${RESPONSES}${SINGLE_EXAMPLE}`,
PATH__EXAMPLES = `${RESPONSES}${MANY_EXAMPLES}`,
PATH__EXAMPLE__PARAMETER = '$..parameters..example',
PATH__EXAMPLES__PARAMETER = '$..parameters..examples.*.value',
PATH__EXAMPLE__REQUEST_BODY = '$..requestBody.content.application/json.example',
PATH__EXAMPLES__REQUEST_BODY = '$..requestBody.content.application/json.examples.*.value',
PATH__EXAMPLE__REQUEST_BODY = `${REQUEST}${SINGLE_EXAMPLE}`,
PATH__EXAMPLES__REQUEST_BODY = `${REQUEST}${MANY_EXAMPLES}`,
PROP__SCHEMA = 'schema',
PROP__EXAMPLE = 'example',
PROP__EXAMPLES = 'examples';
Expand All @@ -29,10 +33,8 @@ const ExampleType = {

module.exports = {
buildValidationMap,
escapeExampleName,
getJsonPathsToExamples,
prepare,
unescapeExampleNames
prepare
};

// IMPLEMENTATION DETAILS
Expand All @@ -53,17 +55,18 @@ function getJsonPathsToExamples() {
}

/**
* Builds a map with the path to the repsonse-schema as key and the paths to the examples, as value. The path of the
* schema is derived from the path to the example and doesn't necessarily mean that the schema actually exists.
* Builds a map with the json-pointers to the response-schema as key and the json-pointers to the examples, as value.
* The pointer of the schema is derived from the pointer to the example and doesn't necessarily mean
* that the schema actually exists.
* @param {Array.<String>} pathsExamples Paths to the examples
* @returns {Object.<String, String>} Map with schema-path as key and example-paths as value
* @returns {Object.<String, String>} Map with schema-pointers as key and example-pointers as value
* @private
*/
function buildValidationMap(pathsExamples) {
const exampleTypesOfSchemas = new Map();
return pathsExamples.reduce((validationMap, pathExample) => {
const { pathSchemaAsArray, exampleType } = _getSchemaPathOfExample(pathExample),
pathSchema = jsonPath.toPathString(pathSchemaAsArray),
const { pathSchemaAsArray, exampleType } = _getSchemaPointerOfExample(pathExample),
pathSchema = pathSchemaAsArray.join('/'),
exampleTypeOfSchema = exampleTypesOfSchemas.get(pathSchema);
if (exampleTypeOfSchema) {
exampleTypeOfSchema !== exampleType && _throwMutuallyExclusiveError(pathSchemaAsArray);
Expand Down Expand Up @@ -91,37 +94,18 @@ function prepare(openapiSpec, { noAdditionalProperties, allPropertiesRequired }
}

/**
* Escapes the name of the example. In order to do that, a backtick has to be added to the beginning of the key.
* @param {string} rawPath Unescaped path
* @returns {string} Escaped path
* @private
*/
function escapeExampleName(rawPath) {
return rawPath.replace(/\['examples'\]\['(.*)\]\['value'\]$/, "['examples']['`$1]['value']");
}

/**
* Escaped example-names reflect in the result (where they shouldn't). This function reverts it.
* @param {string} rawPath Escaped path
* @returns {string} Unescaped path
*/
function unescapeExampleNames(rawPath) {
return rawPath && rawPath.replace(/\/examples\/`(.*)\/value$/, '/examples/$1/value');
}

/**
* Gets a JSON-path to the corresponding response-schema, based on a JSON-path to an example.
* Gets a JSON-pointer to the corresponding response-schema, based on a JSON-pointer to an example.
*
* It is assumed that the JSON-path to the example is valid and existing.
* @param {String} pathExample JSON-path to example
* It is assumed that the JSON-pointer to the example is valid and existing.
* @param {String} examplePointer JSON-pointer to example
* @returns {{
* exampleType: ExampleType,
* pathSchema: String
* }} JSON-path to the corresponding response-schema
* @private
*/
function _getSchemaPathOfExample(pathExample) {
const pathSegs = jsonPath.toPathArray(pathExample).slice(),
function _getSchemaPointerOfExample(examplePointer) {
const pathSegs = examplePointer.split('/'),
idxExample = pathSegs.lastIndexOf(PROP__EXAMPLE),
/** @type ExampleType */
exampleType = idxExample > -1
Expand All @@ -137,6 +121,7 @@ function _getSchemaPathOfExample(pathExample) {
};
}


/**
* Checks if only `example` or `examples` is set for the schema, as they are mutually exclusive by OpenAPI-spec.
* @param {Array.<String>} pathSchemaAsArray JSON-path to the Schema, as JSON-path-array
Expand All @@ -149,7 +134,7 @@ function _throwMutuallyExclusiveError(pathSchemaAsArray) {
type: ErrorType.errorAndErrorsMutuallyExclusive,
message: 'Properties "error" and "errors" are mutually exclusive',
params: {
pathContext: jsonPath.toPointer(pathContextAsArray)
pathContext: pathContextAsArray.join('/')
}
});
}
Loading