@azure-tools/typespec-ts 0.56.1-dev.1 → 0.57.0-dev.10
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 +6 -0
- package/dist/src/framework/hooks/sdk-types.d.ts.map +1 -1
- package/dist/src/framework/hooks/sdk-types.js +2 -0
- package/dist/src/framework/hooks/sdk-types.js.map +1 -1
- package/dist/src/interfaces.d.ts +4 -0
- package/dist/src/interfaces.d.ts.map +1 -1
- package/dist/src/interfaces.js.map +1 -1
- package/dist/src/lib.d.ts +11 -1
- package/dist/src/lib.d.ts.map +1 -1
- package/dist/src/lib.js +11 -0
- package/dist/src/lib.js.map +1 -1
- package/dist/src/metadata/build-package-file.d.ts +1 -0
- package/dist/src/metadata/build-package-file.d.ts.map +1 -1
- package/dist/src/metadata/build-package-file.js +3 -0
- package/dist/src/metadata/build-package-file.js.map +1 -1
- package/dist/src/metadata/package-json/build-azure-monorepo-package.d.ts +2 -0
- package/dist/src/metadata/package-json/build-azure-monorepo-package.d.ts.map +1 -1
- package/dist/src/metadata/package-json/build-azure-monorepo-package.js +1 -0
- package/dist/src/metadata/package-json/build-azure-monorepo-package.js.map +1 -1
- package/dist/src/modular/build-classical-client.d.ts.map +1 -1
- package/dist/src/modular/build-classical-client.js +19 -33
- package/dist/src/modular/build-classical-client.js.map +1 -1
- package/dist/src/modular/build-operations.d.ts.map +1 -1
- package/dist/src/modular/build-operations.js +4 -5
- package/dist/src/modular/build-operations.js.map +1 -1
- package/dist/src/modular/build-root-index.js +1 -1
- package/dist/src/modular/build-root-index.js.map +1 -1
- package/dist/src/modular/emit-models.d.ts.map +1 -1
- package/dist/src/modular/emit-models.js +46 -23
- package/dist/src/modular/emit-models.js.map +1 -1
- package/dist/src/modular/emit-samples.d.ts.map +1 -1
- package/dist/src/modular/emit-samples.js +29 -21
- package/dist/src/modular/emit-samples.js.map +1 -1
- package/dist/src/modular/emit-tests.js +2 -2
- package/dist/src/modular/emit-tests.js.map +1 -1
- package/dist/src/modular/helpers/classical-operation-helpers.d.ts.map +1 -1
- package/dist/src/modular/helpers/classical-operation-helpers.js +17 -15
- package/dist/src/modular/helpers/classical-operation-helpers.js.map +1 -1
- package/dist/src/modular/helpers/client-helpers.d.ts.map +1 -1
- package/dist/src/modular/helpers/client-helpers.js +7 -6
- package/dist/src/modular/helpers/client-helpers.js.map +1 -1
- package/dist/src/modular/helpers/example-value-helpers.d.ts +2 -1
- package/dist/src/modular/helpers/example-value-helpers.d.ts.map +1 -1
- package/dist/src/modular/helpers/example-value-helpers.js +34 -24
- package/dist/src/modular/helpers/example-value-helpers.js.map +1 -1
- package/dist/src/modular/helpers/naming-helpers.d.ts.map +1 -1
- package/dist/src/modular/helpers/naming-helpers.js +3 -3
- package/dist/src/modular/helpers/naming-helpers.js.map +1 -1
- package/dist/src/modular/helpers/operation-helpers.d.ts.map +1 -1
- package/dist/src/modular/helpers/operation-helpers.js +23 -18
- package/dist/src/modular/helpers/operation-helpers.js.map +1 -1
- package/dist/src/modular/helpers/type-helpers.d.ts.map +1 -1
- package/dist/src/modular/helpers/type-helpers.js +2 -2
- package/dist/src/modular/helpers/type-helpers.js.map +1 -1
- package/dist/src/modular/helpers/visibility-helpers.d.ts +7 -0
- package/dist/src/modular/helpers/visibility-helpers.d.ts.map +1 -0
- package/dist/src/modular/helpers/visibility-helpers.js +353 -0
- package/dist/src/modular/helpers/visibility-helpers.js.map +1 -0
- package/dist/src/modular/serialization/build-deserializer-function.js +6 -4
- package/dist/src/modular/serialization/build-deserializer-function.js.map +1 -1
- package/dist/src/modular/serialization/build-serializer-function.d.ts.map +1 -1
- package/dist/src/modular/serialization/build-serializer-function.js +9 -7
- package/dist/src/modular/serialization/build-serializer-function.js.map +1 -1
- package/dist/src/modular/serialization/build-xml-serializer-function.d.ts.map +1 -1
- package/dist/src/modular/serialization/build-xml-serializer-function.js +11 -13
- package/dist/src/modular/serialization/build-xml-serializer-function.js.map +1 -1
- package/dist/src/modular/type-expressions/get-type-expression.d.ts +1 -0
- package/dist/src/modular/type-expressions/get-type-expression.d.ts.map +1 -1
- package/dist/src/modular/type-expressions/get-type-expression.js +6 -4
- package/dist/src/modular/type-expressions/get-type-expression.js.map +1 -1
- package/dist/src/transform/transform-client-options.d.ts.map +1 -1
- package/dist/src/transform/transform-client-options.js +2 -0
- package/dist/src/transform/transform-client-options.js.map +1 -1
- package/dist/src/utils/client-utils.d.ts.map +1 -1
- package/dist/src/utils/client-utils.js +16 -5
- package/dist/src/utils/client-utils.js.map +1 -1
- package/dist/src/utils/cross-language-def.d.ts.map +1 -1
- package/dist/src/utils/cross-language-def.js +1 -2
- package/dist/src/utils/cross-language-def.js.map +1 -1
- package/dist/src/utils/interfaces.d.ts +1 -0
- package/dist/src/utils/interfaces.d.ts.map +1 -1
- package/dist/src/utils/name-utils.d.ts +15 -0
- package/dist/src/utils/name-utils.d.ts.map +1 -1
- package/dist/src/utils/name-utils.js +60 -0
- package/dist/src/utils/name-utils.js.map +1 -1
- package/dist/src/utils/operation-util.d.ts.map +1 -1
- package/dist/src/utils/operation-util.js +15 -5
- package/dist/src/utils/operation-util.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +35 -35
- package/src/framework/hooks/sdk-types.ts +2 -0
- package/src/interfaces.ts +4 -0
- package/src/lib.ts +13 -0
- package/src/metadata/build-package-file.ts +4 -0
- package/src/metadata/package-json/build-azure-monorepo-package.ts +1 -0
- package/src/modular/build-classical-client.ts +21 -32
- package/src/modular/build-operations.ts +4 -5
- package/src/modular/build-root-index.ts +1 -1
- package/src/modular/emit-models.ts +59 -24
- package/src/modular/emit-samples.ts +42 -34
- package/src/modular/emit-tests.ts +2 -2
- package/src/modular/helpers/classical-operation-helpers.ts +21 -20
- package/src/modular/helpers/client-helpers.ts +6 -5
- package/src/modular/helpers/example-value-helpers.ts +52 -41
- package/src/modular/helpers/naming-helpers.ts +8 -3
- package/src/modular/helpers/operation-helpers.ts +31 -18
- package/src/modular/helpers/type-helpers.ts +2 -5
- package/src/modular/helpers/visibility-helpers.ts +471 -0
- package/src/modular/serialization/build-deserializer-function.ts +6 -9
- package/src/modular/serialization/build-serializer-function.ts +14 -11
- package/src/modular/serialization/build-xml-serializer-function.ts +11 -17
- package/src/modular/type-expressions/get-type-expression.ts +10 -4
- package/src/transform/transform-client-options.ts +3 -0
- package/src/utils/client-utils.ts +28 -4
- package/src/utils/cross-language-def.ts +1 -2
- package/src/utils/interfaces.ts +1 -0
- package/src/utils/name-utils.ts +94 -0
- package/src/utils/operation-util.ts +27 -14
- package/static/static-helpers/storageCompatResponse.ts +3 -2
- package/static/static-helpers/urlTemplate.ts +4 -2
|
@@ -35,7 +35,13 @@ import { refkey } from "../../framework/refkey.js";
|
|
|
35
35
|
import { reportDiagnostic } from "../../lib.js";
|
|
36
36
|
import type { SdkContext } from "../../utils/interfaces.js";
|
|
37
37
|
import { isAzureCoreErrorType } from "../../utils/model-utils.js";
|
|
38
|
-
import {
|
|
38
|
+
import {
|
|
39
|
+
formatOptionalPropertyAccess,
|
|
40
|
+
formatPropertyAccess,
|
|
41
|
+
NameType,
|
|
42
|
+
normalizeSdkName,
|
|
43
|
+
normalizeSdkPropertyName,
|
|
44
|
+
} from "../../utils/name-utils.js";
|
|
39
45
|
import {
|
|
40
46
|
getCollectionFormatFromArrayEncoding,
|
|
41
47
|
getCollectionFormatHelper,
|
|
@@ -1400,7 +1406,7 @@ export function getOperationOptionsName(
|
|
|
1400
1406
|
includeGroupName && operation.name.indexOf("_") === -1
|
|
1401
1407
|
? getClassicalLayerPrefix(prefixes, NameType.Interface)
|
|
1402
1408
|
: "";
|
|
1403
|
-
const optionName = `${prefix}${
|
|
1409
|
+
const optionName = `${prefix}${normalizeSdkName(operation, NameType.Interface)}OptionalParams`;
|
|
1404
1410
|
return optionName;
|
|
1405
1411
|
}
|
|
1406
1412
|
|
|
@@ -1690,17 +1696,19 @@ function isContentType(param: SdkHttpParameter): boolean {
|
|
|
1690
1696
|
|
|
1691
1697
|
function getContentTypeValue(param: SdkHttpParameter, optionalParamName: string = "options") {
|
|
1692
1698
|
const defaultValue = param.clientDefaultValue;
|
|
1699
|
+
const optionAccessor = formatPropertyAccess(
|
|
1700
|
+
optionalParamName,
|
|
1701
|
+
normalizeSdkName(param, NameType.Parameter),
|
|
1702
|
+
);
|
|
1693
1703
|
// allow customers to customize the content type if it's guessed by tcgc.
|
|
1694
1704
|
if (isConstant(param.type)) {
|
|
1695
1705
|
return `contentType: ${getConstantValue(param.type)}`;
|
|
1696
1706
|
}
|
|
1697
1707
|
if (defaultValue) {
|
|
1698
|
-
return `contentType: ${
|
|
1708
|
+
return `contentType: ${optionAccessor} as any ?? "${defaultValue}"`;
|
|
1699
1709
|
} else {
|
|
1700
1710
|
return `contentType: ${
|
|
1701
|
-
!param.optional
|
|
1702
|
-
? normalizeName(param.name, NameType.Property)
|
|
1703
|
-
: `${optionalParamName}.` + param.name + " as any"
|
|
1711
|
+
!param.optional ? normalizeSdkName(param, NameType.Property) : `${optionAccessor} as any`
|
|
1704
1712
|
}`;
|
|
1705
1713
|
}
|
|
1706
1714
|
}
|
|
@@ -1919,10 +1927,11 @@ function getParamAccessor(param: SdkHttpParameter, optionalParamName: string = "
|
|
|
1919
1927
|
if (param.onClient) {
|
|
1920
1928
|
return `${clientPrefix}${getClientParameterName(param)}`;
|
|
1921
1929
|
}
|
|
1930
|
+
const parameterName = normalizeSdkName(param, NameType.Parameter, { shouldGuard: true });
|
|
1922
1931
|
if (getEffectiveOptional(param)) {
|
|
1923
|
-
return
|
|
1932
|
+
return formatOptionalPropertyAccess(optionalParamName, parameterName);
|
|
1924
1933
|
}
|
|
1925
|
-
return
|
|
1934
|
+
return parameterName;
|
|
1926
1935
|
}
|
|
1927
1936
|
|
|
1928
1937
|
/**
|
|
@@ -1948,25 +1957,29 @@ function getMethodParamExpr(
|
|
|
1948
1957
|
return undefined;
|
|
1949
1958
|
}
|
|
1950
1959
|
|
|
1951
|
-
|
|
1960
|
+
let expression = "";
|
|
1952
1961
|
for (let i = 0; i < path.length; i++) {
|
|
1953
1962
|
const segment = path[i]!;
|
|
1954
1963
|
if (i === 0) {
|
|
1955
1964
|
// Normalize names for client-level segments to match the context interface property names
|
|
1956
1965
|
const segmentName = segment.onClient
|
|
1957
1966
|
? getClientParameterName(segment as SdkMethodParameter)
|
|
1958
|
-
: segment.
|
|
1967
|
+
: normalizeSdkName(segment, NameType.Parameter, { shouldGuard: true });
|
|
1959
1968
|
if (segment.optional && !segment.onClient) {
|
|
1960
1969
|
// If the first segment is optional and not on the client, we need to start with the optionalParamName
|
|
1961
|
-
|
|
1970
|
+
expression = formatOptionalPropertyAccess(optionalParamName, segmentName);
|
|
1971
|
+
} else {
|
|
1972
|
+
expression = segmentName;
|
|
1962
1973
|
}
|
|
1963
|
-
parts.push(segmentName);
|
|
1964
1974
|
} else {
|
|
1965
1975
|
const needsOptionalChain = path[i - 1]!.optional;
|
|
1966
|
-
|
|
1976
|
+
const segmentName = normalizeSdkPropertyName(segment);
|
|
1977
|
+
expression = needsOptionalChain
|
|
1978
|
+
? formatOptionalPropertyAccess(expression, segmentName)
|
|
1979
|
+
: formatPropertyAccess(expression, segmentName);
|
|
1967
1980
|
}
|
|
1968
1981
|
}
|
|
1969
|
-
return
|
|
1982
|
+
return expression;
|
|
1970
1983
|
}
|
|
1971
1984
|
|
|
1972
1985
|
function getPathParamExpr(
|
|
@@ -2692,14 +2705,14 @@ export function getPropertyFullName(
|
|
|
2692
2705
|
) {
|
|
2693
2706
|
const normalizedPropertyName =
|
|
2694
2707
|
propertyPath === ""
|
|
2695
|
-
?
|
|
2696
|
-
: normalizeModelPropertyName(context, property)
|
|
2708
|
+
? normalizeSdkName(property, NameType.Parameter, { shouldGuard: true })
|
|
2709
|
+
: normalizeModelPropertyName(context, property);
|
|
2697
2710
|
|
|
2698
2711
|
let fullName = normalizedPropertyName;
|
|
2699
2712
|
if (propertyPath === "" && property.optional) {
|
|
2700
2713
|
fullName = `options?.${normalizedPropertyName}`;
|
|
2701
2714
|
} else if (propertyPath) {
|
|
2702
|
-
fullName = `${propertyPath}[
|
|
2715
|
+
fullName = `${propertyPath}[${normalizedPropertyName}]`;
|
|
2703
2716
|
}
|
|
2704
2717
|
|
|
2705
2718
|
return fullName;
|
|
@@ -2896,7 +2909,7 @@ export function getOperationResponseTypeName(method: [string[], ServiceOperation
|
|
|
2896
2909
|
const prefix = !operation.name.includes("_")
|
|
2897
2910
|
? getClassicalLayerPrefix(prefixes, NameType.Interface)
|
|
2898
2911
|
: "";
|
|
2899
|
-
return `${prefix}${
|
|
2912
|
+
return `${prefix}${normalizeSdkName(operation, NameType.Interface)}Response`;
|
|
2900
2913
|
}
|
|
2901
2914
|
|
|
2902
2915
|
/**
|
|
@@ -7,7 +7,7 @@ import type {
|
|
|
7
7
|
SdkType,
|
|
8
8
|
} from "@azure-tools/typespec-client-generator-core";
|
|
9
9
|
import type { SdkContext } from "../../utils/interfaces.js";
|
|
10
|
-
import {
|
|
10
|
+
import { normalizeSdkPropertyName } from "../../utils/name-utils.js";
|
|
11
11
|
import {
|
|
12
12
|
getPropertyWithOverrides,
|
|
13
13
|
type ModelOverrideOptions,
|
|
@@ -110,10 +110,7 @@ export function buildPropertyNameMapper(
|
|
|
110
110
|
continue;
|
|
111
111
|
}
|
|
112
112
|
const prop = getPropertyWithOverrides(p, overrides);
|
|
113
|
-
mapper.set(
|
|
114
|
-
prop.serializationOptions.json?.name || prop.name,
|
|
115
|
-
normalizeName(prop.name, NameType.Property),
|
|
116
|
-
);
|
|
113
|
+
mapper.set(prop.serializationOptions.json?.name || prop.name, normalizeSdkPropertyName(prop));
|
|
117
114
|
}
|
|
118
115
|
return mapper;
|
|
119
116
|
}
|
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
SdkHttpOperation,
|
|
3
|
+
SdkModelPropertyType,
|
|
4
|
+
SdkModelType,
|
|
5
|
+
SdkServiceMethod,
|
|
6
|
+
SdkType,
|
|
7
|
+
} from "@azure-tools/typespec-client-generator-core";
|
|
8
|
+
import { UsageFlags } from "@azure-tools/typespec-client-generator-core";
|
|
9
|
+
import {
|
|
10
|
+
Visibility,
|
|
11
|
+
getVisibilitySuffix,
|
|
12
|
+
isVisible,
|
|
13
|
+
resolveRequestVisibility,
|
|
14
|
+
} from "@typespec/http";
|
|
15
|
+
|
|
16
|
+
import { getAllOperationsFromClient } from "../../framework/hooks/sdk-types.js";
|
|
17
|
+
import type { SdkContext } from "../../utils/interfaces.js";
|
|
18
|
+
import { getDirectSubtypes } from "./type-helpers.js";
|
|
19
|
+
|
|
20
|
+
const responseUsageFlags =
|
|
21
|
+
UsageFlags.Output |
|
|
22
|
+
UsageFlags.Exception |
|
|
23
|
+
UsageFlags.LroInitial |
|
|
24
|
+
UsageFlags.LroPolling |
|
|
25
|
+
UsageFlags.LroFinalEnvelope;
|
|
26
|
+
|
|
27
|
+
interface SplitState {
|
|
28
|
+
context: SdkContext;
|
|
29
|
+
/**
|
|
30
|
+
* Every reachable `(model, visibility)` pair in the write-body graph, keyed by
|
|
31
|
+
* an emitter-local model identity plus visibility.
|
|
32
|
+
*/
|
|
33
|
+
nodes: Map<string, ModelNode>;
|
|
34
|
+
/**
|
|
35
|
+
* The write clone materialized for each node that `needsClone`, keyed the same
|
|
36
|
+
* way as `nodes`.
|
|
37
|
+
*/
|
|
38
|
+
cloneByKey: Map<string, SdkModelType>;
|
|
39
|
+
/** Stable emitter-local IDs preserving `SdkModelType` object identity. */
|
|
40
|
+
modelIds: WeakMap<SdkModelType, number>;
|
|
41
|
+
nextModelId: number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** A `(model, visibility)` vertex in the write-body reference graph. */
|
|
45
|
+
interface ModelNode {
|
|
46
|
+
model: SdkModelType;
|
|
47
|
+
visibility: Visibility;
|
|
48
|
+
/**
|
|
49
|
+
* Keys of referenced nodes. Discriminated hierarchy links are bidirectional
|
|
50
|
+
* so the whole hierarchy is projected together.
|
|
51
|
+
*/
|
|
52
|
+
refKeys: Set<string>;
|
|
53
|
+
/** True if the model's own properties differ between `visibility` and Read. */
|
|
54
|
+
ownShapeDiffers: boolean;
|
|
55
|
+
/** True if this node can reach any shape-differing node. */
|
|
56
|
+
needsClone: boolean;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Projects request models whose write graph differs from their read graph.
|
|
61
|
+
* This must run before `visitPackageTypes` builds the emit queue.
|
|
62
|
+
*/
|
|
63
|
+
export function applyVisibilityModelSplit(context: SdkContext): void {
|
|
64
|
+
if (!context.emitterOptions?.experimentalSplitModelsByVisibility) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const state = createSplitState(context);
|
|
69
|
+
const methods = context.sdkPackage.clients.flatMap((client) =>
|
|
70
|
+
getAllOperationsFromClient(client),
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
// Phase 1 — collect the reachable `(model, visibility)` reference graph.
|
|
74
|
+
for (const method of methods) {
|
|
75
|
+
collectMethodRoots(state, method);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Phase 2 — mark models whose write graph differs from their read graph.
|
|
79
|
+
markNodesNeedingClones(state);
|
|
80
|
+
|
|
81
|
+
// Phase 3 — materialize projected models and wire their references.
|
|
82
|
+
buildClones(state);
|
|
83
|
+
|
|
84
|
+
// Phase 4 — project the original response models to the canonical Read view.
|
|
85
|
+
projectResponseModelsToRead(state);
|
|
86
|
+
|
|
87
|
+
// Phase 5 — repoint operation parameters and payloads to projected models.
|
|
88
|
+
for (const method of methods) {
|
|
89
|
+
repointMethodBody(state, method);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Register projected models for the subsequent emit queue traversal.
|
|
93
|
+
for (const clone of state.cloneByKey.values()) {
|
|
94
|
+
context.sdkPackage.models.push(clone);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Creates the graph state using Read as the canonical model visibility. */
|
|
99
|
+
function createSplitState(context: SdkContext): SplitState {
|
|
100
|
+
return {
|
|
101
|
+
context,
|
|
102
|
+
nodes: new Map<string, ModelNode>(),
|
|
103
|
+
cloneByKey: new Map<string, SdkModelType>(),
|
|
104
|
+
modelIds: new WeakMap<SdkModelType, number>(),
|
|
105
|
+
nextModelId: 0,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Stable key identifying a `(model, visibility)` node (and its clone). */
|
|
110
|
+
function nodeKeyFor(state: SplitState, model: SdkModelType, visibility: Visibility): string {
|
|
111
|
+
let modelId = state.modelIds.get(model);
|
|
112
|
+
if (modelId === undefined) {
|
|
113
|
+
modelId = state.nextModelId++;
|
|
114
|
+
state.modelIds.set(model, modelId);
|
|
115
|
+
}
|
|
116
|
+
return `${modelId}|${visibility}`;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Collects the write-body roots of a single operation. The root of each
|
|
121
|
+
* `methodParameterSegments` entry is the client-method parameter.
|
|
122
|
+
*/
|
|
123
|
+
function collectMethodRoots(state: SplitState, method: SdkServiceMethod<SdkHttpOperation>): void {
|
|
124
|
+
const operation = method.operation;
|
|
125
|
+
const bodyParam = operation?.bodyParam;
|
|
126
|
+
if (!bodyParam) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const visibility = resolveRequestVisibility(
|
|
131
|
+
state.context.program,
|
|
132
|
+
operation.__raw.operation,
|
|
133
|
+
operation.verb,
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
for (const segment of bodyParam.methodParameterSegments) {
|
|
137
|
+
const methodParam = segment[0];
|
|
138
|
+
if (!methodParam) {
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
collectReferencedModels(state, methodParam.type, visibility, new Set<string>());
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Cycle-safe DFS that records a `(model, visibility)` node and its outgoing
|
|
147
|
+
* references. Nodes are registered before recursion so cyclic graphs terminate.
|
|
148
|
+
*/
|
|
149
|
+
function collectNode(state: SplitState, model: SdkModelType, visibility: Visibility): string {
|
|
150
|
+
const key = nodeKeyFor(state, model, visibility);
|
|
151
|
+
if (state.nodes.has(key)) {
|
|
152
|
+
return key;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const node: ModelNode = {
|
|
156
|
+
model,
|
|
157
|
+
visibility,
|
|
158
|
+
refKeys: new Set<string>(),
|
|
159
|
+
ownShapeDiffers: false,
|
|
160
|
+
needsClone: false,
|
|
161
|
+
};
|
|
162
|
+
// Seed before recursing so cycles terminate at the already-seeded node.
|
|
163
|
+
state.nodes.set(key, node);
|
|
164
|
+
|
|
165
|
+
for (const property of model.properties) {
|
|
166
|
+
if (property.kind === "property" && property.__raw) {
|
|
167
|
+
const visibleInWrite = isVisible(state.context.program, property.__raw, visibility);
|
|
168
|
+
const visibleInRead = isVisible(state.context.program, property.__raw, Visibility.Read);
|
|
169
|
+
if (visibleInWrite !== visibleInRead) {
|
|
170
|
+
node.ownShapeDiffers = true;
|
|
171
|
+
}
|
|
172
|
+
if (!visibleInWrite) {
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (property.kind === "property") {
|
|
178
|
+
collectReferencedModels(state, property.type, visibility, node.refKeys);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (model.additionalProperties) {
|
|
183
|
+
collectReferencedModels(state, model.additionalProperties, visibility, node.refKeys);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (model.baseModel) {
|
|
187
|
+
node.refKeys.add(collectNode(state, model.baseModel, visibility));
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Link base and subtypes bidirectionally so the projected hierarchy stays together.
|
|
191
|
+
if (findDiscriminatedRoot(model)) {
|
|
192
|
+
for (const neighbor of discriminatedNeighbors(model)) {
|
|
193
|
+
const neighborKey = collectNode(state, neighbor, visibility);
|
|
194
|
+
node.refKeys.add(neighborKey);
|
|
195
|
+
state.nodes.get(neighborKey)!.refKeys.add(key);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return key;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/** Collects model references nested in supported container types. */
|
|
203
|
+
function collectReferencedModels(
|
|
204
|
+
state: SplitState,
|
|
205
|
+
type: SdkType,
|
|
206
|
+
visibility: Visibility,
|
|
207
|
+
refKeys: Set<string>,
|
|
208
|
+
): void {
|
|
209
|
+
if (type.kind === "model") {
|
|
210
|
+
refKeys.add(collectNode(state, type, visibility));
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
switch (type.kind) {
|
|
214
|
+
case "array":
|
|
215
|
+
case "dict":
|
|
216
|
+
collectReferencedModels(state, type.valueType, visibility, refKeys);
|
|
217
|
+
break;
|
|
218
|
+
case "nullable":
|
|
219
|
+
collectReferencedModels(state, type.type, visibility, refKeys);
|
|
220
|
+
break;
|
|
221
|
+
case "tuple":
|
|
222
|
+
for (const valueType of type.valueTypes) {
|
|
223
|
+
collectReferencedModels(state, valueType, visibility, refKeys);
|
|
224
|
+
}
|
|
225
|
+
break;
|
|
226
|
+
case "union":
|
|
227
|
+
for (const variantType of type.variantTypes) {
|
|
228
|
+
collectReferencedModels(state, variantType, visibility, refKeys);
|
|
229
|
+
}
|
|
230
|
+
break;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** The base model and direct subtypes that share a node's discriminated tree. */
|
|
235
|
+
function discriminatedNeighbors(model: SdkModelType): SdkModelType[] {
|
|
236
|
+
const neighbors: SdkModelType[] = [];
|
|
237
|
+
if (model.baseModel) {
|
|
238
|
+
neighbors.push(model.baseModel);
|
|
239
|
+
}
|
|
240
|
+
neighbors.push(...getDirectSubtypes(model));
|
|
241
|
+
return neighbors;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Walks up the inheritance chain and returns the top-most ancestor that
|
|
246
|
+
* declares a discriminator.
|
|
247
|
+
*/
|
|
248
|
+
function findDiscriminatedRoot(model: SdkModelType): SdkModelType | undefined {
|
|
249
|
+
let root: SdkModelType | undefined;
|
|
250
|
+
let current: SdkModelType | undefined = model;
|
|
251
|
+
while (current) {
|
|
252
|
+
if (current.discriminatedSubtypes) {
|
|
253
|
+
root = current;
|
|
254
|
+
}
|
|
255
|
+
current = current.baseModel;
|
|
256
|
+
}
|
|
257
|
+
return root;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Marks nodes whose write graph differs from the read graph by
|
|
262
|
+
* reverse-propagating from nodes whose own property membership differs.
|
|
263
|
+
*/
|
|
264
|
+
function markNodesNeedingClones(state: SplitState): void {
|
|
265
|
+
// Reverse edges propagate a nested difference back to every parent.
|
|
266
|
+
const predecessors = new Map<string, string[]>();
|
|
267
|
+
for (const [key, node] of state.nodes) {
|
|
268
|
+
for (const referencedKey of node.refKeys) {
|
|
269
|
+
let referrers = predecessors.get(referencedKey);
|
|
270
|
+
if (!referrers) {
|
|
271
|
+
referrers = [];
|
|
272
|
+
predecessors.set(referencedKey, referrers);
|
|
273
|
+
}
|
|
274
|
+
referrers.push(key);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const queue: string[] = [];
|
|
279
|
+
for (const [key, node] of state.nodes) {
|
|
280
|
+
if (node.ownShapeDiffers) {
|
|
281
|
+
node.needsClone = true;
|
|
282
|
+
queue.push(key);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
while (queue.length > 0) {
|
|
286
|
+
const key = queue.shift()!;
|
|
287
|
+
for (const predecessorKey of predecessors.get(key) ?? []) {
|
|
288
|
+
const predecessor = state.nodes.get(predecessorKey)!;
|
|
289
|
+
if (!predecessor.needsClone) {
|
|
290
|
+
predecessor.needsClone = true;
|
|
291
|
+
queue.push(predecessorKey);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Creates projected model shells, then wires properties and inheritance after
|
|
299
|
+
* every shell exists. This ordering lets forward, cyclic, and mutually recursive
|
|
300
|
+
* references resolve to projected models instead of the original read models.
|
|
301
|
+
*/
|
|
302
|
+
function buildClones(state: SplitState): void {
|
|
303
|
+
// Pass 1: create every shell before wiring references.
|
|
304
|
+
for (const [key, node] of state.nodes) {
|
|
305
|
+
if (!node.needsClone) {
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
const clone: SdkModelType = {
|
|
309
|
+
...node.model,
|
|
310
|
+
name: `${node.model.name}${getVisibilitySuffix(node.visibility, Visibility.Read)}`,
|
|
311
|
+
properties: [],
|
|
312
|
+
// Write projections are request-only: preserve serialization flags, force Input,
|
|
313
|
+
// and remove inherited response roles.
|
|
314
|
+
usage: (node.model.usage | UsageFlags.Input) & ~responseUsageFlags,
|
|
315
|
+
};
|
|
316
|
+
state.cloneByKey.set(key, clone);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Pass 2: remove dropped properties and wire surviving references.
|
|
320
|
+
for (const [key, node] of state.nodes) {
|
|
321
|
+
const clone = state.cloneByKey.get(key);
|
|
322
|
+
if (!clone) {
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
const { model, visibility } = node;
|
|
326
|
+
|
|
327
|
+
const properties: SdkModelPropertyType[] = [];
|
|
328
|
+
for (const property of model.properties) {
|
|
329
|
+
if (
|
|
330
|
+
property.kind === "property" &&
|
|
331
|
+
property.__raw &&
|
|
332
|
+
!isVisible(state.context.program, property.__raw, visibility)
|
|
333
|
+
) {
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
let wiredProperty = property;
|
|
337
|
+
if (property.kind === "property") {
|
|
338
|
+
const projectedType = projectTypeReferences(state, property.type, visibility);
|
|
339
|
+
if (projectedType !== property.type) {
|
|
340
|
+
wiredProperty = { ...property, type: projectedType };
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
properties.push(wiredProperty);
|
|
344
|
+
}
|
|
345
|
+
clone.properties = properties;
|
|
346
|
+
|
|
347
|
+
if (model.additionalProperties) {
|
|
348
|
+
clone.additionalProperties = projectTypeReferences(
|
|
349
|
+
state,
|
|
350
|
+
model.additionalProperties,
|
|
351
|
+
visibility,
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (model.baseModel) {
|
|
356
|
+
clone.baseModel =
|
|
357
|
+
state.cloneByKey.get(nodeKeyFor(state, model.baseModel, visibility)) ?? model.baseModel;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (findDiscriminatedRoot(model)) {
|
|
361
|
+
if (model.discriminatedSubtypes) {
|
|
362
|
+
clone.discriminatedSubtypes = Object.fromEntries(
|
|
363
|
+
Object.entries(model.discriminatedSubtypes).map(([discriminatorValue, subtype]) => [
|
|
364
|
+
discriminatorValue,
|
|
365
|
+
state.cloneByKey.get(nodeKeyFor(state, subtype, visibility)) ?? subtype,
|
|
366
|
+
]),
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/** Projects response-used models to the canonical Read visibility. */
|
|
374
|
+
function projectResponseModelsToRead(state: SplitState): void {
|
|
375
|
+
for (const model of state.context.sdkPackage.models) {
|
|
376
|
+
if ((model.usage & responseUsageFlags) === 0) {
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
379
|
+
model.properties = model.properties.filter(
|
|
380
|
+
(property) =>
|
|
381
|
+
property.kind !== "property" ||
|
|
382
|
+
!property.__raw ||
|
|
383
|
+
isVisible(state.context.program, property.__raw, Visibility.Read),
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/** Replaces model references nested in supported container types. */
|
|
389
|
+
function projectTypeReferences(state: SplitState, type: SdkType, visibility: Visibility): SdkType {
|
|
390
|
+
if (type.kind === "model") {
|
|
391
|
+
return state.cloneByKey.get(nodeKeyFor(state, type, visibility)) ?? type;
|
|
392
|
+
}
|
|
393
|
+
switch (type.kind) {
|
|
394
|
+
case "array":
|
|
395
|
+
case "dict": {
|
|
396
|
+
const projectedValueType = projectTypeReferences(state, type.valueType, visibility);
|
|
397
|
+
return projectedValueType === type.valueType
|
|
398
|
+
? type
|
|
399
|
+
: { ...type, valueType: projectedValueType };
|
|
400
|
+
}
|
|
401
|
+
case "nullable": {
|
|
402
|
+
const projectedType = projectTypeReferences(state, type.type, visibility);
|
|
403
|
+
return projectedType === type.type ? type : { ...type, type: projectedType };
|
|
404
|
+
}
|
|
405
|
+
case "tuple": {
|
|
406
|
+
const projectedValueTypes = type.valueTypes.map((valueType) =>
|
|
407
|
+
projectTypeReferences(state, valueType, visibility),
|
|
408
|
+
);
|
|
409
|
+
return projectedValueTypes.every((valueType, index) => valueType === type.valueTypes[index])
|
|
410
|
+
? type
|
|
411
|
+
: { ...type, valueTypes: projectedValueTypes };
|
|
412
|
+
}
|
|
413
|
+
case "union": {
|
|
414
|
+
const projectedVariantTypes = type.variantTypes.map((variantType) =>
|
|
415
|
+
projectTypeReferences(state, variantType, visibility),
|
|
416
|
+
);
|
|
417
|
+
return projectedVariantTypes.every(
|
|
418
|
+
(variantType, index) => variantType === type.variantTypes[index],
|
|
419
|
+
)
|
|
420
|
+
? type
|
|
421
|
+
: { ...type, variantTypes: projectedVariantTypes };
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
return type;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Repoints client-method parameters and their HTTP payload representation to
|
|
429
|
+
* projected models.
|
|
430
|
+
*/
|
|
431
|
+
function repointMethodBody(state: SplitState, method: SdkServiceMethod<SdkHttpOperation>): void {
|
|
432
|
+
const operation = method.operation;
|
|
433
|
+
const bodyParam = operation?.bodyParam;
|
|
434
|
+
if (!bodyParam) {
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
const visibility = resolveRequestVisibility(
|
|
439
|
+
state.context.program,
|
|
440
|
+
operation.__raw.operation,
|
|
441
|
+
operation.verb,
|
|
442
|
+
);
|
|
443
|
+
|
|
444
|
+
for (const segment of bodyParam.methodParameterSegments) {
|
|
445
|
+
const methodParam = segment[0];
|
|
446
|
+
if (!methodParam) {
|
|
447
|
+
continue;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
const originalType = methodParam.type;
|
|
451
|
+
const projectedType = projectTypeReferences(state, originalType, visibility);
|
|
452
|
+
if (projectedType === originalType) {
|
|
453
|
+
// The write view is identical to the read model (collapse).
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
methodParam.type = projectedType;
|
|
458
|
+
|
|
459
|
+
if (originalType === bodyParam.type) {
|
|
460
|
+
// A non-spread body uses the method parameter model as the payload model.
|
|
461
|
+
bodyParam.type = projectedType;
|
|
462
|
+
} else if (bodyParam.type.kind === "model") {
|
|
463
|
+
// A spread body uses a synthesized wrapper serialized property by property.
|
|
464
|
+
for (const bodyProperty of bodyParam.type.properties) {
|
|
465
|
+
if (bodyProperty.type === originalType) {
|
|
466
|
+
bodyProperty.type = projectedType;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
@@ -184,7 +184,6 @@ function buildPolymorphicDeserializer(
|
|
|
184
184
|
) {
|
|
185
185
|
return;
|
|
186
186
|
}
|
|
187
|
-
const union = subType?.discriminatedSubtypes ? "_Union" : "";
|
|
188
187
|
if (!subType || !subType?.name) {
|
|
189
188
|
reportDiagnostic(context.program, {
|
|
190
189
|
code: "anonymous-type-deserialization",
|
|
@@ -192,14 +191,12 @@ function buildPolymorphicDeserializer(
|
|
|
192
191
|
});
|
|
193
192
|
return; // Skip this subtype
|
|
194
193
|
}
|
|
195
|
-
|
|
196
|
-
const
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
true,
|
|
202
|
-
);
|
|
194
|
+
const union = subType.discriminatedSubtypes ? "Union" : "";
|
|
195
|
+
const subTypeName = normalizeModelName(context, subType, NameType.Interface, !union);
|
|
196
|
+
const subtypeDeserializerName = buildModelDeserializer(context, subType, {
|
|
197
|
+
nameOnly: true,
|
|
198
|
+
skipDiscriminatedUnionSuffix: false,
|
|
199
|
+
}) as string;
|
|
203
200
|
|
|
204
201
|
cases.push(`
|
|
205
202
|
case "${discriminatedValue}":
|