diff --git a/.gitignore b/.gitignore index fab321db21..7a37536a56 100644 --- a/.gitignore +++ b/.gitignore @@ -296,3 +296,4 @@ regression-tests/output # TS incremental build cache *.tsbuildinfo +*.njsproj diff --git a/common/changes/@autorest/openapi-to-typespec/openai-to-typespec-csharp-rename_2024-03-12-05-16.json b/common/changes/@autorest/openapi-to-typespec/openai-to-typespec-csharp-rename_2024-03-12-05-16.json new file mode 100644 index 0000000000..984d48d13d --- /dev/null +++ b/common/changes/@autorest/openapi-to-typespec/openai-to-typespec-csharp-rename_2024-03-12-05-16.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@autorest/openapi-to-typespec", + "comment": "support generating csharp rename decorator when converting to tsp", + "type": "minor" + } + ], + "packageName": "@autorest/openapi-to-typespec" +} \ No newline at end of file diff --git a/packages/extensions/openapi-to-typespec/convert.ps1 b/packages/extensions/openapi-to-typespec/convert.ps1 index 528dbf7587..8e32fbccf3 100644 --- a/packages/extensions/openapi-to-typespec/convert.ps1 +++ b/packages/extensions/openapi-to-typespec/convert.ps1 @@ -10,20 +10,18 @@ #> param( - [Parameter(Mandatory)] - [string] - # Specifies the swagger config file, not the swagger json, but the readme config. + [Parameter(Mandatory = $true, HelpMessage = "Specifies the swagger config file (not the swagger json, but the readme config) or autorest.md file in the azure-sdk-for-net repo if .net related configuration is expected to be included.")] + [string] $swaggerConfigFile, + [Parameter(Mandatory = $false, HelpMessage = "Specified the output folder, deafult to current folder.")] [string] - # Specified the output folder, deafult to current folder. $outputFolder, + [Parameter(Mandatory = $false, HelpMessage = "Specified the csharp codegen, default to https://aka.ms/azsdk/openapi-to-typespec-csharp.")] [string] - # Specified the csharp codegen, default to https://aka.ms/azsdk/openapi-to-typespec-csharp. $csharpCodegen = "https://aka.ms/azsdk/openapi-to-typespec-csharp", + [Parameter(Mandatory = $false, HelpMessage = "Specified the converter codegen, default to https://aka.ms/azsdk/openapi-to-typespec.")] [string] - # Specified the converter codegen, default to https://aka.ms/azsdk/openapi-to-typespec. - $converterCodegen = "." -) + $converterCodegen = ".") function GenerateMetadata () { @@ -42,7 +40,7 @@ function GenerateMetadata () function DoConvert () { Write-Host "##Converting from swagger to tsp with in $outputFolder with $converterCodegen" - $cmd = "autorest --version=3.10.1 --openapi-to-typespec --isAzureSpec --isArm --use=`"$converterCodegen`" --output-folder=$outputFolder $swaggerConfigFile" + $cmd = "autorest --version=3.10.1 --openapi-to-typespec --csharp=false --isAzureSpec --isArm --use=`"$converterCodegen`" --output-folder=$outputFolder $swaggerConfigFile" Write-Host "$cmd" Invoke-Expression $cmd if ($LASTEXITCODE) { exit $LASTEXITCODE } diff --git a/packages/extensions/openapi-to-typespec/src/emiters/emit-client.ts b/packages/extensions/openapi-to-typespec/src/emiters/emit-client.ts index 78b17bce01..36ab539981 100644 --- a/packages/extensions/openapi-to-typespec/src/emiters/emit-client.ts +++ b/packages/extensions/openapi-to-typespec/src/emiters/emit-client.ts @@ -1,5 +1,9 @@ import { getSession } from "../autorest-session"; -import { generateArmResourceClientDecorator, generateObjectClientDecorator } from "../generate/generate-client"; +import { + generateArmResourceClientDecorator, + generateEnumClientDecorator, + generateObjectClientDecorator, +} from "../generate/generate-client"; import { TypespecProgram } from "../interfaces"; import { getOptions } from "../options"; import { formatTypespecFile } from "../utils/format"; @@ -34,8 +38,14 @@ function generateClient(program: TypespecProgram) { .filter((r) => r !== "") .join("\n\n") : ""; - if (objects === "" && armResources === "") { + + const enums = models.enums + .map(generateEnumClientDecorator) + .filter((r) => r !== "") + .join("\n\n"); + + if (objects === "" && armResources === "" && enums === "") { return ""; } - return [imports, "\n", namespaces, "\n", objects, "\n", armResources].join("\n"); + return [imports, "\n", namespaces, "\n", objects, "\n", armResources, "\n", enums].join("\n"); } diff --git a/packages/extensions/openapi-to-typespec/src/emiters/emit-main.ts b/packages/extensions/openapi-to-typespec/src/emiters/emit-main.ts index 5706d088e1..8b858c2b08 100644 --- a/packages/extensions/openapi-to-typespec/src/emiters/emit-main.ts +++ b/packages/extensions/openapi-to-typespec/src/emiters/emit-main.ts @@ -51,8 +51,8 @@ function getArmResourceImports(program: TypespecProgram): string[] { const resourceMetadata = getArmResourcesMetadata(); const imports: string[] = []; - for (const resource in resourceMetadata) { - imports.push(`import "./${resourceMetadata[resource].SwaggerModelName}.tsp";`); + for (const resource in resourceMetadata.Resources) { + imports.push(`import "./${resourceMetadata.Resources[resource].SwaggerModelName}.tsp";`); } if (program.operationGroups.length > 0) { diff --git a/packages/extensions/openapi-to-typespec/src/generate/generate-client.ts b/packages/extensions/openapi-to-typespec/src/generate/generate-client.ts index c340012081..afba783d6c 100644 --- a/packages/extensions/openapi-to-typespec/src/generate/generate-client.ts +++ b/packages/extensions/openapi-to-typespec/src/generate/generate-client.ts @@ -1,10 +1,12 @@ import pluralize from "pluralize"; -import { TspArmResource, TypespecObject } from "../interfaces"; +import { TspArmResource, TypespecObject, TypespecEnum, TypespecOperation } from "../interfaces"; import { generateAugmentedDecorators } from "../utils/decorators"; export function generateObjectClientDecorator(typespecObject: TypespecObject) { const definitions: string[] = []; + definitions.push(generateAugmentedDecorators(typespecObject.name, typespecObject.clientDecorators)); + for (const property of typespecObject.properties) { const decorators = generateAugmentedDecorators( `${typespecObject.name}.${property.name}`, @@ -16,11 +18,50 @@ export function generateObjectClientDecorator(typespecObject: TypespecObject) { return definitions.join("\n"); } +export function generateEnumClientDecorator(typespecEnum: TypespecEnum) { + const definitions: string[] = []; + + definitions.push(generateAugmentedDecorators(typespecEnum.name, typespecEnum.clientDecorators)); + + for (const choice of typespecEnum.members) { + const decorators = generateAugmentedDecorators(`${typespecEnum.name}.${choice.name}`, choice.clientDecorators); + decorators && definitions.push(decorators); + } + + return definitions.join("\n"); +} + +export function generateOperationClientDecorator(operation: TypespecOperation) { + const definitions: string[] = []; + + definitions.push(generateAugmentedDecorators(operation.name, operation.clientDecorators)); + + return definitions.join("\n"); +} + export function generateArmResourceClientDecorator(resource: TspArmResource): string { + const definitions: string[] = []; + const formalOperationGroupName = pluralize(resource.name); + let targetName = formalOperationGroupName; if (resource.name === formalOperationGroupName) { - return `@@clientName(${formalOperationGroupName}OperationGroup, "${formalOperationGroupName}")`; + targetName = `${formalOperationGroupName}OperationGroup}`; + definitions.push(`@@clientName(${formalOperationGroupName}OperationGroup, "${formalOperationGroupName}")`); } - return ""; + + if (resource.clientDecorators && resource.clientDecorators.length > 0) + definitions.push(generateAugmentedDecorators(resource.name, resource.clientDecorators)); + + for (const op of resource.resourceOperations) { + if (op.clientDecorators && op.clientDecorators.length > 0) + definitions.push(generateAugmentedDecorators(`${targetName}.${op.name}`, op.clientDecorators)); + } + + for (const property of resource.properties) { + const decorators = generateAugmentedDecorators(`${targetName}.${property.name}`, property.clientDecorators); + decorators && definitions.push(decorators); + } + + return definitions.join("\n"); } diff --git a/packages/extensions/openapi-to-typespec/src/interfaces.ts b/packages/extensions/openapi-to-typespec/src/interfaces.ts index cf3e2b9aad..c4d5e91b34 100644 --- a/packages/extensions/openapi-to-typespec/src/interfaces.ts +++ b/packages/extensions/openapi-to-typespec/src/interfaces.ts @@ -15,6 +15,7 @@ export interface TypespecOptions { export interface TypespecChoiceValue extends WithDoc { name: string; value: string | number | boolean; + clientDecorators?: TypespecDecorator[]; } export interface WithDoc { @@ -42,6 +43,7 @@ export interface TypespecOperation extends WithDoc, WithSummary, WithFixMe { operationGroupName?: string; operationId?: string; examples?: Record>; + clientDecorators?: TypespecDecorator[]; } export type ResourceKind = @@ -120,6 +122,7 @@ export interface TypespecEnum extends TypespecDataType { members: TypespecChoiceValue[]; isExtensible: boolean; decorators?: TypespecDecorator[]; + clientDecorators?: TypespecDecorator[]; } export interface WithFixMe { @@ -137,6 +140,7 @@ export interface TypespecParameter extends TypespecDataType { isOptional: boolean; type: string; decorators?: TypespecDecorator[]; + clientDecorators?: TypespecDecorator[]; location: TypespecParameterLocation; serializedName: string; defaultValue?: any; @@ -171,6 +175,7 @@ export interface TypespecObject extends TypespecDataType { extendedParents?: string[]; spreadParents?: string[]; decorators?: TypespecDecorator[]; + clientDecorators?: TypespecDecorator[]; alias?: TypespecAlias; } @@ -201,6 +206,7 @@ export interface TspArmResourceOperationBase extends WithDoc, WithFixMe { name: string; templateParameters?: string[]; decorators?: TypespecDecorator[]; + clientDecorators?: TypespecDecorator[]; operationId?: string; examples?: Record>; customizations?: string[]; diff --git a/packages/extensions/openapi-to-typespec/src/main.ts b/packages/extensions/openapi-to-typespec/src/main.ts index a46bd6274f..8b32c5706c 100644 --- a/packages/extensions/openapi-to-typespec/src/main.ts +++ b/packages/extensions/openapi-to-typespec/src/main.ts @@ -17,6 +17,7 @@ import { emitTypespecConfig } from "./emiters/emit-typespec-config"; import { getModel } from "./model"; import { pretransformArmResources } from "./pretransforms/arm-pretransform"; import { pretransformNames } from "./pretransforms/name-pretransform"; +import { pretransformRename } from "./pretransforms/rename-pretransform"; import { markErrorModels } from "./utils/errors"; import { markPagination } from "./utils/paging"; import { markResources } from "./utils/resources"; @@ -27,6 +28,7 @@ export async function processConverter(host: AutorestExtensionHost) { const codeModel = session.model; pretransformNames(codeModel); pretransformArmResources(codeModel); + pretransformRename(codeModel); markPagination(codeModel); markErrorModels(codeModel); markResources(codeModel); diff --git a/packages/extensions/openapi-to-typespec/src/pretransforms/rename-pretransform.ts b/packages/extensions/openapi-to-typespec/src/pretransforms/rename-pretransform.ts new file mode 100644 index 0000000000..2cb9bb01c1 --- /dev/null +++ b/packages/extensions/openapi-to-typespec/src/pretransforms/rename-pretransform.ts @@ -0,0 +1,136 @@ +import { + ChoiceSchema, + CodeModel, + ObjectSchema, + SealedChoiceSchema, + Schema, + ChoiceValue, + Property, + Parameter, + SchemaType, + Operation, +} from "@autorest/codemodel"; +import { TypespecDecorator } from "../interfaces"; +import { getOptions } from "../options"; +import { getLogger } from "../utils/logger"; +import { Metadata, getArmResourcesMetadata } from "../utils/resource-discovery"; + +type RenamableSchema = Schema | Property | Parameter | ChoiceValue | Operation; + +const logger = () => getLogger("rename-pretransform"); + +export function pretransformRename(codeModel: CodeModel): void { + const { isArm } = getOptions(); + if (!isArm) { + return; + } + + const metadata = getArmResourcesMetadata(); + + applyRenameMapping(metadata, codeModel); + applyOverrideOperationName(metadata, codeModel); +} + +export function createCSharpNameDecorator(schema: RenamableSchema): TypespecDecorator { + return { + name: "clientName", + module: "@azure-tools/typespec-client-generator-core", + namespace: "Azure.ClientGenerator.Core", + arguments: [schema.language.csharp!.name, "csharp"], + }; +} + +function parseNewCSharpNameAndSetToSchema(schema: RenamableSchema, renameValue: string) { + const newName = parseNewName(renameValue); + setSchemaCSharpName(schema, newName); +} + +function setSchemaCSharpName(schema: RenamableSchema, newName: string) { + if (!schema.language.csharp) + schema.language.csharp = { name: newName, description: schema.language.default.description }; + else schema.language.csharp.name = newName; +} + +function parseNewName(value: string) { + // TODO: format not supported + return value.split("|")[0].trim(); +} + +function applyOverrideOperationName(metadata: Metadata, codeModel: CodeModel) { + for (const opId in metadata.OverrideOperationName) { + const found = codeModel.operationGroups.flatMap((og) => og.operations).find((op) => op.operationId === opId); + if (found) parseNewCSharpNameAndSetToSchema(found, metadata.OverrideOperationName[opId]); + else + logger().warning( + `Can't find operation to rename for OverrideOperationName rule: ${opId}->${metadata.OverrideOperationName[opId]}`, + ); + } +} + +function applyRenameMapping(metadata: Metadata, codeModel: CodeModel) { + for (const key in metadata.RenameMapping) { + const subKeys = key + .split(".") + .map((s) => s.trim()) + .filter((s) => s.length > 0); + if (subKeys.length === 0) continue; + const lowerFirstSubKey = subKeys[0].toLowerCase(); + const value = metadata.RenameMapping[key]; + + const found: Schema | undefined = [ + ...(codeModel.schemas.choices ?? []), + ...(codeModel.schemas.sealedChoices ?? []), + ...(codeModel.schemas.objects ?? []), + ].find((o: Schema) => o.language.default.name.toLowerCase() === lowerFirstSubKey); + + if (!found) { + logger().warning(`Can't find object or enum for RenameMapping rule: ${key} -> ${value}`); + continue; + } + + if (found.type === SchemaType.Choice || found.type == SchemaType.SealedChoice) { + transformEnum(subKeys, value, found as ChoiceSchema | SealedChoiceSchema); + } else if (found.type === SchemaType.Object) { + transformObject(subKeys, value, found as ObjectSchema); + } else { + logger().error(`Unexpected schema type '${found.type}' found with key ${key}`); + } + } +} + +function transformEnum(keys: string[], value: string, target: ChoiceSchema | SealedChoiceSchema) { + if (keys.length === 1) parseNewCSharpNameAndSetToSchema(target, value); + else if (keys.length === 2) { + const lowerMemberValue = keys[1].toLowerCase(); + const found = target.choices.find((c) => c.language.default.name.toLowerCase() === lowerMemberValue); + if (found) parseNewCSharpNameAndSetToSchema(found, value); + else logger().warning(`Can't find enum member for RenameMapping rule: ${keys.join(".")} -> ${value}`); + } else { + logger().error(`Unexpected keys for enum RenameMapping: ${keys.join(".")}`); + } +} + +function transformObject(keys: string[], value: string, target: ObjectSchema) { + if (keys.length === 1) parseNewCSharpNameAndSetToSchema(target, value); + else if (keys.length === 2) { + const lowerPropertyName = keys[1].toLowerCase(); + const found = target.properties?.find((p) => p.language.default.name.toLowerCase() === lowerPropertyName); + if (found) parseNewCSharpNameAndSetToSchema(found, value); + else logger().warning(`Can't find object property for RenameMapping rule: ${keys.join(".")} -> ${value}`); + } else if (keys.length > 2) { + // handle flatten scenario + const lowerPropName = keys.pop()?.toLowerCase(); + let cur = target; + for (let i = 1; i < keys.length && cur; i++) { + const foundProp = cur.properties?.find((p) => p.language.default.name.toLowerCase() === keys[i].toLowerCase()); + cur = foundProp?.schema as ObjectSchema; + } + const foundProp = cur?.properties?.find((p) => p.language.default.name.toLowerCase() === lowerPropName); + if (foundProp) parseNewCSharpNameAndSetToSchema(foundProp, value); + else { + logger().warning(`Can't find object property for RenameMapping rule: ${keys.join(".")} -> ${value}`); + } + } else { + logger().error(`Unexpected keys for object property RenameMapping: ${keys.join(".")}`); + } +} diff --git a/packages/extensions/openapi-to-typespec/src/transforms/transform-arm-resources.ts b/packages/extensions/openapi-to-typespec/src/transforms/transform-arm-resources.ts index f27c1f72e9..5d5a23ce2e 100644 --- a/packages/extensions/openapi-to-typespec/src/transforms/transform-arm-resources.ts +++ b/packages/extensions/openapi-to-typespec/src/transforms/transform-arm-resources.ts @@ -14,6 +14,8 @@ import { isFirstLevelResource, } from "../interfaces"; import { getOptions, updateOptions } from "../options"; +import { createCSharpNameDecorator } from "../pretransforms/rename-pretransform"; +import { getOperationClientDecorators } from "../utils/decorators"; import { ArmResource, ArmResourceSchema, @@ -105,6 +107,11 @@ export function transformTspArmResource(schema: ArmResourceSchema): TspArmResour decorators.push({ name: "extensionResource" }); } + const armResourceOperations = operations[0]; + const otherOperations = operations[1]; + + const clientDecorators = buildResourceClientDecorators(schema, armResourceOperations, otherOperations); + return { fixMe, resourceKind: getResourceKind(schema), @@ -119,8 +126,9 @@ export function transformTspArmResource(schema: ArmResourceSchema): TspArmResour propertiesPropertyDescription, doc: schema.language.default.description, decorators, - resourceOperations: operations[0], - normalOperations: operations[1], + clientDecorators, + resourceOperations: armResourceOperations, + normalOperations: otherOperations, optionalStandardProperties: getArmCommonTypeVersion() ? getResourceOptionalStandardProperties(schema) : [], baseModelName, locationParent: getLocationParent(schema), @@ -190,6 +198,7 @@ function convertResourceReadOperation( kind: "ArmResourceRead", name: getOperationName(operation.OperationID), operationId: operation.OperationID, + clientDecorators: getOperationClientDecorators(swaggerOperation), templateParameters: baseParameters ? [resourceMetadata.SwaggerModelName, baseParameters] : [resourceMetadata.SwaggerModelName], @@ -206,6 +215,7 @@ function convertResourceExistsOperation(resourceMetadata: ArmResource): TspArmRe doc: swaggerOperation.language.default.description, kind: "ArmResourceExists", name: swaggerOperation.operationId ? getOperationName(swaggerOperation.operationId) : "exists", + clientDecorators: getOperationClientDecorators(swaggerOperation), operationId: swaggerOperation.operationId, parameters: [ `...ResourceInstanceParameters<${resourceMetadata.SwaggerModelName}, BaseParameters<${resourceMetadata.SwaggerModelName}>>`, @@ -256,6 +266,7 @@ function convertResourceCreateOrReplaceOperation( doc: operation.Description, kind: isLongRunning ? "ArmResourceCreateOrReplaceAsync" : "ArmResourceCreateOrReplaceSync", name: operationName, + clientDecorators: getOperationClientDecorators(swaggerOperation), operationId: operation.OperationID, templateParameters: templateParameters, examples: swaggerOperation.extensions?.["x-ms-examples"], @@ -343,6 +354,7 @@ function convertResourceUpdateOperation( doc: operation.Description, kind: kind as any, name: getOperationName(operation.OperationID), + clientDecorators: getOperationClientDecorators(swaggerOperation), operationId: operation.OperationID, templateParameters, examples: swaggerOperation.extensions?.["x-ms-examples"], @@ -379,6 +391,7 @@ function convertResourceDeleteOperation( : "ArmResourceDeleteWithoutOkAsync" : "ArmResourceDeleteSync", name: getOperationName(operation.OperationID), + clientDecorators: getOperationClientDecorators(swaggerOperation), operationId: operation.OperationID, templateParameters, examples: swaggerOperation.extensions?.["x-ms-examples"], @@ -415,6 +428,7 @@ function convertResourceListOperations( doc: operation.Description, kind: "ArmResourceListByParent", name: getOperationName(operation.OperationID), + clientDecorators: getOperationClientDecorators(swaggerOperation), operationId: operation.OperationID, templateParameters: templateParameters, examples: swaggerOperation.extensions?.["x-ms-examples"], @@ -448,6 +462,7 @@ function convertResourceListOperations( doc: operation.Description, kind: "ArmResourceListAtScope", name: getOperationName(operation.OperationID), + clientDecorators: getOperationClientDecorators(swaggerOperation), operationId: operation.OperationID, templateParameters, examples: swaggerOperation.extensions?.["x-ms-examples"], @@ -457,6 +472,7 @@ function convertResourceListOperations( doc: operation.Description, kind: "ArmListBySubscription", name: getOperationName(operation.OperationID), + clientDecorators: getOperationClientDecorators(swaggerOperation), operationId: operation.OperationID, templateParameters: [resourceMetadata.SwaggerModelName], examples: swaggerOperation.extensions?.["x-ms-examples"], @@ -483,6 +499,7 @@ function convertResourceListOperations( name: swaggerOperation.operationId ? getOperationName(swaggerOperation.operationId) : `listBy${resourceMetadata.Parents[0].replace(/Resource$/, "")}`, + clientDecorators: getOperationClientDecorators(swaggerOperation), operationId: swaggerOperation.operationId, templateParameters: baseParameters ? [resourceMetadata.SwaggerModelName, baseParameters] @@ -549,6 +566,7 @@ function convertResourceActionOperations( doc: operation.Description, kind: kind as any, name: operationName, + clientDecorators: getOperationClientDecorators(swaggerOperation), operationId: operation.OperationID, templateParameters, examples: swaggerOperation.extensions?.["x-ms-examples"], @@ -585,6 +603,7 @@ function convertCheckNameAvailabilityOperations( doc: operation.Description, kind: "checkLocalNameAvailability", name: getOperationName(operation.OperationID), + clientDecorators: getOperationClientDecorators(swaggerOperation), operationId: operation.OperationID, examples: swaggerOperation.extensions?.["x-ms-examples"], templateParameters: [request, response], @@ -594,6 +613,7 @@ function convertCheckNameAvailabilityOperations( doc: operation.Description, kind: "checkGlobalNameAvailability", name: getOperationName(operation.OperationID), + clientDecorators: getOperationClientDecorators(swaggerOperation), operationId: operation.OperationID, examples: swaggerOperation.extensions?.["x-ms-examples"], templateParameters: [request, response], @@ -860,6 +880,19 @@ function buildResourceDecorators(schema: ArmResourceSchema): TypespecDecorator[] return resourceModelDecorators; } +function buildResourceClientDecorators( + schema: ArmResourceSchema, + armResourceOperations: TspArmResourceOperation[], + normalOperations: TypespecOperation[], +): TypespecDecorator[] { + const clientDecorator: TypespecDecorator[] = []; + if (schema.language.csharp?.name) { + clientDecorator.push(createCSharpNameDecorator(schema)); + } + + return clientDecorator; +} + function getSingletonName(schema: ArmResourceSchema): string { const key = schema.resourceMetadata.ResourceKey; const pathLast = schema.resourceMetadata.GetOperations[0].Path.split("/").pop() ?? ""; diff --git a/packages/extensions/openapi-to-typespec/src/transforms/transform-choices.ts b/packages/extensions/openapi-to-typespec/src/transforms/transform-choices.ts index 946f35556c..e94a453900 100644 --- a/packages/extensions/openapi-to-typespec/src/transforms/transform-choices.ts +++ b/packages/extensions/openapi-to-typespec/src/transforms/transform-choices.ts @@ -1,7 +1,7 @@ import { ChoiceSchema, ChoiceValue, CodeModel, SchemaType, SealedChoiceSchema } from "@autorest/codemodel"; import { getDataTypes } from "../data-types"; import { TypespecChoiceValue, TypespecEnum } from "../interfaces"; -import { getEnumDecorators } from "../utils/decorators"; +import { getEnumChoiceClientDecorators, getEnumClientDecorators, getEnumDecorators } from "../utils/decorators"; import { transformValue } from "../utils/values"; export function transformEnum(schema: SealedChoiceSchema | ChoiceSchema, codeModel: CodeModel): TypespecEnum { @@ -12,6 +12,7 @@ export function transformEnum(schema: SealedChoiceSchema | ChoiceSchema, codeMod if (!typespecEnum) { typespecEnum = { decorators: getEnumDecorators(schema), + clientDecorators: getEnumClientDecorators(schema), doc: schema.language.default.description, kind: "enum", name: schema.language.default.name.replace(/-/g, "_"), @@ -36,6 +37,7 @@ function transformChoiceMember(member: ChoiceValue): TypespecChoiceValue { doc: member.language.default.description, name: member.language.default.name, value: transformValue(member.value), + clientDecorators: getEnumChoiceClientDecorators(member), }; } diff --git a/packages/extensions/openapi-to-typespec/src/transforms/transform-object.ts b/packages/extensions/openapi-to-typespec/src/transforms/transform-object.ts index 6c0b5b5aef..a02e59a3c7 100644 --- a/packages/extensions/openapi-to-typespec/src/transforms/transform-object.ts +++ b/packages/extensions/openapi-to-typespec/src/transforms/transform-object.ts @@ -14,7 +14,12 @@ import { get } from "lodash"; import { getDataTypes } from "../data-types"; import { TypespecObject, TypespecObjectProperty } from "../interfaces"; import { addCorePageAlias } from "../utils/alias"; -import { getModelDecorators, getPropertyClientDecorators, getPropertyDecorators } from "../utils/decorators"; +import { + getModelClientDecorators, + getModelDecorators, + getPropertyClientDecorators, + getPropertyDecorators, +} from "../utils/decorators"; import { getDiscriminator, getOwnDiscriminator } from "../utils/discriminator"; import { getLogger } from "../utils/logger"; import { @@ -89,6 +94,7 @@ export function transformObject(schema: ObjectSchema, codeModel: CodeModel): Typ extendedParents: getExtendedParents(schema), spreadParents: getSpreadParents(schema, codeModel), decorators: getModelDecorators(schema), + clientDecorators: getModelClientDecorators(schema), }; addCorePageAlias(updatedVisited); diff --git a/packages/extensions/openapi-to-typespec/src/transforms/transform-operations.ts b/packages/extensions/openapi-to-typespec/src/transforms/transform-operations.ts index 5db4a4057a..6717f1a632 100644 --- a/packages/extensions/openapi-to-typespec/src/transforms/transform-operations.ts +++ b/packages/extensions/openapi-to-typespec/src/transforms/transform-operations.ts @@ -21,7 +21,7 @@ import { } from "../interfaces"; import { transformDataType } from "../model"; import { getOptions } from "../options"; -import { getPropertyDecorators } from "../utils/decorators"; +import { getOperationClientDecorators, getPropertyDecorators } from "../utils/decorators"; import { getLogger } from "../utils/logger"; import { getLanguageMetadata } from "../utils/metadata"; import { isConstantSchema } from "../utils/schemas"; @@ -114,6 +114,7 @@ export function transformRequest(_request: Request, operation: Operation, codeMo doc, summary, parameters, + clientDecorators: getOperationClientDecorators(operation), verb: transformVerb(requests?.[0].protocol), route: transformRoute(requests?.[0].protocol), responses: [...new Set(transformedResponses)], diff --git a/packages/extensions/openapi-to-typespec/src/utils/decorators.ts b/packages/extensions/openapi-to-typespec/src/utils/decorators.ts index 599f5676b5..902b85acf0 100644 --- a/packages/extensions/openapi-to-typespec/src/utils/decorators.ts +++ b/packages/extensions/openapi-to-typespec/src/utils/decorators.ts @@ -1,15 +1,18 @@ import { ChoiceSchema, ObjectSchema, + ChoiceValue, Parameter, Property, Schema, SchemaType, SealedChoiceSchema, SerializationStyle, + Operation, isNumberSchema, } from "@autorest/codemodel"; import { TypespecDecorator, DecoratorArgument } from "../interfaces"; +import { createCSharpNameDecorator } from "../pretransforms/rename-pretransform"; import { getOwnDiscriminator } from "./discriminator"; import { isSealedChoiceSchema, isStringSchema } from "./schemas"; @@ -55,6 +58,15 @@ export function getModelDecorators(model: ObjectSchema): TypespecDecorator[] { return decorators; } +export function getModelClientDecorators(model: ObjectSchema): TypespecDecorator[] { + const decorators: TypespecDecorator[] = []; + + if (model.language.csharp?.name) { + decorators.push(createCSharpNameDecorator(model)); + } + return decorators; +} + export function getPropertyDecorators(element: Property | Parameter): TypespecDecorator[] { const decorators: TypespecDecorator[] = []; @@ -153,6 +165,10 @@ export function getPropertyClientDecorators(element: Property | Parameter): Type }); } + if (element.language.csharp?.name) { + decorators.push(createCSharpNameDecorator(element)); + } + return decorators; } @@ -240,6 +256,35 @@ export function getEnumDecorators(enumeration: SealedChoiceSchema | ChoiceSchema return decorators; } + +export function getEnumClientDecorators(enumeration: SealedChoiceSchema | ChoiceSchema): TypespecDecorator[] { + const decorators: TypespecDecorator[] = []; + + if (enumeration.language.csharp?.name) { + decorators.push(createCSharpNameDecorator(enumeration)); + } + + return decorators; +} + +export function getEnumChoiceClientDecorators(enumChoice: ChoiceValue): TypespecDecorator[] { + const decorators: TypespecDecorator[] = []; + + if (enumChoice.language.csharp?.name) { + decorators.push(createCSharpNameDecorator(enumChoice)); + } + return decorators; +} + +export function getOperationClientDecorators(operation: Operation): TypespecDecorator[] { + const decorators: TypespecDecorator[] = []; + + if (operation.language.csharp?.name) { + decorators.push(createCSharpNameDecorator(operation)); + } + return decorators; +} + export function generateDecorators(decorators: TypespecDecorator[] = []): string { const definitions: string[] = []; for (const decorator of decorators ?? []) { diff --git a/packages/extensions/openapi-to-typespec/src/utils/imports.ts b/packages/extensions/openapi-to-typespec/src/utils/imports.ts index bff41f371e..151490165b 100644 --- a/packages/extensions/openapi-to-typespec/src/utils/imports.ts +++ b/packages/extensions/openapi-to-typespec/src/utils/imports.ts @@ -1,4 +1,4 @@ -import { TypespecProgram } from "../interfaces"; +import { TypespecDecorator, TypespecProgram } from "../interfaces"; import { getOptions } from "../options"; type Imports = { @@ -46,12 +46,36 @@ export function getModelsImports(program: TypespecProgram) { export function getClientImports(program: TypespecProgram) { const modules = new Set(); const namespaces = new Set(); + const addImports = (decs: TypespecDecorator[] | undefined) => { + for (const dec of decs ?? []) { + dec.module && modules.add(`import "${dec.module}";`); + dec.namespace && namespaces.add(`using ${dec.namespace};`); + } + }; for (const model of program.models.objects) { + addImports(model.clientDecorators); for (const property of model.properties) { - for (const decorator of property.clientDecorators ?? []) { - decorator.module && modules.add(`import "${decorator.module}";`); - decorator.namespace && namespaces.add(`using ${decorator.namespace};`); - } + addImports(property.clientDecorators); + } + } + + for (const model of program.models.enums) { + addImports(model.clientDecorators); + for (const choice of model.members) { + addImports(choice.clientDecorators); + } + } + + for (const resource of program.models.armResources) { + addImports(resource.clientDecorators); + for (const property of resource.properties) { + addImports(property.clientDecorators); + } + for (const op of resource.resourceOperations) { + addImports(op.clientDecorators); + } + for (const op of resource.normalOperations) { + addImports(op.clientDecorators); } } diff --git a/packages/extensions/openapi-to-typespec/src/utils/resource-discovery.ts b/packages/extensions/openapi-to-typespec/src/utils/resource-discovery.ts index 1340bfe292..4295a63723 100644 --- a/packages/extensions/openapi-to-typespec/src/utils/resource-discovery.ts +++ b/packages/extensions/openapi-to-typespec/src/utils/resource-discovery.ts @@ -21,6 +21,12 @@ export interface _ArmPagingMetadata { NextLinkName: string; } +export interface Metadata { + Resources: Record; + RenameMapping: Record; + OverrideOperationName: Record; +} + export interface ArmResource { Name: string; GetOperations: _ArmResourceOperation[]; @@ -46,7 +52,7 @@ export interface ArmResource { IsSingletonResource: boolean; } -let armResourceCache: Record | undefined; +let metadataCache: Metadata | undefined; export interface OperationWithResourceOperationFlag extends Operation { isResourceOperation?: boolean; @@ -118,19 +124,19 @@ export function getResourceExistOperation(resource: ArmResource): Operation | un } } -export function getArmResourcesMetadata(): Record { - if (armResourceCache) { - return armResourceCache; +export function getArmResourcesMetadata(): Metadata { + if (metadataCache) { + return metadataCache; } const session = getSession(); const outputFolder: string = session.configuration["output-folder"] ?? ""; try { const content = readFileSync(join(outputFolder, "resources.json"), "utf-8"); - const { Resources }: { Resources: Record } = JSON.parse(content); - armResourceCache = Resources; + const metadata: Metadata = JSON.parse(content); + metadataCache = metadata; - return armResourceCache; + return metadataCache; } catch (e) { throw new Error(`Failed to load resources.json from ${outputFolder} \n ${e}`); } @@ -141,7 +147,8 @@ export interface ArmResourceSchema extends ObjectSchema { } export function tagSchemaAsResource(schema: ObjectSchema): void { - const resourcesMetadata = getArmResourcesMetadata(); + const metadata = getArmResourcesMetadata(); + const resourcesMetadata = metadata.Resources; for (const resourceName in resourcesMetadata) { if (resourcesMetadata[resourceName].SwaggerModelName.toLowerCase() === schema.language.default.name.toLowerCase()) {