diff --git a/README.md b/README.md
index 4a75d40d..58ad1389 100644
--- a/README.md
+++ b/README.md
@@ -133,6 +133,51 @@ fastify.ready(err => {
fastify.swagger()
})
```
+
+
+## OpenAPI validation formats
+
+There are three way to enable the use of OpenAPI validation formats:
+
+1) ### Project Level
+
+```js
+const fastify = require('fastify')()
+
+fastify.register(require('fastify-swagger'), {
+ customCompiler: true
+})
+```
+
+2) ### Instance Level
+
+```js
+const{ fastifySwagger, validatorCompiler } = require("fastify-swagger");
+
+fastify.setValidatorCompiler(validatorCompiler)
+```
+
+2) ### Route Level
+
+```js
+const{ fastifySwagger, validatorCompiler } = require("fastify-swagger");
+
+fastify.get(
+ '/',
+ {
+ schema: {
+ body: {
+ type: 'object',
+ properties: { file: { type: 'string', format: 'binary' } }
+ }
+ },
+ validatorCompiler
+ },
+ ( ) => {}
+)
+```
+
+
## API
@@ -218,6 +263,7 @@ An example of using `fastify-swagger` with `static` mode enabled can be found [h
| Option | Default | Description |
| ------------------ | ---------------- | ------------------------------------------------------------------------------------------------------------------------- |
| exposeRoute | false | Exposes documentation route. |
+ | customCompiler | false | If `true` enable OpenAPI validation formats (binary, byte, int32, int64) as a schema |
| hiddenTag | X-HIDDEN | Tag to control hiding of routes. |
| hideUntagged | false | If `true` remove routes without tags from resulting Swagger/OpenAPI schema file. |
| initOAuth | {} | Configuration options for [Swagger UI initOAuth](https://swagger.io/docs/open-source-tools/swagger-ui/usage/oauth2/). |
@@ -447,6 +493,8 @@ Please specify `type: 'null'` for the response otherwise Fastify itself will fai
}
```
+
+
#### OpenAPI Parameter Options
@@ -702,11 +750,42 @@ You can integration this plugin with ```fastify-helmet``` with some little work.
})
```
+
+
+### upload File Schema
+
+1) Enable open api validation formats (`docs available in upper`)
+
+2) FileUpload Schema
+
+```javascript
+
+async function routes(fastify, opts, next){
+ fastify.post("/upload", {
+ schema: {
+ type: "object",
+ body: {
+ type: "object",
+ properties: {
+ file: {
+ type: "file",
+ format: "binary"
+ }
+ }
+ }
+
+ }}, ()=>{})
+}
+
+```
+
## `$id` and `$ref` usage
+
+### Development
+
-## Development
In order to start development run:
```
npm i
diff --git a/examples/options.js b/examples/options.js
index ea31100a..5d8c3c0c 100644
--- a/examples/options.js
+++ b/examples/options.js
@@ -30,6 +30,7 @@ const swaggerOption = {
}
const openapiOption = {
+ customCompiler: true,
openapi: {
info: {
title: 'Test swagger',
diff --git a/index.js b/index.js
index 04348477..577c2974 100644
--- a/index.js
+++ b/index.js
@@ -1,8 +1,15 @@
'use strict'
const fp = require('fastify-plugin')
+const { validatorCompiler } = require('./lib/validatorCompiler')
function fastifySwagger (fastify, opts, next) {
+ // enabling custom or validator complier form opts object
+ const customCompiler = opts.customCompiler || null
+ if (customCompiler) {
+ fastify.setValidatorCompiler(validatorCompiler)
+ }
+
// by default the mode is dynamic, as plugin initially was developed
opts.mode = opts.mode || 'dynamic'
@@ -25,7 +32,10 @@ function fastifySwagger (fastify, opts, next) {
fastify.decorate('swaggerCSP', require('./static/csp.json'))
}
-module.exports = fp(fastifySwagger, {
+const plugin = fp(fastifySwagger, {
fastify: '>=3.x',
name: 'fastify-swagger'
})
+
+module.exports = plugin
+module.exports.validatorCompiler = validatorCompiler
diff --git a/lib/validatorCompiler.js b/lib/validatorCompiler.js
new file mode 100644
index 00000000..dc69fa60
--- /dev/null
+++ b/lib/validatorCompiler.js
@@ -0,0 +1,92 @@
+'use strict'
+
+const Ajv = require('ajv')
+const Decimal = require('decimal.js')
+
+const range = {
+
+ int64Bit: {
+ min: new Decimal('-9223372036854775808'),
+ max: new Decimal('9223372036854775807')
+ },
+ int32Bit: {
+ min: new Decimal('-2147483648'),
+ max: new Decimal('2147483647')
+ },
+ bytes: {
+ min: new Decimal('-128'),
+ max: new Decimal('127')
+ }
+
+}
+
+const binaryValidation = (data) => {
+ const binaryRegex = /^[0-1]{1,}$/g
+ return binaryRegex.test(data)
+}
+
+const byteValidation = (data) => {
+ const notBase64 = /[^A-Z0-9+/=]/i
+
+ const len = data.length
+ if (!len || len % 4 !== 0 || notBase64.test(data)) {
+ return false
+ }
+
+ const firstPaddingChar = data.indexOf('=')
+ return firstPaddingChar === -1 ||
+ firstPaddingChar === len - 1 ||
+ (firstPaddingChar === len - 2 && data[len - 1] === '=')
+}
+
+const int64bitValidation = (data) => {
+ return (
+ Number.isInteger(+data) &&
+ range.int64Bit.max.greaterThanOrEqualTo(data) &&
+ range.int64Bit.min.lessThanOrEqualTo(data)
+ )
+}
+
+const int32bitValidation = (data) => {
+ return (
+ Number.isInteger(+data) &&
+ range.int32Bit.max.greaterThanOrEqualTo(data) &&
+ range.int32Bit.min.lessThanOrEqualTo(data)
+ )
+}
+
+const validatorCompiler = (schema) => {
+ const ajv = new Ajv({
+ removeAdditional: true,
+ useDefaults: true,
+ coerceTypes: true,
+ nullable: true
+ })
+
+ ajv.addFormat('binary', {
+ type: 'string',
+ validate: binaryValidation
+ })
+ ajv.addFormat('byte', {
+ type: 'string',
+ validate: byteValidation
+ })
+ ajv.addFormat('int32', {
+ type: 'number',
+ validate: int32bitValidation
+ })
+ ajv.addFormat('int64', {
+ type: 'number',
+ validate: int64bitValidation
+ })
+
+ return ajv.compile(schema)
+}
+
+module.exports = {
+ validatorCompiler,
+ int32bitValidation,
+ int64bitValidation,
+ binaryValidation,
+ byteValidation
+}
diff --git a/package.json b/package.json
index 5f98c0ea..fa8a7ae8 100644
--- a/package.json
+++ b/package.json
@@ -52,6 +52,8 @@
"tsd": "^0.19.0"
},
"dependencies": {
+ "ajv": "^6.12.6",
+ "decimal.js": "^3.0.0",
"fastify-plugin": "^3.0.0",
"fastify-static": "^4.0.0",
"js-yaml": "^4.0.0",
diff --git a/test/esm/esm.mjs b/test/esm/esm.mjs
index a2d7c450..820a0719 100644
--- a/test/esm/esm.mjs
+++ b/test/esm/esm.mjs
@@ -1,11 +1,11 @@
import t from 'tap'
import Fastify from 'fastify'
-import swaggerDefault from '../../index.js'
+import fastifySwagger from '../../index.js'
t.test('esm support', async t => {
const fastify = Fastify()
- fastify.register(swaggerDefault)
+ fastify.register(fastifySwagger)
await fastify.ready()
diff --git a/test/validatorCompiler.js b/test/validatorCompiler.js
new file mode 100644
index 00000000..5e7aae66
--- /dev/null
+++ b/test/validatorCompiler.js
@@ -0,0 +1,128 @@
+'use strict'
+
+const {
+ test
+} = require('tap')
+const Fastify = require('fastify')
+const {
+ openapiOption
+} = require('../examples/options')
+
+const {
+ validatorCompiler,
+ fastifySwagger
+} = require('../index')
+
+const { binaryValidation, byteValidation, int32bitValidation, int64bitValidation } = require('../lib/validatorCompiler')
+
+test('validator compiler is function', t => {
+ t.type(validatorCompiler, 'function')
+ t.ok('passed validator compiler is function')
+ t.end()
+})
+
+test('binary validation', t => {
+ const data = binaryValidation('0100110001')
+ if (data) {
+ t.pass()
+ } else {
+ t.fail()
+ }
+ t.end()
+})
+
+test('byte validation', t => {
+ const byteData = 'MTIz'
+ const data1 = byteValidation(byteData)
+ const data2 = byteValidation('abc')
+
+ if (data1) {
+ t.pass()
+ } else {
+ t.fail()
+ }
+
+ if (!data2) {
+ t.pass()
+ } else {
+ t.fail()
+ }
+
+ const data3 = byteValidation('QCPvv6VAI++/pQ==')
+
+ if (data3) {
+ t.pass()
+ } else {
+ t.fail()
+ }
+ t.end()
+})
+
+test('int 32 bit validation', t => {
+ const data = int32bitValidation('123')
+ if (data) {
+ t.pass()
+ } else {
+ t.fail()
+ }
+ t.end()
+})
+
+test('int 64 bit validation', t => {
+ const data = int64bitValidation('512')
+ if (data) {
+ t.pass()
+ } else {
+ t.fail()
+ }
+ t.end()
+})
+
+test('validator compiler working', t => {
+ const fastify = Fastify()
+ fastify.register(fastifySwagger, openapiOption)
+ fastify.post('/', {
+ schema: {
+ type: 'object',
+ consumes: ['multipart/form-data', 'application/json'],
+ body: {
+ type: 'object',
+ properties: {
+ fileName: {
+ type: 'string'
+ },
+ upload: {
+ type: 'file',
+ format: 'binary'
+ }
+ }
+ }
+ }
+
+ }, (req, res) => {
+ return {
+ status: 'success',
+ code: 200,
+ msg: 'operation completed successfully'
+ }
+ })
+
+ fastify.ready(() => {
+ fastify.inject({
+ method: 'POST',
+ url: '/',
+ headers: {
+ 'content-type': 'application/json'
+ },
+ payload: {
+ fileName: 'aditya_picture'
+ }
+ }, (err, res) => {
+ const data = res.json()
+ t.error(err)
+ t.equal(data.code, 200)
+ t.same(data, { status: 'success', code: 200, msg: 'operation completed successfully' })
+ t.end()
+ })
+ })
+})