@goast/typescript 0.8.0-beta.1 → 0.8.0

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.
Files changed (114) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +52 -52
  3. package/assets/client/angular/angular-service.utils.ts +82 -82
  4. package/assets/client/angular/request-builder.ts +377 -377
  5. package/assets/client/fetch/fetch-client.utils.ts +52 -52
  6. package/assets/client/k6/request-builder.js +448 -448
  7. package/assets/client/k6/request-builder.ts +366 -366
  8. package/assets/stubs/easy-network-stub/easy-network-stub.utils.ts +257 -257
  9. package/esm/src/generators/models/model-generator.d.ts +22 -0
  10. package/esm/src/generators/models/model-generator.d.ts.map +1 -1
  11. package/esm/src/generators/models/model-generator.js +37 -5
  12. package/esm/src/generators/services/angular-services/angular-service-generator.js +9 -9
  13. package/esm/src/generators/services/angular-services/angular-services-generator.js +2 -2
  14. package/esm/src/generators/services/easy-network-stub/easy-network-stub-generator.js +9 -9
  15. package/esm/src/generators/services/fetch-clients/fetch-client-generator.js +4 -4
  16. package/esm/src/generators/services/k6-clients/k6-client-generator.d.ts.map +1 -1
  17. package/esm/src/generators/services/k6-clients/k6-client-generator.js +23 -13
  18. package/esm/src/import-collection.d.ts.map +1 -1
  19. package/esm/src/import-collection.js +2 -2
  20. package/package.json +2 -2
  21. package/script/src/generators/models/model-generator.d.ts +22 -0
  22. package/script/src/generators/models/model-generator.d.ts.map +1 -1
  23. package/script/src/generators/models/model-generator.js +37 -5
  24. package/script/src/generators/services/angular-services/angular-service-generator.js +9 -9
  25. package/script/src/generators/services/angular-services/angular-services-generator.js +2 -2
  26. package/script/src/generators/services/easy-network-stub/easy-network-stub-generator.js +9 -9
  27. package/script/src/generators/services/fetch-clients/fetch-client-generator.js +4 -4
  28. package/script/src/generators/services/k6-clients/k6-client-generator.d.ts.map +1 -1
  29. package/script/src/generators/services/k6-clients/k6-client-generator.js +23 -13
  30. package/script/src/import-collection.d.ts.map +1 -1
  31. package/script/src/import-collection.js +1 -1
  32. package/src/mod.ts +7 -0
  33. package/src/src/assets.ts +9 -0
  34. package/src/src/ast/_index.ts +54 -0
  35. package/src/src/ast/common.ts +1 -0
  36. package/src/src/ast/index.ts +1 -0
  37. package/src/src/ast/node.ts +10 -0
  38. package/src/src/ast/nodes/argument.ts +40 -0
  39. package/src/src/ast/nodes/array-type.ts +49 -0
  40. package/src/src/ast/nodes/arrow-function.ts +91 -0
  41. package/src/src/ast/nodes/call.ts +55 -0
  42. package/src/src/ast/nodes/class.ts +133 -0
  43. package/src/src/ast/nodes/constructor-parameter.ts +70 -0
  44. package/src/src/ast/nodes/constructor.ts +77 -0
  45. package/src/src/ast/nodes/decorator.ts +76 -0
  46. package/src/src/ast/nodes/doc-tag.ts +291 -0
  47. package/src/src/ast/nodes/doc.ts +102 -0
  48. package/src/src/ast/nodes/enum-value.ts +72 -0
  49. package/src/src/ast/nodes/enum.ts +77 -0
  50. package/src/src/ast/nodes/export.ts +54 -0
  51. package/src/src/ast/nodes/function-type.ts +60 -0
  52. package/src/src/ast/nodes/function.ts +107 -0
  53. package/src/src/ast/nodes/generic-parameter.ts +71 -0
  54. package/src/src/ast/nodes/indexer.ts +60 -0
  55. package/src/src/ast/nodes/interface.ts +117 -0
  56. package/src/src/ast/nodes/intersection-type.ts +67 -0
  57. package/src/src/ast/nodes/lookup-type.ts +53 -0
  58. package/src/src/ast/nodes/method.ts +138 -0
  59. package/src/src/ast/nodes/object-type.ts +52 -0
  60. package/src/src/ast/nodes/object.ts +48 -0
  61. package/src/src/ast/nodes/parameter.ts +85 -0
  62. package/src/src/ast/nodes/property-accessor.ts +173 -0
  63. package/src/src/ast/nodes/property.ts +192 -0
  64. package/src/src/ast/nodes/reference.ts +194 -0
  65. package/src/src/ast/nodes/string.ts +49 -0
  66. package/src/src/ast/nodes/tuple.ts +53 -0
  67. package/src/src/ast/nodes/type-alias.ts +75 -0
  68. package/src/src/ast/nodes/typeof.ts +42 -0
  69. package/src/src/ast/nodes/types.ts +40 -0
  70. package/src/src/ast/nodes/union-type.ts +62 -0
  71. package/src/src/ast/nodes/variable.ts +73 -0
  72. package/src/src/ast/references/angular.ts +24 -0
  73. package/src/src/ast/references/easy-network-stub.ts +21 -0
  74. package/src/src/ast/references/index.ts +5 -0
  75. package/src/src/ast/references/k6.ts +8 -0
  76. package/src/src/ast/references/rxjs.ts +7 -0
  77. package/src/src/ast/references/typescript.ts +35 -0
  78. package/src/src/ast/utils/get-type-script-builder-options.ts +19 -0
  79. package/src/src/ast/utils/to-ts-node.ts +27 -0
  80. package/src/src/ast/utils/write-ts-generic-parameters.ts +25 -0
  81. package/src/src/ast/utils/write-ts-members.ts +28 -0
  82. package/src/src/ast/utils/write-ts-nodes.ts +35 -0
  83. package/src/src/ast/utils/write-ts-parameters.ts +25 -0
  84. package/src/src/common-results.ts +26 -0
  85. package/src/src/config.ts +96 -0
  86. package/src/src/file-builder.ts +137 -0
  87. package/src/src/generators/file-generator.ts +15 -0
  88. package/src/src/generators/index.ts +6 -0
  89. package/src/src/generators/models/index.ts +3 -0
  90. package/src/src/generators/models/model-generator.ts +530 -0
  91. package/src/src/generators/models/models-generator.ts +108 -0
  92. package/src/src/generators/models/models.ts +121 -0
  93. package/src/src/generators/services/angular-services/angular-service-generator.ts +431 -0
  94. package/src/src/generators/services/angular-services/angular-services-generator.ts +295 -0
  95. package/src/src/generators/services/angular-services/index.ts +3 -0
  96. package/src/src/generators/services/angular-services/models.ts +277 -0
  97. package/src/src/generators/services/angular-services/refs.ts +54 -0
  98. package/src/src/generators/services/easy-network-stub/easy-network-stub-generator.ts +250 -0
  99. package/src/src/generators/services/easy-network-stub/easy-network-stubs-generator.ts +211 -0
  100. package/src/src/generators/services/easy-network-stub/index.ts +3 -0
  101. package/src/src/generators/services/easy-network-stub/models.ts +124 -0
  102. package/src/src/generators/services/easy-network-stub/refs.ts +47 -0
  103. package/src/src/generators/services/fetch-clients/fetch-client-generator.ts +305 -0
  104. package/src/src/generators/services/fetch-clients/fetch-clients-generator.ts +163 -0
  105. package/src/src/generators/services/fetch-clients/index.ts +3 -0
  106. package/src/src/generators/services/fetch-clients/models.ts +130 -0
  107. package/src/src/generators/services/fetch-clients/refs.ts +21 -0
  108. package/src/src/generators/services/k6-clients/index.ts +3 -0
  109. package/src/src/generators/services/k6-clients/k6-client-generator.ts +515 -0
  110. package/src/src/generators/services/k6-clients/k6-clients-generator.ts +171 -0
  111. package/src/src/generators/services/k6-clients/models.ts +247 -0
  112. package/src/src/generators/services/k6-clients/refs.ts +16 -0
  113. package/src/src/import-collection.ts +236 -0
  114. package/src/src/utils.ts +79 -0
