@azure-tools/typespec-ts 0.51.0 → 0.51.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/CHANGELOG.md +12 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +8 -4
- package/dist/src/index.js.map +1 -1
- package/dist/src/lib.d.ts +10 -3
- package/dist/src/lib.d.ts.map +1 -1
- package/dist/src/lib.js +6 -1
- package/dist/src/lib.js.map +1 -1
- package/dist/src/modular/emitModels.d.ts.map +1 -1
- package/dist/src/modular/emitModels.js +45 -11
- package/dist/src/modular/emitModels.js.map +1 -1
- package/dist/src/modular/helpers/operationHelpers.d.ts.map +1 -1
- package/dist/src/modular/helpers/operationHelpers.js +65 -43
- package/dist/src/modular/helpers/operationHelpers.js.map +1 -1
- package/dist/src/modular/static-helpers-metadata.d.ts +3 -3
- package/dist/src/modular/static-helpers-metadata.js +3 -3
- package/dist/src/modular/static-helpers-metadata.js.map +1 -1
- package/dist/src/modular/type-expressions/get-type-expression.d.ts.map +1 -1
- package/dist/src/modular/type-expressions/get-type-expression.js +4 -1
- package/dist/src/modular/type-expressions/get-type-expression.js.map +1 -1
- package/dist/src/transform/transfromRLCOptions.d.ts.map +1 -1
- package/dist/src/transform/transfromRLCOptions.js +3 -1
- package/dist/src/transform/transfromRLCOptions.js.map +1 -1
- package/dist/src/utils/clientUtils.d.ts.map +1 -1
- package/dist/src/utils/clientUtils.js +4 -0
- package/dist/src/utils/clientUtils.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +12 -12
- package/src/index.ts +6 -0
- package/src/lib.ts +14 -1
- package/src/modular/emitModels.ts +52 -13
- package/src/modular/helpers/operationHelpers.ts +72 -59
- package/src/modular/static-helpers-metadata.ts +3 -3
- package/src/modular/type-expressions/get-type-expression.ts +3 -1
- package/src/transform/transfromRLCOptions.ts +4 -1
- package/src/utils/clientUtils.ts +4 -0
- package/static/static-helpers/serialization/get-binary-stream-response-browser.mts +23 -0
- package/static/static-helpers/serialization/get-binary-stream-response.ts +23 -0
- package/static/static-helpers/serialization/xml-helpers.ts +1 -1
- package/static/static-helpers/serialization/get-binary-response-body-browser.mts +0 -22
- package/static/static-helpers/serialization/get-binary-response-body.ts +0 -24
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@azure-tools/typespec-ts",
|
|
3
|
-
"version": "0.51.
|
|
3
|
+
"version": "0.51.1",
|
|
4
4
|
"description": "An experimental TypeSpec emitter for TypeScript RLC",
|
|
5
5
|
"main": "dist/src/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -18,15 +18,15 @@
|
|
|
18
18
|
"license": "MIT",
|
|
19
19
|
"devDependencies": {
|
|
20
20
|
"@azure-rest/core-client": "^2.3.1",
|
|
21
|
-
"@typespec/http-specs": "0.1.0-
|
|
22
|
-
"@typespec/spector": "0.1.0-dev.
|
|
23
|
-
"@typespec/spec-api": "0.1.0-alpha.14-dev.
|
|
21
|
+
"@typespec/http-specs": "0.1.0-dev.2",
|
|
22
|
+
"@typespec/spector": "0.1.0-dev.3",
|
|
23
|
+
"@typespec/spec-api": "0.1.0-alpha.14-dev.3",
|
|
24
24
|
"@typespec/tspd": "0.74.1",
|
|
25
|
-
"@azure-tools/azure-http-specs": "0.1.0-alpha.39-dev.
|
|
26
|
-
"@azure-tools/typespec-autorest": "^0.66.
|
|
27
|
-
"@azure-tools/typespec-azure-core": "^0.66.
|
|
28
|
-
"@azure-tools/typespec-azure-resource-manager": "^0.66.
|
|
29
|
-
"@azure-tools/typespec-client-generator-core": "^0.66.
|
|
25
|
+
"@azure-tools/azure-http-specs": "0.1.0-alpha.39-dev.7",
|
|
26
|
+
"@azure-tools/typespec-autorest": "^0.66.1",
|
|
27
|
+
"@azure-tools/typespec-azure-core": "^0.66.1",
|
|
28
|
+
"@azure-tools/typespec-azure-resource-manager": "^0.66.1",
|
|
29
|
+
"@azure-tools/typespec-client-generator-core": "^0.66.4",
|
|
30
30
|
"@azure/abort-controller": "^2.1.2",
|
|
31
31
|
"@azure/core-auth": "^1.6.0",
|
|
32
32
|
"@azure/core-lro": "^3.1.0",
|
|
@@ -64,8 +64,8 @@
|
|
|
64
64
|
"js-yaml": "^4.1.0"
|
|
65
65
|
},
|
|
66
66
|
"peerDependencies": {
|
|
67
|
-
"@azure-tools/typespec-azure-core": "^0.66.
|
|
68
|
-
"@azure-tools/typespec-client-generator-core": "^0.66.
|
|
67
|
+
"@azure-tools/typespec-azure-core": "^0.66.1",
|
|
68
|
+
"@azure-tools/typespec-client-generator-core": "^0.66.4",
|
|
69
69
|
"@typespec/compiler": "^1.10.0",
|
|
70
70
|
"@typespec/http": "^1.10.0",
|
|
71
71
|
"@typespec/rest": "^0.80.0",
|
|
@@ -73,7 +73,7 @@
|
|
|
73
73
|
"@typespec/xml": "^0.80.0"
|
|
74
74
|
},
|
|
75
75
|
"dependencies": {
|
|
76
|
-
"@azure-tools/rlc-common": "^0.51.
|
|
76
|
+
"@azure-tools/rlc-common": "^0.51.1",
|
|
77
77
|
"fast-xml-parser": "^4.5.0",
|
|
78
78
|
"fs-extra": "^11.1.0",
|
|
79
79
|
"lodash": "^4.17.21",
|
package/src/index.ts
CHANGED
|
@@ -124,6 +124,12 @@ export async function $onEmit(context: EmitContext) {
|
|
|
124
124
|
const program: Program = context.program;
|
|
125
125
|
const emitterOptions: EmitterOptions = context.options;
|
|
126
126
|
const dpgContext = await createContextWithDefaultOptions(context);
|
|
127
|
+
|
|
128
|
+
// Report any diagnostics from TCGC
|
|
129
|
+
if (dpgContext.diagnostics?.length > 0) {
|
|
130
|
+
program.reportDiagnostics(dpgContext.diagnostics);
|
|
131
|
+
}
|
|
132
|
+
|
|
127
133
|
// Enrich the dpg context with path detail and common options
|
|
128
134
|
await enrichDpgContext();
|
|
129
135
|
const rlcOptions = dpgContext.rlcOptions ?? {};
|
package/src/lib.ts
CHANGED
|
@@ -92,6 +92,13 @@ export interface EmitterOptions {
|
|
|
92
92
|
* Defaults to `false`.
|
|
93
93
|
*/
|
|
94
94
|
"enable-storage-compat"?: boolean;
|
|
95
|
+
/**
|
|
96
|
+
* When set to true, TypeSpec `unknown` type will be translated to `Record<string, unknown>`
|
|
97
|
+
* instead of `any` in the generated Modular SDK. This is useful when migrating from HLC
|
|
98
|
+
* where `unknown` in swagger mapped to `Record<string, unknown>` in the SDK.
|
|
99
|
+
* (Modular SDK only) Defaults to `false`.
|
|
100
|
+
*/
|
|
101
|
+
"treat-unknown-as-record"?: boolean;
|
|
95
102
|
}
|
|
96
103
|
|
|
97
104
|
export const RLCOptionsSchema: JSONSchemaType<EmitterOptions> = {
|
|
@@ -388,6 +395,12 @@ export const RLCOptionsSchema: JSONSchemaType<EmitterOptions> = {
|
|
|
388
395
|
nullable: true,
|
|
389
396
|
description:
|
|
390
397
|
"When enabled, every regular (non-LRO, non-paging) operation return type is augmented with a `_response` property containing `rawResponse` (PathUncheckedResponse), `parsedBody`, and `parsedHeaders`. Defaults to `false`."
|
|
398
|
+
},
|
|
399
|
+
"treat-unknown-as-record": {
|
|
400
|
+
type: "boolean",
|
|
401
|
+
nullable: true,
|
|
402
|
+
description:
|
|
403
|
+
"When set to true, TypeSpec `unknown` type will be translated to `Record<string, unknown>` instead of `any` in the generated Modular SDK. This is useful when migrating from HLC where `unknown` in swagger mapped to `Record<string, unknown>`. (Modular SDK only) Defaults to `false`."
|
|
391
404
|
}
|
|
392
405
|
},
|
|
393
406
|
required: []
|
|
@@ -573,7 +586,7 @@ const libDef = {
|
|
|
573
586
|
"prefix-adding-in-enum-member": {
|
|
574
587
|
severity: "warning",
|
|
575
588
|
messages: {
|
|
576
|
-
default: paramMessage`Enum member name ${"memberName"} is
|
|
589
|
+
default: paramMessage`Enum member name ${"memberName"} is not a valid TypeScript identifier. It has been renamed to ${"normalizedName"} using the enum type name ${"enumTypeName"} as prefix.`
|
|
577
590
|
}
|
|
578
591
|
},
|
|
579
592
|
"default-value-object": {
|
|
@@ -597,13 +597,14 @@ export function buildEnumTypes(
|
|
|
597
597
|
type: SdkEnumType,
|
|
598
598
|
reportMemberNameDiagnostic = false // if reportMemberNameDiagnostic is true, it will report diagnostic for enum member name
|
|
599
599
|
): [TypeAliasDeclarationStructure, EnumDeclarationStructure] {
|
|
600
|
+
const rawMembers = type.values.map((value) =>
|
|
601
|
+
emitEnumMember(context, value, reportMemberNameDiagnostic)
|
|
602
|
+
);
|
|
600
603
|
const enumDeclaration: EnumDeclarationStructure = {
|
|
601
604
|
kind: StructureKind.Enum,
|
|
602
605
|
name: `Known${normalizeModelName(context, type)}`,
|
|
603
606
|
isExported: true,
|
|
604
|
-
members:
|
|
605
|
-
emitEnumMember(context, value, reportMemberNameDiagnostic)
|
|
606
|
-
)
|
|
607
|
+
members: deduplicateEnumMemberNames(rawMembers)
|
|
607
608
|
};
|
|
608
609
|
|
|
609
610
|
const enumAsUnion: TypeAliasDeclarationStructure = {
|
|
@@ -650,27 +651,65 @@ function getExtensibleEnumDescription(
|
|
|
650
651
|
].join(" \n");
|
|
651
652
|
}
|
|
652
653
|
|
|
654
|
+
/**
|
|
655
|
+
* Deduplicates enum member names by appending a numeric suffix (_1, _2, ...)
|
|
656
|
+
* to all members that share the same normalized name.
|
|
657
|
+
* This handles cases where different values (e.g. "10" and "1.0") normalize
|
|
658
|
+
* to the same identifier.
|
|
659
|
+
*/
|
|
660
|
+
function deduplicateEnumMemberNames(
|
|
661
|
+
members: EnumMemberStructure[]
|
|
662
|
+
): EnumMemberStructure[] {
|
|
663
|
+
const nameCount = new Map<string, number>();
|
|
664
|
+
for (const member of members) {
|
|
665
|
+
const name = member.name as string;
|
|
666
|
+
nameCount.set(name, (nameCount.get(name) ?? 0) + 1);
|
|
667
|
+
}
|
|
668
|
+
const nameCurrentIndex = new Map<string, number>();
|
|
669
|
+
return members.map((member) => {
|
|
670
|
+
const name = member.name as string;
|
|
671
|
+
if ((nameCount.get(name) ?? 0) > 1) {
|
|
672
|
+
const index = (nameCurrentIndex.get(name) ?? 0) + 1;
|
|
673
|
+
nameCurrentIndex.set(name, index);
|
|
674
|
+
return { ...member, name: `${name}_${index}` };
|
|
675
|
+
}
|
|
676
|
+
return member;
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
|
|
653
680
|
function emitEnumMember(
|
|
654
681
|
context: SdkContext,
|
|
655
682
|
member: SdkEnumValueType,
|
|
656
683
|
reportMemberNameDiagnostic = false // if reportMemberNameDiagnostic is true, it will report diagnostic for enum member name
|
|
657
684
|
): EnumMemberStructure {
|
|
658
|
-
const
|
|
685
|
+
const shouldNormalizeName = !member.name.startsWith("$DO_NOT_NORMALIZE$");
|
|
686
|
+
const enumTypeName = normalizeName(
|
|
687
|
+
member.enumType.name,
|
|
688
|
+
NameType.Interface,
|
|
689
|
+
true
|
|
690
|
+
);
|
|
691
|
+
let normalizedMemberName = context.rlcOptions?.ignoreEnumMemberNameNormalize
|
|
659
692
|
? fixLeadingNumber(member.name, NameType.EnumMemberName) // need to fix the leading number also for enum member
|
|
660
693
|
: normalizeName(member.name, NameType.EnumMemberName, true);
|
|
694
|
+
// If the member name starts with _ due to a leading digit (not because the original has _),
|
|
695
|
+
// replace the _ prefix with the enum type name for a more descriptive identifier
|
|
661
696
|
if (
|
|
662
|
-
|
|
697
|
+
shouldNormalizeName &&
|
|
663
698
|
normalizedMemberName.toLowerCase().startsWith("_") &&
|
|
664
699
|
!member.name.toLowerCase().startsWith("_")
|
|
665
700
|
) {
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
701
|
+
normalizedMemberName = enumTypeName + normalizedMemberName.slice(1);
|
|
702
|
+
if (reportMemberNameDiagnostic) {
|
|
703
|
+
reportDiagnostic(context.program, {
|
|
704
|
+
code: "prefix-adding-in-enum-member",
|
|
705
|
+
format: {
|
|
706
|
+
memberName: member.name,
|
|
707
|
+
normalizedName: normalizedMemberName,
|
|
708
|
+
enumTypeName
|
|
709
|
+
},
|
|
710
|
+
target: NoTarget
|
|
711
|
+
});
|
|
712
|
+
}
|
|
674
713
|
}
|
|
675
714
|
const memberStructure: EnumMemberStructure = {
|
|
676
715
|
kind: StructureKind.EnumMember,
|
|
@@ -191,17 +191,9 @@ export function getDeserializePrivateFunction(
|
|
|
191
191
|
|
|
192
192
|
// Check if we need to wrap the non-model return type
|
|
193
193
|
const { shouldWrap, isBinary } = checkWrapNonModelReturn(context, operation);
|
|
194
|
-
// For binary wrap, the deserializer receives
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
? resolveReference(dependencies.StreamableMethod)
|
|
198
|
-
: PathUncheckedResponseReference;
|
|
199
|
-
const parameters: OptionalKind<ParameterDeclarationStructure>[] = [
|
|
200
|
-
{
|
|
201
|
-
name: "result",
|
|
202
|
-
type: resultParamType
|
|
203
|
-
}
|
|
204
|
-
];
|
|
194
|
+
// For binary wrap, the deserializer receives PathUncheckedResponse & { blobBody, readableStreamBody }
|
|
195
|
+
// which is returned by getBinaryStreamResponse.
|
|
196
|
+
const isBinaryWrap = shouldWrap && isBinary;
|
|
205
197
|
const isLroOnly = isLroOnlyOperation(operation);
|
|
206
198
|
const isLroAndPaging = isLroAndPagingOperation(operation);
|
|
207
199
|
const isPagingOnly = isPagingOnlyOperation(operation);
|
|
@@ -236,6 +228,16 @@ export function getDeserializePrivateFunction(
|
|
|
236
228
|
returnType = { name: "", type: "void" };
|
|
237
229
|
}
|
|
238
230
|
|
|
231
|
+
const resultParamName = "result";
|
|
232
|
+
const resultParamType = isBinaryWrap
|
|
233
|
+
? `${PathUncheckedResponseReference} & ${returnType.type}`
|
|
234
|
+
: PathUncheckedResponseReference;
|
|
235
|
+
const parameters: OptionalKind<ParameterDeclarationStructure>[] = [
|
|
236
|
+
{
|
|
237
|
+
name: resultParamName,
|
|
238
|
+
type: resultParamType
|
|
239
|
+
}
|
|
240
|
+
];
|
|
239
241
|
const functionStatement: OptionalKind<FunctionDeclarationStructure> = {
|
|
240
242
|
isAsync: true,
|
|
241
243
|
isExported: true,
|
|
@@ -247,17 +249,14 @@ export function getDeserializePrivateFunction(
|
|
|
247
249
|
const createRestErrorReference = resolveReference(
|
|
248
250
|
dependencies.createRestError
|
|
249
251
|
);
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
)
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
"}"
|
|
259
|
-
);
|
|
260
|
-
}
|
|
252
|
+
statements.push(
|
|
253
|
+
`const expectedStatuses = ${getExpectedStatuses(operation)};`
|
|
254
|
+
);
|
|
255
|
+
statements.push(
|
|
256
|
+
`if(!expectedStatuses.includes(result.status)){`,
|
|
257
|
+
`${getExceptionThrowStatement(context, operation)}`,
|
|
258
|
+
"}"
|
|
259
|
+
);
|
|
261
260
|
const deserializedType =
|
|
262
261
|
isLroOnly || isLroAndPaging
|
|
263
262
|
? operation?.lroMetadata?.finalResponse?.result
|
|
@@ -396,19 +395,11 @@ export function getDeserializePrivateFunction(
|
|
|
396
395
|
// Handle wrap-non-model-return for non-LRO, non-paging operations
|
|
397
396
|
if (shouldWrap) {
|
|
398
397
|
if (isBinary) {
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
const deserializeError = getExceptionDetails(
|
|
403
|
-
context,
|
|
404
|
-
operation
|
|
405
|
-
).defaultDeserializer;
|
|
406
|
-
const deserializeErrorStr = deserializeError
|
|
407
|
-
? `, ${deserializeError}`
|
|
408
|
-
: "";
|
|
398
|
+
// Binary wrap: getBinaryStream already resolved the stream,
|
|
399
|
+
// status check and error.details handling ran above.
|
|
400
|
+
// Return the platform-specific stream properties.
|
|
409
401
|
statements.push(
|
|
410
|
-
`return
|
|
411
|
-
`
|
|
402
|
+
`return { blobBody: result.blobBody, readableStreamBody: result.readableStreamBody };`
|
|
412
403
|
);
|
|
413
404
|
} else {
|
|
414
405
|
// Non-model response: wrap with body property
|
|
@@ -913,7 +904,8 @@ function getOperationSignatureParameters(
|
|
|
913
904
|
!(
|
|
914
905
|
p.isGeneratedName &&
|
|
915
906
|
(p.name === "contentType" || p.name === "accept")
|
|
916
|
-
) // skip tcgc generated contentType and accept header parameter
|
|
907
|
+
) && // skip tcgc generated contentType and accept header parameter
|
|
908
|
+
getClientOptions(p, "headerCollectionPrefix") === undefined // skip headers with collection prefix
|
|
917
909
|
)
|
|
918
910
|
.map((p) => {
|
|
919
911
|
return {
|
|
@@ -1011,6 +1003,7 @@ export function getOperationFunction(
|
|
|
1011
1003
|
name: getOperationResponseTypeName(method),
|
|
1012
1004
|
type: resolveReference(refkey(operation, "response"))
|
|
1013
1005
|
};
|
|
1006
|
+
bodyType = returnType.type;
|
|
1014
1007
|
} else if (response.type) {
|
|
1015
1008
|
const type = response.type;
|
|
1016
1009
|
|
|
@@ -1091,23 +1084,6 @@ export function getOperationFunction(
|
|
|
1091
1084
|
const resultVarName = generateLocallyUniqueName("result", paramNames);
|
|
1092
1085
|
|
|
1093
1086
|
const parameterList = parameters.map((p) => p.name).join(", ");
|
|
1094
|
-
// For binary wrap, pass the StreamableMethod directly to the deserializer.
|
|
1095
|
-
// The deserializer uses asBrowserStream()/asNodeStream() to build the wrapper.
|
|
1096
|
-
if (wrapReturn && wrapReturnIsBinary) {
|
|
1097
|
-
const streamableMethodVarName = generateLocallyUniqueName(
|
|
1098
|
-
"streamableMethod",
|
|
1099
|
-
paramNames
|
|
1100
|
-
);
|
|
1101
|
-
statements.push(
|
|
1102
|
-
`const ${streamableMethodVarName} = _${name}Send(${parameterList});`
|
|
1103
|
-
);
|
|
1104
|
-
statements.push(`return _${name}Deserialize(${streamableMethodVarName});`);
|
|
1105
|
-
return {
|
|
1106
|
-
...functionStatement,
|
|
1107
|
-
statements
|
|
1108
|
-
} as FunctionDeclarationStructure & { propertyName?: string };
|
|
1109
|
-
}
|
|
1110
|
-
|
|
1111
1087
|
// When storage-compat is enabled, set up the onResponse interceptor before sending
|
|
1112
1088
|
const storageCompatVarName = generateLocallyUniqueName(
|
|
1113
1089
|
"_storageCompat",
|
|
@@ -1132,6 +1108,8 @@ export function getOperationFunction(
|
|
|
1132
1108
|
|
|
1133
1109
|
// Special case for binary-only bodies: use helper to call streaming methods so that Core doesn't poison the response body by
|
|
1134
1110
|
// doing a UTF-8 decode on the raw bytes.
|
|
1111
|
+
// For binary wrap, use getBinaryStreamResponse which preserves blobBody/readableStreamBody properties.
|
|
1112
|
+
// For non-wrapped binary, use getBinaryResponse which buffers the body into Uint8Array.
|
|
1135
1113
|
if (response?.type?.kind === "bytes" && response.type.encode === "bytes") {
|
|
1136
1114
|
const streamableMethodVarName = generateLocallyUniqueName(
|
|
1137
1115
|
"streamableMethod",
|
|
@@ -1140,8 +1118,12 @@ export function getOperationFunction(
|
|
|
1140
1118
|
statements.push(
|
|
1141
1119
|
`const ${streamableMethodVarName} = _${name}Send(${sendParameterList});`
|
|
1142
1120
|
);
|
|
1121
|
+
const binaryHelper =
|
|
1122
|
+
wrapReturn && wrapReturnIsBinary
|
|
1123
|
+
? SerializationHelpers.getBinaryStreamResponse
|
|
1124
|
+
: SerializationHelpers.getBinaryResponse;
|
|
1143
1125
|
statements.push(
|
|
1144
|
-
`const ${resultVarName} = await ${resolveReference(
|
|
1126
|
+
`const ${resultVarName} = await ${resolveReference(binaryHelper)}(${streamableMethodVarName});`
|
|
1145
1127
|
);
|
|
1146
1128
|
} else {
|
|
1147
1129
|
statements.push(
|
|
@@ -1773,7 +1755,7 @@ export function getParameterMap(
|
|
|
1773
1755
|
? getHeaderSerializedName(param)
|
|
1774
1756
|
: getPropertySerializedName(param);
|
|
1775
1757
|
|
|
1776
|
-
if (isConstant(param.type)) {
|
|
1758
|
+
if (isConstant(param.type) && !isOptional(param)) {
|
|
1777
1759
|
return `"${serializedName}": ${getConstantValue(param.type)}`;
|
|
1778
1760
|
}
|
|
1779
1761
|
|
|
@@ -3081,17 +3063,48 @@ export function checkWrapNonModelReturn(
|
|
|
3081
3063
|
const type = response.type;
|
|
3082
3064
|
const contentTypes = operation.operation.responses[0]?.contentTypes ?? [];
|
|
3083
3065
|
|
|
3084
|
-
//
|
|
3066
|
+
// Determine whether to wrap based on the HLC PropertyKind mapping.
|
|
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)
|
|
3072
|
+
// HLC: bytes → binary payload → separate binary handling
|
|
3085
3073
|
if (type.__raw && isBinaryPayload(context, type.__raw, contentTypes)) {
|
|
3086
3074
|
return { shouldWrap: true, isBinary: true };
|
|
3087
3075
|
}
|
|
3088
3076
|
|
|
3089
|
-
//
|
|
3090
|
-
|
|
3091
|
-
|
|
3077
|
+
// Case: model array (e.g. Foo[]) → no wrap
|
|
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;
|
|
3092
3099
|
}
|
|
3093
3100
|
|
|
3094
|
-
|
|
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 };
|
|
3095
3108
|
}
|
|
3096
3109
|
|
|
3097
3110
|
/**
|
|
@@ -59,10 +59,10 @@ export const SerializationHelpers = {
|
|
|
59
59
|
name: "getBinaryResponse",
|
|
60
60
|
location: "serialization/get-binary-response.ts"
|
|
61
61
|
},
|
|
62
|
-
|
|
62
|
+
getBinaryStreamResponse: {
|
|
63
63
|
kind: "function",
|
|
64
|
-
name: "
|
|
65
|
-
location: "serialization/get-binary-response
|
|
64
|
+
name: "getBinaryStreamResponse",
|
|
65
|
+
location: "serialization/get-binary-stream-response.ts"
|
|
66
66
|
},
|
|
67
67
|
areAllPropsUndefined: {
|
|
68
68
|
kind: "function",
|
|
@@ -40,7 +40,9 @@ export function getTypeExpression(
|
|
|
40
40
|
case "enum":
|
|
41
41
|
return getEnumExpression(context, type);
|
|
42
42
|
case "unknown":
|
|
43
|
-
return
|
|
43
|
+
return context.rlcOptions?.treatUnknownAsRecord
|
|
44
|
+
? "Record<string, unknown>"
|
|
45
|
+
: "any";
|
|
44
46
|
case "boolean":
|
|
45
47
|
return "boolean";
|
|
46
48
|
case "decimal":
|
|
@@ -93,6 +93,8 @@ function extractRLCOptions(
|
|
|
93
93
|
const compatibilityQueryMultiFormat =
|
|
94
94
|
emitterOptions["compatibility-query-multi-format"];
|
|
95
95
|
const enableStorageCompat = emitterOptions["enable-storage-compat"] === true;
|
|
96
|
+
const treatUnknownAsRecord =
|
|
97
|
+
emitterOptions["treat-unknown-as-record"] === true;
|
|
96
98
|
const typespecTitleMap = emitterOptions["typespec-title-map"];
|
|
97
99
|
const hasSubscriptionId = getSubscriptionId(dpgContext);
|
|
98
100
|
const ignoreNullableOnOptional = getIgnoreNullableOnOptional(
|
|
@@ -138,7 +140,8 @@ function extractRLCOptions(
|
|
|
138
140
|
ignoreNullableOnOptional,
|
|
139
141
|
wrapNonModelReturn,
|
|
140
142
|
isMultiService,
|
|
141
|
-
enableStorageCompat
|
|
143
|
+
enableStorageCompat,
|
|
144
|
+
treatUnknownAsRecord
|
|
142
145
|
};
|
|
143
146
|
}
|
|
144
147
|
|
package/src/utils/clientUtils.ts
CHANGED
|
@@ -58,6 +58,8 @@ export function getRLCClients(
|
|
|
58
58
|
service: service,
|
|
59
59
|
type: service,
|
|
60
60
|
services: [service],
|
|
61
|
+
subClients: [],
|
|
62
|
+
clientPath: clientName,
|
|
61
63
|
arm: Boolean(dpgContext.arm),
|
|
62
64
|
crossLanguageDefinitionId: `${getNamespaceFullName(
|
|
63
65
|
service
|
|
@@ -77,6 +79,8 @@ export function getRLCClients(
|
|
|
77
79
|
service: service,
|
|
78
80
|
type: service,
|
|
79
81
|
services: [service],
|
|
82
|
+
subClients: [],
|
|
83
|
+
clientPath: clientName,
|
|
80
84
|
arm: Boolean(dpgContext.arm),
|
|
81
85
|
crossLanguageDefinitionId: `${getNamespaceFullName(
|
|
82
86
|
service
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { HttpResponse, StreamableMethod } from "@azure-rest/core-client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Resolves a StreamableMethod into a binary stream response using browser streaming.
|
|
5
|
+
* Returns both the raw HttpResponse (for status/header inspection) and a blobBody Promise.
|
|
6
|
+
* Error handling is left to the caller so that generated deserializers can apply
|
|
7
|
+
* operation-specific error deserialization (per-status-code details, exception headers, etc.).
|
|
8
|
+
*/
|
|
9
|
+
export async function getBinaryStreamResponse(
|
|
10
|
+
streamableMethod: StreamableMethod
|
|
11
|
+
): Promise<
|
|
12
|
+
HttpResponse & {
|
|
13
|
+
blobBody?: Promise<Blob>;
|
|
14
|
+
readableStreamBody?: NodeJS.ReadableStream;
|
|
15
|
+
}
|
|
16
|
+
> {
|
|
17
|
+
const response = await streamableMethod.asBrowserStream();
|
|
18
|
+
return {
|
|
19
|
+
...response,
|
|
20
|
+
blobBody: new Response(response.body).blob(),
|
|
21
|
+
readableStreamBody: undefined,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { HttpResponse, StreamableMethod } from "@azure-rest/core-client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Resolves a StreamableMethod into a binary stream response using Node.js streaming.
|
|
5
|
+
* Returns both the raw HttpResponse (for status/header inspection) and the readable stream body.
|
|
6
|
+
* Error handling is left to the caller so that generated deserializers can apply
|
|
7
|
+
* operation-specific error deserialization (per-status-code details, exception headers, etc.).
|
|
8
|
+
*/
|
|
9
|
+
export async function getBinaryStreamResponse(
|
|
10
|
+
streamableMethod: StreamableMethod
|
|
11
|
+
): Promise<
|
|
12
|
+
HttpResponse & {
|
|
13
|
+
blobBody?: Promise<Blob>;
|
|
14
|
+
readableStreamBody?: NodeJS.ReadableStream;
|
|
15
|
+
}
|
|
16
|
+
> {
|
|
17
|
+
const response = await streamableMethod.asNodeStream();
|
|
18
|
+
return {
|
|
19
|
+
...response,
|
|
20
|
+
blobBody: undefined,
|
|
21
|
+
readableStreamBody: response.body
|
|
22
|
+
};
|
|
23
|
+
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { createRestError, StreamableMethod } from "@azure-rest/core-client";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Get the binary response body from a StreamableMethod, returning it as a Blob in browser environments and as a NodeJS.ReadableStream in Node.js environments. This function checks the response status against expected statuses and throws a RestError if the status is unexpected, optionally deserializing error details using a provided deserialization function.
|
|
5
|
-
*/
|
|
6
|
-
export async function getBinaryResponseBody(
|
|
7
|
-
result: StreamableMethod,
|
|
8
|
-
expectedStatuses: string[] = ["200"],
|
|
9
|
-
deserializeError?: (error: any) => unknown
|
|
10
|
-
): Promise<{ blobBody?: Promise<Blob>; readableStreamBody?: NodeJS.ReadableStream }> {
|
|
11
|
-
const browserStream = await result.asBrowserStream();
|
|
12
|
-
if (!expectedStatuses.includes(browserStream.status)) {
|
|
13
|
-
const error = createRestError(browserStream);
|
|
14
|
-
if (deserializeError) {
|
|
15
|
-
error.details = deserializeError(browserStream.body);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
throw error;
|
|
19
|
-
}
|
|
20
|
-
return { blobBody: new Response(browserStream.body).blob(), readableStreamBody: undefined };
|
|
21
|
-
|
|
22
|
-
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { createRestError, StreamableMethod } from "@azure-rest/core-client";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Gets the binary response body from a StreamableMethod, returning it as a NodeJS.ReadableStream in Node.js environments. In browser environments, this function returns `undefined` for the stream and instead provides a `blobBody` that is a Promise resolving to a Blob, since browser environments do not support NodeJS.ReadableStream.
|
|
5
|
-
*/
|
|
6
|
-
export async function getBinaryResponseBody(
|
|
7
|
-
result: StreamableMethod,
|
|
8
|
-
expectedStatuses: string[] = ["200"],
|
|
9
|
-
deserializeError?: (error: any) => unknown
|
|
10
|
-
): Promise<{
|
|
11
|
-
blobBody?: Promise<Blob>;
|
|
12
|
-
readableStreamBody?: NodeJS.ReadableStream;
|
|
13
|
-
}> {
|
|
14
|
-
const nodeStream = await result.asNodeStream();
|
|
15
|
-
if (!expectedStatuses.includes(nodeStream.status)) {
|
|
16
|
-
const error = createRestError(nodeStream);
|
|
17
|
-
if (deserializeError) {
|
|
18
|
-
error.details = deserializeError(nodeStream.body);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
throw error;
|
|
22
|
-
}
|
|
23
|
-
return { blobBody: undefined, readableStreamBody: nodeStream.body };
|
|
24
|
-
}
|