@@ -120,32 +120,58 @@ export class OpenAPIMockValidator {
120120 return { valid : true , errors : [ ] , warnings : existingWarnings } ;
121121 }
122122
123- const errors : ValidationError [ ] = ( ajv . errors || [ ] ) . map ( ( err : Record < string , unknown > ) => {
123+ const rawErrors : ValidationError [ ] = ( ajv . errors || [ ] ) . map ( ( err : Record < string , unknown > ) => {
124124 const params = err . params as Record < string , unknown > | undefined ;
125+ const instancePath = ( err . instancePath as string ) || '' ;
126+ const dotPath = toDotPath ( instancePath ) ;
127+
125128 const error : ValidationError = {
126- path : ( err . instancePath as string ) || '/' ,
129+ path : dotPath ,
127130 message : ( err . message as string ) || 'validation failed' ,
128131 keyword : err . keyword as string ,
129132 } ;
130133
134+ if ( err . keyword === 'required' ) {
135+ const missingProp = params ?. missingProperty as string ;
136+ error . path = dotPath ? `${ dotPath } .${ missingProp } ` : missingProp ;
137+ error . message = 'missing required property' ;
138+ }
139+
131140 if ( err . keyword === 'type' ) {
132141 error . expected = String ( params ?. type ) ;
133142 error . received = typeof payload === 'object' && payload !== null
134- ? typeof getValueAtPath ( payload , err . instancePath as string )
143+ ? typeof getValueAtPath ( payload , instancePath )
135144 : typeof payload ;
145+ error . message = `expected ${ error . expected } , got ${ error . received } ` ;
136146 }
137147
138148 if ( err . keyword === 'enum' ) {
139- error . expected = ( params ?. allowedValues as unknown [ ] ) ?. join ( ', ' ) ;
149+ const allowed = ( params ?. allowedValues as unknown [ ] ) ;
150+ error . expected = allowed ?. join ( ', ' ) ;
151+ error . message = `must be one of: ${ allowed ?. map ( v => `"${ v } "` ) . join ( ', ' ) } ` ;
140152 }
141153
142154 if ( err . keyword === 'additionalProperties' ) {
143- error . path = `${ err . instancePath } /${ params ?. additionalProperty } ` ;
155+ const extra = params ?. additionalProperty as string ;
156+ error . path = dotPath ? `${ dotPath } .${ extra } ` : extra ;
157+ error . message = 'unexpected property' ;
158+ }
159+
160+ if ( err . keyword === 'oneOf' ) {
161+ error . message = 'does not match any allowed schema (oneOf)' ;
162+ }
163+
164+ if ( err . keyword === 'anyOf' ) {
165+ error . message = 'does not match any allowed schema (anyOf)' ;
144166 }
145167
146168 return error ;
147169 } ) ;
148170
171+ // Collapse oneOf/anyOf: if the final error is a oneOf/anyOf keyword,
172+ // keep only that summary and drop the per-branch sub-errors
173+ const errors = collapseCompositionErrors ( rawErrors ) ;
174+
149175 return { valid : false , errors, warnings : existingWarnings } ;
150176 }
151177
@@ -234,3 +260,27 @@ function getValueAtPath(obj: unknown, path: string): unknown {
234260 }
235261 return current ;
236262}
263+
264+ function toDotPath ( instancePath : string ) : string {
265+ if ( ! instancePath || instancePath === '/' ) return 'response' ;
266+ const parts = instancePath . split ( '/' ) . filter ( Boolean ) ;
267+ const segments = parts . map ( ( p ) => / ^ \d + $ / . test ( p ) ? `[${ p } ]` : `.${ p } ` ) ;
268+ return `response${ segments . join ( '' ) } ` ;
269+ }
270+
271+ function collapseCompositionErrors ( errors : ValidationError [ ] ) : ValidationError [ ] {
272+ if ( errors . length <= 1 ) return errors ;
273+
274+ const last = errors [ errors . length - 1 ] ;
275+ if ( last . keyword === 'oneOf' || last . keyword === 'anyOf' ) {
276+ const prefix = last . path ;
277+ // Keep the composition error itself and any errors NOT under that path
278+ return errors . filter ( ( e ) => {
279+ if ( e === last ) return true ;
280+ // Drop sub-errors that are children of the composition path
281+ return ! e . path . startsWith ( prefix ) ;
282+ } ) ;
283+ }
284+
285+ return errors ;
286+ }
0 commit comments