UNPKG

@azure-tools/typespec-azure-resource-manager

Version:

TypeSpec Azure Resource Manager library

445 lines 19.3 kB
import { $doc, getFriendlyName, ignoreDiagnostics, } from "@typespec/compiler"; import { unsafe_mutateSubgraph as mutateSubgraph, unsafe_MutatorFlow as MutatorFlow, } from "@typespec/compiler/experimental"; import { useStateMap } from "@typespec/compiler/utils"; import { $route, getHttpOperation, isPathParam } from "@typespec/http"; import { $actionSegment, $autoRoute, $createsOrReplacesResource, $deletesResource, $readsResource, $updatesResource, getActionSegment, getParentResource, getSegment, } from "@typespec/rest"; import { pascalCase } from "change-case"; import { reportDiagnostic } from "./lib.js"; import { isArmLibraryNamespace } from "./namespace.js"; import { getArmResourceInfo, getResourceBaseType, getResourcePathElements, isArmVirtualResource, isCustomAzureResource, ResourceBaseType, } from "./resource.js"; import { ArmStateKeys } from "./state.js"; export function getArmResourceOperations(program, resourceType) { let operations = program.stateMap(ArmStateKeys.armResourceOperations).get(resourceType); if (!operations) { operations = { lifecycle: {}, lists: {}, actions: {} }; program.stateMap(ArmStateKeys.armResourceOperations).set(resourceType, operations); } return operations; } function resolveHttpOperations(program, data) { const result = {}; for (const [key, item] of Object.entries(data)) { const httpOperation = ignoreDiagnostics(getHttpOperation(program, item.operation)); result[key] = { ...item, path: httpOperation.path, httpOperation: httpOperation, resourceName: getResourceNameForOperation(program, item, httpOperation.path), }; } return result; } export function resolveResourceOperations(program, resourceType) { const operations = getArmResourceOperations(program, resourceType); // Returned the updated operations object return { lifecycle: resolveHttpOperations(program, operations.lifecycle), actions: resolveHttpOperations(program, operations.actions), lists: resolveHttpOperations(program, operations.lists), }; } function setResourceLifecycleOperation(context, target, resourceType, kind, resourceName) { // Only register methods from non-templated interface types if (target.interface === undefined || target.interface.node === undefined || target.interface.node.templateParameters.length > 0) { return; } // We can't resolve the operation path yet so treat the operation as a partial // type so that we can fill in the missing details later const operations = getArmResourceOperations(context.program, resourceType); const resolvedResourceName = resourceName ?? resourceType.name; const operation = { name: target.name, kind, operation: target, operationGroup: target.interface.name, resourceName: resolvedResourceName, }; operations.lifecycle[kind] = operation; setArmOperationIdentifier(context.program, target, resourceType, { name: target.name, kind: kind, operation: target, operationGroup: target.interface.name, resourceModelName: resourceType.name, resourceName: resolvedResourceName, }); const operationId = { name: target.name, kind: kind, operation: target, operationGroup: target.interface.name, resource: resourceType, resourceName: resolvedResourceName, }; addArmResourceOperation(context.program, resourceType, operationId); } export const [getArmOperationList, setArmOperationList] = useStateMap(ArmStateKeys.resourceOperationList); export function getArmResourceOperationList(program, resourceType) { let operations = getArmOperationList(program, resourceType); if (operations === undefined) { operations = new Set(); setArmOperationList(program, resourceType, operations); } return operations; } export function addArmResourceOperation(program, resourceType, operationData) { const operations = getArmResourceOperationList(program, resourceType); operations.add(operationData); setArmOperationList(program, resourceType, operations); } export const [getArmResourceOperationData, setArmResourceOperationData] = useStateMap(ArmStateKeys.armResourceOperationData); export function setArmOperationIdentifier(program, target, resourceType, data) { // Initialize the operations for the resource type if not already done if (!getArmResourceOperationData(program, target)) { setArmResourceOperationData(program, target, { ...data }); } } export const $armResourceCheckExistence = (context, target, resourceType, resourceName) => { setResourceLifecycleOperation(context, target, resourceType, "checkExistence", resourceName); }; export const $armResourceRead = (context, target, resourceType, resourceName) => { context.call($readsResource, target, resourceType); setResourceLifecycleOperation(context, target, resourceType, "read", resourceName); }; export const $armResourceCreateOrUpdate = (context, target, resourceType, resourceName) => { context.call($createsOrReplacesResource, target, resourceType); setResourceLifecycleOperation(context, target, resourceType, "createOrUpdate", resourceName); }; export const $armResourceUpdate = (context, target, resourceType, resourceName) => { context.call($updatesResource, target, resourceType); setResourceLifecycleOperation(context, target, resourceType, "update", resourceName); }; export const $armResourceDelete = (context, target, resourceType, resourceName) => { context.call($deletesResource, target, resourceType); setResourceLifecycleOperation(context, target, resourceType, "delete", resourceName); }; export const $armResourceList = (context, target, resourceType, resourceName) => { // Only register methods from non-templated interface types if (target.interface === undefined || target.interface.node === undefined || target.interface.node.templateParameters.length > 0) { return; } // We can't resolve the operation path yet so treat the operation as a partial // type so that we can fill in the missing details later const operations = getArmResourceOperations(context.program, resourceType); const resolvedResourceName = resourceName ?? resourceType.name; const operation = { name: target.name, kind: "list", operation: target, operationGroup: target.interface.name, resourceName: resolvedResourceName, }; operations.lists[target.name] = operation; const opId = { name: target.name, kind: "list", operation: target, operationGroup: target.interface.name, resource: resourceType, resourceName: resolvedResourceName, }; addArmResourceOperation(context.program, resourceType, opId); setArmOperationIdentifier(context.program, target, resourceType, { kind: "list", name: target.name, operation: target, operationGroup: target.interface.name, resourceModelName: resourceType.name, resourceName: resolvedResourceName, }); }; export function armRenameListByOperationInternal(context, entity, resourceType, parentTypeName, parentFriendlyTypeName, applyOperationRename) { const { program } = context; if (parentTypeName === undefined || parentTypeName === "" || parentFriendlyTypeName === undefined || parentFriendlyTypeName === "") { [parentTypeName, parentFriendlyTypeName] = getArmParentName(context.program, resourceType); } const parentType = getParentResource(program, resourceType); if (parentType && !isArmVirtualResource(program, parentType) && !isCustomAzureResource(program, parentType)) { const parentResourceInfo = getArmResourceInfo(program, parentType); if (!parentResourceInfo && resourceType.namespace !== undefined && isArmLibraryNamespace(program, resourceType.namespace)) return; if (!parentResourceInfo) { reportDiagnostic(program, { code: "parent-type", messageId: "notResourceType", target: resourceType, format: { type: resourceType.name, parent: parentType.name }, }); return; } // Make sure the first character of the name is upper-cased parentTypeName = parentType.name[0].toUpperCase() + parentType.name.substring(1); } // Add a formatted doc string too context.call($doc, entity, `List ${resourceType.name} resources by ${parentType ? parentTypeName : parentFriendlyTypeName}`, undefined); if (applyOperationRename === undefined || applyOperationRename === true) { // Set the operation name entity.name = parentTypeName === "Extension" || parentTypeName === undefined || parentTypeName.length < 1 ? "list" : `listBy${parentTypeName}`; } } function getArmParentName(program, resource) { const parent = getParentResource(program, resource); if (parent && (isArmVirtualResource(program, parent) || isCustomAzureResource(program, parent))) { const parentName = getFriendlyName(program, parent) ?? parent.name; if (parentName === undefined || parentName.length < 2) { return ["", ""]; } return [ parentName, parentName.length > 1 ? parentName.charAt(0).toLowerCase() + parentName.substring(1) : "", ]; } switch (getResourceBaseType(program, resource)) { case ResourceBaseType.Extension: return ["Extension", "parent"]; case ResourceBaseType.Location: return ["Location", "location"]; case ResourceBaseType.Subscription: return ["Subscription", "subscription"]; case ResourceBaseType.Tenant: return ["Tenant", "tenant"]; case ResourceBaseType.ResourceGroup: default: return ["ResourceGroup", "resource group"]; } } export const $armResourceAction = (context, target, resourceType, resourceName) => { const { program } = context; // Only register methods from non-templated interface types if (target.interface === undefined || target.interface.node === undefined || target.interface.node.templateParameters.length > 0) { return; } // We can't resolve the operation path yet so treat the operation as a partial // type so that we can fill in the missing details later const operations = getArmResourceOperations(program, resourceType); const resolvedResourceName = resourceName ?? resourceType.name; const operation = { name: target.name, kind: "action", operation: target, operationGroup: target.interface.name, resourceName: resolvedResourceName, }; operations.actions[target.name] = operation; const opId = { name: target.name, kind: "action", operation: target, operationGroup: target.interface.name, resource: resourceType, resourceName: resolvedResourceName, }; addArmResourceOperation(program, resourceType, opId); setArmOperationIdentifier(context.program, target, resourceType, { kind: "action", name: target.name, operation: target, operationGroup: target.interface.name, resourceModelName: resourceType.name, resourceName: resolvedResourceName, }); const segment = getSegment(program, target) ?? getActionSegment(program, target); if (!segment) { // Also apply the @actionSegment decorator to the operation context.call($actionSegment, target, uncapitalize(target.name)); } }; function uncapitalize(name) { if (name === "") { return name; } return name[0].toLowerCase() + name.substring(1); } export const $armResourceCollectionAction = (context, target) => { context.program.stateMap(ArmStateKeys.armResourceCollectionAction).set(target, true); }; export function isArmCollectionAction(program, target) { return program.stateMap(ArmStateKeys.armResourceCollectionAction).get(target) === true; } export const $armOperationRoute = (context, target, options) => { const route = options?.route; if (!route && !options?.useStaticRoute) { context.call($autoRoute, target); return; } if (route && route.length > 0) { context.call($route, target, route); } }; export function getRouteOptions(program, target) { let options = undefined; if (target.interface) { options = options || program.stateMap(ArmStateKeys.armResourceRoute).get(target.interface); if (options) return options; } if (target.sourceOperation?.interface) { options = options || program.stateMap(ArmStateKeys.armResourceRoute).get(target.sourceOperation.interface); } if (target.sourceOperation?.interface?.sourceInterfaces[0]) { options = options || program .stateMap(ArmStateKeys.armResourceRoute) .get(target.sourceOperation.interface.sourceInterfaces[0]); } if (options) return options; return { useStaticRoute: false, }; } function storeRenamePathParameters(program, target, sourceName, targetName) { let renameMap = program.stateMap(ArmStateKeys.renamePathParameters).get(target); if (renameMap === undefined) { renameMap = new Map(); } renameMap.set(sourceName, targetName); program.stateMap(ArmStateKeys.renamePathParameters).set(target, renameMap); } function getRenamePathParameter(program, target, sourceName, targetName) { const renameMap = program.stateMap(ArmStateKeys.renamePathParameters).get(target); if (renameMap === undefined) { program.stateMap(ArmStateKeys.renamePathParameters).set(target, new Map()); return false; } return renameMap.get(sourceName) === targetName; } /** * Renames a path parameter in an Azure Resource Manager operation. * @param context The decorator context. * @param target The operation to modify. * @param sourceParameterName The name of the parameter to rename. * @param targetParameterName The new name for the parameter. * @returns */ export const $renamePathParameter = (context, target, sourceParameterName, targetParameterName) => { const { program } = context; if (getRenamePathParameter(program, target, sourceParameterName, targetParameterName)) { return; } const toMutate = target.parameters; const existingTarget = toMutate.properties.get(targetParameterName); const existingSource = toMutate.properties.get(sourceParameterName); if (existingSource === undefined && existingTarget !== undefined) return; if (existingTarget !== undefined) { reportDiagnostic(context.program, { code: "invalid-parameter-rename", messageId: "overwrite", format: { oldName: sourceParameterName, newName: targetParameterName }, target: context.decoratorTarget, }); return; } if (existingSource === undefined) { reportDiagnostic(context.program, { code: "invalid-parameter-rename", messageId: "missing", format: { oldName: sourceParameterName }, target: context.decoratorTarget, }); return; } if (!isPathParam(program, existingSource)) { reportDiagnostic(context.program, { code: "invalid-parameter-rename", messageId: "notpath", format: { oldName: sourceParameterName }, target: context.decoratorTarget, }); return; } const mutated = mutateSubgraph(program, [createParamMutator(sourceParameterName, targetParameterName)], toMutate); target.parameters = mutated.type; storeRenamePathParameters(program, target, sourceParameterName, targetParameterName); }; function createParamMutator(sourceParameterName, targetParameterName) { return { name: "RenameMutator", Model: { filter: (m, prog) => { const param = m.properties.get(sourceParameterName); if (m.properties.has(targetParameterName) || param === undefined || !isPathParam(prog, param)) { return MutatorFlow.DoNotMutate; } return MutatorFlow.DoNotRecur; }, mutate: (_, clone) => { const param = clone.properties.get(sourceParameterName); param.name = targetParameterName; clone.properties.delete(sourceParameterName); clone.properties.set(targetParameterName, param); return MutatorFlow.DoNotRecur; }, }, }; } export function getDefaultLegacyExtensionResourceName(path, resourceName, operationKind) { const providerIndex = path.lastIndexOf("/providers"); if (providerIndex > -1 && providerIndex < path.length - 1) { const targetPath = path.slice(0, providerIndex); const extensionPath = path.slice(providerIndex); const extensionInfo = getResourcePathElements(extensionPath, operationKind); if (!extensionInfo) return resourceName; const extensionName = extensionInfo.resourceType.types.flatMap((t) => pascalCase(t)).join(""); if (targetPath.length === 0) { return extensionName; } if (targetPath.length === 1) { return `${pascalCase(targetPath[0].replaceAll("{", "").replaceAll("}", ""))}${extensionName}`; } const targetInfo = getResourcePathElements(targetPath, "read"); if (!targetInfo || targetInfo.resourceType.types.length === 0) return resourceName; const types = targetInfo.resourceType.types; return `${pascalCase(types[types.length - 1])}${extensionName}`; } return resourceName; } function getDefaultLegacyResourceName(operation, httpOp) { const pathInfo = getResourcePathElements(httpOp, operation.kind); if (pathInfo !== undefined) { let types = pathInfo.resourceType.types; if (types.length > 1) { types = types.slice(types.length - 2); } return types.flatMap((t) => pascalCase(t)).join(""); } else { return operation.resourceModelName; } } export function getResourceNameForOperation(program, operation, operationPath) { if (operation.resourceName !== undefined && operation.resourceName.length > 0) return operation.resourceName; if (operation.resourceKind === "legacy-extension") { return getDefaultLegacyExtensionResourceName(operationPath, operation.resourceModelName, operation.kind); } if (operation.resourceKind === "legacy") { return getDefaultLegacyResourceName(operation, operationPath); } return undefined; } //# sourceMappingURL=operations.js.map