1- import Ajv from 'ajv'
1+ import { Ajv , type Options } from 'ajv'
2+ import addFormats from 'ajv-formats'
23import {
4+ FastifyServerOptions ,
35 type FastifyInstance ,
46 type FastifyReply ,
57 type FastifyRequest ,
@@ -18,11 +20,56 @@ import {
1820} from './interfaces.js'
1921import { get } from './utils.js'
2022
23+ // Fix CJS/ESM interoperability
24+
2125export interface ValidationResult extends FastifyValidationResult {
2226 dataPath : any
2327 instancePath : string
2428}
2529
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+
2673export function niceJoin ( array : string [ ] , lastSeparator : string = ' and ' , separator : string = ', ' ) : string {
2774 switch ( array . length ) {
2875 case 0 :
@@ -89,13 +136,15 @@ export const validationMessagesFormatters: Record<string, ValidationFormatter> =
89136 invalidResponse : code =>
90137 `The response returned from the endpoint violates its specification for the HTTP status ${ code } .` ,
91138 invalidFormat : format => `must match format "${ format } " (format)`
139+ /* c8 ignore next */
92140}
93141
94142export function convertValidationErrors (
95143 section : RequestSection ,
96144 data : Record < string , unknown > ,
97145 validationErrors : ValidationResult [ ]
98146) : Validations {
147+ /* c8 ignore next 2 */
99148 const errors : Record < string , string > = { }
100149
101150 if ( section === 'querystring' ) {
@@ -182,6 +231,7 @@ export function convertValidationErrors(
182231 }
183232
184233 // No custom message was found, default to input one replacing the starting verb and adding some path info
234+ /* c8 ignore next 3 */
185235 if ( ! message . length ) {
186236 message = `${ e . message ?. replace ( / ^ s h o u l d / , 'must' ) } (${ e . keyword } )`
187237 }
@@ -272,31 +322,22 @@ export function addResponseValidation(this: FastifyInstance, route: RouteOptions
272322}
273323
274324export 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 */
283326 const hasCustomizer = typeof configuration . responseValidatorCustomizer === 'function'
284327
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+
285331 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 )
294335
336+ // Add instance schemas
295337 compiler . addSchema ( Object . values ( instance . getSchemas ( ) ) )
296- compiler . addKeyword ( 'example' )
297338
339+ // Customize if required to
298340 if ( hasCustomizer ) {
299- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
300341 configuration . responseValidatorCustomizer ! ( compiler )
301342 }
302343
0 commit comments