@azure-tools/rlc-common 1.0.0-beta.2 → 1.0.0-beta.5
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/.eslintrc.json +23 -0
- package/.prettierignore +1 -0
- package/.prettierrc +7 -0
- package/.rush/temp/package-deps_build.json +26 -21
- package/.rush/temp/shrinkwrap-deps.json +81 -6
- package/CHANGELOG.md +15 -1
- package/CONTRIBUTING.md +29 -0
- package/README.md +3 -0
- package/dist/buildClient.js +48 -10
- package/dist/buildClient.js.map +1 -1
- package/dist/buildClientDefinitions.js.map +1 -1
- package/dist/buildIsUnexpectedHelper.js +52 -51
- package/dist/buildIsUnexpectedHelper.js.map +1 -1
- package/dist/buildMethodShortcuts.js +1 -1
- package/dist/buildMethodShortcuts.js.map +1 -1
- package/dist/buildObjectTypes.js +16 -9
- package/dist/buildObjectTypes.js.map +1 -1
- package/dist/buildParameterTypes.js +41 -2
- package/dist/buildParameterTypes.js.map +1 -1
- package/dist/buildResponseTypes.js +1 -1
- package/dist/buildResponseTypes.js.map +1 -1
- package/dist/buildTopLevelIndexFile.js +5 -3
- package/dist/buildTopLevelIndexFile.js.map +1 -1
- package/dist/helpers/nameConstructors.js +2 -2
- package/dist/helpers/nameConstructors.js.map +1 -1
- package/dist/helpers/nameUtils.js +1 -1
- package/dist/helpers/nameUtils.js.map +1 -1
- package/dist/helpers/operationHelpers.js +1 -1
- package/dist/helpers/operationHelpers.js.map +1 -1
- package/dist/helpers/shortcutMethods.js +1 -1
- package/dist/helpers/shortcutMethods.js.map +1 -1
- package/dist/interfaces.js.map +1 -1
- package/dist/metadata/buildPackageFile.js +5 -4
- package/dist/metadata/buildPackageFile.js.map +1 -1
- package/dist/metadata/buildReadmeFile.js +1 -1
- package/dist/package.json +1 -1
- package/dist/static/paginateContent.js +1 -1
- package/dist/test/template.js +1 -0
- package/dist/test/template.js.map +1 -1
- package/dist-esm/buildClient.js +47 -9
- package/dist-esm/buildClient.js.map +1 -1
- package/dist-esm/buildClientDefinitions.js +1 -1
- package/dist-esm/buildClientDefinitions.js.map +1 -1
- package/dist-esm/buildIsUnexpectedHelper.js +52 -51
- package/dist-esm/buildIsUnexpectedHelper.js.map +1 -1
- package/dist-esm/buildMethodShortcuts.js +1 -1
- package/dist-esm/buildMethodShortcuts.js.map +1 -1
- package/dist-esm/buildObjectTypes.js +16 -7
- package/dist-esm/buildObjectTypes.js.map +1 -1
- package/dist-esm/buildParameterTypes.js +45 -1
- package/dist-esm/buildParameterTypes.js.map +1 -1
- package/dist-esm/buildResponseTypes.js +1 -1
- package/dist-esm/buildResponseTypes.js.map +1 -1
- package/dist-esm/buildTopLevelIndexFile.js +5 -3
- package/dist-esm/buildTopLevelIndexFile.js.map +1 -1
- package/dist-esm/helpers/nameConstructors.js +2 -2
- package/dist-esm/helpers/nameConstructors.js.map +1 -1
- package/dist-esm/helpers/nameUtils.js +1 -1
- package/dist-esm/helpers/nameUtils.js.map +1 -1
- package/dist-esm/helpers/operationHelpers.js +1 -1
- package/dist-esm/helpers/operationHelpers.js.map +1 -1
- package/dist-esm/helpers/shortcutMethods.js +1 -1
- package/dist-esm/helpers/shortcutMethods.js.map +1 -1
- package/dist-esm/interfaces.js.map +1 -1
- package/dist-esm/metadata/buildPackageFile.js +5 -4
- package/dist-esm/metadata/buildPackageFile.js.map +1 -1
- package/dist-esm/metadata/buildReadmeFile.js +1 -1
- package/dist-esm/package.json +1 -1
- package/dist-esm/static/paginateContent.js +1 -1
- package/dist-esm/test/template.js +1 -0
- package/dist-esm/test/template.js.map +1 -1
- package/package.json +10 -4
- package/src/buildClient.ts +74 -11
- package/src/buildClientDefinitions.ts +6 -2
- package/src/buildIsUnexpectedHelper.ts +52 -52
- package/src/buildMethodShortcuts.ts +1 -1
- package/src/buildObjectTypes.ts +24 -8
- package/src/buildParameterTypes.ts +53 -2
- package/src/buildResponseTypes.ts +1 -1
- package/src/buildTopLevelIndexFile.ts +5 -3
- package/src/helpers/nameConstructors.ts +2 -2
- package/src/helpers/nameUtils.ts +1 -1
- package/src/helpers/operationHelpers.ts +1 -1
- package/src/helpers/schemaHelpers.ts +1 -1
- package/src/helpers/shortcutMethods.ts +1 -1
- package/src/interfaces.ts +8 -1
- package/src/metadata/buildPackageFile.ts +5 -4
- package/src/metadata/buildReadmeFile.ts +1 -1
- package/src/static/paginateContent.ts +1 -1
- package/src/test/template.ts +1 -0
- package/types/buildParameterTypes.d.ts +9 -1
- package/types/interfaces.d.ts +7 -1
- package/types/test/template.d.ts +1 -1
|
@@ -18,8 +18,12 @@ import {
|
|
|
18
18
|
} from "./helpers/operationHelpers.js";
|
|
19
19
|
import { PathMetadata, Paths, RLCModel } from "./interfaces.js";
|
|
20
20
|
import { generateMethodShortcuts } from "./helpers/shortcutMethods.js";
|
|
21
|
-
import { REST_CLIENT_RESERVED } from
|
|
22
|
-
import {
|
|
21
|
+
import { REST_CLIENT_RESERVED } from "./buildMethodShortcuts.js";
|
|
22
|
+
import {
|
|
23
|
+
CasingConvention,
|
|
24
|
+
NameType,
|
|
25
|
+
normalizeName
|
|
26
|
+
} from "./helpers/nameUtils.js";
|
|
23
27
|
import { pascalCase } from "./helpers/nameUtils.js";
|
|
24
28
|
|
|
25
29
|
export function buildClientDefinitions(model: RLCModel) {
|
|
@@ -22,9 +22,9 @@ export function buildIsUnexpectedHelper(model: RLCModel) {
|
|
|
22
22
|
});
|
|
23
23
|
|
|
24
24
|
let map: Record<string, string[]> = {};
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
const allResponseTypes: Set<string> = new Set();
|
|
26
|
+
const allErrorTypes: Set<string> = new Set();
|
|
27
|
+
const overloads: OptionalKind<FunctionDeclarationOverloadStructure>[] = [];
|
|
28
28
|
const pathDictionary = model.paths;
|
|
29
29
|
|
|
30
30
|
for (const [path, details] of Object.entries(pathDictionary)) {
|
|
@@ -112,7 +112,7 @@ export function buildIsUnexpectedHelper(model: RLCModel) {
|
|
|
112
112
|
} pathDetails = responseMap[\`\${method} \${url.pathname}\`];
|
|
113
113
|
if (!pathDetails) {`,
|
|
114
114
|
hasTemplate
|
|
115
|
-
? "pathDetails =
|
|
115
|
+
? "pathDetails = getParametrizedPathSuccess(method, url.pathname);"
|
|
116
116
|
: `return true;`,
|
|
117
117
|
` }
|
|
118
118
|
return !pathDetails.includes(response.status);
|
|
@@ -122,7 +122,7 @@ export function buildIsUnexpectedHelper(model: RLCModel) {
|
|
|
122
122
|
if (hasTemplate) {
|
|
123
123
|
isErrorHelper.addFunction({
|
|
124
124
|
isExported: false,
|
|
125
|
-
name: "
|
|
125
|
+
name: "getParametrizedPathSuccess",
|
|
126
126
|
parameters: [
|
|
127
127
|
{
|
|
128
128
|
name: "method",
|
|
@@ -136,75 +136,75 @@ export function buildIsUnexpectedHelper(model: RLCModel) {
|
|
|
136
136
|
returnType: `string[]`,
|
|
137
137
|
statements: [
|
|
138
138
|
`
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
//
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
139
|
+
const pathParts = path.split("/");
|
|
140
|
+
|
|
141
|
+
// Traverse list to match the longest candidate
|
|
142
|
+
// matchedLen: the length of candidate path
|
|
143
|
+
// matchedValue: the matched status code array
|
|
144
|
+
let matchedLen = -1,
|
|
145
|
+
matchedValue: string[] = [];
|
|
146
|
+
|
|
147
|
+
// Iterate the responseMap to find a match
|
|
148
|
+
for (const [key, value] of Object.entries(responseMap)) {
|
|
149
|
+
// Extracting the path from the map key which is in format
|
|
150
|
+
// GET /path/foo
|
|
151
|
+
if (!key.startsWith(method)) {
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
const candidatePath = getPathFromMapKey(key);
|
|
155
|
+
// Get each part of the url path
|
|
156
|
+
const candidateParts = candidatePath.split("/");
|
|
157
|
+
|
|
158
158
|
// track if we have found a match to return the values found.
|
|
159
159
|
let found = true;
|
|
160
|
-
for (
|
|
160
|
+
for (
|
|
161
|
+
let i = candidateParts.length - 1, j = pathParts.length - 1;
|
|
162
|
+
i >= 1 && j >= 1;
|
|
163
|
+
i--, j--
|
|
164
|
+
) {
|
|
161
165
|
if (
|
|
162
166
|
candidateParts[i]?.startsWith("{") &&
|
|
163
|
-
candidateParts[i]?.
|
|
167
|
+
candidateParts[i]?.indexOf("}") !== -1
|
|
164
168
|
) {
|
|
169
|
+
const start = candidateParts[i]!.indexOf("}") + 1,
|
|
170
|
+
end = candidateParts[i]?.length;
|
|
165
171
|
// If the current part of the candidate is a "template" part
|
|
166
|
-
//
|
|
167
|
-
//
|
|
172
|
+
// Try to use the suffix of pattern to match the path
|
|
173
|
+
// {guid} ==> $
|
|
174
|
+
// {guid}:export ==> :export$
|
|
175
|
+
const isMatched = new RegExp(
|
|
176
|
+
\`\${candidateParts[i]?.slice(start, end)}\`
|
|
177
|
+
).test(pathParts[j] || '');
|
|
178
|
+
|
|
179
|
+
if (!isMatched) {
|
|
180
|
+
found = false;
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
168
183
|
continue;
|
|
169
184
|
}
|
|
170
|
-
|
|
185
|
+
|
|
171
186
|
// If the candidate part is not a template and
|
|
172
187
|
// the parts don't match mark the candidate as not found
|
|
173
188
|
// to move on with the next candidate path.
|
|
174
|
-
if (candidateParts[i] !== pathParts[
|
|
189
|
+
if (candidateParts[i] !== pathParts[j]) {
|
|
175
190
|
found = false;
|
|
176
191
|
break;
|
|
177
192
|
}
|
|
178
193
|
}
|
|
179
|
-
|
|
194
|
+
|
|
180
195
|
// We finished evaluating the current candidate parts
|
|
181
|
-
// if
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
196
|
+
// Update the matched value if and only if we found the longer pattern
|
|
197
|
+
if (found && candidatePath.length > matchedLen) {
|
|
198
|
+
matchedLen = candidatePath.length;
|
|
199
|
+
matchedValue = value;
|
|
185
200
|
}
|
|
186
201
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
// No match was found, return an empty array.
|
|
190
|
-
return [];
|
|
202
|
+
|
|
203
|
+
return matchedValue;
|
|
191
204
|
`
|
|
192
205
|
]
|
|
193
206
|
});
|
|
194
207
|
|
|
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
208
|
isErrorHelper.addFunction({
|
|
209
209
|
isExported: false,
|
|
210
210
|
name: "getPathFromMapKey",
|
|
@@ -22,7 +22,7 @@ export const REST_CLIENT_RESERVED: ReservedName[] = [
|
|
|
22
22
|
];
|
|
23
23
|
|
|
24
24
|
export function buildMethodShortcutImplementation(paths: Paths) {
|
|
25
|
-
|
|
25
|
+
const keys: Record<string, string[]> = {};
|
|
26
26
|
for (const path of Object.keys(paths)) {
|
|
27
27
|
const groupName = normalizeName(
|
|
28
28
|
paths[path].operationGroupName,
|
package/src/buildObjectTypes.ts
CHANGED
|
@@ -123,7 +123,7 @@ function getPolymorphicTypeAlias(
|
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
const description = objectSchema.description;
|
|
126
|
-
|
|
126
|
+
|
|
127
127
|
return {
|
|
128
128
|
kind: StructureKind.TypeAlias,
|
|
129
129
|
...(description && { docs: [{ description }] }),
|
|
@@ -316,7 +316,7 @@ function getImmediateParentsNames(
|
|
|
316
316
|
return [];
|
|
317
317
|
}
|
|
318
318
|
|
|
319
|
-
|
|
319
|
+
const extendFrom: string[] = [];
|
|
320
320
|
|
|
321
321
|
// If an immediate parent is a DictionarySchema, that means that the object has been marked
|
|
322
322
|
// with additional properties. We need to add Record<string, unknown> to the extend list and
|
|
@@ -350,7 +350,13 @@ function getPropertySignatures(
|
|
|
350
350
|
schemaUsage: SchemaContext[],
|
|
351
351
|
importedModels: Set<string>
|
|
352
352
|
) {
|
|
353
|
-
|
|
353
|
+
let validProperties = Object.keys(properties);
|
|
354
|
+
if (schemaUsage.includes(SchemaContext.Input)) {
|
|
355
|
+
validProperties = validProperties.filter((p) => {
|
|
356
|
+
return !properties[p].readOnly;
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
return validProperties.map((p) =>
|
|
354
360
|
getPropertySignature(
|
|
355
361
|
{ ...properties[p], name: p },
|
|
356
362
|
schemaUsage,
|
|
@@ -374,11 +380,7 @@ export function getPropertySignature(
|
|
|
374
380
|
|
|
375
381
|
const description = property.description;
|
|
376
382
|
const type =
|
|
377
|
-
(
|
|
378
|
-
property.usage?.includes(SchemaContext.Output)) ||
|
|
379
|
-
(schemaUsage.includes(SchemaContext.Exception) &&
|
|
380
|
-
property.usage?.includes(SchemaContext.Exception))) &&
|
|
381
|
-
property.outputTypeName
|
|
383
|
+
generateForOutput(schemaUsage, property.usage) && property.outputTypeName
|
|
382
384
|
? property.outputTypeName
|
|
383
385
|
: property.typeName
|
|
384
386
|
? property.typeName
|
|
@@ -387,7 +389,21 @@ export function getPropertySignature(
|
|
|
387
389
|
name: propertyName,
|
|
388
390
|
...(description && { docs: [{ description }] }),
|
|
389
391
|
hasQuestionToken: !property.required,
|
|
392
|
+
isReadonly:
|
|
393
|
+
generateForOutput(schemaUsage, property.usage) && property.readOnly,
|
|
390
394
|
type,
|
|
391
395
|
kind: StructureKind.PropertySignature
|
|
392
396
|
};
|
|
393
397
|
}
|
|
398
|
+
|
|
399
|
+
function generateForOutput(
|
|
400
|
+
schemaUsage: SchemaContext[],
|
|
401
|
+
propertyUsage?: SchemaContext[]
|
|
402
|
+
) {
|
|
403
|
+
return (
|
|
404
|
+
(schemaUsage.includes(SchemaContext.Output) &&
|
|
405
|
+
propertyUsage?.includes(SchemaContext.Output)) ||
|
|
406
|
+
(schemaUsage.includes(SchemaContext.Exception) &&
|
|
407
|
+
propertyUsage?.includes(SchemaContext.Exception))
|
|
408
|
+
);
|
|
409
|
+
}
|
|
@@ -11,12 +11,12 @@ import {
|
|
|
11
11
|
import * as path from "path";
|
|
12
12
|
import {
|
|
13
13
|
ImportKind,
|
|
14
|
+
ObjectSchema,
|
|
14
15
|
ParameterMetadata,
|
|
15
16
|
ParameterMetadatas,
|
|
16
17
|
RLCModel,
|
|
17
18
|
Schema
|
|
18
19
|
} from "./interfaces.js";
|
|
19
|
-
import { NameType, normalizeName } from "./helpers/nameUtils.js";
|
|
20
20
|
import {
|
|
21
21
|
getParameterBaseName,
|
|
22
22
|
getParameterTypeName
|
|
@@ -92,6 +92,11 @@ export function buildParameterTypes(model: RLCModel) {
|
|
|
92
92
|
i
|
|
93
93
|
);
|
|
94
94
|
|
|
95
|
+
const bodyTypeAlias = buildBodyTypeAlias(parameter);
|
|
96
|
+
if (bodyTypeAlias) {
|
|
97
|
+
parametersFile.addTypeAlias(bodyTypeAlias);
|
|
98
|
+
}
|
|
99
|
+
|
|
95
100
|
// Add interfaces for body and query parameters
|
|
96
101
|
parametersFile.addInterfaces([
|
|
97
102
|
...(bodyParameterDefinition ?? []),
|
|
@@ -215,7 +220,7 @@ function getPropertyFromSchema(schema: Schema): PropertySignatureStructure {
|
|
|
215
220
|
name: schema.name,
|
|
216
221
|
...(description && { docs: [{ description }] }),
|
|
217
222
|
type: schema.type,
|
|
218
|
-
hasQuestionToken: !
|
|
223
|
+
hasQuestionToken: !schema.required,
|
|
219
224
|
kind: StructureKind.PropertySignature
|
|
220
225
|
};
|
|
221
226
|
}
|
|
@@ -438,3 +443,49 @@ function buildBodyParametersDefinition(
|
|
|
438
443
|
];
|
|
439
444
|
}
|
|
440
445
|
}
|
|
446
|
+
|
|
447
|
+
export function buildBodyTypeAlias(parameters: ParameterMetadatas) {
|
|
448
|
+
const bodyParameters = parameters.body;
|
|
449
|
+
if (
|
|
450
|
+
!bodyParameters ||
|
|
451
|
+
!bodyParameters?.body ||
|
|
452
|
+
!bodyParameters?.body.length
|
|
453
|
+
) {
|
|
454
|
+
return undefined;
|
|
455
|
+
}
|
|
456
|
+
const schema = bodyParameters.body[0] as ObjectSchema;
|
|
457
|
+
const headerParameters = (parameters.parameters || []).filter(
|
|
458
|
+
(p) => p.type === "header" && p.name === "contentType"
|
|
459
|
+
);
|
|
460
|
+
if (!headerParameters.length || headerParameters.length > 1) {
|
|
461
|
+
return undefined;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
const contentType = headerParameters[0].param.type;
|
|
465
|
+
const readOnlyProperties = [];
|
|
466
|
+
if (schema.properties) {
|
|
467
|
+
for (const propertyName of Object.keys(schema.properties)) {
|
|
468
|
+
const prop = schema.properties[propertyName];
|
|
469
|
+
if (prop?.readOnly) {
|
|
470
|
+
if (propertyName.startsWith('"') && propertyName.endsWith('"')) {
|
|
471
|
+
readOnlyProperties.push(`${propertyName}`);
|
|
472
|
+
} else {
|
|
473
|
+
readOnlyProperties.push(`"${propertyName}"`);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
const description = `${schema.description}`;
|
|
480
|
+
const typeName = `${schema.typeName}ResourceMergeAndPatch`;
|
|
481
|
+
if (contentType.includes("application/merge-patch+json")) {
|
|
482
|
+
const type = `Partial<${schema.typeName}>`;
|
|
483
|
+
return {
|
|
484
|
+
// kind: StructureKind.TypeAlias,
|
|
485
|
+
...(description && { docs: [{ description }] }),
|
|
486
|
+
name: `${typeName}`,
|
|
487
|
+
type,
|
|
488
|
+
isExported: true
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { NameType, normalizeName } from "./helpers/nameUtils.js";
|
|
5
5
|
import { RLCModel } from "./interfaces.js";
|
|
6
6
|
import { Project } from "ts-morph";
|
|
7
|
+
import * as path from "path";
|
|
7
8
|
|
|
8
9
|
const batchOutputFolder: [string, string, string][] = [];
|
|
9
10
|
|
|
@@ -17,7 +18,7 @@ export function buildTopLevelIndex(model: RLCModel) {
|
|
|
17
18
|
if (srcPath) {
|
|
18
19
|
const clientName = model.libraryName;
|
|
19
20
|
const moduleName = normalizeName(clientName, NameType.File);
|
|
20
|
-
const relativePath = srcPath.
|
|
21
|
+
const relativePath = "." + srcPath.substring(srcPath.indexOf("/src/") + 4);
|
|
21
22
|
batchOutputFolder.push([relativePath, clientName, moduleName]);
|
|
22
23
|
}
|
|
23
24
|
if (
|
|
@@ -40,7 +41,8 @@ export function buildTopLevelIndex(model: RLCModel) {
|
|
|
40
41
|
indexFile.addExportDeclaration({
|
|
41
42
|
namedExports: [...allModules]
|
|
42
43
|
});
|
|
43
|
-
|
|
44
|
-
|
|
44
|
+
const content = indexFile.getFullText();
|
|
45
|
+
const filePath = path.join(srcPath.substring(0, srcPath.indexOf("/src/") + 5), `index.ts`);
|
|
46
|
+
return { path: filePath, content };
|
|
45
47
|
}
|
|
46
48
|
}
|
|
@@ -18,7 +18,7 @@ export function getResponseTypeName(
|
|
|
18
18
|
operationName?: string,
|
|
19
19
|
statusCode?: string
|
|
20
20
|
): string {
|
|
21
|
-
if (
|
|
21
|
+
if (operationName) {
|
|
22
22
|
baseNameOrOperationGroup = getResponseBaseName(
|
|
23
23
|
baseNameOrOperationGroup,
|
|
24
24
|
operationName!,
|
|
@@ -79,7 +79,7 @@ export function getParameterTypeName(
|
|
|
79
79
|
baseNameOrOperationGroup: string,
|
|
80
80
|
operationName?: string
|
|
81
81
|
) {
|
|
82
|
-
if (
|
|
82
|
+
if (operationName) {
|
|
83
83
|
baseNameOrOperationGroup = getParameterBaseName(
|
|
84
84
|
baseNameOrOperationGroup,
|
|
85
85
|
operationName!
|
package/src/helpers/nameUtils.ts
CHANGED
|
@@ -202,7 +202,7 @@ function toCasing(str: string, casing: CasingConvention): string {
|
|
|
202
202
|
}
|
|
203
203
|
|
|
204
204
|
function getNameParts(name: string) {
|
|
205
|
-
|
|
205
|
+
const parts = name.split(/[-._ ]+/);
|
|
206
206
|
|
|
207
207
|
return parts.length > 0 ? parts : [name];
|
|
208
208
|
}
|
|
@@ -26,7 +26,7 @@ export function buildMethodDefinitions(
|
|
|
26
26
|
|
|
27
27
|
for (const method of verbMethods) {
|
|
28
28
|
const description = method.description;
|
|
29
|
-
|
|
29
|
+
const areAllOptional = method.hasOptionalOptions;
|
|
30
30
|
|
|
31
31
|
methodDefinitions.push({
|
|
32
32
|
name: key,
|
|
@@ -13,7 +13,7 @@ import { NameType, normalizeName } from "./nameUtils.js";
|
|
|
13
13
|
export function generateMethodShortcuts(
|
|
14
14
|
paths: Paths
|
|
15
15
|
): OptionalKind<InterfaceDeclarationStructure>[] {
|
|
16
|
-
|
|
16
|
+
const keys: Record<string, OptionalKind<MethodSignatureStructure>[]> = {};
|
|
17
17
|
for (const path in paths) {
|
|
18
18
|
const groupName = paths[path].operationGroupName;
|
|
19
19
|
const definitions = buildOperationDefinitions(paths[path]);
|
package/src/interfaces.ts
CHANGED
|
@@ -6,7 +6,7 @@ export interface RLCModel {
|
|
|
6
6
|
paths: Paths;
|
|
7
7
|
options?: RLCOptions;
|
|
8
8
|
schemas: Schema[];
|
|
9
|
-
|
|
9
|
+
apiVersionInQueryParam?: Parameter;
|
|
10
10
|
parameters?: OperationParameter[];
|
|
11
11
|
responses?: OperationResponse[];
|
|
12
12
|
importSet?: Map<ImportKind, Set<string>>;
|
|
@@ -14,9 +14,15 @@ export interface RLCModel {
|
|
|
14
14
|
urlInfo?: UrlInfo;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
export interface PathTemplateApiVersion {
|
|
18
|
+
value: string;
|
|
19
|
+
templateName: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
17
22
|
export interface UrlInfo {
|
|
18
23
|
endpoint?: string;
|
|
19
24
|
urlParameters?: PathParameter[];
|
|
25
|
+
pathTemplateApiVersion?: PathTemplateApiVersion;
|
|
20
26
|
}
|
|
21
27
|
export interface AnnotationDetails {
|
|
22
28
|
hasPaging?: boolean;
|
|
@@ -63,6 +69,7 @@ export type PathParameter = {
|
|
|
63
69
|
name: string;
|
|
64
70
|
type: string;
|
|
65
71
|
description?: string;
|
|
72
|
+
value?: string | number | boolean;
|
|
66
73
|
};
|
|
67
74
|
|
|
68
75
|
export interface OperationAnnotations {
|
|
@@ -8,6 +8,8 @@ import {
|
|
|
8
8
|
} from "../helpers/operationHelpers.js";
|
|
9
9
|
import { RLCModel } from "../interfaces.js";
|
|
10
10
|
|
|
11
|
+
let hasPaging = false;
|
|
12
|
+
let hasLRO = false;
|
|
11
13
|
export function buildPackageFile(model: RLCModel, hasSamplesGenerated = false) {
|
|
12
14
|
const generateMetadata = Boolean(model.options?.generateMetadata);
|
|
13
15
|
if (!generateMetadata) {
|
|
@@ -57,9 +59,8 @@ function restLevelPackage(model: RLCModel, hasSamplesGenerated: boolean) {
|
|
|
57
59
|
if (packageDetails.version.includes("beta")) {
|
|
58
60
|
apiRefUrlQueryParameter = "?view=azure-node-preview";
|
|
59
61
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const hasLRO = hasPollingOperations(model);
|
|
62
|
+
hasPaging = hasPaging || hasPagingOperations(model);
|
|
63
|
+
hasLRO = hasLRO || hasPollingOperations(model);
|
|
63
64
|
const packageInfo: Record<string, any> = {
|
|
64
65
|
name: `${packageDetails.name}`,
|
|
65
66
|
"sdk-type": "client",
|
|
@@ -146,7 +147,7 @@ function restLevelPackage(model: RLCModel, hasSamplesGenerated: boolean) {
|
|
|
146
147
|
prettier: "2.2.1",
|
|
147
148
|
rimraf: "^3.0.0",
|
|
148
149
|
"source-map-support": "^0.5.9",
|
|
149
|
-
typescript: "~4.
|
|
150
|
+
typescript: "~4.8.0"
|
|
150
151
|
}
|
|
151
152
|
};
|
|
152
153
|
|
|
@@ -168,7 +168,7 @@ function createMetadata(model: RLCModel): Metadata | undefined {
|
|
|
168
168
|
const clientPackageName = packageDetails?.name;
|
|
169
169
|
const clientClassName = getClientName(model);
|
|
170
170
|
const serviceName = getServiceName(model);
|
|
171
|
-
|
|
171
|
+
let apiRefUrlQueryParameter: string = "";
|
|
172
172
|
if (packageDetails?.version.includes("beta")) {
|
|
173
173
|
apiRefUrlQueryParameter = "?view=azure-node-preview";
|
|
174
174
|
}
|
|
@@ -156,7 +156,7 @@ function checkPagingRequest(response: PathUncheckedResponse): void {
|
|
|
156
156
|
];
|
|
157
157
|
if (!Http2xxStatusCodes.includes(response.status)) {
|
|
158
158
|
throw createRestError(
|
|
159
|
-
\`Pagination failed with unexpected statusCode \${response.status
|
|
159
|
+
\`Pagination failed with unexpected statusCode \${response.status}\`,
|
|
160
160
|
response
|
|
161
161
|
);
|
|
162
162
|
}
|
package/src/test/template.ts
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
|
-
import { RLCModel } from "./interfaces.js";
|
|
1
|
+
import { ParameterMetadatas, RLCModel } from "./interfaces.js";
|
|
2
2
|
export declare function buildParameterTypes(model: RLCModel): {
|
|
3
3
|
path: string;
|
|
4
4
|
content: string;
|
|
5
5
|
} | undefined;
|
|
6
|
+
export declare function buildBodyTypeAlias(parameters: ParameterMetadatas): {
|
|
7
|
+
name: string;
|
|
8
|
+
type: string;
|
|
9
|
+
isExported: boolean;
|
|
10
|
+
docs?: {
|
|
11
|
+
description: string;
|
|
12
|
+
}[] | undefined;
|
|
13
|
+
} | undefined;
|
package/types/interfaces.d.ts
CHANGED
|
@@ -4,16 +4,21 @@ export interface RLCModel {
|
|
|
4
4
|
paths: Paths;
|
|
5
5
|
options?: RLCOptions;
|
|
6
6
|
schemas: Schema[];
|
|
7
|
-
|
|
7
|
+
apiVersionInQueryParam?: Parameter;
|
|
8
8
|
parameters?: OperationParameter[];
|
|
9
9
|
responses?: OperationResponse[];
|
|
10
10
|
importSet?: Map<ImportKind, Set<string>>;
|
|
11
11
|
annotations?: AnnotationDetails;
|
|
12
12
|
urlInfo?: UrlInfo;
|
|
13
13
|
}
|
|
14
|
+
export interface PathTemplateApiVersion {
|
|
15
|
+
value: string;
|
|
16
|
+
templateName: string;
|
|
17
|
+
}
|
|
14
18
|
export interface UrlInfo {
|
|
15
19
|
endpoint?: string;
|
|
16
20
|
urlParameters?: PathParameter[];
|
|
21
|
+
pathTemplateApiVersion?: PathTemplateApiVersion;
|
|
17
22
|
}
|
|
18
23
|
export interface AnnotationDetails {
|
|
19
24
|
hasPaging?: boolean;
|
|
@@ -54,6 +59,7 @@ export declare type PathParameter = {
|
|
|
54
59
|
name: string;
|
|
55
60
|
type: string;
|
|
56
61
|
description?: string;
|
|
62
|
+
value?: string | number | boolean;
|
|
57
63
|
};
|
|
58
64
|
export interface OperationAnnotations {
|
|
59
65
|
isLongRunning?: boolean;
|
package/types/test/template.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export declare const envContent = "\nimport * as dotenv from \"dotenv\";\n\ndotenv.config();\n";
|
|
2
2
|
export declare const envBrowserContent = "\nimport * as dotenv from \"dotenv\";\n\ndotenv.config();\n";
|
|
3
|
-
export declare const karmaConfig = "\n// https://github.com/karma-runner/karma-chrome-launcher\nprocess.env.CHROME_BIN = require(\"puppeteer\").executablePath();\nrequire(\"dotenv\").config();\nconst { relativeRecordingsPath } = require(\"@azure-tools/test-recorder\");\nprocess.env.RECORDINGS_RELATIVE_PATH = relativeRecordingsPath();\n\nmodule.exports = function (config) {\n config.set({\n // base path that will be used to resolve all patterns (eg. files, exclude)\n basePath: \"./\",\n\n // frameworks to use\n // available frameworks: https://npmjs.org/browse/keyword/karma-adapter\n frameworks: [\"source-map-support\", \"mocha\"],\n\n plugins: [\n \"karma-mocha\",\n \"karma-mocha-reporter\",\n \"karma-chrome-launcher\",\n \"karma-edge-launcher\",\n \"karma-firefox-launcher\",\n \"karma-ie-launcher\",\n \"karma-env-preprocessor\",\n \"karma-coverage\",\n \"karma-sourcemap-loader\",\n \"karma-junit-reporter\",\n \"karma-source-map-support\",\n ],\n\n // list of files / patterns to load in the browser\n files: [\n \"dist-test/index.browser.js\",\n { pattern: \"dist-test/index.browser.js.map\", type: \"html\", included: false, served: true },\n ],\n\n // list of files / patterns to exclude\n exclude: [],\n\n // preprocess matching files before serving them to the browser\n // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor\n preprocessors: {\n \"**/*.js\": [\"sourcemap\", \"env\"],\n // IMPORTANT: COMMENT following line if you want to debug in your browsers!!\n // Preprocess source file to calculate code coverage, however this will make source file unreadable\n // \"dist-test/index.js\": [\"coverage\"]\n },\n\n envPreprocessor: [\n \"TEST_MODE\",\n \"ENDPOINT\",\n \"AZURE_CLIENT_SECRET\",\n \"AZURE_CLIENT_ID\",\n \"AZURE_TENANT_ID\",\n \"SUBSCRIPTION_ID\",\n ],\n\n // test results reporter to use\n // possible values: 'dots', 'progress'\n // available reporters: https://npmjs.org/browse/keyword/karma-reporter\n reporters: [\"mocha\", \"coverage\", \"junit\"],\n\n coverageReporter: {\n // specify a common output directory\n dir: \"coverage-browser/\",\n reporters: [\n { type: \"json\", subdir: \".\", file: \"coverage.json\" },\n { type: \"lcovonly\", subdir: \".\", file: \"lcov.info\" },\n { type: \"html\", subdir: \"html\" },\n { type: \"cobertura\", subdir: \".\", file: \"cobertura-coverage.xml\" },\n ],\n },\n\n junitReporter: {\n outputDir: \"\", // results will be saved as $outputDir/$browserName.xml\n outputFile: \"test-results.browser.xml\", // if included, results will be saved as $outputDir/$browserName/$outputFile\n suite: \"\", // suite will become the package name attribute in xml testsuite element\n useBrowserName: false, // add browser name to report and classes names\n nameFormatter: undefined, // function (browser, result) to customize the name attribute in xml testcase element\n classNameFormatter: undefined, // function (browser, result) to customize the classname attribute in xml testcase element\n properties: {}, // key value pair of properties to add to the <properties> section of the report\n },\n\n // web server port\n port: 9876,\n\n // enable / disable colors in the output (reporters and logs)\n colors: true,\n\n // level of logging\n // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG\n logLevel: config.LOG_INFO,\n\n // enable / disable watching file and executing tests whenever any file changes\n autoWatch: false,\n\n // --no-sandbox allows our tests to run in Linux without having to change the system.\n // --disable-web-security allows us to authenticate from the browser without having to write tests using interactive auth, which would be far more complex.\n browsers: [\"ChromeHeadlessNoSandbox\"],\n customLaunchers: {\n ChromeHeadlessNoSandbox: {\n base: \"ChromeHeadless\",\n flags: [\"--no-sandbox\", \"--disable-web-security\"],\n },\n },\n\n // Continuous Integration mode\n // if true, Karma captures browsers, runs the tests and exits\n singleRun: false,\n\n // Concurrency level\n // how many browser should be started simultaneous\n concurrency: 1,\n\n browserNoActivityTimeout: 60000000,\n browserDisconnectTimeout: 10000,\n browserDisconnectTolerance: 3,\n\n client: {\n mocha: {\n // change Karma's debug.html to the mocha web reporter\n reporter: \"html\",\n timeout: \"600000\",\n },\n },\n });\n};\n";
|
|
3
|
+
export declare const karmaConfig = "\n// https://github.com/karma-runner/karma-chrome-launcher\nprocess.env.CHROME_BIN = require(\"puppeteer\").executablePath();\nrequire(\"dotenv\").config();\nconst { relativeRecordingsPath } = require(\"@azure-tools/test-recorder\");\nprocess.env.RECORDINGS_RELATIVE_PATH = relativeRecordingsPath();\n\nmodule.exports = function (config) {\n config.set({\n // base path that will be used to resolve all patterns (eg. files, exclude)\n basePath: \"./\",\n\n // frameworks to use\n // available frameworks: https://npmjs.org/browse/keyword/karma-adapter\n frameworks: [\"source-map-support\", \"mocha\"],\n\n plugins: [\n \"karma-mocha\",\n \"karma-mocha-reporter\",\n \"karma-chrome-launcher\",\n \"karma-edge-launcher\",\n \"karma-firefox-launcher\",\n \"karma-ie-launcher\",\n \"karma-env-preprocessor\",\n \"karma-coverage\",\n \"karma-sourcemap-loader\",\n \"karma-junit-reporter\",\n \"karma-source-map-support\",\n ],\n\n // list of files / patterns to load in the browser\n files: [\n \"dist-test/index.browser.js\",\n { pattern: \"dist-test/index.browser.js.map\", type: \"html\", included: false, served: true },\n ],\n\n // list of files / patterns to exclude\n exclude: [],\n\n // preprocess matching files before serving them to the browser\n // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor\n preprocessors: {\n \"**/*.js\": [\"sourcemap\", \"env\"],\n // IMPORTANT: COMMENT following line if you want to debug in your browsers!!\n // Preprocess source file to calculate code coverage, however this will make source file unreadable\n // \"dist-test/index.js\": [\"coverage\"]\n },\n\n envPreprocessor: [\n \"TEST_MODE\",\n \"ENDPOINT\",\n \"AZURE_CLIENT_SECRET\",\n \"AZURE_CLIENT_ID\",\n \"AZURE_TENANT_ID\",\n \"SUBSCRIPTION_ID\",\n \"RECORDINGS_RELATIVE_PATH\",\n ],\n\n // test results reporter to use\n // possible values: 'dots', 'progress'\n // available reporters: https://npmjs.org/browse/keyword/karma-reporter\n reporters: [\"mocha\", \"coverage\", \"junit\"],\n\n coverageReporter: {\n // specify a common output directory\n dir: \"coverage-browser/\",\n reporters: [\n { type: \"json\", subdir: \".\", file: \"coverage.json\" },\n { type: \"lcovonly\", subdir: \".\", file: \"lcov.info\" },\n { type: \"html\", subdir: \"html\" },\n { type: \"cobertura\", subdir: \".\", file: \"cobertura-coverage.xml\" },\n ],\n },\n\n junitReporter: {\n outputDir: \"\", // results will be saved as $outputDir/$browserName.xml\n outputFile: \"test-results.browser.xml\", // if included, results will be saved as $outputDir/$browserName/$outputFile\n suite: \"\", // suite will become the package name attribute in xml testsuite element\n useBrowserName: false, // add browser name to report and classes names\n nameFormatter: undefined, // function (browser, result) to customize the name attribute in xml testcase element\n classNameFormatter: undefined, // function (browser, result) to customize the classname attribute in xml testcase element\n properties: {}, // key value pair of properties to add to the <properties> section of the report\n },\n\n // web server port\n port: 9876,\n\n // enable / disable colors in the output (reporters and logs)\n colors: true,\n\n // level of logging\n // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG\n logLevel: config.LOG_INFO,\n\n // enable / disable watching file and executing tests whenever any file changes\n autoWatch: false,\n\n // --no-sandbox allows our tests to run in Linux without having to change the system.\n // --disable-web-security allows us to authenticate from the browser without having to write tests using interactive auth, which would be far more complex.\n browsers: [\"ChromeHeadlessNoSandbox\"],\n customLaunchers: {\n ChromeHeadlessNoSandbox: {\n base: \"ChromeHeadless\",\n flags: [\"--no-sandbox\", \"--disable-web-security\"],\n },\n },\n\n // Continuous Integration mode\n // if true, Karma captures browsers, runs the tests and exits\n singleRun: false,\n\n // Concurrency level\n // how many browser should be started simultaneous\n concurrency: 1,\n\n browserNoActivityTimeout: 60000000,\n browserDisconnectTimeout: 10000,\n browserDisconnectTolerance: 3,\n\n client: {\n mocha: {\n // change Karma's debug.html to the mocha web reporter\n reporter: \"html\",\n timeout: \"600000\",\n },\n },\n });\n};\n";
|
|
4
4
|
export declare const recordedClientContent = "\nimport { Context } from \"mocha\";\nimport { Recorder, RecorderStartOptions } from \"@azure-tools/test-recorder\";\nimport \"./env\";\n\nconst envSetupForPlayback: Record<string, string> = {\n ENDPOINT: \"https://endpoint\",\n AZURE_CLIENT_ID: \"azure_client_id\",\n AZURE_CLIENT_SECRET: \"azure_client_secret\",\n AZURE_TENANT_ID: \"88888888-8888-8888-8888-888888888888\",\n SUBSCRIPTION_ID: \"azure_subscription_id\"\n};\n\nconst recorderEnvSetup: RecorderStartOptions = {\n envSetupForPlayback\n};\n\n/**\n* creates the recorder and reads the environment variables from the `.env` file.\n* Should be called first in the test suite to make sure environment variables are\n* read before they are being used.\n*/\nexport async function createRecorder(context: Context): Promise<Recorder> {\n const recorder = new Recorder(context.currentTest);\n await recorder.start(recorderEnvSetup);\n return recorder;\n}\n";
|
|
5
5
|
export declare const sampleTestContent = "\nimport { Recorder } from \"@azure-tools/test-recorder\";\nimport { assert } from \"chai\";\nimport { createRecorder } from \"./utils/recordedClient\";\nimport { Context } from \"mocha\";\n\ndescribe(\"My test\", () => {\n let recorder: Recorder;\n\n beforeEach(async function(this: Context) {\n recorder = await createRecorder(this);\n });\n\n afterEach(async function() {\n await recorder.stop();\n });\n\n it(\"sample test\", async function() {\n assert.equal(1, 1);\n });\n});\n";
|