@azure-tools/typespec-providerhub-controller 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/src/generate.d.ts +13 -0
- package/dist/src/generate.d.ts.map +1 -0
- package/dist/src/generate.js +1592 -0
- package/dist/src/generate.js.map +1 -0
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +3 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/lib.d.ts +202 -0
- package/dist/src/lib.d.ts.map +1 -0
- package/dist/src/lib.js +143 -0
- package/dist/src/lib.js.map +1 -0
- package/dist/src/testing/index.d.ts +3 -0
- package/dist/src/testing/index.d.ts.map +1 -0
- package/dist/src/testing/index.js +29 -0
- package/dist/src/testing/index.js.map +1 -0
- package/package.json +74 -0
- package/readme.md +66 -0
- package/templates/liftr/closedEnum.sq +27 -0
- package/templates/liftr/model.sq +43 -0
- package/templates/liftr/openEnum.sq +34 -0
- package/templates/liftr/resourceControllerBase.sq +140 -0
- package/templates/liftr/serviceRoutingConstants.sq +35 -0
- package/templates/liftr/single/Operation.sq +40 -0
- package/templates/liftr/single/OperationAction.sq +31 -0
- package/templates/liftr/single/OperationControllerBase.sq +38 -0
- package/templates/liftr/single/OperationDisplay.sq +35 -0
- package/templates/liftr/single/OperationListResult.sq +17 -0
- package/templates/liftr/single/OperationOrigin.sq +31 -0
- package/templates/providerhub/closedEnum.sq +24 -0
- package/templates/providerhub/model.sq +103 -0
- package/templates/providerhub/modelCopy.sq +98 -0
- package/templates/providerhub/modelCopyExtension.sq +39 -0
- package/templates/providerhub/modelVersionComposite.sq +69 -0
- package/templates/providerhub/modelVersionStandard.sq +62 -0
- package/templates/providerhub/openEnum.sq +34 -0
- package/templates/providerhub/resourceController.sq +20 -0
- package/templates/providerhub/resourceControllerBase.Logger.sq +48 -0
- package/templates/providerhub/resourceControllerBase.sq +154 -0
- package/templates/providerhub/resourceProviderRegistration.sq +11 -0
- package/templates/providerhub/resourceRegistration.sq +27 -0
- package/templates/providerhub/serviceRoutingConstants.sq +38 -0
- package/templates/providerhub/single/OperationControllerBase.sq +52 -0
- package/templates/providerhub/versionCollection.sq +28 -0
- package/templates/providerhub/versionedContractResolver.sq +57 -0
- package/templates/providerhub/versionedSerializer.sq +38 -0
|
@@ -0,0 +1,1592 @@
|
|
|
1
|
+
import { getArmProviderNamespace, getArmResource, getArmResources, } from "@azure-tools/typespec-azure-resource-manager";
|
|
2
|
+
import { createProjectedNameProgram, getBaseFileName, getDirectoryPath, getDiscriminator, getDoc, getFormat, getFriendlyName, getKnownValues, getMaxLength, getMinLength, getNamespaceFullName, getPattern, ignoreDiagnostics, isNeverType, isTemplateDeclaration, joinPaths, listServices, NoTarget, resolvePath, } from "@typespec/compiler";
|
|
3
|
+
import { getQueryParamName, getRoutePath, isQueryParam, } from "@typespec/http";
|
|
4
|
+
import { getActionSegment, getParentResource } from "@typespec/rest";
|
|
5
|
+
import { getAddedOnVersions, getRemovedOnVersions, getVersionDependencies, resolveVersions, } from "@typespec/versioning";
|
|
6
|
+
import Handlebars from "handlebars";
|
|
7
|
+
import { fileURLToPath } from "url";
|
|
8
|
+
import { getTracer, reportDiagnostic } from "./lib.js";
|
|
9
|
+
export async function $onEmit(context) {
|
|
10
|
+
var _a;
|
|
11
|
+
const emitterOptions = context.options;
|
|
12
|
+
const rootPath = resolvePath(getDirectoryPath(fileURLToPath(import.meta.url)), "..");
|
|
13
|
+
const options = {
|
|
14
|
+
controllerOutputPath: context.emitterOutputDir,
|
|
15
|
+
controllerModulePath: rootPath,
|
|
16
|
+
controllerHost: (emitterOptions === null || emitterOptions === void 0 ? void 0 : emitterOptions["controller-host"]) || "providerhub",
|
|
17
|
+
operationPollingLocation: (emitterOptions === null || emitterOptions === void 0 ? void 0 : emitterOptions["operation-polling-location"]) || "tenant",
|
|
18
|
+
registrationOutputPath: emitterOptions === null || emitterOptions === void 0 ? void 0 : emitterOptions["registration-output-path"],
|
|
19
|
+
includeOperationController: (_a = emitterOptions === null || emitterOptions === void 0 ? void 0 : emitterOptions["include-operation-controller"]) !== null && _a !== void 0 ? _a : false,
|
|
20
|
+
generatedCodeKind: (emitterOptions === null || emitterOptions === void 0 ? void 0 : emitterOptions["code-kind"]) || "full",
|
|
21
|
+
};
|
|
22
|
+
const generator = createServiceCodeGenerator(context.program, options);
|
|
23
|
+
await (generator === null || generator === void 0 ? void 0 : generator.generateServiceCode(context.program.host));
|
|
24
|
+
}
|
|
25
|
+
function createServiceCodeGenerator(p, options) {
|
|
26
|
+
const tracer = getTracer(p);
|
|
27
|
+
const rootPath = options.controllerModulePath;
|
|
28
|
+
const { program } = createProjectedNameProgram(p, "csharp");
|
|
29
|
+
const jsonView = createProjectedNameProgram(p, "json");
|
|
30
|
+
const services = listServices(p).filter((x) => getArmProviderNamespace(program, x.type));
|
|
31
|
+
if (services.length === 0) {
|
|
32
|
+
reportDiagnostic(p, {
|
|
33
|
+
code: "no-provider-namespace",
|
|
34
|
+
target: NoTarget,
|
|
35
|
+
});
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const service = services[0];
|
|
39
|
+
const serviceRootNamespace = services[0].type;
|
|
40
|
+
const serviceNamespaceName = getNamespaceFullName(serviceRootNamespace);
|
|
41
|
+
if (!serviceNamespaceName || !serviceRootNamespace) {
|
|
42
|
+
return {
|
|
43
|
+
generateServiceCode() {
|
|
44
|
+
return Promise.resolve();
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
checkNoServiceDependencies(serviceRootNamespace);
|
|
49
|
+
const serviceName = getServiceName(serviceNamespaceName);
|
|
50
|
+
const serviceNamespace = "Microsoft." + serviceName;
|
|
51
|
+
const providerNamespace = getArmProviderNamespace(program, serviceRootNamespace);
|
|
52
|
+
const serviceVersions = getServiceVersions(service);
|
|
53
|
+
const PutName = "createOrUpdate", PatchName = "update", DeleteName = "delete", GetName = "read";
|
|
54
|
+
const csharpTypeCache = new Map();
|
|
55
|
+
tracer.trace("info", ["Generation info:", ` - Service name: ${serviceName}`, ` - Rootpath: ${rootPath}`].join("\n"));
|
|
56
|
+
function getFullyQualifiedResourceType(resource) {
|
|
57
|
+
const segments = [];
|
|
58
|
+
let r = resource;
|
|
59
|
+
while (r) {
|
|
60
|
+
segments.push(r.resourceTypeName);
|
|
61
|
+
r = r.parent;
|
|
62
|
+
}
|
|
63
|
+
segments.push(resource.nameSpace);
|
|
64
|
+
return segments.reverse().join("/");
|
|
65
|
+
}
|
|
66
|
+
const outputModel = {
|
|
67
|
+
nameSpace: serviceNamespace,
|
|
68
|
+
providerNamespace: providerNamespace,
|
|
69
|
+
serviceName: serviceName,
|
|
70
|
+
resources: [],
|
|
71
|
+
models: [],
|
|
72
|
+
enumerations: [],
|
|
73
|
+
versions: serviceVersions,
|
|
74
|
+
includeOperationController: options.includeOperationController,
|
|
75
|
+
};
|
|
76
|
+
return { generateServiceCode };
|
|
77
|
+
// for now, ensure that versioned dependencies are not in the service namespace
|
|
78
|
+
function checkNoServiceDependencies(namespace) {
|
|
79
|
+
function isVersionMap(checkType) {
|
|
80
|
+
return checkType.delete !== undefined;
|
|
81
|
+
}
|
|
82
|
+
const deps = getVersionDependencies(program, namespace);
|
|
83
|
+
if (deps && deps.size > 0) {
|
|
84
|
+
for (const [_, value] of deps) {
|
|
85
|
+
if (isVersionMap(value) && value.size > 1) {
|
|
86
|
+
const versions = new Set();
|
|
87
|
+
for (const [_, targetVersion] of value) {
|
|
88
|
+
versions.add(targetVersion);
|
|
89
|
+
}
|
|
90
|
+
if (versions.size > 1) {
|
|
91
|
+
reportDiagnostic(program, {
|
|
92
|
+
code: "no-versioned-dependencies",
|
|
93
|
+
target: namespace,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// get the versions defined on the root namespace
|
|
101
|
+
function getServiceVersions(service) {
|
|
102
|
+
var _a;
|
|
103
|
+
const versions = [];
|
|
104
|
+
const records = resolveVersions(program, service.type);
|
|
105
|
+
if (records) {
|
|
106
|
+
for (const version of records) {
|
|
107
|
+
if (version.rootVersion !== undefined)
|
|
108
|
+
versions.push(version.rootVersion.value);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (versions.length < 1)
|
|
112
|
+
versions.push((_a = service.version) !== null && _a !== void 0 ? _a : "0000-00-00");
|
|
113
|
+
return versions;
|
|
114
|
+
}
|
|
115
|
+
function reportProgress(message) {
|
|
116
|
+
tracer.trace("progress", message);
|
|
117
|
+
}
|
|
118
|
+
function getServiceName(serviceNamespace) {
|
|
119
|
+
const dotPos = serviceNamespace.indexOf(".");
|
|
120
|
+
return serviceNamespace.substring(dotPos + 1).replace(/\./g, "");
|
|
121
|
+
}
|
|
122
|
+
async function generateServiceCode(host) {
|
|
123
|
+
var _a;
|
|
124
|
+
const genPath = options.controllerOutputPath;
|
|
125
|
+
const registrationOutputPath = options.registrationOutputPath;
|
|
126
|
+
// maps resource model name to arm Namespace
|
|
127
|
+
const resourceNamespaceTable = new Map();
|
|
128
|
+
// create child model map
|
|
129
|
+
const modelDiscriminatorInfoMap = new Map();
|
|
130
|
+
function transformPathParameter(parameter, typespecType) {
|
|
131
|
+
return {
|
|
132
|
+
name: parameter.name,
|
|
133
|
+
serializedName: jsonView.getProjectedName(parameter.param),
|
|
134
|
+
type: "string",
|
|
135
|
+
description: getDoc(program, parameter.param),
|
|
136
|
+
sourceNode: typespecType.node,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
const modelsToGenerate = new Map();
|
|
140
|
+
const resources = new Map();
|
|
141
|
+
function populateResources() {
|
|
142
|
+
var _a;
|
|
143
|
+
function getStandardOperation(operation, modelName, sourceType) {
|
|
144
|
+
const pathParams = operation.httpOperation.parameters.parameters
|
|
145
|
+
.filter((p) => p.type === "path")
|
|
146
|
+
.map((p) => transformPathParameter(p, sourceType));
|
|
147
|
+
const operationName = transformCSharpIdentifier(operation.name);
|
|
148
|
+
switch (operation.kind) {
|
|
149
|
+
case GetName:
|
|
150
|
+
return {
|
|
151
|
+
name: operationName,
|
|
152
|
+
kind: operation.kind,
|
|
153
|
+
parameters: pathParams,
|
|
154
|
+
returnType: modelName,
|
|
155
|
+
verb: "GET",
|
|
156
|
+
sourceNode: sourceType.node,
|
|
157
|
+
};
|
|
158
|
+
case PutName:
|
|
159
|
+
return {
|
|
160
|
+
name: operationName,
|
|
161
|
+
kind: operation.kind,
|
|
162
|
+
parameters: [
|
|
163
|
+
...pathParams,
|
|
164
|
+
{
|
|
165
|
+
name: "body",
|
|
166
|
+
location: "body",
|
|
167
|
+
description: "The resource data.",
|
|
168
|
+
type: modelName,
|
|
169
|
+
sourceNode: sourceType.node,
|
|
170
|
+
},
|
|
171
|
+
],
|
|
172
|
+
returnType: modelName,
|
|
173
|
+
verb: "PUT",
|
|
174
|
+
requestParameter: {
|
|
175
|
+
name: "body",
|
|
176
|
+
location: "body",
|
|
177
|
+
description: "The resource data.",
|
|
178
|
+
type: modelName,
|
|
179
|
+
},
|
|
180
|
+
sourceNode: sourceType.node,
|
|
181
|
+
};
|
|
182
|
+
case DeleteName:
|
|
183
|
+
return {
|
|
184
|
+
name: operationName,
|
|
185
|
+
kind: operation.kind,
|
|
186
|
+
parameters: pathParams,
|
|
187
|
+
returnType: "void",
|
|
188
|
+
verb: "Delete",
|
|
189
|
+
sourceNode: sourceType.node,
|
|
190
|
+
};
|
|
191
|
+
case PatchName:
|
|
192
|
+
return {
|
|
193
|
+
name: operationName,
|
|
194
|
+
kind: operation.kind,
|
|
195
|
+
parameters: [
|
|
196
|
+
...pathParams,
|
|
197
|
+
{
|
|
198
|
+
name: "body",
|
|
199
|
+
location: "body",
|
|
200
|
+
description: "The resource patch data.",
|
|
201
|
+
type: `${modelName}Update`,
|
|
202
|
+
sourceNode: sourceType.node,
|
|
203
|
+
},
|
|
204
|
+
],
|
|
205
|
+
returnType: modelName,
|
|
206
|
+
verb: "PATCH",
|
|
207
|
+
requestParameter: {
|
|
208
|
+
name: "body",
|
|
209
|
+
location: "body",
|
|
210
|
+
description: "The resource patch data.",
|
|
211
|
+
type: `${modelName}Update`,
|
|
212
|
+
},
|
|
213
|
+
sourceNode: sourceType.node,
|
|
214
|
+
};
|
|
215
|
+
default:
|
|
216
|
+
return undefined;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
const visitedTypes = new Set();
|
|
220
|
+
const visitedOperations = new Map();
|
|
221
|
+
function visitOperation(operation, httpOperation, resource) {
|
|
222
|
+
var _a, _b, _c, _d, _e, _f;
|
|
223
|
+
const operationType = operation.operation;
|
|
224
|
+
const operationKey = httpOperation.container.name + "." + operationType.name;
|
|
225
|
+
let bodyProp = undefined;
|
|
226
|
+
if (!visitedOperations.has(operationKey)) {
|
|
227
|
+
visitedOperations.set(operationKey, operationType);
|
|
228
|
+
const returnType = extractResponseType(operationType);
|
|
229
|
+
if (returnType) {
|
|
230
|
+
visitType(returnType);
|
|
231
|
+
}
|
|
232
|
+
const parameters = [];
|
|
233
|
+
httpOperation.parameters.parameters.forEach((httpParam) => {
|
|
234
|
+
const prop = httpParam.param;
|
|
235
|
+
if (isQueryParam(program, prop) && getQueryParamName(program, prop) === "api-version") {
|
|
236
|
+
// skip standard api-version parameter
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
const propType = getCSharpType(prop.type);
|
|
240
|
+
if (propType) {
|
|
241
|
+
visitType(prop.type);
|
|
242
|
+
const paramDescription = getDoc(program, prop);
|
|
243
|
+
ensureCSharpIdentifier(prop, prop.name);
|
|
244
|
+
parameters.push({
|
|
245
|
+
name: prop.name,
|
|
246
|
+
serializedName: jsonView.getProjectedName(prop),
|
|
247
|
+
type: propType.name,
|
|
248
|
+
description: paramDescription,
|
|
249
|
+
location: httpParam.type,
|
|
250
|
+
sourceNode: prop.node,
|
|
251
|
+
default: prop.default && formatDefaultValue(prop.type, prop.default),
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
if (httpOperation.parameters.bodyType) {
|
|
256
|
+
const bodyParam = httpOperation.parameters.bodyParameter;
|
|
257
|
+
const bodyType = getCSharpType(httpOperation.parameters.bodyType);
|
|
258
|
+
const paramDescription = bodyParam ? getDoc(program, bodyParam) : undefined;
|
|
259
|
+
visitType(httpOperation.parameters.bodyType);
|
|
260
|
+
if (bodyParam) {
|
|
261
|
+
ensureCSharpIdentifier(bodyParam, bodyParam.name);
|
|
262
|
+
}
|
|
263
|
+
if (bodyType) {
|
|
264
|
+
bodyProp = {
|
|
265
|
+
name: (_a = bodyParam === null || bodyParam === void 0 ? void 0 : bodyParam.name) !== null && _a !== void 0 ? _a : "body",
|
|
266
|
+
type: bodyType.name,
|
|
267
|
+
description: paramDescription,
|
|
268
|
+
location: "body",
|
|
269
|
+
sourceNode: bodyParam === null || bodyParam === void 0 ? void 0 : bodyParam.node,
|
|
270
|
+
};
|
|
271
|
+
parameters.push(bodyProp);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
ensureCSharpIdentifier(operationType, operationType.name);
|
|
275
|
+
const outOperation = {
|
|
276
|
+
name: transformCSharpIdentifier(operationType.name),
|
|
277
|
+
kind: operation.kind,
|
|
278
|
+
returnType: (_b = returnType === null || returnType === void 0 ? void 0 : returnType.name) !== null && _b !== void 0 ? _b : "void",
|
|
279
|
+
parameters: parameters,
|
|
280
|
+
subPath: (_c = getActionSegment(program, operation.operation)) !== null && _c !== void 0 ? _c : (_d = getRoutePath(program, operation.operation)) === null || _d === void 0 ? void 0 : _d.path,
|
|
281
|
+
verb: httpOperation.verb,
|
|
282
|
+
sourceNode: operationType.node,
|
|
283
|
+
};
|
|
284
|
+
if (bodyProp !== undefined) {
|
|
285
|
+
outOperation.requestParameter = bodyProp;
|
|
286
|
+
}
|
|
287
|
+
// use the default path for actions
|
|
288
|
+
if (outOperation.kind === "action" && !outOperation.subPath) {
|
|
289
|
+
outOperation.subPath = outOperation.name;
|
|
290
|
+
}
|
|
291
|
+
let exists = false;
|
|
292
|
+
if (resource) {
|
|
293
|
+
exists = (_f = (_e = resource.operations) === null || _e === void 0 ? void 0 : _e.some((op) => op.name === outOperation.name)) !== null && _f !== void 0 ? _f : false;
|
|
294
|
+
}
|
|
295
|
+
if (resource && !exists) {
|
|
296
|
+
resource.operations.push(outOperation);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
function visitModel(model) {
|
|
301
|
+
function tryAddModel(model) {
|
|
302
|
+
const modelKey = getFriendlyName(program, model) || model.name;
|
|
303
|
+
if (!modelsToGenerate.has(modelKey) && !getKnownType(model)) {
|
|
304
|
+
modelsToGenerate.set(modelKey, model);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
function addModelDiscriminatorInfoToMap(model, baseType, fieldName, discriminatorValue) {
|
|
308
|
+
const baseDiscriminatorInfo = {
|
|
309
|
+
baseType: baseType,
|
|
310
|
+
propertyName: fieldName,
|
|
311
|
+
value: discriminatorValue,
|
|
312
|
+
};
|
|
313
|
+
if (!modelDiscriminatorInfoMap.has(model)) {
|
|
314
|
+
modelDiscriminatorInfoMap.set(model, baseDiscriminatorInfo);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
tryAddModel(model);
|
|
318
|
+
// create discriminatorInfo if has @discriminator
|
|
319
|
+
const discriminator = getDiscriminator(program, model);
|
|
320
|
+
if (discriminator) {
|
|
321
|
+
const baseType = getCSharpType(model);
|
|
322
|
+
addModelDiscriminatorInfoToMap(model, baseType, discriminator.propertyName, "");
|
|
323
|
+
// add all children to be generated
|
|
324
|
+
for (const child of model.derivedModels.filter((x) => !isTemplateDeclaration(x))) {
|
|
325
|
+
const childFieldType = child.properties.get(discriminator.propertyName).type;
|
|
326
|
+
if (childFieldType.kind === "String") {
|
|
327
|
+
tryAddModel(child);
|
|
328
|
+
addModelDiscriminatorInfoToMap(child, baseType, discriminator.propertyName, childFieldType.value.toString());
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
function extractResponseType(operation) {
|
|
334
|
+
const model = operation.returnType;
|
|
335
|
+
if (model.kind === "Union") {
|
|
336
|
+
let outModel = undefined;
|
|
337
|
+
model === null || model === void 0 ? void 0 : model.options.forEach((option) => {
|
|
338
|
+
if (option.kind === "Model" &&
|
|
339
|
+
option.name === "ArmResponse" &&
|
|
340
|
+
option.templateArguments) {
|
|
341
|
+
const innerModel = option.templateArguments[0];
|
|
342
|
+
if (innerModel && innerModel.kind === "Model") {
|
|
343
|
+
outModel = innerModel;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
else if (option.kind === "Model" && option.name !== "ErrorResponse") {
|
|
347
|
+
outModel = option;
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
if (outModel === undefined) {
|
|
351
|
+
reportDiagnostic(program, {
|
|
352
|
+
code: "invalid-response",
|
|
353
|
+
format: { operationName: operation ? operation.name : "<unknown>" },
|
|
354
|
+
target: operation,
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
return outModel;
|
|
358
|
+
}
|
|
359
|
+
return model.kind === "Model" ? model : undefined;
|
|
360
|
+
}
|
|
361
|
+
function visitType(typespecType) {
|
|
362
|
+
var _a, _b;
|
|
363
|
+
if (!visitedTypes.has(typespecType)) {
|
|
364
|
+
visitedTypes.add(typespecType);
|
|
365
|
+
switch (typespecType.kind) {
|
|
366
|
+
case "Tuple":
|
|
367
|
+
typespecType.values.forEach((element) => {
|
|
368
|
+
visitType(element);
|
|
369
|
+
});
|
|
370
|
+
break;
|
|
371
|
+
case "TemplateParameter":
|
|
372
|
+
(_a = typespecType.instantiationParameters) === null || _a === void 0 ? void 0 : _a.forEach((element) => {
|
|
373
|
+
visitType(element);
|
|
374
|
+
});
|
|
375
|
+
break;
|
|
376
|
+
case "Union":
|
|
377
|
+
typespecType.options.forEach((element) => {
|
|
378
|
+
visitType(element);
|
|
379
|
+
});
|
|
380
|
+
break;
|
|
381
|
+
case "ModelProperty":
|
|
382
|
+
visitType(typespecType.type);
|
|
383
|
+
break;
|
|
384
|
+
case "Model":
|
|
385
|
+
if (typespecType.baseModel) {
|
|
386
|
+
visitType(typespecType.baseModel);
|
|
387
|
+
}
|
|
388
|
+
(_b = typespecType.templateArguments) === null || _b === void 0 ? void 0 : _b.forEach((element) => {
|
|
389
|
+
visitType(element);
|
|
390
|
+
});
|
|
391
|
+
// A type with a friendly name should be treated as a unique type
|
|
392
|
+
if (getFriendlyName(program, typespecType) || !getKnownType(typespecType)) {
|
|
393
|
+
typespecType.properties.forEach((element) => {
|
|
394
|
+
visitType(element);
|
|
395
|
+
});
|
|
396
|
+
visitModel(typespecType);
|
|
397
|
+
}
|
|
398
|
+
break;
|
|
399
|
+
default:
|
|
400
|
+
// do nothing
|
|
401
|
+
break;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
for (const resourceDetails of getArmResources(program)) {
|
|
406
|
+
if (!resources.has(resourceDetails.name)) {
|
|
407
|
+
const modelName = resourceDetails.name;
|
|
408
|
+
const listName = `${modelName}ListResult`;
|
|
409
|
+
const matchingNamespace = resourceDetails.armProviderNamespace;
|
|
410
|
+
const cSharpModelName = transformCSharpIdentifier(modelName);
|
|
411
|
+
resourceNamespaceTable.set(modelName, resourceDetails.armProviderNamespace);
|
|
412
|
+
const standardOps = [
|
|
413
|
+
"read",
|
|
414
|
+
"createOrUpdate",
|
|
415
|
+
"update",
|
|
416
|
+
"delete",
|
|
417
|
+
];
|
|
418
|
+
const map = new Map();
|
|
419
|
+
standardOps.forEach((op) => {
|
|
420
|
+
const armOperation = resourceDetails.operations.lifecycle[op];
|
|
421
|
+
if (armOperation) {
|
|
422
|
+
const value = getStandardOperation(armOperation, cSharpModelName, resourceDetails.typespecType);
|
|
423
|
+
if (value && !map.has(value.name)) {
|
|
424
|
+
map.set(value.name, value);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
});
|
|
428
|
+
if (map.size < 1) {
|
|
429
|
+
reportDiagnostic(program, {
|
|
430
|
+
code: "no-resource-operations",
|
|
431
|
+
format: { resource: resourceDetails.name },
|
|
432
|
+
target: resourceDetails.typespecType,
|
|
433
|
+
});
|
|
434
|
+
continue;
|
|
435
|
+
}
|
|
436
|
+
if (resourceDetails.operations.lifecycle.read === undefined &&
|
|
437
|
+
resourceDetails.operations.lifecycle.createOrUpdate === undefined) {
|
|
438
|
+
reportDiagnostic(program, {
|
|
439
|
+
code: "no-resource-read-or-create-operation",
|
|
440
|
+
format: { resource: resourceDetails.name },
|
|
441
|
+
target: resourceDetails.typespecType,
|
|
442
|
+
});
|
|
443
|
+
continue;
|
|
444
|
+
}
|
|
445
|
+
const parentResourceModel = getParentResource(program, resourceDetails.typespecType);
|
|
446
|
+
const parentResource = parentResourceModel
|
|
447
|
+
? getArmResource(program, parentResourceModel)
|
|
448
|
+
: undefined;
|
|
449
|
+
const outResource = {
|
|
450
|
+
hasResourceGroupList: resourceDetails.operations.lists["ListByResourceGroup"] !== undefined,
|
|
451
|
+
hasSubscriptionList: resourceDetails.operations.lists["ListBySubscription"] !== undefined,
|
|
452
|
+
serviceName: serviceName,
|
|
453
|
+
// TODO: This is currently a problem! We don't have a canonical path
|
|
454
|
+
itemPath: (resourceDetails.operations.lifecycle.read ||
|
|
455
|
+
resourceDetails.operations.lifecycle.createOrUpdate).path,
|
|
456
|
+
name: resourceDetails.name,
|
|
457
|
+
resourceTypeName: resourceDetails.collectionName,
|
|
458
|
+
parent: parentResource ? resources.get(parentResource.name) : undefined,
|
|
459
|
+
nameSpace: serviceNamespace,
|
|
460
|
+
nameParameter: (_a = resourceDetails.keyName) !== null && _a !== void 0 ? _a : "name",
|
|
461
|
+
serializedName: resourceDetails.collectionName,
|
|
462
|
+
operations: [...map.values()],
|
|
463
|
+
specificationArmProviderNamespace: matchingNamespace,
|
|
464
|
+
specificationModelName: transformCSharpIdentifier(modelName),
|
|
465
|
+
specificationListModelName: transformCSharpIdentifier(listName),
|
|
466
|
+
sourceNode: resourceDetails.typespecType.node,
|
|
467
|
+
};
|
|
468
|
+
// Loop through the lifecycle operations again to visit their types
|
|
469
|
+
// now that we have the outResource
|
|
470
|
+
for (const lifecycleOp of Object.values(resourceDetails.operations.lifecycle)) {
|
|
471
|
+
visitOperation(lifecycleOp, lifecycleOp.httpOperation, outResource);
|
|
472
|
+
}
|
|
473
|
+
// Visit all action operations
|
|
474
|
+
for (const actionOpName in resourceDetails.operations.actions) {
|
|
475
|
+
const actionOp = resourceDetails.operations.actions[actionOpName];
|
|
476
|
+
visitOperation(actionOp, actionOp.httpOperation, outResource);
|
|
477
|
+
}
|
|
478
|
+
// NOTE: We explicitly skip visiting list operations because the
|
|
479
|
+
// MetaRP will be providing the behavior for listing all resource
|
|
480
|
+
// types.
|
|
481
|
+
resources.set(modelName, outResource);
|
|
482
|
+
outputModel.resources.push(outResource);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
function populateModels() {
|
|
487
|
+
const models = new Map();
|
|
488
|
+
function populateModel(typespecType) {
|
|
489
|
+
var _a, _b, _c, _d, _e, _f;
|
|
490
|
+
if (typespecType.kind === "Model") {
|
|
491
|
+
const friendlyName = getFriendlyName(program, typespecType);
|
|
492
|
+
const typeRef = getCSharpType(typespecType);
|
|
493
|
+
tracer.trace("type-mapping", `*** ${typespecType.name} => ${typeRef === null || typeRef === void 0 ? void 0 : typeRef.name}`, typespecType.node);
|
|
494
|
+
if (typeRef) {
|
|
495
|
+
const outModel = {
|
|
496
|
+
name: (_a = friendlyName !== null && friendlyName !== void 0 ? friendlyName : typeRef === null || typeRef === void 0 ? void 0 : typeRef.name) !== null && _a !== void 0 ? _a : typespecType.name,
|
|
497
|
+
nameSpace: (_b = typeRef === null || typeRef === void 0 ? void 0 : typeRef.nameSpace) !== null && _b !== void 0 ? _b : serviceNamespace,
|
|
498
|
+
properties: [],
|
|
499
|
+
description: getDoc(program, typespecType),
|
|
500
|
+
serviceName: serviceName,
|
|
501
|
+
typeParameters: ((_c = typespecType.templateMapper) === null || _c === void 0 ? void 0 : _c.args)
|
|
502
|
+
? typespecType.templateMapper.args.map((arg) => getCSharpType(arg))
|
|
503
|
+
: [],
|
|
504
|
+
isDerivedType: false,
|
|
505
|
+
isImplementer: false,
|
|
506
|
+
isBuiltIn: (_d = typeRef === null || typeRef === void 0 ? void 0 : typeRef.isBuiltIn) !== null && _d !== void 0 ? _d : false,
|
|
507
|
+
validations: getValidations(typespecType),
|
|
508
|
+
converters: getConverters(typespecType),
|
|
509
|
+
sourceNode: typespecType.node,
|
|
510
|
+
discriminatorInfo: modelDiscriminatorInfoMap.get(typespecType),
|
|
511
|
+
isVersioned: false,
|
|
512
|
+
isSerialized: false,
|
|
513
|
+
hasVersionedAncestor: false,
|
|
514
|
+
};
|
|
515
|
+
// The model type may need to derive from another type if:
|
|
516
|
+
// - It explicitly has a base model type
|
|
517
|
+
// - It is an instantiation of a templated type without a friendly name
|
|
518
|
+
// (the friendly name is used to rename the type without derivation)
|
|
519
|
+
if (typespecType.baseModel ||
|
|
520
|
+
(!friendlyName &&
|
|
521
|
+
typespecType.templateMapper &&
|
|
522
|
+
typespecType.templateMapper.args &&
|
|
523
|
+
typespecType.templateMapper.args.length > 0)) {
|
|
524
|
+
outModel.isDerivedType = true;
|
|
525
|
+
const baseType = [];
|
|
526
|
+
if (typespecType.baseModel) {
|
|
527
|
+
const converted = getCSharpType(typespecType.baseModel);
|
|
528
|
+
if (converted) {
|
|
529
|
+
baseType.push(converted);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
if (typespecType.templateNode) {
|
|
533
|
+
const templateBase = program.checker.getTypeForNode(typespecType.templateNode);
|
|
534
|
+
if (templateBase && templateBase.kind === "Model") {
|
|
535
|
+
const converted = getCSharpType(templateBase);
|
|
536
|
+
if (converted) {
|
|
537
|
+
baseType.push(converted);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
outModel.baseClass = baseType.length > 0 ? baseType[0] : undefined;
|
|
542
|
+
}
|
|
543
|
+
if (typespecType.properties && typespecType.properties.size > 0) {
|
|
544
|
+
// Filter out unwanted properties from the resource model type:
|
|
545
|
+
// - name: Use the name property defined in the base ArmResource type instead
|
|
546
|
+
// - systemData
|
|
547
|
+
(_f = (_e = [...typespecType.properties.values()]) === null || _e === void 0 ? void 0 : _e.filter((prop) => prop.name !== "systemData" &&
|
|
548
|
+
!(prop.name === "name" && getArmResource(program, typespecType)))) === null || _f === void 0 ? void 0 : _f.forEach((val) => {
|
|
549
|
+
const decl = getPropertyDecl(val, typespecType);
|
|
550
|
+
if (decl) {
|
|
551
|
+
outModel.properties.push(decl);
|
|
552
|
+
if (decl.versions.length > 0) {
|
|
553
|
+
outModel.isVersioned = true;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
if (!outModel.isBuiltIn) {
|
|
559
|
+
models.set(outModel.name, outModel);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
function checkModelAncestors(model) {
|
|
565
|
+
var _a, _b;
|
|
566
|
+
const ancestors = new Map();
|
|
567
|
+
function isVersioned(model) {
|
|
568
|
+
if (ancestors.has(model.name))
|
|
569
|
+
return false;
|
|
570
|
+
if (model.isVersioned || model.hasVersionedAncestor)
|
|
571
|
+
return true;
|
|
572
|
+
if (model.baseClass) {
|
|
573
|
+
const baseModel = models.get(model.baseClass.name);
|
|
574
|
+
if (baseModel) {
|
|
575
|
+
ancestors.set(baseModel.name, baseModel);
|
|
576
|
+
return isVersioned(baseModel);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
return false;
|
|
580
|
+
}
|
|
581
|
+
let versioned = false;
|
|
582
|
+
if (model.baseClass) {
|
|
583
|
+
const baseModel = models.get(model.baseClass.name);
|
|
584
|
+
if (baseModel) {
|
|
585
|
+
versioned = isVersioned(baseModel);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
model.hasVersionedAncestor = versioned;
|
|
589
|
+
if (model.isVersioned && !model.hasVersionedAncestor) {
|
|
590
|
+
model.isImplementer = true;
|
|
591
|
+
model.implements = (_a = model.implements) !== null && _a !== void 0 ? _a : [];
|
|
592
|
+
model.implements.push({
|
|
593
|
+
name: "IVersionedResource",
|
|
594
|
+
nameSpace: "Microsoft.TypeSpec.ProviderHub.Controller",
|
|
595
|
+
isBuiltIn: true,
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
if (!model.baseClass) {
|
|
599
|
+
model.isSerialized = true;
|
|
600
|
+
if (!model.isVersioned) {
|
|
601
|
+
model.isImplementer = true;
|
|
602
|
+
model.implements = (_b = model.implements) !== null && _b !== void 0 ? _b : [];
|
|
603
|
+
model.implements.push({
|
|
604
|
+
name: "ISerializationTracker",
|
|
605
|
+
nameSpace: "Microsoft.TypeSpec.ProviderHub.Controller",
|
|
606
|
+
isBuiltIn: true,
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
modelsToGenerate.forEach((r) => {
|
|
612
|
+
if (!models.has(r.name)) {
|
|
613
|
+
populateModel(r);
|
|
614
|
+
}
|
|
615
|
+
});
|
|
616
|
+
models.forEach((model) => {
|
|
617
|
+
checkModelAncestors(model);
|
|
618
|
+
outputModel.models.push(model);
|
|
619
|
+
});
|
|
620
|
+
program.trace("providerhub-controller.generate", ["Models", "------", JSON.stringify(models, replacer)].join("\n"));
|
|
621
|
+
program.trace("providerhub-controller.generate", ["Enums", "------", JSON.stringify(outputModel.enumerations, replacer)].join("\n"));
|
|
622
|
+
}
|
|
623
|
+
function replacer(key, value) {
|
|
624
|
+
if (key === "sourceNode") {
|
|
625
|
+
return "<redacted>";
|
|
626
|
+
}
|
|
627
|
+
if (value instanceof Map) {
|
|
628
|
+
return {
|
|
629
|
+
dataType: "Map",
|
|
630
|
+
value: Array.from(value.entries()), // or with spread: value: [...value]
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
else {
|
|
634
|
+
return value;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
getArmResources(program).forEach((resource) => {
|
|
638
|
+
const cType = getCSharpType(resource.typespecType);
|
|
639
|
+
const resourceMeta = getArmResource(program, resource.typespecType);
|
|
640
|
+
tracer.trace("resource", [
|
|
641
|
+
"ARM RESOURCE DETAILS:",
|
|
642
|
+
"--------------------",
|
|
643
|
+
" armProviderNamespace: " + resourceMeta.armProviderNamespace,
|
|
644
|
+
" resourceModelName: " + resourceMeta.name,
|
|
645
|
+
" resourceKind: " + resourceMeta.kind,
|
|
646
|
+
" collectionName: " + resourceMeta.collectionName,
|
|
647
|
+
" operations: " + resourceMeta.operations,
|
|
648
|
+
" resourceNameParam: " + resourceMeta.keyName,
|
|
649
|
+
`-- ${resourceMeta.name} => ${cType === null || cType === void 0 ? void 0 : cType.nameSpace}.${cType === null || cType === void 0 ? void 0 : cType.name}`,
|
|
650
|
+
].join("\n"));
|
|
651
|
+
});
|
|
652
|
+
populateResources();
|
|
653
|
+
populateModels();
|
|
654
|
+
function getPropertyDecl(property, parent) {
|
|
655
|
+
var _a;
|
|
656
|
+
// check for potential template instantiations or other properties defined inline
|
|
657
|
+
switch (property.type.kind) {
|
|
658
|
+
case "Model":
|
|
659
|
+
if (property.type.indexer === undefined &&
|
|
660
|
+
getFriendlyName(program, property.type) === undefined &&
|
|
661
|
+
((property.type.templateNode && property.type.templateNode !== null) ||
|
|
662
|
+
(((_a = property.type.templateMapper) === null || _a === void 0 ? void 0 : _a.args) && property.type.templateMapper.args.length > 0))) {
|
|
663
|
+
reportDiagnostic(program, {
|
|
664
|
+
code: "no-inline-properties",
|
|
665
|
+
format: { propertyName: property.name, modelName: (parent === null || parent === void 0 ? void 0 : parent.name) || "" },
|
|
666
|
+
target: property,
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
break;
|
|
670
|
+
case "Object":
|
|
671
|
+
case "ModelProperty":
|
|
672
|
+
case "Intrinsic":
|
|
673
|
+
case "Number":
|
|
674
|
+
case "String":
|
|
675
|
+
case "Enum":
|
|
676
|
+
case "Scalar":
|
|
677
|
+
break;
|
|
678
|
+
default:
|
|
679
|
+
reportDiagnostic(program, {
|
|
680
|
+
code: "no-inline-properties",
|
|
681
|
+
format: { propertyName: property.name, modelName: (parent === null || parent === void 0 ? void 0 : parent.name) || "" },
|
|
682
|
+
target: property,
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
let outPropertyType = getCSharpType(property.type);
|
|
686
|
+
if (outPropertyType.isValueType && property.optional) {
|
|
687
|
+
outPropertyType = {
|
|
688
|
+
name: `${outPropertyType.name}?`,
|
|
689
|
+
nameSpace: outPropertyType.nameSpace,
|
|
690
|
+
isBuiltIn: outPropertyType.isBuiltIn,
|
|
691
|
+
isValueType: outPropertyType.isValueType,
|
|
692
|
+
typeParameters: outPropertyType.typeParameters
|
|
693
|
+
? [...outPropertyType.typeParameters]
|
|
694
|
+
: undefined,
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
ensureCSharpIdentifier(property, property.name);
|
|
698
|
+
const outProperty = {
|
|
699
|
+
name: transformCSharpIdentifier(property.name),
|
|
700
|
+
serializedName: jsonView.getProjectedName(property.projectionSource),
|
|
701
|
+
type: outPropertyType,
|
|
702
|
+
validations: getValidations(property),
|
|
703
|
+
converters: getConverters(property),
|
|
704
|
+
versions: getVersions(property),
|
|
705
|
+
description: getDoc(program, property),
|
|
706
|
+
default: property.default && formatDefaultValue(property.type, property.default),
|
|
707
|
+
};
|
|
708
|
+
return outProperty;
|
|
709
|
+
}
|
|
710
|
+
function formatDefaultValue(propertyType, defaultValue) {
|
|
711
|
+
switch (defaultValue.kind) {
|
|
712
|
+
case "String":
|
|
713
|
+
case "Number":
|
|
714
|
+
case "Boolean":
|
|
715
|
+
return formatPrimitiveType(defaultValue);
|
|
716
|
+
case "Tuple":
|
|
717
|
+
return formatTupleValue(propertyType, defaultValue);
|
|
718
|
+
case "EnumMember":
|
|
719
|
+
return `${defaultValue.enum.name}.${defaultValue.name}`;
|
|
720
|
+
default:
|
|
721
|
+
throw new Error(`Unsupported default value '${defaultValue.kind}'`);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
function formatTupleValue(propertyType, defaultValue) {
|
|
725
|
+
const items = defaultValue.values.map((x) => { var _a;
|
|
726
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain
|
|
727
|
+
return formatDefaultValue((_a = propertyType.indexer) === null || _a === void 0 ? void 0 : _a.value, x); });
|
|
728
|
+
const type = getCSharpType(propertyType);
|
|
729
|
+
return `new ${type.name} { ${items.join(", ")} }`;
|
|
730
|
+
}
|
|
731
|
+
function formatPrimitiveType(type) {
|
|
732
|
+
switch (type.kind) {
|
|
733
|
+
case "String":
|
|
734
|
+
return `"${type.value}"`;
|
|
735
|
+
case "Number":
|
|
736
|
+
return `${type.value}`;
|
|
737
|
+
case "Boolean":
|
|
738
|
+
return `${type.value}`;
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
function transformCSharpIdentifier(identifier) {
|
|
742
|
+
return identifier[0].toLocaleUpperCase() + identifier.substring(1);
|
|
743
|
+
}
|
|
744
|
+
function isValidCSharpIdentifier(identifier) {
|
|
745
|
+
return identifier.match(/^[A-Za-z_][\w-]*$/) !== null;
|
|
746
|
+
}
|
|
747
|
+
function ensureCSharpIdentifier(target, name) {
|
|
748
|
+
var _a, _b;
|
|
749
|
+
let location = "";
|
|
750
|
+
switch (target.kind) {
|
|
751
|
+
case "Enum":
|
|
752
|
+
location = `enum ${target.name}`;
|
|
753
|
+
break;
|
|
754
|
+
case "EnumMember":
|
|
755
|
+
location = `enum ${target.enum.name}`;
|
|
756
|
+
break;
|
|
757
|
+
case "Interface":
|
|
758
|
+
location = `interface ${target.name}`;
|
|
759
|
+
break;
|
|
760
|
+
case "Model":
|
|
761
|
+
location = `model ${target.name}`;
|
|
762
|
+
break;
|
|
763
|
+
case "ModelProperty": {
|
|
764
|
+
const model = target.model;
|
|
765
|
+
if (!model) {
|
|
766
|
+
reportDiagnostic(program, {
|
|
767
|
+
code: "missing-type-parent",
|
|
768
|
+
format: { type: "ModelProperty", name: target.name },
|
|
769
|
+
target: target,
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
else {
|
|
773
|
+
location = `property '${target.name}' in model ${model === null || model === void 0 ? void 0 : model.name}`;
|
|
774
|
+
if (!model.name) {
|
|
775
|
+
location = `parameter '${target.name}' in operation`;
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
break;
|
|
779
|
+
}
|
|
780
|
+
case "Namespace":
|
|
781
|
+
location = `namespace ${target.name}`;
|
|
782
|
+
break;
|
|
783
|
+
case "Operation": {
|
|
784
|
+
const parent = target.interface
|
|
785
|
+
? `interface ${target.interface.name}`
|
|
786
|
+
: `namespace ${(_a = target.namespace) === null || _a === void 0 ? void 0 : _a.name}`;
|
|
787
|
+
location = `operation ${target.name} in ${parent}`;
|
|
788
|
+
break;
|
|
789
|
+
}
|
|
790
|
+
case "Union":
|
|
791
|
+
location = `union ${target.name}`;
|
|
792
|
+
break;
|
|
793
|
+
case "UnionVariant": {
|
|
794
|
+
if (target.node !== undefined) {
|
|
795
|
+
const parent = program.checker.getTypeForNode(target.node.parent);
|
|
796
|
+
if ((parent === null || parent === void 0 ? void 0 : parent.kind) === "Union")
|
|
797
|
+
location = `variant ${String(target.name)} in union ${parent === null || parent === void 0 ? void 0 : parent.name}`;
|
|
798
|
+
}
|
|
799
|
+
break;
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
if (!isValidCSharpIdentifier(name)) {
|
|
803
|
+
reportDiagnostic(program, {
|
|
804
|
+
code: "invalid-identifier",
|
|
805
|
+
format: { identifier: name, location: location },
|
|
806
|
+
target: (_b = target.node) !== null && _b !== void 0 ? _b : NoTarget,
|
|
807
|
+
});
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
function getPatternAttribute(parameter) {
|
|
811
|
+
return {
|
|
812
|
+
name: "Pattern",
|
|
813
|
+
parameters: [
|
|
814
|
+
{
|
|
815
|
+
value: parameter,
|
|
816
|
+
type: "string",
|
|
817
|
+
},
|
|
818
|
+
],
|
|
819
|
+
};
|
|
820
|
+
}
|
|
821
|
+
function getBase64UrlConverter() {
|
|
822
|
+
return {
|
|
823
|
+
name: "Base64UrlJsonConverter",
|
|
824
|
+
parameters: [],
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
function getLengthAttribute(minLength, maxLength) {
|
|
828
|
+
var _a, _b;
|
|
829
|
+
const output = {
|
|
830
|
+
name: "Length",
|
|
831
|
+
parameters: [],
|
|
832
|
+
};
|
|
833
|
+
if (minLength) {
|
|
834
|
+
(_a = output.parameters) === null || _a === void 0 ? void 0 : _a.push({ type: "int", value: minLength });
|
|
835
|
+
}
|
|
836
|
+
if (maxLength) {
|
|
837
|
+
(_b = output.parameters) === null || _b === void 0 ? void 0 : _b.push({ type: "int", value: maxLength });
|
|
838
|
+
}
|
|
839
|
+
return output;
|
|
840
|
+
}
|
|
841
|
+
function createInlineEnum(typespecType, name) {
|
|
842
|
+
name !== null && name !== void 0 ? name : (name = typespecType.name);
|
|
843
|
+
ensureCSharpIdentifier(typespecType, name);
|
|
844
|
+
const outEnum = {
|
|
845
|
+
isClosed: false,
|
|
846
|
+
nameSpace: serviceNamespace,
|
|
847
|
+
serviceName: serviceName,
|
|
848
|
+
name: transformCSharpIdentifier(name),
|
|
849
|
+
values: [],
|
|
850
|
+
sourceNode: typespecType.node,
|
|
851
|
+
};
|
|
852
|
+
typespecType.members.forEach((option) => {
|
|
853
|
+
ensureCSharpIdentifier(option, option.name);
|
|
854
|
+
outEnum.values.push({
|
|
855
|
+
name: option.name,
|
|
856
|
+
value: option.value,
|
|
857
|
+
sourceNode: option.node,
|
|
858
|
+
});
|
|
859
|
+
});
|
|
860
|
+
outputModel.enumerations.push(outEnum);
|
|
861
|
+
const outType = {
|
|
862
|
+
name: outEnum.name,
|
|
863
|
+
nameSpace: "Microsoft.Service.Models",
|
|
864
|
+
isBuiltIn: false,
|
|
865
|
+
isValueType: true,
|
|
866
|
+
};
|
|
867
|
+
return outType;
|
|
868
|
+
}
|
|
869
|
+
function getValidations(typespecType) {
|
|
870
|
+
var _a, _b;
|
|
871
|
+
const visited = new Map();
|
|
872
|
+
function getLocalValidators(localType) {
|
|
873
|
+
if (visited.has(localType)) {
|
|
874
|
+
return visited.get(localType);
|
|
875
|
+
}
|
|
876
|
+
const output = [];
|
|
877
|
+
// Add SafeInt attribute for safeint fields
|
|
878
|
+
if (localType.kind === "Scalar" && localType.name === "safeint") {
|
|
879
|
+
output.push({
|
|
880
|
+
name: "SafeInt",
|
|
881
|
+
});
|
|
882
|
+
}
|
|
883
|
+
const pattern = getPattern(program, localType);
|
|
884
|
+
if (pattern) {
|
|
885
|
+
output.push(getPatternAttribute(pattern));
|
|
886
|
+
}
|
|
887
|
+
const minLength = getMinLength(program, localType);
|
|
888
|
+
const maxLength = getMaxLength(program, localType);
|
|
889
|
+
if (minLength || maxLength) {
|
|
890
|
+
output.push(getLengthAttribute(minLength, maxLength));
|
|
891
|
+
}
|
|
892
|
+
visited.set(localType, output);
|
|
893
|
+
return output;
|
|
894
|
+
}
|
|
895
|
+
const outValidations = getLocalValidators(typespecType);
|
|
896
|
+
switch (typespecType.kind) {
|
|
897
|
+
case "Tuple":
|
|
898
|
+
typespecType.values.forEach((v) => getValidations(v).forEach((val) => outValidations.push(val)));
|
|
899
|
+
break;
|
|
900
|
+
case "Union":
|
|
901
|
+
typespecType.variants.forEach((variant) => getValidations(variant.type).forEach((val) => outValidations.push(val)));
|
|
902
|
+
break;
|
|
903
|
+
case "Model":
|
|
904
|
+
if ((_a = typespecType.indexer) === null || _a === void 0 ? void 0 : _a.value) {
|
|
905
|
+
getValidations((_b = typespecType.indexer) === null || _b === void 0 ? void 0 : _b.value).forEach((i) => outValidations.push(i));
|
|
906
|
+
}
|
|
907
|
+
if (typespecType.baseModel) {
|
|
908
|
+
getValidations(typespecType.baseModel).forEach((val) => outValidations.push(val));
|
|
909
|
+
}
|
|
910
|
+
if (typespecType.templateNode) {
|
|
911
|
+
const templateType = program.checker.getTypeForNode(typespecType.templateNode);
|
|
912
|
+
getValidations(templateType).forEach((val) => outValidations.push(val));
|
|
913
|
+
}
|
|
914
|
+
break;
|
|
915
|
+
case "Scalar":
|
|
916
|
+
if (typespecType.baseScalar) {
|
|
917
|
+
getValidations(typespecType.baseScalar).forEach((val) => outValidations.push(val));
|
|
918
|
+
}
|
|
919
|
+
break;
|
|
920
|
+
case "ModelProperty":
|
|
921
|
+
getValidations(typespecType.type).forEach((val) => outValidations.push(val));
|
|
922
|
+
break;
|
|
923
|
+
default:
|
|
924
|
+
// do nothing
|
|
925
|
+
break;
|
|
926
|
+
}
|
|
927
|
+
return outValidations;
|
|
928
|
+
}
|
|
929
|
+
function getConverters(typespecType) {
|
|
930
|
+
var _a, _b;
|
|
931
|
+
const visited = new Map();
|
|
932
|
+
function getLocalConverters(localType) {
|
|
933
|
+
var _a;
|
|
934
|
+
if (visited.has(localType)) {
|
|
935
|
+
return visited.get(localType);
|
|
936
|
+
}
|
|
937
|
+
const output = [];
|
|
938
|
+
const format = getFormat(program, localType);
|
|
939
|
+
if (format && format === "base64Url") {
|
|
940
|
+
output.push(getBase64UrlConverter());
|
|
941
|
+
}
|
|
942
|
+
// Add JsonConverter attribute for duration fields
|
|
943
|
+
if (ignoreDiagnostics(program.checker.isTypeAssignableTo((_a = localType.projectionBase) !== null && _a !== void 0 ? _a : localType, program.checker.getStdType("duration"), localType))) {
|
|
944
|
+
output.push({
|
|
945
|
+
name: "XsdTimeSpanConverter",
|
|
946
|
+
parameters: [],
|
|
947
|
+
});
|
|
948
|
+
}
|
|
949
|
+
visited.set(localType, output);
|
|
950
|
+
return output;
|
|
951
|
+
}
|
|
952
|
+
const outConverters = getLocalConverters(typespecType);
|
|
953
|
+
switch (typespecType.kind) {
|
|
954
|
+
case "Tuple":
|
|
955
|
+
typespecType.values.forEach((v) => getConverters(v).forEach((val) => outConverters.push(val)));
|
|
956
|
+
break;
|
|
957
|
+
case "Union":
|
|
958
|
+
typespecType.options.forEach((o) => getConverters(o).forEach((val) => outConverters.push(val)));
|
|
959
|
+
break;
|
|
960
|
+
case "Model":
|
|
961
|
+
if ((_a = typespecType.indexer) === null || _a === void 0 ? void 0 : _a.value) {
|
|
962
|
+
getConverters((_b = typespecType.indexer) === null || _b === void 0 ? void 0 : _b.value).forEach((i) => outConverters.push(i));
|
|
963
|
+
}
|
|
964
|
+
if (typespecType.baseModel) {
|
|
965
|
+
getConverters(typespecType.baseModel).forEach((val) => outConverters.push(val));
|
|
966
|
+
}
|
|
967
|
+
if (typespecType.templateNode) {
|
|
968
|
+
const templateType = program.checker.getTypeForNode(typespecType.templateNode);
|
|
969
|
+
getConverters(templateType).forEach((val) => outConverters.push(val));
|
|
970
|
+
}
|
|
971
|
+
break;
|
|
972
|
+
case "ModelProperty":
|
|
973
|
+
getConverters(typespecType.type).forEach((val) => outConverters.push(val));
|
|
974
|
+
break;
|
|
975
|
+
default:
|
|
976
|
+
// do nothing
|
|
977
|
+
break;
|
|
978
|
+
}
|
|
979
|
+
return outConverters;
|
|
980
|
+
}
|
|
981
|
+
function getVersions(typespecType) {
|
|
982
|
+
var _a, _b;
|
|
983
|
+
const visited = new Map();
|
|
984
|
+
function getLocalVersions(localType) {
|
|
985
|
+
if (visited.has(localType)) {
|
|
986
|
+
return visited.get(localType);
|
|
987
|
+
}
|
|
988
|
+
const output = [];
|
|
989
|
+
const added = getAddedOnVersions(program, localType);
|
|
990
|
+
if (added && added.length > 0) {
|
|
991
|
+
// for now, add on a single attribute, verify that multiple attributes work as expected, then do this for each
|
|
992
|
+
output.push({ name: "Added", onVersion: added[0].value });
|
|
993
|
+
}
|
|
994
|
+
const removed = getRemovedOnVersions(program, localType);
|
|
995
|
+
if (removed && removed.length > 0) {
|
|
996
|
+
// for now, add on a single attribute, verify that multiple attributes work as expected, then do this for each
|
|
997
|
+
output.push({ name: "Removed", onVersion: removed[0].value });
|
|
998
|
+
}
|
|
999
|
+
visited.set(localType, output);
|
|
1000
|
+
return output;
|
|
1001
|
+
}
|
|
1002
|
+
const outVersions = new Set(getLocalVersions(typespecType));
|
|
1003
|
+
if (outVersions.size === 0) {
|
|
1004
|
+
switch (typespecType.kind) {
|
|
1005
|
+
case "ModelProperty":
|
|
1006
|
+
getVersions(typespecType.type).forEach((i) => outVersions.add(i));
|
|
1007
|
+
break;
|
|
1008
|
+
case "Model":
|
|
1009
|
+
if (typespecType.instantiationParameters) {
|
|
1010
|
+
typespecType.instantiationParameters
|
|
1011
|
+
.flatMap((i) => getVersions(i))
|
|
1012
|
+
.forEach((i) => outVersions.add(i));
|
|
1013
|
+
}
|
|
1014
|
+
if ((_a = typespecType.indexer) === null || _a === void 0 ? void 0 : _a.value) {
|
|
1015
|
+
getVersions((_b = typespecType.indexer) === null || _b === void 0 ? void 0 : _b.value).forEach((x) => outVersions.add(x));
|
|
1016
|
+
}
|
|
1017
|
+
break;
|
|
1018
|
+
default:
|
|
1019
|
+
// in the case of other types, versioning attributes on type components
|
|
1020
|
+
// do not flow to model properties
|
|
1021
|
+
break;
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
return [...outVersions.values()];
|
|
1025
|
+
}
|
|
1026
|
+
function isSealedBaseModel(refType) {
|
|
1027
|
+
return refType && refType.isBuiltIn && refType.nameSpace.toLowerCase().startsWith("system");
|
|
1028
|
+
}
|
|
1029
|
+
function getCSharpType(typespecType) {
|
|
1030
|
+
const cached = csharpTypeCache.get(typespecType);
|
|
1031
|
+
if (cached) {
|
|
1032
|
+
return cached;
|
|
1033
|
+
}
|
|
1034
|
+
const type = getCSharpTypeInternal(typespecType);
|
|
1035
|
+
if (type) {
|
|
1036
|
+
csharpTypeCache.set(typespecType, type);
|
|
1037
|
+
}
|
|
1038
|
+
return type;
|
|
1039
|
+
}
|
|
1040
|
+
function getCSharpTypeInternal(typespecType) {
|
|
1041
|
+
switch (typespecType.kind) {
|
|
1042
|
+
case "String":
|
|
1043
|
+
return { name: "string", nameSpace: "System", isBuiltIn: true };
|
|
1044
|
+
case "Boolean":
|
|
1045
|
+
return { name: "bool", nameSpace: "System", isBuiltIn: true };
|
|
1046
|
+
case "Union":
|
|
1047
|
+
// Need to figure out if we want to support unions, otherwise this will require a static analysis rule.
|
|
1048
|
+
reportDiagnostic(program, { code: "no-union", target: typespecType.node });
|
|
1049
|
+
return undefined;
|
|
1050
|
+
case "Tuple":
|
|
1051
|
+
const params = [];
|
|
1052
|
+
typespecType.values.forEach((val) => {
|
|
1053
|
+
const ref = getCSharpType(val);
|
|
1054
|
+
if (ref) {
|
|
1055
|
+
params.push(ref);
|
|
1056
|
+
}
|
|
1057
|
+
});
|
|
1058
|
+
return {
|
|
1059
|
+
isBuiltIn: false,
|
|
1060
|
+
name: "Tuple",
|
|
1061
|
+
nameSpace: "System.Collections.Generic",
|
|
1062
|
+
typeParameters: params,
|
|
1063
|
+
};
|
|
1064
|
+
case "Enum":
|
|
1065
|
+
return createInlineEnum(typespecType);
|
|
1066
|
+
case "Scalar":
|
|
1067
|
+
const csharpType = getCSharpTypeForScalar(typespecType);
|
|
1068
|
+
// Check if there is known value and then return an enum instead.
|
|
1069
|
+
const values = getKnownValues(program, typespecType);
|
|
1070
|
+
if (values) {
|
|
1071
|
+
return createInlineEnum(values, typespecType.name);
|
|
1072
|
+
}
|
|
1073
|
+
else {
|
|
1074
|
+
return csharpType;
|
|
1075
|
+
}
|
|
1076
|
+
case "Model":
|
|
1077
|
+
const friendlyName = getFriendlyName(program, typespecType);
|
|
1078
|
+
// Is the type templated with only one type?
|
|
1079
|
+
if (!friendlyName &&
|
|
1080
|
+
typespecType.baseModel &&
|
|
1081
|
+
(!typespecType.properties || typespecType.properties.size === 0)) {
|
|
1082
|
+
const outRef = getCSharpType(typespecType.baseModel);
|
|
1083
|
+
if (isSealedBaseModel(outRef)) {
|
|
1084
|
+
return outRef;
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
const typespecIntrinsicType = getCSharpTypeForTypeSpecIntrinsicModels(typespecType);
|
|
1088
|
+
if (typespecIntrinsicType !== undefined) {
|
|
1089
|
+
return typespecIntrinsicType;
|
|
1090
|
+
}
|
|
1091
|
+
const known = getKnownType(typespecType);
|
|
1092
|
+
if (typespecType.name === undefined || typespecType.name === "") {
|
|
1093
|
+
return undefined;
|
|
1094
|
+
}
|
|
1095
|
+
if (known) {
|
|
1096
|
+
return known;
|
|
1097
|
+
}
|
|
1098
|
+
const modelName = friendlyName !== null && friendlyName !== void 0 ? friendlyName : typespecType.name;
|
|
1099
|
+
ensureCSharpIdentifier(typespecType, modelName);
|
|
1100
|
+
return {
|
|
1101
|
+
name: transformCSharpIdentifier(modelName),
|
|
1102
|
+
nameSpace: serviceNamespace,
|
|
1103
|
+
isBuiltIn: false,
|
|
1104
|
+
};
|
|
1105
|
+
case "Intrinsic":
|
|
1106
|
+
return undefined;
|
|
1107
|
+
case "TemplateParameter":
|
|
1108
|
+
return undefined;
|
|
1109
|
+
default:
|
|
1110
|
+
return undefined;
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
function getCSharpTypeForTypeSpecIntrinsicModels(typespecType) {
|
|
1114
|
+
if (typespecType.indexer) {
|
|
1115
|
+
if (isNeverType(typespecType.indexer.key)) {
|
|
1116
|
+
}
|
|
1117
|
+
else {
|
|
1118
|
+
const name = typespecType.indexer.key.name;
|
|
1119
|
+
if (name === "string") {
|
|
1120
|
+
const valType = typespecType.indexer.value;
|
|
1121
|
+
return {
|
|
1122
|
+
name: "IDictionary",
|
|
1123
|
+
nameSpace: "System.Collections",
|
|
1124
|
+
isBuiltIn: true,
|
|
1125
|
+
typeParameters: [
|
|
1126
|
+
{ name: "string", nameSpace: "System", isBuiltIn: true },
|
|
1127
|
+
getCSharpType(valType),
|
|
1128
|
+
],
|
|
1129
|
+
};
|
|
1130
|
+
}
|
|
1131
|
+
else if (name === "integer") {
|
|
1132
|
+
const arrType = getCSharpType(typespecType.indexer.value);
|
|
1133
|
+
if (arrType) {
|
|
1134
|
+
return {
|
|
1135
|
+
name: arrType.name + "[]",
|
|
1136
|
+
nameSpace: arrType.nameSpace,
|
|
1137
|
+
isBuiltIn: true,
|
|
1138
|
+
};
|
|
1139
|
+
}
|
|
1140
|
+
return undefined;
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
return undefined;
|
|
1145
|
+
}
|
|
1146
|
+
function getCSharpTypeForScalar(scalar) {
|
|
1147
|
+
if (program.checker.isStdType(scalar)) {
|
|
1148
|
+
return getCSharpTypeForStdScalars(scalar);
|
|
1149
|
+
}
|
|
1150
|
+
else if (scalar.baseScalar) {
|
|
1151
|
+
return getCSharpTypeForScalar(scalar.baseScalar);
|
|
1152
|
+
}
|
|
1153
|
+
reportDiagnostic(program, {
|
|
1154
|
+
code: "unrecognized-scalar",
|
|
1155
|
+
format: { typeName: scalar.name },
|
|
1156
|
+
target: scalar,
|
|
1157
|
+
});
|
|
1158
|
+
return { name: "Object", nameSpace: "System", isBuiltIn: true, isValueType: false };
|
|
1159
|
+
}
|
|
1160
|
+
function getCSharpTypeForStdScalars(scalar) {
|
|
1161
|
+
switch (scalar.name) {
|
|
1162
|
+
case "bytes":
|
|
1163
|
+
return { name: "byte[]", nameSpace: "System", isBuiltIn: true, isValueType: false };
|
|
1164
|
+
case "int8":
|
|
1165
|
+
return { name: "SByte", nameSpace: "System", isBuiltIn: true, isValueType: true };
|
|
1166
|
+
case "uint8":
|
|
1167
|
+
return { name: "Byte", nameSpace: "System", isBuiltIn: true, isValueType: true };
|
|
1168
|
+
case "int16":
|
|
1169
|
+
return { name: "Int16", nameSpace: "System", isBuiltIn: true, isValueType: true };
|
|
1170
|
+
case "uint16":
|
|
1171
|
+
return { name: "UInt16", nameSpace: "System", isBuiltIn: true, isValueType: true };
|
|
1172
|
+
case "int32":
|
|
1173
|
+
return { name: "int", nameSpace: "System", isBuiltIn: true, isValueType: true };
|
|
1174
|
+
case "uint32":
|
|
1175
|
+
return { name: "UInt32", nameSpace: "System", isBuiltIn: true, isValueType: true };
|
|
1176
|
+
case "integer":
|
|
1177
|
+
case "int64":
|
|
1178
|
+
return { name: "long", nameSpace: "System", isBuiltIn: true, isValueType: true };
|
|
1179
|
+
case "uint64":
|
|
1180
|
+
return { name: "UInt64", nameSpace: "System", isBuiltIn: true, isValueType: true };
|
|
1181
|
+
case "safeint":
|
|
1182
|
+
return { name: "long", nameSpace: "System", isBuiltIn: true, isValueType: true };
|
|
1183
|
+
case "float":
|
|
1184
|
+
case "float64":
|
|
1185
|
+
return { name: "double", nameSpace: "System", isBuiltIn: true, isValueType: true };
|
|
1186
|
+
case "float32":
|
|
1187
|
+
return { name: "float", nameSpace: "System", isBuiltIn: true, isValueType: true };
|
|
1188
|
+
case "string":
|
|
1189
|
+
return { name: "string", nameSpace: "System", isBuiltIn: true, isValueType: false };
|
|
1190
|
+
case "boolean":
|
|
1191
|
+
return { name: "bool", nameSpace: "System", isBuiltIn: true, isValueType: true };
|
|
1192
|
+
case "plainDate":
|
|
1193
|
+
return { name: "DateTime", nameSpace: "System", isBuiltIn: true, isValueType: true };
|
|
1194
|
+
case "zonedDateTime":
|
|
1195
|
+
return {
|
|
1196
|
+
name: "DateTimeOffset",
|
|
1197
|
+
nameSpace: "System",
|
|
1198
|
+
isBuiltIn: true,
|
|
1199
|
+
isValueType: true,
|
|
1200
|
+
};
|
|
1201
|
+
case "plainTime":
|
|
1202
|
+
return { name: "DateTime", nameSpace: "System", isBuiltIn: true, isValueType: true };
|
|
1203
|
+
case "duration":
|
|
1204
|
+
return { name: "TimeSpan", nameSpace: "System", isBuiltIn: true, isValueType: true };
|
|
1205
|
+
case "numeric":
|
|
1206
|
+
reportDiagnostic(program, { code: "no-numeric", target: scalar });
|
|
1207
|
+
return { name: "Object", nameSpace: "System", isBuiltIn: true, isValueType: false };
|
|
1208
|
+
case "url":
|
|
1209
|
+
return { name: "string", nameSpace: "System", isBuiltIn: true, isValueType: false };
|
|
1210
|
+
default:
|
|
1211
|
+
reportDiagnostic(program, {
|
|
1212
|
+
code: "unrecognized-scalar",
|
|
1213
|
+
format: { typeName: scalar.name },
|
|
1214
|
+
target: scalar,
|
|
1215
|
+
});
|
|
1216
|
+
return { name: "Object", nameSpace: "System", isBuiltIn: true, isValueType: false };
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
function getKnownType(model) {
|
|
1220
|
+
switch (model.name) {
|
|
1221
|
+
case "ErrorResponse":
|
|
1222
|
+
return {
|
|
1223
|
+
isBuiltIn: true,
|
|
1224
|
+
name: "Resource",
|
|
1225
|
+
nameSpace: "Microsoft.TypeSpec.ProviderHub",
|
|
1226
|
+
};
|
|
1227
|
+
case "ArmResponse":
|
|
1228
|
+
return {
|
|
1229
|
+
isBuiltIn: true,
|
|
1230
|
+
name: "ArmResponse",
|
|
1231
|
+
nameSpace: "Microsoft.TypeSpec.ProviderHub",
|
|
1232
|
+
};
|
|
1233
|
+
case "ArmNoContentResponse":
|
|
1234
|
+
case "ArmAcceptedResponse":
|
|
1235
|
+
case "ArmDeleteAcceptedResponse":
|
|
1236
|
+
case "ArmDeletedNoContentResponse":
|
|
1237
|
+
case "ArmCreatedResponse":
|
|
1238
|
+
return {
|
|
1239
|
+
isBuiltIn: true,
|
|
1240
|
+
name: "void",
|
|
1241
|
+
nameSpace: "System",
|
|
1242
|
+
};
|
|
1243
|
+
case "Operation":
|
|
1244
|
+
return {
|
|
1245
|
+
isBuiltIn: true,
|
|
1246
|
+
name: "Operation",
|
|
1247
|
+
nameSpace: "Microsoft.TypeSpec.ProviderHub",
|
|
1248
|
+
};
|
|
1249
|
+
case "OperationListResult":
|
|
1250
|
+
return {
|
|
1251
|
+
isBuiltIn: true,
|
|
1252
|
+
name: "OperationListResult",
|
|
1253
|
+
nameSpace: "Microsoft.TypeSpec.ProviderHub",
|
|
1254
|
+
};
|
|
1255
|
+
case "ArmResource":
|
|
1256
|
+
return {
|
|
1257
|
+
isBuiltIn: true,
|
|
1258
|
+
name: "Resource",
|
|
1259
|
+
nameSpace: "Microsoft.TypeSpec.ProviderHub",
|
|
1260
|
+
};
|
|
1261
|
+
case "TrackedResourceBase":
|
|
1262
|
+
case "TrackedResource": {
|
|
1263
|
+
const baseResource = {
|
|
1264
|
+
isBuiltIn: true,
|
|
1265
|
+
name: "TrackedResource",
|
|
1266
|
+
nameSpace: "Microsoft.TypeSpec.ProviderHub",
|
|
1267
|
+
};
|
|
1268
|
+
if (model.templateArguments && model.templateArguments.length === 1) {
|
|
1269
|
+
const propertiesType = getCSharpType(model.templateArguments[0]);
|
|
1270
|
+
if (propertiesType) {
|
|
1271
|
+
baseResource.typeParameters = [propertiesType];
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
return baseResource;
|
|
1275
|
+
}
|
|
1276
|
+
case "ProxyResourceBase":
|
|
1277
|
+
case "ProxyResource":
|
|
1278
|
+
return {
|
|
1279
|
+
isBuiltIn: true,
|
|
1280
|
+
name: "ProxyResource",
|
|
1281
|
+
nameSpace: "Microsoft.TypeSpec.ProviderHub",
|
|
1282
|
+
};
|
|
1283
|
+
case "SystemData":
|
|
1284
|
+
return {
|
|
1285
|
+
isBuiltIn: true,
|
|
1286
|
+
name: "SystemData",
|
|
1287
|
+
nameSpace: "Microsoft.TypeSpec.ProviderHub",
|
|
1288
|
+
};
|
|
1289
|
+
case "ExtensionResourceBase":
|
|
1290
|
+
case "ExtensionResource":
|
|
1291
|
+
return {
|
|
1292
|
+
isBuiltIn: true,
|
|
1293
|
+
name: "ProxyResource",
|
|
1294
|
+
nameSpace: "Microsoft.TypeSpec.ProviderHub",
|
|
1295
|
+
};
|
|
1296
|
+
case "Pageable":
|
|
1297
|
+
case "Page": {
|
|
1298
|
+
const returnValue = {
|
|
1299
|
+
isBuiltIn: true,
|
|
1300
|
+
name: "Pageable",
|
|
1301
|
+
nameSpace: "Microsoft.TypeSpec.ProviderHub",
|
|
1302
|
+
typeParameters: [],
|
|
1303
|
+
};
|
|
1304
|
+
const innerType = model.templateArguments
|
|
1305
|
+
? getCSharpType(model.templateArguments[0])
|
|
1306
|
+
: undefined;
|
|
1307
|
+
if (innerType) {
|
|
1308
|
+
returnValue.typeParameters.push(innerType);
|
|
1309
|
+
}
|
|
1310
|
+
return returnValue;
|
|
1311
|
+
}
|
|
1312
|
+
default:
|
|
1313
|
+
return undefined;
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
async function generateResource(resource) {
|
|
1317
|
+
const resourceControllerPath = joinPaths(genPath, `${resource.name}Controller.cs`);
|
|
1318
|
+
const resourceControllerBasePath = joinPaths(genPath, `${resource.name}ControllerBase.cs`);
|
|
1319
|
+
const resourceControllerBaseLoggerPath = joinPaths(genPath, `${resource.name}ControllerBase.Logger.cs`);
|
|
1320
|
+
tracer.trace("info", "Writing resource controller for " + resource.name, undefined);
|
|
1321
|
+
await program.host.writeFile(resolvePath(resourceControllerBasePath), await compileHandlebarsTemplate(resolvePath(joinPaths(templatePath, "resourceControllerBase.sq")), resource));
|
|
1322
|
+
await program.host.writeFile(resolvePath(resourceControllerBaseLoggerPath), await compileHandlebarsTemplate(resolvePath(joinPaths(templatePath, "resourceControllerBase.Logger.sq")), resource));
|
|
1323
|
+
await program.host.writeFile(resolvePath(resourceControllerPath), await compileHandlebarsTemplate(resolvePath(joinPaths(templatePath, "resourceController.sq")), resource));
|
|
1324
|
+
if (registrationOutputPath) {
|
|
1325
|
+
await generateRegistration(resource);
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
async function generateResourceProviderReg(serviceModel) {
|
|
1329
|
+
const outputPath = joinPaths(registrationOutputPath, `${serviceModel.providerNamespace}.json`);
|
|
1330
|
+
const regTemplate = resolvePath(joinPaths(templatePath, "resourceProviderRegistration.sq"));
|
|
1331
|
+
await program.host.writeFile(outputPath, await compileHandlebarsTemplate(regTemplate, { serviceName: serviceModel.serviceName }));
|
|
1332
|
+
}
|
|
1333
|
+
async function generateRegistration(resource) {
|
|
1334
|
+
var _a;
|
|
1335
|
+
const fullyQualifiedResourceType = getFullyQualifiedResourceType(resource);
|
|
1336
|
+
const resourceRegistrationPath = joinPaths(registrationOutputPath, `${fullyQualifiedResourceType}.json`);
|
|
1337
|
+
await createDirIfNotExists(getDirectoryPath(resourceRegistrationPath)).catch((err) => reportDiagnostic(program, {
|
|
1338
|
+
code: "creating-dir",
|
|
1339
|
+
format: { error: err },
|
|
1340
|
+
target: NoTarget,
|
|
1341
|
+
}));
|
|
1342
|
+
tracer.trace("info", `Writing resource registrations for ${fullyQualifiedResourceType}`, undefined);
|
|
1343
|
+
const extensions = new Set();
|
|
1344
|
+
for (const operation of (_a = resource.operations) !== null && _a !== void 0 ? _a : []) {
|
|
1345
|
+
const extensionMap = {
|
|
1346
|
+
put: ["ResourceCreationValidate", "ResourceCreationBegin", "ResourceCreationCompleted"],
|
|
1347
|
+
patch: ["ResourcePatchValidate", "ResourcePatchBegin", "ResourcePatchCompleted"],
|
|
1348
|
+
delete: [
|
|
1349
|
+
"ResourceDeletionValidate",
|
|
1350
|
+
"ResourceDeletionBegin",
|
|
1351
|
+
"ResourceDeletionCompleted",
|
|
1352
|
+
],
|
|
1353
|
+
get: ["ResourceReadValidate"],
|
|
1354
|
+
post: ["ResourcePostAction"],
|
|
1355
|
+
};
|
|
1356
|
+
const _extensions = extensionMap[operation.verb.toLowerCase()];
|
|
1357
|
+
if (_extensions) {
|
|
1358
|
+
_extensions.forEach((element) => {
|
|
1359
|
+
extensions.add(element);
|
|
1360
|
+
});
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
await program.host.writeFile(resolvePath(resourceRegistrationPath), await compileHandlebarsTemplate(resolvePath(joinPaths(templatePath, "resourceRegistration.sq")), {
|
|
1364
|
+
apiVersion: serviceVersions,
|
|
1365
|
+
extensions: Array.from(extensions.values()),
|
|
1366
|
+
}));
|
|
1367
|
+
}
|
|
1368
|
+
async function generateModel(model) {
|
|
1369
|
+
async function renderModelTemplate(outFile, templateFile, model) {
|
|
1370
|
+
await program.host.writeFile(resolvePath(outFile), await compileHandlebarsTemplate(templateFile, model));
|
|
1371
|
+
}
|
|
1372
|
+
await renderModelTemplate(joinPaths(genPath, "models", model.name + ".cs"), resolvePath(joinPaths(templatePath, "model.sq")), model);
|
|
1373
|
+
if (!model.baseClass || model.baseClass.name !== "Pageable") {
|
|
1374
|
+
await renderModelTemplate(joinPaths(genPath, "models", `${model.name}CopyHelper.cs`), resolvePath(joinPaths(templatePath, "modelCopy.sq")), model);
|
|
1375
|
+
await renderModelTemplate(joinPaths(genPath, "models", `${model.name}Extensions.cs`), resolvePath(joinPaths(templatePath, "modelCopyExtension.sq")), model);
|
|
1376
|
+
}
|
|
1377
|
+
if (model.isVersioned) {
|
|
1378
|
+
const template = model.hasVersionedAncestor
|
|
1379
|
+
? "modelVersionComposite.sq"
|
|
1380
|
+
: "modelVersionStandard.sq";
|
|
1381
|
+
await renderModelTemplate(joinPaths(genPath, "models", `${model.name}VersionInfo.cs`), resolvePath(joinPaths(templatePath, template)), model);
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
async function generateEnum(model) {
|
|
1385
|
+
const modelPath = genPath + "/models/" + model.name + ".cs";
|
|
1386
|
+
const templateFile = resolvePath(joinPaths(templatePath, model.isClosed ? "closedEnum.sq" : "openEnum.sq"));
|
|
1387
|
+
await program.host.writeFile(resolvePath(modelPath), await compileHandlebarsTemplate(templateFile, model));
|
|
1388
|
+
}
|
|
1389
|
+
async function generateSingleDirectory(basePath, outPath) {
|
|
1390
|
+
reportProgress("+++++++");
|
|
1391
|
+
reportProgress("Generating single file templates");
|
|
1392
|
+
reportProgress(" basePath: " + basePath);
|
|
1393
|
+
reportProgress(" outPath: " + outPath);
|
|
1394
|
+
const singleTemplatePath = joinPaths(templatePath, "single");
|
|
1395
|
+
const files = await host.readDir(singleTemplatePath);
|
|
1396
|
+
for (const file of files) {
|
|
1397
|
+
// If we are not asked to include the operation controller, skip any
|
|
1398
|
+
// singleton files starting with "Operation"
|
|
1399
|
+
if (!options.includeOperationController) {
|
|
1400
|
+
if (file.startsWith("Operation")) {
|
|
1401
|
+
continue;
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
const templatePath = resolvePath(joinPaths(singleTemplatePath, file));
|
|
1405
|
+
await generateSingleFile(templatePath, outPath).catch((err) => reportDiagnostic(program, {
|
|
1406
|
+
code: "creating-file",
|
|
1407
|
+
format: { filename: file, error: err },
|
|
1408
|
+
target: NoTarget,
|
|
1409
|
+
}));
|
|
1410
|
+
}
|
|
1411
|
+
reportProgress("++++++");
|
|
1412
|
+
async function generateSingleFile(templatePath, outPath) {
|
|
1413
|
+
const templateFile = getBaseFileName(templatePath);
|
|
1414
|
+
const baseName = templateFile.substring(0, templateFile.lastIndexOf("."));
|
|
1415
|
+
const outFile = joinPaths(outPath, baseName + ".cs");
|
|
1416
|
+
reportProgress(` -- ${templateFile} => ${outFile}`);
|
|
1417
|
+
const content = await compileHandlebarsTemplate(templatePath, outputModel);
|
|
1418
|
+
await program.host.writeFile(resolvePath(outFile), content).catch((err) => reportDiagnostic(program, {
|
|
1419
|
+
code: "writing-file",
|
|
1420
|
+
format: { filename: outFile, error: err },
|
|
1421
|
+
target: NoTarget,
|
|
1422
|
+
}));
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
async function createDirIfNotExists(targetPath) {
|
|
1426
|
+
if (!(await program.host.stat(targetPath).catch((err) => {
|
|
1427
|
+
return false;
|
|
1428
|
+
}))) {
|
|
1429
|
+
await program.host.mkdirp(targetPath);
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
async function ensureCleanDirectory(targetPath) {
|
|
1433
|
+
try {
|
|
1434
|
+
await program.host.stat(targetPath);
|
|
1435
|
+
await host.rm(targetPath, { recursive: true });
|
|
1436
|
+
}
|
|
1437
|
+
catch { }
|
|
1438
|
+
await host.mkdirp(targetPath);
|
|
1439
|
+
}
|
|
1440
|
+
const service = outputModel.serviceName;
|
|
1441
|
+
const templates = new Map();
|
|
1442
|
+
Handlebars.registerPartial("renderComment", '{{#each (split description "\n") as |line|}}/// {{trim line}}\n{{/each}}');
|
|
1443
|
+
const compileHandlebarsTemplate = async (fileName, data) => {
|
|
1444
|
+
let templateDelegate = templates.get(fileName);
|
|
1445
|
+
if (!templateDelegate) {
|
|
1446
|
+
const generationViewTemplate = (await program.host.readFile(fileName)).text;
|
|
1447
|
+
templateDelegate = Handlebars.compile(generationViewTemplate, {
|
|
1448
|
+
noEscape: true,
|
|
1449
|
+
});
|
|
1450
|
+
templates.set(fileName, templateDelegate);
|
|
1451
|
+
}
|
|
1452
|
+
return templateDelegate(data, { helpers: commonHelper });
|
|
1453
|
+
};
|
|
1454
|
+
const commonHelper = {
|
|
1455
|
+
decl: (op) => op.parameters
|
|
1456
|
+
.map((p) => p.type + " " + p.name + (p.default ? ` = ${p.default}` : ""))
|
|
1457
|
+
.join(", "),
|
|
1458
|
+
call: (op) => op.parameters.map((p) => p.name).join(", "),
|
|
1459
|
+
increment: (i) => i + 1,
|
|
1460
|
+
decrement: (i) => i - 1,
|
|
1461
|
+
bodyParamName: (op) => op.parameters[op.parameters.length - 1].name,
|
|
1462
|
+
typeParamList: (op) => op.typeParameters.map((p) => p.name).join(", "),
|
|
1463
|
+
callByValue: (op) => op.parameters
|
|
1464
|
+
.map((p) => (p.type === "string" ? `@"${p.value}"` : p.value))
|
|
1465
|
+
.join(", "),
|
|
1466
|
+
initialCaps: (str) => str ? str.charAt(0).toUpperCase() + str.slice(1).toLowerCase() : "",
|
|
1467
|
+
isDefined: (value) => value !== undefined,
|
|
1468
|
+
eq: (a, b) => a === b,
|
|
1469
|
+
eqi: (a, b) => a.toLowerCase() === b.toLowerCase(),
|
|
1470
|
+
ne: (a, b) => a !== b,
|
|
1471
|
+
or: (a, b) => !!a || !!b,
|
|
1472
|
+
and: (a, b) => a && b,
|
|
1473
|
+
not: (a) => !a,
|
|
1474
|
+
contains: (a, b) => a.includes(b),
|
|
1475
|
+
notCustomOp: (op) => op.kind !== "action",
|
|
1476
|
+
getOperationAction: (operation) => {
|
|
1477
|
+
const subPath = operation.subPath || "";
|
|
1478
|
+
const actionName = subPath.length ? subPath[0].toUpperCase() + subPath.substring(1) : "";
|
|
1479
|
+
const mapping = {
|
|
1480
|
+
get: "Read",
|
|
1481
|
+
put: "Create",
|
|
1482
|
+
delete: "Delete",
|
|
1483
|
+
patch: "Patch",
|
|
1484
|
+
post: actionName,
|
|
1485
|
+
};
|
|
1486
|
+
return operation.kind === "action" ? actionName : mapping[operation.verb.toLowerCase()];
|
|
1487
|
+
},
|
|
1488
|
+
join: (arr, separator) => arr.join(separator),
|
|
1489
|
+
joinByName: (arr, separator) => arr.map((i) => i.name).join(separator),
|
|
1490
|
+
split: (str, separator) => (str ? str.split(separator) : ""),
|
|
1491
|
+
trim: (str) => (str ? str.trim() : ""),
|
|
1492
|
+
// cspell:ignore csharpname
|
|
1493
|
+
csharpname: (str) => transformCSharpIdentifier(str),
|
|
1494
|
+
curly: (open) => (open ? "{" : "}"),
|
|
1495
|
+
hasConverter: (model) => { var _a; return ((_a = model === null || model === void 0 ? void 0 : model.properties) === null || _a === void 0 ? void 0 : _a.some((p) => p.converters.length)) || (model === null || model === void 0 ? void 0 : model.discriminatorInfo); },
|
|
1496
|
+
versionName: (v) => `Version_${v.replace(/-/g, "").replace("preview", "Preview")}`,
|
|
1497
|
+
};
|
|
1498
|
+
const operationsPath = genPath;
|
|
1499
|
+
const routesPath = resolvePath(joinPaths(genPath, service + "ServiceRoutes.cs"));
|
|
1500
|
+
const serializerPath = resolvePath(joinPaths(genPath, "VersionedSerializer.cs"));
|
|
1501
|
+
const contractResolverPath = resolvePath(joinPaths(genPath, "VersionedContractResolver.cs"));
|
|
1502
|
+
const versionCollectionPath = resolvePath(joinPaths(genPath, `${service}ServiceVersions.cs`));
|
|
1503
|
+
const templatePath = resolvePath(joinPaths(rootPath, "..", "templates", options.controllerHost));
|
|
1504
|
+
const modelsPath = joinPaths(genPath, "models");
|
|
1505
|
+
if (!program.compilerOptions.noEmit && !program.hasError()) {
|
|
1506
|
+
await ensureCleanDirectory(genPath).catch((err) => reportDiagnostic(program, {
|
|
1507
|
+
code: "cleaning-dir",
|
|
1508
|
+
format: { error: err },
|
|
1509
|
+
target: NoTarget,
|
|
1510
|
+
}));
|
|
1511
|
+
await createDirIfNotExists(operationsPath).catch((err) => reportDiagnostic(program, {
|
|
1512
|
+
code: "creating-dir",
|
|
1513
|
+
format: { error: err },
|
|
1514
|
+
target: NoTarget,
|
|
1515
|
+
}));
|
|
1516
|
+
if (options.generatedCodeKind !== "controller") {
|
|
1517
|
+
await createDirIfNotExists(modelsPath).catch((err) => reportDiagnostic(program, {
|
|
1518
|
+
code: "creating-dir",
|
|
1519
|
+
format: { error: err },
|
|
1520
|
+
target: NoTarget,
|
|
1521
|
+
}));
|
|
1522
|
+
}
|
|
1523
|
+
if (options.generatedCodeKind !== "model" &&
|
|
1524
|
+
outputModel.versions &&
|
|
1525
|
+
outputModel.versions.length > 1) {
|
|
1526
|
+
await program.host.writeFile(versionCollectionPath, await compileHandlebarsTemplate(joinPaths(templatePath, "versionCollection.sq"), outputModel));
|
|
1527
|
+
await program.host.writeFile(contractResolverPath, await compileHandlebarsTemplate(joinPaths(templatePath, "versionedContractResolver.sq"), outputModel));
|
|
1528
|
+
await program.host.writeFile(serializerPath, await compileHandlebarsTemplate(joinPaths(templatePath, "versionedSerializer.sq"), outputModel));
|
|
1529
|
+
}
|
|
1530
|
+
await generateSingleDirectory(rootPath, operationsPath).catch((err) => reportDiagnostic(program, {
|
|
1531
|
+
code: "creating-dir",
|
|
1532
|
+
format: { error: err },
|
|
1533
|
+
target: NoTarget,
|
|
1534
|
+
}));
|
|
1535
|
+
if (registrationOutputPath) {
|
|
1536
|
+
if (resolvePath(registrationOutputPath) !== resolvePath(genPath)) {
|
|
1537
|
+
await ensureCleanDirectory(registrationOutputPath).catch((err) => reportDiagnostic(program, {
|
|
1538
|
+
code: "cleaning-dir",
|
|
1539
|
+
format: { error: err },
|
|
1540
|
+
target: NoTarget,
|
|
1541
|
+
}));
|
|
1542
|
+
await createDirIfNotExists(joinPaths(registrationOutputPath, outputModel.providerNamespace)).catch((err) => reportDiagnostic(program, {
|
|
1543
|
+
code: "creating-dir",
|
|
1544
|
+
format: { error: err },
|
|
1545
|
+
target: NoTarget,
|
|
1546
|
+
}));
|
|
1547
|
+
}
|
|
1548
|
+
await generateResourceProviderReg(outputModel);
|
|
1549
|
+
}
|
|
1550
|
+
if (options.generatedCodeKind !== "model") {
|
|
1551
|
+
await program.host.writeFile(routesPath, await compileHandlebarsTemplate(joinPaths(templatePath, "serviceRoutingConstants.sq"), outputModel));
|
|
1552
|
+
for (const resource of outputModel.resources) {
|
|
1553
|
+
await generateResource(resource).catch((error) => {
|
|
1554
|
+
var _a, _b;
|
|
1555
|
+
return reportDiagnostic(program, {
|
|
1556
|
+
code: "generating-resource",
|
|
1557
|
+
format: {
|
|
1558
|
+
namespace: (_a = resource === null || resource === void 0 ? void 0 : resource.nameSpace) !== null && _a !== void 0 ? _a : "",
|
|
1559
|
+
resourceName: (_b = resource === null || resource === void 0 ? void 0 : resource.name) !== null && _b !== void 0 ? _b : "",
|
|
1560
|
+
error,
|
|
1561
|
+
},
|
|
1562
|
+
target: NoTarget,
|
|
1563
|
+
});
|
|
1564
|
+
});
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
if (options.generatedCodeKind !== "controller") {
|
|
1568
|
+
outputModel.models = outputModel.models.filter((m) => !outputModel.enumerations.some((e) => e.name === m.name));
|
|
1569
|
+
for (const model of outputModel.models) {
|
|
1570
|
+
tracer.trace("info", `Rendering model ${model.nameSpace}.${model.name}`, model.sourceNode);
|
|
1571
|
+
await generateModel(model).catch((error) => {
|
|
1572
|
+
var _a, _b;
|
|
1573
|
+
return reportDiagnostic(program, {
|
|
1574
|
+
code: "generating-model",
|
|
1575
|
+
format: {
|
|
1576
|
+
namespace: (_a = model === null || model === void 0 ? void 0 : model.nameSpace) !== null && _a !== void 0 ? _a : "",
|
|
1577
|
+
modelName: (_b = model === null || model === void 0 ? void 0 : model.name) !== null && _b !== void 0 ? _b : "",
|
|
1578
|
+
error,
|
|
1579
|
+
},
|
|
1580
|
+
target: NoTarget,
|
|
1581
|
+
});
|
|
1582
|
+
});
|
|
1583
|
+
}
|
|
1584
|
+
for (const enumeration of (_a = outputModel.enumerations) !== null && _a !== void 0 ? _a : []) {
|
|
1585
|
+
tracer.trace("info", `Rendering enum ${enumeration.name}`, enumeration.sourceNode);
|
|
1586
|
+
await generateEnum(enumeration);
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
//# sourceMappingURL=generate.js.map
|