@goast/typescript 0.5.2-k6.1 → 0.5.2
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/LICENSE +21 -21
- package/assets/client/k6/request-builder.ts +362 -362
- package/package.json +1 -1
- package/src/mod.ts +7 -0
- package/src/src/assets.ts +9 -0
- package/src/src/ast/_index.ts +54 -0
- package/src/src/ast/common.ts +1 -0
- package/src/src/ast/index.ts +1 -0
- package/src/src/ast/node.ts +10 -0
- package/src/src/ast/nodes/argument.ts +40 -0
- package/src/src/ast/nodes/array-type.ts +49 -0
- package/src/src/ast/nodes/arrow-function.ts +91 -0
- package/src/src/ast/nodes/call.ts +55 -0
- package/src/src/ast/nodes/class.ts +133 -0
- package/src/src/ast/nodes/constructor-parameter.ts +70 -0
- package/src/src/ast/nodes/constructor.ts +77 -0
- package/src/src/ast/nodes/decorator.ts +76 -0
- package/src/src/ast/nodes/doc-tag.ts +291 -0
- package/src/src/ast/nodes/doc.ts +102 -0
- package/src/src/ast/nodes/enum-value.ts +72 -0
- package/src/src/ast/nodes/enum.ts +77 -0
- package/src/src/ast/nodes/export.ts +54 -0
- package/src/src/ast/nodes/function-type.ts +60 -0
- package/src/src/ast/nodes/function.ts +107 -0
- package/src/src/ast/nodes/generic-parameter.ts +71 -0
- package/src/src/ast/nodes/indexer.ts +60 -0
- package/src/src/ast/nodes/interface.ts +117 -0
- package/src/src/ast/nodes/intersection-type.ts +67 -0
- package/src/src/ast/nodes/lookup-type.ts +53 -0
- package/src/src/ast/nodes/method.ts +138 -0
- package/src/src/ast/nodes/object-type.ts +52 -0
- package/src/src/ast/nodes/object.ts +48 -0
- package/src/src/ast/nodes/parameter.ts +85 -0
- package/src/src/ast/nodes/property-accessor.ts +173 -0
- package/src/src/ast/nodes/property.ts +192 -0
- package/src/src/ast/nodes/reference.ts +194 -0
- package/src/src/ast/nodes/string.ts +49 -0
- package/src/src/ast/nodes/tuple.ts +53 -0
- package/src/src/ast/nodes/type-alias.ts +75 -0
- package/src/src/ast/nodes/typeof.ts +42 -0
- package/src/src/ast/nodes/types.ts +40 -0
- package/src/src/ast/nodes/union-type.ts +62 -0
- package/src/src/ast/nodes/variable.ts +73 -0
- package/src/src/ast/references/angular.ts +24 -0
- package/src/src/ast/references/easy-network-stub.ts +21 -0
- package/src/src/ast/references/index.ts +5 -0
- package/src/src/ast/references/k6.ts +8 -0
- package/src/src/ast/references/rxjs.ts +7 -0
- package/src/src/ast/references/typescript.ts +35 -0
- package/src/src/ast/utils/get-type-script-builder-options.ts +19 -0
- package/src/src/ast/utils/to-ts-node.ts +27 -0
- package/src/src/ast/utils/write-ts-generic-parameters.ts +25 -0
- package/src/src/ast/utils/write-ts-members.ts +28 -0
- package/src/src/ast/utils/write-ts-nodes.ts +35 -0
- package/src/src/ast/utils/write-ts-parameters.ts +25 -0
- package/src/src/common-results.ts +26 -0
- package/src/src/config.ts +89 -0
- package/src/src/file-builder.ts +136 -0
- package/src/src/generators/file-generator.ts +15 -0
- package/src/src/generators/index.ts +6 -0
- package/src/src/generators/models/index.ts +3 -0
- package/src/src/generators/models/model-generator.ts +469 -0
- package/src/src/generators/models/models-generator.ts +108 -0
- package/src/src/generators/models/models.ts +121 -0
- package/src/src/generators/services/angular-services/angular-service-generator.ts +406 -0
- package/src/src/generators/services/angular-services/angular-services-generator.ts +295 -0
- package/src/src/generators/services/angular-services/index.ts +3 -0
- package/src/src/generators/services/angular-services/models.ts +277 -0
- package/src/src/generators/services/angular-services/refs.ts +54 -0
- package/src/src/generators/services/easy-network-stub/easy-network-stub-generator.ts +242 -0
- package/src/src/generators/services/easy-network-stub/easy-network-stubs-generator.ts +211 -0
- package/src/src/generators/services/easy-network-stub/index.ts +3 -0
- package/src/src/generators/services/easy-network-stub/models.ts +124 -0
- package/src/src/generators/services/easy-network-stub/refs.ts +45 -0
- package/src/src/generators/services/fetch-clients/fetch-client-generator.ts +267 -0
- package/src/src/generators/services/fetch-clients/fetch-clients-generator.ts +163 -0
- package/src/src/generators/services/fetch-clients/index.ts +3 -0
- package/src/src/generators/services/fetch-clients/models.ts +130 -0
- package/src/src/generators/services/fetch-clients/refs.ts +21 -0
- package/src/src/generators/services/k6-clients/index.ts +3 -0
- package/src/src/generators/services/k6-clients/k6-client-generator.ts +465 -0
- package/src/src/generators/services/k6-clients/k6-clients-generator.ts +171 -0
- package/src/src/generators/services/k6-clients/models.ts +247 -0
- package/src/src/generators/services/k6-clients/refs.ts +16 -0
- package/src/src/import-collection.ts +236 -0
- package/src/src/utils.ts +72 -0
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
import { resolve } from 'node:path';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
type ApiEndpoint,
|
|
5
|
+
type ApiResponse,
|
|
6
|
+
type ApiSchema,
|
|
7
|
+
type AppendValueGroup,
|
|
8
|
+
appendValueGroup,
|
|
9
|
+
builderTemplate as s,
|
|
10
|
+
type MaybePromise,
|
|
11
|
+
toCasing,
|
|
12
|
+
} from '@goast/core';
|
|
13
|
+
|
|
14
|
+
import { ts } from '../../../ast/index.js';
|
|
15
|
+
import type { TypeScriptExportOutput } from '../../../common-results.js';
|
|
16
|
+
import { TypeScriptFileBuilder } from '../../../file-builder.js';
|
|
17
|
+
import type { TypeScriptImportOptions } from '../../../import-collection.js';
|
|
18
|
+
import { TypeScriptFileGenerator } from '../../file-generator.js';
|
|
19
|
+
import type { TypeScriptK6ClientGeneratorContext, TypeScriptK6ClientGeneratorOutput } from './models.js';
|
|
20
|
+
|
|
21
|
+
type Context = TypeScriptK6ClientGeneratorContext;
|
|
22
|
+
type Output = TypeScriptK6ClientGeneratorOutput;
|
|
23
|
+
type Builder = TypeScriptFileBuilder;
|
|
24
|
+
|
|
25
|
+
export interface TypeScriptK6ClientGenerator<TOutput extends Output = Output> {
|
|
26
|
+
generate(context: Context): MaybePromise<TOutput>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export class DefaultTypeScriptK6ClientGenerator extends TypeScriptFileGenerator<Context, Output>
|
|
30
|
+
implements TypeScriptK6ClientGenerator {
|
|
31
|
+
public override generate(ctx: TypeScriptK6ClientGeneratorContext): MaybePromise<TypeScriptK6ClientGeneratorOutput> {
|
|
32
|
+
const responseModels = this.generateResponseModels(ctx);
|
|
33
|
+
|
|
34
|
+
const filePath = this.getFilePath(ctx);
|
|
35
|
+
const name = this.getClassName(ctx);
|
|
36
|
+
|
|
37
|
+
TypeScriptFileBuilder.generate({
|
|
38
|
+
logName: `k6 client ${name}`,
|
|
39
|
+
filePath,
|
|
40
|
+
options: ctx.config,
|
|
41
|
+
generator: (b) => b.append(this.getClientFileContent(ctx)),
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
filePath,
|
|
46
|
+
component: name,
|
|
47
|
+
responseModels,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// #region Response Model
|
|
52
|
+
protected generateResponseModels(ctx: Context): Record<string, TypeScriptExportOutput> {
|
|
53
|
+
const result: Record<string, TypeScriptExportOutput> = {};
|
|
54
|
+
const filePath = this.getResponseModelFilePath(ctx);
|
|
55
|
+
const builder = new TypeScriptFileBuilder(filePath, ctx.config);
|
|
56
|
+
|
|
57
|
+
for (const endpoint of ctx.service.endpoints) {
|
|
58
|
+
const name = this.getResponseModelName(ctx, endpoint);
|
|
59
|
+
builder.appendLine(this.getResponseModelFileContent(ctx, endpoint));
|
|
60
|
+
result[endpoint.id] = { component: name, filePath };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
builder.writeToFile();
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
protected getResponseModelFileContent(ctx: Context, endpoint: ApiEndpoint): AppendValueGroup<Builder> {
|
|
68
|
+
const result = appendValueGroup<Builder>([]);
|
|
69
|
+
const statusCodes = this.getEndpointStatusCodes(ctx, endpoint);
|
|
70
|
+
let statusCodeType: ts.Reference<Builder>;
|
|
71
|
+
|
|
72
|
+
const statusCodesTypeName = toCasing(`${endpoint.name}_StatusCodes`, ctx.config.typeNameCasing);
|
|
73
|
+
result.values.push(ts.typeAlias(statusCodesTypeName, ts.unionType(Object.keys(statusCodes))));
|
|
74
|
+
if (ctx.config.strictResponseTypes) {
|
|
75
|
+
statusCodeType = ts.reference(statusCodesTypeName);
|
|
76
|
+
} else {
|
|
77
|
+
statusCodeType = ctx.refs.httpStatusCode();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
result.values.push(
|
|
81
|
+
ts.typeAlias(
|
|
82
|
+
this.getResponseModelName(ctx, endpoint),
|
|
83
|
+
ts.intersectionType([
|
|
84
|
+
ts.unionType([
|
|
85
|
+
...Object.entries(statusCodes).map(([key, value]) => {
|
|
86
|
+
return ts.intersectionType<Builder>([
|
|
87
|
+
ts.refs.omit([ts.refs.k6.response(), ts.string('json')]),
|
|
88
|
+
ts.objectType({
|
|
89
|
+
members: [
|
|
90
|
+
ts.property('status', { type: key }),
|
|
91
|
+
ts.method('json', {
|
|
92
|
+
returnType: value.type,
|
|
93
|
+
}),
|
|
94
|
+
ts.method('json', {
|
|
95
|
+
parameters: [ts.parameter('selector', { type: ts.refs.string() })],
|
|
96
|
+
returnType: ts.refs.k6.jsonValue(),
|
|
97
|
+
}),
|
|
98
|
+
],
|
|
99
|
+
}),
|
|
100
|
+
]);
|
|
101
|
+
}),
|
|
102
|
+
!ctx.config.strictResponseTypes
|
|
103
|
+
? ts.intersectionType([
|
|
104
|
+
ts.refs.k6.response(),
|
|
105
|
+
ts.objectType({
|
|
106
|
+
members: [
|
|
107
|
+
ts.property('status', {
|
|
108
|
+
type: ts.refs.exclude([ctx.refs.httpStatusCode(), ts.reference(statusCodesTypeName)]),
|
|
109
|
+
}),
|
|
110
|
+
],
|
|
111
|
+
}),
|
|
112
|
+
])
|
|
113
|
+
: null,
|
|
114
|
+
]),
|
|
115
|
+
ts.objectType({ members: [ts.property('status', { type: 'TStatus' })] }),
|
|
116
|
+
]),
|
|
117
|
+
{
|
|
118
|
+
doc: ts.doc({
|
|
119
|
+
description: `Response model for operation ${this.getEndpointMethodName(ctx, endpoint)}`,
|
|
120
|
+
tags: [endpoint.deprecated ? ts.docTag('deprecated') : null],
|
|
121
|
+
}),
|
|
122
|
+
export: true,
|
|
123
|
+
generics: [
|
|
124
|
+
ts.genericParameter('TStatus', {
|
|
125
|
+
constraint: statusCodeType,
|
|
126
|
+
default: statusCodeType,
|
|
127
|
+
}),
|
|
128
|
+
],
|
|
129
|
+
},
|
|
130
|
+
),
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
return result;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
protected getEndpointStatusCodes(ctx: Context, endpoint: ApiEndpoint): Record<number, { type: ts.Type<Builder> }> {
|
|
137
|
+
return Object.fromEntries([
|
|
138
|
+
...Object.entries(ctx.config.defaultStatusCodeResponseTypes).map(([key, value]) => [
|
|
139
|
+
key,
|
|
140
|
+
{
|
|
141
|
+
parser: value?.parser ?? 'text',
|
|
142
|
+
type: typeof value?.type === 'function'
|
|
143
|
+
? this.getSchemaType(ctx, value.type(ctx.data.schemas))
|
|
144
|
+
: value ?? ts.refs.never(),
|
|
145
|
+
},
|
|
146
|
+
]),
|
|
147
|
+
...endpoint.responses
|
|
148
|
+
.filter((x) => x.statusCode)
|
|
149
|
+
.map((x) => [
|
|
150
|
+
x.statusCode,
|
|
151
|
+
{
|
|
152
|
+
type: this.getSchemaType(ctx, x.contentOptions[0]?.schema, { fallback: ts.refs.never() }),
|
|
153
|
+
},
|
|
154
|
+
]),
|
|
155
|
+
]);
|
|
156
|
+
}
|
|
157
|
+
// #endregion
|
|
158
|
+
|
|
159
|
+
protected getClientFileContent(ctx: Context): AppendValueGroup<Builder> {
|
|
160
|
+
const code = appendValueGroup<Builder>([], '\n');
|
|
161
|
+
for (const endpoint of ctx.service.endpoints) {
|
|
162
|
+
if (endpoint.parameters.length > 0 || endpoint.requestBody !== undefined) {
|
|
163
|
+
code.values.push(this.getEndpointParamsType(ctx, endpoint));
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
code.values.push(
|
|
167
|
+
appendValueGroup<Builder>(
|
|
168
|
+
ctx.service.endpoints.map((e) =>
|
|
169
|
+
ts.variable(this.getEndpointPathPropertyName(ctx, e), {
|
|
170
|
+
readonly: true,
|
|
171
|
+
value: ts.string(e.path),
|
|
172
|
+
})
|
|
173
|
+
),
|
|
174
|
+
),
|
|
175
|
+
);
|
|
176
|
+
code.values.push(this.getClass(ctx));
|
|
177
|
+
return code;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
protected getEndpointParamsType(ctx: Context, endpoint: ApiEndpoint): ts.Doc<Builder> | ts.TypeAlias<Builder> {
|
|
181
|
+
const importOptions: TypeScriptImportOptions = ctx.config.language === 'javascript' ? { type: 'js-doc' } : {};
|
|
182
|
+
const description = `Parameters for operation ${this.getEndpointMethodName(ctx, endpoint)}`;
|
|
183
|
+
const typeName = this.getEndpointParamsTypeName(ctx, endpoint);
|
|
184
|
+
const properties: { name: string; type: ts.Type<Builder>; description: string | undefined; required: boolean }[] =
|
|
185
|
+
endpoint.parameters.map((parameter) => {
|
|
186
|
+
const schema = parameter.schema;
|
|
187
|
+
return {
|
|
188
|
+
name: toCasing(parameter.name, ctx.config.propertyNameCasing),
|
|
189
|
+
type: schema
|
|
190
|
+
? (b) => b.appendModelUsage(ctx.input.typescript.models[schema.id], importOptions)
|
|
191
|
+
: this.getAnyType(ctx),
|
|
192
|
+
description: (parameter.deprecated ? 'Deprecated: ' : '') + parameter.description,
|
|
193
|
+
required: parameter.required,
|
|
194
|
+
};
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
if (endpoint.requestBody !== undefined) {
|
|
198
|
+
const body = endpoint.requestBody;
|
|
199
|
+
const schema = body.content[0].schema;
|
|
200
|
+
properties.push({
|
|
201
|
+
name: 'body',
|
|
202
|
+
type: schema
|
|
203
|
+
? (b) => b.appendModelUsage(ctx.input.typescript.models[schema.id], importOptions)
|
|
204
|
+
: this.getAnyType(ctx),
|
|
205
|
+
description: body.description,
|
|
206
|
+
required: body.required,
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (ctx.config.language === 'javascript') {
|
|
211
|
+
return ts.doc<Builder>({
|
|
212
|
+
description,
|
|
213
|
+
tags: [
|
|
214
|
+
ts.docTag('typedef', typeName),
|
|
215
|
+
...properties.map((p) =>
|
|
216
|
+
ts.docTag<'property', Builder>('property', p.required ? p.name : `[${p.name}]`, {
|
|
217
|
+
type: p.type,
|
|
218
|
+
text: p.description,
|
|
219
|
+
})
|
|
220
|
+
),
|
|
221
|
+
],
|
|
222
|
+
});
|
|
223
|
+
} else {
|
|
224
|
+
return ts.typeAlias<Builder>(
|
|
225
|
+
typeName,
|
|
226
|
+
ts.objectType({
|
|
227
|
+
members: properties.map((p) =>
|
|
228
|
+
ts.property(p.name, {
|
|
229
|
+
type: p.type,
|
|
230
|
+
optional: !p.required,
|
|
231
|
+
doc: p.description ? ts.doc({ description: p.description }) : undefined,
|
|
232
|
+
})
|
|
233
|
+
),
|
|
234
|
+
}),
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
protected getClass(ctx: Context): ts.Class<Builder> {
|
|
240
|
+
const isJs = ctx.config.language === 'javascript';
|
|
241
|
+
const rootUrlType = ts.refs.string();
|
|
242
|
+
const paramType = ts.functionType({
|
|
243
|
+
returnType: ts.refs.k6.params({ importType: isJs ? 'js-doc' : 'type-import' }),
|
|
244
|
+
});
|
|
245
|
+
return ts.class<Builder>(this.getClassName(ctx), {
|
|
246
|
+
doc: ts.doc({
|
|
247
|
+
description: ctx.service.description,
|
|
248
|
+
tags: [
|
|
249
|
+
isJs ? ts.docTag('property', 'rootUrl', 'The root URL for this client.', { type: ts.refs.string() }) : null,
|
|
250
|
+
ctx.service.endpoints.length === 0 || ctx.service.endpoints.some((x) => !x.deprecated)
|
|
251
|
+
? null
|
|
252
|
+
: ts.docTag('deprecated'),
|
|
253
|
+
],
|
|
254
|
+
}),
|
|
255
|
+
export: true,
|
|
256
|
+
members: [
|
|
257
|
+
...(isJs ? [] : [
|
|
258
|
+
ts.property('rootUrl', {
|
|
259
|
+
type: rootUrlType,
|
|
260
|
+
readonly: true,
|
|
261
|
+
accessModifier: 'public',
|
|
262
|
+
doc: ts.doc({ description: 'The root URL for this client.' }),
|
|
263
|
+
}),
|
|
264
|
+
ts.property('_defaultK6ParamsFactory', {
|
|
265
|
+
type: paramType,
|
|
266
|
+
optional: true,
|
|
267
|
+
readonly: true,
|
|
268
|
+
accessModifier: 'private',
|
|
269
|
+
doc: ts.doc({ description: 'A factory function that returns the default K6 parameters.' }),
|
|
270
|
+
}),
|
|
271
|
+
]),
|
|
272
|
+
ts.constructor({
|
|
273
|
+
doc: ts.doc({
|
|
274
|
+
description: 'Creates a new instance of the client.',
|
|
275
|
+
tags: [
|
|
276
|
+
ts.docTag('param', 'rootUrl', 'The root URL for this client.', { type: isJs ? rootUrlType : undefined }),
|
|
277
|
+
ts.docTag(
|
|
278
|
+
'param',
|
|
279
|
+
'[defaultK6ParamsFactory]',
|
|
280
|
+
'A factory function that returns the default K6 parameters.',
|
|
281
|
+
{ type: isJs ? paramType : undefined },
|
|
282
|
+
),
|
|
283
|
+
],
|
|
284
|
+
}),
|
|
285
|
+
parameters: [
|
|
286
|
+
ts.constructorParameter('rootUrl', { type: isJs ? undefined : rootUrlType }),
|
|
287
|
+
ts.constructorParameter('defaultK6ParamsFactory', { type: isJs ? undefined : paramType, optional: true }),
|
|
288
|
+
],
|
|
289
|
+
body: s`this.rootUrl = rootUrl;
|
|
290
|
+
this._defaultK6ParamsFactory = defaultK6ParamsFactory;`,
|
|
291
|
+
}),
|
|
292
|
+
...ctx.service.endpoints.map((e) => this.getEndpointMethod(ctx, e)),
|
|
293
|
+
this.getGetK6ParamsMethod(ctx),
|
|
294
|
+
],
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
protected getEndpointMethod(ctx: Context, endpoint: ApiEndpoint): ts.Method<Builder> {
|
|
299
|
+
const isJs = ctx.config.language === 'javascript';
|
|
300
|
+
const hasParams = this.hasEndpointParams(ctx, endpoint);
|
|
301
|
+
const paramsOptional = !endpoint.parameters.some((p) => p.required) && !endpoint.requestBody?.required;
|
|
302
|
+
const responseModelType = ts.reference(
|
|
303
|
+
this.getResponseModelName(ctx, endpoint),
|
|
304
|
+
this.getResponseModelFilePath(ctx),
|
|
305
|
+
{ importType: ctx.config.language === 'javascript' ? 'js-doc' : 'type-import' },
|
|
306
|
+
);
|
|
307
|
+
const returnType = responseModelType;
|
|
308
|
+
const accept = this.getEndpointSuccessResponse(ctx, endpoint)?.contentOptions[0]?.type ?? '*/*';
|
|
309
|
+
const paramsType = hasParams ? ts.reference(this.getEndpointParamsTypeName(ctx, endpoint)) : null;
|
|
310
|
+
const returnTypeWithPromise = ctx.config.async ? ts.refs.promise([returnType]) : returnType;
|
|
311
|
+
|
|
312
|
+
return ts.method<Builder>(this.getEndpointMethodName(ctx, endpoint), {
|
|
313
|
+
async: ctx.config.async,
|
|
314
|
+
parameters: [
|
|
315
|
+
hasParams ? ts.parameter('params', { type: isJs ? null : paramsType }) : null,
|
|
316
|
+
ts.parameter('k6Params', { type: isJs ? null : ts.refs.k6.params({ importType: 'type-import' }) }),
|
|
317
|
+
],
|
|
318
|
+
returnType: isJs ? null : returnTypeWithPromise,
|
|
319
|
+
doc: ts.doc({
|
|
320
|
+
description: endpoint.description,
|
|
321
|
+
tags: isJs
|
|
322
|
+
? [
|
|
323
|
+
hasParams ? ts.docTag('param', paramsOptional ? '[params]' : 'params', { type: paramsType }) : null,
|
|
324
|
+
ts.docTag('param', '[k6Params]', { type: ts.refs.k6.params({ importType: 'js-doc' }) }),
|
|
325
|
+
ts.docTag('returns', { type: returnTypeWithPromise }),
|
|
326
|
+
endpoint.deprecated ? ts.docTag('deprecated') : null,
|
|
327
|
+
]
|
|
328
|
+
: [],
|
|
329
|
+
}),
|
|
330
|
+
accessModifier: isJs ? null : 'public',
|
|
331
|
+
body: appendValueGroup(
|
|
332
|
+
[
|
|
333
|
+
s`const rb = new ${ctx.refs.requestBuilder()}(this.rootUrl, ${
|
|
334
|
+
this.getEndpointPathPropertyName(ctx, endpoint)
|
|
335
|
+
}, '${endpoint.method}');`,
|
|
336
|
+
hasParams
|
|
337
|
+
? (b) =>
|
|
338
|
+
b.appendIf(paramsOptional, 'if (params) ').parenthesizeIf(
|
|
339
|
+
paramsOptional,
|
|
340
|
+
'{}',
|
|
341
|
+
appendValueGroup(
|
|
342
|
+
[
|
|
343
|
+
...endpoint.parameters
|
|
344
|
+
.filter((p) => p.target === 'path' || p.target === 'query' || p.target === 'header')
|
|
345
|
+
.map((p) => {
|
|
346
|
+
const options = ts.object({
|
|
347
|
+
members: [
|
|
348
|
+
p.style !== undefined ? ts.property('style', { value: ts.string(p.style) }) : null,
|
|
349
|
+
p.explode !== undefined ? ts.property('explode', { value: ts.toNode(p.explode) }) : null,
|
|
350
|
+
],
|
|
351
|
+
});
|
|
352
|
+
return s<Builder>`rb.${p.target}(${ts.string(p.name)}, params.${
|
|
353
|
+
toCasing(
|
|
354
|
+
p.name,
|
|
355
|
+
ctx.config.propertyNameCasing,
|
|
356
|
+
)
|
|
357
|
+
}, ${options});`;
|
|
358
|
+
}),
|
|
359
|
+
endpoint.requestBody
|
|
360
|
+
? s`rb.body(params.body, ${
|
|
361
|
+
ts.string(endpoint.requestBody.content[0].type ?? 'application/json')
|
|
362
|
+
});`
|
|
363
|
+
: null,
|
|
364
|
+
],
|
|
365
|
+
'\n',
|
|
366
|
+
),
|
|
367
|
+
{ multiline: true },
|
|
368
|
+
)
|
|
369
|
+
: null,
|
|
370
|
+
'',
|
|
371
|
+
ctx.config.language === 'javascript'
|
|
372
|
+
? s`return /** @type {${returnType}} */ (${s.indent`
|
|
373
|
+
${ctx.config.async ? 'await rb.buildAsync' : 'rb.build'}({${s.indent`
|
|
374
|
+
accept: ${ts.string(accept)},
|
|
375
|
+
params: this.getK6Params(k6Params),`}
|
|
376
|
+
})`}
|
|
377
|
+
);`
|
|
378
|
+
: s`return (${s.indent`
|
|
379
|
+
${ctx.config.async ? 'await rb.buildAsync' : 'rb.build'}({${s.indent`
|
|
380
|
+
accept: ${ts.string(accept)},
|
|
381
|
+
params: this.getK6Params(k6Params),`}
|
|
382
|
+
})`}
|
|
383
|
+
) as unknown as ${returnType};`,
|
|
384
|
+
],
|
|
385
|
+
'\n',
|
|
386
|
+
),
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
protected getGetK6ParamsMethod(ctx: Context): ts.Method<Builder> {
|
|
391
|
+
const isJs = ctx.config.language === 'javascript';
|
|
392
|
+
return ts.method('getK6Params', {
|
|
393
|
+
accessModifier: isJs ? null : 'private',
|
|
394
|
+
doc: isJs
|
|
395
|
+
? ts.doc({
|
|
396
|
+
tags: [
|
|
397
|
+
ts.docTag('private'),
|
|
398
|
+
ts.docTag('param', '[k6Params]', { type: ts.refs.k6.params({ importType: 'js-doc' }) }),
|
|
399
|
+
],
|
|
400
|
+
})
|
|
401
|
+
: null,
|
|
402
|
+
parameters: [ts.parameter('k6Params', { type: isJs ? null : ts.refs.k6.params({ importType: 'type-import' }) })],
|
|
403
|
+
body: s`return Object.assign({}, this._defaultK6ParamsFactory ? this._defaultK6ParamsFactory() : {}, k6Params);`,
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
protected getEndpointSuccessResponse(_ctx: Context, endpoint: ApiEndpoint): ApiResponse | undefined {
|
|
408
|
+
return (
|
|
409
|
+
endpoint.responses.find((x) => x.statusCode && x.statusCode >= 200 && x.statusCode < 300) ??
|
|
410
|
+
endpoint.responses.find((x) => x.statusCode === undefined)
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
protected getSchemaType(
|
|
415
|
+
ctx: Context,
|
|
416
|
+
schema: ApiSchema | undefined,
|
|
417
|
+
options?: TypeScriptImportOptions & { fallback?: ts.Type<Builder> },
|
|
418
|
+
): ts.Type<Builder> {
|
|
419
|
+
const output = schema && ctx.input.typescript.models[schema.id];
|
|
420
|
+
if (!output) return options?.fallback ?? this.getAnyType(ctx);
|
|
421
|
+
return (b) => b.appendModelUsage(output, options);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
protected getClassName(ctx: Context): string {
|
|
425
|
+
return toCasing(ctx.service.name, ctx.config.clientNameCasing ?? ctx.config.typeNameCasing);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
protected getResponseModelName(ctx: Context, endpoint: ApiEndpoint): string {
|
|
429
|
+
return toCasing(`${this.getEndpointMethodName(ctx, endpoint)}ApiResponse`, ctx.config.typeNameCasing);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
protected getEndpointParamsTypeName(ctx: Context, endpoint: ApiEndpoint): string {
|
|
433
|
+
return toCasing(this.getEndpointMethodName(ctx, endpoint) + '_Params', ctx.config.typeNameCasing);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
protected getEndpointPathPropertyName(ctx: Context, endpoint: ApiEndpoint): string {
|
|
437
|
+
return toCasing(this.getEndpointMethodName(ctx, endpoint) + '_Path', ctx.config.constantNameCasing);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
protected getEndpointMethodName(ctx: Context, endpoint: ApiEndpoint): string {
|
|
441
|
+
return toCasing(endpoint.name, ctx.config.functionNameCasing);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
protected getFilePath(ctx: Context): string {
|
|
445
|
+
return resolve(
|
|
446
|
+
ctx.config.outputDir,
|
|
447
|
+
ctx.config.clientDir,
|
|
448
|
+
`${toCasing(ctx.service.name, ctx.config.clientFileNameCasing ?? ctx.config.fileNameCasing)}.${
|
|
449
|
+
ctx.config.language === 'typescript' ? 'ts' : 'js'
|
|
450
|
+
}`,
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
protected getResponseModelFilePath(ctx: Context): string {
|
|
455
|
+
return resolve(
|
|
456
|
+
ctx.config.outputDir,
|
|
457
|
+
ctx.config.responseModelsDir,
|
|
458
|
+
`${toCasing(ctx.service.name, ctx.config.responseModelFileNameCasing ?? ctx.config.fileNameCasing)}.ts`,
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
protected hasEndpointParams(_ctx: Context, endpoint: ApiEndpoint): boolean {
|
|
463
|
+
return endpoint.parameters.length > 0 || endpoint.requestBody !== undefined;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { resolve } from 'node:path';
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
import fs from 'fs-extra';
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
type ApiService,
|
|
8
|
+
type AppendValueGroup,
|
|
9
|
+
appendValueGroup,
|
|
10
|
+
Factory,
|
|
11
|
+
type MaybePromise,
|
|
12
|
+
type OpenApiGeneratorContext,
|
|
13
|
+
OpenApiServicesGenerationProviderBase,
|
|
14
|
+
} from '@goast/core';
|
|
15
|
+
|
|
16
|
+
import { copyAssetFile } from '../../../assets.js';
|
|
17
|
+
import { ts } from '../../../ast/index.js';
|
|
18
|
+
import { TypeScriptFileBuilder } from '../../../file-builder.js';
|
|
19
|
+
import { DefaultTypeScriptK6ClientGenerator, type TypeScriptK6ClientGenerator } from './k6-client-generator.js';
|
|
20
|
+
import {
|
|
21
|
+
defaultTypeScriptK6ClientsGeneratorConfig,
|
|
22
|
+
type TypeScriptK6ClientGeneratorOutput,
|
|
23
|
+
type TypeScriptK6ClientsGeneratorConfig,
|
|
24
|
+
type TypeScriptK6ClientsGeneratorContext,
|
|
25
|
+
type TypeScriptK6ClientsGeneratorInput,
|
|
26
|
+
type TypeScriptK6ClientsGeneratorOutput,
|
|
27
|
+
} from './models.js';
|
|
28
|
+
import { getReferenceFactories } from './refs.js';
|
|
29
|
+
|
|
30
|
+
type Input = TypeScriptK6ClientsGeneratorInput;
|
|
31
|
+
type Output = TypeScriptK6ClientsGeneratorOutput;
|
|
32
|
+
type Config = TypeScriptK6ClientsGeneratorConfig;
|
|
33
|
+
type ServiceOutput = TypeScriptK6ClientGeneratorOutput;
|
|
34
|
+
type Context = TypeScriptK6ClientsGeneratorContext;
|
|
35
|
+
|
|
36
|
+
export class TypeScriptK6ClientsGenerator extends OpenApiServicesGenerationProviderBase<
|
|
37
|
+
Input,
|
|
38
|
+
Output,
|
|
39
|
+
Config,
|
|
40
|
+
ServiceOutput,
|
|
41
|
+
Context
|
|
42
|
+
> {
|
|
43
|
+
private readonly _clientGeneratorFactory: Factory<TypeScriptK6ClientGenerator, []>;
|
|
44
|
+
|
|
45
|
+
constructor(clientGeneratorFactory?: Factory<TypeScriptK6ClientGenerator, []>) {
|
|
46
|
+
super();
|
|
47
|
+
this._clientGeneratorFactory = clientGeneratorFactory ??
|
|
48
|
+
Factory.fromValue(new DefaultTypeScriptK6ClientGenerator());
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
protected override initResult(): Output {
|
|
52
|
+
return {
|
|
53
|
+
typescript: {
|
|
54
|
+
k6Clients: {},
|
|
55
|
+
indexFiles: {
|
|
56
|
+
k6Clients: undefined,
|
|
57
|
+
k6ResponseModels: undefined,
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
protected override buildContext(
|
|
64
|
+
context: OpenApiGeneratorContext<Input>,
|
|
65
|
+
config?: Partial<Config> | undefined,
|
|
66
|
+
): Context {
|
|
67
|
+
const providerContext = this.getProviderContext(context, config, defaultTypeScriptK6ClientsGeneratorConfig);
|
|
68
|
+
return Object.assign(providerContext, {
|
|
69
|
+
refs: getReferenceFactories(providerContext.config),
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
protected override async generateAdditionalFiles(ctx: TypeScriptK6ClientsGeneratorContext): Promise<void> {
|
|
74
|
+
await this.generateUtilsFiles(ctx);
|
|
75
|
+
ctx.output.typescript.indexFiles = this.generateIndexFiles(ctx);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
protected override generateService(ctx: Context, service: ApiService): MaybePromise<ServiceOutput> {
|
|
79
|
+
const clientGenerator = this._clientGeneratorFactory.create();
|
|
80
|
+
return clientGenerator.generate({
|
|
81
|
+
...ctx,
|
|
82
|
+
service,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
protected override addServiceResult(ctx: Context, service: ApiService, result: ServiceOutput): void {
|
|
87
|
+
ctx.output.typescript.k6Clients[service.id] = result;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
protected async generateUtilsFiles(ctx: Context): Promise<void> {
|
|
91
|
+
const targetDir = resolve(ctx.config.outputDir, ctx.config.utilsDir);
|
|
92
|
+
await fs.ensureDir(targetDir);
|
|
93
|
+
|
|
94
|
+
const ext = ctx.config.language === 'javascript' ? '.js' : '.ts';
|
|
95
|
+
await copyAssetFile(`client/k6/request-builder${ext}`, targetDir, 'Request Builder');
|
|
96
|
+
|
|
97
|
+
if (!ctx.config.strictResponseTypes) {
|
|
98
|
+
TypeScriptFileBuilder.generate({
|
|
99
|
+
logName: 'http status codes',
|
|
100
|
+
filePath: resolve(targetDir, 'http-status-code.ts'),
|
|
101
|
+
options: ctx.config,
|
|
102
|
+
generator: (b) => b.append(this.getHttpStatusCodeFileContent(ctx)),
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
protected getHttpStatusCodeFileContent(ctx: Context): AppendValueGroup<TypeScriptFileBuilder> {
|
|
108
|
+
return appendValueGroup(
|
|
109
|
+
[
|
|
110
|
+
ts.typeAlias('HttpStatusCode', ts.unionType(ctx.config.possibleStatusCodes), {
|
|
111
|
+
doc: ts.doc({ description: 'The possible status codes that any API endpoint can return.' }),
|
|
112
|
+
export: true,
|
|
113
|
+
}),
|
|
114
|
+
],
|
|
115
|
+
'\n',
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
protected generateIndexFiles(ctx: Context): Output['typescript']['indexFiles'] {
|
|
120
|
+
const clientsIndexFilePath = this.getIndexFilePath(ctx);
|
|
121
|
+
const responseModelsIndexFilePath = this.getResponseModelsIndexFilePath(ctx);
|
|
122
|
+
|
|
123
|
+
TypeScriptFileBuilder.tryGenerate({
|
|
124
|
+
logName: 'clients index file',
|
|
125
|
+
filePath: clientsIndexFilePath,
|
|
126
|
+
options: ctx.config,
|
|
127
|
+
generator: (b) => b.append(this.getIndexFileContent(ctx)),
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
TypeScriptFileBuilder.tryGenerate({
|
|
131
|
+
logName: 'response models index file',
|
|
132
|
+
filePath: responseModelsIndexFilePath,
|
|
133
|
+
options: ctx.config,
|
|
134
|
+
generator: (b) => b.append(this.getResponseModelsIndexFileContent(ctx)),
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
k6Clients: clientsIndexFilePath,
|
|
139
|
+
k6ResponseModels: responseModelsIndexFilePath,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
protected getIndexFilePath(ctx: Context): string | null {
|
|
144
|
+
return ctx.config.clientsIndexFile
|
|
145
|
+
? resolve(
|
|
146
|
+
ctx.config.outputDir,
|
|
147
|
+
ctx.config.clientsIndexFile.replace(/\.[jt]s$/, ctx.config.language === 'javascript' ? '.js' : '.ts'),
|
|
148
|
+
)
|
|
149
|
+
: null;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
protected getResponseModelsIndexFilePath(ctx: Context): string | null {
|
|
153
|
+
return ctx.config.responseModelsIndexFile
|
|
154
|
+
? resolve(ctx.config.outputDir, ctx.config.responseModelsIndexFile)
|
|
155
|
+
: null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
protected getIndexFileContent(ctx: Context): AppendValueGroup<TypeScriptFileBuilder> {
|
|
159
|
+
return appendValueGroup(
|
|
160
|
+
Object.values(ctx.output.typescript.k6Clients).map((x) => ts.export(x.component, x.filePath)),
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
protected getResponseModelsIndexFileContent(ctx: Context): AppendValueGroup<TypeScriptFileBuilder> {
|
|
165
|
+
return appendValueGroup(
|
|
166
|
+
Object.values(ctx.output.typescript.k6Clients)
|
|
167
|
+
.flatMap((x) => Object.values(x.responseModels))
|
|
168
|
+
.map((x) => ts.export(x.component, x.filePath)),
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
}
|