@azure-tools/rlc-common 1.0.0-beta.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/.rush/temp/package-deps_build.json +45 -0
- package/.rush/temp/shrinkwrap-deps.json +72 -0
- package/CHANGELOG.md +3 -0
- package/dist/buildClient.js +198 -0
- package/dist/buildClient.js.map +1 -0
- package/dist/buildClientDefinitions.js +160 -0
- package/dist/buildClientDefinitions.js.map +1 -0
- package/dist/buildIndexFile.js +150 -0
- package/dist/buildIndexFile.js.map +1 -0
- package/dist/buildIsUnexpectedHelper.js +219 -0
- package/dist/buildIsUnexpectedHelper.js.map +1 -0
- package/dist/buildMethodShortcuts.js +50 -0
- package/dist/buildMethodShortcuts.js.map +1 -0
- package/dist/buildObjectTypes.js +250 -0
- package/dist/buildObjectTypes.js.map +1 -0
- package/dist/buildPaginateHelper.js +30 -0
- package/dist/buildPaginateHelper.js.map +1 -0
- package/dist/buildParameterTypes.js +287 -0
- package/dist/buildParameterTypes.js.map +1 -0
- package/dist/buildPollingHelper.js +21 -0
- package/dist/buildPollingHelper.js.map +1 -0
- package/dist/buildResponseTypes.js +122 -0
- package/dist/buildResponseTypes.js.map +1 -0
- package/dist/buildSchemaType.js +44 -0
- package/dist/buildSchemaType.js.map +1 -0
- package/dist/buildTopLevelIndexFile.js +45 -0
- package/dist/buildTopLevelIndexFile.js.map +1 -0
- package/dist/helpers/nameConstructors.js +41 -0
- package/dist/helpers/nameConstructors.js.map +1 -0
- package/dist/helpers/nameUtils.js +196 -0
- package/dist/helpers/nameUtils.js.map +1 -0
- package/dist/helpers/operationHelpers.js +83 -0
- package/dist/helpers/operationHelpers.js.map +1 -0
- package/dist/helpers/schemaHelpers.js +27 -0
- package/dist/helpers/schemaHelpers.js.map +1 -0
- package/dist/helpers/shortcutMethods.js +46 -0
- package/dist/helpers/shortcutMethods.js.map +1 -0
- package/dist/index.js +44 -0
- package/dist/index.js.map +1 -0
- package/dist/interfaces.js +18 -0
- package/dist/interfaces.js.map +1 -0
- package/dist/metadata/buildApiExtractorConfig.js +56 -0
- package/dist/metadata/buildApiExtractorConfig.js.map +1 -0
- package/dist/metadata/buildESLintConfig.js +33 -0
- package/dist/metadata/buildESLintConfig.js.map +1 -0
- package/dist/metadata/buildLicenseFile.js +41 -0
- package/dist/metadata/buildLicenseFile.js.map +1 -0
- package/dist/metadata/buildPackageFile.js +232 -0
- package/dist/metadata/buildPackageFile.js.map +1 -0
- package/dist/metadata/buildReadmeFile.js +170 -0
- package/dist/metadata/buildReadmeFile.js.map +1 -0
- package/dist/metadata/buildRollupConfig.js +144 -0
- package/dist/metadata/buildRollupConfig.js.map +1 -0
- package/dist/metadata/buildTsConfig.js +72 -0
- package/dist/metadata/buildTsConfig.js.map +1 -0
- package/dist/package.json +1 -0
- package/dist/static/paginateContent.js +214 -0
- package/dist/static/paginateContent.js.map +1 -0
- package/dist/static/pollingContent.js +78 -0
- package/dist/static/pollingContent.js.map +1 -0
- package/dist/test/buildEnvFile.js +31 -0
- package/dist/test/buildEnvFile.js.map +1 -0
- package/dist/test/buildKarmaConfig.js +19 -0
- package/dist/test/buildKarmaConfig.js.map +1 -0
- package/dist/test/buildRecordedClient.js +22 -0
- package/dist/test/buildRecordedClient.js.map +1 -0
- package/dist/test/buildSampleTest.js +19 -0
- package/dist/test/buildSampleTest.js.map +1 -0
- package/dist/test/template.js +192 -0
- package/dist/test/template.js.map +1 -0
- package/dist-esm/buildClient.js +191 -0
- package/dist-esm/buildClient.js.map +1 -0
- package/dist-esm/buildClientDefinitions.js +155 -0
- package/dist-esm/buildClientDefinitions.js.map +1 -0
- package/dist-esm/buildIndexFile.js +145 -0
- package/dist-esm/buildIndexFile.js.map +1 -0
- package/dist-esm/buildIsUnexpectedHelper.js +215 -0
- package/dist-esm/buildIsUnexpectedHelper.js.map +1 -0
- package/dist-esm/buildMethodShortcuts.js +46 -0
- package/dist-esm/buildMethodShortcuts.js.map +1 -0
- package/dist-esm/buildObjectTypes.js +249 -0
- package/dist-esm/buildObjectTypes.js.map +1 -0
- package/dist-esm/buildPaginateHelper.js +26 -0
- package/dist-esm/buildPaginateHelper.js.map +1 -0
- package/dist-esm/buildParameterTypes.js +288 -0
- package/dist-esm/buildParameterTypes.js.map +1 -0
- package/dist-esm/buildPollingHelper.js +17 -0
- package/dist-esm/buildPollingHelper.js.map +1 -0
- package/dist-esm/buildResponseTypes.js +127 -0
- package/dist-esm/buildResponseTypes.js.map +1 -0
- package/dist-esm/buildSchemaType.js +39 -0
- package/dist-esm/buildSchemaType.js.map +1 -0
- package/dist-esm/buildTopLevelIndexFile.js +41 -0
- package/dist-esm/buildTopLevelIndexFile.js.map +1 -0
- package/dist-esm/helpers/nameConstructors.js +34 -0
- package/dist-esm/helpers/nameConstructors.js.map +1 -0
- package/dist-esm/helpers/nameUtils.js +187 -0
- package/dist-esm/helpers/nameUtils.js.map +1 -0
- package/dist-esm/helpers/operationHelpers.js +72 -0
- package/dist-esm/helpers/operationHelpers.js.map +1 -0
- package/dist-esm/helpers/schemaHelpers.js +21 -0
- package/dist-esm/helpers/schemaHelpers.js.map +1 -0
- package/dist-esm/helpers/shortcutMethods.js +42 -0
- package/dist-esm/helpers/shortcutMethods.js.map +1 -0
- package/dist-esm/index.js +28 -0
- package/dist-esm/index.js.map +1 -0
- package/dist-esm/interfaces.js +15 -0
- package/dist-esm/interfaces.js.map +1 -0
- package/dist-esm/metadata/buildApiExtractorConfig.js +51 -0
- package/dist-esm/metadata/buildApiExtractorConfig.js.map +1 -0
- package/dist-esm/metadata/buildESLintConfig.js +28 -0
- package/dist-esm/metadata/buildESLintConfig.js.map +1 -0
- package/dist-esm/metadata/buildLicenseFile.js +36 -0
- package/dist-esm/metadata/buildLicenseFile.js.map +1 -0
- package/dist-esm/metadata/buildPackageFile.js +234 -0
- package/dist-esm/metadata/buildPackageFile.js.map +1 -0
- package/dist-esm/metadata/buildReadmeFile.js +167 -0
- package/dist-esm/metadata/buildReadmeFile.js.map +1 -0
- package/dist-esm/metadata/buildRollupConfig.js +139 -0
- package/dist-esm/metadata/buildRollupConfig.js.map +1 -0
- package/dist-esm/metadata/buildTsConfig.js +67 -0
- package/dist-esm/metadata/buildTsConfig.js.map +1 -0
- package/dist-esm/package.json +1 -0
- package/dist-esm/static/paginateContent.js +211 -0
- package/dist-esm/static/paginateContent.js.map +1 -0
- package/dist-esm/static/pollingContent.js +75 -0
- package/dist-esm/static/pollingContent.js.map +1 -0
- package/dist-esm/test/buildEnvFile.js +24 -0
- package/dist-esm/test/buildEnvFile.js.map +1 -0
- package/dist-esm/test/buildKarmaConfig.js +14 -0
- package/dist-esm/test/buildKarmaConfig.js.map +1 -0
- package/dist-esm/test/buildRecordedClient.js +17 -0
- package/dist-esm/test/buildRecordedClient.js.map +1 -0
- package/dist-esm/test/buildSampleTest.js +14 -0
- package/dist-esm/test/buildSampleTest.js.map +1 -0
- package/dist-esm/test/template.js +189 -0
- package/dist-esm/test/template.js.map +1 -0
- package/package.json +40 -0
- package/publishPackage.js +11 -0
- package/rlc-common.build.log +2 -0
- package/src/buildClient.ts +251 -0
- package/src/buildClientDefinitions.ts +231 -0
- package/src/buildIndexFile.ts +172 -0
- package/src/buildIsUnexpectedHelper.ts +240 -0
- package/src/buildMethodShortcuts.ts +75 -0
- package/src/buildObjectTypes.ts +393 -0
- package/src/buildPaginateHelper.ts +33 -0
- package/src/buildParameterTypes.ts +435 -0
- package/src/buildPollingHelper.ts +18 -0
- package/src/buildResponseTypes.ts +169 -0
- package/src/buildSchemaType.ts +56 -0
- package/src/buildTopLevelIndexFile.ts +46 -0
- package/src/helpers/nameConstructors.ts +93 -0
- package/src/helpers/nameUtils.ts +227 -0
- package/src/helpers/operationHelpers.ts +103 -0
- package/src/helpers/schemaHelpers.ts +25 -0
- package/src/helpers/shortcutMethods.ts +60 -0
- package/src/index.ts +28 -0
- package/src/interfaces.ts +212 -0
- package/src/metadata/buildApiExtractorConfig.ts +59 -0
- package/src/metadata/buildESLintConfig.ts +34 -0
- package/src/metadata/buildLicenseFile.ts +39 -0
- package/src/metadata/buildPackageFile.ts +271 -0
- package/src/metadata/buildReadmeFile.ts +231 -0
- package/src/metadata/buildRollupConfig.ts +147 -0
- package/src/metadata/buildTsConfig.ts +79 -0
- package/src/static/paginateContent.ts +210 -0
- package/src/static/pollingContent.ts +74 -0
- package/src/test/buildEnvFile.ts +26 -0
- package/src/test/buildKarmaConfig.ts +15 -0
- package/src/test/buildRecordedClient.ts +18 -0
- package/src/test/buildSampleTest.ts +15 -0
- package/src/test/template.ts +192 -0
- package/tsconfig-cjs.json +9 -0
- package/tsconfig-common.json +13 -0
- package/tsconfig.json +13 -0
- package/types/buildClient.d.ts +2 -0
- package/types/buildClientDefinitions.d.ts +5 -0
- package/types/buildIndexFile.d.ts +5 -0
- package/types/buildIsUnexpectedHelper.d.ts +5 -0
- package/types/buildMethodShortcuts.d.ts +4 -0
- package/types/buildObjectTypes.d.ts +14 -0
- package/types/buildPaginateHelper.d.ts +5 -0
- package/types/buildParameterTypes.d.ts +5 -0
- package/types/buildPollingHelper.d.ts +5 -0
- package/types/buildResponseTypes.d.ts +5 -0
- package/types/buildSchemaType.d.ts +19 -0
- package/types/buildTopLevelIndexFile.d.ts +5 -0
- package/types/helpers/nameConstructors.d.ts +28 -0
- package/types/helpers/nameUtils.d.ts +25 -0
- package/types/helpers/operationHelpers.d.ts +9 -0
- package/types/helpers/schemaHelpers.d.ts +4 -0
- package/types/helpers/shortcutMethods.d.ts +3 -0
- package/types/index.d.ts +25 -0
- package/types/interfaces.d.ts +186 -0
- package/types/metadata/buildApiExtractorConfig.d.ts +5 -0
- package/types/metadata/buildESLintConfig.d.ts +5 -0
- package/types/metadata/buildLicenseFile.d.ts +5 -0
- package/types/metadata/buildPackageFile.d.ts +5 -0
- package/types/metadata/buildReadmeFile.d.ts +5 -0
- package/types/metadata/buildRollupConfig.d.ts +5 -0
- package/types/metadata/buildTsConfig.d.ts +5 -0
- package/types/static/paginateContent.d.ts +1 -0
- package/types/static/pollingContent.d.ts +1 -0
- package/types/test/buildEnvFile.d.ts +9 -0
- package/types/test/buildKarmaConfig.d.ts +5 -0
- package/types/test/buildRecordedClient.d.ts +5 -0
- package/types/test/buildSampleTest.d.ts +5 -0
- package/types/test/template.d.ts +5 -0
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
// Copyright (c) Microsoft Corporation.
|
|
2
|
+
// Licensed under the MIT License.
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
CallSignatureDeclarationStructure,
|
|
6
|
+
InterfaceDeclarationStructure,
|
|
7
|
+
OptionalKind,
|
|
8
|
+
Project,
|
|
9
|
+
SourceFile,
|
|
10
|
+
StructureKind,
|
|
11
|
+
Writers
|
|
12
|
+
} from "ts-morph";
|
|
13
|
+
import * as path from "path";
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
buildMethodDefinitions,
|
|
17
|
+
getPathParamDefinitions
|
|
18
|
+
} from "./helpers/operationHelpers.js";
|
|
19
|
+
import { PathMetadata, Paths, RLCModel } from "./interfaces.js";
|
|
20
|
+
import { generateMethodShortcuts } from "./helpers/shortcutMethods.js";
|
|
21
|
+
import { REST_CLIENT_RESERVED } from './buildMethodShortcuts.js'
|
|
22
|
+
import { CasingConvention, NameType, normalizeName } from "./helpers/nameUtils.js";
|
|
23
|
+
import { pascalCase } from "./helpers/nameUtils.js";
|
|
24
|
+
|
|
25
|
+
export function buildClientDefinitions(model: RLCModel) {
|
|
26
|
+
const options = {
|
|
27
|
+
importedParameters: new Set<string>(),
|
|
28
|
+
importedResponses: new Set<string>(),
|
|
29
|
+
clientImports: new Set<string>()
|
|
30
|
+
};
|
|
31
|
+
const project = new Project();
|
|
32
|
+
const srcPath = model.srcPath;
|
|
33
|
+
const filePath = path.join(srcPath, `clientDefinitions.ts`);
|
|
34
|
+
const clientDefinitionsFile = project.createSourceFile(filePath, undefined, {
|
|
35
|
+
overwrite: true
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// Get all paths
|
|
39
|
+
const pathDictionary = model.paths;
|
|
40
|
+
let shortcuts: OptionalKind<InterfaceDeclarationStructure>[] = [];
|
|
41
|
+
// There may be operations without an operation group, those shortcut
|
|
42
|
+
// methods need to be handled differently.
|
|
43
|
+
let shortcutsInOperationGroup: { name: string; type: string }[] = [];
|
|
44
|
+
|
|
45
|
+
if (model.options?.includeShortcuts) {
|
|
46
|
+
shortcuts = generateMethodShortcuts(model.paths);
|
|
47
|
+
clientDefinitionsFile.addInterfaces(shortcuts);
|
|
48
|
+
shortcutsInOperationGroup = shortcuts
|
|
49
|
+
.filter((s) => s.name !== "ClientOperations")
|
|
50
|
+
.map((s) => getShortcutName(s.name));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
clientDefinitionsFile.addInterface({
|
|
54
|
+
name: "Routes",
|
|
55
|
+
isExported: true,
|
|
56
|
+
callSignatures: getPathFirstRoutesInterfaceDefinition(
|
|
57
|
+
pathDictionary,
|
|
58
|
+
clientDefinitionsFile,
|
|
59
|
+
options
|
|
60
|
+
)
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const clientName = model.libraryName;
|
|
64
|
+
|
|
65
|
+
const clientInterfaceName = clientName.endsWith("Client")
|
|
66
|
+
? `${clientName}`
|
|
67
|
+
: `${clientName}Client`;
|
|
68
|
+
|
|
69
|
+
clientDefinitionsFile.addTypeAlias({
|
|
70
|
+
isExported: true,
|
|
71
|
+
name: clientInterfaceName,
|
|
72
|
+
type: Writers.intersectionType(
|
|
73
|
+
"Client",
|
|
74
|
+
Writers.objectType({
|
|
75
|
+
properties: [
|
|
76
|
+
{ name: "path", type: "Routes" },
|
|
77
|
+
...shortcutsInOperationGroup
|
|
78
|
+
]
|
|
79
|
+
}),
|
|
80
|
+
// If the length of shortcuts in operation group and all shortcutsInOperationGroup
|
|
81
|
+
// is the same, then we don't have any operations at the client level
|
|
82
|
+
// Otherwise we need to make the client interface name an union with the
|
|
83
|
+
// definition of all client level shortcut methods
|
|
84
|
+
...(shortcutsInOperationGroup.length !== shortcuts.length
|
|
85
|
+
? [`ClientOperations`]
|
|
86
|
+
: [])
|
|
87
|
+
)
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
if (options.importedParameters.size) {
|
|
91
|
+
clientDefinitionsFile.addImportDeclaration({
|
|
92
|
+
namedImports: [...options.importedParameters],
|
|
93
|
+
moduleSpecifier: "./parameters"
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (options.importedResponses.size) {
|
|
98
|
+
clientDefinitionsFile.addImportDeclaration({
|
|
99
|
+
namedImports: [...options.importedResponses],
|
|
100
|
+
moduleSpecifier: "./responses"
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
options.clientImports.add("Client");
|
|
105
|
+
options.clientImports.add("StreamableMethod");
|
|
106
|
+
clientDefinitionsFile.addImportDeclarations([
|
|
107
|
+
{
|
|
108
|
+
namedImports: [...options.clientImports],
|
|
109
|
+
moduleSpecifier: "@azure-rest/core-client"
|
|
110
|
+
}
|
|
111
|
+
]);
|
|
112
|
+
|
|
113
|
+
return { path: filePath, content: clientDefinitionsFile.getFullText() };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function getPathFirstRoutesInterfaceDefinition(
|
|
117
|
+
paths: Paths,
|
|
118
|
+
sourcefile: SourceFile,
|
|
119
|
+
options: {
|
|
120
|
+
importedParameters: Set<string>;
|
|
121
|
+
importedResponses: Set<string>;
|
|
122
|
+
clientImports: Set<string>;
|
|
123
|
+
}
|
|
124
|
+
): CallSignatureDeclarationStructure[] {
|
|
125
|
+
const operationGroupCount = getOperationGroupCount(paths);
|
|
126
|
+
|
|
127
|
+
const signatures: CallSignatureDeclarationStructure[] = [];
|
|
128
|
+
for (const key of Object.keys(paths)) {
|
|
129
|
+
for (const verb of Object.keys(paths[key].methods)) {
|
|
130
|
+
for (const method of paths[key].methods[verb]) {
|
|
131
|
+
options.importedParameters.add(method.optionsName);
|
|
132
|
+
method.returnType
|
|
133
|
+
.split(" | ")
|
|
134
|
+
.forEach((item) => options.importedResponses.add(item));
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
generatePathFirstRouteMethodsDefinition(
|
|
138
|
+
paths[key],
|
|
139
|
+
operationGroupCount,
|
|
140
|
+
sourcefile
|
|
141
|
+
);
|
|
142
|
+
const pathParams = paths[key].pathParameters;
|
|
143
|
+
signatures.push({
|
|
144
|
+
docs: [
|
|
145
|
+
`Resource for '${key
|
|
146
|
+
.replace(/}/g, "\\}")
|
|
147
|
+
.replace(
|
|
148
|
+
/{/g,
|
|
149
|
+
"\\{"
|
|
150
|
+
)}' has methods for the following verbs: ${Object.keys(
|
|
151
|
+
paths[key].methods
|
|
152
|
+
).join(", ")}`
|
|
153
|
+
],
|
|
154
|
+
parameters: [
|
|
155
|
+
{ name: "path", type: `"${key}"` },
|
|
156
|
+
...getPathParamDefinitions(pathParams)
|
|
157
|
+
],
|
|
158
|
+
returnType: getOperationReturnTypeName(
|
|
159
|
+
paths[key],
|
|
160
|
+
getOperationGroupCount(paths)
|
|
161
|
+
),
|
|
162
|
+
kind: StructureKind.CallSignature
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
return signatures;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function getOperationGroupCount(paths: Paths) {
|
|
169
|
+
const operationGroups = Object.keys(paths)
|
|
170
|
+
.map((p) => paths[p].operationGroupName)
|
|
171
|
+
.filter((p) => p && p !== "Client");
|
|
172
|
+
const uniqueNames = new Set(operationGroups);
|
|
173
|
+
|
|
174
|
+
return uniqueNames.size;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function getOperationReturnTypeName(
|
|
178
|
+
{ operationGroupName, name }: PathMetadata,
|
|
179
|
+
operationGroupCount: number
|
|
180
|
+
) {
|
|
181
|
+
if (
|
|
182
|
+
operationGroupCount > 1 &&
|
|
183
|
+
operationGroupName &&
|
|
184
|
+
operationGroupName !== "Client"
|
|
185
|
+
) {
|
|
186
|
+
return `${pascalCase(operationGroupName)}${pascalCase(name)}`;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return pascalCase(name);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function generatePathFirstRouteMethodsDefinition(
|
|
193
|
+
path: PathMetadata,
|
|
194
|
+
operationGroupCount: number,
|
|
195
|
+
file: SourceFile
|
|
196
|
+
): void {
|
|
197
|
+
const methodDefinitions = buildMethodDefinitions(path.methods);
|
|
198
|
+
const interfaceDef = {
|
|
199
|
+
methods: methodDefinitions,
|
|
200
|
+
name: getOperationReturnTypeName(path, operationGroupCount),
|
|
201
|
+
isExported: true
|
|
202
|
+
};
|
|
203
|
+
file.addInterface(interfaceDef);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function getShortcutName(interfaceName: string) {
|
|
207
|
+
const endIndex = shouldKeepSuffix(interfaceName)
|
|
208
|
+
? undefined
|
|
209
|
+
: interfaceName.length - "Operations".length;
|
|
210
|
+
const clientProperty = normalizeName(
|
|
211
|
+
interfaceName.substring(0, endIndex),
|
|
212
|
+
NameType.OperationGroup,
|
|
213
|
+
true,
|
|
214
|
+
REST_CLIENT_RESERVED,
|
|
215
|
+
CasingConvention.Camel
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
return {
|
|
219
|
+
name: clientProperty,
|
|
220
|
+
type: interfaceName
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function shouldKeepSuffix(name: string) {
|
|
225
|
+
const reservedNames = [
|
|
226
|
+
"pipelineOperations",
|
|
227
|
+
"pathOperations",
|
|
228
|
+
"pathUncheckedOperations"
|
|
229
|
+
];
|
|
230
|
+
return reservedNames.some((r) => r.toLowerCase() === name.toLowerCase());
|
|
231
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
// Copyright (c) Microsoft Corporation.
|
|
2
|
+
// Licensed under the MIT License.
|
|
3
|
+
|
|
4
|
+
import { Project, SourceFile } from "ts-morph";
|
|
5
|
+
import { NameType, normalizeName } from "./helpers/nameUtils.js";
|
|
6
|
+
import {
|
|
7
|
+
hasInputModels,
|
|
8
|
+
hasOutputModels,
|
|
9
|
+
hasPagingOperations,
|
|
10
|
+
hasPollingOperations,
|
|
11
|
+
hasUnexpectedHelper
|
|
12
|
+
} from "./helpers/operationHelpers.js";
|
|
13
|
+
import { RLCModel } from "./interfaces.js";
|
|
14
|
+
import * as path from "path";
|
|
15
|
+
|
|
16
|
+
export function buildIndexFile(model: RLCModel) {
|
|
17
|
+
const multiClient = Boolean(model.options?.multiClient),
|
|
18
|
+
batch = model.options?.batch;
|
|
19
|
+
const project = new Project();
|
|
20
|
+
const { srcPath } = model;
|
|
21
|
+
const filePath = path.join(srcPath, `index.ts`);
|
|
22
|
+
const indexFile = project.createSourceFile(filePath, undefined, {
|
|
23
|
+
overwrite: true
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
if (!multiClient || !batch || batch?.length === 1) {
|
|
27
|
+
// if we are generate single client package for RLC
|
|
28
|
+
generateRLCIndex(indexFile, model);
|
|
29
|
+
} else {
|
|
30
|
+
generateRLCIndexForMultiClient(indexFile, model);
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
path: filePath,
|
|
34
|
+
content: indexFile.getFullText()
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// to generate a index.ts for each single module inside the multi client RLC package
|
|
39
|
+
function generateRLCIndexForMultiClient(file: SourceFile, model: RLCModel) {
|
|
40
|
+
const clientName = model.libraryName;
|
|
41
|
+
const createClientFuncName = `createClient`;
|
|
42
|
+
const moduleName = normalizeName(clientName, NameType.File);
|
|
43
|
+
|
|
44
|
+
file.addImportDeclaration({
|
|
45
|
+
namespaceImport: "Parameters",
|
|
46
|
+
moduleSpecifier: "./parameters"
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
file.addImportDeclaration({
|
|
50
|
+
namespaceImport: "Responses",
|
|
51
|
+
moduleSpecifier: "./responses"
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
file.addImportDeclaration({
|
|
55
|
+
namespaceImport: "Client",
|
|
56
|
+
moduleSpecifier: "./clientDefinitions"
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const exports = ["Parameters", "Responses", "Client"];
|
|
60
|
+
if (hasInputModels(model)) {
|
|
61
|
+
file.addImportDeclaration({
|
|
62
|
+
namespaceImport: "Models",
|
|
63
|
+
moduleSpecifier: "./models"
|
|
64
|
+
});
|
|
65
|
+
exports.push("Models");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (hasOutputModels(model)) {
|
|
69
|
+
file.addImportDeclaration({
|
|
70
|
+
namespaceImport: "OutputModels",
|
|
71
|
+
moduleSpecifier: "./outputModels"
|
|
72
|
+
});
|
|
73
|
+
exports.push("OutputModels");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (hasPagingOperations(model)) {
|
|
77
|
+
file.addImportDeclaration({
|
|
78
|
+
namespaceImport: "PaginateHelper",
|
|
79
|
+
moduleSpecifier: "./paginateHelper"
|
|
80
|
+
});
|
|
81
|
+
exports.push("PaginateHelper");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (hasPollingOperations(model)) {
|
|
85
|
+
file.addImportDeclaration({
|
|
86
|
+
namespaceImport: "PollingHelper",
|
|
87
|
+
moduleSpecifier: "./pollingHelper"
|
|
88
|
+
});
|
|
89
|
+
exports.push("PollingHelper");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
file.addExportDeclarations([
|
|
93
|
+
{
|
|
94
|
+
moduleSpecifier: `./${moduleName}`,
|
|
95
|
+
namedExports: [`${createClientFuncName}`]
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
namedExports: [...exports]
|
|
99
|
+
}
|
|
100
|
+
]);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function generateRLCIndex(file: SourceFile, model: RLCModel) {
|
|
104
|
+
const clientName = model.libraryName;
|
|
105
|
+
const createClientFuncName = `${clientName}`;
|
|
106
|
+
const moduleName = normalizeName(clientName, NameType.File);
|
|
107
|
+
|
|
108
|
+
file.addImportDeclaration({
|
|
109
|
+
moduleSpecifier: `./${moduleName}`,
|
|
110
|
+
defaultImport: createClientFuncName
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
file.addExportDeclarations([
|
|
114
|
+
{
|
|
115
|
+
moduleSpecifier: `./${moduleName}`
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
moduleSpecifier: "./parameters"
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
moduleSpecifier: "./responses"
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
moduleSpecifier: "./clientDefinitions"
|
|
125
|
+
}
|
|
126
|
+
]);
|
|
127
|
+
|
|
128
|
+
if (hasUnexpectedHelper(model)) {
|
|
129
|
+
file.addExportDeclarations([
|
|
130
|
+
{
|
|
131
|
+
moduleSpecifier: "./isUnexpected"
|
|
132
|
+
}
|
|
133
|
+
]);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (hasInputModels(model)) {
|
|
137
|
+
file.addExportDeclarations([
|
|
138
|
+
{
|
|
139
|
+
moduleSpecifier: "./models"
|
|
140
|
+
}
|
|
141
|
+
]);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (hasOutputModels(model)) {
|
|
145
|
+
file.addExportDeclarations([
|
|
146
|
+
{
|
|
147
|
+
moduleSpecifier: "./outputModels"
|
|
148
|
+
}
|
|
149
|
+
]);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (hasPagingOperations(model)) {
|
|
153
|
+
file.addExportDeclarations([
|
|
154
|
+
{
|
|
155
|
+
moduleSpecifier: "./paginateHelper"
|
|
156
|
+
}
|
|
157
|
+
]);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (hasPollingOperations(model)) {
|
|
161
|
+
file.addExportDeclarations([
|
|
162
|
+
{
|
|
163
|
+
moduleSpecifier: "./pollingHelper"
|
|
164
|
+
}
|
|
165
|
+
]);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
file.addExportAssignment({
|
|
169
|
+
expression: createClientFuncName,
|
|
170
|
+
isExportEquals: false
|
|
171
|
+
});
|
|
172
|
+
}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
// Copyright (c) Microsoft Corporation.
|
|
2
|
+
// Licensed under the MIT License.
|
|
3
|
+
|
|
4
|
+
import { Paths, RLCModel } from "./interfaces.js";
|
|
5
|
+
import * as path from "path";
|
|
6
|
+
import {
|
|
7
|
+
FunctionDeclarationOverloadStructure,
|
|
8
|
+
OptionalKind,
|
|
9
|
+
Project,
|
|
10
|
+
VariableDeclarationKind
|
|
11
|
+
} from "ts-morph";
|
|
12
|
+
import { hasUnexpectedHelper } from "./helpers/operationHelpers.js";
|
|
13
|
+
export function buildIsUnexpectedHelper(model: RLCModel) {
|
|
14
|
+
if (!hasUnexpectedHelper(model)) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const project = new Project();
|
|
18
|
+
const srcPath = model.srcPath;
|
|
19
|
+
const filePath = path.join(srcPath, `isUnexpected.ts`);
|
|
20
|
+
const isErrorHelper = project.createSourceFile(filePath, undefined, {
|
|
21
|
+
overwrite: true
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
let map: Record<string, string[]> = {};
|
|
25
|
+
let allResponseTypes: Set<string> = new Set();
|
|
26
|
+
let allErrorTypes: Set<string> = new Set();
|
|
27
|
+
let overloads: OptionalKind<FunctionDeclarationOverloadStructure>[] = [];
|
|
28
|
+
const pathDictionary = model.paths;
|
|
29
|
+
|
|
30
|
+
for (const [path, details] of Object.entries(pathDictionary)) {
|
|
31
|
+
for (const [methodName, methodDetails] of Object.entries(details.methods)) {
|
|
32
|
+
const originalMethod = methodName.toUpperCase();
|
|
33
|
+
const operation = `${originalMethod} ${path}`;
|
|
34
|
+
const success = methodDetails[0].successStatus;
|
|
35
|
+
map = { ...map, ...{ [operation]: success } };
|
|
36
|
+
|
|
37
|
+
// LROs may call the same path but with GET
|
|
38
|
+
// to get the operation status.
|
|
39
|
+
if (
|
|
40
|
+
methodDetails[0].annotations?.isLongRunning &&
|
|
41
|
+
originalMethod !== "GET"
|
|
42
|
+
) {
|
|
43
|
+
const operation = `GET ${path}`;
|
|
44
|
+
const success =
|
|
45
|
+
(pathDictionary[path].methods["get"] &&
|
|
46
|
+
pathDictionary[path].methods["get"][0]?.successStatus) ??
|
|
47
|
+
methodDetails[0].successStatus;
|
|
48
|
+
map = { ...map, ...{ [operation]: success } };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const successTypes = methodDetails[0].responseTypes.success;
|
|
52
|
+
const errorTypes = methodDetails[0].responseTypes.error;
|
|
53
|
+
|
|
54
|
+
if (!successTypes.length || !errorTypes.length || !errorTypes[0]) {
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
successTypes.forEach((t) => allResponseTypes.add(t));
|
|
59
|
+
errorTypes.forEach((t) => {
|
|
60
|
+
allResponseTypes.add(t);
|
|
61
|
+
allErrorTypes.add(t);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
overloads.push({
|
|
65
|
+
isExported: true,
|
|
66
|
+
parameters: [
|
|
67
|
+
{
|
|
68
|
+
name: "response",
|
|
69
|
+
type: [...successTypes, ...errorTypes].join(" | ")
|
|
70
|
+
}
|
|
71
|
+
],
|
|
72
|
+
returnType: `response is ${errorTypes[0]}`
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
isErrorHelper.addImportDeclaration({
|
|
77
|
+
namedImports: [...allResponseTypes],
|
|
78
|
+
moduleSpecifier: "./responses"
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
isErrorHelper.addVariableStatement({
|
|
82
|
+
declarations: [
|
|
83
|
+
{
|
|
84
|
+
name: "responseMap",
|
|
85
|
+
initializer: JSON.stringify(map),
|
|
86
|
+
type: "Record<string, string[]>"
|
|
87
|
+
}
|
|
88
|
+
],
|
|
89
|
+
declarationKind: VariableDeclarationKind.Const
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
if (allErrorTypes.size) {
|
|
93
|
+
const hasTemplate = hasParametrizedPath(pathDictionary);
|
|
94
|
+
isErrorHelper.addFunction({
|
|
95
|
+
overloads,
|
|
96
|
+
isExported: true,
|
|
97
|
+
name: "isUnexpected",
|
|
98
|
+
parameters: [
|
|
99
|
+
{
|
|
100
|
+
name: "response",
|
|
101
|
+
type: [...allResponseTypes].join(" | ")
|
|
102
|
+
}
|
|
103
|
+
],
|
|
104
|
+
returnType: `response is ${[...allErrorTypes].join(" | ")}`,
|
|
105
|
+
statements: [
|
|
106
|
+
`
|
|
107
|
+
const lroOriginal = response.headers["x-ms-original-url"];
|
|
108
|
+
const url = new URL(lroOriginal ?? response.request.url);
|
|
109
|
+
const method = response.request.method;
|
|
110
|
+
${
|
|
111
|
+
hasTemplate ? "let" : "const"
|
|
112
|
+
} pathDetails = responseMap[\`\${method} \${url.pathname}\`];
|
|
113
|
+
if (!pathDetails) {`,
|
|
114
|
+
hasTemplate
|
|
115
|
+
? "pathDetails = geParametrizedPathSuccess(method, url.pathname);"
|
|
116
|
+
: `return true;`,
|
|
117
|
+
` }
|
|
118
|
+
return !pathDetails.includes(response.status);
|
|
119
|
+
`
|
|
120
|
+
]
|
|
121
|
+
});
|
|
122
|
+
if (hasTemplate) {
|
|
123
|
+
isErrorHelper.addFunction({
|
|
124
|
+
isExported: false,
|
|
125
|
+
name: "geParametrizedPathSuccess",
|
|
126
|
+
parameters: [
|
|
127
|
+
{
|
|
128
|
+
name: "method",
|
|
129
|
+
type: "string"
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
name: "path",
|
|
133
|
+
type: "string"
|
|
134
|
+
}
|
|
135
|
+
],
|
|
136
|
+
returnType: `string[]`,
|
|
137
|
+
statements: [
|
|
138
|
+
`
|
|
139
|
+
const pathParts = path.split("/");
|
|
140
|
+
|
|
141
|
+
// Iterate the responseMap to find a match
|
|
142
|
+
for (const [key, value] of Object.entries(responseMap)) {
|
|
143
|
+
// Extracting the path from the map key which is in format
|
|
144
|
+
// GET /path/foo
|
|
145
|
+
if (!key.startsWith(method)) {
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
const candidatePath = getPathFromMapKey(key);
|
|
149
|
+
// Get each part of the url path
|
|
150
|
+
const candidateParts = candidatePath.split("/");
|
|
151
|
+
|
|
152
|
+
// If the candidate and actual paths don't match in size
|
|
153
|
+
// we move on to the next candidate path
|
|
154
|
+
if (
|
|
155
|
+
candidateParts.length === pathParts.length &&
|
|
156
|
+
hasParametrizedPath(key)
|
|
157
|
+
) {
|
|
158
|
+
// track if we have found a match to return the values found.
|
|
159
|
+
let found = true;
|
|
160
|
+
for (let i = 0; i < candidateParts.length; i++) {
|
|
161
|
+
if (
|
|
162
|
+
candidateParts[i]?.startsWith("{") &&
|
|
163
|
+
candidateParts[i]?.endsWith("}")
|
|
164
|
+
) {
|
|
165
|
+
// If the current part of the candidate is a "template" part
|
|
166
|
+
// it is a match with the actual path part on hand
|
|
167
|
+
// skip as the parameterized part can match anything
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// If the candidate part is not a template and
|
|
172
|
+
// the parts don't match mark the candidate as not found
|
|
173
|
+
// to move on with the next candidate path.
|
|
174
|
+
if (candidateParts[i] !== pathParts[i]) {
|
|
175
|
+
found = false;
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// We finished evaluating the current candidate parts
|
|
181
|
+
// if all parts matched we return the success values form
|
|
182
|
+
// the path mapping.
|
|
183
|
+
if (found) {
|
|
184
|
+
return value;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// No match was found, return an empty array.
|
|
190
|
+
return [];
|
|
191
|
+
`
|
|
192
|
+
]
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
isErrorHelper.addFunction({
|
|
196
|
+
isExported: false,
|
|
197
|
+
name: "hasParametrizedPath",
|
|
198
|
+
parameters: [
|
|
199
|
+
{
|
|
200
|
+
name: "path",
|
|
201
|
+
type: "string"
|
|
202
|
+
}
|
|
203
|
+
],
|
|
204
|
+
returnType: `boolean`,
|
|
205
|
+
statements: [`return path.includes("/{");`]
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
isErrorHelper.addFunction({
|
|
209
|
+
isExported: false,
|
|
210
|
+
name: "getPathFromMapKey",
|
|
211
|
+
parameters: [
|
|
212
|
+
{
|
|
213
|
+
name: "mapKey",
|
|
214
|
+
type: "string"
|
|
215
|
+
}
|
|
216
|
+
],
|
|
217
|
+
returnType: `string`,
|
|
218
|
+
statements: [
|
|
219
|
+
`const pathStart = mapKey.indexOf("/");
|
|
220
|
+
return mapKey.slice(pathStart);`
|
|
221
|
+
]
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return {
|
|
227
|
+
path: filePath,
|
|
228
|
+
content: isErrorHelper.getFullText()
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function hasParametrizedPath(pathDictionary: Paths): boolean {
|
|
233
|
+
for (const [path] of Object.entries(pathDictionary)) {
|
|
234
|
+
if (path.includes("/{") && path.includes("}")) {
|
|
235
|
+
return true;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// Copyright (c) Microsoft Corporation.
|
|
2
|
+
// Licensed under the MIT License.
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
CasingConvention,
|
|
6
|
+
NameType,
|
|
7
|
+
normalizeName,
|
|
8
|
+
ReservedName
|
|
9
|
+
} from "./helpers/nameUtils.js";
|
|
10
|
+
import { Paths, PathParameter, PathMetadata } from "./interfaces.js";
|
|
11
|
+
|
|
12
|
+
export const REST_CLIENT_RESERVED: ReservedName[] = [
|
|
13
|
+
{ name: "path", reservedFor: [NameType.Property, NameType.OperationGroup] },
|
|
14
|
+
{
|
|
15
|
+
name: "pathUnchecked",
|
|
16
|
+
reservedFor: [NameType.Property, NameType.OperationGroup]
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
name: "pipeline",
|
|
20
|
+
reservedFor: [NameType.Property, NameType.OperationGroup]
|
|
21
|
+
}
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
export function buildMethodShortcutImplementation(paths: Paths) {
|
|
25
|
+
let keys: Record<string, string[]> = {};
|
|
26
|
+
for (const path of Object.keys(paths)) {
|
|
27
|
+
const groupName = normalizeName(
|
|
28
|
+
paths[path].operationGroupName,
|
|
29
|
+
NameType.OperationGroup,
|
|
30
|
+
true,
|
|
31
|
+
REST_CLIENT_RESERVED,
|
|
32
|
+
CasingConvention.Camel
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
if (keys[groupName]) {
|
|
36
|
+
keys[groupName].push(...buildOperationDeclarations(path, paths[path]));
|
|
37
|
+
} else {
|
|
38
|
+
keys[groupName] = buildOperationDeclarations(path, paths[path]);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return keys;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function buildOperationDeclarations(path: string, pathMetadata: PathMetadata) {
|
|
45
|
+
let ops: string[] = [];
|
|
46
|
+
for (const method of Object.keys(pathMetadata.methods)) {
|
|
47
|
+
for (const op of pathMetadata.methods[method]) {
|
|
48
|
+
const pathParams = pathMetadata?.pathParameters;
|
|
49
|
+
const name = normalizeName(op.operationName, NameType.Property);
|
|
50
|
+
const methodDefinitions = generateOperationDeclaration(
|
|
51
|
+
path,
|
|
52
|
+
name,
|
|
53
|
+
method,
|
|
54
|
+
pathParams
|
|
55
|
+
);
|
|
56
|
+
ops = [...ops, methodDefinitions];
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return ops;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function generateOperationDeclaration(
|
|
64
|
+
path: string,
|
|
65
|
+
operationName: string,
|
|
66
|
+
method: string,
|
|
67
|
+
pathParams: PathParameter[] = []
|
|
68
|
+
): string {
|
|
69
|
+
const pathParamNames = `${
|
|
70
|
+
pathParams.length > 0 ? `${pathParams.map((p) => p.name)},` : ""
|
|
71
|
+
}`;
|
|
72
|
+
return `"${operationName}": (${pathParamNames} options) => {
|
|
73
|
+
return client.path("${path}", ${pathParamNames}).${method}(options);
|
|
74
|
+
}`;
|
|
75
|
+
}
|