UNPKG

@autorest/openapi-to-typespec

Version:

Autorest plugin to scaffold a Typespec definition from an OpenAPI document

226 lines (202 loc) 7.49 kB
import { readFileSync } from "fs"; import { join } from "path"; import { CodeModel, isObjectSchema, ObjectSchema, Operation, SchemaResponse } from "@autorest/codemodel"; import { getArmCommonTypeVersion, getSession } from "../autorest-session"; import { TypespecObject, TspArmResource, TypespecEnum } from "../interfaces"; import { getSkipList } from "./type-mapping"; export interface _ArmResourceOperation { Name: string; Path: string; Method: string; OperationID: string; IsLongRunning: boolean; Description?: string; PagingMetadata: _ArmPagingMetadata | null; } export interface _ArmPagingMetadata { Method: string; ItemName: string; NextLinkName: string; } export interface Metadata { Resources: Record<string, ArmResource[]>; RenameMapping: Record<string, string>; OverrideOperationName: Record<string, string>; } export interface ArmResource { Name: string; GetOperation?: _ArmResourceOperation; ExistOperation?: _ArmResourceOperation; CreateOperation?: _ArmResourceOperation; UpdateOperation?: _ArmResourceOperation; DeleteOperation?: _ArmResourceOperation; ListOperations: _ArmResourceOperation[]; OperationsFromResourceGroupExtension: _ArmResourceOperation[]; OperationsFromSubscriptionExtension: _ArmResourceOperation[]; OperationsFromManagementGroupExtension: _ArmResourceOperation[]; OperationsFromTenantExtension: _ArmResourceOperation[]; OtherOperations: _ArmResourceOperation[]; Parents: string[]; SwaggerModelName: string; ResourceType: string; ResourceKey: string; ResourceKeySegment: string; IsTrackedResource: boolean; IsTenantResource: boolean; IsSubscriptionResource: boolean; IsManagementGroupResource: boolean; IsExtensionResource: boolean; IsSingletonResource: boolean; } let metadataCache: Metadata | undefined; export interface OperationWithResourceOperationFlag extends Operation { isResourceOperation?: boolean; } export function getResourceOperations(resource: ArmResource): Record<string, Operation> { const operations: Record<string, Operation> = {}; const codeModel = getSession().model; const allOperations: _ArmResourceOperation[] = ( [ resource.GetOperation, resource.CreateOperation, resource.ExistOperation, resource.UpdateOperation, resource.DeleteOperation, ].filter((o) => o !== undefined) as _ArmResourceOperation[] ) .concat(resource.ListOperations) .concat(resource.OperationsFromResourceGroupExtension) .concat(resource.OperationsFromSubscriptionExtension) .concat(resource.OperationsFromManagementGroupExtension) .concat(resource.OperationsFromTenantExtension) .concat(resource.OtherOperations); for (const operationGroup of codeModel.operationGroups) { for (const operation of operationGroup.operations) { for (const operationMetadata of allOperations) { if (operation.operationId === operationMetadata.OperationID) { operations[operation.operationId] = operation; (operation as OperationWithResourceOperationFlag).isResourceOperation = true; } } } } return operations; } export function getSingletonResouceListOperation(resource: ArmResource): Operation | undefined { const codeModel = getSession().model; if (resource.IsSingletonResource) { let predictSingletonResourcePath: string | undefined; if (resource.IsSingletonResource) { predictSingletonResourcePath = resource.GetOperation!.Path.split("/").slice(0, -1).join("/"); } for (const operationGroup of codeModel.operationGroups) { for (const operation of operationGroup.operations) { // for singleton resource, c# will drop the list operation but we need to get it back if ( operation.requests?.length && operation.requests[0].protocol?.http?.path === predictSingletonResourcePath && operation.requests[0].protocol.http?.method === "get" ) { return operation; } } } } } export function getResourceExistOperation(resource: ArmResource): Operation | undefined { const codeModel = getSession().model; for (const operationGroup of codeModel.operationGroups) { for (const operation of operationGroup.operations) { if ( operation.requests?.length && operation.requests[0].protocol?.http?.path === resource.GetOperation!.Path && operation.requests[0].protocol.http?.method === "head" ) { return operation; } } } } export interface ArmResourceSchema extends ObjectSchema { resourceMetadata: ArmResource[]; } export interface ArmResourcePropertiesModel extends ObjectSchema { isPropertiesModel: boolean; } export function tagSchemaAsResource(schema: ObjectSchema, metadata: Metadata): void { const resourcesMetadata = metadata.Resources; for (const resourceName in resourcesMetadata) { if (resourcesMetadata[resourceName][0].SwaggerModelName === schema.language.default.name) { (schema as ArmResourceSchema).resourceMetadata = resourcesMetadata[resourceName]; const propertiesModel = schema.properties?.find((p) => p.serializedName === "properties"); if (propertiesModel && isObjectSchema(propertiesModel.schema)) { (propertiesModel.schema as ArmResourcePropertiesModel).isPropertiesModel = true; } return; } } } export function isResourceSchema(schema: ObjectSchema): schema is ArmResourceSchema { return Boolean((schema as ArmResourceSchema).resourceMetadata); } const _ArmCoreTypes = [ "ResourceProvisioningState", "OperationListResult", "Origin", "OperationDisplay", "OperationStatusResult", "ErrorDetail", "ErrorAdditionalInfo", "SystemData", "Operation", "ErrorResponse", ]; const _ArmCoreCustomTypes = [ "TrackedResource", "ProxyResource", "ExtensionResource", "ManagedIdentityProperties", "UserAssignedIdentity", "ManagedSystemAssignedIdentity", "ManagedSystemIdentityProperties", "EntityTag", "ResourcePlan", "ResourceSku", ]; export function filterArmModels(codeModel: CodeModel, objects: TypespecObject[]): TypespecObject[] { const filtered = [..._ArmCoreTypes]; if (getArmCommonTypeVersion()) { filtered.push(..._ArmCoreCustomTypes); } for (const operationGroup of codeModel.operationGroups) { for (const operation of operationGroup.operations) { if (operation.requests?.[0].protocol?.http?.path.match(/^\/providers\/[^/]+\/operations$/)) { const okResponse = operation.responses?.filter((o) => o.protocol.http?.statusCodes.includes("200"))?.[0]; const objectName = (okResponse as SchemaResponse)?.schema?.language.default.name; if (objectName) { filtered.push(objectName); } } } } filtered.push( ...(codeModel.schemas.objects?.filter((o) => isResourceSchema(o)).map((o) => o.language.default.name) ?? []), ); filtered.push(...getSkipList()); return objects.filter((o) => !filtered.includes(o.name)); } const _ArmCoreEnums = [ "CreatedByType", "Origin", "ActionType", "CheckNameAvailabilityRequest", "CheckNameAvailabilityReason", ]; const _ArmCoreCustomEnums = ["ManagedIdentityType", "ManagedSystemIdentityType", "SkuTier"]; export function filterArmEnums(enums: TypespecEnum[]): TypespecEnum[] { const filtered = [..._ArmCoreEnums]; if (getArmCommonTypeVersion()) { filtered.push(..._ArmCoreCustomEnums); } return enums.filter((e) => !filtered.includes(e.name)); }