@typespec/http-server-csharp 0.58.0-alpha.1 → 0.58.0-alpha.10-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.
Files changed (66) hide show
  1. package/cmd/hscs.js +2 -0
  2. package/dist/src/cli/cli.d.ts +2 -0
  3. package/dist/src/cli/cli.d.ts.map +1 -0
  4. package/dist/src/cli/cli.js +124 -0
  5. package/dist/src/cli/cli.js.map +1 -0
  6. package/dist/src/{attributes.d.ts → lib/attributes.d.ts} +1 -1
  7. package/dist/src/lib/attributes.d.ts.map +1 -0
  8. package/dist/src/{attributes.js → lib/attributes.js} +52 -8
  9. package/dist/src/lib/attributes.js.map +1 -0
  10. package/dist/src/{boilerplate.d.ts → lib/boilerplate.d.ts} +2 -0
  11. package/dist/src/lib/boilerplate.d.ts.map +1 -0
  12. package/dist/src/{boilerplate.js → lib/boilerplate.js} +105 -40
  13. package/dist/src/lib/boilerplate.js.map +1 -0
  14. package/dist/src/lib/index.d.ts.map +1 -0
  15. package/dist/src/lib/index.js.map +1 -0
  16. package/dist/src/{interfaces.d.ts → lib/interfaces.d.ts} +23 -0
  17. package/dist/src/lib/interfaces.d.ts.map +1 -0
  18. package/dist/src/{interfaces.js → lib/interfaces.js} +7 -3
  19. package/dist/src/lib/interfaces.js.map +1 -0
  20. package/dist/src/{lib.d.ts → lib/lib.d.ts} +25 -1
  21. package/dist/src/lib/lib.d.ts.map +1 -0
  22. package/dist/src/{lib.js → lib/lib.js} +31 -0
  23. package/dist/src/lib/lib.js.map +1 -0
  24. package/dist/src/lib/scaffolding.d.ts +21 -0
  25. package/dist/src/lib/scaffolding.d.ts.map +1 -0
  26. package/dist/src/lib/scaffolding.js +412 -0
  27. package/dist/src/lib/scaffolding.js.map +1 -0
  28. package/dist/src/lib/service.d.ts.map +1 -0
  29. package/dist/src/{service.js → lib/service.js} +332 -230
  30. package/dist/src/lib/service.js.map +1 -0
  31. package/dist/src/lib/testing/index.d.ts.map +1 -0
  32. package/dist/src/{testing → lib/testing}/index.js +1 -0
  33. package/dist/src/lib/testing/index.js.map +1 -0
  34. package/dist/src/lib/type-helpers.d.ts.map +1 -0
  35. package/dist/src/lib/type-helpers.js.map +1 -0
  36. package/dist/src/lib/utils.d.ts +88 -0
  37. package/dist/src/lib/utils.d.ts.map +1 -0
  38. package/dist/src/lib/utils.js +1173 -0
  39. package/dist/src/lib/utils.js.map +1 -0
  40. package/package.json +32 -23
  41. package/dist/src/attributes.d.ts.map +0 -1
  42. package/dist/src/attributes.js.map +0 -1
  43. package/dist/src/boilerplate.d.ts.map +0 -1
  44. package/dist/src/boilerplate.js.map +0 -1
  45. package/dist/src/index.d.ts.map +0 -1
  46. package/dist/src/index.js.map +0 -1
  47. package/dist/src/interfaces.d.ts.map +0 -1
  48. package/dist/src/interfaces.js.map +0 -1
  49. package/dist/src/lib.d.ts.map +0 -1
  50. package/dist/src/lib.js.map +0 -1
  51. package/dist/src/service.d.ts.map +0 -1
  52. package/dist/src/service.js.map +0 -1
  53. package/dist/src/testing/index.d.ts.map +0 -1
  54. package/dist/src/testing/index.js.map +0 -1
  55. package/dist/src/type-helpers.d.ts.map +0 -1
  56. package/dist/src/type-helpers.js.map +0 -1
  57. package/dist/src/utils.d.ts +0 -48
  58. package/dist/src/utils.d.ts.map +0 -1
  59. package/dist/src/utils.js +0 -628
  60. package/dist/src/utils.js.map +0 -1
  61. /package/dist/src/{index.d.ts → lib/index.d.ts} +0 -0
  62. /package/dist/src/{index.js → lib/index.js} +0 -0
  63. /package/dist/src/{service.d.ts → lib/service.d.ts} +0 -0
  64. /package/dist/src/{testing → lib/testing}/index.d.ts +0 -0
  65. /package/dist/src/{type-helpers.d.ts → lib/type-helpers.d.ts} +0 -0
  66. /package/dist/src/{type-helpers.js → lib/type-helpers.js} +0 -0
@@ -1,29 +1,35 @@
1
- import { getDoc, getNamespaceFullName, getService, isErrorModel, isNeverType, isNullType, isVoidType, } from "@typespec/compiler";
2
- import { CodeTypeEmitter, Placeholder, StringBuilder, code, createAssetEmitter, } from "@typespec/compiler/emitter-framework";
3
- import { Visibility, createMetadataInfo, getHttpOperation, isStatusCode, } from "@typespec/http";
1
+ import { getDoc, getNamespaceFullName, getService, isErrorModel, isNeverType, isNullType, isTemplateDeclaration, isVoidType, } from "@typespec/compiler";
2
+ import { CodeTypeEmitter, StringBuilder, code, createAssetEmitter, } from "@typespec/compiler/emitter-framework";
3
+ import { createRekeyableMap } from "@typespec/compiler/utils";
4
+ import { getHttpOperation, getHttpPart, isStatusCode, } from "@typespec/http";
4
5
  import { getResourceOperation } from "@typespec/rest";
5
6
  import { execFile } from "child_process";
6
- import { getSerializationSourceFiles } from "./boilerplate.js";
7
+ import { getEncodedNameAttribute } from "./attributes.js";
8
+ import { GeneratedFileHeader, GeneratedFileHeaderWithNullable, getSerializationSourceFiles, } from "./boilerplate.js";
7
9
  import { CSharpSourceType, CSharpType, NameCasingType, } from "./interfaces.js";
8
10
  import { reportDiagnostic } from "./lib.js";
11
+ import { getBusinessLogicImplementations, } from "./scaffolding.js";
9
12
  import { getRecordType, isKnownReferenceType } from "./type-helpers.js";
10
- import { HttpMetadata, UnknownType, coalesceTypes, ensureCSharpIdentifier, ensureCleanDirectory, formatComment, getCSharpIdentifier, getCSharpStatusCode, getCSharpType, 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";
11
14
  export async function $onEmit(context) {
12
15
  let _unionCounter = 0;
13
16
  const controllers = new Map();
14
17
  const NoResourceContext = "RPCOperations";
18
+ const doNotEmit = context.program.compilerOptions.noEmit || false;
15
19
  class CSharpCodeEmitter extends CodeTypeEmitter {
16
20
  #metadateMap = new Map();
17
- #licenseHeader = `// Copyright (c) Microsoft Corporation. All rights reserved.
18
- // Licensed under the MIT License.`;
21
+ #generatedFileHeaderWithNullable = GeneratedFileHeaderWithNullable;
22
+ #generatedFileHeader = GeneratedFileHeader;
19
23
  #sourceTypeKey = "sourceType";
20
24
  #libraryFiles = getSerializationSourceFiles(this.emitter);
21
25
  #baseNamespace = undefined;
22
26
  #emitterOutputType = context.options["output-type"];
23
- #metaInfo = createMetadataInfo(this.emitter.getProgram(), {
24
- canonicalVisibility: Visibility.Read,
25
- canShareProperty: (p) => true,
26
- });
27
+ #emitMocks = context.options["emit-mocks"];
28
+ #useSwagger = context.options["use-swaggerui"] || false;
29
+ #openapiPath = context.options["openapi-path"] || "openapi/openapi.yaml";
30
+ #mockRegistrations = new Map();
31
+ #mockFiles = [];
32
+ #opHelpers = new CSharpOperationHelpers(this.emitter);
27
33
  arrayDeclaration(array, name, elementType) {
28
34
  return this.emitter.result.declaration(ensureCSharpIdentifier(this.emitter.getProgram(), array, name), code `${this.emitter.emitTypeReference(elementType)}[]`);
29
35
  }
@@ -34,14 +40,23 @@ export async function $onEmit(context) {
34
40
  return this.emitter.result.rawCode(code `${boolean.value === true ? "true" : "false"}`);
35
41
  }
36
42
  unionLiteral(union) {
37
- const csType = this.#coalesceUnionTypes(union);
43
+ const csType = coalesceUnionTypes(this.emitter.getProgram(), union);
38
44
  return this.emitter.result.rawCode(csType && csType.isBuiltIn ? csType.name : "object");
39
45
  }
40
46
  declarationName(declarationType) {
41
47
  switch (declarationType.kind) {
42
48
  case "Enum":
49
+ if (!declarationType.name)
50
+ return `Enum${_unionCounter++}`;
51
+ return getCSharpIdentifier(declarationType.name, NameCasingType.Class);
43
52
  case "Interface":
53
+ if (!declarationType.name)
54
+ return `Interface${_unionCounter++}`;
55
+ return getCSharpIdentifier(declarationType.name, NameCasingType.Class);
44
56
  case "Model":
57
+ if (!declarationType.name)
58
+ return getModelDeclarationName(this.emitter.getProgram(), declarationType, `${_unionCounter++}`);
59
+ return getCSharpIdentifier(declarationType.name, NameCasingType.Class);
45
60
  case "Operation":
46
61
  return getCSharpIdentifier(declarationType.name, NameCasingType.Class);
47
62
  case "Union":
@@ -60,8 +75,7 @@ export async function $onEmit(context) {
60
75
  const doc = getDoc(this.emitter.getProgram(), en);
61
76
  const attributes = getModelAttributes(program, en, enumName);
62
77
  this.#metadateMap.set(en, new CSharpType({ name: enumName, namespace: namespace }));
63
- return this.emitter.result.declaration(enumName, code `${this.#licenseHeader}
64
- // <auto-generated />
78
+ return this.emitter.result.declaration(enumName, code `${this.#generatedFileHeader}
65
79
 
66
80
  ${this.#emitUsings()}
67
81
 
@@ -72,7 +86,7 @@ export async function $onEmit(context) {
72
86
  ${attributes.map((attribute) => attribute.getApplicationString(this.emitter.getContext().scope)).join("\n")}
73
87
  public enum ${enumName}
74
88
  {
75
- ${this.emitter.emitEnumMembers(en)};
89
+ ${this.emitter.emitEnumMembers(en)}
76
90
  }
77
91
  } `);
78
92
  }
@@ -81,11 +95,7 @@ export async function $onEmit(context) {
81
95
  const enumFile = this.emitter.createSourceFile(`models/${enumName}.cs`);
82
96
  enumFile.meta[this.#sourceTypeKey] = CSharpSourceType.Model;
83
97
  const enumNamespace = `${this.#getOrSetBaseNamespace(en)}.Models`;
84
- return {
85
- namespace: enumNamespace,
86
- file: enumFile,
87
- scope: enumFile.globalScope,
88
- };
98
+ return this.#createEnumContext(enumNamespace, enumFile, enumName);
89
99
  }
90
100
  enumMembers(en) {
91
101
  const result = new StringBuilder();
@@ -94,9 +104,11 @@ export async function $onEmit(context) {
94
104
  i++;
95
105
  const memberName = ensureCSharpIdentifier(this.emitter.getProgram(), member, name);
96
106
  this.#metadateMap.set(member, { name: memberName });
97
- 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)}`);
98
110
  if (i < en.members.size)
99
- result.pushLiteralSegment(", ");
111
+ result.pushLiteralSegment(",\n");
100
112
  }
101
113
  return this.emitter.result.rawCode(result.reduce());
102
114
  }
@@ -108,13 +120,15 @@ export async function $onEmit(context) {
108
120
  return this.emitter.result.rawCode(code `System.Text.Json.Nodes.JsonNode`);
109
121
  case "ErrorType":
110
122
  case "never":
111
- case "void":
112
123
  reportDiagnostic(this.emitter.getProgram(), {
113
124
  code: "invalid-intrinsic",
114
125
  target: intrinsic,
115
126
  format: { typeName: intrinsic.name },
116
127
  });
117
128
  return "";
129
+ case "void":
130
+ const type = getCSharpTypeForIntrinsic(this.emitter.getProgram(), intrinsic);
131
+ return this.emitter.result.rawCode(`${type?.type.getTypeReference()}`);
118
132
  }
119
133
  }
120
134
  #emitUsings(file) {
@@ -127,13 +141,17 @@ export async function $onEmit(context) {
127
141
  return builder.segments.join("\n");
128
142
  }
129
143
  modelDeclaration(model, name) {
144
+ const parts = this.#getMultipartParts(model);
145
+ if (parts.length > 0) {
146
+ parts.forEach((p) => this.emitter.emitType(p));
147
+ return "";
148
+ }
130
149
  const className = ensureCSharpIdentifier(this.emitter.getProgram(), model, name);
131
150
  const namespace = this.emitter.getContext().namespace;
132
151
  const doc = getDoc(this.emitter.getProgram(), model);
133
152
  const attributes = getModelAttributes(this.emitter.getProgram(), model, className);
134
153
  this.#metadateMap.set(model, new CSharpType({ name: className, namespace: namespace }));
135
- const decl = this.emitter.result.declaration(className, code `${this.#licenseHeader}
136
- // <auto-generated />
154
+ const decl = this.emitter.result.declaration(className, code `${this.#generatedFileHeader}
137
155
 
138
156
  ${this.#emitUsings()}
139
157
 
@@ -146,22 +164,31 @@ export async function $onEmit(context) {
146
164
  return decl;
147
165
  }
148
166
  modelDeclarationContext(model, name) {
167
+ if (this.#isMultipartModel(model))
168
+ return {};
149
169
  const modelName = ensureCSharpIdentifier(this.emitter.getProgram(), model, name);
150
170
  const modelFile = this.emitter.createSourceFile(`models/${modelName}.cs`);
151
171
  modelFile.meta[this.#sourceTypeKey] = CSharpSourceType.Model;
152
172
  const modelNamespace = `${this.#getOrSetBaseNamespace(model)}.Models`;
153
- return this.#createModelContext(modelNamespace, modelFile);
173
+ return this.#createModelContext(modelNamespace, modelFile, modelName);
154
174
  }
155
175
  modelInstantiationContext(model) {
176
+ if (this.#isMultipartModel(model))
177
+ return {};
156
178
  const modelName = getModelInstantiationName(this.emitter.getProgram(), model, model.name);
157
179
  const sourceFile = this.emitter.createSourceFile(`models/${modelName}.cs`);
158
180
  sourceFile.meta[this.#sourceTypeKey] = CSharpSourceType.Model;
159
181
  const modelNamespace = `${this.#getOrSetBaseNamespace(model)}.Models`;
160
- const context = this.#createModelContext(modelNamespace, sourceFile);
182
+ const context = this.#createModelContext(modelNamespace, sourceFile, model.name);
161
183
  context.instantiationName = modelName;
162
184
  return context;
163
185
  }
164
186
  modelInstantiation(model, name) {
187
+ const parts = this.#getMultipartParts(model);
188
+ if (parts.length > 0) {
189
+ parts.forEach((p) => this.emitter.emitType(p));
190
+ return "";
191
+ }
165
192
  const program = this.emitter.getProgram();
166
193
  const recordType = getRecordType(program, model);
167
194
  if (recordType !== undefined) {
@@ -171,53 +198,100 @@ export async function $onEmit(context) {
171
198
  const className = context.instantiationName ?? name;
172
199
  return this.modelDeclaration(model, className);
173
200
  }
201
+ #getMultipartParts(model) {
202
+ const parts = [...model.properties.values()]
203
+ .flatMap((p) => getHttpPart(this.emitter.getProgram(), p.type)?.type)
204
+ .filter((t) => t !== undefined);
205
+ if (model.baseModel) {
206
+ return parts.concat(this.#getMultipartParts(model.baseModel));
207
+ }
208
+ return parts;
209
+ }
210
+ #isMultipartModel(model) {
211
+ const multipartTypes = this.#getMultipartParts(model);
212
+ return multipartTypes.length > 0;
213
+ }
174
214
  modelProperties(model) {
175
215
  const result = new StringBuilder();
176
216
  for (const [_, prop] of model.properties) {
177
217
  if (!isVoidType(prop.type) &&
178
218
  !isNeverType(prop.type) &&
179
219
  !isNullType(prop.type) &&
180
- !isErrorModel(this.emitter.getProgram(), prop.type))
220
+ !isErrorModel(this.emitter.getProgram(), prop.type)) {
181
221
  result.push(code `${this.emitter.emitModelProperty(prop)}`);
222
+ }
182
223
  }
183
224
  return result.reduce();
184
225
  }
185
- #isRecord(type) {
186
- return type.kind === "Model" && type.name === "Record" && type.indexer !== undefined;
226
+ modelLiteralContext(model) {
227
+ const name = this.emitter.emitDeclarationName(model) || "";
228
+ return this.modelDeclarationContext(model, name);
229
+ }
230
+ modelLiteral(model) {
231
+ const modelName = this.emitter.getContext().name;
232
+ reportDiagnostic(this.emitter.getProgram(), {
233
+ code: "anonymous-model",
234
+ target: model,
235
+ format: { emittedName: modelName },
236
+ });
237
+ return this.modelDeclaration(model, modelName);
238
+ }
239
+ #isInheritedProperty(property) {
240
+ const visited = [];
241
+ function isInherited(model, propertyName) {
242
+ if (visited.includes(model))
243
+ return false;
244
+ visited.push(model);
245
+ if (model.properties.has(propertyName))
246
+ return true;
247
+ if (model.baseModel === undefined)
248
+ return false;
249
+ return isInherited(model.baseModel, propertyName);
250
+ }
251
+ const model = property.model;
252
+ if (model === undefined || model.baseModel === undefined)
253
+ return false;
254
+ return isInherited(model.baseModel, property.name);
187
255
  }
188
256
  modelPropertyLiteral(property) {
189
257
  if (isStatusCode(this.emitter.getProgram(), property))
190
258
  return "";
191
- const propertyName = ensureCSharpIdentifier(this.emitter.getProgram(), property, property.name);
192
- const [typeName, typeDefault] = this.#findPropertyType(property);
259
+ let propertyName = ensureCSharpIdentifier(this.emitter.getProgram(), property, property.name);
260
+ const { typeReference: typeName, defaultValue: typeDefault, nullableType: nullable, } = this.#findPropertyType(property);
193
261
  const doc = getDoc(this.emitter.getProgram(), property);
194
- const attributes = getModelAttributes(this.emitter.getProgram(), property);
195
- // eslint-disable-next-line deprecation/deprecation
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
+ }
268
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
196
269
  const defaultValue = property.default
197
- ? // eslint-disable-next-line deprecation/deprecation
270
+ ? // eslint-disable-next-line @typescript-eslint/no-deprecated
198
271
  code `${this.emitter.emitType(property.default)}`
199
272
  : typeDefault;
200
273
  return this.emitter.result
201
- .rawCode(code `${doc ? `${formatComment(doc)}\n` : ""}${`${attributes.map((attribute) => attribute.getApplicationString(this.emitter.getContext().scope)).join("\n")}${attributes?.length > 0 ? "\n" : ""}`}public ${typeName}${property.optional && isValueType(this.emitter.getProgram(), property.type) ? "?" : ""} ${propertyName} { get; ${typeDefault ? "}" : "set; }"}${defaultValue ? ` = ${defaultValue};\n` : "\n"}
274
+ .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)
275
+ ? "?"
276
+ : ""} ${propertyName} { get; ${typeDefault ? "}" : "set; }"}${defaultValue ? ` = ${defaultValue};\n` : "\n"}
202
277
  `);
203
278
  }
204
279
  #findPropertyType(property) {
205
- switch (property.type.kind) {
206
- case "String":
207
- return [code `string`, `"${property.type.value}"`];
208
- case "Boolean":
209
- return [code `bool`, `${property.type.value === true ? true : false}`];
210
- case "Number":
211
- case "Object":
212
- return [code `object`, undefined];
213
- case "Model":
214
- if (this.#isRecord(property.type)) {
215
- return [code `JsonObject`, undefined];
216
- }
217
- return [code `${this.emitter.emitTypeReference(property.type)}`, undefined];
218
- default:
219
- return [code `${this.emitter.emitTypeReference(property.type)}`, undefined];
280
+ return this.#opHelpers.getTypeInfo(this.emitter.getProgram(), property.type);
281
+ }
282
+ #isMultipartRequest(operation) {
283
+ const body = operation.parameters.body;
284
+ if (body === undefined)
285
+ return false;
286
+ return body.bodyKind === "multipart";
287
+ }
288
+ #hasMultipartOperation(iface) {
289
+ for (const [_, operation] of iface.operations) {
290
+ const [httpOp, _] = getHttpOperation(this.emitter.getProgram(), operation);
291
+ if (this.#isMultipartRequest(httpOp))
292
+ return true;
220
293
  }
294
+ return false;
221
295
  }
222
296
  modelPropertyReference(property) {
223
297
  return code `${this.emitter.emitTypeReference(property.type)}`;
@@ -232,8 +306,7 @@ export async function $onEmit(context) {
232
306
  const doc = getDoc(this.emitter.getProgram(), iface);
233
307
  const attributes = getModelAttributes(this.emitter.getProgram(), iface, ifaceName);
234
308
  this.#metadateMap.set(iface, new CSharpType({ name: ifaceName, namespace: namespace }));
235
- const decl = this.emitter.result.declaration(ifaceName, code `${this.#licenseHeader}
236
- // <auto-generated />
309
+ const decl = this.emitter.result.declaration(ifaceName, code `${this.#generatedFileHeaderWithNullable}
237
310
 
238
311
  ${this.#emitUsings()}
239
312
 
@@ -252,7 +325,7 @@ export async function $onEmit(context) {
252
325
  sourceFile.meta[this.#sourceTypeKey] = CSharpSourceType.Interface;
253
326
  const ifaceNamespace = this.#getOrSetBaseNamespace(iface);
254
327
  const modelNamespace = `${ifaceNamespace}.Models`;
255
- const context = this.#createModelContext(ifaceNamespace, sourceFile);
328
+ const context = this.#createModelContext(ifaceNamespace, sourceFile, ifaceName);
256
329
  context.file.imports.set("System", ["System"]);
257
330
  context.file.imports.set("System.Net", ["System.Net"]);
258
331
  context.file.imports.set("System.Text.Json", ["System.Text.Json"]);
@@ -261,6 +334,11 @@ export async function $onEmit(context) {
261
334
  ]);
262
335
  context.file.imports.set("System.Threading.Tasks", ["System.Threading.Tasks"]);
263
336
  context.file.imports.set("Microsoft.AspNetCore.Mvc", ["Microsoft.AspNetCore.Mvc"]);
337
+ if (this.#hasMultipartOperation(iface)) {
338
+ context.file.imports.set("Microsoft.AspNetCore.WebUtilities", [
339
+ "Microsoft.AspNetCore.WebUtilities",
340
+ ]);
341
+ }
264
342
  context.file.imports.set(modelNamespace, [modelNamespace]);
265
343
  return context;
266
344
  }
@@ -269,6 +347,16 @@ export async function $onEmit(context) {
269
347
  const builder = new StringBuilder();
270
348
  const metadata = new HttpMetadata();
271
349
  const context = this.emitter.getContext();
350
+ const name = `${ensureCSharpIdentifier(this.emitter.getProgram(), iface, iface.name, NameCasingType.Class)}`;
351
+ const ifaceNamespace = this.#getOrSetBaseNamespace(iface);
352
+ const namespace = `${ifaceNamespace}`;
353
+ const mock = {
354
+ className: name,
355
+ interfaceName: `I${name}`,
356
+ methods: [],
357
+ namespace: namespace,
358
+ usings: [`${ifaceNamespace}.Models`],
359
+ };
272
360
  for (const [name, operation] of iface.operations) {
273
361
  const doc = getDoc(this.emitter.getProgram(), operation);
274
362
  const returnTypes = [];
@@ -279,10 +367,22 @@ export async function $onEmit(context) {
279
367
  const returnInfo = coalesceTypes(this.emitter.getProgram(), returnTypes, context.namespace);
280
368
  const returnType = returnInfo?.type || UnknownType;
281
369
  const opName = ensureCSharpIdentifier(this.emitter.getProgram(), operation, name, NameCasingType.Method);
282
- const opDecl = this.emitter.result.declaration(opName, code `${doc ? `${formatComment(doc)}\n` : ""}${returnType.name === "void" ? "Task" : `Task<${returnType.getTypeReference(context.scope)}>`} ${opName}Async( ${this.#emitInterfaceOperationParameters(operation, opName, "")});`);
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});`);
381
+ mock.methods.push(opImpl);
283
382
  builder.push(code `${opDecl.value}\n`);
284
383
  this.emitter.emitInterfaceOperation(operation);
285
384
  }
385
+ this.#mockRegistrations.set(mock.interfaceName, mock);
286
386
  return builder.reduce();
287
387
  }
288
388
  interfaceOperationDeclarationContext(operation) {
@@ -298,33 +398,68 @@ export async function $onEmit(context) {
298
398
  const operationName = ensureCSharpIdentifier(this.emitter.getProgram(), operation, name, NameCasingType.Method);
299
399
  const doc = getDoc(this.emitter.getProgram(), operation);
300
400
  const [httpOperation, _] = getHttpOperation(this.emitter.getProgram(), operation);
301
- const declParams = this.#emitHttpOperationParameters(httpOperation);
302
- const responseInfo = this.#getOperationResponse(httpOperation);
303
- let status = "200";
304
- let response = new CSharpType({
305
- name: "void",
306
- namespace: "System",
307
- isBuiltIn: true,
308
- isValueType: false,
309
- });
310
- if (responseInfo !== undefined) {
311
- [status, response] = responseInfo;
401
+ const multipart = this.#isMultipartRequest(httpOperation);
402
+ const parameters = this.#opHelpers.getParameters(this.emitter.getProgram(), httpOperation);
403
+ const declParams = getHttpDeclParameters(parameters);
404
+ if (multipart) {
405
+ const context = this.emitter.getContext();
406
+ context.file.imports.set("Microsoft.AspNetCore.WebUtilities", [
407
+ "Microsoft.AspNetCore.WebUtilities",
408
+ ]);
409
+ context.file.imports.set("Microsoft.AspNetCore.Http.Extensions", [
410
+ "Microsoft.AspNetCore.Http.Extensions",
411
+ ]);
312
412
  }
413
+ const responseInfo = this.#getOperationResponse(httpOperation);
414
+ const status = responseInfo?.statusCode ?? 200;
415
+ const response = responseInfo?.resultType ??
416
+ new CSharpType({
417
+ name: "void",
418
+ namespace: "System",
419
+ isBuiltIn: true,
420
+ isValueType: false,
421
+ });
313
422
  const hasResponseValue = response.name !== "void";
314
- const resultString = `${status === "204" ? "NoContent" : "Ok"}`;
315
- return this.emitter.result.declaration(operation.name, code `
423
+ const resultString = `${status === 204 ? "NoContent" : "Ok"}`;
424
+ if (!this.#isMultipartRequest(httpOperation)) {
425
+ return this.emitter.result.declaration(operation.name, code `
426
+ ${doc ? `${formatComment(doc)}` : ""}
427
+ [${getOperationVerbDecorator(httpOperation)}]
428
+ [Route("${httpOperation.path}")]
429
+ ${this.emitter.emitOperationReturnType(operation)}
430
+ public virtual async Task<IActionResult> ${operationName}(${declParams})
431
+ {
432
+ ${hasResponseValue
433
+ ? `var result = await ${this.emitter.getContext().resourceName}Impl.${operationName}Async(${getBusinessLogicCallParameters(parameters)});
434
+ return ${resultString}(result);`
435
+ : `await ${this.emitter.getContext().resourceName}Impl.${operationName}Async(${getBusinessLogicCallParameters(parameters)});
436
+ return ${resultString}();`}
437
+ }`);
438
+ }
439
+ else {
440
+ return this.emitter.result.declaration(operation.name, code `
316
441
  ${doc ? `${formatComment(doc)}` : ""}
317
442
  [${getOperationVerbDecorator(httpOperation)}]
318
443
  [Route("${httpOperation.path}")]
444
+ [Consumes("multipart/form-data")]
319
445
  ${this.emitter.emitOperationReturnType(operation)}
320
446
  public virtual async Task<IActionResult> ${operationName}(${declParams})
321
447
  {
448
+ var boundary = Request.GetMultipartBoundary();
449
+ if (boundary == null)
450
+ {
451
+ return BadRequest("Request missing multipart boundary");
452
+ }
453
+
454
+
455
+ var reader = new MultipartReader(boundary, Request.Body);
322
456
  ${hasResponseValue
323
- ? `var result = await ${this.emitter.getContext().resourceName}Impl.${operationName}Async(${this.#emitOperationCallParameters(httpOperation)});
457
+ ? `var result = await ${this.emitter.getContext().resourceName}Impl.${operationName}Async(${getBusinessLogicCallParameters(parameters)});
324
458
  return ${resultString}(result);`
325
- : `await ${this.emitter.getContext().resourceName}Impl.${operationName}Async(${this.#emitOperationCallParameters(httpOperation)});
459
+ : `await ${this.emitter.getContext().resourceName}Impl.${operationName}Async(${getBusinessLogicCallParameters(parameters)});
326
460
  return ${resultString}();`}
327
461
  }`);
462
+ }
328
463
  }
329
464
  operationDeclarationContext(operation, name) {
330
465
  const resource = getResourceOperation(this.emitter.getProgram(), operation);
@@ -337,20 +472,19 @@ export async function $onEmit(context) {
337
472
  const operationName = ensureCSharpIdentifier(this.emitter.getProgram(), operation, name, NameCasingType.Method);
338
473
  const doc = getDoc(this.emitter.getProgram(), operation);
339
474
  const [httpOperation, _] = getHttpOperation(this.emitter.getProgram(), operation);
340
- const declParams = this.#emitHttpOperationParameters(httpOperation);
475
+ const parameters = this.#opHelpers.getParameters(this.emitter.getProgram(), httpOperation);
476
+ const declParams = getHttpDeclParameters(parameters);
341
477
  const responseInfo = this.#getOperationResponse(httpOperation);
342
- let status = "200";
343
- let response = new CSharpType({
344
- name: "void",
345
- namespace: "System",
346
- isBuiltIn: true,
347
- isValueType: false,
348
- });
349
- if (responseInfo !== undefined) {
350
- [status, response] = responseInfo;
351
- }
478
+ const status = responseInfo?.statusCode ?? 200;
479
+ const response = responseInfo?.resultType ??
480
+ new CSharpType({
481
+ name: "void",
482
+ namespace: "System",
483
+ isBuiltIn: true,
484
+ isValueType: false,
485
+ });
352
486
  const hasResponseValue = response.name !== "void";
353
- const resultString = `${status === "204" ? "NoContent" : "Ok"}`;
487
+ const resultString = `${status === 204 ? "NoContent" : "Ok"}`;
354
488
  return this.emitter.result.declaration(operation.name, code `
355
489
  ${doc ? `${formatComment(doc)}` : ""}
356
490
  [${getOperationVerbDecorator(httpOperation)}]
@@ -359,9 +493,9 @@ export async function $onEmit(context) {
359
493
  public virtual async Task<IActionResult> ${operationName}(${declParams})
360
494
  {
361
495
  ${hasResponseValue
362
- ? `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)});
363
497
  return ${resultString}(result);`
364
- : `await ${this.emitter.getContext().resourceName}Impl.${operationName}Async(${this.#emitOperationCallParameters(httpOperation)});
498
+ : `await ${this.emitter.getContext().resourceName}Impl.${operationName}Async(${getBusinessLogicCallParameters(parameters)});
365
499
  return ${resultString}();`}
366
500
  }`);
367
501
  }
@@ -369,20 +503,27 @@ export async function $onEmit(context) {
369
503
  const [httpOperation, _] = getHttpOperation(this.emitter.getProgram(), operation);
370
504
  return this.#emitOperationResponses(httpOperation);
371
505
  }
506
+ stringTemplate(stringTemplate) {
507
+ return this.emitter.result.rawCode(stringTemplate.stringValue || "");
508
+ }
372
509
  #getOperationResponse(operation) {
373
510
  const validResponses = operation.responses.filter((r) => !isErrorModel(this.emitter.getProgram(), r.type) &&
374
511
  getCSharpStatusCode(r.statusCodes) !== undefined);
375
512
  if (validResponses.length < 1)
376
513
  return undefined;
377
514
  const response = validResponses[0];
378
- const statusCode = getCSharpStatusCode(response.statusCodes);
379
- if (statusCode === undefined)
515
+ const csharpStatusCode = getCSharpStatusCode(response.statusCodes);
516
+ if (csharpStatusCode === undefined)
380
517
  return undefined;
381
518
  const responseType = new HttpMetadata().resolveLogicalResponseType(this.emitter.getProgram(), response);
382
519
  const context = this.emitter.getContext();
383
520
  const result = getCSharpType(this.emitter.getProgram(), responseType, context.namespace);
384
521
  const resultType = result?.type || UnknownType;
385
- return [statusCode, resultType];
522
+ return {
523
+ csharpStatusCode,
524
+ resultType,
525
+ statusCode: response.statusCodes,
526
+ };
386
527
  }
387
528
  #emitOperationResponses(operation) {
388
529
  const builder = new StringBuilder();
@@ -397,7 +538,8 @@ export async function $onEmit(context) {
397
538
  }
398
539
  }
399
540
  for (const response of operation.responses) {
400
- this.emitter.emitType(response.type);
541
+ if (!isEmptyResponseModel(this.emitter.getProgram(), response.type))
542
+ this.emitter.emitType(response.type);
401
543
  }
402
544
  return builder.reduce();
403
545
  }
@@ -411,45 +553,8 @@ export async function $onEmit(context) {
411
553
  const resultType = result?.type || UnknownType;
412
554
  return resultType.getTypeReference(context.scope);
413
555
  }
414
- #emitInterfaceOperationParameters(operation, operationName, resourceName) {
415
- const signature = new StringBuilder();
416
- const requiredParams = [];
417
- const optionalParams = [];
418
- let totalParams = 0;
419
- for (const [_, parameter] of operation.parameters.properties) {
420
- if (parameter.optional)
421
- optionalParams.push(parameter);
422
- else
423
- requiredParams.push(parameter);
424
- totalParams++;
425
- }
426
- let i = 1;
427
- for (const requiredParam of requiredParams) {
428
- signature.push(code `${this.emitter.emitTypeReference(requiredParam.type)} ${ensureCSharpIdentifier(this.emitter.getProgram(), requiredParam, requiredParam.name, NameCasingType.Parameter)}${i++ < totalParams ? ", " : ""}`);
429
- }
430
- for (const optionalParam of optionalParams) {
431
- signature.push(code `${this.emitter.emitTypeReference(optionalParam.type)}? ${ensureCSharpIdentifier(this.emitter.getProgram(), optionalParam, optionalParam.name, NameCasingType.Parameter)}${i++ < totalParams ? ", " : ""}`);
432
- }
433
- return signature.reduce();
434
- }
435
- #emitHttpOperationParameters(operation) {
436
- const signature = new StringBuilder();
437
- const bodyParam = operation.parameters.body;
438
- let i = 0;
439
- //const pathParameters = operation.parameters.parameters.filter((p) => p.type === "path");
440
- for (const parameter of operation.parameters.parameters) {
441
- i++;
442
- if (parameter.param.type.kind !== "Intrinsic" || parameter.param.type.name !== "never") {
443
- signature.push(code `${this.#emitOperationSignatureParameter(operation, parameter)}${i < operation.parameters.parameters.length || bodyParam !== undefined ? ", " : ""}`);
444
- }
445
- }
446
- if (bodyParam !== undefined) {
447
- signature.push(code `${this.emitter.emitTypeReference(this.#metaInfo.getEffectivePayloadType(bodyParam.type, Visibility.Create & Visibility.Update))} body`);
448
- }
449
- return signature.reduce();
450
- }
451
556
  unionDeclaration(union, name) {
452
- const baseType = this.#coalesceUnionTypes(union);
557
+ const baseType = coalesceUnionTypes(this.emitter.getProgram(), union);
453
558
  if (baseType.isBuiltIn && baseType.name === "string") {
454
559
  const program = this.emitter.getProgram();
455
560
  const unionName = ensureCSharpIdentifier(program, union, name);
@@ -457,8 +562,7 @@ export async function $onEmit(context) {
457
562
  const doc = getDoc(this.emitter.getProgram(), union);
458
563
  const attributes = getModelAttributes(program, union, unionName);
459
564
  this.#metadateMap.set(union, new CSharpType({ name: unionName, namespace: namespace }));
460
- return this.emitter.result.declaration(unionName, code `${this.#licenseHeader}
461
- // <auto-generated />
565
+ return this.emitter.result.declaration(unionName, code `${this.#generatedFileHeader}
462
566
 
463
567
  ${this.#emitUsings()}
464
568
 
@@ -476,7 +580,7 @@ export async function $onEmit(context) {
476
580
  return this.emitter.result.rawCode(code `${baseType.getTypeReference()}`);
477
581
  }
478
582
  unionDeclarationContext(union) {
479
- const baseType = this.#coalesceUnionTypes(union);
583
+ const baseType = coalesceUnionTypes(this.emitter.getProgram(), union);
480
584
  if (baseType.isBuiltIn && baseType.name === "string") {
481
585
  const unionName = ensureCSharpIdentifier(this.emitter.getProgram(), union, union.name || "Union");
482
586
  const unionFile = this.emitter.createSourceFile(`models/${unionName}.cs`);
@@ -517,55 +621,10 @@ export async function $onEmit(context) {
517
621
  unionVariantContext(union) {
518
622
  return super.unionVariantContext(union);
519
623
  }
520
- #emitOperationSignatureParameter(operation, httpParam) {
521
- const name = httpParam.param.name;
522
- const parameter = httpParam.param;
523
- const emittedName = ensureCSharpIdentifier(this.emitter.getProgram(), parameter, name, NameCasingType.Parameter);
524
- const [emittedType, emittedDefault] = this.#findPropertyType(parameter);
525
- // eslint-disable-next-line deprecation/deprecation
526
- const defaultValue = parameter.default
527
- ? // eslint-disable-next-line deprecation/deprecation
528
- code `${this.emitter.emitType(parameter.default)}`
529
- : emittedDefault;
530
- return this.emitter.result.rawCode(code `${httpParam.type !== "path" ? this.#emitParameterAttribute(httpParam) : ""}${emittedType} ${emittedName}${defaultValue === undefined ? "" : ` = ${defaultValue}`}`);
531
- }
532
- #emitOperationCallParameters(operation) {
533
- const signature = new StringBuilder();
534
- const bodyParam = operation.parameters.body;
535
- let i = 0;
536
- //const pathParameters = operation.parameters.parameters.filter((p) => p.type === "path");
537
- for (const parameter of operation.parameters.parameters) {
538
- i++;
539
- if (!isNeverType(parameter.param.type) &&
540
- !isNullType(parameter.param.type) &&
541
- !isVoidType(parameter.param.type)) {
542
- signature.push(code `${this.#emitOperationCallParameter(operation, parameter)}${i < operation.parameters.parameters.length || bodyParam !== undefined ? ", " : ""}`);
543
- }
544
- }
545
- if (bodyParam !== undefined) {
546
- signature.push(code `body`);
547
- }
548
- return signature.reduce();
549
- }
550
- #emitOperationCallParameter(operation, httpParam) {
551
- const name = httpParam.param.name;
552
- const parameter = httpParam.param;
553
- const emittedName = ensureCSharpIdentifier(this.emitter.getProgram(), parameter, name, NameCasingType.Parameter);
554
- return this.emitter.result.rawCode(code `${emittedName}`);
555
- }
556
- #emitParameterAttribute(parameter) {
557
- switch (parameter.type) {
558
- case "header":
559
- return code `[FromHeader(Name="${parameter.name}")] `;
560
- case "query":
561
- return code `[FromQuery(Name="${parameter.name}")] `;
562
- default:
563
- return "";
564
- }
565
- }
566
- #createModelContext(namespace, file) {
624
+ #createModelContext(namespace, file, name) {
567
625
  const context = {
568
626
  namespace: namespace,
627
+ name: name,
569
628
  file: file,
570
629
  scope: file.globalScope,
571
630
  };
@@ -574,13 +633,30 @@ export async function $onEmit(context) {
574
633
  context.file.imports.set("System.Text.Json.Serialization", [
575
634
  "System.Text.Json.Serialization",
576
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
+ ]);
577
652
  return context;
578
653
  }
579
654
  #createOrGetResourceContext(name, operation, resource) {
655
+ name = ensureCSharpIdentifier(this.emitter.getProgram(), operation, name, NameCasingType.Class);
580
656
  let context = controllers.get(name);
581
657
  if (context !== undefined)
582
658
  return context;
583
- const sourceFile = this.emitter.createSourceFile(`controllers/${name}ControllerBase.cs`);
659
+ const sourceFile = this.emitter.createSourceFile(`controllers/${name}Controller.cs`);
584
660
  const namespace = this.#getOrSetBaseNamespace(operation);
585
661
  const modelNamespace = `${namespace}.Models`;
586
662
  sourceFile.meta[this.#sourceTypeKey] = CSharpSourceType.Controller;
@@ -607,6 +683,7 @@ export async function $onEmit(context) {
607
683
  controllers.set(name, context);
608
684
  return context;
609
685
  }
686
+ // eslint-disable-next-line no-unused-private-class-members
610
687
  #getNamespaceFullName(namespace) {
611
688
  return namespace
612
689
  ? ensureCSharpIdentifier(this.emitter.getProgram(), namespace, getNamespaceFullName(namespace))
@@ -621,8 +698,6 @@ export async function $onEmit(context) {
621
698
  return scalarType.getTypeReference(this.emitter.getContext().scope);
622
699
  }
623
700
  scalarDeclaration(scalar, name) {
624
- const foo = new Placeholder();
625
- foo.setValue;
626
701
  const scalarType = getCSharpTypeForScalar(this.emitter.getProgram(), scalar);
627
702
  return scalarType.getTypeReference(this.emitter.getContext().scope);
628
703
  }
@@ -631,6 +706,12 @@ export async function $onEmit(context) {
631
706
  if (sourceFile === libFile.source)
632
707
  return libFile.emitted;
633
708
  }
709
+ if (this.#mockFiles.length > 0) {
710
+ for (const mock of this.#mockFiles) {
711
+ if (sourceFile === mock.source)
712
+ return mock.emitted;
713
+ }
714
+ }
634
715
  const emittedSourceFile = {
635
716
  path: sourceFile.path,
636
717
  contents: "",
@@ -655,17 +736,21 @@ export async function $onEmit(context) {
655
736
  #emitControllerContents(file) {
656
737
  const namespace = file.meta.namespace;
657
738
  const contents = new StringBuilder();
658
- contents.push(`${this.#licenseHeader}\n`);
659
- contents.push("// <auto-generated />\n\n");
739
+ contents.push(`${this.#generatedFileHeaderWithNullable}\n\n`);
660
740
  contents.push(code `${this.#emitUsings(file)}\n`);
661
741
  contents.push("\n");
662
742
  contents.push(`namespace ${namespace}.Controllers\n`);
663
743
  contents.push("{\n");
664
744
  contents.push("[ApiController]\n");
665
- contents.push(`public abstract partial class ${file.meta["resource"]}Base: ControllerBase\n`);
745
+ contents.push(`public partial class ${file.meta["resource"]}: ControllerBase\n`);
666
746
  contents.push("{\n");
667
747
  contents.push("\n");
668
- contents.push(code `internal abstract I${file.meta.resourceName} ${file.meta.resourceName}Impl { get;}\n`);
748
+ contents.push(`public ${file.meta["resource"]}(I${file.meta.resourceName} operations)\n`);
749
+ contents.push("{\n");
750
+ contents.push(` ${file.meta.resourceName}Impl = operations;\n`);
751
+ contents.push("}");
752
+ contents.push("\n");
753
+ contents.push(code `internal virtual I${file.meta.resourceName} ${file.meta.resourceName}Impl { get;}\n`);
669
754
  for (const decl of file.globalScope.declarations) {
670
755
  contents.push(decl.value + "\n");
671
756
  }
@@ -695,6 +780,8 @@ export async function $onEmit(context) {
695
780
  }
696
781
  return current;
697
782
  }
783
+ // TODO: remove?
784
+ // eslint-disable-next-line no-unused-private-class-members
698
785
  #getTemplateParameters(model) {
699
786
  if (!model.templateMapper)
700
787
  return "";
@@ -714,41 +801,17 @@ export async function $onEmit(context) {
714
801
  return params.reduce();
715
802
  return "";
716
803
  }
717
- #coalesceUnionTypes(union) {
718
- const defaultValue = new CSharpType({
719
- name: "object",
720
- namespace: "System",
721
- isValueType: false,
722
- });
723
- let current = undefined;
724
- for (const [_, variant] of union.variants.entries()) {
725
- let candidate;
726
- switch (variant.type.kind) {
727
- case "Boolean":
728
- candidate = new CSharpType({ name: "bool", namespace: "System", isValueType: true });
729
- break;
730
- case "String":
731
- candidate = new CSharpType({ name: "string", namespace: "System", isValueType: false });
732
- break;
733
- case "Union":
734
- candidate = this.#coalesceUnionTypes(variant.type);
735
- break;
736
- case "Scalar":
737
- candidate = getCSharpTypeForScalar(this.emitter.getProgram(), variant.type);
738
- break;
739
- default:
740
- return defaultValue;
741
- }
742
- current = current ?? candidate;
743
- if (current === undefined || !candidate.equals(current))
744
- return defaultValue;
745
- }
746
- return current ?? defaultValue;
747
- }
748
804
  writeOutput(sourceFiles) {
749
805
  for (const source of this.#libraryFiles) {
750
806
  sourceFiles.push(source.source);
751
807
  }
808
+ if (this.#emitMocks === "all") {
809
+ if (this.#mockRegistrations.size > 0) {
810
+ const mocks = getBusinessLogicImplementations(this.emitter, this.#mockRegistrations, this.#useSwagger, this.#openapiPath);
811
+ this.#mockFiles.push(...mocks);
812
+ sourceFiles.push(...mocks.flatMap((l) => l.source));
813
+ }
814
+ }
752
815
  const emittedSourceFiles = [];
753
816
  for (const source of sourceFiles) {
754
817
  switch (this.#emitterOutputType) {
@@ -785,11 +848,14 @@ export async function $onEmit(context) {
785
848
  return this.#baseNamespace;
786
849
  }
787
850
  }
788
- function processNameSpace(program, target) {
789
- const service = getService(program, target);
851
+ function processNameSpace(program, target, service) {
852
+ if (!service)
853
+ service = getService(program, target);
790
854
  if (service) {
791
855
  for (const [_, model] of target.models) {
792
- emitter.emitType(model);
856
+ if (!isTemplateDeclaration(model) && !isEmptyResponseModel(program, model)) {
857
+ emitter.emitType(model);
858
+ }
793
859
  }
794
860
  for (const [_, en] of target.enums) {
795
861
  emitter.emitType(en);
@@ -798,10 +864,44 @@ export async function $onEmit(context) {
798
864
  emitter.emitType(sc);
799
865
  }
800
866
  for (const [_, iface] of target.interfaces) {
801
- emitter.emitType(iface);
867
+ if (!isTemplateDeclaration(iface)) {
868
+ emitter.emitType(iface);
869
+ }
870
+ }
871
+ if (target.operations.size > 0) {
872
+ // Collect interface operations for a business logic interface and controller
873
+ const nsOps = [];
874
+ for (const [name, op] of target.operations) {
875
+ if (!isTemplateDeclaration(op)) {
876
+ nsOps.push([name, op]);
877
+ }
878
+ }
879
+ const iface = program.checker.createAndFinishType({
880
+ node: undefined,
881
+ sourceInterfaces: [],
882
+ decorators: [],
883
+ operations: createRekeyableMap(nsOps),
884
+ kind: "Interface",
885
+ name: `${target.name}Operations`,
886
+ namespace: target,
887
+ entityKind: "Type",
888
+ isFinished: true,
889
+ });
890
+ try {
891
+ for (const [_, op] of nsOps) {
892
+ op.interface = iface;
893
+ }
894
+ emitter.emitType(iface);
895
+ }
896
+ finally {
897
+ for (const [_, op] of nsOps) {
898
+ op.interface = undefined;
899
+ }
900
+ target.interfaces.delete(iface.name);
901
+ }
802
902
  }
803
- for (const [_, op] of target.operations) {
804
- emitter.emitType(op);
903
+ for (const [_, sub] of target.namespaces) {
904
+ processNameSpace(program, sub, service);
805
905
  }
806
906
  }
807
907
  else {
@@ -814,16 +914,18 @@ export async function $onEmit(context) {
814
914
  const ns = context.program.checker.getGlobalNamespaceType();
815
915
  const options = emitter.getOptions();
816
916
  processNameSpace(context.program, ns);
817
- await ensureCleanDirectory(context.program, options.emitterOutputDir);
818
- await emitter.writeOutput();
819
- if (options["skip-format"] === undefined || options["skip-format"] === false) {
820
- await execFile("dotnet", [
821
- "format",
822
- "whitespace",
823
- emitter.getOptions().emitterOutputDir,
824
- "--include-generated",
825
- "--folder",
826
- ]);
917
+ if (!doNotEmit) {
918
+ await ensureCleanDirectory(context.program, options.emitterOutputDir);
919
+ await emitter.writeOutput();
920
+ if (options["skip-format"] === undefined || options["skip-format"] === false) {
921
+ await execFile("dotnet", [
922
+ "format",
923
+ "whitespace",
924
+ emitter.getOptions().emitterOutputDir,
925
+ "--include-generated",
926
+ "--folder",
927
+ ]);
928
+ }
827
929
  }
828
930
  }
829
931
  //# sourceMappingURL=service.js.map