@acrool/rtk-query-codegen-openapi 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -0
- package/lib/bin/cli.mjs +3 -0
- package/lib/bin/cli.mjs.map +1 -0
- package/lib/index.d.mts +141 -0
- package/lib/index.d.ts +141 -0
- package/lib/index.js +877 -0
- package/lib/index.js.map +1 -0
- package/lib/index.mjs +850 -0
- package/lib/index.mjs.map +1 -0
- package/package.json +90 -0
- package/src/bin/cli.ts +66 -0
- package/src/codegen.ts +175 -0
- package/src/generate.ts +627 -0
- package/src/generators/react-hooks.ts +93 -0
- package/src/index.ts +68 -0
- package/src/types.ts +150 -0
- package/src/utils/capitalize.ts +3 -0
- package/src/utils/factory.ts +29 -0
- package/src/utils/getOperationDefinitions.ts +20 -0
- package/src/utils/getV3Doc.ts +24 -0
- package/src/utils/index.ts +8 -0
- package/src/utils/isQuery.ts +8 -0
- package/src/utils/isValidUrl.ts +9 -0
- package/src/utils/messages.ts +7 -0
- package/src/utils/prettier.ts +46 -0
- package/src/utils/removeUndefined.ts +3 -0
package/src/generate.ts
ADDED
|
@@ -0,0 +1,627 @@
|
|
|
1
|
+
import camelCase from 'lodash.camelcase';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import ApiGenerator, {
|
|
4
|
+
getOperationName as _getOperationName,
|
|
5
|
+
getReferenceName,
|
|
6
|
+
isReference,
|
|
7
|
+
supportDeepObjects,
|
|
8
|
+
createPropertyAssignment,
|
|
9
|
+
createQuestionToken,
|
|
10
|
+
isValidIdentifier,
|
|
11
|
+
keywordType,
|
|
12
|
+
} from 'oazapfts/generate';
|
|
13
|
+
import type { OpenAPIV3 } from 'openapi-types';
|
|
14
|
+
import ts from 'typescript';
|
|
15
|
+
import type { ObjectPropertyDefinitions } from './codegen';
|
|
16
|
+
import { generateCreateApiCall, generateEndpointDefinition, generateImportNode, generateTagTypes } from './codegen';
|
|
17
|
+
import { generateReactHooks } from './generators/react-hooks';
|
|
18
|
+
import type {
|
|
19
|
+
EndpointMatcher,
|
|
20
|
+
EndpointOverrides,
|
|
21
|
+
GenerationOptions,
|
|
22
|
+
OperationDefinition,
|
|
23
|
+
ParameterDefinition,
|
|
24
|
+
ParameterMatcher,
|
|
25
|
+
TextMatcher,
|
|
26
|
+
} from './types';
|
|
27
|
+
import { capitalize, getOperationDefinitions, getV3Doc, removeUndefined, isQuery as testIsQuery } from './utils';
|
|
28
|
+
import { factory } from './utils/factory';
|
|
29
|
+
|
|
30
|
+
const generatedApiName = 'injectedRtkApi';
|
|
31
|
+
const v3DocCache: Record<string, OpenAPIV3.Document> = {};
|
|
32
|
+
|
|
33
|
+
// Add IUseFetcherArgs import
|
|
34
|
+
const useFetcherImport = generateImportNode('@acrool/react-fetcher', {
|
|
35
|
+
IUseFetcherArgs: 'IUseFetcherArgs',
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
function defaultIsDataResponse(
|
|
39
|
+
code: string,
|
|
40
|
+
includeDefault: boolean,
|
|
41
|
+
response: OpenAPIV3.ResponseObject,
|
|
42
|
+
allResponses: OpenAPIV3.ResponsesObject
|
|
43
|
+
) {
|
|
44
|
+
if (includeDefault && code === 'default') {
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
const parsedCode = Number(code);
|
|
48
|
+
return !Number.isNaN(parsedCode) && parsedCode >= 200 && parsedCode < 300;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function getOperationName({ verb, path, operation }: Pick<OperationDefinition, 'verb' | 'path' | 'operation'>) {
|
|
52
|
+
return _getOperationName(verb, path, operation.operationId);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function getTags({ verb, pathItem }: Pick<OperationDefinition, 'verb' | 'pathItem'>): string[] {
|
|
56
|
+
const tags = verb && pathItem[verb]?.tags ? pathItem[verb].tags : [];
|
|
57
|
+
return tags.map((tag) => tag.toString());
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function patternMatches(pattern?: TextMatcher) {
|
|
61
|
+
const filters = Array.isArray(pattern) ? pattern : [pattern];
|
|
62
|
+
return function matcher(operationName: string) {
|
|
63
|
+
if (!pattern) return true;
|
|
64
|
+
return filters.some((filter) =>
|
|
65
|
+
typeof filter === 'string' ? filter === operationName : filter?.test(operationName)
|
|
66
|
+
);
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function operationMatches(pattern?: EndpointMatcher) {
|
|
71
|
+
const checkMatch = typeof pattern === 'function' ? pattern : patternMatches(pattern);
|
|
72
|
+
return function matcher(operationDefinition: OperationDefinition) {
|
|
73
|
+
if (!pattern) return true;
|
|
74
|
+
const operationName = getOperationName(operationDefinition);
|
|
75
|
+
return checkMatch(operationName, operationDefinition);
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function argumentMatches(pattern?: ParameterMatcher) {
|
|
80
|
+
const checkMatch = typeof pattern === 'function' ? pattern : patternMatches(pattern);
|
|
81
|
+
return function matcher(argumentDefinition: ParameterDefinition) {
|
|
82
|
+
if (!pattern || argumentDefinition.in === 'path') return true;
|
|
83
|
+
const argumentName = argumentDefinition.name;
|
|
84
|
+
return checkMatch(argumentName, argumentDefinition);
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function withQueryComment<T extends ts.Node>(node: T, def: QueryArgDefinition, hasTrailingNewLine: boolean): T {
|
|
89
|
+
const comment = def.origin === 'param' ? def.param.description : def.body.description;
|
|
90
|
+
if (comment) {
|
|
91
|
+
return ts.addSyntheticLeadingComment(
|
|
92
|
+
node,
|
|
93
|
+
ts.SyntaxKind.MultiLineCommentTrivia,
|
|
94
|
+
`* ${comment} `,
|
|
95
|
+
hasTrailingNewLine
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
return node;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function getOverrides(
|
|
102
|
+
operation: OperationDefinition,
|
|
103
|
+
endpointOverrides?: EndpointOverrides[]
|
|
104
|
+
): EndpointOverrides | undefined {
|
|
105
|
+
return endpointOverrides?.find((override) => operationMatches(override.pattern)(operation));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export async function generateApi(
|
|
109
|
+
spec: string,
|
|
110
|
+
{
|
|
111
|
+
apiFile,
|
|
112
|
+
apiImport = 'api',
|
|
113
|
+
exportName = 'enhancedApi',
|
|
114
|
+
argSuffix = 'ApiArg',
|
|
115
|
+
responseSuffix = 'ApiResponse',
|
|
116
|
+
operationNameSuffix = '',
|
|
117
|
+
hooks = false,
|
|
118
|
+
tag = false,
|
|
119
|
+
outputFile,
|
|
120
|
+
isDataResponse = defaultIsDataResponse,
|
|
121
|
+
filterEndpoints,
|
|
122
|
+
endpointOverrides,
|
|
123
|
+
unionUndefined,
|
|
124
|
+
encodePathParams = false,
|
|
125
|
+
encodeQueryParams = false,
|
|
126
|
+
flattenArg = false,
|
|
127
|
+
includeDefault = false,
|
|
128
|
+
useEnumType = false,
|
|
129
|
+
mergeReadWriteOnly = false,
|
|
130
|
+
httpResolverOptions,
|
|
131
|
+
}: GenerationOptions
|
|
132
|
+
) {
|
|
133
|
+
const v3Doc = (v3DocCache[spec] ??= await getV3Doc(spec, httpResolverOptions));
|
|
134
|
+
|
|
135
|
+
const apiGen = new ApiGenerator(v3Doc, {
|
|
136
|
+
unionUndefined,
|
|
137
|
+
useEnumType,
|
|
138
|
+
mergeReadWriteOnly,
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// temporary workaround for https://github.com/oazapfts/oazapfts/issues/491
|
|
142
|
+
if (apiGen.spec.components?.schemas) {
|
|
143
|
+
apiGen.preprocessComponents(apiGen.spec.components.schemas);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const operationDefinitions = getOperationDefinitions(v3Doc).filter(operationMatches(filterEndpoints));
|
|
147
|
+
|
|
148
|
+
const resultFile = ts.createSourceFile(
|
|
149
|
+
'someFileName.ts',
|
|
150
|
+
'',
|
|
151
|
+
ts.ScriptTarget.Latest,
|
|
152
|
+
/*setParentNodes*/ false,
|
|
153
|
+
ts.ScriptKind.TS
|
|
154
|
+
);
|
|
155
|
+
const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
|
|
156
|
+
|
|
157
|
+
const interfaces: Record<string, ts.InterfaceDeclaration | ts.TypeAliasDeclaration> = {};
|
|
158
|
+
function registerInterface(declaration: ts.InterfaceDeclaration | ts.TypeAliasDeclaration) {
|
|
159
|
+
const name = declaration.name.escapedText.toString();
|
|
160
|
+
if (name in interfaces) {
|
|
161
|
+
throw new Error(`interface/type alias ${name} already registered`);
|
|
162
|
+
}
|
|
163
|
+
interfaces[name] = declaration;
|
|
164
|
+
return declaration;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (outputFile) {
|
|
168
|
+
outputFile = path.resolve(process.cwd(), outputFile);
|
|
169
|
+
if (apiFile.startsWith('.')) {
|
|
170
|
+
apiFile = path.relative(path.dirname(outputFile), apiFile);
|
|
171
|
+
apiFile = apiFile.replace(/\\/g, '/');
|
|
172
|
+
if (!apiFile.startsWith('.')) apiFile = `./${apiFile}`;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
apiFile = apiFile.replace(/\.[jt]sx?$/, '');
|
|
176
|
+
|
|
177
|
+
return printer.printNode(
|
|
178
|
+
ts.EmitHint.Unspecified,
|
|
179
|
+
factory.createSourceFile(
|
|
180
|
+
[
|
|
181
|
+
generateImportNode(apiFile, { [apiImport]: 'api' }),
|
|
182
|
+
...(tag ? [generateTagTypes({ addTagTypes: extractAllTagTypes({ operationDefinitions }) })] : []),
|
|
183
|
+
generateCreateApiCall({
|
|
184
|
+
tag,
|
|
185
|
+
endpointDefinitions: factory.createObjectLiteralExpression(
|
|
186
|
+
operationDefinitions.map((operationDefinition) =>
|
|
187
|
+
generateEndpoint({
|
|
188
|
+
operationDefinition,
|
|
189
|
+
overrides: getOverrides(operationDefinition, endpointOverrides),
|
|
190
|
+
})
|
|
191
|
+
),
|
|
192
|
+
true
|
|
193
|
+
),
|
|
194
|
+
}),
|
|
195
|
+
factory.createExportDeclaration(
|
|
196
|
+
undefined,
|
|
197
|
+
false,
|
|
198
|
+
factory.createNamedExports([
|
|
199
|
+
factory.createExportSpecifier(
|
|
200
|
+
factory.createIdentifier(generatedApiName),
|
|
201
|
+
factory.createIdentifier(exportName)
|
|
202
|
+
),
|
|
203
|
+
]),
|
|
204
|
+
undefined
|
|
205
|
+
),
|
|
206
|
+
...Object.values(interfaces),
|
|
207
|
+
...apiGen.aliases,
|
|
208
|
+
...apiGen.enumAliases,
|
|
209
|
+
...(hooks
|
|
210
|
+
? [
|
|
211
|
+
generateReactHooks({
|
|
212
|
+
exportName: generatedApiName,
|
|
213
|
+
operationDefinitions,
|
|
214
|
+
endpointOverrides,
|
|
215
|
+
config: hooks,
|
|
216
|
+
}),
|
|
217
|
+
]
|
|
218
|
+
: []),
|
|
219
|
+
],
|
|
220
|
+
factory.createToken(ts.SyntaxKind.EndOfFileToken),
|
|
221
|
+
ts.NodeFlags.None
|
|
222
|
+
),
|
|
223
|
+
resultFile
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
function extractAllTagTypes({ operationDefinitions }: { operationDefinitions: OperationDefinition[] }) {
|
|
227
|
+
const allTagTypes = new Set<string>();
|
|
228
|
+
|
|
229
|
+
for (const operationDefinition of operationDefinitions) {
|
|
230
|
+
const { verb, pathItem } = operationDefinition;
|
|
231
|
+
for (const tag of getTags({ verb, pathItem })) {
|
|
232
|
+
allTagTypes.add(tag);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return [...allTagTypes];
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function generateQueryArgType(operationDefinition: OperationDefinition): ts.TypeNode {
|
|
239
|
+
const {
|
|
240
|
+
operation: { parameters = [], requestBody },
|
|
241
|
+
} = operationDefinition;
|
|
242
|
+
|
|
243
|
+
const properties: ts.PropertySignature[] = [];
|
|
244
|
+
|
|
245
|
+
// Add body property if needed
|
|
246
|
+
if (requestBody && !isReference(requestBody)) {
|
|
247
|
+
const bodySchema = requestBody.content?.['application/json']?.schema;
|
|
248
|
+
if (bodySchema) {
|
|
249
|
+
properties.push(
|
|
250
|
+
factory.createPropertySignature(
|
|
251
|
+
undefined,
|
|
252
|
+
factory.createIdentifier('body'),
|
|
253
|
+
undefined,
|
|
254
|
+
factory.createTypeReferenceNode(factory.createIdentifier('any'), undefined)
|
|
255
|
+
)
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Add params property if needed
|
|
261
|
+
const queryParams = parameters.filter((p): p is OpenAPIV3.ParameterObject => !isReference(p) && p.in === 'query');
|
|
262
|
+
if (queryParams.length > 0) {
|
|
263
|
+
properties.push(
|
|
264
|
+
factory.createPropertySignature(
|
|
265
|
+
undefined,
|
|
266
|
+
factory.createIdentifier('params'),
|
|
267
|
+
undefined,
|
|
268
|
+
factory.createTypeLiteralNode(
|
|
269
|
+
queryParams.map((param) =>
|
|
270
|
+
factory.createPropertySignature(
|
|
271
|
+
undefined,
|
|
272
|
+
factory.createIdentifier(param.name),
|
|
273
|
+
factory.createToken(ts.SyntaxKind.QuestionToken),
|
|
274
|
+
factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword)
|
|
275
|
+
)
|
|
276
|
+
)
|
|
277
|
+
)
|
|
278
|
+
)
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Add headers property if needed
|
|
283
|
+
const headerParams = parameters.filter((p): p is OpenAPIV3.ParameterObject => !isReference(p) && p.in === 'header');
|
|
284
|
+
if (headerParams.length > 0) {
|
|
285
|
+
properties.push(
|
|
286
|
+
factory.createPropertySignature(
|
|
287
|
+
undefined,
|
|
288
|
+
factory.createIdentifier('headers'),
|
|
289
|
+
undefined,
|
|
290
|
+
factory.createTypeLiteralNode(
|
|
291
|
+
headerParams.map((param) =>
|
|
292
|
+
factory.createPropertySignature(
|
|
293
|
+
undefined,
|
|
294
|
+
factory.createIdentifier(param.name),
|
|
295
|
+
factory.createToken(ts.SyntaxKind.QuestionToken),
|
|
296
|
+
factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword)
|
|
297
|
+
)
|
|
298
|
+
)
|
|
299
|
+
)
|
|
300
|
+
)
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return factory.createTypeLiteralNode([
|
|
305
|
+
factory.createPropertySignature(
|
|
306
|
+
undefined,
|
|
307
|
+
factory.createIdentifier('variables'),
|
|
308
|
+
undefined,
|
|
309
|
+
factory.createTypeLiteralNode(properties)
|
|
310
|
+
),
|
|
311
|
+
factory.createPropertySignature(
|
|
312
|
+
undefined,
|
|
313
|
+
factory.createIdentifier('fetchOptions'),
|
|
314
|
+
factory.createToken(ts.SyntaxKind.QuestionToken),
|
|
315
|
+
factory.createTypeReferenceNode(factory.createIdentifier('any'), undefined)
|
|
316
|
+
),
|
|
317
|
+
]);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function generateQueryArgDefinitions(operationDefinition: OperationDefinition): QueryArgDefinitions {
|
|
321
|
+
const {
|
|
322
|
+
operation: { parameters = [], requestBody },
|
|
323
|
+
} = operationDefinition;
|
|
324
|
+
|
|
325
|
+
const queryArg: QueryArgDefinitions = {};
|
|
326
|
+
|
|
327
|
+
// Add body definition if needed
|
|
328
|
+
if (requestBody && !isReference(requestBody)) {
|
|
329
|
+
const bodySchema = requestBody.content?.['application/json']?.schema;
|
|
330
|
+
if (bodySchema) {
|
|
331
|
+
queryArg['body'] = {
|
|
332
|
+
name: 'body',
|
|
333
|
+
originalName: 'body',
|
|
334
|
+
type: factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword),
|
|
335
|
+
required: true,
|
|
336
|
+
origin: 'body',
|
|
337
|
+
body: requestBody,
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Add parameter definitions
|
|
343
|
+
parameters
|
|
344
|
+
.filter((p): p is OpenAPIV3.ParameterObject => !isReference(p))
|
|
345
|
+
.forEach((param) => {
|
|
346
|
+
queryArg[param.name] = {
|
|
347
|
+
name: param.name,
|
|
348
|
+
originalName: param.name,
|
|
349
|
+
type: factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
|
|
350
|
+
required: param.required || false,
|
|
351
|
+
origin: 'param',
|
|
352
|
+
param,
|
|
353
|
+
};
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
return queryArg;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function generateEndpoint({
|
|
360
|
+
operationDefinition,
|
|
361
|
+
overrides,
|
|
362
|
+
}: {
|
|
363
|
+
operationDefinition: OperationDefinition;
|
|
364
|
+
overrides?: EndpointOverrides;
|
|
365
|
+
}) {
|
|
366
|
+
const {
|
|
367
|
+
verb,
|
|
368
|
+
path,
|
|
369
|
+
pathItem,
|
|
370
|
+
operation,
|
|
371
|
+
operation: { responses, requestBody },
|
|
372
|
+
} = operationDefinition;
|
|
373
|
+
const operationName = getOperationName({ verb, path, operation });
|
|
374
|
+
const tags = tag ? getTags({ verb, pathItem }) : [];
|
|
375
|
+
const isQuery = testIsQuery(verb, overrides);
|
|
376
|
+
|
|
377
|
+
const returnsJson = apiGen.getResponseType(responses) === 'json';
|
|
378
|
+
let ResponseType: ts.TypeNode = factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword);
|
|
379
|
+
if (returnsJson) {
|
|
380
|
+
const returnTypes = Object.entries(responses || {})
|
|
381
|
+
.map(([code, response]) =>
|
|
382
|
+
isDataResponse(code, includeDefault, response as OpenAPIV3.ResponseObject, responses as OpenAPIV3.ResponsesObject) && response
|
|
383
|
+
? apiGen.getTypeFromResponse(response as OpenAPIV3.ResponseObject)
|
|
384
|
+
: undefined
|
|
385
|
+
)
|
|
386
|
+
.filter(removeUndefined);
|
|
387
|
+
|
|
388
|
+
if (returnTypes.length === 1) {
|
|
389
|
+
ResponseType = returnTypes[0];
|
|
390
|
+
} else if (returnTypes.length > 1) {
|
|
391
|
+
ResponseType = factory.createUnionTypeNode(returnTypes);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const QueryArg = generateQueryArgType(operationDefinition);
|
|
396
|
+
const wrappedQueryArg = factory.createTypeReferenceNode(factory.createIdentifier('IUseFetcherArgs'), [QueryArg]);
|
|
397
|
+
|
|
398
|
+
const endpointBuilder = factory.createIdentifier('build');
|
|
399
|
+
|
|
400
|
+
const Response = factory.createTypeReferenceNode(
|
|
401
|
+
factory.createIdentifier(`${capitalize(operationName)}${responseSuffix}`),
|
|
402
|
+
undefined
|
|
403
|
+
);
|
|
404
|
+
|
|
405
|
+
const queryArgDefinitions = generateQueryArgDefinitions(operationDefinition);
|
|
406
|
+
|
|
407
|
+
const extraEndpointsProps = isQuery
|
|
408
|
+
? generateQueryEndpointProps({ operationDefinition })
|
|
409
|
+
: generateMutationEndpointProps({ operationDefinition });
|
|
410
|
+
|
|
411
|
+
return generateEndpointDefinition({
|
|
412
|
+
operationName,
|
|
413
|
+
type: isQuery ? 'query' : 'mutation',
|
|
414
|
+
Response,
|
|
415
|
+
QueryArg: wrappedQueryArg,
|
|
416
|
+
queryFn: generateQueryFn({
|
|
417
|
+
operationDefinition,
|
|
418
|
+
queryArg: queryArgDefinitions,
|
|
419
|
+
isFlatArg: flattenArg,
|
|
420
|
+
isQuery,
|
|
421
|
+
encodePathParams,
|
|
422
|
+
encodeQueryParams,
|
|
423
|
+
}),
|
|
424
|
+
extraEndpointsProps,
|
|
425
|
+
tags,
|
|
426
|
+
endpointBuilder,
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
function generateQueryFn({
|
|
431
|
+
operationDefinition,
|
|
432
|
+
queryArg,
|
|
433
|
+
isFlatArg,
|
|
434
|
+
isQuery,
|
|
435
|
+
encodePathParams,
|
|
436
|
+
encodeQueryParams,
|
|
437
|
+
}: {
|
|
438
|
+
operationDefinition: OperationDefinition;
|
|
439
|
+
queryArg: QueryArgDefinitions;
|
|
440
|
+
isFlatArg: boolean;
|
|
441
|
+
isQuery: boolean;
|
|
442
|
+
encodePathParams: boolean;
|
|
443
|
+
encodeQueryParams: boolean;
|
|
444
|
+
}) {
|
|
445
|
+
const {
|
|
446
|
+
verb,
|
|
447
|
+
path,
|
|
448
|
+
operation: { parameters = [], requestBody },
|
|
449
|
+
} = operationDefinition;
|
|
450
|
+
|
|
451
|
+
const bodyParameter =
|
|
452
|
+
requestBody && !isReference(requestBody) ? requestBody.content?.['application/json']?.schema : undefined;
|
|
453
|
+
const bodyArg = bodyParameter ? queryArg['body'] : undefined;
|
|
454
|
+
|
|
455
|
+
const pathParameters = parameters
|
|
456
|
+
.filter((p): p is OpenAPIV3.ParameterObject => !isReference(p) && p.in === 'path')
|
|
457
|
+
.map((param) => ({
|
|
458
|
+
name: param.name,
|
|
459
|
+
originalName: param.name,
|
|
460
|
+
type: factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
|
|
461
|
+
required: param.required,
|
|
462
|
+
param,
|
|
463
|
+
origin: 'param' as const,
|
|
464
|
+
}));
|
|
465
|
+
|
|
466
|
+
const queryParameters = parameters
|
|
467
|
+
.filter((p): p is OpenAPIV3.ParameterObject => !isReference(p) && p.in === 'query')
|
|
468
|
+
.map((param) => ({
|
|
469
|
+
name: param.name,
|
|
470
|
+
originalName: param.name,
|
|
471
|
+
type: factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
|
|
472
|
+
required: param.required,
|
|
473
|
+
param,
|
|
474
|
+
origin: 'param' as const,
|
|
475
|
+
}));
|
|
476
|
+
|
|
477
|
+
const headerParameters = parameters
|
|
478
|
+
.filter((p): p is OpenAPIV3.ParameterObject => !isReference(p) && p.in === 'header')
|
|
479
|
+
.map((param) => ({
|
|
480
|
+
name: param.name,
|
|
481
|
+
originalName: param.name,
|
|
482
|
+
type: factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
|
|
483
|
+
required: param.required,
|
|
484
|
+
param,
|
|
485
|
+
origin: 'param' as const,
|
|
486
|
+
}));
|
|
487
|
+
|
|
488
|
+
const rootObject = factory.createIdentifier('queryArg');
|
|
489
|
+
|
|
490
|
+
const objectProperties: ts.ObjectLiteralElementLike[] = [
|
|
491
|
+
factory.createPropertyAssignment(
|
|
492
|
+
'url',
|
|
493
|
+
generatePathExpression(path, pathParameters, rootObject, isFlatArg, encodePathParams)
|
|
494
|
+
),
|
|
495
|
+
factory.createPropertyAssignment('method', factory.createStringLiteral(verb.toUpperCase())),
|
|
496
|
+
];
|
|
497
|
+
|
|
498
|
+
if (bodyArg) {
|
|
499
|
+
objectProperties.push(
|
|
500
|
+
factory.createPropertyAssignment(
|
|
501
|
+
'body',
|
|
502
|
+
factory.createPropertyAccessExpression(
|
|
503
|
+
factory.createPropertyAccessExpression(rootObject, factory.createIdentifier('variables')),
|
|
504
|
+
factory.createIdentifier('body')
|
|
505
|
+
)
|
|
506
|
+
)
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
if (queryParameters.length) {
|
|
511
|
+
objectProperties.push(
|
|
512
|
+
factory.createPropertyAssignment(
|
|
513
|
+
'params',
|
|
514
|
+
factory.createPropertyAccessExpression(
|
|
515
|
+
factory.createPropertyAccessExpression(rootObject, factory.createIdentifier('variables')),
|
|
516
|
+
factory.createIdentifier('params')
|
|
517
|
+
)
|
|
518
|
+
)
|
|
519
|
+
);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
if (headerParameters.length) {
|
|
523
|
+
objectProperties.push(
|
|
524
|
+
factory.createPropertyAssignment(
|
|
525
|
+
'headers',
|
|
526
|
+
factory.createPropertyAccessExpression(
|
|
527
|
+
factory.createPropertyAccessExpression(rootObject, factory.createIdentifier('variables')),
|
|
528
|
+
factory.createIdentifier('headers')
|
|
529
|
+
)
|
|
530
|
+
)
|
|
531
|
+
);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// Add fetchOptions
|
|
535
|
+
objectProperties.push(
|
|
536
|
+
factory.createPropertyAssignment(
|
|
537
|
+
'fetchOptions',
|
|
538
|
+
factory.createPropertyAccessExpression(rootObject, factory.createIdentifier('fetchOptions'))
|
|
539
|
+
)
|
|
540
|
+
);
|
|
541
|
+
|
|
542
|
+
return factory.createArrowFunction(
|
|
543
|
+
undefined,
|
|
544
|
+
undefined,
|
|
545
|
+
[factory.createParameterDeclaration(undefined, undefined, rootObject)],
|
|
546
|
+
undefined,
|
|
547
|
+
factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
|
|
548
|
+
factory.createParenthesizedExpression(factory.createObjectLiteralExpression(objectProperties, true))
|
|
549
|
+
);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// eslint-disable-next-line no-empty-pattern
|
|
553
|
+
function generateQueryEndpointProps({}: { operationDefinition: OperationDefinition }): ObjectPropertyDefinitions {
|
|
554
|
+
return {}; /* TODO needs implementation - skip for now */
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// eslint-disable-next-line no-empty-pattern
|
|
558
|
+
function generateMutationEndpointProps({}: { operationDefinition: OperationDefinition }): ObjectPropertyDefinitions {
|
|
559
|
+
return {}; /* TODO needs implementation - skip for now */
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
function accessProperty(rootObject: ts.Identifier, propertyName: string) {
|
|
564
|
+
return isValidIdentifier(propertyName)
|
|
565
|
+
? factory.createPropertyAccessExpression(rootObject, factory.createIdentifier(propertyName))
|
|
566
|
+
: factory.createElementAccessExpression(rootObject, factory.createStringLiteral(propertyName));
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
function generatePathExpression(
|
|
570
|
+
path: string,
|
|
571
|
+
pathParameters: QueryArgDefinition[],
|
|
572
|
+
rootObject: ts.Identifier,
|
|
573
|
+
isFlatArg: boolean,
|
|
574
|
+
encodePathParams: boolean
|
|
575
|
+
): ts.Expression {
|
|
576
|
+
const expressions: Array<[string, string]> = [];
|
|
577
|
+
|
|
578
|
+
const head = path.replace(/\{(.*?)}(.*?)(?=\{|$)/g, (_, expression, literal) => {
|
|
579
|
+
const param = pathParameters.find((p) => p.originalName === expression);
|
|
580
|
+
if (!param) {
|
|
581
|
+
throw new Error(`path parameter ${expression} does not seem to be defined in '${path}'!`);
|
|
582
|
+
}
|
|
583
|
+
expressions.push([param.name, literal]);
|
|
584
|
+
return '';
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
return expressions.length
|
|
588
|
+
? factory.createTemplateExpression(
|
|
589
|
+
factory.createTemplateHead(head, head),
|
|
590
|
+
expressions.map(([prop, literal], index) => {
|
|
591
|
+
const value = factory.createPropertyAccessExpression(
|
|
592
|
+
factory.createPropertyAccessExpression(rootObject, factory.createIdentifier('variables')),
|
|
593
|
+
factory.createIdentifier(prop)
|
|
594
|
+
);
|
|
595
|
+
const encodedValue = encodePathParams
|
|
596
|
+
? factory.createCallExpression(factory.createIdentifier('encodeURIComponent'), undefined, [
|
|
597
|
+
factory.createCallExpression(factory.createIdentifier('String'), undefined, [value]),
|
|
598
|
+
])
|
|
599
|
+
: value;
|
|
600
|
+
return factory.createTemplateSpan(
|
|
601
|
+
encodedValue,
|
|
602
|
+
index === expressions.length - 1
|
|
603
|
+
? factory.createTemplateTail(literal, literal)
|
|
604
|
+
: factory.createTemplateMiddle(literal, literal)
|
|
605
|
+
);
|
|
606
|
+
})
|
|
607
|
+
)
|
|
608
|
+
: factory.createStringLiteral(head);
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
type QueryArgDefinition = {
|
|
612
|
+
name: string;
|
|
613
|
+
originalName: string;
|
|
614
|
+
type: ts.TypeNode;
|
|
615
|
+
required?: boolean;
|
|
616
|
+
param?: OpenAPIV3.ParameterObject;
|
|
617
|
+
} & (
|
|
618
|
+
| {
|
|
619
|
+
origin: 'param';
|
|
620
|
+
param: OpenAPIV3.ParameterObject;
|
|
621
|
+
}
|
|
622
|
+
| {
|
|
623
|
+
origin: 'body';
|
|
624
|
+
body: OpenAPIV3.RequestBodyObject;
|
|
625
|
+
}
|
|
626
|
+
);
|
|
627
|
+
type QueryArgDefinitions = Record<string, QueryArgDefinition>;
|