api-schema-builder
Advanced tools
Comparing version 0.0.2 to 1.0.0
{ | ||
"name": "api-schema-builder", | ||
"version": "0.0.2", | ||
"version": "1.0.0", | ||
"description": "build schema with validators for each endpoint", | ||
@@ -31,3 +31,3 @@ "main": "src/index.js", | ||
"type": "git", | ||
"url": "git://github.com:galcohen92/api-schema-builder.git" | ||
"url": "https://github.com/Zooz/api-schema-builder.git" | ||
}, | ||
@@ -43,5 +43,5 @@ "keywords": [ | ||
"bugs": { | ||
"url": "https:////github.com:galcohen92/api-schema-builder.git" | ||
"url": "https://github.com/Zooz/api-schema-builder/issues" | ||
}, | ||
"homepage": "https:////github.com:galcohen92/api-schema-builder.git", | ||
"homepage": "https://github.com/Zooz/api-schema-builder.git", | ||
"author": "Idan Tovi", | ||
@@ -48,0 +48,0 @@ "dependencies": { |
125
README.md
# api-schema-builder | ||
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) | ||
<!-- START doctoc generated TOC please keep comment here to allow auto update --> | ||
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --> | ||
**Table of Contents** <!-- *generated with [DocToc](https://github.com/thlorenz/doctoc)* --> | ||
- [Install](#install) | ||
- [API](#api) | ||
- [How to use](#how-to-use) | ||
- [api-schema-builder.buildSchema(PathToSwaggerFile, options)](#express-ajv-swagger-validationgetSchemapathtoswaggerfile-options) | ||
- [Arguments](#arguments) | ||
- [Options](#options) | ||
- [Response](#response) | ||
- [Usage Example](#usage-example) | ||
- [Important Notes](#important-notes) | ||
- [Open api 3 - known issues](#open-api-3---known-issues) | ||
- [Running Tests](#running-tests) | ||
<!-- END doctoc generated TOC please keep comment here to allow auto update --> | ||
## Install | ||
```bash | ||
npm install --save api-schema-builder | ||
``` | ||
## API | ||
### How to use | ||
```js | ||
var apiSchemaBuilder = require('api-schema-builder'); | ||
``` | ||
### api-schema-builder.buildSchema(PathToSwaggerFile, options) | ||
buildSchema the middleware with the swagger definition. | ||
The function return Promise. | ||
#### Arguments | ||
* `PathToSwaggerFile` – Path to the swagger definition | ||
* `options` – Additional options for the middleware. | ||
#### 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`. | ||
* `parameters.validate`: ajv validator that check: paths, files, queries, headers. | ||
* `body.validate`: ajv validator that check: body only. | ||
##### Options | ||
Options currently supports:. | ||
- `formats` - Array of formats that can be added to `ajv` configuration, each element in the array should include `name` and `pattern`. | ||
- `keywords` - Array of keywords that can be added to `ajv` configuration, each element in the array can be either an object or a function. | ||
If the element is an object, it must include `name` and `definition`. If the element is a function, it should accept `ajv` as its first argument and inside the function you need to call `ajv.addKeyword` to add your custom keyword | ||
- `makeOptionalAttributesNullable` - Boolean that forces preprocessing of Swagger schema to include 'null' as possible type for all non-required properties. Main use-case for this is to ensure correct handling of null values when Ajv type coercion is enabled | ||
- `ajvConfigBody` - Object that will be passed as config to new Ajv instance which will be used for validating request body. Can be useful to e. g. enable type coercion (to automatically convert strings to numbers etc). See Ajv documentation for supported values. | ||
- `ajvConfigParams` - Object that will be passed as config to new Ajv instance which will be used for validating request body. See Ajv documentation for supported values. | ||
- `contentTypeValidation` - Boolean that indicates if to perform content type validation in case `consume` field is specified and the request body is not empty. | ||
- `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) | ||
```js | ||
formats: [ | ||
{ name: 'double', pattern: /\d+\.(\d+)+/ }, | ||
{ name: 'int64', pattern: /^\d{1,19}$/ }, | ||
{ name: 'int32', pattern: /^\d{1,10}$/ } | ||
] | ||
``` | ||
## Usage Example | ||
```js | ||
apiSchemaBuilder.getSchema('test/unit-tests/input-validation/pet-store-swagger.yaml') | ||
.then(function (schema) { | ||
let schemaEndpoint = schema['/pet']['post']; | ||
//validate parameters | ||
let isParametersMatch = schemaEndpoint.parameters.validate({ query: {}, | ||
headers: { 'public-key': '1.0'},path: {},files: undefined }); | ||
expect(schemaEndpoint.parameters.errors).to.be.equal(null); | ||
expect(isParametersMatch).to.be.true; | ||
//validate body | ||
let isBodysMatch =schemaEndpoint.body.validate({'bark': 111}); | ||
expect(schemaEndpoint.body.errors).to.be.eql([{ | ||
'dataPath': '.bark', | ||
'keyword': 'type', | ||
'message': 'should be string', | ||
'params': { | ||
'type': 'string' | ||
}, | ||
'schemaPath': '#/properties/bark/type'} | ||
]) | ||
}); | ||
expect(isBodysMatch).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` | ||
## Open api 3 - known issues | ||
- supporting inheritance with discriminator , only if the ancestor object is the discriminator. | ||
- The discriminator supports in the inheritance chain stop when getting to a child with no discriminator (a leaf in the inheritance tree), meaning a leaf can't have a field which starts a new inheritance tree. | ||
so child with no discriminator cant point to other child with discriminator, | ||
## Running Tests | ||
Using mocha and istanbul | ||
```bash | ||
npm run test | ||
``` | ||
[npm-image]: https://img.shields.io/npm/v/express-ajv-swagger-validation.svg?style=flat | ||
[npm-url]: https://npmjs.org/package/express-ajv-swagger-validation | ||
[travis-image]: https://travis-ci.org/Zooz/express-ajv-swagger-validation.svg?branch=master | ||
[travis-url]: https://travis-ci.org/Zooz/express-ajv-swagger-validation | ||
[coveralls-image]: https://coveralls.io/repos/github/Zooz/express-ajv-swagger-validation/badge.svg?branch=master | ||
[coveralls-url]: https://coveralls.io/github/Zooz/express-ajv-swagger-validation?branch=master | ||
[downloads-image]: http://img.shields.io/npm/dm/express-ajv-swagger-validation.svg?style=flat | ||
[downloads-url]: https://npmjs.org/package/express-ajv-swagger-validation | ||
[license-image]: https://img.shields.io/badge/license-Apache_2.0-green.svg?style=flat | ||
[license-url]: LICENSE |
@@ -17,3 +17,3 @@ 'use strict'; | ||
*/ | ||
function getSchema(swaggerPath, options) { | ||
function buildSchema(swaggerPath, options) { | ||
let schemas = {}; | ||
@@ -28,2 +28,4 @@ let middlewareOptions = options || {}; | ||
const dereferenced = swaggers[0]; | ||
const referenced = swaggers[1]; | ||
Object.keys(dereferenced.paths).forEach(function (currentPath) { | ||
@@ -39,3 +41,3 @@ let pathParameters = dereferenced.paths[currentPath].parameters || []; | ||
if (isOpenApi3){ | ||
schemas[parsedPath][currentMethod].body = oas3.buildBodyValidation(dereferenced, swaggers[1], currentPath, currentMethod, middlewareOptions); | ||
schemas[parsedPath][currentMethod].body = oas3.buildBodyValidation(dereferenced, referenced, currentPath, currentMethod, middlewareOptions); | ||
} else { | ||
@@ -50,6 +52,27 @@ let bodySchema = middlewareOptions.expectFormFieldsInBody | ||
const validatedBodySchema = oas2.getValidatedBodySchema(bodySchema); | ||
schemas[parsedPath][currentMethod].body = oas2.buildBodyValidation(validatedBodySchema, dereferenced.definitions, swaggers[1], currentPath, currentMethod, parsedPath, middlewareOptions); | ||
let bodySchemaReference = referenced.paths[currentPath][currentMethod].parameters.filter(function (parameter) { return parameter.in === 'body' })[0]; | ||
let schemaReference = bodySchemaReference.schema['$ref']; | ||
schemas[parsedPath][currentMethod].body = oas2.buildBodyValidation(validatedBodySchema, dereferenced.definitions, referenced, currentPath, currentMethod, parsedPath, middlewareOptions, schemaReference); | ||
} | ||
} | ||
//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 headersValidator = responseDereferenceHeaders ? buildHeadersValidation(responseDereferenceHeaders, middlewareOptions) : undefined; | ||
let responseSchema = referenced.paths[currentPath][currentMethod].responses[statusCode].schema; | ||
let bodyValidator = responseSchema ? oas2.buildBodyValidation(responseDereferenceSchema, dereferenced.definitions, referenced, currentPath, currentMethod, parsedPath, middlewareOptions, responseSchema['$ref']) : undefined; | ||
if(headersValidator || bodyValidator){ | ||
schemas[parsedPath][currentMethod].responses[statusCode] = new Validators.ResponseValidator({body:bodyValidator,headers:headersValidator}); | ||
} | ||
} | ||
}); | ||
let localParameters = parameters.filter(function (parameter) { | ||
@@ -158,4 +181,31 @@ return parameter.in !== 'body'; | ||
//split to diff parsers if needed | ||
function buildHeadersValidation(headers, middlewareOptions) { | ||
let ajv = new Ajv( {allErrors: true, coerceTypes: 'array', ...middlewareOptions.ajvConfigParams}); | ||
ajvUtils.addCustomKeyword(ajv, middlewareOptions.formats); | ||
var ajvHeadersSchema = { | ||
title: 'HTTP headers', | ||
type: 'object', | ||
properties: {}, | ||
required: [], | ||
additionalProperties: true | ||
}; | ||
Object.keys(headers).forEach(key => { | ||
let headerObj = headers[key]; | ||
const headerName = key.toLowerCase(); | ||
ajvHeadersSchema.properties[headerName] = {description:headerObj.description, type: headerObj.type}; | ||
headerObj.required && ajvHeadersSchema.required.push(key); | ||
}, this); | ||
//todo - should i need it? | ||
// ajvHeadersSchema.content = createContentTypeHeaders(middlewareOptions.contentTypeValidation, contentTypes); | ||
return new Validators.SimpleValidator(ajv.compile(ajvHeadersSchema)); | ||
} | ||
module.exports = { | ||
getSchema: getSchema | ||
buildSchema: buildSchema | ||
}; |
@@ -36,14 +36,8 @@ | ||
function buildBodyValidation(schema, swaggerDefinitions, originalSwagger, currentPath, currentMethod, parsedPath, middlewareOptions = {}) { | ||
const defaultAjvOptions = { | ||
allErrors: true | ||
// unknownFormats: 'ignore' | ||
}; | ||
const options = Object.assign({}, defaultAjvOptions, middlewareOptions.ajvConfigBody); | ||
let ajv = new Ajv(options); | ||
function buildBodyValidation(schema, swaggerDefinitions, originalSwagger, currentPath, currentMethod, parsedPath, middlewareOptions = {}, schemaReference) { | ||
let ajv = new Ajv({allErrors: true, ...middlewareOptions.ajvConfigBody}); | ||
ajvUtils.addCustomKeyword(ajv, middlewareOptions.formats, middlewareOptions.keywords); | ||
if (schema.discriminator) { | ||
return buildInheritance(schema.discriminator, swaggerDefinitions, originalSwagger, currentPath, currentMethod, parsedPath, ajv); | ||
return buildInheritance(schema.discriminator, swaggerDefinitions, originalSwagger, currentPath, currentMethod, parsedPath, ajv, schemaReference); | ||
} else { | ||
@@ -53,5 +47,3 @@ return new Validators.SimpleValidator(ajv.compile(schema)); | ||
} | ||
function buildInheritance(discriminator, dereferencedDefinitions, swagger, currentPath, currentMethod, parsedPath, ajv) { | ||
let bodySchema = swagger.paths[currentPath][currentMethod].parameters.filter(function (parameter) { return parameter.in === 'body' })[0]; | ||
function buildInheritance(discriminator, dereferencedDefinitions, swagger, currentPath, currentMethod, parsedPath, ajv, schemaReference) { | ||
var inheritsObject = { | ||
@@ -65,3 +57,3 @@ inheritance: [] | ||
swagger.definitions[key].allOf.forEach(element => { | ||
if (element['$ref'] && element['$ref'] === bodySchema.schema['$ref']) { | ||
if (element['$ref'] && element['$ref'] === schemaReference) { | ||
inheritsObject[key] = ajv.compile(dereferencedDefinitions[key]); | ||
@@ -68,0 +60,0 @@ inheritsObject.inheritance.push(key); |
@@ -0,0 +0,0 @@ /** |
@@ -6,9 +6,12 @@ 'use strict'; | ||
SimpleValidator = require('./SimpleValidator'), | ||
DiscriminatorValidator = require('./DiscriminatorValidator'); | ||
DiscriminatorValidator = require('./DiscriminatorValidator'), | ||
ResponseValidator = require('./ResponseValidator'); | ||
module.exports = { | ||
Validator: Validator, | ||
OneOfValidator: OneOfValidator, | ||
ResponseValidator:ResponseValidator, | ||
SimpleValidator: SimpleValidator, | ||
DiscriminatorValidator | ||
}; | ||
}; |
@@ -14,3 +14,3 @@ 'use strict'; | ||
const swaggerPath = path.join(__dirname, 'wrong-path.yaml'); | ||
return schemaValidatorGenerator.getSchema(swaggerPath, {}) | ||
return schemaValidatorGenerator.buildSchema(swaggerPath, {}) | ||
.then(function(m) { throw new Error('was not supposed to succeed') }) | ||
@@ -17,0 +17,0 @@ .catch(function(m) { expect(m.message).to.contains('Error opening file') }) |
@@ -14,4 +14,4 @@ 'use strict'; | ||
before(function () { | ||
const swaggerPath = path.join(__dirname, 'pets.yaml'); | ||
return schemaValidatorGenerator.getSchema(swaggerPath, { | ||
const swaggerPath = path.join(__dirname, 'pets-request.yaml'); | ||
return schemaValidatorGenerator.buildSchema(swaggerPath, { | ||
ajvConfigBody: { | ||
@@ -18,0 +18,0 @@ coerceTypes: true |
@@ -14,4 +14,4 @@ 'use strict'; | ||
before(function () { | ||
const swaggerPath = path.join(__dirname, 'pets.yaml'); | ||
return schemaValidatorGenerator.getSchema(swaggerPath, {}).then(receivedSchema => { | ||
const swaggerPath = path.join(__dirname, 'pets-request.yaml'); | ||
return schemaValidatorGenerator.buildSchema(swaggerPath, {}).then(receivedSchema => { | ||
schema = receivedSchema; | ||
@@ -18,0 +18,0 @@ }); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No bug tracker
MaintenancePackage does not have a linked bug tracker in package.json.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
No website
QualityPackage does not have a website.
Found 1 instance in 1 package
390190
1713
0
0
127
61