Skip to content

Commit

Permalink
Refactor resource operation generation to accept customization
Browse files Browse the repository at this point in the history
  • Loading branch information
Pan Shao committed Dec 17, 2024
1 parent fbd40b8 commit 120eabd
Show file tree
Hide file tree
Showing 334 changed files with 15,917 additions and 4,810 deletions.
26 changes: 13 additions & 13 deletions packages/extensions/openapi-to-typespec/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,16 @@
"@azure-tools/codegen": "~2.10.0",
"@autorest/extension-base": "~3.6.0",
"@autorest/codemodel": "~4.20.0",
"@typespec/compiler": "^0.62.0",
"@typespec/rest": "^0.62.0",
"@typespec/http": "^0.62.0",
"@typespec/versioning": "^0.62.0",
"@typespec/prettier-plugin-typespec": "^0.62.0",
"@azure-tools/typespec-azure-core": "^0.48.0",
"@azure-tools/typespec-autorest": "^0.48.0",
"@azure-tools/typespec-azure-resource-manager": "^0.48.0",
"@typespec/openapi": "^0.62.0",
"@typespec/openapi3": "^0.62.0",
"@typespec/compiler": "^0.63.0",
"@typespec/rest": "^0.63.0",
"@typespec/http": "^0.63.0",
"@typespec/versioning": "^0.63.0",
"@typespec/prettier-plugin-typespec": "^0.63.0",
"@azure-tools/typespec-azure-core": "^0.49.0",
"@azure-tools/typespec-autorest": "^0.49.0",
"@azure-tools/typespec-azure-resource-manager": "^0.49.0",
"@typespec/openapi": "^0.63.0",
"@typespec/openapi3": "^0.63.0",
"prettier": "~3.1.0",
"lodash": "~4.17.20",
"pluralize": "^8.0.0",
Expand All @@ -68,9 +68,9 @@
"fs-extra": "^10.1.0",
"@types/fs-extra": "^9.0.13",
"chalk": "^4.1.0",
"@azure-tools/typespec-autorest": "^0.48.0",
"@azure-tools/typespec-client-generator-core": "^0.48.0",
"@azure-tools/typespec-azure-rulesets": "^0.48.0",
"@azure-tools/typespec-autorest": "^0.49.0",
"@azure-tools/typespec-client-generator-core": "^0.49.0",
"@azure-tools/typespec-azure-rulesets": "^0.49.0",
"webpack-cli": "~5.1.4",
"webpack": "~5.89.0",
"@typescript-eslint/eslint-plugin": "^6.11.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export async function emitTypespecConfig(filePath: string, programDetails: Types
"@azure-tools/typespec-autorest":
azure-resource-provider-folder: "data-plane"
emitter-output-dir: "{project-root}/.."
examples-directory: "{project-root}/examples"
examples-dir: "{project-root}/examples"
output-file: "{azure-resource-provider-folder}/{service-name}/{version-status}/{version}/openapi.json"
# Uncomment this line and add "@azure-tools/typespec-python" to your package.json to generate Python code
# "@azure-tools/typespec-python":
Expand Down Expand Up @@ -43,7 +43,7 @@ options:
emitter-output-dir: "{project-root}/.."
azure-resource-provider-folder: "resource-manager"
output-file: "{azure-resource-provider-folder}/{service-name}/{version-status}/{version}/${swaggerName}"
examples-directory: "{project-root}/examples"${
examples-dir: "{project-root}/examples"${
isFullCompatible
? `
arm-resource-flattening: true`
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import { Case } from "change-case-all";
import { TypespecOperation, TspArmResource, TypespecProgram } from "interfaces";
import {
TypespecOperation,
TspArmResource,
TypespecProgram,
isArmResourceActionOperation,
TypespecTemplateModel,
TypespecVoidType,
TspLroHeaders,
} from "../interfaces";
import _ from "lodash";
import pluralize from "pluralize";
import { getOptions } from "../options";
Expand All @@ -9,7 +17,8 @@ import { generateDocs } from "../utils/docs";
import { getLogger } from "../utils/logger";
import { getModelPropertiesDeclarations } from "../utils/model-generation";
import { generateSuppressions } from "../utils/suppressions";
import { generateOperation } from "./generate-operations";
import { generateOperation, generateParameters } from "./generate-operations";
import { generateParameter } from "./generate-parameter";

const logger = () => getLogger("generate-arm-resource");

Expand All @@ -29,7 +38,7 @@ export function generateArmResource(resource: TspArmResource): string {
}

for (const o of resource.resourceOperations) {
for (const d of o.customizations ?? []) {
for (const d of o.augmentedDecorators ?? []) {
definitions.push(`${d}`);
}
}
Expand Down Expand Up @@ -104,14 +113,27 @@ function generateArmResourceOperation(resource: TspArmResource): string {
if (isFullCompatible && operation.suppressions) {
definitions.push(...generateSuppressions(operation.suppressions));
}
if (operation.kind === "ArmResourceExists") {
definitions.push(`op ${operation.name}(${operation.parameters.join(",")}): ${operation.responses.join("|")}`);
} else if (operation.templateParameters?.length) {
definitions.push(`${operation.name} is ${operation.kind}<${(operation.templateParameters ?? []).join(",")}>`);

if (isArmResourceActionOperation(operation)) {
definitions.push(
`${operation.name} is ${operation.kind}<${operation.resource}, ${operation.request}, ${generateArmResponse(
operation.response,
)}${operation.baseParameters ? `, BaseParameters = ${operation.baseParameters[0]}` : ""}${
operation.parameters ? `, Parameters = { ${generateParameters(operation.parameters)} }` : ""
}${operation.lroHeaders ? `, LroHeaders = ${generateLroHeaders(operation.lroHeaders)}` : ""}>`,
);
} else {
definitions.push(`${operation.name} is ${operation.kind}`);
definitions.push(
`${operation.name} is ${operation.kind}<${operation.resource}${
operation.patchModel ? `, PatchModel = ${operation.patchModel}` : ""
}${operation.baseParameters ? `, BaseParameters = ${operation.baseParameters[0]}` : ""}${
operation.parameters ? `, Parameters = { ${generateParameters(operation.parameters)} }` : ""
}${operation.response ? `, Response = ${generateArmResponse(operation.response)}` : ""}${
operation.lroHeaders ? `, LroHeaders = ${generateLroHeaders(operation.lroHeaders)}` : ""
}>`,
);
}
definitions.push("");
definitions.push("\n");
}
for (const operation of resource.normalOperations) {
if (
Expand All @@ -131,6 +153,33 @@ function generateArmResourceOperation(resource: TspArmResource): string {
return definitions.join("\n");
}

function generateLroHeaders(lroHeaders: TspLroHeaders): string {
if (lroHeaders === "Azure-AsyncOperation") {
return "ArmAsyncOperationHeader & Azure.Core.Foundations.RetryAfterHeader";
} else if (lroHeaders === "Location") {
return "ArmLroLocationHeader & Azure.Core.Foundations.RetryAfterHeader";
}
throw new Error(`Unknown LRO header: ${lroHeaders}`);
}

function generateArmResponse(responses: TypespecTemplateModel[] | TypespecVoidType): string {
if (!Array.isArray(responses)) {
return "void";
}

return responses.map((r) => generateTemplateModel(r)).join(" | ");
}

function generateTemplateModel(templateModel: TypespecTemplateModel): string {
return `${templateModel.name}${
templateModel.arguments ? `<${templateModel.arguments.map((a) => a.name).join(",")}>` : ""
}${
templateModel.additionalProperties
? ` & { ${templateModel.additionalProperties.map((p) => generateParameter(p)).join(";")} }`
: ""
}`;
}

export function generateArmResourceExamples(resource: TspArmResource): Record<string, string> {
const formalOperationGroupName = pluralize(resource.name);
const examples: Record<string, string> = {};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,15 +173,14 @@ export function generateOperationGroup(operationGroup: TypespecOperationGroup) {

statements.push(`${doc}`);
const hasInterface = Boolean(name);
const hasProvider =
operations.find((o) => (o as TspArmProviderActionOperation).kind === "ArmProviderActionAsync") !== undefined;
const hasProvider = operations.find((o) => (o as TspArmProviderActionOperation).kind !== undefined) !== undefined;
if (hasProvider && hasInterface) {
statements.push(`@armResourceOperations`);
}
hasInterface && statements.push(`interface ${name} {`);

for (const operation of operations) {
if ((operation as TspArmProviderActionOperation).kind === "ArmProviderActionAsync") {
if ((operation as TspArmProviderActionOperation).kind !== undefined) {
statements.push(generateProviderAction(operation as TspArmProviderActionOperation));
} else {
statements.push(generateOperation(operation as TypespecOperation, operationGroup));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { TypespecParameter } from "../interfaces";
import { getOptions } from "../options";
import { generateDecorators } from "../utils/decorators";
import { generateDocs } from "../utils/docs";
import { generateSuppressionForDocumentRequired } from "../utils/suppressions";
import { generateSuppressionForDocumentRequired, generateSuppressions } from "../utils/suppressions";

const _ARM_PARAM_REPLACEMENTS: { [key: string]: string } = {
subscriptionId: "...SubscriptionIdParameter",
Expand All @@ -11,15 +11,15 @@ const _ARM_PARAM_REPLACEMENTS: { [key: string]: string } = {
};

export function generateParameter(parameter: TypespecParameter): string {
const { isArm, isFullCompatible } = getOptions();
const { isArm } = getOptions();
if (isArm && _ARM_PARAM_REPLACEMENTS[parameter.name] !== undefined) {
return _ARM_PARAM_REPLACEMENTS[parameter.name];
}
const definitions: string[] = [];
const doc = generateDocs(parameter);
if (doc === "" && isFullCompatible) definitions.push(generateSuppressionForDocumentRequired());
definitions.push(doc);

parameter.suppressions && definitions.push(...generateSuppressions(parameter.suppressions));
const decorators = generateDecorators(parameter.decorators);
decorators && definitions.push(decorators);
let defaultValue = "";
Expand Down
117 changes: 82 additions & 35 deletions packages/extensions/openapi-to-typespec/src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ export interface WithSummary {
summary?: string;
}

export interface WithDecorators {
decorators?: TypespecDecorator[];
clientDecorators?: TypespecDecorator[];
}

export interface TypespecOperationGroup extends WithDoc {
name: string;
operations: (TypespecOperation | TspArmProviderActionOperation)[];
Expand Down Expand Up @@ -107,6 +112,17 @@ export interface TypespecDataType extends WithDoc, WithFixMe, WithSuppressDirect
name: string;
}

export interface TypespecVoidType extends TypespecDataType {
kind: "void";
name: "_";
}

export interface TypespecTemplateModel extends TypespecDataType {
kind: "template";
arguments?: TypespecDataType[];
additionalProperties?: TypespecParameter[];
}

export interface TypespecWildcardType extends TypespecDataType {
kind: "wildcard";
}
Expand Down Expand Up @@ -211,38 +227,46 @@ export function isFirstLevelResource(value: string): value is FirstLevelResource
return FIRST_LEVEL_RESOURCE.includes(value as FirstLevelResource);
}

export interface TspArmResourceOperationBase extends WithDoc, WithFixMe, WithSuppressDirectives {
kind: string;
export type TspArmResourceOperation =
| TspArmResourceActionOperation
| TspArmResourceListOperation
| TspArmResourceLifeCycleOperation;

export interface TspArmResourceOperationBase
extends WithDoc,
WithSummary,
WithDecorators,
WithFixMe,
WithSuppressDirectives {
kind: TspArmOperationType;
name: string;
templateParameters?: string[];
decorators?: TypespecDecorator[];
clientDecorators?: TypespecDecorator[];
resource: string;
baseParameters?: string[];
parameters?: TypespecParameter[];
response?: TypespecTemplateModel[] | TypespecVoidType;
operationId?: string;
lroHeaders?: TspLroHeaders;
examples?: Record<string, Record<string, unknown>>;
customizations?: string[];
augmentedDecorators?: string[];
patchModel?: string;
}

// TO-DO: consolidate with other templates
export interface TspArmProviderActionOperation extends WithDoc, WithSummary {
kind: "ArmProviderActionAsync";
name: string;
action?: string;
responses?: string[];
verb: string;
scope?: "TenantActionScope" | "SubscriptionActionScope";
parameters: TypespecParameter[];
request?: TypespecParameter;
decorators?: TypespecDecorator[];
export interface TspArmResourceActionOperation extends TspArmResourceOperationBase {
kind: "ArmResourceActionSync" | "ArmResourceActionAsync";
request: string;
response: TypespecTemplateModel[] | TypespecVoidType;
}

export type TspArmResourceOperation =
| TspArmResourceListOperation
| TspArmResourceNonListOperation
| TspArmResourceExistsOperation;
export function isArmResourceActionOperation(
operation: TspArmResourceOperation,
): operation is TspArmResourceActionOperation {
return operation.kind === "ArmResourceActionSync" || operation.kind === "ArmResourceActionAsync";
}

export interface TspArmResourceNonListOperation extends TspArmResourceOperationBase {
export interface TspArmResourceLifeCycleOperation extends TspArmResourceOperationBase {
kind:
| "ArmResourceRead"
| "ArmResourceCheckExistence"
| "ArmResourceCreateOrReplaceSync"
| "ArmResourceCreateOrReplaceAsync"
| "ArmResourcePatchSync"
Expand All @@ -252,25 +276,47 @@ export interface TspArmResourceNonListOperation extends TspArmResourceOperationB
| "ArmCustomPatchSync"
| "ArmCustomPatchAsync"
| "ArmResourceDeleteSync"
| "ArmResourceDeleteAsync"
| "ArmResourceDeleteWithoutOkAsync"
| "ArmResourceActionSync"
| "ArmResourceActionNoContentSync"
| "ArmResourceActionAsync"
| "ArmResourceActionNoResponseContentAsync"
| "checkGlobalNameAvailability"
| "checkLocalNameAvailability"
| "checkNameAvailability";
| "ArmResourceDeleteWithoutOkAsync";
}

export interface TspArmResourceListOperation extends TspArmResourceOperationBase {
kind: "ArmResourceListByParent" | "ArmListBySubscription" | "ArmResourceListAtScope";
}

export interface TspArmResourceExistsOperation extends TspArmResourceOperationBase {
kind: "ArmResourceExists";
parameters: string[];
responses: string[];
export type TspLroHeaders = "Azure-AsyncOperation" | "Location";
export type TspArmOperationType =
| "ArmResourceRead"
| "ArmResourceCheckExistence"
| "ArmResourceCreateOrReplaceSync"
| "ArmResourceCreateOrReplaceAsync"
| "ArmResourcePatchSync"
| "ArmResourcePatchAsync"
| "ArmTagsPatchSync"
| "ArmTagsPatchAsync"
| "ArmCustomPatchSync"
| "ArmCustomPatchAsync"
| "ArmResourceDeleteSync"
| "ArmResourceDeleteWithoutOkAsync"
| "ArmResourceActionSync"
| "ArmResourceActionAsync"
| "checkGlobalNameAvailability"
| "checkLocalNameAvailability"
| "checkNameAvailability"
| "ArmResourceListByParent"
| "ArmListBySubscription"
| "ArmResourceListAtScope";

// TO-DO: consolidate with other templates
export interface TspArmProviderActionOperation extends WithDoc, WithSummary {
kind: "ArmProviderActionAsync" | "ArmProviderActionSync";
name: string;
action?: string;
responses?: string[];
verb: string;
scope?: "TenantActionScope" | "SubscriptionActionScope";
parameters: TypespecParameter[];
request?: TypespecParameter;
decorators?: TypespecDecorator[];
}

export interface TspArmResource extends TypespecObject {
Expand All @@ -282,6 +328,7 @@ export interface TspArmResource extends TypespecObject {
propertiesPropertyClientDecorator: TypespecDecorator[];
resourceParent?: TspArmResource;
resourceOperations: TspArmResourceOperation[];
// TO-DO: delete
normalOperations: TypespecOperation[];
optionalStandardProperties: string[];
baseModelName?: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ export function createClientNameDecorator(target: string, value: string): Typesp
name: "clientName",
module: "@azure-tools/typespec-client-generator-core",
namespace: "Azure.ClientGenerator.Core",
arguments: [target, value],
target,
arguments: [value],
};
}

Expand Down
Loading

0 comments on commit 120eabd

Please sign in to comment.