Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support, openapi spec added #458

Closed
wants to merge 25 commits into from
Closed
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
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
79 changes: 79 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,51 @@ fastify.ready(err => {
fastify.swagger()
})
```

<a name="openapi.validation"></a>
## OpenAPI validation formats

There are three way to enable open api specification
Adityapanther marked this conversation as resolved.
Show resolved Hide resolved

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
},
( ) => {}
)
```


<a name="api"></a>
## API

Expand Down Expand Up @@ -207,6 +252,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 it's `true` means fastify will support swagger open api specs (binary, byte, int32, int64) as a schema |
Adityapanther marked this conversation as resolved.
Show resolved Hide resolved
| 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/). |
Expand Down Expand Up @@ -408,6 +454,8 @@ Please specify `type: 'null'` for the response otherwise Fastify itself will fai
}
```



<a name="route.openapi"></a>
#### OpenAPI Parameter Options

Expand Down Expand Up @@ -637,6 +685,37 @@ You can integration this plugin with ```fastify-helmet``` with some little work.
})
```


<a name= "route.fileupload"></a>
### 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"
}
}
}

}}, ()=>{})
}

```



<a name="development"></a>
### Development
In order to start development run:
Expand Down
1 change: 1 addition & 0 deletions examples/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const swaggerOption = {
}

const openapiOption = {
customCompiler: true,
openapi: {
info: {
title: 'Test swagger',
Expand Down
12 changes: 11 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
@@ -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'

Expand All @@ -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
92 changes: 92 additions & 0 deletions lib/validatorCompiler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
'use strict'

const Ajv = require('ajv')
Adityapanther marked this conversation as resolved.
Show resolved Hide resolved
const Decimal = require('decimal.js')
mcollina marked this conversation as resolved.
Show resolved Hide resolved

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',
mcollina marked this conversation as resolved.
Show resolved Hide resolved
validate: binaryValidation
})
ajv.addFormat('byte', {
type: 'string',
validate: byteValidation
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At every validation execution, we are creating a new ajv instance and compilation.
We must avoid it.

If you precompile the functions, it should work as well

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you provide example

})
ajv.addFormat('int32', {
type: 'number',
validate: int32bitValidation
})
ajv.addFormat('int64', {
type: 'number',
validate: int64bitValidation
})
Adityapanther marked this conversation as resolved.
Show resolved Hide resolved

return ajv.compile(schema)
}

module.exports = {
validatorCompiler,
int32bitValidation,
int64bitValidation,
binaryValidation,
byteValidation
}
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@
"tsd": "^0.17.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",
Expand Down
4 changes: 2 additions & 2 deletions test/esm/esm.mjs
Original file line number Diff line number Diff line change
@@ -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()

Expand Down
115 changes: 115 additions & 0 deletions test/validatorCompiler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
const {
Adityapanther marked this conversation as resolved.
Show resolved Hide resolved
test
} = require('tap')
const Fastify = require('fastify')
// const Swagger = require('swagger-parser')
Adityapanther marked this conversation as resolved.
Show resolved Hide resolved
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'],
body: {
type: 'object',
properties: {
upload: {
type: 'file',
format: 'binary'
}
}
}
}

}, () => {})

fastify.ready(err => {
Adityapanther marked this conversation as resolved.
Show resolved Hide resolved
t.error(err)
t.end()
})

// fastify.inject({
// method: 'POST',
// url: '/'
// }, (err, res) => {
// t.error(err)
// t.equal(res.statusCode, 200)
// t.ok()
// // const payload = JSON.parse(res.payload)
// })
// t.end()
Adityapanther marked this conversation as resolved.
Show resolved Hide resolved
})