1
- import Ajv from 'ajv'
1
+ import { Ajv , type Options } from 'ajv'
2
+ import addFormats from 'ajv-formats'
2
3
import {
4
+ FastifyServerOptions ,
3
5
type FastifyInstance ,
4
6
type FastifyReply ,
5
7
type FastifyRequest ,
@@ -18,11 +20,56 @@ import {
18
20
} from './interfaces.js'
19
21
import { get } from './utils.js'
20
22
23
+ // Fix CJS/ESM interoperability
24
+
21
25
export interface ValidationResult extends FastifyValidationResult {
22
26
dataPath : any
23
27
instancePath : string
24
28
}
25
29
30
+ /* c8 ignore next 15 */
31
+ /*
32
+ The fastify defaults, with the following modifications:
33
+ * coerceTypes is set to false
34
+ * removeAdditional is set to false
35
+ * allErrors is set to true
36
+ * uriResolver has been removed
37
+ */
38
+ export const defaultAjvOptions : Options = {
39
+ coerceTypes : false ,
40
+ useDefaults : true ,
41
+ removeAdditional : false ,
42
+ addUsedSchema : false ,
43
+ allErrors : true
44
+ }
45
+
46
+ function buildAjv ( options ?: Options , plugins ?: ( Function | [ Function , unknown ] ) [ ] ) : Ajv {
47
+ // Create the instance
48
+ const compiler : Ajv = new Ajv ( {
49
+ ...defaultAjvOptions ,
50
+ ...options
51
+ } )
52
+
53
+ // Add plugins
54
+ let formatPluginAdded = false
55
+ for ( const pluginSpec of plugins ?? [ ] ) {
56
+ const [ plugin , pluginOpts ] : [ Function , unknown ] = Array . isArray ( pluginSpec ) ? pluginSpec : [ pluginSpec , undefined ]
57
+
58
+ if ( plugin . name === 'formatsPlugin' ) {
59
+ formatPluginAdded = true
60
+ }
61
+
62
+ plugin ( compiler , pluginOpts )
63
+ }
64
+
65
+ if ( ! formatPluginAdded ) {
66
+ // @ts -expect-error Wrong typing
67
+ addFormats ( compiler )
68
+ }
69
+
70
+ return compiler
71
+ }
72
+
26
73
export function niceJoin ( array : string [ ] , lastSeparator : string = ' and ' , separator : string = ', ' ) : string {
27
74
switch ( array . length ) {
28
75
case 0 :
@@ -89,13 +136,15 @@ export const validationMessagesFormatters: Record<string, ValidationFormatter> =
89
136
invalidResponse : code =>
90
137
`The response returned from the endpoint violates its specification for the HTTP status ${ code } .` ,
91
138
invalidFormat : format => `must match format "${ format } " (format)`
139
+ /* c8 ignore next */
92
140
}
93
141
94
142
export function convertValidationErrors (
95
143
section : RequestSection ,
96
144
data : Record < string , unknown > ,
97
145
validationErrors : ValidationResult [ ]
98
146
) : Validations {
147
+ /* c8 ignore next 2 */
99
148
const errors : Record < string , string > = { }
100
149
101
150
if ( section === 'querystring' ) {
@@ -182,6 +231,7 @@ export function convertValidationErrors(
182
231
}
183
232
184
233
// No custom message was found, default to input one replacing the starting verb and adding some path info
234
+ /* c8 ignore next 3 */
185
235
if ( ! message . length ) {
186
236
message = `${ e . message ?. replace ( / ^ s h o u l d / , 'must' ) } (${ e . keyword } )`
187
237
}
@@ -272,31 +322,22 @@ export function addResponseValidation(this: FastifyInstance, route: RouteOptions
272
322
}
273
323
274
324
export function compileResponseValidationSchema ( this : FastifyInstance , configuration : Configuration ) : void {
275
- // Fix CJS/ESM interoperability
276
- // @ts -expect-error Fix types
277
- let AjvConstructor = Ajv as Ajv & { default ?: Ajv }
278
-
279
- if ( AjvConstructor . default ) {
280
- AjvConstructor = AjvConstructor . default
281
- }
282
-
325
+ /* c8 ignore next 3 */
283
326
const hasCustomizer = typeof configuration . responseValidatorCustomizer === 'function'
284
327
328
+ // This is hackish, but it is the only way to get the options from fastify at the moment.
329
+ const kOptions = Object . getOwnPropertySymbols ( this ) . find ( s => s . description === 'fastify.options' ) !
330
+
285
331
for ( const [ instance , validators , schemas ] of this [ kHttpErrorsEnhancedResponseValidations ] ) {
286
- // @ts -expect-error Fix types
287
- const compiler : Ajv = new AjvConstructor ( {
288
- // The fastify defaults, with the exception of removeAdditional and coerceTypes, which have been reversed
289
- removeAdditional : false ,
290
- useDefaults : true ,
291
- coerceTypes : false ,
292
- allErrors : true
293
- } )
332
+ // Create the compiler using exactly the same options as fastify
333
+ const ajvOptions = ( instance [ kOptions as keyof FastifyInstance ] as FastifyServerOptions ) ?. ajv ?? { }
334
+ const compiler = buildAjv ( ajvOptions . customOptions , ajvOptions . plugins )
294
335
336
+ // Add instance schemas
295
337
compiler . addSchema ( Object . values ( instance . getSchemas ( ) ) )
296
- compiler . addKeyword ( 'example' )
297
338
339
+ // Customize if required to
298
340
if ( hasCustomizer ) {
299
- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
300
341
configuration . responseValidatorCustomizer ! ( compiler )
301
342
}
302
343
0 commit comments