@uphold/fastify-openapi-router-plugin 0.7.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -59,6 +59,7 @@ await fastify.register(import('@fastify/fastify-openapi-router-plugin'), {
59
59
  | `spec` | `string` or `object` | **REQUIRED**. A file path or object of your OpenAPI specification. |
60
60
  | `securityHandlers` | `object` | An object containing the security handlers that match [Security Schemes](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#security-scheme-object) described in your OpenAPI specification. |
61
61
  | `securityErrorMapper` | `function` | A function that allows mapping the default `UnauthorizedError` to a custom error. |
62
+ | `notImplementedErrorMapper` | `function` | A function that allows mapping the default `NotImplementedError` to a custom error. |
62
63
 
63
64
  #### `spec`
64
65
 
@@ -146,7 +147,7 @@ Any error thrown by the security handler will be internally wrapped in a `Securi
146
147
 
147
148
  #### `securityErrorMapper`
148
149
 
149
- The plugin will throw an `UnauthorizedError` when none of the `security` blocks succeed. By default, this error originates a `401` reply with `{ code: 'FST_OAS_UNAUTHORIZED', 'message': 'Unauthorized' }` as the payload. You can override this behavior by leveraging the `securityErrorMapper` option:
150
+ The plugin will throw an `UnauthorizedError` when none of the `security` blocks succeed. By default, this error originates a `401` reply with `{ code: 'FST_OAS_UNAUTHORIZED', message: 'Unauthorized' }` as the payload. You can override this behavior by leveraging the `securityErrorMapper` option:
150
151
 
151
152
  ```js
152
153
  await fastify.register(import('@fastify/fastify-openapi-router-plugin'), {
@@ -173,7 +174,8 @@ The `securityReport` property of the unauthorized error contains an array of obj
173
174
  schemes: {
174
175
  OAuth2: {
175
176
  ok: false,
176
- // The error will be either be a `fastify.oas.errors.SecurityHandlerError` or a `fastify.oas.errors.ScopesMismatchError` if the scopes were not satisfied.
177
+ // The error will be either be a `fastify.oas.errors.SecurityHandlerError` or a
178
+ // `fastify.oas.errors.ScopesMismatchError` if the scopes were not satisfied.
177
179
  error: <Error>,
178
180
  }
179
181
  }
@@ -183,6 +185,21 @@ The `securityReport` property of the unauthorized error contains an array of obj
183
185
 
184
186
  If you don't define a `securityErrorMapper`, you can still catch the `UnauthorizedError` in your fastify error handler.
185
187
 
188
+ #### `notImplementedErrorMapper`
189
+
190
+ The plugin will throw an `NotImplementedError` when you install a handler for not implemented specs through `fastify.oas.installNotImplementedRoutes()` function. By default, this error originates a `501` reply with `{ code: 'FST_OAS_NOT_IMPLEMENTED', message: 'Not implemented' }` as the payload. You can override this behavior by leveraging the `notImplementedErrorMapper` option:
191
+
192
+ ```js
193
+ await fastify.register(import('@fastify/fastify-openapi-router-plugin'), {
194
+ spec: './petstore.json',
195
+ notImplementedErrorMapper: (notImplementedError) => {
196
+ return MyNotImplementedError();
197
+ },
198
+ });
199
+ ```
200
+
201
+ If you don't define a `notImplementedErrorMapper`, you can still catch the `NotImplementedError` in your fastify error handler.
202
+
186
203
  ### Decorators
187
204
 
188
205
  #### `fastify.oas.route(options)`
@@ -204,12 +221,42 @@ fastify.oas.route({
204
221
  });
205
222
  ```
206
223
 
224
+ #### `fastify.oas.installNotImplementedRoutes()`
225
+
226
+ This function will register handlers with fastify for the routes in the spec that were not registered. You can use this, for example, when you want to give a more specific error for the consumer for operations that you still do not have an implementation.
227
+
228
+ > [!IMPORTANT]
229
+ > Make sure you `await` the calls to `fastify.register()` for your routes before calling this function to make sure that fastify executes them before the call to `fastify.oas.installNotImplementedRoutes()`.
230
+
231
+ **Example**
232
+
233
+ ```js
234
+ await fastify.register(import('@fastify/fastify-openapi-router-plugin'), {
235
+ spec: './petstore.json'
236
+ });
237
+
238
+ fastify.oas.route({
239
+ operationId: 'getPetById',
240
+ handler: (request, reply) => {}
241
+ });
242
+
243
+ // Routes in spec that were not registered through `fastify.oas.route` will get a 'not implemented' handler.
244
+ fastify.oas.installNotImplementedRoutes();
245
+
246
+ // Finish fastify setup.
247
+ await fastify.ready();
248
+ ```
249
+
250
+ > [!TIP]
251
+ > If you need to customize the `NotImplementedError` that is thrown by default, you can use the [notImplementedErrorMapper](#notimplementederrormapper) in order to change the default behavior.
252
+
207
253
  #### `fastify.oas.errors`
208
254
 
209
- This object contains all error classes that can be thrown by the plugin:
255
+ This object contains all error classes used by the plugin:
210
256
 
211
- - `UnauthorizedError`: Thrown when all security schemes verification failed.
212
- - `ScopesMismatchError`: Thrown when the scopes returned by the security handler do not satisfy the scopes defined in the API operation.
257
+ - `SecurityHandlerError`: Used to wrap a security handler error.
258
+ - `ScopesMismatchError`: Used when the scopes returned by the security handler do not satisfy the scopes defined in the API operation.
259
+ - `UnauthorizedError`: Used to indicate that the request is unauthorized, containing a `securityReport`. Check the [`securityErrorMapper`](#securityerrormapper) section for more information.
213
260
 
214
261
  #### `request.oas`
215
262
 
@@ -217,7 +264,7 @@ For your convenience, the object `request.oas` is populated with data related to
217
264
 
218
265
  - `operation` is the raw API operation that activated the Fastify route.
219
266
  - `security` is an object where keys are security scheme names and values the returned `data` field from security handlers.
220
- - `securityReport`: A detailed report of the security verification process. Check the [`securityErrorMapper`](#security-error-mapper) section for more information.
267
+ - `securityReport`: A detailed report of the security verification process. Check the [`securityErrorMapper`](#securityerrormapper) section for more information.
221
268
 
222
269
  **Example**
223
270
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uphold/fastify-openapi-router-plugin",
3
- "version": "0.7.1",
3
+ "version": "1.0.0",
4
4
  "description": "A plugin for Fastify to connect routes with a OpenAPI 3.x specification",
5
5
  "main": "./src/index.js",
6
6
  "types": "./types/index.d.ts",
@@ -1,11 +1,19 @@
1
+ import { NotImplementedError, createNotImplementedError } from './not-implemented-error.js';
1
2
  import { ScopesMismatchError, createScopesMismatchError } from './scopes-mismatch-error.js';
2
3
  import { SecurityHandlerError, createSecurityHandlerError } from './security-handler-error.js';
3
4
  import { UnauthorizedError, createUnauthorizedError } from './unauthorized-error.js';
4
5
 
5
6
  const errors = {
7
+ NotImplementedError,
6
8
  ScopesMismatchError,
7
9
  SecurityHandlerError,
8
10
  UnauthorizedError
9
11
  };
10
12
 
11
- export { createScopesMismatchError, createUnauthorizedError, createSecurityHandlerError, errors };
13
+ export {
14
+ createScopesMismatchError,
15
+ createUnauthorizedError,
16
+ createNotImplementedError,
17
+ createSecurityHandlerError,
18
+ errors
19
+ };
@@ -0,0 +1,11 @@
1
+ import createError from '@fastify/error';
2
+
3
+ const NotImplementedError = createError('FST_OAS_NOT_IMPLEMENTED', 'Not implemented', 501);
4
+
5
+ const createNotImplementedError = () => {
6
+ const err = new NotImplementedError();
7
+
8
+ return err;
9
+ };
10
+
11
+ export { NotImplementedError, createNotImplementedError };
package/src/index.test.js CHANGED
@@ -13,6 +13,9 @@ describe('Fastify plugin', () => {
13
13
  '/pets': {
14
14
  get: {
15
15
  operationId: 'getPets'
16
+ },
17
+ post: {
18
+ operationId: 'postPets'
16
19
  }
17
20
  }
18
21
  }
@@ -46,48 +49,119 @@ describe('Fastify plugin', () => {
46
49
  });
47
50
  });
48
51
 
49
- it('should throw registering a route if `operationId` is not in spec', async () => {
50
- const app = fastify({ logger: false });
52
+ describe('route()', () => {
53
+ it('should throw registering a route if `operationId` is not in spec', async () => {
54
+ const app = fastify({ logger: false });
51
55
 
52
- await app.register(OpenAPIRouter, { spec });
56
+ await app.register(OpenAPIRouter, { spec });
53
57
 
54
- expect(() =>
55
- app.oas.route({
56
- handler: () => {},
57
- operationId: 'getUnknown'
58
- })
59
- ).toThrowError(`Missing 'getUnknown' in OpenAPI spec.`);
60
- });
58
+ expect(() =>
59
+ app.oas.route({
60
+ handler: () => {},
61
+ operationId: 'getUnknown'
62
+ })
63
+ ).toThrowError(`Missing 'getUnknown' in OpenAPI spec.`);
64
+ });
61
65
 
62
- it('should set a route-level onRequest hook', async () => {
63
- const app = fastify({ logger: false });
64
- const onRequest = vi.fn(async () => {});
66
+ it('should set a route-level onRequest hook', async () => {
67
+ const app = fastify({ logger: false });
68
+ const onRequest = vi.fn(async () => {});
65
69
 
66
- await app.register(OpenAPIRouter, { spec });
70
+ await app.register(OpenAPIRouter, { spec });
67
71
 
68
- app.oas.route({
69
- handler: async () => {},
70
- onRequest,
71
- operationId: 'getPets'
72
+ app.oas.route({
73
+ handler: async () => {},
74
+ onRequest,
75
+ operationId: 'getPets'
76
+ });
77
+
78
+ await app.inject({ url: '/pets' });
79
+
80
+ expect(onRequest).toHaveBeenCalledTimes(1);
72
81
  });
73
82
 
74
- await app.inject({ url: '/pets' });
83
+ it('should not override internal route options', async () => {
84
+ const app = fastify({ logger: false });
75
85
 
76
- expect(onRequest).toHaveBeenCalledTimes(1);
86
+ await app.register(OpenAPIRouter, { spec });
87
+
88
+ expect(() =>
89
+ app.oas.route({
90
+ handler: async () => {},
91
+ method: 'PUT',
92
+ operationId: 'getPets',
93
+ url: '/pets/:id'
94
+ })
95
+ ).toThrowError(`Not allowed to override 'method', 'schema' or 'url' for operation 'getPets'.`);
96
+ });
77
97
  });
78
98
 
79
- it('should not override internal route options', async () => {
80
- const app = fastify({ logger: false });
99
+ describe('installNotImplementedRoutes()', () => {
100
+ it('should install handlers for not registered routes that will return a not implemented error', async () => {
101
+ const app = fastify({ logger: false });
81
102
 
82
- await app.register(OpenAPIRouter, { spec });
103
+ await app.register(OpenAPIRouter, { spec });
83
104
 
84
- expect(() =>
85
105
  app.oas.route({
86
- handler: async () => {},
87
- method: 'PUT',
88
- operationId: 'getPets',
89
- url: '/pets/:id'
90
- })
91
- ).toThrowError(`Not allowed to override 'method', 'schema' or 'url' for operation 'getPets'.`);
106
+ handler: async () => {
107
+ return 'pets';
108
+ },
109
+ operationId: 'getPets'
110
+ });
111
+
112
+ vi.spyOn(app, 'route');
113
+
114
+ app.oas.installNotImplementedRoutes();
115
+
116
+ expect(app.route).toHaveBeenCalledWith({
117
+ handler: expect.any(Function),
118
+ method: 'POST',
119
+ onRequest: [expect.any(Function)],
120
+ schema: {
121
+ headers: {
122
+ properties: {},
123
+ required: [],
124
+ type: 'object'
125
+ },
126
+ params: {
127
+ properties: {},
128
+ required: [],
129
+ type: 'object'
130
+ },
131
+ query: {
132
+ properties: {},
133
+ required: [],
134
+ type: 'object'
135
+ },
136
+ response: {}
137
+ },
138
+ url: '/pets'
139
+ });
140
+
141
+ const getPetsResult = await app.inject({ url: '/pets' });
142
+
143
+ expect(getPetsResult.body).toMatchInlineSnapshot('"pets"');
144
+
145
+ const postPetsResult = await app.inject({ method: 'POST', url: '/pets' });
146
+
147
+ expect(postPetsResult.body).toMatchInlineSnapshot(
148
+ `"{"statusCode":501,"code":"FST_OAS_NOT_IMPLEMENTED","error":"Not Implemented","message":"Not implemented"}"`
149
+ );
150
+ });
151
+
152
+ it('should call `notImplementedErrorMapper` option if provided', async () => {
153
+ const app = fastify({ logger: false });
154
+ const notImplementedErrorMapper = vi.fn(() => new Error('Foo'));
155
+
156
+ await app.register(OpenAPIRouter, { notImplementedErrorMapper, spec });
157
+
158
+ app.oas.installNotImplementedRoutes();
159
+
160
+ const postPetsResult = await app.inject({ method: 'POST', url: '/pets' });
161
+
162
+ expect(postPetsResult.body).toMatchInlineSnapshot(
163
+ `"{"statusCode":500,"error":"Internal Server Error","message":"Foo"}"`
164
+ );
165
+ });
92
166
  });
93
167
  });
@@ -21,16 +21,20 @@ export const parse = async options => {
21
21
  for (const method in methods) {
22
22
  const operation = methods[method];
23
23
 
24
+ const securityFn = applySecurity(operation, spec, options.securityHandlers, options.securityErrorMapper);
25
+ const applyParamsCoercingFn = applyParamsCoercing(operation);
26
+
24
27
  // Build fastify route.
25
28
  const route = {
26
29
  method: method.toUpperCase(),
27
30
  onRequest: [
28
- async function (request) {
31
+ async function openApiRouterOnRequestHook(request) {
29
32
  request[DECORATOR_NAME].operation = operation;
30
- },
31
- applySecurity(operation, spec, options.securityHandlers, options.securityErrorMapper),
32
- applyParamsCoercing(operation)
33
- ].filter(Boolean),
33
+
34
+ await securityFn?.(request);
35
+ applyParamsCoercingFn?.(request);
36
+ }
37
+ ],
34
38
  schema: {
35
39
  headers: parseParams(operation.parameters, 'header'),
36
40
  params: parseParams(operation.parameters, 'path'),
@@ -87,7 +87,7 @@ export const applyParamsCoercing = operation => {
87
87
  })
88
88
  .filter(Boolean);
89
89
 
90
- return async request => {
90
+ return request => {
91
91
  coerceArrayParametersFns.forEach(fn => fn(request));
92
92
  };
93
93
  };
@@ -103,9 +103,9 @@ describe('applySecurity()', () => {
103
103
  });
104
104
 
105
105
  it('should return undefined if `security` is disabled in operation', async () => {
106
- const onRequest = applySecurity({ security: [] }, { security: [{ OAuth2: [] }] }, {});
106
+ const securityFn = applySecurity({ security: [] }, { security: [{ OAuth2: [] }] }, {});
107
107
 
108
- expect(onRequest).toBeUndefined();
108
+ expect(securityFn).toBeUndefined();
109
109
  });
110
110
 
111
111
  it('should stop at the first successful security block', async () => {
@@ -135,9 +135,9 @@ describe('applySecurity()', () => {
135
135
  OAuth2: vi.fn(async () => ({ data: 'OAuth2 data', scopes: [] }))
136
136
  };
137
137
 
138
- const onRequest = applySecurity(operation, spec, securityHandlers);
138
+ const securityFn = applySecurity(operation, spec, securityHandlers);
139
139
 
140
- await onRequest(request);
140
+ await securityFn(request);
141
141
 
142
142
  expect(securityHandlers.ApiKey).toHaveBeenCalledTimes(1);
143
143
  expect(securityHandlers.ApiKey).toHaveBeenCalledWith('api key', request);
@@ -190,9 +190,9 @@ describe('applySecurity()', () => {
190
190
  OAuth2: vi.fn(async () => ({ data: 'OAuth2 data', scopes: [] }))
191
191
  };
192
192
 
193
- const onRequest = applySecurity(operation, spec, securityHandlers);
193
+ const securityFn = applySecurity(operation, spec, securityHandlers);
194
194
 
195
- await onRequest(request);
195
+ await securityFn(request);
196
196
 
197
197
  expect(securityHandlers.ApiKey).toHaveBeenCalledTimes(1);
198
198
  expect(securityHandlers.ApiKey).toHaveBeenCalledWith('api key', request);
@@ -249,10 +249,10 @@ describe('applySecurity()', () => {
249
249
  OAuth2: vi.fn(async () => ({ data: 'OAuth2 data', scopes: [] }))
250
250
  };
251
251
 
252
- const onRequest = applySecurity(operation, spec, securityHandlers);
252
+ const securityFn = applySecurity(operation, spec, securityHandlers);
253
253
 
254
254
  try {
255
- await onRequest(request);
255
+ await securityFn(request);
256
256
  } catch (error) {
257
257
  expect(error).toBeInstanceOf(errors.UnauthorizedError);
258
258
  expect(error.securityReport).toMatchInlineSnapshot(`
@@ -299,12 +299,12 @@ describe('applySecurity()', () => {
299
299
  })
300
300
  };
301
301
 
302
- const onRequest = applySecurity(operation, spec, securityHandlers);
302
+ const securityFn = applySecurity(operation, spec, securityHandlers);
303
303
 
304
304
  expect.assertions(2);
305
305
 
306
306
  try {
307
- await onRequest(request);
307
+ await securityFn(request);
308
308
  } catch (err) {
309
309
  expect(err).toBeInstanceOf(errors.UnauthorizedError);
310
310
  expect(err.securityReport).toMatchInlineSnapshot(`
@@ -354,9 +354,9 @@ describe('applySecurity()', () => {
354
354
  OAuth2: vi.fn(async () => ({ data: 'OAuth2 data', scopes: ['write'] }))
355
355
  };
356
356
 
357
- const onRequest = applySecurity(operation, spec, securityHandlers);
357
+ const securityFn = applySecurity(operation, spec, securityHandlers);
358
358
 
359
- await onRequest(request);
359
+ await securityFn(request);
360
360
 
361
361
  expect(securityHandlers.OAuth2).toHaveBeenCalledTimes(1);
362
362
  });
@@ -385,12 +385,12 @@ describe('applySecurity()', () => {
385
385
  })
386
386
  };
387
387
 
388
- const onRequest = applySecurity(operation, spec, securityHandlers);
388
+ const securityFn = applySecurity(operation, spec, securityHandlers);
389
389
 
390
390
  expect.assertions(2);
391
391
 
392
392
  try {
393
- await onRequest(request);
393
+ await securityFn(request);
394
394
  } catch (err) {
395
395
  expect(err).toBeInstanceOf(errors.UnauthorizedError);
396
396
  expect(securityHandlers.OAuth2).toHaveBeenCalledTimes(1);
@@ -420,11 +420,11 @@ describe('applySecurity()', () => {
420
420
  OAuth2: vi.fn(async () => ({ data: 'OAuth2 data', scopes: [] }))
421
421
  };
422
422
 
423
- const onRequest = applySecurity(operation, spec, securityHandlers);
423
+ const securityFn = applySecurity(operation, spec, securityHandlers);
424
424
 
425
425
  expect.assertions(3);
426
426
 
427
- await onRequest(request);
427
+ await securityFn(request);
428
428
 
429
429
  expect(securityHandlers.ApiKey).not.toHaveBeenCalled();
430
430
  expect(securityHandlers.OAuth2).toHaveBeenCalledTimes(1);
@@ -468,12 +468,12 @@ describe('applySecurity()', () => {
468
468
  OAuth2: vi.fn(() => ({ data: 'OAuth2 data', scopes: ['read'] }))
469
469
  };
470
470
 
471
- const onRequest = applySecurity(operation, spec, securityHandlers);
471
+ const securityFn = applySecurity(operation, spec, securityHandlers);
472
472
 
473
473
  expect.assertions(2);
474
474
 
475
475
  try {
476
- await onRequest(request);
476
+ await securityFn(request);
477
477
  } catch (err) {
478
478
  expect(err).toBeInstanceOf(errors.UnauthorizedError);
479
479
  expect(err.securityReport).toMatchInlineSnapshot(`
@@ -513,9 +513,9 @@ describe('applySecurity()', () => {
513
513
  OAuth2: vi.fn(() => {})
514
514
  };
515
515
 
516
- const onRequest = applySecurity(operation, spec, securityHandlers);
516
+ const securityFn = applySecurity(operation, spec, securityHandlers);
517
517
 
518
- await onRequest(request);
518
+ await securityFn(request);
519
519
 
520
520
  expect(request[DECORATOR_NAME].security).toMatchObject({ OAuth2: undefined });
521
521
  expect(request[DECORATOR_NAME].securityReport).toMatchInlineSnapshot(`
@@ -554,12 +554,12 @@ describe('applySecurity()', () => {
554
554
  OAuth2: vi.fn(() => {})
555
555
  };
556
556
 
557
- const onRequest = applySecurity(operation, spec, securityHandlers);
557
+ const securityFn = applySecurity(operation, spec, securityHandlers);
558
558
 
559
559
  expect.assertions(2);
560
560
 
561
561
  try {
562
- await onRequest(request);
562
+ await securityFn(request);
563
563
  } catch (err) {
564
564
  expect(err).toBeInstanceOf(errors.UnauthorizedError);
565
565
  expect(err.securityReport).toMatchInlineSnapshot(`
@@ -603,12 +603,12 @@ describe('applySecurity()', () => {
603
603
  const customError = new Error('Mapped error');
604
604
  const securityErrorMapper = vi.fn(() => customError);
605
605
 
606
- const onRequest = applySecurity(operation, spec, securityHandlers, securityErrorMapper);
606
+ const securityFn = applySecurity(operation, spec, securityHandlers, securityErrorMapper);
607
607
 
608
608
  expect.assertions(3);
609
609
 
610
610
  try {
611
- await onRequest(request);
611
+ await securityFn(request);
612
612
  } catch (err) {
613
613
  expect(err).toBe(customError);
614
614
  expect(securityErrorMapper).toHaveBeenCalledTimes(1);
package/src/plugin.js CHANGED
@@ -1,9 +1,34 @@
1
1
  import { DECORATOR_NAME } from './utils/constants.js';
2
- import { errors } from './errors/index.js';
2
+ import { createNotImplementedError, errors } from './errors/index.js';
3
3
  import { parse } from './parser/index.js';
4
4
 
5
- const createRoute = (fastify, routes) => {
6
- return ({ method, onRequest, operationId, schema, url, ...routeOptions }) => {
5
+ const createRoute = (fastify, routes, notImplementedErrorMapper) => {
6
+ const missingRoutes = new Set(Object.values(routes));
7
+
8
+ const addMissingRoutes = () => {
9
+ missingRoutes.forEach(route => {
10
+ fastify.route({
11
+ ...route,
12
+ handler: () => {
13
+ if (typeof notImplementedErrorMapper === 'function') {
14
+ const error = notImplementedErrorMapper(createNotImplementedError());
15
+
16
+ if (error instanceof Error) {
17
+ throw error;
18
+ }
19
+
20
+ throw createNotImplementedError();
21
+ }
22
+
23
+ throw createNotImplementedError();
24
+ }
25
+ });
26
+ });
27
+
28
+ missingRoutes.clear();
29
+ };
30
+
31
+ const addRoute = ({ method, onRequest, operationId, schema, url, ...routeOptions }) => {
7
32
  const route = routes[operationId];
8
33
 
9
34
  // Throw an error if the operation is unknown.
@@ -19,6 +44,8 @@ const createRoute = (fastify, routes) => {
19
44
  // Check if there is a routeOptions.onRequest hook.
20
45
  if (typeof onRequest === 'function') {
21
46
  route.onRequest.push(onRequest);
47
+ } else if (Array.isArray(onRequest)) {
48
+ route.onRequest.push(...onRequest);
22
49
  }
23
50
 
24
51
  // Register a new route.
@@ -26,6 +53,13 @@ const createRoute = (fastify, routes) => {
26
53
  ...routes[operationId],
27
54
  ...routeOptions
28
55
  });
56
+
57
+ missingRoutes.delete(route);
58
+ };
59
+
60
+ return {
61
+ addMissingRoutes,
62
+ addRoute
29
63
  };
30
64
  };
31
65
 
@@ -34,10 +68,13 @@ const plugin = async (fastify, options) => {
34
68
 
35
69
  const routes = await parse(options);
36
70
 
71
+ const { addMissingRoutes, addRoute } = createRoute(fastify, routes, options.notImplementedErrorMapper);
72
+
37
73
  // Decorate fastify object.
38
74
  fastify.decorate(DECORATOR_NAME, {
39
75
  errors,
40
- route: createRoute(fastify, routes)
76
+ installNotImplementedRoutes: addMissingRoutes,
77
+ route: addRoute
41
78
  });
42
79
 
43
80
  // Avoid decorating the request with reference types.
@@ -45,7 +82,7 @@ const plugin = async (fastify, options) => {
45
82
  fastify.decorateRequest(DECORATOR_NAME, null);
46
83
 
47
84
  // Instead, decorate each incoming request.
48
- fastify.addHook('onRequest', async request => {
85
+ fastify.addHook('onRequest', async function openApiRouterGlobalOnRequestHook(request) {
49
86
  request[DECORATOR_NAME] = {
50
87
  operation: {},
51
88
  security: {},