@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.
- package/cmd/hscs.js +2 -0
- package/dist/src/cli/cli.d.ts +2 -0
- package/dist/src/cli/cli.d.ts.map +1 -0
- package/dist/src/cli/cli.js +124 -0
- package/dist/src/cli/cli.js.map +1 -0
- package/dist/src/{attributes.d.ts → lib/attributes.d.ts} +1 -1
- package/dist/src/lib/attributes.d.ts.map +1 -0
- package/dist/src/{attributes.js → lib/attributes.js} +52 -8
- package/dist/src/lib/attributes.js.map +1 -0
- package/dist/src/{boilerplate.d.ts → lib/boilerplate.d.ts} +2 -0
- package/dist/src/lib/boilerplate.d.ts.map +1 -0
- package/dist/src/{boilerplate.js → lib/boilerplate.js} +105 -40
- package/dist/src/lib/boilerplate.js.map +1 -0
- package/dist/src/lib/index.d.ts.map +1 -0
- package/dist/src/lib/index.js.map +1 -0
- package/dist/src/{interfaces.d.ts → lib/interfaces.d.ts} +23 -0
- package/dist/src/lib/interfaces.d.ts.map +1 -0
- package/dist/src/{interfaces.js → lib/interfaces.js} +7 -3
- package/dist/src/lib/interfaces.js.map +1 -0
- package/dist/src/{lib.d.ts → lib/lib.d.ts} +25 -1
- package/dist/src/lib/lib.d.ts.map +1 -0
- package/dist/src/{lib.js → lib/lib.js} +31 -0
- package/dist/src/lib/lib.js.map +1 -0
- package/dist/src/lib/scaffolding.d.ts +21 -0
- package/dist/src/lib/scaffolding.d.ts.map +1 -0
- package/dist/src/lib/scaffolding.js +412 -0
- package/dist/src/lib/scaffolding.js.map +1 -0
- package/dist/src/lib/service.d.ts.map +1 -0
- package/dist/src/{service.js → lib/service.js} +332 -230
- package/dist/src/lib/service.js.map +1 -0
- package/dist/src/lib/testing/index.d.ts.map +1 -0
- package/dist/src/{testing → lib/testing}/index.js +1 -0
- package/dist/src/lib/testing/index.js.map +1 -0
- package/dist/src/lib/type-helpers.d.ts.map +1 -0
- package/dist/src/lib/type-helpers.js.map +1 -0
- package/dist/src/lib/utils.d.ts +88 -0
- package/dist/src/lib/utils.d.ts.map +1 -0
- package/dist/src/lib/utils.js +1173 -0
- package/dist/src/lib/utils.js.map +1 -0
- package/package.json +32 -23
- package/dist/src/attributes.d.ts.map +0 -1
- package/dist/src/attributes.js.map +0 -1
- package/dist/src/boilerplate.d.ts.map +0 -1
- package/dist/src/boilerplate.js.map +0 -1
- package/dist/src/index.d.ts.map +0 -1
- package/dist/src/index.js.map +0 -1
- package/dist/src/interfaces.d.ts.map +0 -1
- package/dist/src/interfaces.js.map +0 -1
- package/dist/src/lib.d.ts.map +0 -1
- package/dist/src/lib.js.map +0 -1
- package/dist/src/service.d.ts.map +0 -1
- package/dist/src/service.js.map +0 -1
- package/dist/src/testing/index.d.ts.map +0 -1
- package/dist/src/testing/index.js.map +0 -1
- package/dist/src/type-helpers.d.ts.map +0 -1
- package/dist/src/type-helpers.js.map +0 -1
- package/dist/src/utils.d.ts +0 -48
- package/dist/src/utils.d.ts.map +0 -1
- package/dist/src/utils.js +0 -628
- package/dist/src/utils.js.map +0 -1
- /package/dist/src/{index.d.ts → lib/index.d.ts} +0 -0
- /package/dist/src/{index.js → lib/index.js} +0 -0
- /package/dist/src/{service.d.ts → lib/service.d.ts} +0 -0
- /package/dist/src/{testing → lib/testing}/index.d.ts +0 -0
- /package/dist/src/{type-helpers.d.ts → lib/type-helpers.d.ts} +0 -0
- /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,
|
|
3
|
-
import {
|
|
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 {
|
|
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
|
-
#
|
|
18
|
-
|
|
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
|
-
#
|
|
24
|
-
|
|
25
|
-
|
|
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 =
|
|
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.#
|
|
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
|
|
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.#
|
|
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
|
-
|
|
186
|
-
|
|
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
|
-
|
|
192
|
-
const
|
|
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
|
-
|
|
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
|
|
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 ${
|
|
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
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
return
|
|
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.#
|
|
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
|
|
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
|
|
302
|
-
const
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
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 ===
|
|
315
|
-
|
|
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
|
-
|
|
457
|
+
? `var result = await ${this.emitter.getContext().resourceName}Impl.${operationName}Async(${getBusinessLogicCallParameters(parameters)});
|
|
324
458
|
return ${resultString}(result);`
|
|
325
|
-
|
|
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
|
|
475
|
+
const parameters = this.#opHelpers.getParameters(this.emitter.getProgram(), httpOperation);
|
|
476
|
+
const declParams = getHttpDeclParameters(parameters);
|
|
341
477
|
const responseInfo = this.#getOperationResponse(httpOperation);
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
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 ===
|
|
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(${
|
|
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(${
|
|
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
|
|
379
|
-
if (
|
|
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
|
|
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.
|
|
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 =
|
|
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.#
|
|
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 =
|
|
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
|
-
#
|
|
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}
|
|
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.#
|
|
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
|
|
745
|
+
contents.push(`public partial class ${file.meta["resource"]}: ControllerBase\n`);
|
|
666
746
|
contents.push("{\n");
|
|
667
747
|
contents.push("\n");
|
|
668
|
-
contents.push(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 [_,
|
|
804
|
-
|
|
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
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
"
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
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
|