77 extractParameters ,
88 extractRequestBody ,
99 formatTypeValue ,
10+ getTypeFromSchema ,
1011} from './generate-schema'
1112import { wrapInterfaceKeyGuard } from './wrap-interface-key-guard'
1213
@@ -40,6 +41,118 @@ export function generateInterface(
4041 } as const
4142 const convertCaseType = options ?. convertCase ?? 'camel'
4243
44+ // Helper function to extract schema names from $ref
45+ const extractSchemaNameFromRef = ( ref : string ) : string | null => {
46+ if ( ref . startsWith ( '#/components/schemas/' ) ) {
47+ return ref . replace ( '#/components/schemas/' , '' )
48+ }
49+ return null
50+ }
51+
52+ // Helper function to collect schema names from a schema object
53+ const collectSchemaNames = (
54+ schemaObj : OpenAPIV3_1 . SchemaObject | OpenAPIV3_1 . ReferenceObject ,
55+ targetSet : Set < string > ,
56+ ) : void => {
57+ if ( '$ref' in schemaObj ) {
58+ const schemaName = extractSchemaNameFromRef ( schemaObj . $ref )
59+ if ( schemaName ) {
60+ targetSet . add ( schemaName )
61+ }
62+ return
63+ }
64+
65+ const schema = schemaObj as OpenAPIV3_1 . SchemaObject
66+
67+ // Check allOf, anyOf, oneOf
68+ if ( schema . allOf ) {
69+ schema . allOf . forEach ( ( s ) => {
70+ collectSchemaNames ( s , targetSet )
71+ } )
72+ }
73+ if ( schema . anyOf ) {
74+ schema . anyOf . forEach ( ( s ) => {
75+ collectSchemaNames ( s , targetSet )
76+ } )
77+ }
78+ if ( schema . oneOf ) {
79+ schema . oneOf . forEach ( ( s ) => {
80+ collectSchemaNames ( s , targetSet )
81+ } )
82+ }
83+
84+ // Check properties
85+ if ( schema . properties ) {
86+ Object . values ( schema . properties ) . forEach ( ( prop ) => {
87+ collectSchemaNames ( prop , targetSet )
88+ } )
89+ }
90+
91+ // Check items (for arrays)
92+ if ( schema . type === 'array' && 'items' in schema && schema . items ) {
93+ collectSchemaNames ( schema . items , targetSet )
94+ }
95+ }
96+
97+ // Track which schemas are used in request body and responses
98+ const requestSchemaNames = new Set < string > ( )
99+ const responseSchemaNames = new Set < string > ( )
100+
101+ // First, collect schema names used in request body and responses
102+ if ( schema . paths ) {
103+ for ( const pathItem of Object . values ( schema . paths ) ) {
104+ if ( ! pathItem ) continue
105+
106+ const methods = [ 'get' , 'post' , 'put' , 'delete' , 'patch' ] as const
107+ for ( const method of methods ) {
108+ const operation = pathItem [ method ]
109+ if ( ! operation ) continue
110+
111+ // Collect request body schemas
112+ if ( operation . requestBody ) {
113+ if ( '$ref' in operation . requestBody ) {
114+ // Extract schema name from $ref if it's a schema reference
115+ const schemaName = extractSchemaNameFromRef (
116+ operation . requestBody . $ref ,
117+ )
118+ if ( schemaName ) {
119+ requestSchemaNames . add ( schemaName )
120+ }
121+ } else {
122+ const content = operation . requestBody . content
123+ const jsonContent = content ?. [ 'application/json' ]
124+ if ( jsonContent && 'schema' in jsonContent && jsonContent . schema ) {
125+ collectSchemaNames ( jsonContent . schema , requestSchemaNames )
126+ }
127+ }
128+ }
129+
130+ // Collect response schemas
131+ if ( operation . responses ) {
132+ for ( const response of Object . values ( operation . responses ) ) {
133+ if ( '$ref' in response ) {
134+ // Extract schema name from $ref if it's a schema reference
135+ const schemaName = extractSchemaNameFromRef ( response . $ref )
136+ if ( schemaName ) {
137+ responseSchemaNames . add ( schemaName )
138+ }
139+ } else if ( 'content' in response ) {
140+ const content = response . content
141+ const jsonContent = content ?. [ 'application/json' ]
142+ if (
143+ jsonContent &&
144+ 'schema' in jsonContent &&
145+ jsonContent . schema
146+ ) {
147+ collectSchemaNames ( jsonContent . schema , responseSchemaNames )
148+ }
149+ }
150+ }
151+ }
152+ }
153+ }
154+ }
155+
43156 // Iterate through OpenAPI paths and extract each endpoint
44157 if ( schema . paths ) {
45158 for ( const [ path , pathItem ] of Object . entries ( schema . paths ) ) {
@@ -82,9 +195,58 @@ export function generateInterface(
82195 }
83196
84197 // Extract request body
85- const requestBody = extractRequestBody ( operation . requestBody , schema )
86- if ( requestBody !== undefined ) {
87- endpoint . body = requestBody
198+ // Check if request body uses a component schema
199+ let requestBodyType : unknown
200+ if ( operation . requestBody ) {
201+ if ( '$ref' in operation . requestBody ) {
202+ // RequestBodyObject reference - skip for now
203+ const requestBody = extractRequestBody (
204+ operation . requestBody ,
205+ schema ,
206+ )
207+ if ( requestBody !== undefined ) {
208+ requestBodyType = requestBody
209+ }
210+ } else {
211+ const content = operation . requestBody . content
212+ const jsonContent = content ?. [ 'application/json' ]
213+ if ( jsonContent && 'schema' in jsonContent && jsonContent . schema ) {
214+ // Check if schema is a direct reference to components.schemas
215+ if ( '$ref' in jsonContent . schema ) {
216+ const schemaName = extractSchemaNameFromRef (
217+ jsonContent . schema . $ref ,
218+ )
219+ // Check if schema exists in components.schemas and is used in request body
220+ if (
221+ schemaName &&
222+ schema . components ?. schemas ?. [ schemaName ] &&
223+ requestSchemaNames . has ( schemaName )
224+ ) {
225+ // Use component reference
226+ requestBodyType = `DevupRequestComponentStruct['${ schemaName } ']`
227+ } else {
228+ const requestBody = extractRequestBody (
229+ operation . requestBody ,
230+ schema ,
231+ )
232+ if ( requestBody !== undefined ) {
233+ requestBodyType = requestBody
234+ }
235+ }
236+ } else {
237+ const requestBody = extractRequestBody (
238+ operation . requestBody ,
239+ schema ,
240+ )
241+ if ( requestBody !== undefined ) {
242+ requestBodyType = requestBody
243+ }
244+ }
245+ }
246+ }
247+ }
248+ if ( requestBodyType !== undefined ) {
249+ endpoint . body = requestBodyType
88250 }
89251
90252 // Generate path key (normalize path by replacing {param} with converted param and removing slashes)
@@ -106,6 +268,29 @@ export function generateInterface(
106268 }
107269 }
108270
271+ // Extract components schemas
272+ const requestComponents : Record < string , unknown > = { }
273+ const responseComponents : Record < string , unknown > = { }
274+ if ( schema . components ?. schemas ) {
275+ for ( const [ schemaName , schemaObj ] of Object . entries (
276+ schema . components . schemas ,
277+ ) ) {
278+ if ( schemaObj ) {
279+ const { type : schemaType } = getTypeFromSchema (
280+ schemaObj as OpenAPIV3_1 . SchemaObject | OpenAPIV3_1 . ReferenceObject ,
281+ schema ,
282+ )
283+ // Keep original schema name as-is
284+ if ( requestSchemaNames . has ( schemaName ) ) {
285+ requestComponents [ schemaName ] = schemaType
286+ }
287+ if ( responseSchemaNames . has ( schemaName ) ) {
288+ responseComponents [ schemaName ] = schemaType
289+ }
290+ }
291+ }
292+ }
293+
109294 // Generate TypeScript interface string
110295 const interfaceContent = Object . entries ( endpoints )
111296 . flatMap ( ( [ method , value ] ) => {
@@ -133,5 +318,35 @@ export function generateInterface(
133318 } )
134319 . join ( '\n' )
135320
136- return `import "@devup-api/fetch";\n\ndeclare module "@devup-api/fetch" {\n${ interfaceContent } \n}`
321+ // Generate RequestComponentStruct interface
322+ const requestComponentEntries = Object . entries ( requestComponents )
323+ . map ( ( [ key , value ] ) => {
324+ const formattedValue = formatTypeValue ( value , 2 )
325+ return ` ${ wrapInterfaceKeyGuard ( key ) } : ${ formattedValue } `
326+ } )
327+ . join ( ';\n' )
328+
329+ const requestComponentInterface =
330+ requestComponentEntries . length > 0
331+ ? ` interface DevupRequestComponentStruct {\n${ requestComponentEntries } ;\n }`
332+ : ' interface DevupRequestComponentStruct {}'
333+
334+ // Generate ResponseComponentStruct interface
335+ const responseComponentEntries = Object . entries ( responseComponents )
336+ . map ( ( [ key , value ] ) => {
337+ const formattedValue = formatTypeValue ( value , 2 )
338+ return ` ${ wrapInterfaceKeyGuard ( key ) } : ${ formattedValue } `
339+ } )
340+ . join ( ';\n' )
341+
342+ const responseComponentInterface =
343+ responseComponentEntries . length > 0
344+ ? ` interface DevupResponseComponentStruct {\n${ responseComponentEntries } ;\n }`
345+ : ' interface DevupResponseComponentStruct {}'
346+
347+ const allInterfaces = interfaceContent
348+ ? `${ interfaceContent } \n\n${ requestComponentInterface } \n\n${ responseComponentInterface } `
349+ : `${ requestComponentInterface } \n\n${ responseComponentInterface } `
350+
351+ return `import "@devup-api/fetch";\n\ndeclare module "@devup-api/fetch" {\n${ allInterfaces } \n}`
137352}
0 commit comments