@@ -0,0 +1,121 @@
1
+ import type {
2
+ ApiSchema,
3
+ DefaultGenerationProviderConfig,
4
+ ExtendedStringCasing,
5
+ Nullable,
6
+ OpenApiSchemasGenerationProviderContext,
7
+ } from '@goast/core';
8
+
9
+ import { ts } from '../../ast/index.js';
10
+ import type { TypeScriptComponentOutput } from '../../common-results.js';
11
+ import { defaultTypeScriptGeneratorConfig, type TypeScriptGeneratorConfig } from '../../config.js';
12
+ import type { TypeScriptFileBuilder } from '../../file-builder.js';
13
+
14
+ export type TypeScriptModelGeneratorConfig = TypeScriptGeneratorConfig & {
15
+ /**
16
+ * Determines how to generate enums.
17
+ * - 'union': Generate enums as union types.
18
+ * - 'prefer-enum': Generate enums as enums if possible, otherwise as union types.
19
+ * @default 'union'
20
+ */
21
+ enumGeneration: 'union' | 'prefer-enum';
22
+
23
+ /**
24
+ * Whether to generate immutable types.
25
+ * @default false
26
+ */
27
+ immutableTypes: boolean;
28
+
29
+ /**
30
+ * Whether to inline unnamed schemas. If false, unnamed schemas will be generated as separate types.
31
+ * @default true
32
+ */
33
+ inlineUnnamedSchemas: boolean;
34
+
35
+ /**
36
+ * The directory where the models should be saved. The path is relative to the output directory.
37
+ * @default 'models'
38
+ */
39
+ modelsDir: string;
40
+
41
+ /**
42
+ * The casing of the model file names. If nullish, the `fileNameCasing` is used.
43
+ * @default undefined
44
+ */
45
+ modelFileNameCasing: Nullable<ExtendedStringCasing>;
46
+
47
+ /**
48
+ * Determines how to declare types.
49
+ * - 'type': Always declare types as `type`.
50
+ * - 'prefer-interface': Prefer `interface` over `type` if possible.
51
+ * @default 'type'
52
+ */
53
+ typeDeclaration: 'type' | 'prefer-interface';
54
+
55
+ /**
56
+ * The casing to use for the base type of discriminator schemas.
57
+ * @default `_<SchemaName>Base`
58
+ */
59
+ discriminatorBaseTypeCasing: ExtendedStringCasing;
60
+
61
+ /**
62
+ * The type to use for binary schemas.
63
+ * @default `ts.refs.blob`
64
+ */
65
+ binarySchemaType: () => ts.Type<TypeScriptFileBuilder>;
66
+ };
67
+
68
+ export type TypeScriptModelsGeneratorConfig = TypeScriptModelGeneratorConfig & {
69
+ /**
70
+ * The path to the index file where all models should be exported. If nullish, no index file will be generated.
71
+ * @default 'models.ts'
72
+ */
73
+ modelsIndexFile: Nullable<string>;
74
+ };
75
+
76
+ export const defaultTypeScriptModelGeneratorConfig: DefaultGenerationProviderConfig<TypeScriptModelGeneratorConfig> = {
77
+ ...defaultTypeScriptGeneratorConfig,
78
+
79
+ enumGeneration: 'union',
80
+ immutableTypes: false,
81
+ inlineUnnamedSchemas: true,
82
+ modelsDir: 'models',
83
+ modelFileNameCasing: undefined,
84
+ typeDeclaration: 'type',
85
+ discriminatorBaseTypeCasing: { casing: 'pascal', prefix: '_', suffix: 'Base' },
86
+ binarySchemaType: ts.refs.blob,
87
+ };
88
+
89
+ export const defaultTypeScriptModelsGeneratorConfig: DefaultGenerationProviderConfig<TypeScriptModelsGeneratorConfig> =
90
+ {
91
+ ...defaultTypeScriptModelGeneratorConfig,
92
+
93
+ modelsIndexFile: 'models.ts',
94
+ };
95
+
96
+ // deno-lint-ignore ban-types
97
+ export type TypeScriptModelsGeneratorInput = {};
98
+
99
+ export type TypeScriptModelsGeneratorOutput = {
100
+ typescript: {
101
+ models: {
102
+ [schemaId: string]: TypeScriptModelGeneratorOutput;
103
+ };
104
+ indexFiles: {
105
+ models: Nullable<string>;
106
+ };
107
+ };
108
+ };
109
+
110
+ export type TypeScriptModelGeneratorOutput = TypeScriptComponentOutput;
111
+
112
+ export type TypeScriptModelsGeneratorContext = OpenApiSchemasGenerationProviderContext<
113
+ TypeScriptModelsGeneratorInput,
114
+ TypeScriptModelsGeneratorOutput,
115
+ TypeScriptModelsGeneratorConfig,
116
+ TypeScriptModelGeneratorOutput
117
+ >;
118
+
119
+ export type TypeScriptModelGeneratorContext = TypeScriptModelsGeneratorContext & {
120
+ schema: ApiSchema;
121
+ };
@@ -0,0 +1,431 @@
1
+ import { resolve } from 'node:path';
2
+
3
+ import {
4
+ type ApiComponent,
5
+ type ApiEndpoint,
6
+ type ApiResponse,
7
+ type ApiSchema,
8
+ type AppendValueGroup,
9
+ appendValueGroup,
10
+ type BasicAppendValue,
11
+ builderTemplate as s,
12
+ getSourceDisplayName,
13
+ type MaybePromise,
14
+ notNullish,
15
+ type Nullable,
16
+ toCasing,
17
+ } from '@goast/core';
18
+
19
+ import { ts } from '../../../ast/index.js';
20
+ import type { TypeScriptExportOutput } from '../../../common-results.js';
21
+ import { TypeScriptFileBuilder } from '../../../file-builder.js';
22
+ import { getSourceDocLine } from '../../../utils.js';
23
+ import { TypeScriptFileGenerator } from '../../file-generator.js';
24
+ import type { TypeScriptAngularServiceGeneratorContext, TypeScriptAngularServiceGeneratorOutput } from './models.js';
25
+
26
+ type Context = TypeScriptAngularServiceGeneratorContext;
27
+ type Output = TypeScriptAngularServiceGeneratorOutput;
28
+ type Builder = TypeScriptFileBuilder;
29
+
30
+ export interface TypeScriptAngularServiceGenerator<TOutput extends Output = Output> {
31
+ generate(ctx: Context): MaybePromise<TOutput>;
32
+ }
33
+
34
+ export class DefaultTypeScriptAngularServiceGenerator extends TypeScriptFileGenerator<Context, Output>
35
+ implements TypeScriptAngularServiceGenerator {
36
+ public generate(ctx: Context): MaybePromise<Output> {
37
+ const responseModels = this.generateResponseModels(ctx);
38
+
39
+ const filePath = this.getServiceFilePath(ctx);
40
+ const name = this.getServiceClassName(ctx);
41
+
42
+ TypeScriptFileBuilder.generate({
43
+ logName: `service ${name}`,
44
+ filePath,
45
+ options: ctx.config,
46
+ generator: (b) => {
47
+ ctx.service.endpoints.forEach((endpoint) => {
48
+ console.log(
49
+ ` ${getSourceDisplayName(ctx.data, endpoint)} [${toCasing(endpoint.name, ctx.config.functionNameCasing)}]`,
50
+ );
51
+ });
52
+ b.append(this.getServiceFileContent(ctx));
53
+ },
54
+ });
55
+
56
+ return {
57
+ filePath,
58
+ component: name,
59
+ responseModels,
60
+ };
61
+ }
62
+
63
+ // #region Response Model
64
+ protected generateResponseModels(ctx: Context): Record<string, TypeScriptExportOutput> {
65
+ const result: Record<string, TypeScriptExportOutput> = {};
66
+ const filePath = this.getResponseModelFilePath(ctx);
67
+ const builder = new TypeScriptFileBuilder(filePath, ctx.config);
68
+
69
+ for (const endpoint of ctx.service.endpoints) {
70
+ const name = this.getResponseModelName(ctx, endpoint);
71
+ builder.appendLine(this.getResponseModelFileContent(ctx, endpoint));
72
+ result[endpoint.id] = { component: name, filePath };
73
+ }
74
+
75
+ builder.writeToFile();
76
+ return result;
77
+ }
78
+
79
+ protected getResponseModelFileContent(ctx: Context, endpoint: ApiEndpoint): AppendValueGroup<Builder> {
80
+ const result = appendValueGroup<Builder>([]);
81
+ const statusCodes = this.getEndpointStatusCodes(ctx, endpoint);
82
+ let statusCodeType: ts.Reference<Builder>;
83
+
84
+ const statusCodesTypeName = toCasing(`${endpoint.name}_StatusCodes`, ctx.config.typeNameCasing);
85
+ result.values.push(ts.typeAlias(statusCodesTypeName, ts.unionType(Object.keys(statusCodes))));
86
+ if (ctx.config.strictResponseTypes) {
87
+ statusCodeType = ts.reference(statusCodesTypeName);
88
+ } else {
89
+ statusCodeType = ctx.refs.httpStatusCode();
90
+ }
91
+
92
+ result.values.push(
93
+ ts.typeAlias(
94
+ this.getResponseModelName(ctx, endpoint),
95
+ ts.intersectionType([
96
+ ts.unionType([
97
+ ...Object.entries(statusCodes).map(([key, value]) => {
98
+ const isSuccess = key.startsWith('2');
99
+ return ts.intersectionType<Builder>([
100
+ isSuccess
101
+ ? ts.refs.angular.httpResponse([value.type])
102
+ : ts.refs.omit([ts.refs.angular.httpErrorResponse(), ts.string('error')]),
103
+ ts.objectType({
104
+ members: [
105
+ isSuccess ? null : ts.property('error', { type: ts.unionType([value.type, ts.refs.null_()]) }),
106
+ ts.property('status', { type: key }),
107
+ ts.property('ok', { type: isSuccess ? 'true' : 'false' }),
108
+ ],
109
+ }),
110
+ ]);
111
+ }),
112
+ !ctx.config.strictResponseTypes
113
+ ? ts.intersectionType([
114
+ ts.refs.angular.httpResponse([this.getAnyType(ctx)]),
115
+ ts.objectType({
116
+ members: [
117
+ ts.property('status', {
118
+ type: ts.refs.exclude([ctx.refs.httpStatusCode(), ts.reference(statusCodesTypeName)]),
119
+ }),
120
+ ],
121
+ }),
122
+ ])
123
+ : null,
124
+ ]),
125
+ ts.objectType({ members: [ts.property('status', { type: 'TStatus' })] }),
126
+ ]),
127
+ {
128
+ doc: ts.doc({
129
+ description: `Response model for operation ${this.getEndpointMethodName(ctx, endpoint)}`,
130
+ tags: [endpoint.deprecated ? ts.docTag('deprecated') : null],
131
+ }),
132
+ export: true,
133
+ generics: [
134
+ ts.genericParameter('TStatus', {
135
+ constraint: statusCodeType,
136
+ default: statusCodeType,
137
+ }),
138
+ ],
139
+ },
140
+ ),
141
+ );
142
+
143
+ return result;
144
+ }
145
+
146
+ protected getEndpointStatusCodes(
147
+ ctx: Context,
148
+ endpoint: ApiEndpoint,
149
+ ): Record<number, { parser: 'text' | 'json'; type: ts.Type<Builder> }> {
150
+ return Object.fromEntries([
151
+ ...Object.entries(ctx.config.defaultStatusCodeResponseTypes).map(([key, value]) => [
152
+ key,
153
+ {
154
+ parser: value?.parser ?? 'text',
155
+ type: typeof value?.type === 'function'
156
+ ? this.getSchemaType(ctx, value.type(ctx.data.schemas))
157
+ : value ?? this.getAnyType(ctx),
158
+ },
159
+ ]),
160
+ ...endpoint.responses
161
+ .filter((x) => x.statusCode)
162
+ .map((x) => [
163
+ x.statusCode,
164
+ {
165
+ parser: this.contentTypeToResponseType(x.contentOptions[0]?.type),
166
+ type: this.getSchemaType(ctx, x.contentOptions[0]?.schema, this.getAnyType(ctx)),
167
+ },
168
+ ]),
169
+ ]);
170
+ }
171
+ // #endregion
172
+
173
+ protected getServiceFileContent(ctx: Context): AppendValueGroup<Builder> {
174
+ const code = appendValueGroup<Builder>([], '\n');
175
+ for (const endpoint of ctx.service.endpoints) {
176
+ if (endpoint.parameters.length > 0 || endpoint.requestBody !== undefined) {
177
+ code.values.push(this.getEndpointParamsType(ctx, endpoint));
178
+ }
179
+ }
180
+ code.values.push(this.getClass(ctx));
181
+ return code;
182
+ }
183
+
184
+ protected getEndpointParamsType(ctx: Context, endpoint: ApiEndpoint): ts.TypeAlias<Builder> {
185
+ const type = ts.objectType<Builder>();
186
+
187
+ for (const parameter of endpoint.parameters) {
188
+ const schema = parameter.schema;
189
+ type.members.push(
190
+ ts.property(toCasing(parameter.name, ctx.config.propertyNameCasing), {
191
+ doc: ts.doc({
192
+ description: this.getDocDescription(ctx, parameter),
193
+ tags: [parameter.deprecated ? ts.docTag('deprecated') : null],
194
+ }),
195
+ type: schema ? (b) => b.appendModelUsage(ctx.input.typescript.models[schema.id]) : this.getAnyType(ctx),
196
+ optional: !parameter.required,
197
+ }),
198
+ );
199
+ }
200
+
201
+ if (endpoint.requestBody !== undefined) {
202
+ const body = endpoint.requestBody;
203
+ const schema = body.content[0].schema;
204
+ type.members.push(
205
+ ts.property('body', {
206
+ doc: ts.doc({ description: this.getDocDescription(ctx, body) }),
207
+ type: schema ? (b) => b.appendModelUsage(ctx.input.typescript.models[schema.id]) : this.getAnyType(ctx),
208
+ optional: !body.required,
209
+ }),
210
+ );
211
+ }
212
+
213
+ return ts.typeAlias(this.getEndpointParamsTypeName(ctx, endpoint), type, {
214
+ doc: ts.doc({ description: `Parameters for operation ${this.getEndpointMethodName(ctx, endpoint)}` }),
215
+ });
216
+ }
217
+
218
+ protected getClass(ctx: Context): ts.Class<Builder> {
219
+ return ts.class<Builder>(this.getServiceClassName(ctx), {
220
+ doc: ts.doc({
221
+ description: ctx.service.description,
222
+ tags: [
223
+ ctx.service.endpoints.length === 0 || ctx.service.endpoints.some((x) => !x.deprecated)
224
+ ? null
225
+ : ts.docTag('deprecated'),
226
+ ],
227
+ }),
228
+ decorators: [
229
+ ts.decorator(
230
+ ts.refs.angular.injectable(),
231
+ ctx.config.provideKind === 'root' ? [ts.toNode({ providedIn: 'root' })] : [],
232
+ ),
233
+ ],
234
+ export: true,
235
+ extends: ctx.refs.apiBaseService(),
236
+ members: [
237
+ ...ctx.service.endpoints.map((e) =>
238
+ ts.property(this.getEndpointPathPropertyName(ctx, e), {
239
+ accessModifier: 'private',
240
+ static: true,
241
+ readonly: true,
242
+ value: ts.string(e.path),
243
+ })
244
+ ),
245
+ ...ctx.service.endpoints.map((e) => this.getEndpointMethod(ctx, e)),
246
+ ],
247
+ });
248
+ }
249
+
250
+ protected getEndpointMethod(ctx: Context, endpoint: ApiEndpoint): ts.Method<Builder> {
251
+ const hasParams = this.hasEndpointParams(ctx, endpoint);
252
+ const paramsOptional = !endpoint.parameters.some((p) => p.required) && !endpoint.requestBody?.required;
253
+ const responseModelType = ts.reference(
254
+ this.getResponseModelName(ctx, endpoint),
255
+ this.getResponseModelFilePath(ctx),
256
+ {
257
+ importType: 'type-import',
258
+ },
259
+ );
260
+ const returnType = ctx.refs.abortablePromise([responseModelType]);
261
+ const accept = this.getEndpointSuccessResponseType(ctx, endpoint);
262
+ const responseType = this.contentTypeToResponseType(accept);
263
+ return ts.method<Builder>(this.getEndpointMethodName(ctx, endpoint), {
264
+ accessModifier: 'public',
265
+ parameters: [
266
+ hasParams
267
+ ? ts.parameter('params', {
268
+ optional: paramsOptional,
269
+ type: ts.reference(this.getEndpointParamsTypeName(ctx, endpoint)),
270
+ })
271
+ : null,
272
+ ts.parameter('context', { optional: true, type: ts.refs.angular.httpContext() }),
273
+ ],
274
+ doc: ts.doc({
275
+ description: this.getDocDescription(ctx, endpoint),
276
+ tags: [endpoint.deprecated ? ts.docTag('deprecated') : null],
277
+ }),
278
+ returnType,
279
+ body: appendValueGroup(
280
+ [
281
+ s`const rb = new ${ctx.refs.requestBuilder()}(this.rootUrl, ${
282
+ this.getServiceClassName(
283
+ ctx,
284
+ )
285
+ }.${this.getEndpointPathPropertyName(ctx, endpoint)}, '${endpoint.method}');`,
286
+ hasParams
287
+ ? (b) =>
288
+ b.appendIf(paramsOptional, 'if (params) ').parenthesizeIf(
289
+ paramsOptional,
290
+ '{}',
291
+ appendValueGroup(
292
+ [
293
+ ...endpoint.parameters
294
+ .filter((p) => p.target === 'path' || p.target === 'query' || p.target === 'header')
295
+ .map((p) => {
296
+ const options = ts.object({
297
+ members: [
298
+ p.style !== undefined ? ts.property('style', { value: ts.string(p.style) }) : null,
299
+ p.explode !== undefined ? ts.property('explode', { value: ts.toNode(p.explode) }) : null,
300
+ ],
301
+ });
302
+ return s<Builder>`rb.${p.target}(${ts.string(p.name)}, params.${
303
+ toCasing(
304
+ p.name,
305
+ ctx.config.propertyNameCasing,
306
+ )
307
+ }, ${options});`;
308
+ }),
309
+ endpoint.requestBody
310
+ ? s`rb.body(params.body, ${ts.string(this.getEndpointRequestContentType(ctx, endpoint))});`
311
+ : null,
312
+ ],
313
+ '\n',
314
+ ),
315
+ { multiline: true },
316
+ )
317
+ : null,
318
+ '',
319
+ s`return ${ctx.refs.waitForResponse([responseModelType])}(${s.indent`
320
+ this.http.request(rb.build({${s.indent`
321
+ responseType: ${ts.string(responseType)},
322
+ accept: ${ts.string(accept)},
323
+ context,`}
324
+ })),
325
+ {${s.indent`
326
+ errorResponseTypes: ${
327
+ ts.object({
328
+ members: [
329
+ ...Object.entries(this.getEndpointStatusCodes(ctx, endpoint))
330
+ .filter(([key]) => !key.startsWith('2'))
331
+ .map(([key, value]) => {
332
+ return ts.property(key, { value: ts.string(value.parser) });
333
+ }),
334
+ ],
335
+ })
336
+ }`}
337
+ }`}
338
+ )`,
339
+ ],
340
+ '\n',
341
+ ),
342
+ });
343
+ }
344
+
345
+ protected getDocDescription<T>(
346
+ ctx: Context,
347
+ component: ApiComponent<T> & { description?: string },
348
+ ): BasicAppendValue<Builder> {
349
+ const docSegments = [component.description?.trim()];
350
+
351
+ if (ctx.config.includeSourceInDocs) {
352
+ docSegments.push(getSourceDocLine(component));
353
+ }
354
+
355
+ return docSegments.filter(notNullish).join('\n\n');
356
+ }
357
+
358
+ protected getEndpointRequestContentType(ctx: Context, endpoint: ApiEndpoint): string {
359
+ return ctx.config.defaultRequestContentType ?? endpoint?.requestBody?.content[0].type ?? 'application/json';
360
+ }
361
+
362
+ protected getEndpointSuccessResponseType(ctx: Context, endpoint: ApiEndpoint): string {
363
+ const response = this.getEndpointSuccessResponse(ctx, endpoint);
364
+ if (!response) return '*/*';
365
+
366
+ if (!ctx.config.defaultSuccessResponseContentType) return response.contentOptions[0]?.type ?? '*/*';
367
+
368
+ return (
369
+ response.contentOptions.find((x) => x.type === ctx.config.defaultSuccessResponseContentType)?.type ??
370
+ response.contentOptions[0]?.type ??
371
+ '*/*'
372
+ );
373
+ }
374
+
375
+ protected getEndpointSuccessResponse(_ctx: Context, endpoint: ApiEndpoint): ApiResponse | undefined {
376
+ return (
377
+ endpoint.responses.find((x) => x.statusCode && x.statusCode >= 200 && x.statusCode < 300) ??
378
+ endpoint.responses.find((x) => x.statusCode === undefined)
379
+ );
380
+ }
381
+
382
+ protected getServiceClassName(ctx: Context): string {
383
+ return toCasing(ctx.service.name + '_Service', ctx.config.typeNameCasing);
384
+ }
385
+
386
+ protected getResponseModelName(ctx: Context, endpoint: ApiEndpoint): string {
387
+ return toCasing(`${this.getEndpointMethodName(ctx, endpoint)}ApiResponse`, ctx.config.typeNameCasing);
388
+ }
389
+
390
+ protected getEndpointParamsTypeName(ctx: Context, endpoint: ApiEndpoint): string {
391
+ return toCasing(this.getEndpointMethodName(ctx, endpoint) + '_Params', ctx.config.typeNameCasing);
392
+ }
393
+
394
+ protected getEndpointPathPropertyName(ctx: Context, endpoint: ApiEndpoint): string {
395
+ return toCasing(this.getEndpointMethodName(ctx, endpoint) + '_Path', ctx.config.constantNameCasing);
396
+ }
397
+
398
+ protected getEndpointMethodName(ctx: Context, endpoint: ApiEndpoint): string {
399
+ return toCasing(endpoint.name, ctx.config.functionNameCasing);
400
+ }
401
+
402
+ protected getServiceFilePath(ctx: Context): string {
403
+ return resolve(
404
+ ctx.config.outputDir,
405
+ ctx.config.servicesDir,
406
+ `${toCasing(ctx.service.name, ctx.config.serviceFileNameCasing ?? ctx.config.fileNameCasing)}.ts`,
407
+ );
408
+ }
409
+
410
+ protected getResponseModelFilePath(ctx: Context): string {
411
+ return resolve(
412
+ ctx.config.outputDir,
413
+ ctx.config.responseModelsDir,
414
+ `${toCasing(ctx.service.name, ctx.config.responseModelFileNameCasing ?? ctx.config.fileNameCasing)}.ts`,
415
+ );
416
+ }
417
+
418
+ protected hasEndpointParams(_ctx: Context, endpoint: ApiEndpoint): boolean {
419
+ return endpoint.parameters.length > 0 || endpoint.requestBody !== undefined;
420
+ }
421
+
422
+ protected getSchemaType(ctx: Context, schema: ApiSchema | undefined, fallback?: ts.Type<Builder>): ts.Type<Builder> {
423
+ const output = schema && ctx.input.typescript.models[schema.id];
424
+ if (!output) return fallback ?? this.getAnyType(ctx);
425
+ return (b) => b.appendModelUsage(output);
426
+ }
427
+
428
+ private contentTypeToResponseType(contentType: Nullable<string>): 'text' | 'json' {
429
+ return contentType?.includes('json') ? 'json' : 'text';
430
+ }
431
+ }