Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
7 changes: 0 additions & 7 deletions dist/Dockerfile

This file was deleted.

2 changes: 1 addition & 1 deletion dist/cli.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/cli.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/index.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/index.js.map

Large diffs are not rendered by default.

96 changes: 67 additions & 29 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ module.exports = {
'default': validateExamples,
validateFile,
validateExample,
validateExamplesByMap
validateExamplesByMap,
getValidatorFactory
};

// IMPLEMENTATION DETAILS
Expand Down Expand Up @@ -99,17 +100,24 @@ module.exports = {
* "unsupported format" errors). If an Array with only one string is
* provided where the formats are separated with `\n`, the entries
* will be expanded to a new array containing all entries.
* @param {Function} [specPostprocessor] Provides implementation of spec postprocessor
* @param {Function} [validatorFactory] Validator factory provider
* @returns {ValidationResponse}
*/
async function validateExamples(openapiSpec, { noAdditionalProperties, ignoreFormats, allPropertiesRequired } = {}) {
async function validateExamples(openapiSpec, { noAdditionalProperties, ignoreFormats, allPropertiesRequired,
specPostprocessor = (spec) => spec,
validatorFactory = (spec, ignoreFormats) => _initValidatorFactory(spec, { ignoreFormats })
} = {}) {
const impl = Determiner.getImplementation(openapiSpec);
openapiSpec = await refParser.dereference(openapiSpec);
openapiSpec = impl.prepare(openapiSpec, { noAdditionalProperties, allPropertiesRequired });
openapiSpec = _postprocess(openapiSpec, specPostprocessor);
let pathsExamples = impl.getJsonPathsToExamples()
.reduce((res, pathToExamples) => {
return res.concat(_pathToPointer(pathToExamples, openapiSpec));
}, []);
return _validateExamplesPaths({ impl }, pathsExamples, openapiSpec, { ignoreFormats });
const createValidator = validatorFactory(openapiSpec, ignoreFormats);
return _validateExamplesPaths({ impl, createValidator }, pathsExamples, openapiSpec);
}

/**
Expand All @@ -121,16 +129,24 @@ async function validateExamples(openapiSpec, { noAdditionalProperties, ignoreFor
* "unsupported format" errors). If an Array with only one string is
* provided where the formats are separated with `\n`, the entries
* will be expanded to a new array containing all entries.
* @param {Function} [specPostprocessor] Provides implementation of spec postprocessor
* @param {Function} [validatorFactory] Validator factory provider
* @returns {ValidationResponse}
*/
async function validateFile(filePath, { noAdditionalProperties, ignoreFormats, allPropertiesRequired } = {}) {
async function validateFile(filePath, { noAdditionalProperties, ignoreFormats, allPropertiesRequired,
specPostprocessor = (spec) => spec,
validatorFactory = (spec, ignoreFormats) => _initValidatorFactory(spec, { ignoreFormats })
} = {}) {
let openapiSpec = null;
try {
openapiSpec = await _parseSpec(filePath);
} catch (err) {
return createValidationResponse({ errors: [ApplicationError.create(err)] });
}
return validateExamples(openapiSpec, { noAdditionalProperties, ignoreFormats, allPropertiesRequired });
return validateExamples(openapiSpec, {
noAdditionalProperties, ignoreFormats, allPropertiesRequired,
specPostprocessor, validatorFactory
});
}

/**
Expand All @@ -147,11 +163,15 @@ async function validateFile(filePath, { noAdditionalProperties, ignoreFormats, a
* "unsupported format" errors). If an Array with only one string is
* provided where the formats are separated with `\n`, the entries
* will be expanded to a new array containing all entries.
* @param {Function} [specPostprocessor] Provides implementation of spec postprocessor
* @param {Function} [validatorFactory] Validator factory provider
* @returns {ValidationResponse}
*/
async function validateExamplesByMap(filePathSchema, globMapExternalExamples,
{ cwdToMappingFile, noAdditionalProperties, ignoreFormats, allPropertiesRequired } = {}
) {
{ cwdToMappingFile, noAdditionalProperties, ignoreFormats, allPropertiesRequired,
specPostprocessor = (spec) => spec,
validatorFactory = (spec, ignoreFormats) => _initValidatorFactory(spec, { ignoreFormats })
} = {}) {
let matchingFilePathsMapping = 0;
const filePathsMaps = glob.sync(
globMapExternalExamples,
Expand All @@ -169,6 +189,7 @@ async function validateExamplesByMap(filePathSchema, globMapExternalExamples,
openapiSpec = await _parseSpec(filePathSchema);
openapiSpec = Determiner.getImplementation(openapiSpec)
.prepare(openapiSpec, { noAdditionalProperties, allPropertiesRequired });
openapiSpec = _postprocess(openapiSpec, specPostprocessor);
} catch (err) {
responses.push(createValidationResponse({ errors: [ApplicationError.create(err)] }));
continue;
Expand All @@ -179,13 +200,11 @@ async function validateExamplesByMap(filePathSchema, globMapExternalExamples,
responses.push(
_validate(
statistics => {
return _handleExamplesByMapValidation(
openapiSpec, mapExternalExamples, statistics, {
cwdToMappingFile,
dirPathMapExternalExamples: path.dirname(filePathMapExternalExamples),
ignoreFormats
}
).map(
return _handleExamplesByMapValidation(openapiSpec, mapExternalExamples, statistics, {
cwdToMappingFile,
dirPathMapExternalExamples: path.dirname(filePathMapExternalExamples),
ignoreFormats, validatorFactory
}).map(
(/** @type ApplicationError */ error) => Object.assign(error, {
mapFilePath: path.normalize(filePathMapExternalExamples)
})
Expand Down Expand Up @@ -216,12 +235,16 @@ async function validateExamplesByMap(filePathSchema, globMapExternalExamples,
* "unsupported format" errors). If an Array with only one string is
* provided where the formats are separated with `\n`, the entries
* will be expanded to a new array containing all entries.
* @param {Function} [specPostprocessor] Provides implementation of spec postprocessor
* @param {Function} [validatorFactory] Validator factory provider
* @returns {ValidationResponse}
*/
async function validateExample(filePathSchema, pathSchema, filePathExample, {
noAdditionalProperties,
ignoreFormats,
allPropertiesRequired
allPropertiesRequired,
specPostprocessor = (spec) => spec,
validatorFactory = (spec, ignoreFormats) => _initValidatorFactory(spec, { ignoreFormats })
} = {}) {
let example = null,
schema = null,
Expand All @@ -231,13 +254,15 @@ async function validateExample(filePathSchema, pathSchema, filePathExample, {
openapiSpec = await _parseSpec(filePathSchema);
openapiSpec = Determiner.getImplementation(openapiSpec)
.prepare(openapiSpec, { noAdditionalProperties, allPropertiesRequired });
openapiSpec = _postprocess(openapiSpec, specPostprocessor);
schema = _extractSchema(_getSchmaPointer(pathSchema, openapiSpec), openapiSpec);
} catch (err) {
return createValidationResponse({ errors: [ApplicationError.create(err)] });
}
const createValidator = validatorFactory(openapiSpec, ignoreFormats);
return _validate(
statistics => _validateExample({
createValidator: _initValidatorFactory(openapiSpec, { ignoreFormats }),
createValidator,
schema,
example,
statistics,
Expand Down Expand Up @@ -312,11 +337,12 @@ function _validate(validationHandler) {
* "unsupported format" errors). If an Array with only one string is
* provided where the formats are separated with `\n`, the entries
* will be expanded to a new array containing all entries.
* @param {Function} [validatorFactory] Validator factory provider
* @returns {Array.<ApplicationError>}
* @private
*/
function _handleExamplesByMapValidation(openapiSpec, mapExternalExamples, statistics,
{ cwdToMappingFile = false, dirPathMapExternalExamples, ignoreFormats }
{ cwdToMappingFile = false, dirPathMapExternalExamples, ignoreFormats, validatorFactory }
) {
return flatMap(Object.entries(mapExternalExamples), ([pathSchema, filePathsExample]) => {
let schema = null;
Expand All @@ -326,6 +352,8 @@ function _handleExamplesByMapValidation(openapiSpec, mapExternalExamples, statis
// If the schema can't be found, don't even attempt to process the examples
return ApplicationError.create(err);
}
const createValidator = validatorFactory(openapiSpec, ignoreFormats);

return flatMap(
flatten([filePathsExample]),
filePathExample => {
Expand All @@ -352,7 +380,7 @@ function _handleExamplesByMapValidation(openapiSpec, mapExternalExamples, statis
return [ApplicationError.create(err)];
}
return flatMap(examples, example => _validateExample({
createValidator: _initValidatorFactory(openapiSpec, { ignoreFormats }),
createValidator,
schema,
example: example.content,
statistics,
Expand Down Expand Up @@ -435,23 +463,19 @@ function _getSchmaPointer(pathSchema, openapiSpec) {
/**
* Validates examples at the given paths in the OpenAPI-spec.
* @param {Object} impl Spec-dependant validator
* @param {Function} createValidator Validator factory
* @param {Array.<String>} pathsExamples JSON-paths to examples
* @param {Object} openapiSpec OpenAPI-spec
* @param {Array.<string>} [ignoreFormats] List of datatype formats that shall be ignored (to prevent
* "unsupported format" errors). If an Array with only one string is
* provided where the formats are separated with `\n`, the entries
* will be expanded to a new array containing all entries.
* @returns {ValidationResponse}
* @private
*/
function _validateExamplesPaths({ impl }, pathsExamples, openapiSpec, { ignoreFormats }) {
function _validateExamplesPaths({ impl, createValidator }, pathsExamples, openapiSpec) {
const statistics = _initStatistics(),
validationResult = {
valid: true,
statistics,
errors: []
},
createValidator = _initValidatorFactory(openapiSpec, { ignoreFormats });
};
let validationMap;
try {
// Create mapping between JSON-schemas and examples
Expand Down Expand Up @@ -590,18 +614,32 @@ function _validateExample({ createValidator, schema, example, statistics, filePa
* @private
*/
function _initValidatorFactory(specSchema, { ignoreFormats }) {
const formats = ignoreFormats && ignoreFormats.reduce((result, entry) => {
result[entry] = () => true;
return result;
}, {});
return getValidatorFactory(specSchema, {
schemaId: 'auto',
discriminator: true,
strict: false,
allErrors: true,
formats: ignoreFormats && ignoreFormats.reduce((result, entry) => {
result[entry] = () => true;
return result;
}, {})
formats
});
}

/***
* Run spec postprocess if defined.
* @returns modified OAS spec or the original one in case of errors
*/
function _postprocess(oasSpec, specPostprocessor) {
let result = oasSpec;
if (typeof specPostprocessor === 'function') {
result = specPostprocessor(oasSpec);
if (!result) { throw new Error('Postprocessor has to be specified'); }
}
return result;
}

/**
* Extracts the schema in the OpenAPI-spec at the given JSON-pointer.
* @param {string} schemaPointer JSON-pointer to the schema
Expand Down
9 changes: 5 additions & 4 deletions src/validator.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,13 @@ module.exports = {
* Get a factory-function to create a prepared validator-instance
* @param {Object} specSchema OpenAPI-spec of which potential local references will be extracted
* @param {Object} [options] Options for the validator
* @param {Function} [provider] Ajv provider
* @returns {function(): (ajv | ajv.Ajv)}
*/
function getValidatorFactory(specSchema, options) {
function getValidatorFactory(specSchema, options, { provider = (opt) => new Ajv(opt) } = {}) {
const preparedSpecSchema = _createReferenceSchema(specSchema);
return () => {
const validator = new Ajv(options);
const validator = provider(options);
addFormats(validator);

validator.addSchema(preparedSpecSchema);
Expand All @@ -49,7 +50,7 @@ function compileValidate(validator, responseSchema) {
try {
result = validator.compile(preparedResponseSchema);
} catch (e) {
result = () => {};
result = () => { };
result.errors = [e];
}
return result;
Expand Down Expand Up @@ -79,7 +80,7 @@ function _replaceRefsToPreparedSpecSchema(schema) {
json: schema,
callback(value, type, payload) {
if (!value.startsWith('#')) { return; }
payload.parent[payload.parentProperty] = `${ ID__SPEC_SCHEMA }${ value }`;
payload.parent[payload.parentProperty] = `${ID__SPEC_SCHEMA}${value}`;
}
});
}
Expand Down
95 changes: 95 additions & 0 deletions test/data/v3/custom-postprocessing/readOnly.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
{
"openapi": "3.0.0",
"info": {
"version": "1.0.0",
"title": "Swagger Petstore",
"license": {
"name": "MIT"
}
},
"tags": [
{
"name": "foo",
"description": "Everything about your foo"
}
],
"servers": [
{
"url": "http://petstore.swagger.io/v1"
}
],
"paths": {
"/foo": {
"post": {
"summary": "Add foo",
"operationId": "addFoo",
"description": "Create Foo",
"tags": [
"foo"
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Foo"
},
"examples": {
"invalid1": {
"value": {
"bar": "aaa",
"baz": 0
}
},
"invalid2": {
"value": {
"bar": "aaa",
"xxx": "xxx"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Foo"
},
"examples": {
"valid": {
"description": "here it should be correct",
"value": {
"bar": "aaa",
"baz": 0
}
}
}
}
}
}
}
}
}
},
"components": {
"schemas": {
"Foo": {
"type": "object",
"description": "Simple Foo",
"properties": {
"bar": {
"type": "string"
},
"baz": {
"type": "integer",
"readOnly": true
}
},
"additionalProperties": false
}
}
}
}
Loading