@@ -23,6 +23,7 @@ export interface EndpointDefinition {
2323 body ?: unknown
2424 query ?: Record < string , ParameterDefinition >
2525 response ?: unknown
26+ error ?: unknown
2627}
2728
2829// Helper function to extract schema names from $ref
@@ -96,6 +97,14 @@ export function generateInterface(
9697 // Track which schemas are used in request body and responses
9798 const requestSchemaNames = new Set < string > ( )
9899 const responseSchemaNames = new Set < string > ( )
100+ const errorSchemaNames = new Set < string > ( )
101+
102+ // Helper function to check if a status code is an error response
103+ const isErrorStatusCode = ( statusCode : string ) : boolean => {
104+ if ( statusCode === 'default' ) return true
105+ const code = parseInt ( statusCode , 10 )
106+ return code >= 400 && code < 600
107+ }
99108
100109 // First, collect schema names used in request body and responses
101110 if ( schema . paths ) {
@@ -126,14 +135,21 @@ export function generateInterface(
126135 }
127136 }
128137
129- // Collect response schemas
138+ // Collect response and error schemas
130139 if ( operation . responses ) {
131- for ( const response of Object . values ( operation . responses ) ) {
140+ for ( const [ statusCode , response ] of Object . entries (
141+ operation . responses ,
142+ ) ) {
143+ const isError = isErrorStatusCode ( statusCode )
132144 if ( '$ref' in response ) {
133145 // Extract schema name from $ref if it's a schema reference
134146 const schemaName = extractSchemaNameFromRef ( response . $ref )
135147 if ( schemaName ) {
136- responseSchemaNames . add ( schemaName )
148+ if ( isError ) {
149+ errorSchemaNames . add ( schemaName )
150+ } else {
151+ responseSchemaNames . add ( schemaName )
152+ }
137153 }
138154 } else if ( 'content' in response ) {
139155 const content = response . content
@@ -143,7 +159,11 @@ export function generateInterface(
143159 'schema' in jsonContent &&
144160 jsonContent . schema
145161 ) {
146- collectSchemaNames ( jsonContent . schema , responseSchemaNames )
162+ if ( isError ) {
163+ collectSchemaNames ( jsonContent . schema , errorSchemaNames )
164+ } else {
165+ collectSchemaNames ( jsonContent . schema , responseSchemaNames )
166+ }
147167 }
148168 }
149169 }
@@ -345,6 +365,110 @@ export function generateInterface(
345365 endpoint . response = responseType
346366 }
347367
368+ // Extract error
369+ // Check if error uses a component schema
370+ let errorType : unknown
371+ if ( operation . responses ) {
372+ // Find error responses (4xx, 5xx, or default)
373+ const errorResponse =
374+ operation . responses [ '400' ] ||
375+ operation . responses [ '401' ] ||
376+ operation . responses [ '403' ] ||
377+ operation . responses [ '404' ] ||
378+ operation . responses [ '422' ] ||
379+ operation . responses [ '500' ] ||
380+ operation . responses [ 'default' ] ||
381+ Object . entries ( operation . responses ) . find ( ( [ statusCode ] ) =>
382+ isErrorStatusCode ( statusCode ) ,
383+ ) ?. [ 1 ]
384+
385+ if ( errorResponse ) {
386+ if ( '$ref' in errorResponse ) {
387+ // ResponseObject reference - skip for now
388+ // Could resolve if needed
389+ } else if ( 'content' in errorResponse ) {
390+ const content = errorResponse . content
391+ const jsonContent = content ?. [ 'application/json' ]
392+ if (
393+ jsonContent &&
394+ 'schema' in jsonContent &&
395+ jsonContent . schema
396+ ) {
397+ // Check if schema is a direct reference to components.schemas
398+ if ( '$ref' in jsonContent . schema ) {
399+ const schemaName = extractSchemaNameFromRef (
400+ jsonContent . schema . $ref ,
401+ )
402+ // Check if schema exists in components.schemas and is used in error
403+ if (
404+ schemaName &&
405+ schema . components ?. schemas ?. [ schemaName ] &&
406+ errorSchemaNames . has ( schemaName )
407+ ) {
408+ // Use component reference
409+ errorType = `DevupErrorComponentStruct['${ schemaName } ']`
410+ } else {
411+ // Extract schema type with response options
412+ const responseDefaultNonNullable =
413+ options ?. responseDefaultNonNullable ?? true
414+ const { type : schemaType } = getTypeFromSchema (
415+ jsonContent . schema ,
416+ schema ,
417+ { defaultNonNullable : responseDefaultNonNullable } ,
418+ )
419+ errorType = schemaType
420+ }
421+ } else {
422+ // Check if it's an array with items referencing a component schema
423+ const schemaObj =
424+ jsonContent . schema as OpenAPIV3_1 . SchemaObject
425+ if (
426+ schemaObj . type === 'array' &&
427+ schemaObj . items &&
428+ '$ref' in schemaObj . items
429+ ) {
430+ const schemaName = extractSchemaNameFromRef (
431+ schemaObj . items . $ref ,
432+ )
433+ // Check if schema exists in components.schemas and is used in error
434+ if (
435+ schemaName &&
436+ schema . components ?. schemas ?. [ schemaName ] &&
437+ errorSchemaNames . has ( schemaName )
438+ ) {
439+ // Use component reference for array items
440+ errorType = `Array<DevupErrorComponentStruct['${ schemaName } ']>`
441+ } else {
442+ // Extract schema type with response options
443+ const responseDefaultNonNullable =
444+ options ?. responseDefaultNonNullable ?? true
445+ const { type : schemaType } = getTypeFromSchema (
446+ jsonContent . schema ,
447+ schema ,
448+ { defaultNonNullable : responseDefaultNonNullable } ,
449+ )
450+ errorType = schemaType
451+ }
452+ } else {
453+ // Extract schema type with response options
454+ const responseDefaultNonNullable =
455+ options ?. responseDefaultNonNullable ?? true
456+ const { type : schemaType } = getTypeFromSchema (
457+ jsonContent . schema ,
458+ schema ,
459+ { defaultNonNullable : responseDefaultNonNullable } ,
460+ )
461+ errorType = schemaType
462+ }
463+ }
464+ }
465+ }
466+ }
467+ }
468+ if ( errorType !== undefined ) {
469+ endpoint . error = errorType
470+ }
471+
348472 // Generate path key (normalize path by replacing {param} with converted param and removing slashes)
349473 const normalizedPath = path . replace ( / \{ ( [ ^ } ] + ) \} / g, ( _ , param ) => {
350474 // Convert param name based on case type
@@ -367,6 +491,7 @@ export function generateInterface(
367491 // Extract components schemas
368492 const requestComponents : Record < string , unknown > = { }
369493 const responseComponents : Record < string , unknown > = { }
494+ const errorComponents : Record < string , unknown > = { }
370495 if ( schema . components ?. schemas ) {
371496 for ( const [ schemaName , schemaObj ] of Object . entries (
372497 schema . components . schemas ,
@@ -377,12 +502,16 @@ export function generateInterface(
377502 const responseDefaultNonNullable =
378503 options ?. responseDefaultNonNullable ?? true
379504
505+ // Determine which defaultNonNullable to use based on where schema is used
506+ let defaultNonNullable = responseDefaultNonNullable
507+ if ( requestSchemaNames . has ( schemaName ) ) {
508+ defaultNonNullable = requestDefaultNonNullable
509+ }
510+
380511 const { type : schemaType } = getTypeFromSchema (
381512 schemaObj as OpenAPIV3_1 . SchemaObject | OpenAPIV3_1 . ReferenceObject ,
382513 schema ,
383- requestSchemaNames . has ( schemaName )
384- ? { defaultNonNullable : requestDefaultNonNullable }
385- : { defaultNonNullable : responseDefaultNonNullable } ,
514+ { defaultNonNullable } ,
386515 )
387516 // Keep original schema name as-is
388517 if ( requestSchemaNames . has ( schemaName ) ) {
@@ -391,6 +520,9 @@ export function generateInterface(
391520 if ( responseSchemaNames . has ( schemaName ) ) {
392521 responseComponents [ schemaName ] = schemaType
393522 }
523+ if ( errorSchemaNames . has ( schemaName ) ) {
524+ errorComponents [ schemaName ] = schemaType
525+ }
394526 }
395527 }
396528 }
@@ -443,9 +575,22 @@ export function generateInterface(
443575 ? ` interface DevupResponseComponentStruct {\n${ responseComponentEntries } ;\n }`
444576 : ' interface DevupResponseComponentStruct {}'
445577
578+ // Generate ErrorComponentStruct interface
579+ const errorComponentEntries = Object . entries ( errorComponents )
580+ . map ( ( [ key , value ] ) => {
581+ const formattedValue = formatTypeValue ( value , 2 )
582+ return ` ${ wrapInterfaceKeyGuard ( key ) } : ${ formattedValue } `
583+ } )
584+ . join ( ';\n' )
585+
586+ const errorComponentInterface =
587+ errorComponentEntries . length > 0
588+ ? ` interface DevupErrorComponentStruct {\n${ errorComponentEntries } ;\n }`
589+ : ' interface DevupErrorComponentStruct {}'
590+
446591 const allInterfaces = interfaceContent
447- ? `${ interfaceContent } \n\n${ requestComponentInterface } \n\n${ responseComponentInterface } `
448- : `${ requestComponentInterface } \n\n${ responseComponentInterface } `
592+ ? `${ interfaceContent } \n\n${ requestComponentInterface } \n\n${ responseComponentInterface } \n\n ${ errorComponentInterface } `
593+ : `${ requestComponentInterface } \n\n${ responseComponentInterface } \n\n ${ errorComponentInterface } `
449594
450595 return `import "@devup-api/fetch";\n\ndeclare module "@devup-api/fetch" {\n${ allInterfaces } \n}`
451596}
0 commit comments