Socket
Socket
Sign inDemoInstall

swagger-endpoint-validator

Package Overview
Dependencies
213
Maintainers
5
Versions
15
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 1.1.5 to 2.0.0

lib/constants.js

172

index.js

@@ -1,158 +0,44 @@

const expressSwaggerGenerator = require('express-swagger-generator');
const validator = require('swagger-model-validator');
const swaggerJSDoc = require('swagger-jsdoc');
const swaggerUi = require('swagger-ui-express');
const debug = require('debug')('swagger-endpoint-validator');
const { SUPPORTED_FORMATS } = require('./lib/constants');
const customErrorHandler = require('./lib/customErrorHandler');
const normalizeOptions = require('./lib/normalizeOptions');
const openAPISpecification = require('./lib/openAPISpecification');
const validationEndpoint = require('./lib/validationEndpoint');
const validator = require('./lib/validator');
const errorFactory = require('./lib/errors');
const swaggerValidatorError = errorFactory('swagger_validator');
const SwaggerValidatorError = errorFactory('swagger_validator');
let singleton = null;
let paths = null;
/**
* Replaces :<string> by {string}.
* @param {string} pathStr - string to replace.
* @returns Replaced string.
* Initializes the Swagger endpoint validator.
* @param {Object} app - Express application object.
* @param {Object} options - Configuration object.
* @param {boolean} [options.validateRequests=true] - true to validate the requests.
* @param {boolean} [options.validateResponses=true] - true to validate the responses.
* @param {string} [options.validationEndpoint=null] - endpoint to do schemas validation agains the OpenAPI schema.
* @param {string} options.format - format of the OpenAPI specification documentation.
* @param {Object} [options.yaml={}] - Extra configuration when format = 'yaml'.
* @param {string} [options.yaml.file=null] - path of the yaml file containing the OpenAPI specification.
* @param {Object} [options.yaml_jsdoc={}] - Extra configuration when format = 'yaml_jsdoc'.
* @param {Object} [options.jsdoc={}] - Extra configuration when format = 'jsdoc'.
*/
const formatPath = pathStr => pathStr.replace(/{/g, ':').replace(/}/g, '');
const init = async (app, options) => {
debug('Initializing middleware for this express app...');
/**
* Creates the validator singleton instance.
* @param {object} app - Express application object.
* @param {object} swaggerOptions - Swagger options.
* @param {string} format - One of: 'jsdoc' or 'yaml'.
* @returns Validator singleton instance initialized.
*/
const createInstance = (app, swaggerOptions, format) => {
const instance = {
jsdoc: () => {
const { swaggerDefinition, ...rest } = swaggerOptions;
// Options has to be built this way to avoid "TypeError: Cannot add property swagger, object is not extensible"
const options = {
swaggerDefinition: {
...swaggerOptions.swaggerDefinition,
},
...rest,
};
const swaggerInstance = expressSwaggerGenerator(app)(options);
validator(swaggerInstance);
return swaggerInstance;
},
yaml: () => {
const { swaggerDefinition, ...rest } = swaggerOptions;
// Options for the swagger docs
const options = {
// Import swaggerDefinitions
swaggerDefinition,
...rest,
};
// Initialize swagger-jsdoc -> returns validated swagger spec in json format
const swaggerSpec = swaggerJSDoc(options);
const swaggerInstance = validator(swaggerSpec);
// If a URL is included in the configuration, serve the API doc through it
if (rest.url) {
app.use(rest.url, swaggerUi.serve, swaggerUi.setup(swaggerSpec));
}
return swaggerInstance.swagger;
},
};
return instance[format]();
};
/**
* Validates the payload for a specific endpoint.
* @param {object} payload - Payload to be validated.
* @param {object} request - Express request object.
* @param {boolean} isInput - true if it's an input payload for the endpoint, false otherwise.
* @throws {swaggerValidatorError} Swagger validator error.
*/
const validate = (payload, request, isInput) => {
if (!singleton) {
throw swaggerValidatorError('Swagger not initialized!');
if (!options.format || !Object.values(SUPPORTED_FORMATS).includes(options.format)) {
throw SwaggerValidatorError(`You need to specify the format used in the options. Supported values are ${Object.values(SUPPORTED_FORMATS).join(',')}`);
}
if (!payload || !request) {
throw swaggerValidatorError('payload and request are required to do API validation.');
}
const normalizedOptions = normalizeOptions(options);
const spec = await openAPISpecification.generate(app, normalizedOptions);
await validator.init(app, normalizedOptions, spec);
validationEndpoint.add(app, normalizedOptions);
customErrorHandler.add(app);
const { method } = request;
const { path: routePath } = request.route;
const path = paths[routePath];
if (path) {
const pathMethod = path[method.toLowerCase()];
if (pathMethod) {
let schemaToValidate = isInput
? pathMethod.parameters.find(item => item.name === 'body').schema
: pathMethod.responses['200'].schema;
schemaToValidate = schemaToValidate[0] ? schemaToValidate[0] : schemaToValidate;
const validation = singleton.validateModel(schemaToValidate, payload, false, true);
if (!validation.valid) {
const error = swaggerValidatorError(validation.GetErrorMessages());
error.extra = validation.GetFormattedErrors();
throw error;
}
return { valid: validation.valid };
}
}
throw swaggerValidatorError('payload data not valid for validation.');
debug('Middleware initialized!');
};
/**
* Initializes the validator.
* @param {*} app - Express application object.
* @param {*} swaggerOptions - Swagger options.
* @param {*} format - One of: 'jsdoc' or 'yaml'.
*/
const init = (app, swaggerOptions, format = 'jsdoc') => {
if (!['jsdoc', 'yaml'].includes(format)) {
throw swaggerValidatorError(`${format} format not supported`);
}
if (!singleton) {
singleton = createInstance(app, swaggerOptions, format);
paths = Object.keys(singleton.paths).reduce((acum, item) => (
{ ...acum, [formatPath(item)]: singleton.paths[item] }
), {});
}
};
/**
* Resets the validator.
*/
const reset = () => {
singleton = null;
};
/**
* Validates a payload used as input body to a REST endpoint.
* @param {object} payload - Payload to be validated.
* @param {object} request - Express request object details.
*/
const validateAPIInput = (payload, request) => validate(payload, request, true);
/**
* Validates a payload returned by a REST endpoint.
* @param {object} payload - Payload to be validated.
* @param {object} request - Express request object details.
*/
const validateAPIOutput = (payload, request) => validate(payload, request, false);
module.exports = {
init,
reset,
validateAPIInput,
validateAPIOutput,
};
{
"name": "swagger-endpoint-validator",
"version": "1.1.5",
"version": "2.0.0",
"description": "A validator of API endpoints to check that input and output match with the swagger specification for the API",
"main": "index.js",
"scripts": {
"test": "jest",
"test": "jest --verbose",
"lint": "eslint ."
},
"husky": {
"hooks": {
"pre-commit": "npm run lint && npm run test",
"pre-push": "npm run test"
}
},
"repository": {

@@ -26,16 +32,23 @@ "type": "git",

"dependencies": {
"debug": "^4.1.1",
"express-openapi-validator": "^3.10.0",
"express-swagger-generator": "^1.1.17",
"js-yaml": "^3.13.1",
"openapi-enforcer": "^1.10.1",
"swagger-jsdoc": "^4.0.0",
"swagger-model-validator": "^3.0.18",
"swagger-ui-express": "^4.1.4"
"swagger-ui-express": "^4.1.4",
"swagger2openapi": "^6.0.2"
},
"devDependencies": {
"body-parser": "^1.19.0",
"eslint": "^6.8.0",
"eslint-config-airbnb-base": "^14.1.0",
"eslint-plugin-import": "^2.20.1",
"eslint-plugin-import": "^2.20.2",
"eslint-plugin-jest": "^23.8.2",
"express": "^4.17.1",
"husky": "^4.2.3",
"jest": "^25.1.0",
"jest": "^25.2.4",
"jest-junit": "^10.0.0",
"mock-express": "^1.3.1"
"supertest": "^4.0.2"
},

@@ -42,0 +55,0 @@ "jest-junit": {

@@ -1,93 +0,107 @@

const MockExpress = require('mock-express');
const express = require('express');
const bodyParser = require('body-parser');
const validator = require('../..');
const app = MockExpress(); // notice there's no "new"
const pets = [
{ id: 1, name: 'pet 1', tag: 'dog' },
{ id: 2, name: 'pet 2', tag: 'cat' },
{ id: 3, name: 'pet 3', tag: 'bird' },
{ id: 4, name: 'pet 4', tag: 'dog' },
{ id: 5, name: 'pet 5', tag: 'cat' },
{ id: 6, name: 'pet 6', tag: 'bird' },
];
const validatorOptions = {
swaggerDefinition: {
info: {
title: 'Test API',
description: 'Test Express API with autogenerated swagger doc',
version: '1.0.0',
},
basePath: '/',
},
basedir: process.cwd(),
files: ['./test/jsdoc/**/**.js'],
};
const wrongPets = [
{ id: 1, tag: 'dog' },
{ id: 2, tag: 'cat' },
{ id: 3, tag: 'bird' },
{ id: 4, tag: 'dog' },
{ id: 5, tag: 'cat' },
{ id: 6, tag: 'bird' },
];
validator.init(app, validatorOptions);
const runServer = async () => {
const app = express();
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
/**
* @route GET /test-invalid-output
* @summary Test invalid output
* @group test - Everything about tests
* @param {Input.model} body.body.required
* @returns {Output.model} 200 - Successful operation
* @returns {Error.model} 404 - Error message
*/
app.get('/test-invalid-input', (req, res) => {
try {
const result = validator.validateAPIInput({}, req);
res.status(200).json(result);
} catch (error) {
res.status(404).json({ error });
}
});
await validator.init(app, {
validationEndpoint: '/test',
validateRequests: true,
validateResponses: true,
format: 'jsdoc',
jsdoc: {
swaggerDefinition: {
info: {
version: '1.0.0',
title: 'Swagger Petstore',
description: 'A sample API that uses a petstore as an example to demonstrate features in the OpenAPI 3.0 specification',
termsOfService: 'http://swagger.io/terms/',
contact: {
name: 'Swagger API Team',
email: 'apiteam@swagger.io',
url: 'http://swagger.io',
},
license: {
name: 'Apache 2.0',
url: 'https://www.apache.org/licenses/LICENSE-2.0.html',
},
},
},
basedir: process.cwd(),
files: [
'./test/jsdoc/fake-server.js',
'./test/jsdoc/components.js',
],
},
});
/**
* @route GET /test-invalid-output
* @summary Test invalid output
* @group test - Everything about tests
* @param {Input.model} body.body.required
* @returns {Output.model} 200 - Successful operation
* @returns {Error.model} 404 - Error message
*/
app.get('/test-invalid-output', (req, res) => {
try {
const result = validator.validateAPIOutput({}, req);
res.status(200).json(result);
} catch (error) {
res.status(404).json({ error });
}
});
/**
* @route GET /pets
* @summary Returns all pets from the system that the user has access to
* @param {[string]} tags.query - tags to filter by
* @param {integer} limit.query - maximum number of results to return
* @param {boolean} wrong.query - flag to force the server to return invalid response
* @produces application/json
* @returns {Array.<Pet>} 200 - pet response
* @returns {Error.model} default - unexpected error
*/
app.get(
'/pets',
(req, res) => {
const { wrong } = req.query;
if (wrong) {
res.status(200).json(wrongPets);
} else {
res.status(200).json(pets);
}
},
);
/**
* @route GET /test-valid-input
* @summary Test valid input
* @group test - Everything about tests
* @param {Input.model} body.body.required
* @returns {Output.model} 200 - Successful operation
* @returns {Error.model} 404 - Error message
*/
app.get('/test-valid-input', (req, res) => {
const validInputModel = { name: 'Name is required' };
/**
* @route POST /pets
* @summary Creates a new pet in the store. Duplicates are allowed
* @param {NewPet.model} body.body.required - Pet to add to the store
* @produces application/json
* @returns {Pet.model} 200 - pet response
* @returns {Error.model} default - unexpected error
*/
app.post(
'/pets',
(req, res) => {
const { wrong } = req.query;
if (wrong) {
res.status(200).json({ ...req.body });
} else {
res.status(200).json({ id: 7, ...req.body });
}
},
);
try {
const result = validator.validateAPIInput(validInputModel, req);
res.status(200).json(result);
} catch (error) {
res.status(404).json({ error });
}
});
return app;
};
/**
* @route GET /test-valid-output
* @summary Test valid endpoint
* @group test - Everything about tests
* @param {Input.model} body.body.required
* @returns {Output.model} 200 - Successful operation
* @returns {Error.model} 404 - Error message
*/
app.get('/test-valid-output', (req, res) => {
const validOutputModel = { name: 'Name is required', result: 'Valid result' };
try {
const result = validator.validateAPIOutput(validOutputModel, req);
res.status(200).json(result);
} catch (error) {
res.status(404).json({ error });
}
});
module.exports = app;
module.exports = {
runServer,
};

@@ -1,152 +0,68 @@

const MockExpress = require('mock-express');
const express = require('express');
const bodyParser = require('body-parser');
const validator = require('../..');
const app = MockExpress(); // notice there's no "new"
const pets = [
{ id: 1, name: 'pet 1', tag: 'dog' },
{ id: 2, name: 'pet 2', tag: 'cat' },
{ id: 3, name: 'pet 3', tag: 'bird' },
{ id: 4, name: 'pet 4', tag: 'dog' },
{ id: 5, name: 'pet 5', tag: 'cat' },
{ id: 6, name: 'pet 6', tag: 'bird' },
];
const validatorOptions = {
swaggerDefinition: {
info: {
// API informations (required)
title: 'Hello World', // Title (required)
version: '1.0.0', // Version (required)
description: 'A sample API', // Description (optional)
},
basePath: process.cwd(),
},
apis: ['./test/yaml/**.js'],
};
const wrongPets = [
{ id: 1, tag: 'dog' },
{ id: 2, tag: 'cat' },
{ id: 3, tag: 'bird' },
{ id: 4, tag: 'dog' },
{ id: 5, tag: 'cat' },
{ id: 6, tag: 'bird' },
];
validator.init(app, validatorOptions, 'yaml');
const runServer = async () => {
const app = express();
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
/**
* @swagger
* /test-invalid-input:
* post:
* description: Test POST /test-invalid-input
* tags: [Test]
* produces:
* - application/json
* parameters:
* - name: body
* description: Input payload
* required: true
* type: object
* schema:
* - $ref: '#/definitions/Input'
* responses:
* 200:
* description: successful operation
*/
app.post('/test-invalid-input', (req, res) => {
try {
const result = validator.validateAPIInput({}, req);
res.status(200).json(result);
} catch (error) {
res.status(404).json({ error });
}
});
await validator.init(app, {
validationEndpoint: '/test',
validateRequests: true,
validateResponses: true,
format: 'yaml',
yaml: {
file: './test/yaml/api.yaml',
},
});
/**
* @swagger
* /test-invalid-output:
* get:
* description: Test GET /test-invalid-output
* tags: [Test]
* produces:
* - application/json
* responses:
* 200:
* description: successful operation
* schema:
* $ref: '#/definitions/Output'
*/
app.get('/test-invalid-output', (req, res) => {
try {
const result = validator.validateAPIOutput({}, req);
res.status(200).json(result);
} catch (error) {
res.status(404).json({ error });
}
});
app.get(
'/pets',
(req, res) => {
const { wrong } = req.query;
if (wrong) {
res.status(200).json(wrongPets);
} else {
res.status(200).json(pets);
}
},
);
/**
* @swagger
* /test-invalid-output-2:
* get:
* description: Test GET /test-invalid-output-2
* tags: [Test]
* produces:
* - application/json
* responses:
* 200:
* description: successful operation
* schema:
* $ref: '#/definitions/Output'
*/
app.get('/test-invalid-output-2', (req, res) => {
const invalidOutputModel = { name: 'Name is required', result: 'Valid result', extra: 'no extra fields' };
app.post(
'/pets',
(req, res) => {
const { wrong } = req.query;
if (wrong) {
res.status(200).json({ ...req.body });
} else {
res.status(200).json({ id: 7, ...req.body });
}
},
);
try {
const result = validator.validateAPIOutput(invalidOutputModel, req);
res.status(200).json(result);
} catch (error) {
res.status(404).json({ error });
}
});
return app;
};
/**
* @swagger
* /test-valid-input:
* post:
* description: Test POST /test-valid-input
* tags: [Test]
* produces:
* - application/json
* parameters:
* - name: body
* description: Input payload
* required: true
* type: object
* schema:
* - $ref: '#/definitions/Input'
* responses:
* 200:
* description: successful operation
*/
app.post('/test-valid-input', (req, res) => {
const validInputModel = { name: 'Name is required' };
try {
const result = validator.validateAPIInput(validInputModel, req);
res.status(200).json(result);
} catch (error) {
res.status(404).json({ error });
}
});
/**
* @swagger
* /test-valid-output:
* get:
* description: Test GET /test-valid-output
* tags: [Test]
* produces:
* - application/json
* responses:
* 200:
* description: successful operation
* schema:
* $ref: '#/definitions/Output'
*/
app.get('/test-valid-output', (req, res) => {
const validOutputModel = { name: 'Name is required', result: 'Valid result' };
try {
const result = validator.validateAPIOutput(validOutputModel, req);
res.status(200).json(result);
} catch (error) {
res.status(404).json({ error });
}
});
module.exports = app;
module.exports = {
runServer,
};

@@ -1,44 +0,47 @@

const MockExpress = require('mock-express');
const validator = require('../..'); // diferent instance
describe('init method', () => {
it('should return an exeception when we don\'t send options or the app instance', () => {
expect(() => {
validator.init();
}).toThrow();
describe.skip('unit tests', () => {
describe('sample', () => {
it('should test that true === true', () => {
expect(true).toBe(true);
});
});
});
// describe('init method', () => {
// it('should return an exeception when we don\'t send options or the app instance', () => {
// expect(() => {
// validator.init();
// }).toThrow();
// });
it('should return an exeception when we don\'t send params to validateAPIInput', () => {
expect(() => {
validator.validateAPIInput();
}).toThrow();
});
// it('should return an exeception when we don\'t send params to validateAPIInput', () => {
// expect(() => {
// validator.validateAPIInput();
// }).toThrow();
// });
it('should return an exeception when we don\'t send params to validateAPIOutput', () => {
expect(() => {
validator.validateAPIOutput();
}).toThrow();
});
// it('should return an exeception when we don\'t send params to validateAPIOutput', () => {
// expect(() => {
// validator.validateAPIOutput();
// }).toThrow();
// });
it('should return an exception when initializing validator with unrecognized format', () => {
expect(() => {
const app = MockExpress();
// it('should return an exception when initializing validator with unrecognized format', () => {
// expect(() => {
// const app = MockExpress();
const validatorOptions = {
swaggerDefinition: {
info: {
// API informations (required)
title: 'Hello World', // Title (required)
version: '1.0.0', // Version (required)
description: 'A sample API', // Description (optional)
},
basePath: process.cwd(),
},
apis: ['./test/yaml/**.js'],
};
// const validatorOptions = {
// swaggerDefinition: {
// info: {
// // API informations (required)
// title: 'Hello World', // Title (required)
// version: '1.0.0', // Version (required)
// description: 'A sample API', // Description (optional)
// },
// basePath: process.cwd(),
// },
// apis: ['./test/yaml/**.js'],
// };
validator.init(app, validatorOptions, 'wrong-format');
}).toThrow();
});
});
// validator.init(app, validatorOptions, 'wrong-format');
// }).toThrow();
// });
// });
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc