@typespec/http-server-csharp 0.58.0-alpha.9 → 0.58.0-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/README.md +69 -6
- package/dist/src/cli/cli.js +92 -43
- package/dist/src/cli/cli.js.map +1 -1
- package/dist/src/lib/attributes.d.ts.map +1 -1
- package/dist/src/lib/attributes.js +65 -31
- package/dist/src/lib/attributes.js.map +1 -1
- package/dist/src/lib/boilerplate.d.ts +1 -1
- package/dist/src/lib/boilerplate.d.ts.map +1 -1
- package/dist/src/lib/boilerplate.js +153 -31
- package/dist/src/lib/boilerplate.js.map +1 -1
- package/dist/src/lib/doc.d.ts +5 -0
- package/dist/src/lib/doc.d.ts.map +1 -0
- package/dist/src/lib/doc.js +237 -0
- package/dist/src/lib/doc.js.map +1 -0
- package/dist/src/lib/interfaces.d.ts +48 -4
- package/dist/src/lib/interfaces.d.ts.map +1 -1
- package/dist/src/lib/interfaces.js +89 -28
- package/dist/src/lib/interfaces.js.map +1 -1
- package/dist/src/lib/lib.d.ts +32 -3
- package/dist/src/lib/lib.d.ts.map +1 -1
- package/dist/src/lib/lib.js +57 -2
- package/dist/src/lib/lib.js.map +1 -1
- package/dist/src/lib/project.d.ts +5 -0
- package/dist/src/lib/project.d.ts.map +1 -0
- package/dist/src/lib/project.js +101 -0
- package/dist/src/lib/project.js.map +1 -0
- package/dist/src/lib/scaffolding.d.ts +7 -5
- package/dist/src/lib/scaffolding.d.ts.map +1 -1
- package/dist/src/lib/scaffolding.js +113 -40
- package/dist/src/lib/scaffolding.js.map +1 -1
- package/dist/src/lib/service.d.ts.map +1 -1
- package/dist/src/lib/service.js +496 -587
- package/dist/src/lib/service.js.map +1 -1
- package/dist/src/lib/type-helpers.d.ts +5 -1
- package/dist/src/lib/type-helpers.d.ts.map +1 -1
- package/dist/src/lib/type-helpers.js +32 -3
- package/dist/src/lib/type-helpers.js.map +1 -1
- package/dist/src/lib/utils.d.ts +79 -7
- package/dist/src/lib/utils.d.ts.map +1 -1
- package/dist/src/lib/utils.js +971 -36
- package/dist/src/lib/utils.js.map +1 -1
- package/package.json +39 -26
- package/dist/src/lib/testing/index.d.ts +0 -3
- package/dist/src/lib/testing/index.d.ts.map +0 -1
- package/dist/src/lib/testing/index.js +0 -7
- package/dist/src/lib/testing/index.js.map +0 -1
package/dist/src/lib/utils.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { StringBuilder, code, } from "@typespec/asset-emitter";
|
|
2
|
+
import { NoTarget, getFriendlyName, getMinValue, isArrayModelType, isErrorModel, isNullType, isNumericType, isTemplateInstance, isUnknownType, isVoidType, resolveCompilerOptions, resolvePath, serializeValueAsJson, } from "@typespec/compiler";
|
|
3
|
+
import { $ } from "@typespec/compiler/typekit";
|
|
4
|
+
import { Visibility, createMetadataInfo, getHeaderFieldName, isBody, isBodyRoot, isHeader, isMetadata, isPathParam, isQueryParam, isStatusCode, } from "@typespec/http";
|
|
5
|
+
import { getUniqueItems } from "@typespec/json-schema";
|
|
4
6
|
import { camelCase, pascalCase } from "change-case";
|
|
7
|
+
import { createServer } from "net";
|
|
5
8
|
import { getAttributes } from "./attributes.js";
|
|
6
|
-
import { BooleanValue, CSharpType, NameCasingType, NullValue, NumericValue, StringValue, } from "./interfaces.js";
|
|
7
|
-
import { reportDiagnostic } from "./lib.js";
|
|
9
|
+
import { BooleanValue, CSharpCollectionType, CSharpType, CollectionType, NameCasingType, NullValue, NumericValue, StringValue, checkOrAddNamespaceToScope, } from "./interfaces.js";
|
|
10
|
+
import { CSharpServiceOptions, reportDiagnostic } from "./lib.js";
|
|
11
|
+
import { getDoubleType, getEnumType } from "./type-helpers.js";
|
|
8
12
|
const _scalars = new Map();
|
|
9
13
|
export function getCSharpTypeForScalar(program, scalar) {
|
|
10
14
|
if (_scalars.has(scalar))
|
|
@@ -21,7 +25,7 @@ export function getCSharpTypeForScalar(program, scalar) {
|
|
|
21
25
|
target: scalar,
|
|
22
26
|
});
|
|
23
27
|
const result = new CSharpType({
|
|
24
|
-
name: "
|
|
28
|
+
name: "object",
|
|
25
29
|
namespace: "System",
|
|
26
30
|
isBuiltIn: true,
|
|
27
31
|
isValueType: false,
|
|
@@ -31,9 +35,17 @@ export function getCSharpTypeForScalar(program, scalar) {
|
|
|
31
35
|
}
|
|
32
36
|
export const UnknownType = new CSharpType({
|
|
33
37
|
name: "JsonNode",
|
|
34
|
-
namespace: "System.Text.Json",
|
|
38
|
+
namespace: "System.Text.Json.Nodes",
|
|
39
|
+
isValueType: false,
|
|
40
|
+
isBuiltIn: false,
|
|
41
|
+
isClass: true,
|
|
42
|
+
});
|
|
43
|
+
export const RecordType = new CSharpType({
|
|
44
|
+
name: "JsonObject",
|
|
45
|
+
namespace: "System.Text.Json.Nodes",
|
|
46
|
+
isBuiltIn: false,
|
|
35
47
|
isValueType: false,
|
|
36
|
-
|
|
48
|
+
isClass: true,
|
|
37
49
|
});
|
|
38
50
|
export function getCSharpType(program, type, namespace) {
|
|
39
51
|
const known = getKnownType(program, type);
|
|
@@ -54,8 +66,6 @@ export function getCSharpType(program, type, namespace) {
|
|
|
54
66
|
return { type: standardScalars.get("numeric"), value: new NumericValue(enumValue) };
|
|
55
67
|
case "Intrinsic":
|
|
56
68
|
return getCSharpTypeForIntrinsic(program, type);
|
|
57
|
-
case "Object":
|
|
58
|
-
return { type: UnknownType };
|
|
59
69
|
case "ModelProperty":
|
|
60
70
|
return getCSharpType(program, type.type, namespace);
|
|
61
71
|
case "Scalar":
|
|
@@ -79,18 +89,21 @@ export function getCSharpType(program, type, namespace) {
|
|
|
79
89
|
return {
|
|
80
90
|
type: new CSharpType({
|
|
81
91
|
name: ensureCSharpIdentifier(program, type, type.name, NameCasingType.Class),
|
|
82
|
-
namespace: namespace
|
|
92
|
+
namespace: `${namespace}`,
|
|
83
93
|
isBuiltIn: false,
|
|
84
94
|
isValueType: false,
|
|
95
|
+
isClass: true,
|
|
85
96
|
}),
|
|
86
97
|
};
|
|
87
98
|
case "Enum":
|
|
99
|
+
if (getEnumType(type) === "double")
|
|
100
|
+
return { type: getDoubleType() };
|
|
88
101
|
return {
|
|
89
102
|
type: new CSharpType({
|
|
90
103
|
name: ensureCSharpIdentifier(program, type, type.name, NameCasingType.Class),
|
|
91
|
-
namespace: `${namespace}
|
|
104
|
+
namespace: `${namespace}`,
|
|
92
105
|
isBuiltIn: false,
|
|
93
|
-
isValueType:
|
|
106
|
+
isValueType: true,
|
|
94
107
|
}),
|
|
95
108
|
};
|
|
96
109
|
case "Model":
|
|
@@ -99,15 +112,32 @@ export function getCSharpType(program, type, namespace) {
|
|
|
99
112
|
if (resolvedItem === undefined)
|
|
100
113
|
return undefined;
|
|
101
114
|
const { type: itemType, value: _ } = resolvedItem;
|
|
115
|
+
const uniqueItems = getUniqueItems(program, type);
|
|
116
|
+
const isByte = ["byte", "SByte"].includes(itemType.name);
|
|
117
|
+
const collectionType = CSharpServiceOptions.getInstance().collectionType;
|
|
118
|
+
const returnTypeCollection = uniqueItems
|
|
119
|
+
? CollectionType.ISet
|
|
120
|
+
: isByte
|
|
121
|
+
? CollectionType.Array
|
|
122
|
+
: collectionType;
|
|
123
|
+
const returnType = returnTypeCollection === CollectionType.Array
|
|
124
|
+
? `${itemType.name}[]`
|
|
125
|
+
: `${returnTypeCollection}<${itemType.name}>`;
|
|
102
126
|
return {
|
|
103
|
-
type: new
|
|
104
|
-
name:
|
|
127
|
+
type: new CSharpCollectionType({
|
|
128
|
+
name: returnType,
|
|
105
129
|
namespace: itemType.namespace,
|
|
106
130
|
isBuiltIn: itemType.isBuiltIn,
|
|
107
131
|
isValueType: false,
|
|
108
|
-
|
|
132
|
+
isClass: itemType.isClass,
|
|
133
|
+
isCollection: true,
|
|
134
|
+
}, returnTypeCollection, itemType.name),
|
|
109
135
|
};
|
|
110
136
|
}
|
|
137
|
+
if (isRecord(type))
|
|
138
|
+
return {
|
|
139
|
+
type: RecordType,
|
|
140
|
+
};
|
|
111
141
|
let name = type.name;
|
|
112
142
|
if (isTemplateInstance(type)) {
|
|
113
143
|
name = getModelInstantiationName(program, type, name);
|
|
@@ -115,15 +145,38 @@ export function getCSharpType(program, type, namespace) {
|
|
|
115
145
|
return {
|
|
116
146
|
type: new CSharpType({
|
|
117
147
|
name: ensureCSharpIdentifier(program, type, name, NameCasingType.Class),
|
|
118
|
-
namespace: `${namespace}
|
|
148
|
+
namespace: `${namespace}`,
|
|
119
149
|
isBuiltIn: false,
|
|
120
150
|
isValueType: false,
|
|
151
|
+
isClass: true,
|
|
121
152
|
}),
|
|
122
153
|
};
|
|
123
154
|
default:
|
|
124
155
|
return undefined;
|
|
125
156
|
}
|
|
126
157
|
}
|
|
158
|
+
export function resolveReferenceFromScopes(targetDeclaration, declarationScopes, referenceScopes) {
|
|
159
|
+
function getSourceFile(scopes) {
|
|
160
|
+
for (const scope of scopes) {
|
|
161
|
+
if (scope.kind === "sourceFile") {
|
|
162
|
+
return { scope: scope, file: scope.sourceFile };
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return undefined;
|
|
166
|
+
}
|
|
167
|
+
const decl = getSourceFile(declarationScopes);
|
|
168
|
+
const ref = getSourceFile(referenceScopes);
|
|
169
|
+
if (targetDeclaration.name && decl) {
|
|
170
|
+
const declNs = decl.file.meta["ResolvedNamespace"];
|
|
171
|
+
if (!ref)
|
|
172
|
+
return declNs ? `${declNs}.${targetDeclaration.name} ` : undefined;
|
|
173
|
+
if (checkOrAddNamespaceToScope(declNs, ref.scope)) {
|
|
174
|
+
return targetDeclaration.name;
|
|
175
|
+
}
|
|
176
|
+
return declNs ? `${declNs}.${targetDeclaration.name} ` : undefined;
|
|
177
|
+
}
|
|
178
|
+
return undefined;
|
|
179
|
+
}
|
|
127
180
|
export function coalesceTypes(program, types, namespace) {
|
|
128
181
|
const visited = new Map();
|
|
129
182
|
let candidateType = undefined;
|
|
@@ -356,27 +409,33 @@ export function isValueType(program, type) {
|
|
|
356
409
|
export function formatComment(text, lineLength = 76, lineEnd = "\n") {
|
|
357
410
|
function getNextLine(target) {
|
|
358
411
|
for (let i = lineLength - 1; i > 0; i--) {
|
|
359
|
-
if ([" ", "
|
|
360
|
-
return
|
|
412
|
+
if ([" ", ";"].includes(target.charAt(i))) {
|
|
413
|
+
return `${target.substring(0, i)}`;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
for (let i = lineLength - 1; i < target.length; i++) {
|
|
417
|
+
if ([" ", ";"].includes(target.charAt(i))) {
|
|
418
|
+
return `${target.substring(0, i)}`;
|
|
361
419
|
}
|
|
362
420
|
}
|
|
363
|
-
return
|
|
421
|
+
return `${target.substring(0, lineLength)}`;
|
|
364
422
|
}
|
|
365
|
-
let remaining = text;
|
|
423
|
+
let remaining = text.replaceAll("\n", " ");
|
|
366
424
|
const lines = [];
|
|
367
425
|
while (remaining.length > lineLength) {
|
|
368
426
|
const currentLine = getNextLine(remaining);
|
|
369
427
|
remaining =
|
|
370
428
|
remaining.length > currentLine.length ? remaining.substring(currentLine.length + 1) : "";
|
|
371
|
-
lines.push(currentLine);
|
|
429
|
+
lines.push(`/// ${currentLine}`);
|
|
372
430
|
}
|
|
373
431
|
if (remaining.length > 0)
|
|
374
432
|
lines.push(`/// ${remaining}`);
|
|
375
433
|
return `///<summary>${lineEnd}${lines.join(lineEnd)}${lineEnd}///</summary>`;
|
|
376
434
|
}
|
|
377
|
-
export function getCSharpIdentifier(name, context = NameCasingType.Class) {
|
|
435
|
+
export function getCSharpIdentifier(name, context = NameCasingType.Class, checkReserved = true) {
|
|
378
436
|
if (name === undefined)
|
|
379
437
|
return "Placeholder";
|
|
438
|
+
name = replaceCSharpReservedWord(name, context);
|
|
380
439
|
switch (context) {
|
|
381
440
|
case NameCasingType.Namespace:
|
|
382
441
|
const parts = [];
|
|
@@ -393,6 +452,7 @@ export function getCSharpIdentifier(name, context = NameCasingType.Class) {
|
|
|
393
452
|
}
|
|
394
453
|
export function ensureCSharpIdentifier(program, target, name, context = NameCasingType.Class) {
|
|
395
454
|
let location = "";
|
|
455
|
+
let includeDot = false;
|
|
396
456
|
switch (target.kind) {
|
|
397
457
|
case "Enum":
|
|
398
458
|
location = `enum ${target.name}`;
|
|
@@ -426,11 +486,9 @@ export function ensureCSharpIdentifier(program, target, name, context = NameCasi
|
|
|
426
486
|
case "Namespace":
|
|
427
487
|
location = `namespace ${target.name}`;
|
|
428
488
|
let invalid = false;
|
|
429
|
-
const nsName = new StringBuilder();
|
|
430
489
|
for (const part of name.split(".")) {
|
|
431
490
|
if (!isValidCSharpIdentifier(part)) {
|
|
432
491
|
invalid = true;
|
|
433
|
-
nsName.pushLiteralSegment(transformInvalidIdentifier(part));
|
|
434
492
|
}
|
|
435
493
|
}
|
|
436
494
|
if (invalid) {
|
|
@@ -439,9 +497,9 @@ export function ensureCSharpIdentifier(program, target, name, context = NameCasi
|
|
|
439
497
|
format: { identifier: name, location: location },
|
|
440
498
|
target: target.node ?? NoTarget,
|
|
441
499
|
});
|
|
442
|
-
return nsName.segments.join(".");
|
|
443
500
|
}
|
|
444
|
-
|
|
501
|
+
includeDot = true;
|
|
502
|
+
break;
|
|
445
503
|
case "Operation": {
|
|
446
504
|
const parent = target.interface
|
|
447
505
|
? `interface ${target.interface.name}`
|
|
@@ -453,15 +511,11 @@ export function ensureCSharpIdentifier(program, target, name, context = NameCasi
|
|
|
453
511
|
location = `union ${target.name}`;
|
|
454
512
|
break;
|
|
455
513
|
case "UnionVariant": {
|
|
456
|
-
|
|
457
|
-
const parent = program.checker.getTypeForNode(target.node.parent);
|
|
458
|
-
if (parent?.kind === "Union")
|
|
459
|
-
location = `variant ${String(target.name)} in union ${parent?.name}`;
|
|
460
|
-
}
|
|
514
|
+
location = `variant ${String(target.name)} in union ${target.union.name}`;
|
|
461
515
|
break;
|
|
462
516
|
}
|
|
463
517
|
}
|
|
464
|
-
if (!isValidCSharpIdentifier(name)) {
|
|
518
|
+
if (!isValidCSharpIdentifier(name, includeDot)) {
|
|
465
519
|
reportDiagnostic(program, {
|
|
466
520
|
code: "invalid-identifier",
|
|
467
521
|
format: { identifier: name, location: location },
|
|
@@ -474,6 +528,23 @@ export function ensureCSharpIdentifier(program, target, name, context = NameCasi
|
|
|
474
528
|
export function getModelAttributes(program, entity, cSharpName) {
|
|
475
529
|
return getAttributes(program, entity, cSharpName);
|
|
476
530
|
}
|
|
531
|
+
export function getModelDeclarationName(program, model, defaultSuffix) {
|
|
532
|
+
if (model.name !== null && model.name.length > 0) {
|
|
533
|
+
return ensureCSharpIdentifier(program, model, model.name, NameCasingType.Class);
|
|
534
|
+
}
|
|
535
|
+
if (model.sourceModel && model.sourceModel.name && model.sourceModel.name.length > 0) {
|
|
536
|
+
return ensureCSharpIdentifier(program, model, `${model.sourceModel.name}${defaultSuffix}`, NameCasingType.Class);
|
|
537
|
+
}
|
|
538
|
+
if (model.sourceModels.length > 0) {
|
|
539
|
+
const sourceNames = model.sourceModels
|
|
540
|
+
.filter((m) => m.model.name !== undefined && m.model.name.length > 0)
|
|
541
|
+
.flatMap((m) => ensureCSharpIdentifier(program, model, m.model.name, NameCasingType.Class));
|
|
542
|
+
if (sourceNames.length > 0) {
|
|
543
|
+
return `${sourceNames.join()}${defaultSuffix}`;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
return `Model${defaultSuffix}`;
|
|
547
|
+
}
|
|
477
548
|
export function getModelInstantiationName(program, model, name) {
|
|
478
549
|
const friendlyName = getFriendlyName(program, model);
|
|
479
550
|
if (friendlyName && friendlyName.length > 0)
|
|
@@ -563,12 +634,16 @@ export class HttpMetadata {
|
|
|
563
634
|
});
|
|
564
635
|
switch (responseType.kind) {
|
|
565
636
|
case "Model":
|
|
637
|
+
if (responseType.indexer && responseType.indexer.key.name !== "string")
|
|
638
|
+
return responseType;
|
|
639
|
+
if (isRecord(responseType))
|
|
640
|
+
return responseType;
|
|
566
641
|
const bodyProp = new ModelInfo().filterAllProperties(program, responseType, (p) => isBody(program, p) || isBodyRoot(program, p));
|
|
567
642
|
if (bodyProp !== undefined)
|
|
568
643
|
return metaInfo.getEffectivePayloadType(bodyProp.type, Visibility.Read);
|
|
569
644
|
const anyProp = new ModelInfo().filterAllProperties(program, responseType, (p) => !isMetadata(program, p) && !isStatusCode(program, p));
|
|
570
645
|
if (anyProp === undefined)
|
|
571
|
-
return program.
|
|
646
|
+
return $(program).intrinsic.void;
|
|
572
647
|
if (responseType.name === "") {
|
|
573
648
|
return metaInfo.getEffectivePayloadType(responseType, Visibility.Read);
|
|
574
649
|
}
|
|
@@ -615,8 +690,148 @@ export async function ensureCleanDirectory(program, targetPath) {
|
|
|
615
690
|
catch { }
|
|
616
691
|
await program.host.mkdirp(targetPath);
|
|
617
692
|
}
|
|
618
|
-
export function isValidCSharpIdentifier(identifier) {
|
|
619
|
-
|
|
693
|
+
export function isValidCSharpIdentifier(identifier, isNamespace = false) {
|
|
694
|
+
if (!isNamespace)
|
|
695
|
+
return identifier?.match(/^[A-Za-z_][\w]*$/) !== null;
|
|
696
|
+
return identifier?.match(/^[A-Za-z_][\w.]*$/) !== null;
|
|
697
|
+
}
|
|
698
|
+
export function replaceCSharpReservedWord(identifier, context) {
|
|
699
|
+
function generateReplacement(input) {
|
|
700
|
+
return [input, `${pascalCase(input)}Name`];
|
|
701
|
+
}
|
|
702
|
+
const contextualWords = [
|
|
703
|
+
"add",
|
|
704
|
+
"allows",
|
|
705
|
+
"alias",
|
|
706
|
+
"and",
|
|
707
|
+
"ascending",
|
|
708
|
+
"args",
|
|
709
|
+
"async",
|
|
710
|
+
"await",
|
|
711
|
+
"by",
|
|
712
|
+
"descending",
|
|
713
|
+
"dynamic",
|
|
714
|
+
"equals",
|
|
715
|
+
"field",
|
|
716
|
+
"file",
|
|
717
|
+
"from",
|
|
718
|
+
"get",
|
|
719
|
+
"global",
|
|
720
|
+
"group",
|
|
721
|
+
"init",
|
|
722
|
+
"into",
|
|
723
|
+
"join",
|
|
724
|
+
"let",
|
|
725
|
+
"managed",
|
|
726
|
+
"nameof",
|
|
727
|
+
"nint",
|
|
728
|
+
"not",
|
|
729
|
+
"notnull",
|
|
730
|
+
"nuint",
|
|
731
|
+
"on",
|
|
732
|
+
"or",
|
|
733
|
+
"orderby",
|
|
734
|
+
"partial",
|
|
735
|
+
"record",
|
|
736
|
+
"remove",
|
|
737
|
+
"required",
|
|
738
|
+
"scoped",
|
|
739
|
+
"select",
|
|
740
|
+
"set",
|
|
741
|
+
"unmanaged",
|
|
742
|
+
"value",
|
|
743
|
+
"var",
|
|
744
|
+
"when",
|
|
745
|
+
"where",
|
|
746
|
+
"with",
|
|
747
|
+
"yield",
|
|
748
|
+
];
|
|
749
|
+
const reservedWords = [
|
|
750
|
+
"abstract",
|
|
751
|
+
"as",
|
|
752
|
+
"base",
|
|
753
|
+
"bool",
|
|
754
|
+
"boolean",
|
|
755
|
+
"break",
|
|
756
|
+
"byte",
|
|
757
|
+
"case",
|
|
758
|
+
"catch",
|
|
759
|
+
"char",
|
|
760
|
+
"checked",
|
|
761
|
+
"class",
|
|
762
|
+
"const",
|
|
763
|
+
"continue",
|
|
764
|
+
"decimal",
|
|
765
|
+
"default",
|
|
766
|
+
"do",
|
|
767
|
+
"double",
|
|
768
|
+
"else",
|
|
769
|
+
"enum",
|
|
770
|
+
"event",
|
|
771
|
+
"explicit",
|
|
772
|
+
"extern",
|
|
773
|
+
"false",
|
|
774
|
+
"finally",
|
|
775
|
+
"fixed",
|
|
776
|
+
"float",
|
|
777
|
+
"for",
|
|
778
|
+
"foreach",
|
|
779
|
+
"goto",
|
|
780
|
+
"if",
|
|
781
|
+
"implicit",
|
|
782
|
+
"in",
|
|
783
|
+
"int",
|
|
784
|
+
"interface",
|
|
785
|
+
"internal",
|
|
786
|
+
"is",
|
|
787
|
+
"lock",
|
|
788
|
+
"long",
|
|
789
|
+
"namespace",
|
|
790
|
+
"new",
|
|
791
|
+
"null",
|
|
792
|
+
"object",
|
|
793
|
+
"operator",
|
|
794
|
+
"out",
|
|
795
|
+
"override",
|
|
796
|
+
"params",
|
|
797
|
+
"private",
|
|
798
|
+
"protected",
|
|
799
|
+
"public",
|
|
800
|
+
"readonly",
|
|
801
|
+
"ref",
|
|
802
|
+
"return",
|
|
803
|
+
"sbyte",
|
|
804
|
+
"sealed",
|
|
805
|
+
"short",
|
|
806
|
+
"sizeof",
|
|
807
|
+
"stackalloc",
|
|
808
|
+
"static",
|
|
809
|
+
"string",
|
|
810
|
+
"struct",
|
|
811
|
+
"switch",
|
|
812
|
+
"this",
|
|
813
|
+
"throw",
|
|
814
|
+
"true",
|
|
815
|
+
"try",
|
|
816
|
+
"type",
|
|
817
|
+
"typeof",
|
|
818
|
+
"uint",
|
|
819
|
+
"ulong",
|
|
820
|
+
"unchecked",
|
|
821
|
+
"unsafe",
|
|
822
|
+
"ushort",
|
|
823
|
+
"using",
|
|
824
|
+
"virtual",
|
|
825
|
+
"void",
|
|
826
|
+
"volatile",
|
|
827
|
+
"while",
|
|
828
|
+
];
|
|
829
|
+
const reserved = new Map(reservedWords.concat(contextualWords).map((w) => generateReplacement(w)));
|
|
830
|
+
const check = reserved.get(identifier.toLowerCase());
|
|
831
|
+
if (check !== undefined) {
|
|
832
|
+
return getCSharpIdentifier(check, context, false);
|
|
833
|
+
}
|
|
834
|
+
return identifier;
|
|
620
835
|
}
|
|
621
836
|
export function getValidChar(target, position) {
|
|
622
837
|
if (position === 0) {
|
|
@@ -642,4 +857,724 @@ export function getCSharpStatusCode(entry) {
|
|
|
642
857
|
return undefined;
|
|
643
858
|
}
|
|
644
859
|
}
|
|
860
|
+
/**
|
|
861
|
+
* Returns the full return statement for a controller action based on the HTTP status code.
|
|
862
|
+
* Maps well-known status codes to their idiomatic ASP.NET Core ControllerBase methods,
|
|
863
|
+
* and falls back to `StatusCode(code, ...)` for all other numeric codes.
|
|
864
|
+
*/
|
|
865
|
+
export function getControllerReturnStatement(status, hasValue) {
|
|
866
|
+
if (typeof status === "number") {
|
|
867
|
+
switch (status) {
|
|
868
|
+
case 200:
|
|
869
|
+
return hasValue ? "return Ok(result);" : "return Ok();";
|
|
870
|
+
case 202:
|
|
871
|
+
return hasValue ? "return Accepted(result);" : "return Accepted();";
|
|
872
|
+
case 204:
|
|
873
|
+
return "return NoContent();";
|
|
874
|
+
default:
|
|
875
|
+
return hasValue ? `return StatusCode(${status}, result);` : `return StatusCode(${status});`;
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
// Fallback for ranges and "*"
|
|
879
|
+
return hasValue ? "return Ok(result);" : "return Ok();";
|
|
880
|
+
}
|
|
881
|
+
export function isEmptyResponseModel(program, model) {
|
|
882
|
+
if (model.kind !== "Model")
|
|
883
|
+
return false;
|
|
884
|
+
if (model.properties.size === 0)
|
|
885
|
+
return true;
|
|
886
|
+
return (model.properties.size === 1 &&
|
|
887
|
+
isStatusCode(program, [...model.properties.values()][0]) &&
|
|
888
|
+
!isErrorModel(program, model));
|
|
889
|
+
}
|
|
890
|
+
export function isContentTypeHeader(program, parameter) {
|
|
891
|
+
return (isHeader(program, parameter) &&
|
|
892
|
+
(parameter.name === "contentType" || getHeaderFieldName(program, parameter) === "Content-type"));
|
|
893
|
+
}
|
|
894
|
+
export function isValidParameter(program, parameter) {
|
|
895
|
+
return (!isContentTypeHeader(program, parameter) &&
|
|
896
|
+
(parameter.type.kind !== "Intrinsic" || parameter.type.name !== "never") &&
|
|
897
|
+
parameter.model?.name === "");
|
|
898
|
+
}
|
|
899
|
+
/** Determine whether the given parameter is http metadata */
|
|
900
|
+
export function isHttpMetadata(program, property) {
|
|
901
|
+
return (isPathParam(program, property) || isHeader(program, property) || isQueryParam(program, property));
|
|
902
|
+
}
|
|
903
|
+
export function getBusinessLogicCallParameters(parameters) {
|
|
904
|
+
const builder = new StringBuilder();
|
|
905
|
+
const blParameters = parameters.filter((p) => p.operationKind === "BusinessLogic" || p.operationKind === "All");
|
|
906
|
+
let i = 0;
|
|
907
|
+
for (const param of blParameters) {
|
|
908
|
+
builder.push(code `${getBusinessLogicCallParameter(param)}${++i < blParameters.length ? ", " : ""}`);
|
|
909
|
+
}
|
|
910
|
+
return builder.reduce();
|
|
911
|
+
}
|
|
912
|
+
export function getBusinessLogicDeclParameters(parameters) {
|
|
913
|
+
const builder = new StringBuilder();
|
|
914
|
+
const blParameters = parameters.filter((p) => p.operationKind === "BusinessLogic" || p.operationKind === "All");
|
|
915
|
+
let i = 0;
|
|
916
|
+
for (const param of blParameters) {
|
|
917
|
+
builder.push(code `${getBusinessLogicSignatureParameter(param)}${++i < blParameters.length ? ", " : ""}`);
|
|
918
|
+
}
|
|
919
|
+
return builder.reduce();
|
|
920
|
+
}
|
|
921
|
+
export function getHttpDeclParameters(parameters) {
|
|
922
|
+
const builder = new StringBuilder();
|
|
923
|
+
const blParameters = parameters.filter((p) => p.operationKind === "Http" || p.operationKind === "All");
|
|
924
|
+
let i = 0;
|
|
925
|
+
for (const param of blParameters) {
|
|
926
|
+
builder.push(code `${getHttpSignatureParameter(param)}${++i < blParameters.length ? ", " : ""}`);
|
|
927
|
+
}
|
|
928
|
+
return builder.reduce();
|
|
929
|
+
}
|
|
930
|
+
export function getBusinessLogicCallParameter(param) {
|
|
931
|
+
const builder = new StringBuilder();
|
|
932
|
+
builder.push(code `${param.callName}`);
|
|
933
|
+
return builder.reduce();
|
|
934
|
+
}
|
|
935
|
+
export function getBusinessLogicSignatureParameter(param) {
|
|
936
|
+
const builder = new StringBuilder();
|
|
937
|
+
builder.push(code `${param.typeName}${param.optional || param.nullable ? "? " : " "}${param.name}`);
|
|
938
|
+
return builder.reduce();
|
|
939
|
+
}
|
|
940
|
+
export function getHttpSignatureParameter(param) {
|
|
941
|
+
const builder = new StringBuilder();
|
|
942
|
+
builder.push(code `${getHttpParameterDecorator(param)}${getBusinessLogicSignatureParameter(param)}${param.defaultValue === undefined ? "" : code ` = ${typeof param.defaultValue === "boolean" ? code `${param.defaultValue.toString()}` : code `${param.defaultValue}`}`}`);
|
|
943
|
+
return builder.reduce();
|
|
944
|
+
}
|
|
945
|
+
export function getHttpParameterDecorator(parameter) {
|
|
946
|
+
switch (parameter.httpParameterKind) {
|
|
947
|
+
case "query":
|
|
948
|
+
return code `[FromQuery${parameter.httpParameterName ? code `(Name="${parameter.httpParameterName}")` : ""}] `;
|
|
949
|
+
case "header":
|
|
950
|
+
return code `[FromHeader${parameter.httpParameterName ? code `(Name="${parameter.httpParameterName}")` : ""}] `;
|
|
951
|
+
default:
|
|
952
|
+
return "";
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
export function getParameterKind(parameter) {
|
|
956
|
+
switch (parameter.type) {
|
|
957
|
+
case "path":
|
|
958
|
+
return "path";
|
|
959
|
+
case "cookie":
|
|
960
|
+
case "header":
|
|
961
|
+
return "header";
|
|
962
|
+
case "query":
|
|
963
|
+
return "query";
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
export function canHaveDefault(program, type) {
|
|
967
|
+
switch (type.kind) {
|
|
968
|
+
case "Boolean":
|
|
969
|
+
case "EnumMember":
|
|
970
|
+
case "Enum":
|
|
971
|
+
case "Number":
|
|
972
|
+
case "String":
|
|
973
|
+
case "Scalar":
|
|
974
|
+
case "StringTemplate":
|
|
975
|
+
return true;
|
|
976
|
+
case "ModelProperty":
|
|
977
|
+
return canHaveDefault(program, type.type);
|
|
978
|
+
default:
|
|
979
|
+
return false;
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
export class CSharpOperationHelpers {
|
|
983
|
+
constructor(inEmitter) {
|
|
984
|
+
this.emitter = inEmitter;
|
|
985
|
+
this.#anonymousModels = new Map();
|
|
986
|
+
this.#opCache = new Map();
|
|
987
|
+
}
|
|
988
|
+
emitter;
|
|
989
|
+
#anonymousModels;
|
|
990
|
+
#opCache;
|
|
991
|
+
getResponse(program, operation) {
|
|
992
|
+
return new CSharpType({
|
|
993
|
+
name: "void",
|
|
994
|
+
namespace: "System",
|
|
995
|
+
isBuiltIn: true,
|
|
996
|
+
isValueType: true,
|
|
997
|
+
});
|
|
998
|
+
}
|
|
999
|
+
getParameters(program, operation) {
|
|
1000
|
+
function safeConcat(...names) {
|
|
1001
|
+
return names
|
|
1002
|
+
.filter((n) => n !== undefined && n !== null && n.length > 0)
|
|
1003
|
+
.flatMap((s) => getCSharpIdentifier(s, NameCasingType.Class))
|
|
1004
|
+
.join();
|
|
1005
|
+
}
|
|
1006
|
+
const cached = this.#opCache.get(operation.operation);
|
|
1007
|
+
if (cached)
|
|
1008
|
+
return cached;
|
|
1009
|
+
const bodyParam = operation.parameters.body;
|
|
1010
|
+
const isExplicitBodyParam = bodyParam?.property !== undefined;
|
|
1011
|
+
const result = [];
|
|
1012
|
+
if (!cached && operation.verb === "get" && operation.parameters.body !== undefined) {
|
|
1013
|
+
reportDiagnostic(program, {
|
|
1014
|
+
code: "get-request-body",
|
|
1015
|
+
target: operation.operation,
|
|
1016
|
+
format: {},
|
|
1017
|
+
});
|
|
1018
|
+
this.#opCache.set(operation.operation, result);
|
|
1019
|
+
return result;
|
|
1020
|
+
}
|
|
1021
|
+
const validParams = operation.parameters.parameters.filter((p) => isValidParameter(program, p.param));
|
|
1022
|
+
const requiredParams = validParams.filter((p) => p.type === "path" || (!p.param.optional && p.param.defaultValue === undefined));
|
|
1023
|
+
const optionalParams = validParams.filter((p) => p.type !== "path" && (p.param.optional || p.param.defaultValue !== undefined));
|
|
1024
|
+
for (const parameter of requiredParams) {
|
|
1025
|
+
let { typeReference: paramType, defaultValue: paramValue } = this.getTypeInfo(program, parameter.param.type);
|
|
1026
|
+
// cSharp does not allow array defaults in operation parameters
|
|
1027
|
+
if (!canHaveDefault(program, parameter.param)) {
|
|
1028
|
+
paramValue = undefined;
|
|
1029
|
+
}
|
|
1030
|
+
const paramName = ensureCSharpIdentifier(program, parameter.param, parameter.param.name, NameCasingType.Parameter);
|
|
1031
|
+
result.push({
|
|
1032
|
+
isExplicitBody: false,
|
|
1033
|
+
name: paramName,
|
|
1034
|
+
callName: paramName,
|
|
1035
|
+
optional: false,
|
|
1036
|
+
typeName: paramType,
|
|
1037
|
+
defaultValue: paramValue,
|
|
1038
|
+
httpParameterKind: getParameterKind(parameter),
|
|
1039
|
+
httpParameterName: parameter.name,
|
|
1040
|
+
nullable: false,
|
|
1041
|
+
operationKind: "All",
|
|
1042
|
+
});
|
|
1043
|
+
}
|
|
1044
|
+
const overrideParameters = getExplicitBodyParameters(program, operation);
|
|
1045
|
+
if (overrideParameters !== undefined) {
|
|
1046
|
+
for (const overrideParam of overrideParameters) {
|
|
1047
|
+
result.push(overrideParam);
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
else if (bodyParam !== undefined && isExplicitBodyParam) {
|
|
1051
|
+
let { typeReference: bodyType, defaultValue: bodyValue, nullableType: isNullable, } = this.getTypeInfo(program, bodyParam.type);
|
|
1052
|
+
if (!canHaveDefault(program, bodyParam.type)) {
|
|
1053
|
+
bodyValue = undefined;
|
|
1054
|
+
}
|
|
1055
|
+
result.push({
|
|
1056
|
+
isExplicitBody: true,
|
|
1057
|
+
httpParameterKind: "body",
|
|
1058
|
+
name: "body",
|
|
1059
|
+
callName: "body",
|
|
1060
|
+
typeName: bodyType,
|
|
1061
|
+
nullable: isNullable,
|
|
1062
|
+
defaultValue: bodyValue,
|
|
1063
|
+
optional: bodyParam.property?.optional ?? false,
|
|
1064
|
+
operationKind: "All",
|
|
1065
|
+
});
|
|
1066
|
+
}
|
|
1067
|
+
else if (bodyParam !== undefined) {
|
|
1068
|
+
switch (bodyParam.type.kind) {
|
|
1069
|
+
case "Model":
|
|
1070
|
+
let tsBody = bodyParam.type;
|
|
1071
|
+
if (!bodyParam.type.name) {
|
|
1072
|
+
tsBody = program.checker.cloneType(bodyParam.type, {
|
|
1073
|
+
name: safeConcat(operation.operation.interface?.name, operation.operation.name, "Request"),
|
|
1074
|
+
});
|
|
1075
|
+
}
|
|
1076
|
+
const { typeReference: bodyType } = this.getTypeInfo(program, tsBody);
|
|
1077
|
+
const bodyName = ensureCSharpIdentifier(program, bodyParam.type, "body", NameCasingType.Parameter);
|
|
1078
|
+
result.push({
|
|
1079
|
+
isExplicitBody: false,
|
|
1080
|
+
httpParameterKind: "body",
|
|
1081
|
+
name: bodyName,
|
|
1082
|
+
callName: bodyName,
|
|
1083
|
+
typeName: bodyType,
|
|
1084
|
+
nullable: false,
|
|
1085
|
+
defaultValue: undefined,
|
|
1086
|
+
optional: false,
|
|
1087
|
+
operationKind: "Http",
|
|
1088
|
+
});
|
|
1089
|
+
for (const [propName, propDef] of bodyParam.type.properties) {
|
|
1090
|
+
let { typeReference: csType, defaultValue: csValue, nullableType: isNullable, } = this.getTypeInfo(program, propDef.type, propDef);
|
|
1091
|
+
// cSharp does not allow array defaults in operation parameters
|
|
1092
|
+
if (!canHaveDefault(program, propDef)) {
|
|
1093
|
+
csValue = undefined;
|
|
1094
|
+
}
|
|
1095
|
+
const paramName = ensureCSharpIdentifier(program, propDef, propName, NameCasingType.Parameter);
|
|
1096
|
+
const refName = ensureCSharpIdentifier(program, propDef, propName, NameCasingType.Property);
|
|
1097
|
+
result.push({
|
|
1098
|
+
isExplicitBody: false,
|
|
1099
|
+
httpParameterKind: "body",
|
|
1100
|
+
name: paramName,
|
|
1101
|
+
callName: `body.${refName}`,
|
|
1102
|
+
typeName: csType,
|
|
1103
|
+
nullable: isNullable,
|
|
1104
|
+
defaultValue: csValue,
|
|
1105
|
+
optional: propDef.optional,
|
|
1106
|
+
operationKind: "BusinessLogic",
|
|
1107
|
+
});
|
|
1108
|
+
}
|
|
1109
|
+
break;
|
|
1110
|
+
case "ModelProperty":
|
|
1111
|
+
{
|
|
1112
|
+
let { typeReference: csType, defaultValue: csValue, nullableType: isNullable, } = this.getTypeInfo(program, bodyParam.type.type, bodyParam.type);
|
|
1113
|
+
if (!canHaveDefault(program, bodyParam.type)) {
|
|
1114
|
+
csValue = undefined;
|
|
1115
|
+
}
|
|
1116
|
+
const optName = ensureCSharpIdentifier(program, bodyParam.type.type, bodyParam.type.name, NameCasingType.Parameter);
|
|
1117
|
+
result.push({
|
|
1118
|
+
isExplicitBody: true,
|
|
1119
|
+
httpParameterKind: "body",
|
|
1120
|
+
name: optName,
|
|
1121
|
+
callName: optName,
|
|
1122
|
+
typeName: csType,
|
|
1123
|
+
nullable: isNullable,
|
|
1124
|
+
defaultValue: csValue,
|
|
1125
|
+
optional: bodyParam.type.optional,
|
|
1126
|
+
operationKind: "All",
|
|
1127
|
+
});
|
|
1128
|
+
}
|
|
1129
|
+
break;
|
|
1130
|
+
default: {
|
|
1131
|
+
let { typeReference: csType, defaultValue: csValue, nullableType: isNullable, } = this.getTypeInfo(program, bodyParam.type);
|
|
1132
|
+
if (!canHaveDefault(program, bodyParam.type)) {
|
|
1133
|
+
csValue = undefined;
|
|
1134
|
+
}
|
|
1135
|
+
result.push({
|
|
1136
|
+
isExplicitBody: true,
|
|
1137
|
+
httpParameterKind: "body",
|
|
1138
|
+
name: "body",
|
|
1139
|
+
callName: "body",
|
|
1140
|
+
typeName: csType,
|
|
1141
|
+
nullable: isNullable,
|
|
1142
|
+
defaultValue: csValue,
|
|
1143
|
+
optional: false,
|
|
1144
|
+
operationKind: "All",
|
|
1145
|
+
});
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
for (const parameter of optionalParams) {
|
|
1150
|
+
const { typeReference: paramType, defaultValue: paramValue, nullableType: isNullable, } = this.getTypeInfo(program, parameter.param.type, parameter.param);
|
|
1151
|
+
const optName = ensureCSharpIdentifier(program, parameter.param, parameter.param.name, NameCasingType.Parameter);
|
|
1152
|
+
result.push({
|
|
1153
|
+
isExplicitBody: false,
|
|
1154
|
+
name: optName,
|
|
1155
|
+
callName: optName,
|
|
1156
|
+
optional: true,
|
|
1157
|
+
typeName: paramType,
|
|
1158
|
+
defaultValue: paramValue,
|
|
1159
|
+
httpParameterKind: getParameterKind(parameter),
|
|
1160
|
+
httpParameterName: parameter.name,
|
|
1161
|
+
nullable: isNullable,
|
|
1162
|
+
operationKind: "All",
|
|
1163
|
+
});
|
|
1164
|
+
}
|
|
1165
|
+
return result;
|
|
1166
|
+
}
|
|
1167
|
+
getDefaultValue(program, tsType, defaultValue) {
|
|
1168
|
+
if (defaultValue === undefined)
|
|
1169
|
+
return undefined;
|
|
1170
|
+
switch (tsType.kind) {
|
|
1171
|
+
case "Enum":
|
|
1172
|
+
if (defaultValue.valueKind === "EnumValue") {
|
|
1173
|
+
const retVal = this.getTypeInfo(program, tsType);
|
|
1174
|
+
return `${retVal.typeReference}.${ensureCSharpIdentifier(program, defaultValue.value, defaultValue.value.name, NameCasingType.Property)}`;
|
|
1175
|
+
}
|
|
1176
|
+
return JSON.stringify(serializeValueAsJson(this.emitter.getProgram(), defaultValue, tsType));
|
|
1177
|
+
case "Union":
|
|
1178
|
+
const { typeReference: typeRef } = this.getUnionInfo(program, tsType);
|
|
1179
|
+
if (defaultValue.valueKind === "StringValue" && isStringEnumType(program, tsType)) {
|
|
1180
|
+
const matches = [...tsType.variants].filter((v) => typeof v[0] === "string" &&
|
|
1181
|
+
v[1].type.kind === "String" &&
|
|
1182
|
+
v[1].type.value === defaultValue.value);
|
|
1183
|
+
if (matches.length === 1) {
|
|
1184
|
+
return `${typeRef}.${ensureCSharpIdentifier(program, matches[0][1], matches[0][0], NameCasingType.Property)}`;
|
|
1185
|
+
}
|
|
1186
|
+
return undefined;
|
|
1187
|
+
}
|
|
1188
|
+
if (defaultValue.valueKind === "StringValue") {
|
|
1189
|
+
return JSON.stringify(serializeValueAsJson(this.emitter.getProgram(), defaultValue, tsType));
|
|
1190
|
+
}
|
|
1191
|
+
return undefined;
|
|
1192
|
+
default:
|
|
1193
|
+
return JSON.stringify(serializeValueAsJson(this.emitter.getProgram(), defaultValue, tsType));
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
getTypeInfo(program, tsType, modelProperty) {
|
|
1197
|
+
const myEmitter = this.emitter;
|
|
1198
|
+
function extractStringValue(type, span) {
|
|
1199
|
+
switch (type.kind) {
|
|
1200
|
+
case "String":
|
|
1201
|
+
return type.value;
|
|
1202
|
+
case "Boolean":
|
|
1203
|
+
return `${type.value}`;
|
|
1204
|
+
case "Number":
|
|
1205
|
+
return type.valueAsString;
|
|
1206
|
+
case "StringTemplateSpan":
|
|
1207
|
+
if (type.isInterpolated) {
|
|
1208
|
+
return extractStringValue(type.type, span);
|
|
1209
|
+
}
|
|
1210
|
+
else {
|
|
1211
|
+
return type.type.value;
|
|
1212
|
+
}
|
|
1213
|
+
case "ModelProperty":
|
|
1214
|
+
return extractStringValue(type.type, span);
|
|
1215
|
+
case "EnumMember":
|
|
1216
|
+
if (type.value === undefined)
|
|
1217
|
+
return type.name;
|
|
1218
|
+
if (typeof type.value === "string")
|
|
1219
|
+
return type.value;
|
|
1220
|
+
if (typeof type.value === "number")
|
|
1221
|
+
return `${type.value}`;
|
|
1222
|
+
}
|
|
1223
|
+
reportDiagnostic(myEmitter.getProgram(), {
|
|
1224
|
+
code: "invalid-interpolation",
|
|
1225
|
+
target: span,
|
|
1226
|
+
format: {},
|
|
1227
|
+
});
|
|
1228
|
+
return "";
|
|
1229
|
+
}
|
|
1230
|
+
switch (tsType.kind) {
|
|
1231
|
+
case "String":
|
|
1232
|
+
return {
|
|
1233
|
+
typeReference: code `string`,
|
|
1234
|
+
defaultValue: `"${tsType.value}"`,
|
|
1235
|
+
nullableType: false,
|
|
1236
|
+
};
|
|
1237
|
+
case "StringTemplate":
|
|
1238
|
+
const template = tsType;
|
|
1239
|
+
if (template.stringValue !== undefined)
|
|
1240
|
+
return {
|
|
1241
|
+
typeReference: code `string`,
|
|
1242
|
+
defaultValue: `"${template.stringValue}"`,
|
|
1243
|
+
nullableType: false,
|
|
1244
|
+
};
|
|
1245
|
+
const spanResults = [];
|
|
1246
|
+
for (const span of template.spans) {
|
|
1247
|
+
spanResults.push(extractStringValue(span, span));
|
|
1248
|
+
}
|
|
1249
|
+
return {
|
|
1250
|
+
typeReference: code `string`,
|
|
1251
|
+
defaultValue: `"${spanResults.join("")}"`,
|
|
1252
|
+
nullableType: false,
|
|
1253
|
+
};
|
|
1254
|
+
case "Boolean":
|
|
1255
|
+
return {
|
|
1256
|
+
typeReference: code `bool`,
|
|
1257
|
+
defaultValue: `${tsType.value === true ? true : false}`,
|
|
1258
|
+
nullableType: false,
|
|
1259
|
+
};
|
|
1260
|
+
case "Number":
|
|
1261
|
+
const [type, value] = findNumericType(tsType);
|
|
1262
|
+
return { typeReference: code `${type}`, defaultValue: `${value}`, nullableType: false };
|
|
1263
|
+
case "Tuple":
|
|
1264
|
+
const defaults = [];
|
|
1265
|
+
const [csharpType, isObject] = coalesceTsTypes(program, tsType.values);
|
|
1266
|
+
if (isObject)
|
|
1267
|
+
return { typeReference: "object[]", defaultValue: undefined, nullableType: false };
|
|
1268
|
+
for (const value of tsType.values) {
|
|
1269
|
+
const { defaultValue: itemDefault } = this.getTypeInfo(program, value);
|
|
1270
|
+
defaults.push(itemDefault);
|
|
1271
|
+
}
|
|
1272
|
+
const collectionType = CSharpServiceOptions.getInstance().collectionType;
|
|
1273
|
+
switch (collectionType) {
|
|
1274
|
+
case CollectionType.IEnumerable:
|
|
1275
|
+
return {
|
|
1276
|
+
typeReference: code `IEnumerable<${csharpType.getTypeReference(myEmitter.getContext()?.scope)}>`,
|
|
1277
|
+
defaultValue: `new List<${csharpType.getTypeReference(myEmitter.getContext()?.scope)}> {${defaults.join(", ")}}`,
|
|
1278
|
+
nullableType: csharpType.isNullable,
|
|
1279
|
+
};
|
|
1280
|
+
case CollectionType.Array:
|
|
1281
|
+
default:
|
|
1282
|
+
return {
|
|
1283
|
+
typeReference: code `${csharpType.getTypeReference(myEmitter.getContext()?.scope)}[]`,
|
|
1284
|
+
defaultValue: `[${defaults.join(", ")}]`,
|
|
1285
|
+
nullableType: csharpType.isNullable,
|
|
1286
|
+
};
|
|
1287
|
+
}
|
|
1288
|
+
case "Model":
|
|
1289
|
+
let modelResult;
|
|
1290
|
+
const hasUniqueItems = modelProperty
|
|
1291
|
+
? getUniqueItems(program, modelProperty) !== undefined
|
|
1292
|
+
: false;
|
|
1293
|
+
const cachedResult = this.#anonymousModels.get(tsType);
|
|
1294
|
+
if (cachedResult && cachedResult.hasUniqueItems === hasUniqueItems) {
|
|
1295
|
+
return cachedResult;
|
|
1296
|
+
}
|
|
1297
|
+
if (isRecord(tsType)) {
|
|
1298
|
+
modelResult = {
|
|
1299
|
+
typeReference: code `${RecordType.getTypeReference(myEmitter.getContext().scope)}`,
|
|
1300
|
+
nullableType: false,
|
|
1301
|
+
hasUniqueItems: hasUniqueItems,
|
|
1302
|
+
};
|
|
1303
|
+
}
|
|
1304
|
+
else if (isArrayModelType(tsType)) {
|
|
1305
|
+
const typeReference = code `${this.emitter.emitTypeReference(tsType.indexer.value)}`;
|
|
1306
|
+
modelResult = isByteType(tsType.indexer.value)
|
|
1307
|
+
? {
|
|
1308
|
+
typeReference: code `${typeReference}[]`,
|
|
1309
|
+
nullableType: false,
|
|
1310
|
+
hasUniqueItems: hasUniqueItems,
|
|
1311
|
+
}
|
|
1312
|
+
: {
|
|
1313
|
+
typeReference: hasUniqueItems
|
|
1314
|
+
? code `ISet<${typeReference}>`
|
|
1315
|
+
: code `${this.emitter.emitTypeReference(tsType)}`,
|
|
1316
|
+
nullableType: false,
|
|
1317
|
+
hasUniqueItems: hasUniqueItems,
|
|
1318
|
+
};
|
|
1319
|
+
}
|
|
1320
|
+
else {
|
|
1321
|
+
modelResult = {
|
|
1322
|
+
typeReference: code `${this.emitter.emitTypeReference(tsType, this.emitter.getContext())}`,
|
|
1323
|
+
nullableType: false,
|
|
1324
|
+
hasUniqueItems: hasUniqueItems,
|
|
1325
|
+
};
|
|
1326
|
+
}
|
|
1327
|
+
return modelResult;
|
|
1328
|
+
case "ModelProperty":
|
|
1329
|
+
return this.getTypeInfo(program, tsType.type, tsType);
|
|
1330
|
+
case "Enum":
|
|
1331
|
+
if (getEnumType(tsType) === "double")
|
|
1332
|
+
return { typeReference: getDoubleType().getTypeReference(), nullableType: false };
|
|
1333
|
+
return {
|
|
1334
|
+
typeReference: code `${this.emitter.emitTypeReference(tsType)}`,
|
|
1335
|
+
nullableType: false,
|
|
1336
|
+
};
|
|
1337
|
+
case "EnumMember":
|
|
1338
|
+
if (typeof tsType.value === "number") {
|
|
1339
|
+
const stringValue = tsType.value.toString();
|
|
1340
|
+
if (stringValue.includes(".") || stringValue.includes("e"))
|
|
1341
|
+
return { typeReference: "double", defaultValue: stringValue, nullableType: false };
|
|
1342
|
+
return { typeReference: "int", defaultValue: stringValue, nullableType: false };
|
|
1343
|
+
}
|
|
1344
|
+
if (typeof tsType.value === "string") {
|
|
1345
|
+
const retVal = this.getTypeInfo(program, tsType.enum);
|
|
1346
|
+
retVal.defaultValue = `${retVal.typeReference}.${ensureCSharpIdentifier(program, tsType, tsType.name, NameCasingType.Property)}`;
|
|
1347
|
+
return retVal;
|
|
1348
|
+
}
|
|
1349
|
+
return { typeReference: code `object`, nullableType: false };
|
|
1350
|
+
case "Union":
|
|
1351
|
+
return this.getUnionInfo(program, tsType);
|
|
1352
|
+
case "UnionVariant":
|
|
1353
|
+
if (isStringEnumType(program, tsType.union) &&
|
|
1354
|
+
tsType.type.kind === "String" &&
|
|
1355
|
+
typeof tsType.name === "string") {
|
|
1356
|
+
const retVal = this.getUnionInfo(program, tsType.union);
|
|
1357
|
+
retVal.defaultValue = `${retVal.typeReference}.${ensureCSharpIdentifier(program, tsType, tsType.name, NameCasingType.Property)}`;
|
|
1358
|
+
return retVal;
|
|
1359
|
+
}
|
|
1360
|
+
return this.getTypeInfo(program, tsType.type);
|
|
1361
|
+
default:
|
|
1362
|
+
return {
|
|
1363
|
+
typeReference: code `${this.emitter.emitTypeReference(tsType)}`,
|
|
1364
|
+
nullableType: false,
|
|
1365
|
+
};
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
getUnionInfo(program, union) {
|
|
1369
|
+
const propResult = getNonNullableTsType(union);
|
|
1370
|
+
if (propResult === undefined || isStringEnumType(program, union)) {
|
|
1371
|
+
return {
|
|
1372
|
+
typeReference: code `${this.emitter.emitTypeReference(union)}`,
|
|
1373
|
+
nullableType: [...union.variants.values()].some((v) => isNullType(v.type)),
|
|
1374
|
+
};
|
|
1375
|
+
}
|
|
1376
|
+
const candidate = this.getTypeInfo(program, propResult.type);
|
|
1377
|
+
candidate.nullableType = propResult.nullable;
|
|
1378
|
+
return candidate;
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
export function getExplicitBodyParameters(program, httpOperation) {
|
|
1382
|
+
if (httpOperation.parameters.body && httpOperation.parameters.body.bodyKind === "multipart") {
|
|
1383
|
+
return [
|
|
1384
|
+
{
|
|
1385
|
+
name: "reader",
|
|
1386
|
+
callName: "reader",
|
|
1387
|
+
nullable: false,
|
|
1388
|
+
optional: false,
|
|
1389
|
+
typeName: "MultipartReader",
|
|
1390
|
+
isExplicitBody: false,
|
|
1391
|
+
httpParameterKind: "body",
|
|
1392
|
+
operationKind: "BusinessLogic",
|
|
1393
|
+
},
|
|
1394
|
+
];
|
|
1395
|
+
}
|
|
1396
|
+
return undefined;
|
|
1397
|
+
}
|
|
1398
|
+
export function findNumericType(type) {
|
|
1399
|
+
const stringValue = type.valueAsString;
|
|
1400
|
+
if (stringValue.includes(".") || stringValue.includes("e"))
|
|
1401
|
+
return ["double", stringValue];
|
|
1402
|
+
return ["int", stringValue];
|
|
1403
|
+
}
|
|
1404
|
+
export function isStringEnumType(program, union) {
|
|
1405
|
+
const baseType = coalesceUnionTypes(program, union);
|
|
1406
|
+
if (!baseType.isBuiltIn || baseType.name !== "string")
|
|
1407
|
+
return false;
|
|
1408
|
+
return ![...union.variants.values()].some((v) => (v.type.kind === "String" ||
|
|
1409
|
+
v.type.kind === "StringTemplate" ||
|
|
1410
|
+
v.type.kind === "StringTemplateSpan") &&
|
|
1411
|
+
typeof v.name !== "string");
|
|
1412
|
+
}
|
|
1413
|
+
export function coalesceUnionTypes(program, union) {
|
|
1414
|
+
const [result, _] = coalesceTsTypes(program, [...union.variants.values()].flatMap((v) => v.type));
|
|
1415
|
+
return result;
|
|
1416
|
+
}
|
|
1417
|
+
export function getNonNullableTsType(union) {
|
|
1418
|
+
const types = [...union.variants.values()];
|
|
1419
|
+
const nulls = types.flatMap((v) => v.type).filter((t) => isNullType(t));
|
|
1420
|
+
const nonNulls = types.flatMap((v) => v.type).filter((t) => !isNullType(t));
|
|
1421
|
+
if (nonNulls.length === 1)
|
|
1422
|
+
return { type: nonNulls[0], nullable: nulls.length > 0 };
|
|
1423
|
+
return undefined;
|
|
1424
|
+
}
|
|
1425
|
+
export function coalesceTsTypes(program, types) {
|
|
1426
|
+
const defaultValue = [
|
|
1427
|
+
new CSharpType({
|
|
1428
|
+
name: "object",
|
|
1429
|
+
namespace: "System",
|
|
1430
|
+
isBuiltIn: true,
|
|
1431
|
+
isValueType: false,
|
|
1432
|
+
}),
|
|
1433
|
+
true,
|
|
1434
|
+
];
|
|
1435
|
+
let current = undefined;
|
|
1436
|
+
let nullable = false;
|
|
1437
|
+
for (const type of types) {
|
|
1438
|
+
let candidate;
|
|
1439
|
+
switch (type.kind) {
|
|
1440
|
+
case "Boolean":
|
|
1441
|
+
candidate = new CSharpType({ name: "bool", namespace: "System", isValueType: true });
|
|
1442
|
+
break;
|
|
1443
|
+
case "StringTemplate":
|
|
1444
|
+
case "String":
|
|
1445
|
+
candidate = new CSharpType({ name: "string", namespace: "System", isValueType: false });
|
|
1446
|
+
break;
|
|
1447
|
+
case "Number":
|
|
1448
|
+
const stringValue = type.valueAsString;
|
|
1449
|
+
if (stringValue.includes(".") || stringValue.includes("e")) {
|
|
1450
|
+
candidate = new CSharpType({
|
|
1451
|
+
name: "double",
|
|
1452
|
+
namespace: "System",
|
|
1453
|
+
isValueType: true,
|
|
1454
|
+
});
|
|
1455
|
+
}
|
|
1456
|
+
else {
|
|
1457
|
+
candidate = new CSharpType({ name: "int", namespace: "System", isValueType: true });
|
|
1458
|
+
}
|
|
1459
|
+
break;
|
|
1460
|
+
case "Union":
|
|
1461
|
+
candidate = coalesceUnionTypes(program, type);
|
|
1462
|
+
break;
|
|
1463
|
+
case "Scalar":
|
|
1464
|
+
candidate = getCSharpTypeForScalar(program, type);
|
|
1465
|
+
break;
|
|
1466
|
+
case "Intrinsic":
|
|
1467
|
+
if (isNullType(type)) {
|
|
1468
|
+
nullable = true;
|
|
1469
|
+
candidate = current;
|
|
1470
|
+
}
|
|
1471
|
+
else {
|
|
1472
|
+
return defaultValue;
|
|
1473
|
+
}
|
|
1474
|
+
break;
|
|
1475
|
+
default:
|
|
1476
|
+
return defaultValue;
|
|
1477
|
+
}
|
|
1478
|
+
current = current ?? candidate;
|
|
1479
|
+
if (current === undefined || (candidate !== undefined && !candidate.equals(current)))
|
|
1480
|
+
return defaultValue;
|
|
1481
|
+
}
|
|
1482
|
+
if (current !== undefined && nullable === true)
|
|
1483
|
+
current.isNullable = true;
|
|
1484
|
+
return current === undefined ? defaultValue : [current, false];
|
|
1485
|
+
}
|
|
1486
|
+
export function isRecord(type) {
|
|
1487
|
+
return type.kind === "Model" && type.name === "Record" && type.indexer !== undefined;
|
|
1488
|
+
}
|
|
1489
|
+
export async function getFreePort(minPort, maxPort, tries = 100) {
|
|
1490
|
+
const min = Math.floor(minPort);
|
|
1491
|
+
const max = Math.floor(maxPort);
|
|
1492
|
+
if (tries === 0)
|
|
1493
|
+
return min;
|
|
1494
|
+
const diff = Math.abs(max - min);
|
|
1495
|
+
const port = min + Math.floor(Math.random() * diff);
|
|
1496
|
+
const server = createServer();
|
|
1497
|
+
const free = await checkPort(port);
|
|
1498
|
+
if (free) {
|
|
1499
|
+
return port;
|
|
1500
|
+
}
|
|
1501
|
+
// This seems like a bug? tries-- does nothing?
|
|
1502
|
+
// eslint-disable-next-line no-useless-assignment
|
|
1503
|
+
return await getFreePort(min, max, tries--);
|
|
1504
|
+
async function checkPort(port, timeout = 100) {
|
|
1505
|
+
return new Promise((resolve, _) => {
|
|
1506
|
+
server.on("error", (_) => {
|
|
1507
|
+
server.close();
|
|
1508
|
+
resolve(false);
|
|
1509
|
+
});
|
|
1510
|
+
server.listen(port, async () => {
|
|
1511
|
+
try {
|
|
1512
|
+
setTimeout(() => resolve(true), timeout);
|
|
1513
|
+
}
|
|
1514
|
+
catch (e) {
|
|
1515
|
+
resolve(false);
|
|
1516
|
+
}
|
|
1517
|
+
finally {
|
|
1518
|
+
server.close();
|
|
1519
|
+
}
|
|
1520
|
+
});
|
|
1521
|
+
});
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
export async function getOpenApiConfig(program) {
|
|
1525
|
+
const root = program.projectRoot;
|
|
1526
|
+
const [options, _] = await resolveCompilerOptions(program.host, {
|
|
1527
|
+
cwd: root,
|
|
1528
|
+
entrypoint: resolvePath(root, "main.tsp"),
|
|
1529
|
+
});
|
|
1530
|
+
const oaiOptions = options.options !== undefined && Object.keys(options.options).includes("@typespec/openapi3")
|
|
1531
|
+
? options.options["@typespec/openapi3"]
|
|
1532
|
+
: undefined;
|
|
1533
|
+
return {
|
|
1534
|
+
emitted: options.emit !== undefined && options.emit.includes("@typespec/openapi3"),
|
|
1535
|
+
outputDir: oaiOptions?.["emitter-output-dir"],
|
|
1536
|
+
fileName: oaiOptions?.["output-file"],
|
|
1537
|
+
options: oaiOptions,
|
|
1538
|
+
};
|
|
1539
|
+
}
|
|
1540
|
+
export function getStatusCode(program, model) {
|
|
1541
|
+
const statusCodeProperty = new ModelInfo().filterAllProperties(program, model, (p) => isStatusCode(program, p));
|
|
1542
|
+
if (!statusCodeProperty)
|
|
1543
|
+
return undefined;
|
|
1544
|
+
const { type } = statusCodeProperty;
|
|
1545
|
+
switch (type.kind) {
|
|
1546
|
+
case "Union":
|
|
1547
|
+
return {
|
|
1548
|
+
name: statusCodeProperty.name,
|
|
1549
|
+
value: statusCodeProperty.name,
|
|
1550
|
+
requiresConstructorArgument: true,
|
|
1551
|
+
};
|
|
1552
|
+
case "Number":
|
|
1553
|
+
return {
|
|
1554
|
+
value: type.value,
|
|
1555
|
+
};
|
|
1556
|
+
default:
|
|
1557
|
+
return { value: getMinValue(program, statusCodeProperty) ?? `default` };
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
export function isByteType(type) {
|
|
1561
|
+
return type.kind === "Scalar" && ["int8", "uint8"].includes(type.name);
|
|
1562
|
+
}
|
|
1563
|
+
export function getImports(scope, visited) {
|
|
1564
|
+
if (scope === undefined)
|
|
1565
|
+
return [];
|
|
1566
|
+
if (!visited)
|
|
1567
|
+
visited = new Set();
|
|
1568
|
+
if (visited.has(scope))
|
|
1569
|
+
return [];
|
|
1570
|
+
visited.add(scope);
|
|
1571
|
+
switch (scope.kind) {
|
|
1572
|
+
case "namespace":
|
|
1573
|
+
return getImports(scope.parentScope, visited);
|
|
1574
|
+
case "sourceFile":
|
|
1575
|
+
return [...scope.sourceFile.imports.keys()];
|
|
1576
|
+
default:
|
|
1577
|
+
return [];
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
645
1580
|
//# sourceMappingURL=utils.js.map
|