Skip to content

Commit bebd33b

Browse files
authored
Merge pull request #3 from KirillTregubov/main
100% code coverage and linting changes
2 parents 42dd15e + f02d166 commit bebd33b

File tree

6 files changed

+600
-188
lines changed

6 files changed

+600
-188
lines changed

README.md

+66-26
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
# Fastify Enforce Schema
22

3+
[![NPM version](https://img.shields.io/npm/v/fastify-enforce-schema.svg?style=flat)](https://www.npmjs.com/package/fastify-enforce-schema)
4+
[![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](https://standardjs.com/)
5+
36
<p align="center">
47
<img width="250" src="./assets/images/badge.png">
58
</p>
69

710
This plugin enables you to enforce `response`, `body` and `params` schemas on your controllers. The sentiment behind this is that no endpoint should ever be left without validation, and now you can enforce this on your application level, so no endpoints are released without the validation.
811

9-
The plugin works by `"hooking"` into [`onRoute hook`](https://www.fastify.io/docs/latest/Reference/Hooks/#onroute) which as described in the docs, triggers when a new route is registered.
12+
The plugin works by "hooking" into the [`onRoute hook`](https://www.fastify.io/docs/latest/Reference/Hooks/#onroute) which as described in the docs, triggers when a new route is registered.
1013

1114
_This plugin is built together with our [Programmer Network](https://programmer.network/) Community. You can join us on [Twitch](https://twitch.tv/programmer_network) and [Discord](https://discord.gg/ysnpXnY7ba)._
1215

@@ -22,45 +25,82 @@ Using [yarn](https://yarnpkg.com/):
2225

2326
## Usage
2427

25-
```js
26-
import fastify from "fastify";
27-
import enforceSchema from "fastify-enforce-schema";
28+
Route definitions in Fastify (4.x) are synchronous, so you must ensure that this plugin is registered before your route definitions.
2829

29-
const options = {
30-
required: ["response", "body", "params"], // available schemas that you'd want to enforce
31-
exclude: [{ url: "/api/v1/foo/:bar", excludedSchemas: ["body"] }],
32-
// don't enforce body schema validation for a path /api/v1/foo/:bar
33-
};
30+
```js
31+
// ESM
32+
import Fastify from 'fastify'
33+
import enforceSchema from 'fastify-enforce-schema'
34+
35+
const fastify = Fastify()
36+
await fastify.register(enforceSchema, {
37+
// options
38+
})
39+
```
40+
> _Note_: top-level await requires Node.js 14.8.0 or later
3441
35-
fastify.register(enforceSchema, options);
42+
```js
43+
// CommonJS
44+
const fastify = require('fastify')()
45+
const enforceSchema = require('fastify-enforce-schema')
46+
47+
fastify.register(enforceSchema, {
48+
// options
49+
})
50+
fastify.register((fastify, options, done) => {
51+
// your route definitions here...
52+
53+
done()
54+
})
55+
56+
// Plugins are loaded when fastify.listen(), fastify.inject() or fastify.ready() are called
3657
```
3758

3859
## Options
3960

40-
- **required**: response, body or params
61+
```js
62+
{
63+
disabled: ['response', 'body', 'params'], // can also be `true`
64+
exclude: [
65+
{
66+
url: '/foo'
67+
},
68+
{
69+
url: '/bar',
70+
excludeSchemas: ['response'] // [..., 'body', 'params']
71+
}
72+
]
73+
}
74+
```
4175

42-
_Note_ - The body schema will only be enforced on POST, PUT and PATCH
76+
<!-- - **required**: response, body or params<br /> -->
77+
- **disabled**: Disable specific schemas (`body`, `response`, `params`) or disable the plugin by passing `true`. <br />
4378

44-
- **exclude**: Endpoints to exclude by the _routeOptions.path_. Each exclude is an object, with a `url` and optional, `excludeSchemas` array. If the `excludeSchemas` array is not passed, validation for all 3 schemas (`body`, `respone`, `params`) is disabled.
79+
- **exclude**: Endpoints to exclude by the `routeOptions.path`. Each exclude is an object with a `url` and (optional) `excludeSchemas` array. If the `excludeSchemas` array is not passed, validation for all 3 schemas (`body`, `response`, `params`) is disabled.
4580

46-
### **Excluding specific schemas**
81+
By default, all schemas are enforced where appropriate.
4782

48-
To disable schema validation for all three types (response, body, and params), you can set { schema: false }. If you only want to disable the schema for a specific type, you can do so by setting the corresponding key to false. For example, to disable schema validation for the response, you can use { response: false }.
83+
> _Note_: The `body` schema is only enforced on POST, PUT and PATCH routes, and the `params` schema is only enforced on routes with `:params`.
4984
50-
```js
51-
await fastify.register(enforceSchema, {
52-
required: ["response", "body", "params"],
53-
});
85+
### Disable schema enforcement on specific routes
5486

55-
fastify.get("/foo", { schema: false }, (req, reply) => {
56-
reply.code(200);
57-
});
87+
```js
88+
// Exclude all schemas
89+
fastify.get('/foo', { schema: false }, (req, reply) => {
90+
reply.code(200)
91+
})
5892

93+
// Exclude response and params schemas
5994
fastify.get(
60-
"/bar",
61-
{ schema: { response: false, body: false, params: false } },
95+
'/bar:baz',
96+
{ schema: { response: false, params: false } },
6297
(req, reply) => {
63-
reply.code(200);
98+
reply.code(200)
6499
}
65-
);
100+
)
101+
102+
// Exclude body schema
103+
fastify.post('/baz', { schema: { body: false } }, (req, reply) => {
104+
reply.code(200)
105+
})
66106
```

index.js

+40-27
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,36 @@
11
const fp = require('fastify-plugin')
22
const {
3-
getErrrorMessage,
3+
getErrorMessage,
44
hasProperties,
55
initialExcludes,
66
isSchemaTypeExcluded,
7-
SCHEMA_TYPES
7+
SCHEMA_TYPES,
8+
isSchemaDisabled
89
} = require('./utils')
910

1011
function FastifyEnforceSchema (fastify, opts, done) {
11-
if (!opts) {
12-
opts = {}
12+
if (Object.prototype.hasOwnProperty.call(opts, 'required')) {
13+
process.emitWarning(
14+
'The `required` option for fastify-enforce-schema will be removed soon. Since all schemas are enforced by default, consider using the `exclude` option to exclude specific schemas.',
15+
'DeprecationWarning'
16+
)
17+
} else {
18+
opts.required = []
1319
}
1420

15-
if (!Object.prototype.hasOwnProperty.call(opts, 'required')) {
16-
opts.required = []
21+
if (!Object.prototype.hasOwnProperty.call(opts, 'disabled')) {
22+
opts.disabled = []
23+
}
24+
if (opts.disabled === true || (Array.isArray(opts.disabled) && isSchemaDisabled(opts.disabled))) {
25+
done()
26+
return
1727
}
1828

1929
if (!Object.prototype.hasOwnProperty.call(opts, 'exclude')) {
2030
opts.exclude = []
2131
}
2232

23-
const { required, exclude } = opts
33+
const { disabled, exclude } = opts
2434

2535
fastify.addHook('onRoute', (routeOptions) => {
2636
if (
@@ -46,59 +56,62 @@ function FastifyEnforceSchema (fastify, opts, done) {
4656
}
4757

4858
if (!routeOptions?.schema) {
49-
throw new Error(
50-
`schema missing at the path ${routeOptions.method}: "${routeOptions.path}"`
51-
)
59+
throw new Error(getErrorMessage({ schema: true }, routeOptions))
5260
}
5361

5462
if (
5563
routeOptions?.schema?.response !== false &&
5664
!isSchemaTypeExcluded(excludedEntity, SCHEMA_TYPES.response) &&
57-
required.indexOf(SCHEMA_TYPES.response) !== -1
65+
disabled.indexOf(SCHEMA_TYPES.response) === -1
5866
) {
59-
const schema = Object.keys(routeOptions?.schema?.response || [])
67+
const responseKeys = Object.keys(routeOptions?.schema?.response || {})
6068

6169
if (!routeOptions?.schema?.response) {
62-
throw new Error(getErrrorMessage(SCHEMA_TYPES.response, routeOptions))
70+
throw new Error(getErrorMessage({ schemaType: SCHEMA_TYPES.response }, routeOptions))
6371
}
6472

6573
if (
6674
routeOptions?.schema?.response &&
67-
!Object.keys(routeOptions?.schema?.response || []).length
75+
!responseKeys.length
6876
) {
69-
throw new Error('No HTTP status codes provided in the response schema')
77+
throw new Error(getErrorMessage({ message: 'No HTTP status codes provided in the response schema' }, routeOptions))
7078
}
7179

72-
schema.forEach((value) => {
73-
if (!Number.isInteger(parseInt(value, 10))) {
74-
throw new Error(
75-
`"${value}" is not a number. HTTP status codes from 100 - 599 supported`
76-
)
80+
responseKeys.forEach((value) => {
81+
if (value === 'default' || ['1xx', '2xx', '3xx', '4xx', '5xx'].includes(value) || (value >= 100 && value <= 599)) {
82+
if (hasProperties(routeOptions, SCHEMA_TYPES.response, value)) {
83+
done()
84+
return
85+
}
86+
87+
throw new Error(getErrorMessage({ message: `${SCHEMA_TYPES.response} key "${value}" must be a non-empty object` }, routeOptions))
7788
}
7889

79-
if (value < 100 || value > 599) {
80-
throw new Error('HTTP status codes from 100 - 599 supported')
90+
if (Number.isInteger(parseInt(value, 10))) {
91+
throw new Error(getErrorMessage({ message: 'valid HTTP status codes range from 100 - 599' }, routeOptions))
8192
}
93+
94+
throw new Error(getErrorMessage({ message: `"${value}" is not "default" or a supported HTTP status code` }, routeOptions))
8295
})
8396
}
8497
if (
8598
routeOptions?.schema?.body !== false &&
8699
!isSchemaTypeExcluded(excludedEntity, SCHEMA_TYPES.body) &&
87100
['POST', 'PUT', 'PATCH'].includes(routeOptions.method) &&
88-
required.indexOf(SCHEMA_TYPES.body) !== -1 &&
101+
disabled.indexOf(SCHEMA_TYPES.body) === -1 &&
89102
!hasProperties(routeOptions, SCHEMA_TYPES.body)
90103
) {
91-
throw new Error(getErrrorMessage(SCHEMA_TYPES.body, routeOptions))
104+
throw new Error(getErrorMessage({ schemaType: SCHEMA_TYPES.body }, routeOptions))
92105
}
93106

94107
if (
95108
routeOptions?.schema?.params !== false &&
96-
new RegExp(/:\w+/).test(routeOptions.url) &&
109+
/:\w+/.test(routeOptions.url) &&
97110
!isSchemaTypeExcluded(excludedEntity, SCHEMA_TYPES.params) &&
98-
required.indexOf(SCHEMA_TYPES.params) !== -1 &&
111+
disabled.indexOf(SCHEMA_TYPES.params) === -1 &&
99112
!hasProperties(routeOptions, SCHEMA_TYPES.params)
100113
) {
101-
throw new Error(getErrrorMessage(SCHEMA_TYPES.params, routeOptions))
114+
throw new Error(getErrorMessage({ schemaType: SCHEMA_TYPES.params }, routeOptions))
102115
}
103116
})
104117

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "fastify-enforce-schema",
33
"url": "",
44
"author": "Aleksandar Grbic - (https://programmer.network)",
5-
"version": "1.0.7",
5+
"version": "1.0.8",
66
"description": "Enforce AJV schemas across your endpoints.",
77
"repository": {
88
"type": "git",

0 commit comments

Comments
 (0)