Socket
Socket
Sign inDemoInstall

api-schema-builder

Package Overview
Dependencies
Maintainers
2
Versions
35
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

api-schema-builder - npm Package Compare versions

Comparing version 1.0.3 to 1.0.4

22

package.json
{
"name": "api-schema-builder",
"version": "1.0.3",
"version": "1.0.4",
"description": "build schema with validators for each endpoint",

@@ -22,7 +22,7 @@ "main": "src/index.js",

},
"license": "MIT",
"license": "Apache-2.0",
"scripts": {
"test": "node_modules/mocha/bin/_mocha ./test/*/*-test.js --recursive",
"test:coverage": "nyc node_modules/mocha/bin/_mocha --recursive ./test/*/*-test.js ",
"coveralls": "cat ./coverage/lcov.info | ./node_modules/.bin/coveralls",
"test": "mocha --recursive",
"test:coverage": "nyc npm test ",
"coveralls": "cat ./coverage/lcov.info |./node_modules/.bin/coveralls",
"lint": "./node_modules/.bin/eslint src"

@@ -46,3 +46,3 @@ },

"homepage": "https://github.com/Zooz/api-schema-builder.git",
"author": "Idan Tovi",
"author": "Gal Cohen",
"dependencies": {

@@ -56,4 +56,4 @@ "ajv": "^6.6.2",

"chai": "^4.2.0",
"chai-sinon": "^2.8.1",
"coveralls": "^3.0.2",
"chai-as-promised": "^7.1.1",
"coveralls": "^3.0.3",
"eslint": "^5.15.1",

@@ -66,8 +66,4 @@ "eslint-config-standard": "^12.0.0",

"eslint-plugin-standard": "^4.0.0",
"form-data": "^2.3.3",
"lodash": "^4.17.11",
"mocha": "^6.0.2",
"nyc": "^13.3.0",
"request": "^2.88.0",
"sinon": "^4.5.0"
"nyc": "^13.3.0"
},

