@autorest/openapi-to-typespec
Version:
Autorest plugin to scaffold a Typespec definition from an OpenAPI document
282 lines (242 loc) • 9.85 kB
text/typescript
import { Case } from "change-case-all";
import _ from "lodash";
import pluralize from "pluralize";
import {
TspArmResource,
TypespecTemplateModel,
TypespecVoidType,
TypespecParameter,
TypespecDataType,
TspArmResourceOperationGroup,
TspArmOperationType,
} from "../interfaces";
import { getOptions } from "../options";
import { generateAugmentedDecorators, generateDecorators } from "../utils/decorators";
import { generateDocs } from "../utils/docs";
import { getLogger } from "../utils/logger";
import { generateLroHeaders } from "../utils/lro";
import {
generateTemplateModel,
getModelPropertyDeclarations,
getSpreadExpressionDecalaration,
} from "../utils/model-generation";
import { generateSuppressions } from "../utils/suppressions";
import { generateParameters } from "./generate-operations";
import { generateParameter } from "./generate-parameter";
const logger = () => getLogger("generate-arm-resource");
export function generateArmResource(resource: TspArmResource): string {
const definitions: string[] = [];
definitions.push(generateArmResourceModel(resource));
definitions.push("\n");
definitions.push(generateArmResourceOperationGroups(resource));
definitions.push("\n");
for (const a of resource.augmentDecorators ?? []) {
definitions.push(generateAugmentedDecorators(a.target!, [a]));
}
for (const o of resource.resourceOperationGroups.flatMap((g) => g.resourceOperations)) {
for (const d of o.augmentedDecorators ?? []) {
definitions.push(`${d}`);
}
}
return definitions.join("\n");
}
function generateArmResourceModel(resource: TspArmResource): string {
const definitions: string[] = [];
for (const fixme of resource.fixMe ?? []) {
definitions.push(fixme);
}
const doc = generateDocs(resource);
definitions.push(doc);
const decorators = generateDecorators(resource.decorators);
decorators && definitions.push(decorators);
if (resource.resourceParent) {
definitions.push(`@parentResource(${resource.resourceParent.name})`);
}
if (resource.locationParent) {
definitions.push(`@parentResource(${resource.locationParent})`);
}
definitions.push(
`model ${resource.name} is Azure.ResourceManager.${resource.resourceKind}<${resource.propertiesModelName}${
resource.propertiesPropertyRequired ? ", false" : ""
}> {`,
);
for (const property of resource.properties) {
if (property.kind === "property") definitions.push(...getModelPropertyDeclarations(property));
else if (property.kind === "spread") definitions.push(getSpreadExpressionDecalaration(property));
}
definitions.push("}\n");
return definitions.join("\n");
}
function generateArmResourceOperationGroups(resource: TspArmResource): string {
const definitions: string[] = [];
for (const operationGroup of resource.resourceOperationGroups) {
definitions.push(generateArmResourceOperationGroup(operationGroup));
}
return definitions.join("\n");
}
function generateArmResourceOperationGroup(operationGroup: TspArmResourceOperationGroup): string {
const { isFullCompatible } = getOptions();
const definitions: string[] = [];
if (operationGroup.isLegacy) {
definitions.push("@armResourceOperations");
definitions.push(
`interface ${
operationGroup.legacyOperationGroup!.interfaceName
} extends Azure.ResourceManager.Legacy.LegacyOperations<{${operationGroup.legacyOperationGroup!.parentParameters.join(
";",
)}}, ${operationGroup.legacyOperationGroup!.resourceTypeParameter}> {}`,
);
definitions.push("\n");
}
definitions.push("@armResourceOperations");
definitions.push(`interface ${operationGroup.interfaceName} {`);
for (const operation of operationGroup.resourceOperations) {
for (const fixme of operation.fixMe ?? []) {
definitions.push(fixme);
}
definitions.push(generateDocs(operation));
const decorators = generateDecorators(operation.decorators);
decorators && definitions.push(decorators);
if (
isFullCompatible &&
operation.operationId &&
operation.operationId !== getGeneratedOperationId(operationGroup.interfaceName, operation.name)
) {
definitions.push(`@operationId("${operation.operationId}")`);
definitions.push(`#suppress "@azure-tools/typespec-azure-core/no-openapi" "non-standard operations"`);
}
if (isFullCompatible && operation.suppressions) {
definitions.push(...generateSuppressions(operation.suppressions));
}
const operationKind = operationGroup.isLegacy
? `${operationGroup.legacyOperationGroup!.interfaceName}.${getLegacyOperationKind(operation.kind)}`
: operation.kind;
if (operation.kind === "ArmResourceActionSync" || operation.kind === "ArmResourceActionAsync") {
definitions.push(
`${operation.name} is ${operationKind}<${operation.resource}, ${generateArmRequest(
operation.request,
)}, ${generateArmResponse(operation.response)}${
operation.baseParameters && !operationGroup.isLegacy
? `, BaseParameters = ${operation.baseParameters[0]}`
: ""
}${operation.parameters ? `, Parameters = { ${generateParameters(operation.parameters)} }` : ""}${
operation.lroHeaders ? `, LroHeaders = ${generateLroHeaders(operation.lroHeaders)}` : ""
}>;`,
);
} else if (operation.kind === "ArmResourceActionAsyncBase") {
definitions.push(
`${operation.name} is ${operationKind}<${operation.resource}, ${generateArmRequest(
operation.request,
)}, ${generateArmResponse(operation.response)}, BaseParameters = ${operation.baseParameters![0]}${
operation.parameters ? `, Parameters = { ${generateParameters(operation.parameters)} }` : ""
}>;`,
);
} else {
definitions.push(
`${operation.name} is ${operationKind}<${operation.resource}${
operation.patchModel ? `, PatchModel = ${operation.patchModel}` : ""
}${
operation.baseParameters && !operationGroup.isLegacy
? `, BaseParameters = ${operation.baseParameters[0]}`
: ""
}${operation.parameters ? `, Parameters = { ${generateParameters(operation.parameters)} }` : ""}${
operation.response ? `, Response = ${generateArmResponse(operation.response)}` : ""
}${operation.lroHeaders ? `, LroHeaders = ${generateLroHeaders(operation.lroHeaders)}` : ""}>;`,
);
}
definitions.push("\n");
}
definitions.push("}");
return definitions.join("\n");
}
function getLegacyOperationKind(kind: TspArmOperationType): string {
switch (kind) {
case "ArmResourceRead":
return "Read";
case "ArmResourceCheckExistence":
return "CheckExistence";
case "ArmResourceCreateOrReplaceSync":
return "CreateOrUpdateSync";
case "ArmResourceCreateOrReplaceAsync":
return "CreateOrUpdateAsync";
case "ArmResourcePatchSync":
return "PatchSync";
case "ArmResourcePatchAsync":
return "PatchAsync";
case "ArmCustomPatchSync":
return "CustomPatchSync";
case "ArmCustomPatchAsync":
return "CustomPatchAsync";
case "ArmResourceDeleteSync":
return "DeleteSync";
case "ArmResourceDeleteWithoutOkAsync":
return "DeleteWithoutOkAsync";
case "ArmResourceActionSync":
return "ActionSync";
case "ArmResourceActionAsync":
return "ActionAsync";
case "ArmResourceActionAsyncBase":
return "ActionAsyncBase";
case "ArmResourceListByParent":
return "List";
case "ArmListBySubscription":
return "ListBySubscription";
case "ArmResourceListAtScope":
return "ListAtScope";
}
}
function generateArmRequest(request: TypespecParameter | TypespecVoidType | TypespecDataType): string {
if (request.kind === "void") {
return "void";
}
if (request.kind === "parameter") {
return `{${generateParameter(request as TypespecParameter)}}`;
}
return request.name;
}
function generateArmResponse(responses: TypespecTemplateModel[] | TypespecVoidType): string {
if (!Array.isArray(responses)) {
return "void";
}
return responses.map((r) => generateTemplateModel(r)).join(" | ");
}
export function generateArmResourceExamples(resource: TspArmResource): Record<string, string> {
const formalOperationGroupName = pluralize(resource.name);
const examples: Record<string, string> = {};
for (const operation of resource.resourceOperationGroups.flatMap((g) => g.resourceOperations)) {
generateExamples(
operation.examples ?? {},
operation.operationId ?? getGeneratedOperationId(formalOperationGroupName, operation.name),
examples,
);
}
return examples;
}
export function generateExamples(
examples: Record<string, Record<string, unknown>>,
operationId: string,
generatedExamples: Record<string, string>,
) {
const count = _.keys(examples).length;
for (const [title, example] of _.entries(examples)) {
example.operationId = operationId;
example.title = title;
let filename = undefined;
const originalFile = example["x-ms-original-file"] as string;
if (originalFile) {
const exampleIndex = originalFile.lastIndexOf("/examples/");
if (exampleIndex !== -1) {
filename = originalFile.substring(exampleIndex + "/examples/".length);
delete example["x-ms-original-file"];
generatedExamples[filename] = JSON.stringify(example, null, 2);
continue;
}
}
logger().info(
`Cannot find the example original path or the path isn't in the examples folder for operation ${operationId}`,
);
}
}
export function getGeneratedOperationId(operationGroupName: string, operationName: string): string {
return `${Case.pascal(operationGroupName)}_${Case.pascal(operationName)}`;
}