@azure-tools/typespec-ts 0.51.1 → 0.52.0
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/CHANGELOG.md +7 -0
- package/dist/src/modular/buildClassicalClient.d.ts.map +1 -1
- package/dist/src/modular/buildClassicalClient.js +1 -0
- package/dist/src/modular/buildClassicalClient.js.map +1 -1
- package/dist/src/modular/emitModels.d.ts.map +1 -1
- package/dist/src/modular/emitModels.js +28 -2
- package/dist/src/modular/emitModels.js.map +1 -1
- package/dist/src/modular/helpers/operationHelpers.d.ts +1 -1
- package/dist/src/modular/helpers/operationHelpers.d.ts.map +1 -1
- package/dist/src/modular/helpers/operationHelpers.js +156 -78
- package/dist/src/modular/helpers/operationHelpers.js.map +1 -1
- package/dist/src/modular/serialization/buildSerializerFunction.js +2 -7
- package/dist/src/modular/serialization/buildSerializerFunction.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +14 -21
- package/src/modular/buildClassicalClient.ts +1 -0
- package/src/modular/emitModels.ts +34 -2
- package/src/modular/helpers/operationHelpers.ts +182 -93
- package/src/modular/serialization/buildSerializerFunction.ts +2 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@azure-tools/typespec-ts",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.52.0",
|
|
4
4
|
"description": "An experimental TypeSpec emitter for TypeScript RLC",
|
|
5
5
|
"main": "dist/src/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -37,7 +37,6 @@
|
|
|
37
37
|
"@types/chai": "^4.3.1",
|
|
38
38
|
"@types/fs-extra": "^9.0.13",
|
|
39
39
|
"@types/lodash": "^4.17.4",
|
|
40
|
-
"@types/mocha": "^10.0.6",
|
|
41
40
|
"@typescript-eslint/eslint-plugin": "^8.28.0",
|
|
42
41
|
"@typescript-eslint/parser": "^8.28.0",
|
|
43
42
|
"@typespec/compiler": "^1.10.0",
|
|
@@ -51,13 +50,13 @@
|
|
|
51
50
|
"cross-env": "^7.0.3",
|
|
52
51
|
"eslint-plugin-require-extensions": "0.1.3",
|
|
53
52
|
"mkdirp": "^3.0.1",
|
|
54
|
-
"mocha": "^10.4.0",
|
|
55
53
|
"npm-run-all": "~4.1.5",
|
|
56
54
|
"prettier": "^3.3.3",
|
|
57
55
|
"ts-node": "~10.9.1",
|
|
58
|
-
"vitest": "
|
|
59
|
-
"
|
|
60
|
-
"@vitest/coverage-
|
|
56
|
+
"vitest": "^4.1.0",
|
|
57
|
+
"vite": "^6.0.0",
|
|
58
|
+
"@vitest/coverage-v8": "^4.1.0",
|
|
59
|
+
"@vitest/coverage-istanbul": "^4.1.0",
|
|
61
60
|
"@microsoft/api-extractor": "^7.47.5",
|
|
62
61
|
"tsx": "^4.16.5",
|
|
63
62
|
"@types/js-yaml": "^4.0.9",
|
|
@@ -73,7 +72,7 @@
|
|
|
73
72
|
"@typespec/xml": "^0.80.0"
|
|
74
73
|
},
|
|
75
74
|
"dependencies": {
|
|
76
|
-
"@azure-tools/rlc-common": "^0.
|
|
75
|
+
"@azure-tools/rlc-common": "^0.52.0",
|
|
77
76
|
"fast-xml-parser": "^4.5.0",
|
|
78
77
|
"fs-extra": "^11.1.0",
|
|
79
78
|
"lodash": "^4.17.21",
|
|
@@ -89,19 +88,13 @@
|
|
|
89
88
|
"LICENSE",
|
|
90
89
|
"static/**"
|
|
91
90
|
],
|
|
92
|
-
"mocha": {
|
|
93
|
-
"extension": [
|
|
94
|
-
"ts"
|
|
95
|
-
],
|
|
96
|
-
"loader": "ts-node/esm"
|
|
97
|
-
},
|
|
98
91
|
"bugs": {
|
|
99
92
|
"url": "https://github.com/Azure/autorest.typescript/issues"
|
|
100
93
|
},
|
|
101
94
|
"homepage": "https://github.com/Azure/autorest.typescript/tree/main/packages/typespec-ts/",
|
|
102
95
|
"scripts": {
|
|
103
|
-
"test-next": "vitest run
|
|
104
|
-
"test-next:coverage": "vitest run
|
|
96
|
+
"test-next": "vitest run --project test-next",
|
|
97
|
+
"test-next:coverage": "vitest run --project test-next --coverage",
|
|
105
98
|
"clean": "rimraf ./dist ./typespec-output",
|
|
106
99
|
"build": "tsc -p .",
|
|
107
100
|
"test": "npm run test-next && npm run unit-test && npm run integration-test-ci",
|
|
@@ -136,14 +129,14 @@
|
|
|
136
129
|
"generate-tsp-only:azure-modular": "npx tsx ./test/commands/gen-cadl-ranch.js --tag=azure-modular",
|
|
137
130
|
"regen-test-baselines": "npm run generate-tsp-only",
|
|
138
131
|
"integration-test:alone": "npm run integration-test:alone:rlc && npm run integration-test:alone:azure-rlc && npm run integration-test:alone:modular && npm run integration-test:alone:azure-modular",
|
|
139
|
-
"integration-test:alone:rlc": "
|
|
140
|
-
"integration-test:alone:azure-rlc": "
|
|
141
|
-
"integration-test:alone:modular": "
|
|
142
|
-
"integration-test:alone:azure-modular": "
|
|
132
|
+
"integration-test:alone:rlc": "vitest run --project integration-rlc",
|
|
133
|
+
"integration-test:alone:azure-rlc": "vitest run --project integration-azure-rlc",
|
|
134
|
+
"integration-test:alone:modular": "vitest run --project integration-modular",
|
|
135
|
+
"integration-test:alone:azure-modular": "vitest run --project integration-azure-modular",
|
|
143
136
|
"stop-test-server": "npx tsp-spector server stop",
|
|
144
137
|
"unit-test": "npm-run-all --parallel unit-test:rlc unit-test:modular",
|
|
145
|
-
"unit-test:rlc": "
|
|
146
|
-
"unit-test:modular": "cross-env
|
|
138
|
+
"unit-test:rlc": "vitest run --project unit-rlc",
|
|
139
|
+
"unit-test:modular": "cross-env NODE_OPTIONS=--max-old-space-size=8192 vitest run --project unit-modular",
|
|
147
140
|
"regen-docs": "npm run build && tspd doc . --enable-experimental --output-dir ./website/src/content/docs/docs/emitters/clients/typespec-ts/reference --skip-js"
|
|
148
141
|
}
|
|
149
142
|
}
|
|
@@ -732,13 +732,31 @@ function buildModelInterface(
|
|
|
732
732
|
type: SdkModelType
|
|
733
733
|
): InterfaceDeclarationStructure {
|
|
734
734
|
const flattenPropertySet = new Set<SdkModelPropertyType>();
|
|
735
|
+
// For non-input models (output-only, exception, etc.), filter out metadata
|
|
736
|
+
// properties (@header, @query, @path) since they are deserialized separately.
|
|
737
|
+
// For input models, keep metadata properties — users need to pass them.
|
|
738
|
+
const hasInputUsage = (type.usage & UsageFlags.Input) === UsageFlags.Input;
|
|
739
|
+
const isArmResource = isArmResourceModel(type);
|
|
735
740
|
const interfaceStructure = {
|
|
736
741
|
kind: StructureKind.Interface,
|
|
737
742
|
name: normalizeModelName(context, type, NameType.Interface, true),
|
|
738
743
|
isExported: true,
|
|
739
744
|
properties: type.properties
|
|
740
|
-
.filter((p) => !isMetadata(context.program, p.__raw!))
|
|
741
745
|
.filter((p) => {
|
|
746
|
+
if (!hasInputUsage && p.__raw && isMetadata(context.program, p.__raw)) {
|
|
747
|
+
return false;
|
|
748
|
+
}
|
|
749
|
+
// Skip the "name" metadata property on ARM resource models.
|
|
750
|
+
// ARM resource "name" is a @path property inherited from the base Resource type
|
|
751
|
+
// and is handled by the ARM infrastructure, not set by the user directly.
|
|
752
|
+
if (
|
|
753
|
+
isArmResource &&
|
|
754
|
+
p.name === "name" &&
|
|
755
|
+
p.__raw &&
|
|
756
|
+
isMetadata(context.program, p.__raw)
|
|
757
|
+
) {
|
|
758
|
+
return false;
|
|
759
|
+
}
|
|
742
760
|
// filter out the flatten property to be processed later
|
|
743
761
|
if (p.flatten && p.type.kind === "model") {
|
|
744
762
|
flattenPropertySet.add(p);
|
|
@@ -757,7 +775,7 @@ function buildModelInterface(
|
|
|
757
775
|
context,
|
|
758
776
|
flatten.type,
|
|
759
777
|
getAllAncestors(flatten.type)
|
|
760
|
-
)
|
|
778
|
+
);
|
|
761
779
|
interfaceStructure.properties!.push(
|
|
762
780
|
...allProperties.map((p) => {
|
|
763
781
|
// when the flattened property is optional, all its child properties should be optional too
|
|
@@ -932,6 +950,20 @@ export function normalizeModelName(
|
|
|
932
950
|
return `${internalModelPrefix}${normalizeName(namespacePrefix + type.name, nameType, true)}${unionSuffix}`;
|
|
933
951
|
}
|
|
934
952
|
|
|
953
|
+
/**
|
|
954
|
+
* Checks if a model descends from the ARM common-types Resource base type
|
|
955
|
+
* (TrackedResource, ProxyResource, etc.) by walking the ancestor chain.
|
|
956
|
+
*/
|
|
957
|
+
function isArmResourceModel(type: SdkModelType): boolean {
|
|
958
|
+
const ancestors = getAllAncestors(type);
|
|
959
|
+
return ancestors.some(
|
|
960
|
+
(ancestor) =>
|
|
961
|
+
ancestor.kind === "model" &&
|
|
962
|
+
ancestor.crossLanguageDefinitionId ===
|
|
963
|
+
"Azure.ResourceManager.CommonTypes.Resource"
|
|
964
|
+
);
|
|
965
|
+
}
|
|
966
|
+
|
|
935
967
|
function buildModelPolymorphicType(context: SdkContext, type: SdkModelType) {
|
|
936
968
|
// Only include direct subtypes in this union
|
|
937
969
|
const directSubtypes = getDirectSubtypes(type);
|
|
@@ -83,9 +83,9 @@ import {
|
|
|
83
83
|
SdkHttpOperation,
|
|
84
84
|
SdkHttpParameter,
|
|
85
85
|
SdkLroPagingServiceMethod,
|
|
86
|
+
SdkMethodParameter,
|
|
86
87
|
SdkLroServiceMethod,
|
|
87
88
|
SdkMethod,
|
|
88
|
-
SdkMethodParameter,
|
|
89
89
|
SdkModelPropertyType,
|
|
90
90
|
SdkModelType,
|
|
91
91
|
SdkPagingServiceMethod,
|
|
@@ -98,6 +98,7 @@ import {
|
|
|
98
98
|
getHeaderClientOptions,
|
|
99
99
|
getRestErrorCodeHeader
|
|
100
100
|
} from "./clientOptionHelpers.js";
|
|
101
|
+
import { getClientParameterName } from "./clientHelpers.js";
|
|
101
102
|
import { isExtensibleEnum } from "../type-expressions/get-enum-expression.js";
|
|
102
103
|
import { emitInlineModel } from "../type-expressions/get-model-expression.js";
|
|
103
104
|
|
|
@@ -205,7 +206,15 @@ export function getDeserializePrivateFunction(
|
|
|
205
206
|
let returnType;
|
|
206
207
|
|
|
207
208
|
if (isLroOnly || isLroAndPaging) {
|
|
208
|
-
|
|
209
|
+
if (isLroOnly && shouldWrap) {
|
|
210
|
+
// For LRO-only operations with non-model final result, wrap in a response type alias
|
|
211
|
+
returnType = {
|
|
212
|
+
name: getOperationResponseTypeName(method),
|
|
213
|
+
type: resolveReference(refkey(operation, "response"))
|
|
214
|
+
};
|
|
215
|
+
} else {
|
|
216
|
+
returnType = buildLroReturnType(context, operation);
|
|
217
|
+
}
|
|
209
218
|
} else if (isPagingOnly && restResponse?.type) {
|
|
210
219
|
// For paging operations, use the full response model (e.g., _OperationListResult)
|
|
211
220
|
// instead of just the array element type
|
|
@@ -392,7 +401,7 @@ export function getDeserializePrivateFunction(
|
|
|
392
401
|
skipDiscriminatedUnionSuffix: false
|
|
393
402
|
}
|
|
394
403
|
);
|
|
395
|
-
// Handle wrap-non-model-return for non-LRO, non-paging operations
|
|
404
|
+
// Handle wrap-non-model-return for non-LRO, non-paging and LRO-only operations
|
|
396
405
|
if (shouldWrap) {
|
|
397
406
|
if (isBinary) {
|
|
398
407
|
// Binary wrap: getBinaryStream already resolved the stream,
|
|
@@ -403,11 +412,12 @@ export function getDeserializePrivateFunction(
|
|
|
403
412
|
);
|
|
404
413
|
} else {
|
|
405
414
|
// Non-model response: wrap with body property
|
|
406
|
-
// Generate the appropriate deserialization for the body value
|
|
415
|
+
// Generate the appropriate deserialization for the body value.
|
|
416
|
+
// For LRO operations, deserializedRoot may include the sub-path (e.g. result.body.someProperty).
|
|
407
417
|
const bodyValue = deserializeResponseValue(
|
|
408
418
|
context,
|
|
409
419
|
deserializedType,
|
|
410
|
-
|
|
420
|
+
deserializedRoot,
|
|
411
421
|
true,
|
|
412
422
|
getEncodeForType(deserializedType)
|
|
413
423
|
);
|
|
@@ -1217,6 +1227,20 @@ function getLroOnlyOperationFunction(
|
|
|
1217
1227
|
const operationStateReference = resolveReference(
|
|
1218
1228
|
AzurePollingDependencies.OperationState
|
|
1219
1229
|
);
|
|
1230
|
+
|
|
1231
|
+
// When wrap-non-model-return is enabled and the LRO final result is a non-model type,
|
|
1232
|
+
// use the wrapper response type (e.g. GetIkeSasResponse) instead of the raw type (e.g. string).
|
|
1233
|
+
const { shouldWrap } = checkWrapNonModelReturn(
|
|
1234
|
+
context,
|
|
1235
|
+
operation as ServiceOperation
|
|
1236
|
+
);
|
|
1237
|
+
const effectiveReturnTypeStr = shouldWrap
|
|
1238
|
+
? (resolveReference(refkey(operation, "response")) as string)
|
|
1239
|
+
: returnType.type;
|
|
1240
|
+
const effectiveReturnTypeName = shouldWrap
|
|
1241
|
+
? getOperationResponseTypeName(method as [string[], ServiceOperation])
|
|
1242
|
+
: returnType.type;
|
|
1243
|
+
|
|
1220
1244
|
const functionStatement = {
|
|
1221
1245
|
kind: StructureKind.Function,
|
|
1222
1246
|
docs: [
|
|
@@ -1228,9 +1252,9 @@ function getLroOnlyOperationFunction(
|
|
|
1228
1252
|
name,
|
|
1229
1253
|
propertyName: normalizeName(operation.name, NameType.Property),
|
|
1230
1254
|
isLro: true,
|
|
1231
|
-
lroFinalReturnType:
|
|
1255
|
+
lroFinalReturnType: effectiveReturnTypeName,
|
|
1232
1256
|
parameters,
|
|
1233
|
-
returnType: `${pollerLikeReference}<${operationStateReference}<${
|
|
1257
|
+
returnType: `${pollerLikeReference}<${operationStateReference}<${effectiveReturnTypeStr}>, ${effectiveReturnTypeStr}>`
|
|
1234
1258
|
};
|
|
1235
1259
|
|
|
1236
1260
|
const getLongRunningPollerReference = resolveReference(
|
|
@@ -1266,9 +1290,7 @@ function getLroOnlyOperationFunction(
|
|
|
1266
1290
|
.join(", ")}),
|
|
1267
1291
|
${resourceLocationConfig}
|
|
1268
1292
|
${apiVersion ? `apiVersion: ${apiVersion}` : ""}
|
|
1269
|
-
}) as ${pollerLikeReference}<${operationStateReference}<${
|
|
1270
|
-
returnType.type
|
|
1271
|
-
}>, ${returnType.type}>;
|
|
1293
|
+
}) as ${pollerLikeReference}<${operationStateReference}<${effectiveReturnTypeStr}>, ${effectiveReturnTypeStr}>;
|
|
1272
1294
|
`);
|
|
1273
1295
|
|
|
1274
1296
|
return {
|
|
@@ -1531,7 +1553,7 @@ function getHeaderAndBodyParameters(
|
|
|
1531
1553
|
|
|
1532
1554
|
const parametersImplementation: Record<
|
|
1533
1555
|
"header" | "body",
|
|
1534
|
-
{ paramMap: string; param: SdkHttpParameter }[]
|
|
1556
|
+
{ paramMap: string; param: SdkHttpParameter; paramAccessor: string }[]
|
|
1535
1557
|
> = {
|
|
1536
1558
|
header: [],
|
|
1537
1559
|
body: []
|
|
@@ -1556,9 +1578,11 @@ function getHeaderAndBodyParameters(
|
|
|
1556
1578
|
param.methodParameterSegments &&
|
|
1557
1579
|
param.methodParameterSegments.length > 0
|
|
1558
1580
|
) {
|
|
1581
|
+
const paramAccessor = getParamAccessor(param, optionalParamName);
|
|
1559
1582
|
parametersImplementation[param.kind].push({
|
|
1560
|
-
paramMap: getParameterMap(dpgContext, param,
|
|
1561
|
-
param
|
|
1583
|
+
paramMap: getParameterMap(dpgContext, param, paramAccessor),
|
|
1584
|
+
param,
|
|
1585
|
+
paramAccessor
|
|
1562
1586
|
});
|
|
1563
1587
|
}
|
|
1564
1588
|
}
|
|
@@ -1577,7 +1601,7 @@ function getHeaderAndBodyParameters(
|
|
|
1577
1601
|
dpgContext.program,
|
|
1578
1602
|
i.paramMap,
|
|
1579
1603
|
i.param,
|
|
1580
|
-
|
|
1604
|
+
i.paramAccessor
|
|
1581
1605
|
)
|
|
1582
1606
|
)
|
|
1583
1607
|
.join(",\n")}, ...${optionalParamName}.requestOptions?.headers },`;
|
|
@@ -1600,9 +1624,8 @@ function buildHeaderParameter(
|
|
|
1600
1624
|
program: Program,
|
|
1601
1625
|
paramMap: string,
|
|
1602
1626
|
param: SdkHttpParameter,
|
|
1603
|
-
|
|
1627
|
+
paramAccessor: string
|
|
1604
1628
|
): string {
|
|
1605
|
-
const paramName = param.name;
|
|
1606
1629
|
const effectiveOptional = getEffectiveOptional(param);
|
|
1607
1630
|
if (!effectiveOptional && isTypeNullable(param.type) === true) {
|
|
1608
1631
|
reportDiagnostic(program, {
|
|
@@ -1611,12 +1634,13 @@ function buildHeaderParameter(
|
|
|
1611
1634
|
});
|
|
1612
1635
|
return paramMap;
|
|
1613
1636
|
}
|
|
1637
|
+
|
|
1614
1638
|
const conditions = [];
|
|
1615
1639
|
if (effectiveOptional) {
|
|
1616
|
-
conditions.push(`${
|
|
1640
|
+
conditions.push(`${paramAccessor} !== undefined`);
|
|
1617
1641
|
}
|
|
1618
1642
|
if (isTypeNullable(param.type) === true) {
|
|
1619
|
-
conditions.push(`${
|
|
1643
|
+
conditions.push(`${paramAccessor} !== null`);
|
|
1620
1644
|
}
|
|
1621
1645
|
return conditions.length > 0
|
|
1622
1646
|
? `...(${conditions.join(" && ")} ? {${paramMap}} : {})`
|
|
@@ -1747,7 +1771,7 @@ function getEncodingFormat(type: { format?: string }) {
|
|
|
1747
1771
|
export function getParameterMap(
|
|
1748
1772
|
context: SdkContext,
|
|
1749
1773
|
param: SdkHttpParameter,
|
|
1750
|
-
|
|
1774
|
+
paramAccessor: string
|
|
1751
1775
|
): string {
|
|
1752
1776
|
// Use lowercase for header names since HTTP headers are case-insensitive
|
|
1753
1777
|
const serializedName =
|
|
@@ -1772,18 +1796,18 @@ export function getParameterMap(
|
|
|
1772
1796
|
return getCollectionFormatForParam(
|
|
1773
1797
|
context,
|
|
1774
1798
|
param,
|
|
1775
|
-
|
|
1799
|
+
paramAccessor,
|
|
1776
1800
|
serializedName
|
|
1777
1801
|
);
|
|
1778
1802
|
}
|
|
1779
1803
|
|
|
1780
1804
|
// if the parameter or property is optional, we don't need to handle the default value
|
|
1781
1805
|
if (isOptional(param)) {
|
|
1782
|
-
return getOptional(context, param,
|
|
1806
|
+
return getOptional(context, param, serializedName, paramAccessor);
|
|
1783
1807
|
}
|
|
1784
1808
|
|
|
1785
1809
|
if (isRequired(param)) {
|
|
1786
|
-
return getRequired(context, param, serializedName);
|
|
1810
|
+
return getRequired(context, param, serializedName, paramAccessor);
|
|
1787
1811
|
}
|
|
1788
1812
|
|
|
1789
1813
|
reportDiagnostic(context.program, {
|
|
@@ -1802,14 +1826,14 @@ export function getParameterMap(
|
|
|
1802
1826
|
function getCollectionFormatForParam(
|
|
1803
1827
|
context: SdkContext,
|
|
1804
1828
|
param: SdkHttpParameter,
|
|
1805
|
-
|
|
1829
|
+
paramAccessor: string,
|
|
1806
1830
|
serializedName: string
|
|
1807
1831
|
) {
|
|
1808
1832
|
const format = (param as any).collectionFormat;
|
|
1809
1833
|
return `"${serializedName}": ${serializeRequestValue(
|
|
1810
1834
|
context,
|
|
1811
1835
|
param.type,
|
|
1812
|
-
|
|
1836
|
+
paramAccessor,
|
|
1813
1837
|
!param.optional,
|
|
1814
1838
|
format,
|
|
1815
1839
|
serializedName,
|
|
@@ -1877,21 +1901,21 @@ function isRequired(param: SdkHttpParameter) {
|
|
|
1877
1901
|
function getRequired(
|
|
1878
1902
|
context: SdkContext,
|
|
1879
1903
|
param: SdkHttpParameter,
|
|
1880
|
-
serializedName: string
|
|
1904
|
+
serializedName: string,
|
|
1905
|
+
paramAccessor: string
|
|
1881
1906
|
) {
|
|
1882
|
-
const clientValue = `${param.onClient ? "context." : ""}${param.name}`;
|
|
1883
1907
|
if (param.type.kind === "model") {
|
|
1884
1908
|
const propertiesStr = getRequestModelMapping(
|
|
1885
1909
|
context,
|
|
1886
1910
|
{ ...param.type, optional: param.optional },
|
|
1887
|
-
|
|
1911
|
+
paramAccessor
|
|
1888
1912
|
);
|
|
1889
1913
|
return `"${serializedName}": { ${propertiesStr.join(",")} }`;
|
|
1890
1914
|
}
|
|
1891
1915
|
return `"${serializedName}": ${serializeRequestValue(
|
|
1892
1916
|
context,
|
|
1893
1917
|
param.type,
|
|
1894
|
-
|
|
1918
|
+
paramAccessor,
|
|
1895
1919
|
true,
|
|
1896
1920
|
getEncodeForType(param.type),
|
|
1897
1921
|
serializedName,
|
|
@@ -1917,11 +1941,9 @@ function isOptional(param: SdkHttpParameter) {
|
|
|
1917
1941
|
function getOptional(
|
|
1918
1942
|
context: SdkContext,
|
|
1919
1943
|
param: SdkHttpParameter,
|
|
1920
|
-
|
|
1921
|
-
|
|
1944
|
+
serializedName: string,
|
|
1945
|
+
paramAccessor: string
|
|
1922
1946
|
) {
|
|
1923
|
-
const paramName = `${param.onClient ? "context." : `${optionalParamName}?.`}${param.name}`;
|
|
1924
|
-
|
|
1925
1947
|
// Apply client default value if present and type matches
|
|
1926
1948
|
const defaultSuffix =
|
|
1927
1949
|
param.clientDefaultValue !== undefined &&
|
|
@@ -1933,7 +1955,7 @@ function getOptional(
|
|
|
1933
1955
|
const propertiesStr = getRequestModelMapping(
|
|
1934
1956
|
context,
|
|
1935
1957
|
{ ...param.type, optional: param.optional },
|
|
1936
|
-
|
|
1958
|
+
paramAccessor + "?."
|
|
1937
1959
|
);
|
|
1938
1960
|
const serializeContent = `{${propertiesStr.join(",")}}`;
|
|
1939
1961
|
return `"${serializedName}": ${serializeContent}`;
|
|
@@ -1941,7 +1963,7 @@ function getOptional(
|
|
|
1941
1963
|
const serializedValue = serializeRequestValue(
|
|
1942
1964
|
context,
|
|
1943
1965
|
param.type,
|
|
1944
|
-
|
|
1966
|
+
paramAccessor,
|
|
1945
1967
|
false,
|
|
1946
1968
|
getEncodeForType(param.type),
|
|
1947
1969
|
serializedName,
|
|
@@ -1992,7 +2014,7 @@ function getPathParameters(
|
|
|
1992
2014
|
const methodParam = param.methodParameterSegments[0]?.[0];
|
|
1993
2015
|
if (methodParam) {
|
|
1994
2016
|
pathParams.push(
|
|
1995
|
-
`"${param.serializedName}": ${getPathParamExpr(
|
|
2017
|
+
`"${param.serializedName}": ${getPathParamExpr(param, getDefaultValue(param) as string, optionalParamName)}`
|
|
1996
2018
|
);
|
|
1997
2019
|
}
|
|
1998
2020
|
}
|
|
@@ -2028,13 +2050,18 @@ function getQueryParameters(
|
|
|
2028
2050
|
param.methodParameterSegments &&
|
|
2029
2051
|
param.methodParameterSegments.length > 0
|
|
2030
2052
|
) {
|
|
2053
|
+
const paramAccessor = getParamAccessor(param);
|
|
2031
2054
|
parametersImplementation[param.kind].push({
|
|
2032
|
-
paramMap: getParameterMap(
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2055
|
+
paramMap: getParameterMap(
|
|
2056
|
+
dpgContext,
|
|
2057
|
+
{
|
|
2058
|
+
...param,
|
|
2059
|
+
// TODO: remember to remove this hack once compiler gives us a name
|
|
2060
|
+
// https://github.com/microsoft/typespec/issues/6743
|
|
2061
|
+
serializedName: getUriTemplateQueryParamName(param.serializedName)
|
|
2062
|
+
},
|
|
2063
|
+
paramAccessor
|
|
2064
|
+
),
|
|
2038
2065
|
param
|
|
2039
2066
|
});
|
|
2040
2067
|
}
|
|
@@ -2057,19 +2084,83 @@ function escapeUriTemplateParamName(name: string) {
|
|
|
2057
2084
|
});
|
|
2058
2085
|
}
|
|
2059
2086
|
|
|
2087
|
+
/**
|
|
2088
|
+
* Returns the parameter expression matching the operation signature for an HTTP parameter.
|
|
2089
|
+
* 1. Client-level parameter (`param.onClient`): returns `context.<paramName>`.
|
|
2090
|
+
* 2. Method-level parameter with `methodParameterSegments`: returns the expression
|
|
2091
|
+
* built from those segments (e.g. `options?.ocpDate`, `body.nested`).
|
|
2092
|
+
* 3. Fallback: returns the parameter name directly, with optional chaining if optional.
|
|
2093
|
+
*/
|
|
2094
|
+
function getParamAccessor(
|
|
2095
|
+
param: SdkHttpParameter,
|
|
2096
|
+
optionalParamName: string = "options"
|
|
2097
|
+
): string {
|
|
2098
|
+
const methodParamExpr = getMethodParamExpr(param, optionalParamName);
|
|
2099
|
+
const clientPrefix = "context.";
|
|
2100
|
+
if (methodParamExpr) {
|
|
2101
|
+
return param.onClient
|
|
2102
|
+
? `${clientPrefix}${methodParamExpr}`
|
|
2103
|
+
: methodParamExpr;
|
|
2104
|
+
}
|
|
2105
|
+
if (param.onClient) {
|
|
2106
|
+
return `${clientPrefix}${getClientParameterName(param)}`;
|
|
2107
|
+
}
|
|
2108
|
+
if (getEffectiveOptional(param)) {
|
|
2109
|
+
return `${optionalParamName}?.${param.name}`;
|
|
2110
|
+
}
|
|
2111
|
+
return param.name;
|
|
2112
|
+
}
|
|
2113
|
+
|
|
2114
|
+
/**
|
|
2115
|
+
* Builds a property accessor expression from the param's `methodParameterSegments`.
|
|
2116
|
+
* Each segment represents a level of property access (e.g. `options?.nested.value`).
|
|
2117
|
+
* Returns `undefined` when no segments are available, so the caller can fall back.
|
|
2118
|
+
*/
|
|
2119
|
+
function getMethodParamExpr(
|
|
2120
|
+
param: SdkHttpParameter,
|
|
2121
|
+
optionalParamName: string = "options"
|
|
2122
|
+
): string | undefined {
|
|
2123
|
+
const segments = param.methodParameterSegments;
|
|
2124
|
+
if (segments.length === 0) {
|
|
2125
|
+
return undefined;
|
|
2126
|
+
}
|
|
2127
|
+
const path = segments[0];
|
|
2128
|
+
if (!path || path.length < 1) {
|
|
2129
|
+
return undefined;
|
|
2130
|
+
}
|
|
2131
|
+
|
|
2132
|
+
const parts: string[] = [];
|
|
2133
|
+
for (let i = 0; i < path.length; i++) {
|
|
2134
|
+
const segment = path[i]!;
|
|
2135
|
+
if (i === 0) {
|
|
2136
|
+
// Normalize names for client-level segments to match the context interface property names
|
|
2137
|
+
const segmentName = segment.onClient
|
|
2138
|
+
? getClientParameterName(segment as SdkMethodParameter)
|
|
2139
|
+
: segment.name;
|
|
2140
|
+
if (segment.optional && !segment.onClient) {
|
|
2141
|
+
// If the first segment is optional and not on the client, we need to start with the optionalParamName
|
|
2142
|
+
parts.push(`${optionalParamName}?.`);
|
|
2143
|
+
}
|
|
2144
|
+
parts.push(segmentName);
|
|
2145
|
+
} else {
|
|
2146
|
+
const needsOptionalChain = path[i - 1]!.optional;
|
|
2147
|
+
parts.push(`${needsOptionalChain ? "?." : "."}${segment.name}`);
|
|
2148
|
+
}
|
|
2149
|
+
}
|
|
2150
|
+
return parts.join("");
|
|
2151
|
+
}
|
|
2152
|
+
|
|
2060
2153
|
function getPathParamExpr(
|
|
2061
|
-
param:
|
|
2154
|
+
param: SdkHttpParameter,
|
|
2062
2155
|
defaultValue?: string,
|
|
2063
2156
|
optionalParamName: string = "options"
|
|
2064
2157
|
) {
|
|
2065
2158
|
if (isConstant(param.type)) {
|
|
2066
2159
|
return getConstantValue(param.type);
|
|
2067
2160
|
}
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
? `${optionalParamName}["${param.name}"]`
|
|
2072
|
-
: param.name;
|
|
2161
|
+
|
|
2162
|
+
const paramName = getParamAccessor(param, optionalParamName);
|
|
2163
|
+
|
|
2073
2164
|
return defaultValue
|
|
2074
2165
|
? typeof defaultValue === "string"
|
|
2075
2166
|
? `${paramName} ?? "${defaultValue}"`
|
|
@@ -3031,6 +3122,32 @@ export function getOperationResponseTypeName(
|
|
|
3031
3122
|
return `${prefix}${normalizeName(operation.name, NameType.Interface)}Response`;
|
|
3032
3123
|
}
|
|
3033
3124
|
|
|
3125
|
+
/**
|
|
3126
|
+
* Returns true when a type should be wrapped with a `body` property.
|
|
3127
|
+
* Wrapping is needed for primitive/enum types; composite (model, dict, model-array)
|
|
3128
|
+
* and unknown-as-record types map to the HLC PropertyKind.Composite / Dictionary
|
|
3129
|
+
* patterns which do NOT get a body wrapper.
|
|
3130
|
+
*
|
|
3131
|
+
* Covered cases (no wrap):
|
|
3132
|
+
* - model array (e.g. Foo[]) → HLC PropertyKind.Composite
|
|
3133
|
+
* - model → HLC PropertyKind.Composite
|
|
3134
|
+
* - dict / Record<string, unknown> → HLC PropertyKind.Dictionary
|
|
3135
|
+
* - unknown with treatUnknownAsRecord → treated as Dict
|
|
3136
|
+
*
|
|
3137
|
+
* Covered cases (wrap):
|
|
3138
|
+
* - string, boolean, number → HLC PropertyKind.Primitive
|
|
3139
|
+
* - string[] → HLC PropertyKind.Primitive (item kind)
|
|
3140
|
+
* - enum / KnownXxx | string → HLC PropertyKind.Enum
|
|
3141
|
+
* - any / unknown (no treatAsRecord) → HLC PropertyKind.Primitive
|
|
3142
|
+
*/
|
|
3143
|
+
function isWrappableType(context: SdkContext, type: SdkType): boolean {
|
|
3144
|
+
if (type.kind === "array" && type.valueType.kind === "model") return false;
|
|
3145
|
+
if (type.kind === "dict" || type.kind === "model") return false;
|
|
3146
|
+
if (type.kind === "unknown" && context.rlcOptions?.treatUnknownAsRecord)
|
|
3147
|
+
return false;
|
|
3148
|
+
return true;
|
|
3149
|
+
}
|
|
3150
|
+
|
|
3034
3151
|
/**
|
|
3035
3152
|
* Determines whether wrapping the non-model return type is needed for an operation.
|
|
3036
3153
|
* Returns an object with `shouldWrap` (whether to wrap) and `isBinary` (whether it's a binary response).
|
|
@@ -3041,12 +3158,8 @@ export function checkWrapNonModelReturn(
|
|
|
3041
3158
|
): { shouldWrap: boolean; isBinary: boolean } {
|
|
3042
3159
|
const noWrap = { shouldWrap: false, isBinary: false };
|
|
3043
3160
|
|
|
3044
|
-
//
|
|
3045
|
-
if (
|
|
3046
|
-
isLroOnlyOperation(operation) ||
|
|
3047
|
-
isLroAndPagingOperation(operation) ||
|
|
3048
|
-
isPagingOnlyOperation(operation)
|
|
3049
|
-
) {
|
|
3161
|
+
// LRO+paging and paging-only operations are not wrapped
|
|
3162
|
+
if (isLroAndPagingOperation(operation) || isPagingOnlyOperation(operation)) {
|
|
3050
3163
|
return noWrap;
|
|
3051
3164
|
}
|
|
3052
3165
|
|
|
@@ -3055,56 +3168,32 @@ export function checkWrapNonModelReturn(
|
|
|
3055
3168
|
return noWrap;
|
|
3056
3169
|
}
|
|
3057
3170
|
|
|
3058
|
-
|
|
3059
|
-
if (
|
|
3171
|
+
// For LRO-only operations, check the final result type from LRO metadata
|
|
3172
|
+
if (isLroOnlyOperation(operation)) {
|
|
3173
|
+
const lroResultType = operation.lroMetadata?.finalResponse?.result;
|
|
3174
|
+
if (!lroResultType) {
|
|
3175
|
+
return noWrap; // void LRO - no wrap needed
|
|
3176
|
+
}
|
|
3177
|
+
return {
|
|
3178
|
+
shouldWrap: isWrappableType(context, lroResultType),
|
|
3179
|
+
isBinary: false
|
|
3180
|
+
};
|
|
3181
|
+
}
|
|
3182
|
+
|
|
3183
|
+
const { type } = operation.response;
|
|
3184
|
+
if (!type) {
|
|
3060
3185
|
return noWrap; // void return type - no wrap needed
|
|
3061
3186
|
}
|
|
3062
3187
|
|
|
3063
|
-
const type = response.type;
|
|
3064
3188
|
const contentTypes = operation.operation.responses[0]?.contentTypes ?? [];
|
|
3065
3189
|
|
|
3066
|
-
//
|
|
3067
|
-
// HLC wraps with `body` only when the type is PropertyKind.Primitive or PropertyKind.Enum
|
|
3068
|
-
// (i.e. NOT PropertyKind.Composite or PropertyKind.Dictionary).
|
|
3069
|
-
// For array types, HLC uses the item's kind, not the array itself.
|
|
3070
|
-
//
|
|
3071
|
-
// Case: bytes with binary content type → binary wrap (isBinary=true)
|
|
3190
|
+
// bytes with binary content type → binary wrap (isBinary=true)
|
|
3072
3191
|
// HLC: bytes → binary payload → separate binary handling
|
|
3073
3192
|
if (type.__raw && isBinaryPayload(context, type.__raw, contentTypes)) {
|
|
3074
3193
|
return { shouldWrap: true, isBinary: true };
|
|
3075
3194
|
}
|
|
3076
3195
|
|
|
3077
|
-
|
|
3078
|
-
// HLC: model array → PropertyKind.Composite (item kind = model) → no body wrapper
|
|
3079
|
-
// Modular: array with valueType.kind === "model"
|
|
3080
|
-
if (type.kind === "array" && type.valueType.kind === "model") {
|
|
3081
|
-
return noWrap;
|
|
3082
|
-
}
|
|
3083
|
-
|
|
3084
|
-
// Case: any object / Record (e.g. Record<string, unknown>) → no wrap
|
|
3085
|
-
// HLC: SchemaType.AnyObject → PropertyKind.Dictionary → no body wrapper
|
|
3086
|
-
// Modular: kind === "dict"
|
|
3087
|
-
// Case: model → no wrap
|
|
3088
|
-
// HLC: model → PropertyKind.Composite → no body wrapper
|
|
3089
|
-
// Modular: kind === "model"
|
|
3090
|
-
if (type.kind === "dict" || type.kind === "model") {
|
|
3091
|
-
return noWrap;
|
|
3092
|
-
}
|
|
3093
|
-
|
|
3094
|
-
// Case: unknown with treatUnknownAsRecord enabled → no wrap
|
|
3095
|
-
// When treatUnknownAsRecord is enabled, `unknown` is treated as Record<string, unknown>
|
|
3096
|
-
// which maps to HLC PropertyKind.Dictionary → no body wrapper
|
|
3097
|
-
if (type.kind === "unknown" && context.rlcOptions?.treatUnknownAsRecord) {
|
|
3098
|
-
return noWrap;
|
|
3099
|
-
}
|
|
3100
|
-
|
|
3101
|
-
// Remaining cases → wrap with `body`:
|
|
3102
|
-
// - string → HLC PropertyKind.Primitive → modular `string`
|
|
3103
|
-
// - boolean → HLC PropertyKind.Primitive → modular `boolean`
|
|
3104
|
-
// - string[] → HLC PropertyKind.Primitive (array keeps item kind) → modular `string[]`
|
|
3105
|
-
// - any → HLC PropertyKind.Primitive → modular `unknown` or `any`
|
|
3106
|
-
// - enum → HLC PropertyKind.Enum → modular `KnownXxx | string`
|
|
3107
|
-
return { shouldWrap: true, isBinary: false };
|
|
3196
|
+
return { shouldWrap: isWrappableType(context, type), isBinary: false };
|
|
3108
3197
|
}
|
|
3109
3198
|
|
|
3110
3199
|
/**
|