1
+ import { OpenAPIV3 } from 'openapi-types' ;
2
+
3
+ interface OperationInfo {
4
+ method : string ;
5
+ path : string ;
6
+ operationId : string ;
7
+ summary ?: string ;
8
+ description ?: string ;
9
+ parameters ?: OpenAPIV3 . ParameterObject [ ] ;
10
+ requestBody ?: OpenAPIV3 . RequestBodyObject ;
11
+ responses : OpenAPIV3 . ResponsesObject ;
12
+ }
13
+
14
+ function generateAxiosMethod ( operation : OperationInfo ) : string {
15
+ const { method, path, operationId, summary, description, parameters, requestBody, responses } = operation ;
16
+
17
+ // Generate JSDoc
18
+ const jsDocLines = [ '/**' ] ;
19
+ if ( summary ) jsDocLines . push ( ` * ${ summary } ` ) ;
20
+ if ( description ) jsDocLines . push ( ` * ${ description } ` ) ;
21
+
22
+ // Add parameter descriptions
23
+ parameters ?. forEach ( param => {
24
+ const desc = param . description ? ` - ${ param . description } ` : '' ;
25
+ jsDocLines . push ( ` * @param ${ param . in === 'path' ? 'params.' : param . in === 'query' ? 'query.' : '' } ${ param . name } ${ desc } ` ) ;
26
+ } ) ;
27
+
28
+ if ( requestBody && 'description' in requestBody ) {
29
+ jsDocLines . push ( ` * @param data - ${ requestBody . description } ` ) ;
30
+ }
31
+
32
+ // Add return type description
33
+ const responseDetails = Object . entries ( responses ) . find ( ( [ code ] ) => code . startsWith ( '2' ) ) ;
34
+ if ( responseDetails ) {
35
+ const [ code , response ] = responseDetails ;
36
+ const responseObj = response as OpenAPIV3 . ResponseObject ;
37
+ const desc = 'description' in responseObj ? responseObj . description : '' ;
38
+ const contentType = responseObj . content ?. [ 'application/json' ] ?. schema ;
39
+ const typeName = `${ operationId } Response${ code } ` ;
40
+
41
+ if ( contentType ) {
42
+ if ( desc ) {
43
+ jsDocLines . push ( ` * @returns ${ desc } ` ) ;
44
+ }
45
+ jsDocLines . push ( ` * @see ${ typeName } ` ) ;
46
+ } else if ( desc ) {
47
+ jsDocLines . push ( ` * @returns ${ desc } ` ) ;
48
+ }
49
+ }
50
+
51
+ jsDocLines . push ( ' */' ) ;
52
+
53
+ // Generate method parameters
54
+ const params : string [ ] = [ ] ;
55
+ const urlParams = parameters ?. filter ( p => p . in === 'path' ) || [ ] ;
56
+ const queryParams = parameters ?. filter ( p => p . in === 'query' ) || [ ] ;
57
+
58
+ // Add request body parameter if it exists
59
+ if ( requestBody ) {
60
+ params . push ( `data: ${ operationId } Request` ) ;
61
+ }
62
+
63
+ // Add path parameters
64
+ if ( urlParams . length > 0 ) {
65
+ params . push ( `params: { ${
66
+ urlParams . map ( p => `${ p . name } : ${ getTypeFromParam ( p ) } ` ) . join ( ', ' )
67
+ } }`) ;
68
+ }
69
+
70
+ // Add query parameters
71
+ if ( queryParams . length > 0 ) {
72
+ params . push ( `query?: { ${
73
+ queryParams . map ( p => `${ p . name } ${ p . required ? '' : '?' } : ${ getTypeFromParam ( p ) } ` ) . join ( ', ' )
74
+ } }`) ;
75
+ }
76
+
77
+ // Add headers parameter
78
+ params . push ( 'headers?: Record<string, string>' ) ;
79
+
80
+ // Get response type from 2xx response
81
+ const successResponse = Object . entries ( responses ) . find ( ( [ code ] ) => code . startsWith ( '2' ) ) ;
82
+ const responseType = successResponse ? `${ operationId } Response${ successResponse [ 0 ] } ` : 'any' ;
83
+
84
+ // Generate method
85
+ const urlWithParams = urlParams . length > 0
86
+ ? path . replace ( / { ( \w + ) } / g, '${params.$1}' )
87
+ : path ;
88
+
89
+ const methodBody = [
90
+ `const url = \`${ urlWithParams } \`;` ,
91
+ queryParams . length > 0 ? 'const queryString = query ? `?${new URLSearchParams(query)}` : \'\';' : '' ,
92
+ `return this.axios.${ method . toLowerCase ( ) } <${ responseType } >(url${ queryParams . length > 0 ? ' + queryString' : '' } , {
93
+ ${ requestBody ? 'data,' : '' }
94
+ headers
95
+ });`
96
+ ] . filter ( Boolean ) . join ( '\n ' ) ;
97
+
98
+ return `
99
+ ${ jsDocLines . join ( '\n ' ) }
100
+ async ${ operationId } (${ params . join ( ', ' ) } ): Promise<AxiosResponse<${ responseType } >> {
101
+ ${ methodBody }
102
+ }` ;
103
+ }
104
+
105
+ function getTypeFromParam ( param : OpenAPIV3 . ParameterObject ) : string {
106
+ if ( 'schema' in param ) {
107
+ const schema = param . schema as OpenAPIV3 . SchemaObject ;
108
+ switch ( schema . type ) {
109
+ case 'string' : return 'string' ;
110
+ case 'integer' :
111
+ case 'number' : return 'number' ;
112
+ case 'boolean' : return 'boolean' ;
113
+ case 'array' : return 'Array<any>' ; // You might want to make this more specific
114
+ default : return 'any' ;
115
+ }
116
+ }
117
+ return 'any' ;
118
+ }
119
+
120
+ export function generateApiClient ( spec : OpenAPIV3 . Document ) : string {
121
+ let operations : OperationInfo [ ] = [ ] ;
122
+
123
+ // Collect all operations
124
+ Object . entries ( spec . paths || { } ) . forEach ( ( [ path , pathItem ] ) => {
125
+ if ( ! pathItem ) return ;
126
+
127
+ [ 'get' , 'post' , 'put' , 'delete' , 'patch' ] . forEach ( method => {
128
+ const operation = pathItem [ method as keyof OpenAPIV3 . PathItemObject ] as OpenAPIV3 . OperationObject ;
129
+ if ( ! operation ) return ;
130
+
131
+ operations . push ( {
132
+ method : method . toUpperCase ( ) ,
133
+ path,
134
+ operationId : operation . operationId || `${ method } ${ path . replace ( / \W + / g, '_' ) } ` ,
135
+ summary : operation . summary ,
136
+ description : operation . description ,
137
+ parameters : [ ...( pathItem . parameters || [ ] ) , ...( operation . parameters || [ ] ) ] as OpenAPIV3 . ParameterObject [ ] ,
138
+ requestBody : operation . requestBody as OpenAPIV3 . RequestBodyObject ,
139
+ responses : operation . responses
140
+ } ) ;
141
+ } ) ;
142
+ } ) ;
143
+
144
+ // Collect actually used types
145
+ const usedTypes = new Set < string > ( ) ;
146
+ operations . forEach ( op => {
147
+ // Add request type if method has request body
148
+ if ( op . requestBody ) {
149
+ usedTypes . add ( `${ op . operationId } Request` ) ;
150
+ }
151
+ // Add only the 2xx response type that's used
152
+ const successResponse = Object . entries ( op . responses ) . find ( ( [ code ] ) => code . startsWith ( '2' ) ) ;
153
+ if ( successResponse ) {
154
+ usedTypes . add ( `${ op . operationId } Response${ successResponse [ 0 ] } ` ) ;
155
+ }
156
+ } ) ;
157
+
158
+ // Generate the client class
159
+ return `import axios, { AxiosInstance, AxiosResponse } from 'axios';
160
+ import {
161
+ ${ Array . from ( usedTypes ) . join ( ',\n ' ) }
162
+ } from './types';
163
+
164
+ export class ApiClient {
165
+ private axios: AxiosInstance;
166
+
167
+ constructor(baseURL: string, headers?: Record<string, string>) {
168
+ this.axios = axios.create({
169
+ baseURL,
170
+ headers: {
171
+ 'Content-Type': 'application/json',
172
+ ...headers
173
+ }
174
+ });
175
+ }
176
+ ${ operations . map ( op => generateAxiosMethod ( op ) ) . join ( '\n\n' ) }
177
+ }
178
+ ` ;
179
+ }
0 commit comments