@typespec/http-server-csharp 0.58.0-alpha.15-dev.0 → 0.58.0-alpha.15-dev.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/dist/src/lib/attributes.d.ts.map +1 -1
- package/dist/src/lib/attributes.js +6 -1
- package/dist/src/lib/attributes.js.map +1 -1
- package/dist/src/lib/interfaces.d.ts +2 -1
- package/dist/src/lib/interfaces.d.ts.map +1 -1
- package/dist/src/lib/interfaces.js +39 -25
- package/dist/src/lib/interfaces.js.map +1 -1
- package/dist/src/lib/scaffolding.js +21 -15
- package/dist/src/lib/scaffolding.js.map +1 -1
- package/dist/src/lib/service.d.ts.map +1 -1
- package/dist/src/lib/service.js +208 -127
- package/dist/src/lib/service.js.map +1 -1
- package/dist/src/lib/utils.d.ts +8 -3
- package/dist/src/lib/utils.d.ts.map +1 -1
- package/dist/src/lib/utils.js +211 -34
- package/dist/src/lib/utils.js.map +1 -1
- package/package.json +1 -1
package/dist/src/lib/service.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CodeTypeEmitter, StringBuilder, code, createAssetEmitter, } from "@typespec/asset-emitter";
|
|
2
|
-
import { getDoc, getNamespaceFullName, getService, isErrorModel, isNeverType, isNullType, isTemplateDeclaration, isVoidType, resolvePath, serializeValueAsJson, } from "@typespec/compiler";
|
|
2
|
+
import { getDoc, getNamespaceFullName, getService, isErrorModel, isNeverType, isNullType, isNumericType, isTemplateDeclaration, isVoidType, resolvePath, serializeValueAsJson, } from "@typespec/compiler";
|
|
3
3
|
import { createRekeyableMap } from "@typespec/compiler/utils";
|
|
4
4
|
import { getHeaderFieldName, getHttpOperation, getHttpPart, isHeader, isStatusCode, } from "@typespec/http";
|
|
5
5
|
import { getResourceOperation } from "@typespec/rest";
|
|
@@ -8,12 +8,12 @@ import path from "path";
|
|
|
8
8
|
import { getEncodedNameAttribute } from "./attributes.js";
|
|
9
9
|
import { GeneratedFileHeader, GeneratedFileHeaderWithNullable, getSerializationSourceFiles, } from "./boilerplate.js";
|
|
10
10
|
import { getProjectDocs } from "./doc.js";
|
|
11
|
-
import { CSharpSourceType, CSharpType, NameCasingType, } from "./interfaces.js";
|
|
11
|
+
import { CSharpSourceType, CSharpType, NameCasingType, checkOrAddNamespaceToScope, } from "./interfaces.js";
|
|
12
12
|
import { reportDiagnostic } from "./lib.js";
|
|
13
13
|
import { getProjectHelpers } from "./project.js";
|
|
14
14
|
import { getBusinessLogicImplementations, getScaffoldingHelpers, } from "./scaffolding.js";
|
|
15
15
|
import { getEnumType, getRecordType, isKnownReferenceType } from "./type-helpers.js";
|
|
16
|
-
import { CSharpOperationHelpers, HttpMetadata, ModelInfo, UnknownType, coalesceTypes, coalesceUnionTypes, ensureCSharpIdentifier, ensureCleanDirectory, formatComment, getBusinessLogicCallParameters, getBusinessLogicDeclParameters, getCSharpIdentifier, getCSharpStatusCode, getCSharpType, getCSharpTypeForIntrinsic, getCSharpTypeForScalar, getFreePort, getHttpDeclParameters, getModelAttributes, getModelDeclarationName, getModelInstantiationName, getOpenApiConfig, getOperationVerbDecorator, getStatusCode, isEmptyResponseModel, isValueType, } from "./utils.js";
|
|
16
|
+
import { CSharpOperationHelpers, HttpMetadata, ModelInfo, UnknownType, coalesceTypes, coalesceUnionTypes, ensureCSharpIdentifier, ensureCleanDirectory, formatComment, getBusinessLogicCallParameters, getBusinessLogicDeclParameters, getCSharpIdentifier, getCSharpStatusCode, getCSharpType, getCSharpTypeForIntrinsic, getCSharpTypeForScalar, getFreePort, getHttpDeclParameters, getImports, getModelAttributes, getModelDeclarationName, getModelInstantiationName, getOpenApiConfig, getOperationVerbDecorator, getStatusCode, isEmptyResponseModel, isRecord, isStringEnumType, isValueType, } from "./utils.js";
|
|
17
17
|
export async function $onEmit(context) {
|
|
18
18
|
let _unionCounter = 0;
|
|
19
19
|
const controllers = new Map();
|
|
@@ -27,7 +27,8 @@ export async function $onEmit(context) {
|
|
|
27
27
|
#generatedFileHeaderWithNullable = GeneratedFileHeaderWithNullable;
|
|
28
28
|
#generatedFileHeader = GeneratedFileHeader;
|
|
29
29
|
#sourceTypeKey = "sourceType";
|
|
30
|
-
#
|
|
30
|
+
#nsKey = "ResolvedNamespace";
|
|
31
|
+
#namespaces = new Map();
|
|
31
32
|
#emitterOutputType = context.options["output-type"];
|
|
32
33
|
#emitMocks = context.options["emit-mocks"];
|
|
33
34
|
#useSwagger = context.options["use-swaggerui"] || false;
|
|
@@ -35,14 +36,81 @@ export async function $onEmit(context) {
|
|
|
35
36
|
#mockRegistrations = new Map();
|
|
36
37
|
#opHelpers = new CSharpOperationHelpers(this.emitter);
|
|
37
38
|
#fileExists = getFileWriter(this.emitter.getProgram());
|
|
39
|
+
#getOrAddNamespaceForType(type) {
|
|
40
|
+
const program = this.emitter.getProgram();
|
|
41
|
+
switch (type.kind) {
|
|
42
|
+
case "Boolean":
|
|
43
|
+
case "String":
|
|
44
|
+
case "StringTemplate":
|
|
45
|
+
case "Number":
|
|
46
|
+
case "Scalar":
|
|
47
|
+
case "Intrinsic":
|
|
48
|
+
case "FunctionParameter":
|
|
49
|
+
case "ScalarConstructor":
|
|
50
|
+
case "StringTemplateSpan":
|
|
51
|
+
case "TemplateParameter":
|
|
52
|
+
case "Tuple":
|
|
53
|
+
return undefined;
|
|
54
|
+
case "EnumMember":
|
|
55
|
+
return this.#getOrAddNamespaceForType(type.enum);
|
|
56
|
+
case "ModelProperty":
|
|
57
|
+
case "UnionVariant":
|
|
58
|
+
return this.#getOrAddNamespaceForType(type.type);
|
|
59
|
+
case "Model":
|
|
60
|
+
if (isRecord(type) && type.indexer)
|
|
61
|
+
return this.#getOrAddNamespaceForType(type.indexer.value);
|
|
62
|
+
if (type.indexer !== undefined && isNumericType(program, type.indexer?.key))
|
|
63
|
+
return this.#getOrAddNamespaceForType(type.indexer.value);
|
|
64
|
+
return this.#getOrAddNamespace(type.namespace);
|
|
65
|
+
case "Union":
|
|
66
|
+
if (isStringEnumType(program, type))
|
|
67
|
+
return this.#getOrAddNamespace(type.namespace);
|
|
68
|
+
const unionType = this.#coalesceTsUnion(type);
|
|
69
|
+
if (unionType === undefined)
|
|
70
|
+
return undefined;
|
|
71
|
+
return this.#getOrAddNamespaceForType(unionType);
|
|
72
|
+
default:
|
|
73
|
+
return this.#getOrAddNamespace(type.namespace);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
#coalesceTsUnion(union) {
|
|
77
|
+
let result = undefined;
|
|
78
|
+
for (const [_, variant] of union.variants) {
|
|
79
|
+
if (!isNullType(variant.type)) {
|
|
80
|
+
if (result !== undefined && result !== variant.type)
|
|
81
|
+
return undefined;
|
|
82
|
+
result = variant.type;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return result;
|
|
86
|
+
}
|
|
87
|
+
#getOrAddNamespace(typespecNamespace) {
|
|
88
|
+
if (!typespecNamespace?.name)
|
|
89
|
+
return this.#getDefaultNamespace();
|
|
90
|
+
let resolvedNamespace = this.#namespaces.get(typespecNamespace);
|
|
91
|
+
if (resolvedNamespace !== undefined)
|
|
92
|
+
return resolvedNamespace;
|
|
93
|
+
const nsName = getNamespaceFullName(typespecNamespace);
|
|
94
|
+
resolvedNamespace = ensureCSharpIdentifier(this.emitter.getProgram(), typespecNamespace, nsName, NameCasingType.Namespace);
|
|
95
|
+
this.#namespaces.set(typespecNamespace, resolvedNamespace);
|
|
96
|
+
return resolvedNamespace;
|
|
97
|
+
}
|
|
98
|
+
#getDefaultNamespace() {
|
|
99
|
+
return "TypeSpec.Service";
|
|
100
|
+
}
|
|
38
101
|
arrayDeclaration(array, name, elementType) {
|
|
39
102
|
return this.emitter.result.declaration(ensureCSharpIdentifier(this.emitter.getProgram(), array, name), code `${this.emitter.emitTypeReference(elementType)}[]`);
|
|
40
103
|
}
|
|
41
104
|
arrayLiteral(array, elementType) {
|
|
42
|
-
|
|
105
|
+
this.emitter.emitType(elementType, this.emitter.getContext());
|
|
106
|
+
const csType = getCSharpType(this.emitter.getProgram(), array, this.#getOrAddNamespaceForType(elementType));
|
|
107
|
+
if (csType?.type) {
|
|
108
|
+
return code `${csType.type.getTypeReference(this.emitter.getContext()?.scope)}`;
|
|
109
|
+
}
|
|
110
|
+
return code `${this.emitter.emitTypeReference(elementType, this.emitter.getContext())}[]`;
|
|
43
111
|
}
|
|
44
112
|
booleanLiteral(boolean) {
|
|
45
|
-
return
|
|
113
|
+
return code `${boolean.value === true ? "true" : "false"}`;
|
|
46
114
|
}
|
|
47
115
|
unionLiteral(union) {
|
|
48
116
|
const csType = coalesceUnionTypes(this.emitter.getProgram(), union);
|
|
@@ -82,10 +150,7 @@ export async function $onEmit(context) {
|
|
|
82
150
|
const doc = getDoc(this.emitter.getProgram(), en);
|
|
83
151
|
const attributes = getModelAttributes(program, en, enumName);
|
|
84
152
|
this.#metadateMap.set(en, new CSharpType({ name: enumName, namespace: namespace }));
|
|
85
|
-
return this.emitter.result.declaration(enumName, code
|
|
86
|
-
|
|
87
|
-
${this.#emitUsings()}
|
|
88
|
-
|
|
153
|
+
return this.emitter.result.declaration(enumName, code `
|
|
89
154
|
namespace ${namespace}
|
|
90
155
|
{
|
|
91
156
|
|
|
@@ -103,7 +168,7 @@ export async function $onEmit(context) {
|
|
|
103
168
|
const enumName = ensureCSharpIdentifier(this.emitter.getProgram(), en, en.name);
|
|
104
169
|
const enumFile = this.emitter.createSourceFile(`generated/models/${enumName}.cs`);
|
|
105
170
|
enumFile.meta[this.#sourceTypeKey] = CSharpSourceType.Model;
|
|
106
|
-
const enumNamespace =
|
|
171
|
+
const enumNamespace = this.#getOrAddNamespace(en.namespace);
|
|
107
172
|
return this.#createEnumContext(enumNamespace, enumFile, enumName);
|
|
108
173
|
}
|
|
109
174
|
enumMembers(en) {
|
|
@@ -133,9 +198,8 @@ export async function $onEmit(context) {
|
|
|
133
198
|
intrinsic(intrinsic, name) {
|
|
134
199
|
switch (intrinsic.name) {
|
|
135
200
|
case "unknown":
|
|
136
|
-
return this.emitter.result.rawCode(code `System.Text.Json.Nodes.JsonNode`);
|
|
137
201
|
case "null":
|
|
138
|
-
return this.emitter.result.rawCode(code
|
|
202
|
+
return this.emitter.result.rawCode(code `${UnknownType.getTypeReference(this.emitter.getContext().scope)}`);
|
|
139
203
|
case "ErrorType":
|
|
140
204
|
case "never":
|
|
141
205
|
reportDiagnostic(this.emitter.getProgram(), {
|
|
@@ -146,7 +210,7 @@ export async function $onEmit(context) {
|
|
|
146
210
|
return "";
|
|
147
211
|
case "void":
|
|
148
212
|
const type = getCSharpTypeForIntrinsic(this.emitter.getProgram(), intrinsic);
|
|
149
|
-
return this.emitter.result.rawCode(`${type?.type.getTypeReference()}`);
|
|
213
|
+
return this.emitter.result.rawCode(`${type?.type.getTypeReference(this.emitter.getContext().scope)}`);
|
|
150
214
|
}
|
|
151
215
|
}
|
|
152
216
|
#emitUsings(file) {
|
|
@@ -165,8 +229,13 @@ export async function $onEmit(context) {
|
|
|
165
229
|
return "";
|
|
166
230
|
}
|
|
167
231
|
const isErrorType = isErrorModel(this.emitter.getProgram(), model);
|
|
232
|
+
if (model.baseModel && model.baseModel.namespace !== model.namespace) {
|
|
233
|
+
const resolvedNs = this.#getOrAddNamespaceForType(model.baseModel);
|
|
234
|
+
if (resolvedNs)
|
|
235
|
+
checkOrAddNamespaceToScope(resolvedNs, this.emitter.getContext().scope);
|
|
236
|
+
}
|
|
168
237
|
const baseModelRef = model.baseModel
|
|
169
|
-
? code `: ${this.emitter.emitTypeReference(model.baseModel)}`
|
|
238
|
+
? code `: ${this.emitter.emitTypeReference(model.baseModel, this.emitter.getContext())}`
|
|
170
239
|
: "";
|
|
171
240
|
const baseClass = baseModelRef || (isErrorType ? ": HttpServiceException" : "");
|
|
172
241
|
const namespace = this.emitter.getContext().namespace;
|
|
@@ -177,9 +246,7 @@ export async function $onEmit(context) {
|
|
|
177
246
|
? this.getModelExceptionConstructor(this.emitter.getProgram(), model, name, className)
|
|
178
247
|
: "";
|
|
179
248
|
this.#metadateMap.set(model, new CSharpType({ name: className, namespace: namespace }));
|
|
180
|
-
const decl = this.emitter.result.declaration(className, code
|
|
181
|
-
|
|
182
|
-
${this.#emitUsings()}
|
|
249
|
+
const decl = this.emitter.result.declaration(className, code `
|
|
183
250
|
namespace ${namespace} {
|
|
184
251
|
|
|
185
252
|
${doc ? `${formatComment(doc)}\n` : ""}${`${attributes.map((attribute) => attribute.getApplicationString(this.emitter.getContext().scope)).join("\n")}${attributes?.length > 0 ? "\n" : ""}`}public partial class ${className} ${baseClass} {
|
|
@@ -263,20 +330,20 @@ export async function $onEmit(context) {
|
|
|
263
330
|
}
|
|
264
331
|
modelDeclarationContext(model, name) {
|
|
265
332
|
if (this.#isMultipartModel(model))
|
|
266
|
-
return
|
|
333
|
+
return this.emitter.getContext();
|
|
267
334
|
const modelName = ensureCSharpIdentifier(this.emitter.getProgram(), model, name);
|
|
268
335
|
const modelFile = this.emitter.createSourceFile(`generated/models/${modelName}.cs`);
|
|
269
336
|
modelFile.meta[this.#sourceTypeKey] = CSharpSourceType.Model;
|
|
270
|
-
const modelNamespace =
|
|
337
|
+
const modelNamespace = this.#getOrAddNamespace(model.namespace);
|
|
271
338
|
return this.#createModelContext(modelNamespace, modelFile, modelName);
|
|
272
339
|
}
|
|
273
340
|
modelInstantiationContext(model) {
|
|
274
341
|
if (this.#isMultipartModel(model))
|
|
275
|
-
return
|
|
342
|
+
return this.emitter.getContext();
|
|
276
343
|
const modelName = getModelInstantiationName(this.emitter.getProgram(), model, model.name);
|
|
277
344
|
const sourceFile = this.emitter.createSourceFile(`generated/models/${modelName}.cs`);
|
|
278
345
|
sourceFile.meta[this.#sourceTypeKey] = CSharpSourceType.Model;
|
|
279
|
-
const modelNamespace =
|
|
346
|
+
const modelNamespace = this.#getOrAddNamespace(model.namespace);
|
|
280
347
|
const context = this.#createModelContext(modelNamespace, sourceFile, model.name);
|
|
281
348
|
context.instantiationName = modelName;
|
|
282
349
|
return context;
|
|
@@ -290,12 +357,33 @@ export async function $onEmit(context) {
|
|
|
290
357
|
const program = this.emitter.getProgram();
|
|
291
358
|
const recordType = getRecordType(program, model);
|
|
292
359
|
if (recordType !== undefined) {
|
|
293
|
-
|
|
360
|
+
const valueType = this.#getSimpleType(recordType);
|
|
361
|
+
return code `Dictionary<string, ${valueType ? valueType?.getTypeReference() : this.emitter.emitTypeReference(recordType)}>`;
|
|
294
362
|
}
|
|
295
363
|
const context = this.emitter.getContext();
|
|
296
364
|
const className = context.instantiationName ?? name;
|
|
297
365
|
return this.modelDeclaration(model, className);
|
|
298
366
|
}
|
|
367
|
+
#getSimpleType(type) {
|
|
368
|
+
switch (type.kind) {
|
|
369
|
+
case "Model":
|
|
370
|
+
if (!isRecord(type))
|
|
371
|
+
return undefined;
|
|
372
|
+
return getCSharpType(this.emitter.getProgram(), type)?.type;
|
|
373
|
+
case "Boolean":
|
|
374
|
+
case "Intrinsic":
|
|
375
|
+
case "ModelProperty":
|
|
376
|
+
case "Number":
|
|
377
|
+
case "Scalar":
|
|
378
|
+
case "String":
|
|
379
|
+
case "StringTemplate":
|
|
380
|
+
case "StringTemplateSpan":
|
|
381
|
+
case "Tuple":
|
|
382
|
+
return getCSharpType(this.emitter.getProgram(), type)?.type;
|
|
383
|
+
default:
|
|
384
|
+
return undefined;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
299
387
|
#getMultipartParts(model) {
|
|
300
388
|
const parts = [...model.properties.values()]
|
|
301
389
|
.flatMap((p) => getHttpPart(this.emitter.getProgram(), p.type)?.type)
|
|
@@ -357,20 +445,26 @@ export async function $onEmit(context) {
|
|
|
357
445
|
let propertyName = ensureCSharpIdentifier(this.emitter.getProgram(), property, property.name);
|
|
358
446
|
const { typeReference: typeName, defaultValue: typeDefault, nullableType: nullable, } = this.#findPropertyType(property);
|
|
359
447
|
const doc = getDoc(this.emitter.getProgram(), property);
|
|
360
|
-
const attributes = getModelAttributes(this.emitter.getProgram(), property, propertyName)
|
|
448
|
+
const attributes = new Map(getModelAttributes(this.emitter.getProgram(), property, propertyName).map((a) => [
|
|
449
|
+
a.type.name,
|
|
450
|
+
a,
|
|
451
|
+
]));
|
|
361
452
|
const modelName = this.emitter.getContext()["name"];
|
|
362
453
|
if (modelName === propertyName ||
|
|
363
454
|
(this.isDuplicateExceptionName(propertyName) &&
|
|
364
455
|
property.model &&
|
|
365
456
|
isErrorModel(this.emitter.getProgram(), property.model))) {
|
|
366
457
|
propertyName = `${propertyName}Prop`;
|
|
367
|
-
|
|
458
|
+
const attr = getEncodedNameAttribute(this.emitter.getProgram(), property, propertyName);
|
|
459
|
+
if (!attributes.has(attr.type.name))
|
|
460
|
+
attributes.set(attr.type.name, attr);
|
|
368
461
|
}
|
|
369
462
|
const defaultValue = property.defaultValue
|
|
370
463
|
? code `${JSON.stringify(serializeValueAsJson(this.emitter.getProgram(), property.defaultValue, property))}`
|
|
371
464
|
: typeDefault;
|
|
465
|
+
const attributeList = [...attributes.values()];
|
|
372
466
|
return this.emitter.result
|
|
373
|
-
.rawCode(code `${doc ? `${formatComment(doc)}\n` : ""}${`${
|
|
467
|
+
.rawCode(code `${doc ? `${formatComment(doc)}\n` : ""}${`${attributeList.map((attribute) => attribute.getApplicationString(this.emitter.getContext().scope)).join("\n")}${attributeList?.length > 0 ? "\n" : ""}`}public ${this.#isInheritedProperty(property) ? "new " : ""}${typeName}${isValueType(this.emitter.getProgram(), property.type) && (property.optional || nullable)
|
|
374
468
|
? "?"
|
|
375
469
|
: ""} ${propertyName} { get; ${typeDefault ? "}" : "set; }"}${defaultValue ? ` = ${defaultValue};\n` : "\n"}
|
|
376
470
|
`);
|
|
@@ -405,10 +499,7 @@ export async function $onEmit(context) {
|
|
|
405
499
|
const doc = getDoc(this.emitter.getProgram(), iface);
|
|
406
500
|
const attributes = getModelAttributes(this.emitter.getProgram(), iface, ifaceName);
|
|
407
501
|
this.#metadateMap.set(iface, new CSharpType({ name: ifaceName, namespace: namespace }));
|
|
408
|
-
const decl = this.emitter.result.declaration(ifaceName, code
|
|
409
|
-
|
|
410
|
-
${this.#emitUsings()}
|
|
411
|
-
|
|
502
|
+
const decl = this.emitter.result.declaration(ifaceName, code `
|
|
412
503
|
namespace ${namespace} {
|
|
413
504
|
|
|
414
505
|
${doc ? `${formatComment(doc)}\n` : ""}${`${attributes.map((attribute) => attribute.getApplicationString(this.emitter.getContext().scope)).join("\n")}${attributes?.length > 0 ? "\n" : ""}`}public interface ${ifaceName} {
|
|
@@ -422,48 +513,44 @@ export async function $onEmit(context) {
|
|
|
422
513
|
const ifaceName = `I${ensureCSharpIdentifier(this.emitter.getProgram(), iface, name, NameCasingType.Class)}`;
|
|
423
514
|
const sourceFile = this.emitter.createSourceFile(`generated/operations/${ifaceName}.cs`);
|
|
424
515
|
sourceFile.meta[this.#sourceTypeKey] = CSharpSourceType.Interface;
|
|
425
|
-
|
|
426
|
-
const
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
]);
|
|
434
|
-
context.file.imports.set("System.Threading.Tasks", ["System.Threading.Tasks"]);
|
|
435
|
-
context.file.imports.set("Microsoft.AspNetCore.Mvc", ["Microsoft.AspNetCore.Mvc"]);
|
|
516
|
+
sourceFile.meta["nullable"] = true;
|
|
517
|
+
const ifaceNamespace = this.#getOrAddNamespace(iface.namespace);
|
|
518
|
+
sourceFile.imports.set("System", ["System"]);
|
|
519
|
+
sourceFile.imports.set("System.Net", ["System.Net"]);
|
|
520
|
+
sourceFile.imports.set("System.Text.Json", ["System.Text.Json"]);
|
|
521
|
+
sourceFile.imports.set("System.Text.Json.Serialization", ["System.Text.Json.Serialization"]);
|
|
522
|
+
sourceFile.imports.set("System.Threading.Tasks", ["System.Threading.Tasks"]);
|
|
523
|
+
sourceFile.imports.set("Microsoft.AspNetCore.Mvc", ["Microsoft.AspNetCore.Mvc"]);
|
|
436
524
|
if (this.#hasMultipartOperation(iface)) {
|
|
437
|
-
|
|
525
|
+
sourceFile.imports.set("Microsoft.AspNetCore.WebUtilities", [
|
|
438
526
|
"Microsoft.AspNetCore.WebUtilities",
|
|
439
527
|
]);
|
|
440
528
|
}
|
|
441
|
-
|
|
442
|
-
return context;
|
|
529
|
+
return this.#createModelContext(ifaceNamespace, sourceFile, ifaceName);
|
|
443
530
|
}
|
|
444
531
|
interfaceDeclarationOperations(iface) {
|
|
445
532
|
// add in operations
|
|
446
533
|
const builder = new StringBuilder();
|
|
447
|
-
const metadata = new HttpMetadata();
|
|
448
534
|
const context = this.emitter.getContext();
|
|
449
535
|
const name = `${ensureCSharpIdentifier(this.emitter.getProgram(), iface, iface.name, NameCasingType.Class)}`;
|
|
450
|
-
const ifaceNamespace = this.#
|
|
451
|
-
const namespace =
|
|
536
|
+
const ifaceNamespace = this.#getOrAddNamespace(iface.namespace);
|
|
537
|
+
const namespace = ifaceNamespace;
|
|
452
538
|
const mock = {
|
|
453
539
|
className: name,
|
|
454
540
|
interfaceName: `I${name}`,
|
|
455
541
|
methods: [],
|
|
456
542
|
namespace: namespace,
|
|
457
|
-
usings: [
|
|
543
|
+
usings: [],
|
|
458
544
|
};
|
|
459
545
|
for (const [name, operation] of iface.operations) {
|
|
460
546
|
const doc = getDoc(this.emitter.getProgram(), operation);
|
|
461
547
|
const returnTypes = [];
|
|
462
548
|
const [httpOp, _] = getHttpOperation(this.emitter.getProgram(), operation);
|
|
463
549
|
for (const response of httpOp.responses.filter((r) => !isErrorModel(this.emitter.getProgram(), r.type))) {
|
|
464
|
-
|
|
550
|
+
const [_, responseType] = this.#resolveOperationResponse(response, httpOp.operation);
|
|
551
|
+
returnTypes.push(responseType);
|
|
465
552
|
}
|
|
466
|
-
const returnInfo = coalesceTypes(this.emitter.getProgram(), returnTypes,
|
|
553
|
+
const returnInfo = coalesceTypes(this.emitter.getProgram(), returnTypes, namespace);
|
|
467
554
|
const returnType = returnInfo?.type || UnknownType;
|
|
468
555
|
const opName = ensureCSharpIdentifier(this.emitter.getProgram(), operation, name, NameCasingType.Method);
|
|
469
556
|
const parameters = this.#opHelpers.getParameters(this.emitter.getProgram(), httpOp);
|
|
@@ -481,6 +568,7 @@ export async function $onEmit(context) {
|
|
|
481
568
|
builder.push(code `${opDecl.value}\n`);
|
|
482
569
|
this.emitter.emitInterfaceOperation(operation);
|
|
483
570
|
}
|
|
571
|
+
mock.usings.push(...getImports(context.scope));
|
|
484
572
|
this.#mockRegistrations.set(mock.interfaceName, mock);
|
|
485
573
|
return builder.reduce();
|
|
486
574
|
}
|
|
@@ -605,6 +693,23 @@ export async function $onEmit(context) {
|
|
|
605
693
|
stringTemplate(stringTemplate) {
|
|
606
694
|
return this.emitter.result.rawCode(stringTemplate.stringValue || "");
|
|
607
695
|
}
|
|
696
|
+
#resolveOperationResponse(response, operation) {
|
|
697
|
+
function getName(sourceType, part) {
|
|
698
|
+
return ensureCSharpIdentifier(emitter.getProgram(), sourceType, part, NameCasingType.Class);
|
|
699
|
+
}
|
|
700
|
+
let responseType = new HttpMetadata().resolveLogicalResponseType(this.emitter.getProgram(), response);
|
|
701
|
+
if (responseType.kind === "Model" && !responseType.name) {
|
|
702
|
+
const modelName = `${getName(responseType, operation.interface.name)}${getName(responseType, operation.name)}Response}`;
|
|
703
|
+
const returnedType = this.emitter
|
|
704
|
+
.getProgram()
|
|
705
|
+
.checker.cloneType(responseType, { name: modelName });
|
|
706
|
+
responseType = returnedType;
|
|
707
|
+
}
|
|
708
|
+
this.emitter.emitType(responseType);
|
|
709
|
+
const context = this.emitter.getContext();
|
|
710
|
+
const result = getCSharpType(this.emitter.getProgram(), responseType, context.namespace);
|
|
711
|
+
return [result?.type || UnknownType, responseType];
|
|
712
|
+
}
|
|
608
713
|
#getOperationResponse(operation) {
|
|
609
714
|
const validResponses = operation.responses.filter((r) => !isErrorModel(this.emitter.getProgram(), r.type) &&
|
|
610
715
|
getCSharpStatusCode(r.statusCodes) !== undefined);
|
|
@@ -625,46 +730,36 @@ export async function $onEmit(context) {
|
|
|
625
730
|
};
|
|
626
731
|
}
|
|
627
732
|
#emitOperationResponses(operation) {
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
getCSharpStatusCode(r.statusCodes) !== undefined);
|
|
632
|
-
for (const response of validResponses) {
|
|
633
|
-
i++;
|
|
634
|
-
builder.push(code `${this.#emitOperationResponseDecorator(response)}`);
|
|
635
|
-
if (i < validResponses.length) {
|
|
636
|
-
builder.pushLiteralSegment("\n");
|
|
637
|
-
}
|
|
733
|
+
function isValid(program, response) {
|
|
734
|
+
return (!isErrorModel(program, response.type) &&
|
|
735
|
+
getCSharpStatusCode(response.statusCodes) !== undefined);
|
|
638
736
|
}
|
|
737
|
+
const builder = new StringBuilder();
|
|
639
738
|
for (const response of operation.responses) {
|
|
640
|
-
|
|
641
|
-
|
|
739
|
+
const [responseType, _] = this.#resolveOperationResponse(response, operation.operation);
|
|
740
|
+
if (isValid(this.emitter.getProgram(), response)) {
|
|
741
|
+
builder.push(code `${builder.segments.length > 0 ? "\n" : ""}${this.#emitOperationResponseDecorator(response, responseType)}`);
|
|
742
|
+
}
|
|
642
743
|
}
|
|
643
744
|
return builder.reduce();
|
|
644
745
|
}
|
|
645
|
-
#emitOperationResponseDecorator(response) {
|
|
646
|
-
|
|
647
|
-
return this.emitter.result.rawCode(code `[ProducesResponseType((int)${getCSharpStatusCode(response.statusCodes)}, Type = typeof(${this.#emitResponseType(responseType)}))]`);
|
|
746
|
+
#emitOperationResponseDecorator(response, result) {
|
|
747
|
+
return this.emitter.result.rawCode(code `[ProducesResponseType((int)${getCSharpStatusCode(response.statusCodes)}, Type = typeof(${this.#emitResponseType(result)}))]`);
|
|
648
748
|
}
|
|
649
749
|
#emitResponseType(type) {
|
|
650
750
|
const context = this.emitter.getContext();
|
|
651
|
-
|
|
652
|
-
const resultType = result?.type || UnknownType;
|
|
653
|
-
return resultType.getTypeReference(context.scope);
|
|
751
|
+
return type.getTypeReference(context.scope);
|
|
654
752
|
}
|
|
655
753
|
unionDeclaration(union, name) {
|
|
656
754
|
const baseType = coalesceUnionTypes(this.emitter.getProgram(), union);
|
|
657
|
-
if (
|
|
755
|
+
if (isStringEnumType(this.emitter.getProgram(), union)) {
|
|
658
756
|
const program = this.emitter.getProgram();
|
|
659
757
|
const unionName = ensureCSharpIdentifier(program, union, name);
|
|
660
758
|
const namespace = this.emitter.getContext().namespace;
|
|
661
759
|
const doc = getDoc(this.emitter.getProgram(), union);
|
|
662
760
|
const attributes = getModelAttributes(program, union, unionName);
|
|
663
761
|
this.#metadateMap.set(union, new CSharpType({ name: unionName, namespace: namespace }));
|
|
664
|
-
return this.emitter.result.declaration(unionName, code
|
|
665
|
-
|
|
666
|
-
${this.#emitUsings()}
|
|
667
|
-
|
|
762
|
+
return this.emitter.result.declaration(unionName, code `
|
|
668
763
|
namespace ${namespace}
|
|
669
764
|
{
|
|
670
765
|
|
|
@@ -672,24 +767,19 @@ export async function $onEmit(context) {
|
|
|
672
767
|
${attributes.map((attribute) => attribute.getApplicationString(this.emitter.getContext().scope)).join("\n")}
|
|
673
768
|
public enum ${unionName}
|
|
674
769
|
{
|
|
675
|
-
${this.emitter.emitUnionVariants(union)}
|
|
770
|
+
${this.emitter.emitUnionVariants(union)}
|
|
676
771
|
}
|
|
677
772
|
} `);
|
|
678
773
|
}
|
|
679
|
-
return this.emitter.result.rawCode(code `${baseType.getTypeReference()}`);
|
|
774
|
+
return this.emitter.result.rawCode(code `${baseType.getTypeReference(this.emitter.getContext().scope)}`);
|
|
680
775
|
}
|
|
681
776
|
unionDeclarationContext(union) {
|
|
682
|
-
|
|
683
|
-
if (baseType.isBuiltIn && baseType.name === "string") {
|
|
777
|
+
if (isStringEnumType(this.emitter.getProgram(), union)) {
|
|
684
778
|
const unionName = ensureCSharpIdentifier(this.emitter.getProgram(), union, union.name || "Union");
|
|
685
779
|
const unionFile = this.emitter.createSourceFile(`generated/models/${unionName}.cs`);
|
|
686
780
|
unionFile.meta[this.#sourceTypeKey] = CSharpSourceType.Model;
|
|
687
|
-
const unionNamespace =
|
|
688
|
-
return
|
|
689
|
-
namespace: unionNamespace,
|
|
690
|
-
file: unionFile,
|
|
691
|
-
scope: unionFile.globalScope,
|
|
692
|
-
};
|
|
781
|
+
const unionNamespace = this.#getOrAddNamespace(union.namespace);
|
|
782
|
+
return this.#createEnumContext(unionNamespace, unionFile, unionName);
|
|
693
783
|
}
|
|
694
784
|
else {
|
|
695
785
|
return this.emitter.getContext();
|
|
@@ -710,7 +800,9 @@ export async function $onEmit(context) {
|
|
|
710
800
|
const nameHint = name || variant.type.value;
|
|
711
801
|
const memberName = ensureCSharpIdentifier(this.emitter.getProgram(), variant, nameHint);
|
|
712
802
|
this.#metadateMap.set(variant, { name: memberName });
|
|
713
|
-
result.push(code
|
|
803
|
+
result.push(code `
|
|
804
|
+
[JsonStringEnumMemberName("${variant.type.value}")]
|
|
805
|
+
${ensureCSharpIdentifier(this.emitter.getProgram(), variant, nameHint, NameCasingType.Property)}`);
|
|
714
806
|
if (i < union.variants.size)
|
|
715
807
|
result.pushLiteralSegment(", ");
|
|
716
808
|
}
|
|
@@ -721,66 +813,57 @@ export async function $onEmit(context) {
|
|
|
721
813
|
return super.unionVariantContext(union);
|
|
722
814
|
}
|
|
723
815
|
#createModelContext(namespace, file, name) {
|
|
816
|
+
file.imports.set("System", ["System"]);
|
|
817
|
+
file.imports.set("System.Text.Json", ["System.Text.Json"]);
|
|
818
|
+
file.imports.set("System.Text.Json.Serialization", ["System.Text.Json.Serialization"]);
|
|
819
|
+
file.imports.set("TypeSpec.Helpers.JsonConverters", ["TypeSpec.Helpers.JsonConverters"]);
|
|
820
|
+
file.imports.set("TypeSpec.Helpers", ["TypeSpec.Helpers"]);
|
|
821
|
+
file.meta[this.#nsKey] = namespace;
|
|
724
822
|
const context = {
|
|
725
823
|
namespace: namespace,
|
|
726
824
|
name: name,
|
|
727
825
|
file: file,
|
|
728
826
|
scope: file.globalScope,
|
|
729
827
|
};
|
|
730
|
-
context.file.imports.set("System", ["System"]);
|
|
731
|
-
context.file.imports.set("System.Text.Json", ["System.Text.Json"]);
|
|
732
|
-
context.file.imports.set("System.Text.Json.Serialization", [
|
|
733
|
-
"System.Text.Json.Serialization",
|
|
734
|
-
]);
|
|
735
|
-
context.file.imports.set("TypeSpec.Helpers.JsonConverters", [
|
|
736
|
-
"TypeSpec.Helpers.JsonConverters",
|
|
737
|
-
]);
|
|
738
|
-
context.file.imports.set("TypeSpec.Helpers", ["TypeSpec.Helpers"]);
|
|
739
828
|
return context;
|
|
740
829
|
}
|
|
741
830
|
#createEnumContext(namespace, file, name) {
|
|
742
|
-
|
|
831
|
+
file.imports.set("System.Text.Json", ["System.Text.Json"]);
|
|
832
|
+
file.imports.set("System.Text.Json.Serialization", ["System.Text.Json.Serialization"]);
|
|
833
|
+
return {
|
|
743
834
|
namespace: namespace,
|
|
744
835
|
name: name,
|
|
745
836
|
file: file,
|
|
746
837
|
scope: file.globalScope,
|
|
747
838
|
};
|
|
748
|
-
context.file.imports.set("System.Text.Json", ["System.Text.Json"]);
|
|
749
|
-
context.file.imports.set("System.Text.Json.Serialization", [
|
|
750
|
-
"System.Text.Json.Serialization",
|
|
751
|
-
]);
|
|
752
|
-
return context;
|
|
753
839
|
}
|
|
754
840
|
#createOrGetResourceContext(name, operation, resource) {
|
|
755
841
|
name = ensureCSharpIdentifier(this.emitter.getProgram(), operation, name, NameCasingType.Class);
|
|
756
|
-
|
|
842
|
+
const namespace = this.#getOrAddNamespace(operation.namespace);
|
|
843
|
+
let context = controllers.get(`${namespace}.${name}`);
|
|
757
844
|
if (context !== undefined)
|
|
758
845
|
return context;
|
|
759
846
|
const sourceFile = this.emitter.createSourceFile(`generated/controllers/${name}Controller.cs`);
|
|
760
|
-
const namespace = this.#getOrSetBaseNamespace(operation);
|
|
761
|
-
const modelNamespace = `${namespace}.Models`;
|
|
762
847
|
sourceFile.meta[this.#sourceTypeKey] = CSharpSourceType.Controller;
|
|
763
848
|
sourceFile.meta["resourceName"] = name;
|
|
764
849
|
sourceFile.meta["resource"] = `${name}Controller`;
|
|
765
850
|
sourceFile.meta["namespace"] = namespace;
|
|
851
|
+
sourceFile.imports.set("System", ["System"]);
|
|
852
|
+
sourceFile.imports.set("System.Net", ["System.Net"]);
|
|
853
|
+
sourceFile.imports.set("System.Threading.Tasks", ["System.Threading.Tasks"]);
|
|
854
|
+
sourceFile.imports.set("System.Text.Json", ["System.Text.Json"]);
|
|
855
|
+
sourceFile.imports.set("System.Text.Json.Serialization", ["System.Text.Json.Serialization"]);
|
|
856
|
+
sourceFile.imports.set("Microsoft.AspNetCore.Mvc", ["Microsoft.AspNetCore.Mvc"]);
|
|
857
|
+
sourceFile.imports.set(namespace, [namespace]);
|
|
858
|
+
sourceFile.meta[this.#nsKey] = namespace;
|
|
766
859
|
context = {
|
|
767
|
-
namespace:
|
|
860
|
+
namespace: namespace,
|
|
768
861
|
file: sourceFile,
|
|
769
862
|
resourceName: name,
|
|
770
863
|
scope: sourceFile.globalScope,
|
|
771
864
|
resourceType: resource,
|
|
772
865
|
};
|
|
773
|
-
|
|
774
|
-
context.file.imports.set("System.Net", ["System.Net"]);
|
|
775
|
-
context.file.imports.set("System.Threading.Tasks", ["System.Threading.Tasks"]);
|
|
776
|
-
context.file.imports.set("System.Text.Json", ["System.Text.Json"]);
|
|
777
|
-
context.file.imports.set("System.Text.Json.Serialization", [
|
|
778
|
-
"System.Text.Json.Serialization",
|
|
779
|
-
]);
|
|
780
|
-
context.file.imports.set("Microsoft.AspNetCore.Mvc", ["Microsoft.AspNetCore.Mvc"]);
|
|
781
|
-
context.file.imports.set(modelNamespace, [modelNamespace]);
|
|
782
|
-
context.file.imports.set(namespace, [namespace]);
|
|
783
|
-
controllers.set(name, context);
|
|
866
|
+
controllers.set(`${namespace}.${name}`, context);
|
|
784
867
|
return context;
|
|
785
868
|
}
|
|
786
869
|
// eslint-disable-next-line no-unused-private-class-members
|
|
@@ -821,9 +904,20 @@ export async function $onEmit(context) {
|
|
|
821
904
|
}
|
|
822
905
|
#emitCodeContents(file) {
|
|
823
906
|
const contents = new StringBuilder();
|
|
907
|
+
contents.pushLiteralSegment(`${file.meta["nullable"] ? this.#generatedFileHeaderWithNullable : this.#generatedFileHeader}
|
|
908
|
+
|
|
909
|
+
${this.#emitUsings(file)}
|
|
910
|
+
`);
|
|
824
911
|
for (const decl of file.globalScope.declarations) {
|
|
825
912
|
contents.push(decl.value);
|
|
826
913
|
}
|
|
914
|
+
for (const child of file.globalScope.childScopes) {
|
|
915
|
+
if (child.declarations) {
|
|
916
|
+
for (const decl of child.declarations) {
|
|
917
|
+
contents.push(decl.value);
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
}
|
|
827
921
|
return contents.segments.join("\n") + "\n";
|
|
828
922
|
}
|
|
829
923
|
#emitControllerContents(file) {
|
|
@@ -932,19 +1026,6 @@ export async function $onEmit(context) {
|
|
|
932
1026
|
}
|
|
933
1027
|
return super.writeOutput(emittedSourceFiles);
|
|
934
1028
|
}
|
|
935
|
-
#getOrSetBaseNamespace(type) {
|
|
936
|
-
if (this.#baseNamespace === undefined) {
|
|
937
|
-
if (type.namespace !== undefined) {
|
|
938
|
-
this.#baseNamespace = `${type.namespace
|
|
939
|
-
? ensureCSharpIdentifier(this.emitter.getProgram(), type.namespace, getNamespaceFullName(type.namespace))
|
|
940
|
-
: "TypeSpec"}.Service`;
|
|
941
|
-
}
|
|
942
|
-
else {
|
|
943
|
-
this.#baseNamespace = "TypeSpec.Service";
|
|
944
|
-
}
|
|
945
|
-
}
|
|
946
|
-
return this.#baseNamespace;
|
|
947
|
-
}
|
|
948
1029
|
}
|
|
949
1030
|
function processNameSpace(program, target, service) {
|
|
950
1031
|
if (!service)
|