@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.
@@ -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
- #baseNamespace = undefined;
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
- return this.emitter.result.rawCode(code `${this.emitter.emitTypeReference(elementType)}[]`);
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 this.emitter.result.rawCode(code `${boolean.value === true ? "true" : "false"}`);
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 `${this.#generatedFileHeader}
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 = `${this.#getOrSetBaseNamespace(en)}.Models`;
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 `System.Text.Json.Nodes.JsonNode`);
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 `${this.#generatedFileHeader}
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 = `${this.#getOrSetBaseNamespace(model)}.Models`;
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 = `${this.#getOrSetBaseNamespace(model)}.Models`;
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
- return code `Dictionary<string, ${this.emitter.emitTypeReference(recordType)}>`;
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
- attributes.push(getEncodedNameAttribute(this.emitter.getProgram(), property, propertyName));
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` : ""}${`${attributes.map((attribute) => attribute.getApplicationString(this.emitter.getContext().scope)).join("\n")}${attributes?.length > 0 ? "\n" : ""}`}public ${this.#isInheritedProperty(property) ? "new " : ""}${typeName}${isValueType(this.emitter.getProgram(), property.type) && (property.optional || nullable)
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 `${this.#generatedFileHeaderWithNullable}
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
- const ifaceNamespace = this.#getOrSetBaseNamespace(iface);
426
- const modelNamespace = `${ifaceNamespace}.Models`;
427
- const context = this.#createModelContext(ifaceNamespace, sourceFile, ifaceName);
428
- context.file.imports.set("System", ["System"]);
429
- context.file.imports.set("System.Net", ["System.Net"]);
430
- context.file.imports.set("System.Text.Json", ["System.Text.Json"]);
431
- context.file.imports.set("System.Text.Json.Serialization", [
432
- "System.Text.Json.Serialization",
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
- context.file.imports.set("Microsoft.AspNetCore.WebUtilities", [
525
+ sourceFile.imports.set("Microsoft.AspNetCore.WebUtilities", [
438
526
  "Microsoft.AspNetCore.WebUtilities",
439
527
  ]);
440
528
  }
441
- context.file.imports.set(modelNamespace, [modelNamespace]);
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.#getOrSetBaseNamespace(iface);
451
- const namespace = `${ifaceNamespace}`;
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: [`${ifaceNamespace}.Models`],
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
- returnTypes.push(metadata.resolveLogicalResponseType(this.emitter.getProgram(), response));
550
+ const [_, responseType] = this.#resolveOperationResponse(response, httpOp.operation);
551
+ returnTypes.push(responseType);
465
552
  }
466
- const returnInfo = coalesceTypes(this.emitter.getProgram(), returnTypes, context.namespace);
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
- const builder = new StringBuilder();
629
- let i = 0;
630
- const validResponses = operation.responses.filter((r) => !isErrorModel(this.emitter.getProgram(), r.type) &&
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
- if (!isEmptyResponseModel(this.emitter.getProgram(), response.type))
641
- this.emitter.emitType(response.type);
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
- const responseType = new HttpMetadata().resolveLogicalResponseType(this.emitter.getProgram(), response);
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
- const result = getCSharpType(this.emitter.getProgram(), type, context.namespace);
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 (baseType.isBuiltIn && baseType.name === "string") {
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 `${this.#generatedFileHeader}
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
- const baseType = coalesceUnionTypes(this.emitter.getProgram(), union);
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 = `${this.#getOrSetBaseNamespace(union)}.Models`;
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 `${ensureCSharpIdentifier(this.emitter.getProgram(), variant, nameHint)} = "${variant.type.value}"`);
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
- const context = {
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
- let context = controllers.get(name);
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: sourceFile.meta["namespace"],
860
+ namespace: namespace,
768
861
  file: sourceFile,
769
862
  resourceName: name,
770
863
  scope: sourceFile.globalScope,
771
864
  resourceType: resource,
772
865
  };
773
- context.file.imports.set("System", ["System"]);
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)