@@ -74,0 +70,0 @@ "publishConfig": {

# api-schema-builder
[![NPM Version](https://img.shields.io/npm/v/api-schema-builder.svg?style=flat)](https://npmjs.org/package/express-ajv-swagger-validation)
[![Build Status](https://travis-ci.org/Zooz/api-schema-builder.svg?branch=master)](https://travis-ci.org/Zooz/api-schema-builder)
[![Coverage Status](https://coveralls.io/repos/github/Zooz/api-schema-builder/badge.svg?branch=master)](https://coveralls.io/github/Zooz/api-schema-builder?branch=master)
[![Known Vulnerabilities](https://snyk.io/test/npm/api-schema-builder/badge.svg)](https://snyk.io/test/npm/api-schema-builder)
[![Apache 2.0 License](https://img.shields.io/badge/license-Apache_2.0-green.svg?style=flat)](LICENSE)

@@ -43,3 +46,3 @@ This package is used to build schema for input validation base on openapi doc [Swagger (Open API)](https://swagger.io/specification/) definition and [ajv](https://www.npmjs.com/package/ajv)

buildSchema the middleware with the swagger definition.
Build schema that contains ajv validators for each endpoint, it base on swagger definition.

@@ -51,8 +54,8 @@ The function return Promise.

* `PathToSwaggerFile` – Path to the swagger definition
* `options` – Additional options for the middleware.
* `options` – Additional options for build the schema.
#### Response
Array that contains:
* `path_name`: the paths it written in the doc, for example `/pet`.
* `method`: the relevant method it written in the doc, for example `get`.
* `path_name`: the paths it written in the api doc, for example `/pet`.
* `method`: the relevant method it written in the api doc, for example `get`.
* `parameters`:

@@ -64,2 +67,6 @@ * `validate`: ajv validator that check: paths, files, queries and headers.

* `errors`: in case of fail validation it return array of errors, otherwise return null
* `responses`: contain array of statusCodes
* `statusCode`:
* `validate`: ajv validator that check body and headers.
* `errors`: in case of fail validation it return array of errors, otherwise return null

@@ -78,2 +85,4 @@

- `expectFormFieldsInBody` - Boolean that indicates whether form fields of non-file type that are specified in the schema should be validated against request body (e. g. Multer is copying text form fields to body)
- `buildRequests` - Boolean that indicates whether if create validators for requests, default is true.
- `buildResponses` - Boolean that indicates whether if create validators for responses, default is false.

@@ -89,8 +98,10 @@ ```js

## Usage Example
### Validate request
```js
apiSchemaBuilder.getSchema('test/unit-tests/input-validation/pet-store-swagger.yaml')
apiSchemaBuilder.buildSchema('test/unit-tests/input-validation/pet-store-swagger.yaml')
.then(function (schema) {
let schemaEndpoint = schema['/pet']['post'];
//validate parameters
//validate request's parameters
let isParametersMatch = schemaEndpoint.parameters.validate({ query: {},

@@ -101,3 +112,3 @@ headers: { 'public-key': '1.0'},path: {},files: undefined });

//validate body
//validate request's body
let isBodysMatch =schemaEndpoint.body.validate({'bark': 111});

@@ -113,9 +124,34 @@ expect(schemaEndpoint.body.errors).to.be.eql([{

])
expect(isBodysMatch).to.be.false;
});
expect(isBodysMatch).to.be.false;
```
### Validate response
```js
apiSchemaBuilder.buildSchema('test/unit-tests/input-validation/pet-store-swagger.yaml')
.then(function (schema) {
let schemaEndpoint = schema['/pet']['post'].responses['201'];
//validate response's body and headers
let isValid = schemaEndpoint.validate({
body :{ id:11, 'name': 111},
headers:{'x-next': '321'}
})
expect(schemaEndpoint.errors).to.be.eql([
{
'dataPath': '.body.name',
'keyword': 'type',
'message': 'should be string',
'params': {
'type': 'string'
},
'schemaPath': '#/body/properties/name/type'
}])
expect(isValid).to.be.false;
});
```
## Important Notes
- Objects - it is important to set any objects with the property `type: object` inside your swagger file, although it isn't a must in the Swagger (OpenAPI) spec in order to validate it accurately with [ajv](https://www.npmjs.com/package/ajv) it must be marked as `object`
- Response validator - it support now only open api 2.

@@ -122,0 +158,0 @@ ## Open api 3 - known issues

@@ -12,8 +12,8 @@ 'use strict';

/**
* Initialize the input validation middleware`
* @param {string} swaggerPath - the path for the swagger file
* @param {Object} options - options.formats to add formats to ajv, options.beautifyErrors, options.firstError, options.expectFormFieldsInBody, options.fileNameField (default is 'fieldname' - multer package), options.ajvConfigBody and options.ajvConfigParams for config object that will be passed for creation of Ajv instance used for validation of body and parameters appropriately
*/
const DEFAULT_SETTINGS = {
buildRequests: true,
buildResponses: true
};
function buildSchema(swaggerPath, options) {
let updatedOptions = Object.assign({}, DEFAULT_SETTINGS, options);
return Promise.all([

@@ -23,63 +23,31 @@ SwaggerParser.dereference(swaggerPath),

]).then(function ([dereferenced, referenced]) {
return buildValidations(referenced, dereferenced, options);
return buildValidations(referenced, dereferenced, updatedOptions);
});
}
function buildValidations(referenced, dereferenced, options = {}) {
const { makeOptionalAttributesNullable = false } = options;
function buildValidations(referenced, dereferenced, options) {
const { buildRequests, buildResponses } = options;
const schemas = {};
Object.keys(dereferenced.paths).forEach(function (currentPath) {
let pathParameters = dereferenced.paths[currentPath].parameters || [];
let parsedPath = dereferenced.basePath && dereferenced.basePath !== '/' ? dereferenced.basePath.concat(currentPath.replace(/{/g, ':').replace(/}/g, '')) : currentPath.replace(/{/g, ':').replace(/}/g, '');
let parsedPath = dereferenced.basePath && dereferenced.basePath !== '/'
? dereferenced.basePath.concat(currentPath.replace(/{/g, ':').replace(/}/g, ''))
: currentPath.replace(/{/g, ':').replace(/}/g, '');
schemas[parsedPath] = {};
Object.keys(dereferenced.paths[currentPath]).filter(function (parameter) { return parameter !== 'parameters' })
Object.keys(dereferenced.paths[currentPath])
.filter(function (parameter) { return parameter !== 'parameters' })
.forEach(function (currentMethod) {
schemas[parsedPath][currentMethod.toLowerCase()] = {};
const isOpenApi3 = dereferenced.openapi === '3.0.0';
const parameters = dereferenced.paths[currentPath][currentMethod].parameters || [];
if (isOpenApi3) {
schemas[parsedPath][currentMethod].body = oas3.buildBodyValidation(dereferenced, referenced, currentPath, currentMethod, options);
} else {
let bodySchema = options.expectFormFieldsInBody
? parameters.filter(function (parameter) { return (parameter.in === 'body' || (parameter.in === 'formData' && parameter.type !== 'file')) })
: parameters.filter(function (parameter) { return parameter.in === 'body' });
if (makeOptionalAttributesNullable) {
schemaPreprocessor.makeOptionalAttributesNullable(bodySchema);
}
if (bodySchema.length > 0) {
const validatedBodySchema = oas2.getValidatedBodySchema(bodySchema);
let bodySchemaReference = referenced.paths[currentPath][currentMethod].parameters.filter(function (parameter) { return parameter.in === 'body' })[0] || {};
let schemaReference = bodySchemaReference.schema;
schemas[parsedPath][currentMethod].body = oas2.buildBodyValidation(validatedBodySchema, dereferenced.definitions, referenced, currentPath, currentMethod, parsedPath, options, schemaReference);
}
let parsedMethod = currentMethod.toLowerCase();
let requestValidator;
if (buildRequests) {
requestValidator = buildRequestValidator(referenced, dereferenced, currentPath,
parsedPath, currentMethod, options);
}
// response validation
schemas[parsedPath][currentMethod].responses = {};
let responses = dereferenced.paths[currentPath][currentMethod].responses || [];
Object.keys(responses).forEach(statusCode => {
if (statusCode !== 'default') {
let responseDereferenceSchema = responses[statusCode].schema;
let responseDereferenceHeaders = responses[statusCode].headers || [];
let contentTypes = dereferenced.paths[currentPath][currentMethod].produces || dereferenced.paths[currentPath].produces || dereferenced.produces;
let headersValidator = (responseDereferenceHeaders || contentTypes) ? buildHeadersValidation(responseDereferenceHeaders, contentTypes, options) : undefined;
let responseValidator;
if (buildResponses){
responseValidator = buildResponseValidator(referenced, dereferenced, currentPath, parsedPath, currentMethod, options);
}
let responseSchema = referenced.paths[currentPath][currentMethod].responses[statusCode].schema;
let bodyValidator = responseSchema ? oas2.buildBodyValidation(responseDereferenceSchema, dereferenced.definitions, referenced, currentPath, currentMethod, parsedPath, options, responseSchema) : undefined;
if (headersValidator || bodyValidator) {
schemas[parsedPath][currentMethod].responses[statusCode] = new Validators.ResponseValidator({ body: bodyValidator, headers: headersValidator });
}
}
});
let localParameters = parameters.filter(function (parameter) {
return parameter.in !== 'body';
}).concat(pathParameters);
if (localParameters.length > 0 || options.contentTypeValidation) {
schemas[parsedPath][currentMethod].parameters = buildParametersValidation(localParameters,
dereferenced.paths[currentPath][currentMethod].consumes || dereferenced.paths[currentPath].consumes || dereferenced.consumes, options);
}
schemas[parsedPath][parsedMethod] = Object.assign({}, requestValidator, { responses: responseValidator });
});

@@ -90,2 +58,66 @@ });

function buildRequestValidator(referenced, dereferenced, currentPath, parsedPath, currentMethod, options){
let requestSchema = {};
let pathParameters = dereferenced.paths[currentPath].parameters || [];
const isOpenApi3 = dereferenced.openapi === '3.0.0';
const parameters = dereferenced.paths[currentPath][currentMethod].parameters || [];
if (isOpenApi3) {
requestSchema.body = oas3.buildRequestBodyValidation(dereferenced, referenced, currentPath, currentMethod, options);
} else {
let bodySchema = options.expectFormFieldsInBody
? parameters.filter(function (parameter) {
return (parameter.in === 'body' ||
(parameter.in === 'formData' && parameter.type !== 'file'));
})
: parameters.filter(function (parameter) { return parameter.in === 'body' });
options.makeOptionalAttributesNullable && schemaPreprocessor.makeOptionalAttributesNullable(bodySchema);
if (bodySchema.length > 0) {
const validatedBodySchema = oas2.getValidatedBodySchema(bodySchema);
requestSchema.body = oas2.buildRequestBodyValidation(validatedBodySchema, dereferenced.definitions, referenced,
currentPath, currentMethod, options);
}
}
let localParameters = parameters.filter(function (parameter) {
return parameter.in !== 'body';
}).concat(pathParameters);
if (localParameters.length > 0 || options.contentTypeValidation) {
requestSchema.parameters = buildParametersValidation(localParameters,
dereferenced.paths[currentPath][currentMethod].consumes || dereferenced.paths[currentPath].consumes || dereferenced.consumes, options);
}
return requestSchema;
}
function buildResponseValidator(referenced, dereferenced, currentPath, parsedPath, currentMethod, options){
// support now only oas2
if (dereferenced.openapi === '3.0.0'){ return }
const responsesSchema = {};
const responses = dereferenced.paths[currentPath][currentMethod].responses;
if (responses) {
Object.keys(responses).forEach(statusCode => {
let responseDereferenceSchema = responses[statusCode].schema;
let responseDereferenceHeaders = responses[statusCode].headers;
let contentTypes = dereferenced.paths[currentPath][currentMethod].produces || dereferenced.paths[currentPath].produces || dereferenced.produces;
let headersValidator = (responseDereferenceHeaders || contentTypes) ? buildHeadersValidation(responseDereferenceHeaders, contentTypes, options) : undefined;
let bodyValidator = responseDereferenceSchema ? oas2.buildResponseBodyValidation(responseDereferenceSchema,
dereferenced.definitions, referenced, currentPath, currentMethod, options, statusCode) : undefined;
if (headersValidator || bodyValidator) {
responsesSchema[statusCode] = new Validators.ResponseValidator({
body: bodyValidator,
headers: headersValidator
});
}
});
}
return responsesSchema;
}
function createContentTypeHeaders(validate, contentTypes) {

@@ -170,3 +202,3 @@ if (!validate || !contentTypes) return;

}
}, this);
});

@@ -178,3 +210,2 @@ ajvParametersSchema.properties.headers.content = createContentTypeHeaders(options.contentTypeValidation, contentTypes);

// split to diff parsers if needed
function buildHeadersValidation(headers, contentTypes, options) {

@@ -194,15 +225,14 @@ const defaultAjvOptions = {

properties: {},
required: [],
additionalProperties: true
};
Object.keys(headers).forEach(key => {
let headerObj = Object.assign({}, headers[key]);
const headerName = key.toLowerCase();
const headerRequired = headerObj.required;
if (headerRequired) ajvHeadersSchema.required.push(key);
delete headerObj.name;
delete headerObj.required;
ajvHeadersSchema.properties[headerName] = headerObj;
}, this);
if (headers) {
Object.keys(headers).forEach(key => {
let headerObj = Object.assign({}, headers[key]);
const headerName = key.toLowerCase();
delete headerObj.name;
delete headerObj.required;
ajvHeadersSchema.properties[headerName] = headerObj;
});
}

@@ -209,0 +239,0 @@ ajvHeadersSchema.content = createContentTypeHeaders(options.contentTypeValidation, contentTypes);

@@ -8,3 +8,4 @@

getValidatedBodySchema,
buildBodyValidation
buildResponseBodyValidation,
buildRequestBodyValidation
};

@@ -37,13 +38,19 @@

function buildBodyValidation(schema, swaggerDefinitions, originalSwagger, currentPath, currentMethod, parsedPath, options = {}, schemaReference) {
function buildAjvValidator(ajvConfigBody, formats, keywords){
const defaultAjvOptions = {
allErrors: true
};
const ajvOptions = Object.assign({}, defaultAjvOptions, options.ajvConfigBody);
const ajvOptions = Object.assign({}, defaultAjvOptions, ajvConfigBody);
let ajv = new Ajv(ajvOptions);
ajvUtils.addCustomKeyword(ajv, options.formats, options.keywords);
ajvUtils.addCustomKeyword(ajv, formats, keywords);
return ajv;
}
function buildResponseBodyValidation(schema, swaggerDefinitions, originalSwagger, currentPath, currentMethod, options, statusCode) {
let ajv = buildAjvValidator(options.ajvConfigBody, options.formats, options.keywords);
if (schema.discriminator) {
return buildInheritance(schema.discriminator, swaggerDefinitions, originalSwagger, currentPath, currentMethod, parsedPath, ajv, schemaReference);
let schemaReference = originalSwagger.paths[currentPath][currentMethod].responses[statusCode].schema;
return buildInheritance(schema.discriminator, swaggerDefinitions, originalSwagger, currentPath, currentMethod, ajv, schemaReference);
} else {

@@ -53,3 +60,14 @@ return new Validators.SimpleValidator(ajv.compile(schema));

}
function buildInheritance(discriminator, dereferencedDefinitions, swagger, currentPath, currentMethod, parsedPath, ajv, schemaReference = {}) {
function buildRequestBodyValidation(schema, swaggerDefinitions, originalSwagger, currentPath, currentMethod, options) {
let ajv = buildAjvValidator(options.ajvConfigBody, options.formats, options.keywords);
if (schema.discriminator) {
let schemaReference = originalSwagger.paths[currentPath][currentMethod].parameters.filter(function (parameter) { return parameter.in === 'body' })[0].schema;
return buildInheritance(schema.discriminator, swaggerDefinitions, originalSwagger, currentPath, currentMethod, ajv, schemaReference);
} else {
return new Validators.SimpleValidator(ajv.compile(schema));
}
}
function buildInheritance(discriminator, dereferencedDefinitions, swagger, currentPath, currentMethod, ajv, schemaReference = {}) {
var inheritsObject = {

@@ -56,0 +74,0 @@ inheritance: []

@@ -9,6 +9,6 @@

module.exports = {
buildBodyValidation
buildRequestBodyValidation
};
function buildBodyValidation(dereferenced, originalSwagger, currentPath, currentMethod, options = {}) {
function buildRequestBodyValidation(dereferenced, originalSwagger, currentPath, currentMethod, { ajvConfigBody, formats, keywords }) {
if (!dereferenced.paths[currentPath][currentMethod].requestBody) {

@@ -21,6 +21,6 @@ return;

};
const ajvOptions = Object.assign({}, defaultAjvOptions, options.ajvConfigBody);
const ajvOptions = Object.assign({}, defaultAjvOptions, ajvConfigBody);
let ajv = new Ajv(ajvOptions);
ajvUtils.addCustomKeyword(ajv, options.formats, options.keywords);
ajvUtils.addCustomKeyword(ajv, formats, keywords);

@@ -27,0 +27,0 @@ if (bodySchemaV3.discriminator) {

@@ -15,7 +15,6 @@

if (bodySchema) {
// let bodyToValidate = data.body || {}
bodyValidationResult = bodySchema.validate(data.body);
bodyValidationErrors = bodySchema.errors ? addErrorPrefix(bodySchema.errors, 'body') : [];
}
if (headersSchema && data.headers) {
if (headersSchema) {
headersValidationResult = headersSchema.validate(data.headers);

@@ -27,5 +26,4 @@ headersValidationErrors = headersSchema.errors ? addErrorPrefix(headersSchema.errors, 'headers') : [];

this.errors = errors.length === 0 ? null : errors;
let result = bodyValidationResult && headersValidationResult;
return result;
return bodyValidationResult && headersValidationResult;
}

@@ -32,0 +30,0 @@

SocketSocket SOC 2 Logo

Product

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

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc