@typespec/http-server-csharp 0.58.0-alpha.10-dev.0 → 0.58.0-alpha.10-dev.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,15 +1,16 @@
1
1
  import { getDoc, getNamespaceFullName, getService, isErrorModel, isNeverType, isNullType, isTemplateDeclaration, isVoidType, } from "@typespec/compiler";
2
2
  import { CodeTypeEmitter, StringBuilder, code, createAssetEmitter, } from "@typespec/compiler/emitter-framework";
3
3
  import { createRekeyableMap } from "@typespec/compiler/utils";
4
- import { Visibility, createMetadataInfo, getHeaderFieldName, getHttpOperation, getHttpPart, isHeader, isPathParam, isQueryParam, isStatusCode, } from "@typespec/http";
4
+ import { getHttpOperation, getHttpPart, isStatusCode, } from "@typespec/http";
5
5
  import { getResourceOperation } from "@typespec/rest";
6
6
  import { execFile } from "child_process";
7
+ import { getEncodedNameAttribute } from "./attributes.js";
7
8
  import { GeneratedFileHeader, GeneratedFileHeaderWithNullable, getSerializationSourceFiles, } from "./boilerplate.js";
8
9
  import { CSharpSourceType, CSharpType, NameCasingType, } from "./interfaces.js";
9
10
  import { reportDiagnostic } from "./lib.js";
10
- import { getBusinessLogicImplementations, getScaffoldingHelpers, } from "./scaffolding.js";
11
+ import { getBusinessLogicImplementations, } from "./scaffolding.js";
11
12
  import { getRecordType, isKnownReferenceType } from "./type-helpers.js";
12
- import { HttpMetadata, UnknownType, coalesceTypes, ensureCSharpIdentifier, ensureCleanDirectory, formatComment, getCSharpIdentifier, getCSharpStatusCode, getCSharpType, getCSharpTypeForIntrinsic, getCSharpTypeForScalar, getModelAttributes, getModelInstantiationName, getOperationVerbDecorator, isValueType, } from "./utils.js";
13
+ import { CSharpOperationHelpers, HttpMetadata, UnknownType, coalesceTypes, coalesceUnionTypes, ensureCSharpIdentifier, ensureCleanDirectory, formatComment, getBusinessLogicCallParameters, getBusinessLogicDeclParameters, getCSharpIdentifier, getCSharpStatusCode, getCSharpType, getCSharpTypeForIntrinsic, getCSharpTypeForScalar, getHttpDeclParameters, getModelAttributes, getModelDeclarationName, getModelInstantiationName, getOperationVerbDecorator, isEmptyResponseModel, isValueType, } from "./utils.js";
13
14
  export async function $onEmit(context) {
14
15
  let _unionCounter = 0;
15
16
  const controllers = new Map();
@@ -27,14 +28,8 @@ export async function $onEmit(context) {
27
28
  #useSwagger = context.options["use-swaggerui"] || false;
28
29
  #openapiPath = context.options["openapi-path"] || "openapi/openapi.yaml";
29
30
  #mockRegistrations = new Map();
30
- #mockHelpers = this.#emitMocks === "all"
31
- ? getScaffoldingHelpers(this.emitter, this.#useSwagger, this.#openapiPath)
32
- : [];
33
31
  #mockFiles = [];
34
- #metaInfo = createMetadataInfo(this.emitter.getProgram(), {
35
- canonicalVisibility: Visibility.Read,
36
- canShareProperty: (p) => true,
37
- });
32
+ #opHelpers = new CSharpOperationHelpers(this.emitter);
38
33
  arrayDeclaration(array, name, elementType) {
39
34
  return this.emitter.result.declaration(ensureCSharpIdentifier(this.emitter.getProgram(), array, name), code `${this.emitter.emitTypeReference(elementType)}[]`);
40
35
  }
@@ -45,8 +40,8 @@ export async function $onEmit(context) {
45
40
  return this.emitter.result.rawCode(code `${boolean.value === true ? "true" : "false"}`);
46
41
  }
47
42
  unionLiteral(union) {
48
- const csType = this.#coalesceUnionTypes(union);
49
- return this.emitter.result.rawCode(csType && csType.isBuiltIn ? csType.name : "object");
43
+ const csType = coalesceUnionTypes(this.emitter.getProgram(), union);
44
+ return this.emitter.result.rawCode(csType ? csType.getTypeReference(this.emitter.getContext()?.scope) : "object");
50
45
  }
51
46
  declarationName(declarationType) {
52
47
  switch (declarationType.kind) {
@@ -60,7 +55,7 @@ export async function $onEmit(context) {
60
55
  return getCSharpIdentifier(declarationType.name, NameCasingType.Class);
61
56
  case "Model":
62
57
  if (!declarationType.name)
63
- return `Model${_unionCounter++}`;
58
+ return getModelDeclarationName(this.emitter.getProgram(), declarationType, `${_unionCounter++}`);
64
59
  return getCSharpIdentifier(declarationType.name, NameCasingType.Class);
65
60
  case "Operation":
66
61
  return getCSharpIdentifier(declarationType.name, NameCasingType.Class);
@@ -91,7 +86,7 @@ export async function $onEmit(context) {
91
86
  ${attributes.map((attribute) => attribute.getApplicationString(this.emitter.getContext().scope)).join("\n")}
92
87
  public enum ${enumName}
93
88
  {
94
- ${this.emitter.emitEnumMembers(en)};
89
+ ${this.emitter.emitEnumMembers(en)}
95
90
  }
96
91
  } `);
97
92
  }
@@ -100,11 +95,7 @@ export async function $onEmit(context) {
100
95
  const enumFile = this.emitter.createSourceFile(`models/${enumName}.cs`);
101
96
  enumFile.meta[this.#sourceTypeKey] = CSharpSourceType.Model;
102
97
  const enumNamespace = `${this.#getOrSetBaseNamespace(en)}.Models`;
103
- return {
104
- namespace: enumNamespace,
105
- file: enumFile,
106
- scope: enumFile.globalScope,
107
- };
98
+ return this.#createEnumContext(enumNamespace, enumFile, enumName);
108
99
  }
109
100
  enumMembers(en) {
110
101
  const result = new StringBuilder();
@@ -113,9 +104,11 @@ export async function $onEmit(context) {
113
104
  i++;
114
105
  const memberName = ensureCSharpIdentifier(this.emitter.getProgram(), member, name);
115
106
  this.#metadateMap.set(member, { name: memberName });
116
- result.push(code `${ensureCSharpIdentifier(this.emitter.getProgram(), member, name)} = "${member.value ? member.value : name}"`);
107
+ result.push(code `
108
+ [JsonStringEnumMemberName("${member.value ? member.value : name}")]
109
+ ${ensureCSharpIdentifier(this.emitter.getProgram(), member, name)}`);
117
110
  if (i < en.members.size)
118
- result.pushLiteralSegment(", ");
111
+ result.pushLiteralSegment(",\n");
119
112
  }
120
113
  return this.emitter.result.rawCode(result.reduce());
121
114
  }
@@ -243,9 +236,6 @@ export async function $onEmit(context) {
243
236
  });
244
237
  return this.modelDeclaration(model, modelName);
245
238
  }
246
- #isRecord(type) {
247
- return type.kind === "Model" && type.name === "Record" && type.indexer !== undefined;
248
- }
249
239
  #isInheritedProperty(property) {
250
240
  const visited = [];
251
241
  function isInherited(model, propertyName) {
@@ -266,10 +256,15 @@ export async function $onEmit(context) {
266
256
  modelPropertyLiteral(property) {
267
257
  if (isStatusCode(this.emitter.getProgram(), property))
268
258
  return "";
269
- const propertyName = ensureCSharpIdentifier(this.emitter.getProgram(), property, property.name);
270
- const [typeName, typeDefault, nullable] = this.#findPropertyType(property);
259
+ let propertyName = ensureCSharpIdentifier(this.emitter.getProgram(), property, property.name);
260
+ const { typeReference: typeName, defaultValue: typeDefault, nullableType: nullable, } = this.#findPropertyType(property);
271
261
  const doc = getDoc(this.emitter.getProgram(), property);
272
262
  const attributes = getModelAttributes(this.emitter.getProgram(), property, propertyName);
263
+ const modelName = this.emitter.getContext()["name"];
264
+ if (modelName === propertyName) {
265
+ propertyName = `${propertyName}Prop`;
266
+ attributes.push(getEncodedNameAttribute(this.emitter.getProgram(), property, propertyName));
267
+ }
273
268
  // eslint-disable-next-line @typescript-eslint/no-deprecated
274
269
  const defaultValue = property.default
275
270
  ? // eslint-disable-next-line @typescript-eslint/no-deprecated
@@ -282,7 +277,7 @@ export async function $onEmit(context) {
282
277
  `);
283
278
  }
284
279
  #findPropertyType(property) {
285
- return this.#getTypeInfoForTsType(property.type);
280
+ return this.#opHelpers.getTypeInfo(this.emitter.getProgram(), property.type);
286
281
  }
287
282
  #isMultipartRequest(operation) {
288
283
  const body = operation.parameters.body;
@@ -298,118 +293,6 @@ export async function $onEmit(context) {
298
293
  }
299
294
  return false;
300
295
  }
301
- #getTypeInfoForUnion(union) {
302
- const propResult = this.#getNonNullableTsType(union);
303
- if (propResult === undefined) {
304
- return [
305
- code `${emitter.emitTypeReference(union)}`,
306
- undefined,
307
- [...union.variants.values()].filter((v) => isNullType(v.type)).length > 0,
308
- ];
309
- }
310
- const [typeName, typeDefault, _] = this.#getTypeInfoForTsType(propResult.type);
311
- return [typeName, typeDefault, propResult.nullable];
312
- }
313
- #getTypeInfoForTsType(tsType) {
314
- function extractStringValue(type, span) {
315
- switch (type.kind) {
316
- case "String":
317
- return type.value;
318
- case "Boolean":
319
- return `${type.value}`;
320
- case "Number":
321
- return type.valueAsString;
322
- case "StringTemplateSpan":
323
- if (type.isInterpolated) {
324
- return extractStringValue(type.type, span);
325
- }
326
- else {
327
- return type.type.value;
328
- }
329
- case "ModelProperty":
330
- return extractStringValue(type.type, span);
331
- case "EnumMember":
332
- if (type.value === undefined)
333
- return type.name;
334
- if (typeof type.value === "string")
335
- return type.value;
336
- if (typeof type.value === "number")
337
- return `${type.value}`;
338
- }
339
- reportDiagnostic(emitter.getProgram(), {
340
- code: "invalid-interpolation",
341
- target: span,
342
- format: {},
343
- });
344
- return "";
345
- }
346
- switch (tsType.kind) {
347
- case "String":
348
- return [code `string`, `"${tsType.value}"`, false];
349
- case "StringTemplate":
350
- const template = tsType;
351
- if (template.stringValue !== undefined)
352
- return [code `string`, `"${template.stringValue}"`, false];
353
- const spanResults = [];
354
- for (const span of template.spans) {
355
- spanResults.push(extractStringValue(span, span));
356
- }
357
- return [code `string`, `"${spanResults.join("")}"`, false];
358
- case "Boolean":
359
- return [code `bool`, `${tsType.value === true ? true : false}`, false];
360
- case "Number":
361
- const [type, value] = this.#findNumericType(tsType);
362
- return [code `${type}`, `${value}`, false];
363
- case "Tuple":
364
- const defaults = [];
365
- const [csharpType, isObject] = this.#coalesceTypes(tsType.values);
366
- if (isObject)
367
- return ["object[]", undefined, false];
368
- for (const value of tsType.values) {
369
- const [_, itemDefault] = this.#getTypeInfoForTsType(value);
370
- defaults.push(itemDefault);
371
- }
372
- return [
373
- code `${csharpType.getTypeReference()}[]`,
374
- `[${defaults.join(", ")}]`,
375
- csharpType.isNullable,
376
- ];
377
- case "Object":
378
- return [code `object`, undefined, false];
379
- case "Model":
380
- if (this.#isRecord(tsType)) {
381
- return [code `JsonObject`, undefined, false];
382
- }
383
- return [code `${emitter.emitTypeReference(tsType)}`, undefined, false];
384
- case "ModelProperty":
385
- return this.#getTypeInfoForTsType(tsType.type);
386
- case "Enum":
387
- return [code `${emitter.emitTypeReference(tsType)}`, undefined, false];
388
- case "EnumMember":
389
- if (typeof tsType.value === "number") {
390
- const stringValue = tsType.value.toString();
391
- if (stringValue.includes(".") || stringValue.includes("e"))
392
- return ["double", stringValue, false];
393
- return ["int", stringValue, false];
394
- }
395
- if (typeof tsType.value === "string") {
396
- return ["string", tsType.value, false];
397
- }
398
- return [code `object`, undefined, false];
399
- case "Union":
400
- return this.#getTypeInfoForUnion(tsType);
401
- case "UnionVariant":
402
- return this.#getTypeInfoForTsType(tsType.type);
403
- default:
404
- return [code `${emitter.emitTypeReference(tsType)}`, undefined, false];
405
- }
406
- }
407
- #findNumericType(type) {
408
- const stringValue = type.valueAsString;
409
- if (stringValue.includes(".") || stringValue.includes("e"))
410
- return ["double", stringValue];
411
- return ["int", stringValue];
412
- }
413
296
  modelPropertyReference(property) {
414
297
  return code `${this.emitter.emitTypeReference(property.type)}`;
415
298
  }
@@ -484,30 +367,17 @@ export async function $onEmit(context) {
484
367
  const returnInfo = coalesceTypes(this.emitter.getProgram(), returnTypes, context.namespace);
485
368
  const returnType = returnInfo?.type || UnknownType;
486
369
  const opName = ensureCSharpIdentifier(this.emitter.getProgram(), operation, name, NameCasingType.Method);
487
- let opDecl;
488
- let opImpl;
489
- if (this.#isMultipartRequest(httpOp)) {
490
- opImpl = {
491
- methodName: `${opName}Async`,
492
- methodParams: `${this.#emitInterfaceOperationParameters(operation, "MultipartReader reader")}`,
493
- returnType: `${returnType.name === "void" ? "Task" : `Task<${returnType.getTypeReference(context.scope)}>`}`,
494
- instantiatedReturnType: returnType.name === "void"
495
- ? undefined
496
- : `${returnType.getTypeReference(context.scope)}`,
497
- };
498
- opDecl = this.emitter.result.declaration(opName, code `${doc ? `${formatComment(doc)}\n` : ""}${opImpl.returnType} ${opImpl.methodName}( ${opImpl.methodParams});`);
499
- }
500
- else {
501
- opImpl = {
502
- methodName: `${opName}Async`,
503
- methodParams: `${this.#emitInterfaceOperationParameters(operation)}`,
504
- returnType: `${returnType.name === "void" ? "Task" : `Task<${returnType.getTypeReference(context.scope)}>`}`,
505
- instantiatedReturnType: returnType.name === "void"
506
- ? undefined
507
- : `${returnType.getTypeReference(context.scope)}`,
508
- };
509
- opDecl = this.emitter.result.declaration(opName, code `${doc ? `${formatComment(doc)}\n` : ""}${opImpl.returnType} ${opImpl.methodName}( ${opImpl.methodParams});`);
510
- }
370
+ const parameters = this.#opHelpers.getParameters(this.emitter.getProgram(), httpOp);
371
+ const opImpl = {
372
+ methodName: `${opName}Async`,
373
+ methodParams: `${getBusinessLogicDeclParameters(parameters)}`,
374
+ returnType: returnType,
375
+ returnTypeName: `${returnType.name === "void" ? "Task" : `Task<${returnType.getTypeReference(context.scope)}>`}`,
376
+ instantiatedReturnType: returnType.name === "void"
377
+ ? undefined
378
+ : `${returnType.getTypeReference(context.scope)}`,
379
+ };
380
+ const opDecl = this.emitter.result.declaration(opName, code `${doc ? `${formatComment(doc)}\n` : ""}${opImpl.returnTypeName} ${opImpl.methodName}( ${opImpl.methodParams});`);
511
381
  mock.methods.push(opImpl);
512
382
  builder.push(code `${opDecl.value}\n`);
513
383
  this.emitter.emitInterfaceOperation(operation);
@@ -529,9 +399,8 @@ export async function $onEmit(context) {
529
399
  const doc = getDoc(this.emitter.getProgram(), operation);
530
400
  const [httpOperation, _] = getHttpOperation(this.emitter.getProgram(), operation);
531
401
  const multipart = this.#isMultipartRequest(httpOperation);
532
- const declParams = !multipart
533
- ? this.#emitHttpOperationParameters(httpOperation)
534
- : this.#emitHttpOperationParameters(httpOperation, true);
402
+ const parameters = this.#opHelpers.getParameters(this.emitter.getProgram(), httpOperation);
403
+ const declParams = getHttpDeclParameters(parameters);
535
404
  if (multipart) {
536
405
  const context = this.emitter.getContext();
537
406
  context.file.imports.set("Microsoft.AspNetCore.WebUtilities", [
@@ -561,9 +430,9 @@ export async function $onEmit(context) {
561
430
  public virtual async Task<IActionResult> ${operationName}(${declParams})
562
431
  {
563
432
  ${hasResponseValue
564
- ? `var result = await ${this.emitter.getContext().resourceName}Impl.${operationName}Async(${this.#emitOperationCallParameters(httpOperation)});
433
+ ? `var result = await ${this.emitter.getContext().resourceName}Impl.${operationName}Async(${getBusinessLogicCallParameters(parameters)});
565
434
  return ${resultString}(result);`
566
- : `await ${this.emitter.getContext().resourceName}Impl.${operationName}Async(${this.#emitOperationCallParameters(httpOperation)});
435
+ : `await ${this.emitter.getContext().resourceName}Impl.${operationName}Async(${getBusinessLogicCallParameters(parameters)});
567
436
  return ${resultString}();`}
568
437
  }`);
569
438
  }
@@ -585,9 +454,9 @@ export async function $onEmit(context) {
585
454
 
586
455
  var reader = new MultipartReader(boundary, Request.Body);
587
456
  ${hasResponseValue
588
- ? `var result = await ${this.emitter.getContext().resourceName}Impl.${operationName}Async(${this.#emitOperationCallParameters(httpOperation, "reader")});
457
+ ? `var result = await ${this.emitter.getContext().resourceName}Impl.${operationName}Async(${getBusinessLogicCallParameters(parameters)});
589
458
  return ${resultString}(result);`
590
- : `await ${this.emitter.getContext().resourceName}Impl.${operationName}Async(${this.#emitOperationCallParameters(httpOperation, "reader")});
459
+ : `await ${this.emitter.getContext().resourceName}Impl.${operationName}Async(${getBusinessLogicCallParameters(parameters)});
591
460
  return ${resultString}();`}
592
461
  }`);
593
462
  }
@@ -603,7 +472,8 @@ export async function $onEmit(context) {
603
472
  const operationName = ensureCSharpIdentifier(this.emitter.getProgram(), operation, name, NameCasingType.Method);
604
473
  const doc = getDoc(this.emitter.getProgram(), operation);
605
474
  const [httpOperation, _] = getHttpOperation(this.emitter.getProgram(), operation);
606
- const declParams = this.#emitHttpOperationParameters(httpOperation);
475
+ const parameters = this.#opHelpers.getParameters(this.emitter.getProgram(), httpOperation);
476
+ const declParams = getHttpDeclParameters(parameters);
607
477
  const responseInfo = this.#getOperationResponse(httpOperation);
608
478
  const status = responseInfo?.statusCode ?? 200;
609
479
  const response = responseInfo?.resultType ??
@@ -623,9 +493,9 @@ export async function $onEmit(context) {
623
493
  public virtual async Task<IActionResult> ${operationName}(${declParams})
624
494
  {
625
495
  ${hasResponseValue
626
- ? `var result = await ${this.emitter.getContext().resourceName}Impl.${operationName}Async(${this.#emitOperationCallParameters(httpOperation)});
496
+ ? `var result = await ${this.emitter.getContext().resourceName}Impl.${operationName}Async(${getBusinessLogicCallParameters(parameters)});
627
497
  return ${resultString}(result);`
628
- : `await ${this.emitter.getContext().resourceName}Impl.${operationName}Async(${this.#emitOperationCallParameters(httpOperation)});
498
+ : `await ${this.emitter.getContext().resourceName}Impl.${operationName}Async(${getBusinessLogicCallParameters(parameters)});
629
499
  return ${resultString}();`}
630
500
  }`);
631
501
  }
@@ -683,68 +553,8 @@ export async function $onEmit(context) {
683
553
  const resultType = result?.type || UnknownType;
684
554
  return resultType.getTypeReference(context.scope);
685
555
  }
686
- #emitInterfaceOperationParameters(operation, bodyParam) {
687
- const signature = new StringBuilder();
688
- const requiredParams = [];
689
- const optionalParams = [];
690
- let totalParams = 0;
691
- if (bodyParam !== undefined)
692
- totalParams++;
693
- const validParams = [...operation.parameters.properties.entries()].filter(([_, p]) => isValidParameter(this.emitter.getProgram(), p));
694
- for (const [_, parameter] of validParams) {
695
- if (!isContentTypeHeader(this.emitter.getProgram(), parameter) &&
696
- (bodyParam === undefined || isHttpMetadata(this.emitter.getProgram(), parameter))) {
697
- if (parameter.optional || parameter.defaultValue)
698
- optionalParams.push(parameter);
699
- else
700
- requiredParams.push(parameter);
701
- totalParams++;
702
- }
703
- }
704
- let i = 1;
705
- for (const requiredParam of requiredParams) {
706
- const [paramType, _, __] = this.#findPropertyType(requiredParam);
707
- signature.push(code `${paramType} ${ensureCSharpIdentifier(this.emitter.getProgram(), requiredParam, requiredParam.name, NameCasingType.Parameter)}${i++ < totalParams ? ", " : ""}`);
708
- }
709
- if (bodyParam) {
710
- signature.push(bodyParam);
711
- if (i++ < totalParams)
712
- signature.push(", ");
713
- }
714
- for (const optionalParam of optionalParams) {
715
- const [paramType, _, __] = this.#findPropertyType(optionalParam);
716
- signature.push(code `${paramType}? ${ensureCSharpIdentifier(this.emitter.getProgram(), optionalParam, optionalParam.name, NameCasingType.Parameter)}${i++ < totalParams ? ", " : ""}`);
717
- }
718
- return signature.reduce();
719
- }
720
- #emitHttpOperationParameters(operation, bodyParameter) {
721
- const signature = new StringBuilder();
722
- const bodyParam = operation.parameters.body;
723
- let i = 0;
724
- //const pathParameters = operation.parameters.parameters.filter((p) => p.type === "path");
725
- const validParams = operation.parameters.parameters.filter((p) => isValidParameter(this.emitter.getProgram(), p.param));
726
- const requiredParams = validParams.filter((p) => p.type === "path" || (!p.param.optional && p.param.defaultValue === undefined));
727
- const optionalParams = validParams.filter((p) => p.type !== "path" && (p.param.optional || p.param.defaultValue !== undefined));
728
- for (const parameter of requiredParams) {
729
- signature.push(code `${this.#emitOperationSignatureParameter(operation, parameter)}${++i < requiredParams.length ? ", " : ""}`);
730
- }
731
- if (requiredParams.length > 0 &&
732
- (optionalParams.length > 0 || (bodyParameter === undefined && bodyParam !== undefined))) {
733
- signature.push(code `, `);
734
- }
735
- if (bodyParameter === undefined) {
736
- if (bodyParam !== undefined) {
737
- signature.push(code `${this.emitter.emitTypeReference(this.#metaInfo.getEffectivePayloadType(bodyParam.type, Visibility.Create || Visibility.Update))} body${requiredParams.length > 0 && optionalParams.length > 0 ? ", " : ""}`);
738
- }
739
- }
740
- i = 0;
741
- for (const parameter of optionalParams) {
742
- signature.push(code `${this.#emitOperationSignatureParameter(operation, parameter)}${++i < optionalParams.length ? ", " : ""}`);
743
- }
744
- return signature.reduce();
745
- }
746
556
  unionDeclaration(union, name) {
747
- const baseType = this.#coalesceUnionTypes(union);
557
+ const baseType = coalesceUnionTypes(this.emitter.getProgram(), union);
748
558
  if (baseType.isBuiltIn && baseType.name === "string") {
749
559
  const program = this.emitter.getProgram();
750
560
  const unionName = ensureCSharpIdentifier(program, union, name);
@@ -770,7 +580,7 @@ export async function $onEmit(context) {
770
580
  return this.emitter.result.rawCode(code `${baseType.getTypeReference()}`);
771
581
  }
772
582
  unionDeclarationContext(union) {
773
- const baseType = this.#coalesceUnionTypes(union);
583
+ const baseType = coalesceUnionTypes(this.emitter.getProgram(), union);
774
584
  if (baseType.isBuiltIn && baseType.name === "string") {
775
585
  const unionName = ensureCSharpIdentifier(this.emitter.getProgram(), union, union.name || "Union");
776
586
  const unionFile = this.emitter.createSourceFile(`models/${unionName}.cs`);
@@ -811,89 +621,6 @@ export async function $onEmit(context) {
811
621
  unionVariantContext(union) {
812
622
  return super.unionVariantContext(union);
813
623
  }
814
- #emitOperationSignatureParameter(operation, httpParam) {
815
- const name = httpParam.param.name;
816
- const parameter = httpParam.param;
817
- const emittedName = ensureCSharpIdentifier(this.emitter.getProgram(), parameter, name, NameCasingType.Parameter);
818
- let [emittedType, emittedDefault, _] = this.#findPropertyType(parameter);
819
- if (emittedType.toString().endsWith("[]"))
820
- emittedDefault = undefined;
821
- // eslint-disable-next-line @typescript-eslint/no-deprecated
822
- const defaultValue = parameter.default
823
- ? // eslint-disable-next-line @typescript-eslint/no-deprecated
824
- code `${this.emitter.emitType(parameter.default)}`
825
- : emittedDefault;
826
- return this.emitter.result.rawCode(code `${httpParam.type !== "path" ? this.#emitParameterAttribute(httpParam) : ""}${emittedType} ${emittedName}${defaultValue === undefined ? "" : ` = ${defaultValue}`}`);
827
- }
828
- #getBodyParameters(operation) {
829
- const bodyParam = operation.parameters.body;
830
- if (bodyParam === undefined)
831
- return undefined;
832
- if (bodyParam.property !== undefined)
833
- return [bodyParam.property];
834
- if (bodyParam.type.kind !== "Model" || bodyParam.type.properties.size < 1)
835
- return undefined;
836
- return [...bodyParam.type.properties.values()];
837
- }
838
- #emitOperationCallParameters(operation, bodyParameter = "body") {
839
- const signature = new StringBuilder();
840
- let i = 0;
841
- const bodyParameters = this.#getBodyParameters(operation);
842
- //const pathParameters = operation.parameters.parameters.filter((p) => p.type === "path");
843
- const valid = operation.parameters.parameters.filter((p) => isValidParameter(this.emitter.getProgram(), p.param));
844
- const required = valid.filter((p) => p.type === "path" || (!p.param.optional && p.param.defaultValue === undefined));
845
- const optional = valid.filter((p) => p.type !== "path" && (p.param.optional || p.param.defaultValue !== undefined));
846
- for (const parameter of required) {
847
- const contentType = isContentTypeHeader(this.emitter.getProgram(), parameter.param);
848
- i++;
849
- if (!isNeverType(parameter.param.type) &&
850
- !isNullType(parameter.param.type) &&
851
- !isVoidType(parameter.param.type) &&
852
- !contentType) {
853
- signature.push(code `${this.#emitOperationCallParameter(operation, parameter)}${i < valid.length || bodyParameters !== undefined ? ", " : ""}`);
854
- }
855
- }
856
- if (bodyParameters !== undefined) {
857
- if (bodyParameters.length === 1) {
858
- signature.push(code `${bodyParameter}`);
859
- }
860
- else {
861
- let j = 0;
862
- for (const parameter of bodyParameters) {
863
- j++;
864
- const propertyName = ensureCSharpIdentifier(this.emitter.getProgram(), parameter, parameter.name, NameCasingType.Property);
865
- signature.push(code `${bodyParameter}?.${propertyName}${j < bodyParameters.length || i < valid.length ? ", " : ""}`);
866
- }
867
- }
868
- }
869
- for (const parameter of optional) {
870
- const contentType = isContentTypeHeader(this.emitter.getProgram(), parameter.param);
871
- i++;
872
- if (!isNeverType(parameter.param.type) &&
873
- !isNullType(parameter.param.type) &&
874
- !isVoidType(parameter.param.type) &&
875
- !contentType) {
876
- signature.push(code `${this.#emitOperationCallParameter(operation, parameter)}${i < valid.length ? ", " : ""}`);
877
- }
878
- }
879
- return signature.reduce();
880
- }
881
- #emitOperationCallParameter(operation, httpParam) {
882
- const name = httpParam.param.name;
883
- const parameter = httpParam.param;
884
- const emittedName = ensureCSharpIdentifier(this.emitter.getProgram(), parameter, name, NameCasingType.Parameter);
885
- return this.emitter.result.rawCode(code `${emittedName}`);
886
- }
887
- #emitParameterAttribute(parameter) {
888
- switch (parameter.type) {
889
- case "header":
890
- return code `[FromHeader(Name="${parameter.name}")] `;
891
- case "query":
892
- return code `[FromQuery(Name="${parameter.name}")] `;
893
- default:
894
- return "";
895
- }
896
- }
897
624
  #createModelContext(namespace, file, name) {
898
625
  const context = {
899
626
  namespace: namespace,
@@ -906,9 +633,26 @@ export async function $onEmit(context) {
906
633
  context.file.imports.set("System.Text.Json.Serialization", [
907
634
  "System.Text.Json.Serialization",
908
635
  ]);
636
+ context.file.imports.set("TypeSpec.Helpers.JsonConverters", [
637
+ "TypeSpec.Helpers.JsonConverters",
638
+ ]);
639
+ return context;
640
+ }
641
+ #createEnumContext(namespace, file, name) {
642
+ const context = {
643
+ namespace: namespace,
644
+ name: name,
645
+ file: file,
646
+ scope: file.globalScope,
647
+ };
648
+ context.file.imports.set("System.Text.Json", ["System.Text.Json"]);
649
+ context.file.imports.set("System.Text.Json.Serialization", [
650
+ "System.Text.Json.Serialization",
651
+ ]);
909
652
  return context;
910
653
  }
911
654
  #createOrGetResourceContext(name, operation, resource) {
655
+ name = ensureCSharpIdentifier(this.emitter.getProgram(), operation, name, NameCasingType.Class);
912
656
  let context = controllers.get(name);
913
657
  if (context !== undefined)
914
658
  return context;
@@ -962,12 +706,6 @@ export async function $onEmit(context) {
962
706
  if (sourceFile === libFile.source)
963
707
  return libFile.emitted;
964
708
  }
965
- if (this.#emitMocks === "all") {
966
- for (const helper of this.#mockHelpers) {
967
- if (sourceFile === helper.source)
968
- return helper.emitted;
969
- }
970
- }
971
709
  if (this.#mockFiles.length > 0) {
972
710
  for (const mock of this.#mockFiles) {
973
711
  if (sourceFile === mock.source)
@@ -998,7 +736,7 @@ export async function $onEmit(context) {
998
736
  #emitControllerContents(file) {
999
737
  const namespace = file.meta.namespace;
1000
738
  const contents = new StringBuilder();
1001
- contents.push(`${this.#generatedFileHeader}\n\n`);
739
+ contents.push(`${this.#generatedFileHeaderWithNullable}\n\n`);
1002
740
  contents.push(code `${this.#emitUsings(file)}\n`);
1003
741
  contents.push("\n");
1004
742
  contents.push(`namespace ${namespace}.Controllers\n`);
@@ -1063,88 +801,13 @@ export async function $onEmit(context) {
1063
801
  return params.reduce();
1064
802
  return "";
1065
803
  }
1066
- #coalesceUnionTypes(union) {
1067
- const [result, _] = this.#coalesceTypes([...union.variants.values()].flatMap((v) => v.type));
1068
- return result;
1069
- }
1070
- #getNonNullableTsType(union) {
1071
- const types = [...union.variants.values()];
1072
- const nulls = types.flatMap((v) => v.type).filter((t) => isNullType(t));
1073
- const nonNulls = types.flatMap((v) => v.type).filter((t) => !isNullType(t));
1074
- if (nonNulls.length === 1)
1075
- return { type: nonNulls[0], nullable: nulls.length > 0 };
1076
- return undefined;
1077
- }
1078
- #coalesceTypes(types) {
1079
- const defaultValue = [
1080
- new CSharpType({
1081
- name: "object",
1082
- namespace: "System",
1083
- isValueType: false,
1084
- }),
1085
- true,
1086
- ];
1087
- let current = undefined;
1088
- let nullable = false;
1089
- for (const type of types) {
1090
- let candidate = undefined;
1091
- switch (type.kind) {
1092
- case "Boolean":
1093
- candidate = new CSharpType({ name: "bool", namespace: "System", isValueType: true });
1094
- break;
1095
- case "StringTemplate":
1096
- case "String":
1097
- candidate = new CSharpType({ name: "string", namespace: "System", isValueType: false });
1098
- break;
1099
- case "Number":
1100
- const stringValue = type.valueAsString;
1101
- if (stringValue.includes(".") || stringValue.includes("e")) {
1102
- candidate = new CSharpType({
1103
- name: "double",
1104
- namespace: "System",
1105
- isValueType: true,
1106
- });
1107
- }
1108
- else {
1109
- candidate = new CSharpType({ name: "int", namespace: "System", isValueType: true });
1110
- }
1111
- break;
1112
- case "Union":
1113
- candidate = this.#coalesceUnionTypes(type);
1114
- break;
1115
- case "Scalar":
1116
- candidate = getCSharpTypeForScalar(this.emitter.getProgram(), type);
1117
- break;
1118
- case "Intrinsic":
1119
- if (isNullType(type)) {
1120
- nullable = true;
1121
- candidate = current;
1122
- }
1123
- else {
1124
- return defaultValue;
1125
- }
1126
- break;
1127
- default:
1128
- return defaultValue;
1129
- }
1130
- current = current ?? candidate;
1131
- if (current === undefined || (candidate !== undefined && !candidate.equals(current)))
1132
- return defaultValue;
1133
- }
1134
- if (current !== undefined && nullable)
1135
- current.isNullable = true;
1136
- return current === undefined ? defaultValue : [current, false];
1137
- }
1138
804
  writeOutput(sourceFiles) {
1139
805
  for (const source of this.#libraryFiles) {
1140
806
  sourceFiles.push(source.source);
1141
807
  }
1142
808
  if (this.#emitMocks === "all") {
1143
- for (const helper of this.#mockHelpers) {
1144
- sourceFiles.push(helper.source);
1145
- }
1146
809
  if (this.#mockRegistrations.size > 0) {
1147
- const mocks = getBusinessLogicImplementations(this.emitter, this.#mockRegistrations);
810
+ const mocks = getBusinessLogicImplementations(this.emitter, this.#mockRegistrations, this.#useSwagger, this.#openapiPath);
1148
811
  this.#mockFiles.push(...mocks);
1149
812
  sourceFiles.push(...mocks.flatMap((l) => l.source));
1150
813
  }
@@ -1185,26 +848,6 @@ export async function $onEmit(context) {
1185
848
  return this.#baseNamespace;
1186
849
  }
1187
850
  }
1188
- function isEmptyResponseModel(program, model) {
1189
- if (model.kind !== "Model")
1190
- return false;
1191
- return model.properties.size === 1 && isStatusCode(program, [...model.properties.values()][0]);
1192
- }
1193
- function isContentTypeHeader(program, parameter) {
1194
- return (isHeader(program, parameter) &&
1195
- (parameter.name === "contentType" ||
1196
- getHeaderFieldName(program, parameter) === "Content-type"));
1197
- }
1198
- function isValidParameter(program, parameter) {
1199
- return (!isContentTypeHeader(program, parameter) &&
1200
- (parameter.type.kind !== "Intrinsic" || parameter.type.name !== "never"));
1201
- }
1202
- /** Determine whether the given parameter is http metadata */
1203
- function isHttpMetadata(program, property) {
1204
- return (isPathParam(program, property) ||
1205
- isHeader(program, property) ||
1206
- isQueryParam(program, property));
1207
- }
1208
851
  function processNameSpace(program, target, service) {
1209
852
  if (!service)
1210
853
  service = getService(program, target);