UNPKG

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

Version:

TypeSpec Azure Resource Manager library

903 lines 36.9 kB
import { getAllProperties } from "@azure-tools/typespec-azure-core"; import { $tag, getProperty as compilerGetProperty, getKeyName, getNamespaceFullName, getTags, isArrayModelType, isGlobalNamespace, isNeverType, isTemplateDeclaration, isTemplateDeclarationOrInstance, isTemplateInstance, } from "@typespec/compiler"; import { useStateMap } from "@typespec/compiler/utils"; import { getHttpOperation, isPathParam } from "@typespec/http"; import { $autoRoute, getParentResource, getSegment } from "@typespec/rest"; import { pascalCase } from "change-case"; import { reportDiagnostic } from "./lib.js"; import { getArmProviderNamespace, isArmLibraryNamespace, resolveProviderNamespace, } from "./namespace.js"; import { getArmResourceOperationData, getArmResourceOperationList, getResourceNameForOperation, resolveResourceOperations, } from "./operations.js"; import { getArmResource, listArmResources, registerArmResource } from "./private.decorators.js"; import { ArmStateKeys } from "./state.js"; export const [isArmExternalType, setArmExternalType] = useStateMap(ArmStateKeys.armExternalType); export const $armExternalType = (context, entity) => { const { program } = context; if (isTemplateDeclaration(entity)) return; setArmExternalType(program, entity, true); }; /** * Marks the given resource as an external resource * @param context The decorator context * @param entity The resource model * @param propertiesType The type of the resource properties */ export const $armVirtualResource = (context, entity, provider = undefined) => { const { program } = context; if (isTemplateDeclaration(entity)) return; const result = { kind: "Virtual", provider, }; program.stateMap(ArmStateKeys.armBuiltInResource).set(entity, result); const pathProperty = getProperty(entity, (p) => isPathParam(program, p) && getSegment(program, p) !== undefined); if (pathProperty === undefined) { reportDiagnostic(program, { code: "resource-without-path-and-segment", target: entity, }); return; } const collectionName = getSegment(program, pathProperty); const keyName = getKeyName(program, pathProperty); if (collectionName === undefined || keyName === undefined) { reportDiagnostic(program, { code: "resource-without-path-and-segment", target: entity, }); return; } registerArmResource(context, entity); }; export const $customAzureResource = (context, entity, options) => { const { program } = context; const optionsValue = options ?? { isAzureResource: false }; if (isTemplateDeclaration(entity)) return; setCustomResource(program, entity, optionsValue); }; function getProperty(target, predicate) { for (const prop of getAllProperties(target).values()) { if (predicate(prop)) return prop; } return undefined; } /** * Determine if the given model is an external resource. * @param program The program to process. * @param target The model to check. * @returns true if the model or any model it extends is marked as a resource, otherwise false. */ export function isArmVirtualResource(program, target) { return getArmVirtualResourceDetails(program, target) !== undefined; } /** * * @param program The program to process. * @param target The model to get details for * @returns The resource details if the model is an external resource, otherwise undefined. */ export function getArmVirtualResourceDetails(program, target, visited = new Set()) { if (visited.has(target)) return undefined; visited.add(target); if (program.stateMap(ArmStateKeys.armBuiltInResource).has(target)) { return program .stateMap(ArmStateKeys.armBuiltInResource) .get(target); } if (target.baseModel) { const details = getArmVirtualResourceDetails(program, target.baseModel, visited); if (details) return details; } const parent = getParentResource(program, target); if (parent) { return getArmVirtualResourceDetails(program, parent, visited); } return undefined; } const [getCustomResourceOptions, setCustomResource] = useStateMap(ArmStateKeys.customAzureResource); export { getCustomResourceOptions }; /** * Determine if the given model is a custom resource. * @param program The program to process. * @param target The model to check. * @returns true if the model or any model it extends is marked as a resource, otherwise false. */ export function isCustomAzureResource(program, target) { const resourceOptions = getCustomResourceOptions(program, target); if (resourceOptions) return true; if (target.baseModel) return isCustomAzureResource(program, target.baseModel); return false; } function getArmResourceItemPath(operations) { const returnPath = operations.lifecycle.read?.path || operations.lifecycle.createOrUpdate?.path || operations.lifecycle.delete?.path; if (returnPath !== undefined) return returnPath; const actions = Object.values(operations.actions); if (actions.length > 0) { const longPath = actions[0].path; return longPath.substring(0, longPath.lastIndexOf("/")); } return undefined; } function resolveArmResourceDetails(program, resource) { // Combine fully-resolved operation details with the base details we already have const operations = resolveResourceOperations(program, resource.typespecType); // Calculate the resource type path from the itemPath // TODO: This is currently a problem! We don't have a canonical path to use for the itemPath const itemPath = getArmResourceItemPath(operations); const baseType = getResourceBaseType(program, resource.typespecType); const resourceTypePath = getResourceTypePath(resource, itemPath, baseType); return { ...resource, operations, resourceTypePath, }; } function getResourceTypePath(resource, itemPath, baseType) { if (!itemPath) { return undefined; } // Don't calculate resourceTypePath for tenant-level or extension resources if (baseType === ResourceBaseType.Tenant || baseType === ResourceBaseType.Extension) { return undefined; } // For other resources we start with a path that looks like: /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Contoso/Databases/{DatabaseName}/ // We want to move to a path that looks like this: /subscriptions/{subscriptionId}/providers/Microsoft.Contoso/Databases/ // To do so, we need to: // 1) Cut out the resource name from the item path let temporaryPath; const index = itemPath.indexOf(resource.collectionName); if (index !== -1) { const truncatedPath = itemPath.slice(0, index + resource.collectionName.length); temporaryPath = truncatedPath; } else { temporaryPath = itemPath; } // 2) If the resource is rg-scoped, trim off the resourceGroups segments const pattern = /\/resourceGroups\/{[^}]+}/; temporaryPath = temporaryPath.replace(pattern, ""); // 3) Trim off all the other `{}` sections (e.g. {ParentResourceName}), minus the {subscriptionId} const pattern1 = /\/{(?!subscriptionId)[^}]+}/; //; return temporaryPath.replace(pattern1, ""); } /** * This function returns fully-resolved details about all ARM resources * registered in the TypeSpec document including operations and their details. * * It should only be called after the full TypeSpec document has been compiled * so that operation route details are certain to be present. */ export function getArmResources(program) { // Have we cached the resolved resource details already? const cachedResources = program.stateMap(ArmStateKeys.armResourcesCached); if (cachedResources.size > 0) { // Return the cached resource details return Array.from(program.stateMap(ArmStateKeys.armResourcesCached).values()); } // We haven't generated the full resource details yet const resources = []; for (const resource of listArmResources(program)) { const fullResource = resolveArmResourceDetails(program, resource); cachedResources.set(resource.typespecType, fullResource); resources.push(fullResource); } return resources; } export const [getResolvedResources, setResolvedResources] = useStateMap(ArmStateKeys.armResolvedResources); export function getPublicResourceKind(typespecType) { const kind = getArmResourceKind(typespecType); if (kind === undefined) return "Other"; switch (kind) { case "Tracked": return "Tracked"; case "Proxy": return "Proxy"; case "Extension": return "Extension"; default: return "Other"; } } export function resolveArmResources(program) { const provider = resolveProviderNamespace(program); if (provider === undefined) return {}; const resolvedResources = getResolvedResources(program, provider); if (resolvedResources?.resources !== undefined && resolvedResources.resources.length > 0) { // Return the cached resource details return resolvedResources; } // We haven't generated the full resource details yet const resources = []; for (const resource of listArmResources(program)) { const operations = resolveArmResourceOperations(program, resource.typespecType); for (const op of operations) { const fullResource = { ...op, type: resource.typespecType, kind: getPublicResourceKind(resource.typespecType) ?? (operations.length > 0 ? "Tracked" : "Other"), providerNamespace: resource.armProviderNamespace, }; resources.push(fullResource); } } const toProcess = resources.slice(); while (toProcess.length > 0) { const resource = toProcess.shift(); resource.parent = getResourceParent(resources, resource, toProcess); resource.scope = getResourceScope(resources, resource, toProcess); } // Add the unmarked operations const resolved = { resources: resources, providerOperations: getUnassociatedOperations(program).filter((op) => !isArmResourceOperation(program, op.operation)), }; setResolvedResources(program, provider, resolved); return resolved; } function getResourceParent(knownResources, child, resourcesToProcess) { if (child.resourceType.types.length < 2) return undefined; for (const resource of knownResources) { if (resource.resourceType.types.length + 1 === child.resourceType.types.length && resource.resourceType.provider === child.resourceType.provider && resource.resourceType.types.join("/") === child.resourceType.types.slice(0, -1).join("/")) { return resource; } } const parent = { type: child.type, kind: "Other", providerNamespace: child.providerNamespace, resourceType: { provider: child.resourceType.provider, types: child.resourceType.types.slice(0, -1), }, resourceName: getParentName(child.resourceType.types[child.resourceType.types.length - 2]), resourceInstancePath: `/${child.resourceInstancePath .split("/") .filter((s) => s.length > 0) .slice(0, -2) .join("/")}`, operations: { lifecycle: {}, actions: [], lists: [] }, }; knownResources.push(parent); resourcesToProcess.push(parent); return parent; } function getParentName(typeName) { if (typeName.endsWith("s")) { typeName = typeName.slice(0, -1); } return pascalCase(typeName); } function getResourceScope(knownResources, resource, resourcesToProcess) { if (resource.scope !== undefined) return resource.scope; if (resource.parent !== undefined) return getResourceScope(knownResources, resource.parent, resourcesToProcess); const partsIndex = resource.resourceInstancePath.lastIndexOf("/providers"); if (partsIndex === 0) return "Tenant"; const segments = resource.resourceInstancePath .slice(0, partsIndex) .split("/") .filter((s) => s.length > 0); if (segments.length === 1 && isVariableSegment(segments[0])) return "Scope"; if (segments.length === 2 && isVariableSegment(segments[1]) && segments[0].toLowerCase() === "subscriptions") return "Subscription"; if (segments.length === 4 && isVariableSegment(segments[3]) && segments[0].toLowerCase() === "subscriptions" && segments[2].toLowerCase() === "resourcegroups") return "ResourceGroup"; if (segments.length === 4 && isVariableSegment(segments[3]) && segments[0].toLowerCase() === "providers" && segments[1].toLowerCase() === "microsoft.management" && segments[2].toLowerCase() === "managementgroups") return "ManagementGroup"; if (segments.some((s) => s.toLowerCase() === "providers")) { const parentProviderIndex = segments.findLastIndex((s) => s.toLowerCase() === "providers"); if (segments.length < parentProviderIndex + 2) { return "ExternalResource"; } const provider = segments[parentProviderIndex + 1]; if (isVariableSegment(provider)) { return "ExternalResource"; } const typeSegments = segments.slice(parentProviderIndex + 2); if (typeSegments.length % 2 !== 0) { return "ExternalResource"; } const types = []; for (let i = 0; i < typeSegments.length; i++) { if (i % 2 === 0) { if (isVariableSegment(typeSegments[i])) { return "ExternalResource"; } types.push(typeSegments[i]); } else if (!isVariableSegment(typeSegments[i])) { return "ExternalResource"; } } const parent = { type: resource.type, kind: "Other", providerNamespace: provider, resourceType: { provider: provider, types: types, }, resourceName: getParentName(types[types.length - 1]), resourceInstancePath: `/${segments.join("/")}`, operations: { lifecycle: {}, actions: [], lists: [] }, }; for (const knownResource of knownResources) { if (parent.resourceType.provider.toLowerCase() === knownResource.resourceType.provider.toLowerCase() && parent.resourceType.types.flatMap((r) => r.toLowerCase()).join("/") === knownResource.resourceType.types.flatMap((k) => k.toLowerCase()).join("/")) { return knownResource; } } knownResources.push(parent); resourcesToProcess.push(parent); return parent; } return undefined; } function isVariableSegment(segment) { return (segment.startsWith("{") && segment.endsWith("}")) || segment === "default"; } function getResourceInfo(program, operation) { const pathInfo = getResourcePathElements(operation.httpOperation.path, operation.kind); if (pathInfo === undefined) return undefined; return { ...pathInfo, resourceName: operation.resourceName ?? operation.operationGroup, }; } export function getResourcePathElements(path, kind) { const segments = path.split("/").filter((s) => s.length > 0); const providerIndex = segments.findLastIndex((s) => s === "providers"); if (providerIndex === -1 || providerIndex === segments.length - 1) return undefined; const provider = segments[providerIndex + 1]; const typeSegments = []; const instanceSegments = segments.slice(0, providerIndex + 2); for (let i = providerIndex + 2; i < segments.length; i += 2) { if (isVariableSegment(segments[i])) { break; } if (i + 1 < segments.length && isVariableSegment(segments[i + 1])) { typeSegments.push(segments[i]); instanceSegments.push(segments[i]); instanceSegments.push(segments[i + 1]); } else if (i + 1 === segments.length) { switch (kind) { case "list": typeSegments.push(segments[i]); instanceSegments.push(segments[i]); instanceSegments.push("{name}"); break; default: break; } break; } } if (provider !== undefined && typeSegments.length > 0) { return { resourceType: { provider: provider, types: typeSegments, }, resourceInstancePath: `/${instanceSegments.join("/")}`, }; } return undefined; } function tryAddLifecycleOperation(resourceType, sourceOperation, targetResource) { const opType = sourceOperation.kind; const operations = targetResource.operations; switch (opType) { case "read": operations.lifecycle.read ??= []; addUniqueOperation(sourceOperation, operations.lifecycle.read); return true; case "createOrUpdate": operations.lifecycle.createOrUpdate ??= []; addUniqueOperation(sourceOperation, operations.lifecycle.createOrUpdate); return true; case "update": operations.lifecycle.update ??= []; addUniqueOperation(sourceOperation, operations.lifecycle.update); return true; case "delete": operations.lifecycle.delete ??= []; addUniqueOperation(sourceOperation, operations.lifecycle.delete); return true; case "list": operations.lists ??= []; addUniqueOperation(sourceOperation, operations.lists); return true; case "action": operations.actions ??= []; addUniqueOperation(sourceOperation, operations.actions); return true; case "checkExistence": operations.lifecycle.checkExistence ??= []; addUniqueOperation(sourceOperation, operations.lifecycle.checkExistence); return true; case "other": targetResource.associatedOperations ??= []; addUniqueOperation(sourceOperation, targetResource.associatedOperations); return true; } return false; } function addAssociatedOperation(sourceOperation, targetOperation) { targetOperation.associatedOperations ??= []; addUniqueOperation(sourceOperation, targetOperation.associatedOperations); } export function isResourceOperationMatch(source, target) { if (source.resourceName && target.resourceName && source.resourceName.toLowerCase() !== target.resourceName.toLowerCase()) return false; if (source.resourceType.provider.toLowerCase() !== target.resourceType.provider.toLowerCase()) return false; if (source.resourceType.types.length !== target.resourceType.types.length) return false; for (let i = 0; i < source.resourceType.types.length; i++) { if (source.resourceType.types[i].toLowerCase() !== target.resourceType.types[i].toLowerCase()) return false; } /*const sourceSegments = source.resourceInstancePath.split("/"); const targetSegments = target.resourceInstancePath.split("/"); if (sourceSegments.length !== targetSegments.length) return false; for (let i = 0; i < sourceSegments.length; i++) { if (!isVariableSegment(sourceSegments[i])) { if (isVariableSegment(targetSegments[i])) { return false; } if (sourceSegments[i].toLowerCase() !== targetSegments[i].toLowerCase()) return false; } else if (!isVariableSegment(targetSegments[i])) return false; }*/ return true; } export function getUnassociatedOperations(program) { return getAllOperations(program) .map((op) => getResourceOperation(program, op)) .filter((op) => op !== undefined); } export function getResourceOperation(program, operation) { if (operation.kind !== "Operation") return undefined; if (operation.isFinished === false) return undefined; if (isTemplateDeclarationOrInstance(operation) && !isTemplateInstance(operation)) return undefined; if (operation.interface === undefined || operation.interface.name === undefined) return undefined; const [httpOp, _] = getHttpOperation(program, operation); return { path: httpOp.path, httpOperation: httpOp, name: operation.name, kind: "other", operation: operation, operationGroup: operation.interface.name, resourceModelName: "", }; } function isArmResourceOperation(program, operation) { if (operation.kind !== "Operation") return false; if (operation.isFinished === false) return false; if (isTemplateDeclarationOrInstance(operation) && !isTemplateInstance(operation)) return false; return getArmResourceOperationData(program, operation) !== undefined; } function getAllOperations(program, container) { container = container || resolveProviderNamespace(program); if (!container) { return []; } const operations = []; for (const op of container.operations.values()) { if (op.kind === "Operation" && op.isFinished && (!isTemplateDeclarationOrInstance(op) || isTemplateInstance(op)) && !isArmResourceOperation(program, op)) { operations.push(op); } } if (container.kind === "Namespace") { for (const child of container.namespaces.values()) { operations.push(...getAllOperations(program, child)); } for (const iface of container.interfaces.values()) { operations.push(...getAllOperations(program, iface)); } } return operations; } function addUniqueOperation(operation, operations) { if (!operations.some((op) => op.name.toLowerCase() === operation.name.toLowerCase() && op.operationGroup.toLowerCase() === operation.operationGroup.toLowerCase())) { operations.push(operation); } } export function resolveArmResourceOperations(program, resourceType) { const resolvedOperations = new Set(); const operations = getArmResourceOperationList(program, resourceType); for (const operation of operations) { const armOperation = getResourceOperation(program, operation.operation); if (armOperation === undefined) continue; armOperation.kind = operation.kind; armOperation.resourceModelName = operation.resource?.name ?? resourceType.name; const resourceInfo = getResourceInfo(program, armOperation); if (resourceInfo === undefined) continue; armOperation.name = operation.name; armOperation.resourceKind = operation.resourceKind; resourceInfo.resourceName = operation.resourceName ?? getResourceNameForOperation(program, armOperation, resourceInfo.resourceInstancePath) ?? armOperation.resourceModelName; armOperation.resourceName = resourceInfo.resourceName; let matched = false; // Check if we already have an operation for this resource for (const resolvedOp of resolvedOperations) { if (isResourceOperationMatch(resourceInfo, resolvedOp)) { matched = true; if (tryAddLifecycleOperation(resourceInfo.resourceType, armOperation, resolvedOp)) { continue; } addAssociatedOperation(armOperation, resolvedOp); continue; } } if (matched) continue; // If we don't have an operation for this resource, create a new one const newResource = { resourceType: resourceInfo.resourceType, resourceInstancePath: resourceInfo.resourceInstancePath, resourceName: resourceInfo.resourceName, operations: { lifecycle: { read: undefined, createOrUpdate: undefined, update: undefined, delete: undefined, checkExistence: undefined, }, actions: [], lists: [], }, associatedOperations: [], }; if (!tryAddLifecycleOperation(resourceInfo.resourceType, armOperation, newResource)) { addAssociatedOperation(armOperation, newResource); } resolvedOperations.add(newResource); } return [...resolvedOperations.values()].toSorted((a, b) => { // Sort by provider, type, then instance path if (a.resourceType.types.length < b.resourceType.types.length) return -1; if (a.resourceType.types.length > b.resourceType.types.length) return 1; const aSegments = a.resourceInstancePath.split("/"); const bSegments = b.resourceInstancePath.split("/"); if (aSegments.length < bSegments.length) return -1; if (aSegments.length > bSegments.length) return 1; if (a.resourceInstancePath.toLowerCase() < b.resourceInstancePath.toLowerCase()) return -1; if (a.resourceInstancePath.toLowerCase() > b.resourceInstancePath.toLowerCase()) return 1; return 0; }); } export { getArmResource } from "./private.decorators.js"; export function getArmResourceInfo(program, resourceType) { const resourceInfo = getArmResource(program, resourceType); if (!resourceInfo && resourceType.namespace !== undefined && !isArmLibraryNamespace(program, resourceType.namespace)) { reportDiagnostic(program, { code: "arm-resource-missing", format: { type: resourceType.name }, target: resourceType, }); } return resourceInfo; } export function getArmResourceKind(resourceType) { if (resourceType.baseModel) { const coreType = resourceType.baseModel; const coreTypeNamespace = coreType.namespace ? getNamespaceFullName(coreType.namespace) : ""; if (coreType.name.startsWith("TrackedResource") || coreType.name.startsWith("LegacyTrackedResource") || (coreTypeNamespace.startsWith("Azure.ResourceManager") && resourceType.properties.has("location") && resourceType.properties.has("tags"))) { return "Tracked"; } else if (coreType.name.startsWith("ProxyResource")) { return "Proxy"; } else if (coreType.name.startsWith("ExtensionResource")) { return "Extension"; } else if (coreTypeNamespace === "Azure.ResourceManager.CommonTypes") { return "BuiltIn"; } } return undefined; } function getResourceOperationOptions(type) { const defaultOptions = { allowStaticRoutes: false, omitTags: false, }; const options = type; if (options === undefined || typeof options !== "object") { return defaultOptions; } return options; } /** * This decorator is used to identify interfaces containing resource operations. * When applied, it marks the interface with the `@autoRoute` decorator so that * all of its contained operations will have their routes generated * automatically. * * It also adds a `@tag` decorator bearing the name of the interface so that all * of the operations will be grouped based on the interface name in generated * clients. */ export const $armResourceOperations = (context, interfaceType, resourceOperationsOptions) => { const { program } = context; const options = getResourceOperationOptions(resourceOperationsOptions); if (!options.allowStaticRoutes) { // All resource interfaces should use @autoRoute context.call($autoRoute, interfaceType); } if (!options.omitTags) { // If no tag is given for the interface, tag it with the interface name if (getTags(program, interfaceType).length === 0) { context.call($tag, interfaceType, interfaceType.name); } } }; /** * This decorator is used to mark a resource type as a "singleton", a type with * only one instance. The standard set of resource operations can be applied to * such a resource type, they will generate the correct routes and parameter * lists. */ export const $singleton = (context, resourceType, keyValue = "default") => { context.program.stateMap(ArmStateKeys.armSingletonResources).set(resourceType, keyValue); }; export function isSingletonResource(program, resourceType) { return program.stateMap(ArmStateKeys.armSingletonResources).has(resourceType); } export function getSingletonResourceKey(program, resourceType) { return program.stateMap(ArmStateKeys.armSingletonResources).get(resourceType); } export var ResourceBaseType; (function (ResourceBaseType) { ResourceBaseType["Tenant"] = "Tenant"; ResourceBaseType["Subscription"] = "Subscription"; ResourceBaseType["Location"] = "Location"; ResourceBaseType["ResourceGroup"] = "ResourceGroup"; ResourceBaseType["Extension"] = "Extension"; ResourceBaseType["BuiltIn"] = "BuiltIn"; ResourceBaseType["BuiltInSubscription"] = "BuiltInSubscription"; ResourceBaseType["BuiltInResourceGroup"] = "BuiltInResourceGroup"; })(ResourceBaseType || (ResourceBaseType = {})); export const $resourceBaseType = (context, entity, baseType) => { let baseTypeString = ""; if (isNeverType(baseType)) return; if (baseType?.kind === "String") baseTypeString = baseType.value; setResourceBaseType(context.program, entity, baseTypeString); }; export const $tenantResource = (context, entity) => { setResourceBaseType(context.program, entity, "Tenant"); }; export const $subscriptionResource = (context, entity) => { setResourceBaseType(context.program, entity, "Subscription"); }; export const $locationResource = (context, entity) => { setResourceBaseType(context.program, entity, "Location"); }; export const $resourceGroupResource = (context, entity) => { setResourceBaseType(context.program, entity, "ResourceGroup"); }; export const $extensionResource = (context, entity) => { setResourceBaseType(context.program, entity, "Extension"); }; export const $armProviderNameValue = (context, entity) => { const armProvider = getServiceNamespace(context.program, entity); if (armProvider === undefined) return; for (const [_, property] of entity.parameters.properties) { const segment = getSegment(context.program, property); if (segment === "providers" && property.type.kind === "String") property.type.value = armProvider; } }; export const $identifiers = (context, entity, properties) => { const { program } = context; const type = entity.kind === "ModelProperty" ? entity.type : entity; if (type.kind !== "Model" || !isArrayModelType(program, type) || type.indexer.value.kind !== "Model") { reportDiagnostic(program, { code: "decorator-param-wrong-type", messageId: "armIdentifiersIncorrectEntity", target: entity, }); return; } context.program.stateMap(ArmStateKeys.armIdentifiers).set(entity, properties); }; /** * This function returns identifiers using the '@identifiers' decorator * * @param program The program to process. * @param entity The array model type to check. * @returns returns list of arm identifiers for the given array model type if any or undefined. */ export function getArmIdentifiers(program, entity) { return program.stateMap(ArmStateKeys.armIdentifiers).get(entity); } /** * This function returns identifiers using the '@key' decorator. * * @param program The program to process. * @param entity The array model type to check. * @returns returns list of arm identifiers for the given array model type if any or undefined. */ export function getArmKeyIdentifiers(program, entity) { const value = entity.indexer.value; const result = []; if (value.kind === "Model") { for (const property of value.properties.values()) { const pathToKey = getPathToKey(program, property); if (pathToKey !== undefined && !pathToKey.endsWith("/id") && !pathToKey.endsWith("/name")) { result.push(property.name + pathToKey); } else if (getKeyName(program, property) && !["id", "name"].includes(property.name)) { result.push(property.name); } } if (!result.includes("id") && compilerGetProperty(value, "id") !== undefined) { result.push("id"); } } return result.length > 0 ? result : undefined; } function getPathToKey(program, entity, visited = new Set()) { if (entity.type.kind !== "Model") { return undefined; } if (visited.has(entity)) { return undefined; } visited.add(entity); for (const property of entity.type.properties.values()) { if (property.type.kind !== "Model" && getKeyName(program, property)) { return "/" + property.name; } if (property.type.kind === "Model") { const path = getPathToKey(program, property, visited); if (path !== undefined) { return "/" + property.name + path; } } } return undefined; } function getServiceNamespace(program, type) { if (type === undefined) return undefined; switch (type.kind) { case "Operation": return (getServiceNamespace(program, type.namespace) ?? getServiceNamespace(program, type.interface)); case "Interface": return getServiceNamespace(program, type.namespace); case "Namespace": return (getArmProviderNamespace(program, type) ?? (isGlobalNamespace(program, type) ? undefined : getServiceNamespace(program, type.namespace))); default: return undefined; } } export function setResourceBaseType(program, resource, type) { if (program.stateMap(ArmStateKeys.resourceBaseType).has(resource)) { reportDiagnostic(program, { code: "arm-resource-duplicate-base-parameter", target: resource, }); } program.stateMap(ArmStateKeys.resourceBaseType).set(resource, type); } export function getResourceBaseType(program, resource) { const parentTracker = new Set(); let parent = getParentResource(program, resource); while (parent !== undefined) { if (parentTracker.has(parent)) reportDiagnostic(program, { code: "arm-resource-circular-ancestry", target: resource }); parentTracker.add(parent); resource = parent; parent = getParentResource(program, resource); } const keyValue = program .stateMap(ArmStateKeys.resourceBaseType) .get(resource); return resolveResourceBaseType(keyValue); } export function resolveResourceBaseType(type) { let resolvedType = ResourceBaseType.ResourceGroup; if (type !== undefined) { switch (type) { case "Tenant": resolvedType = ResourceBaseType.Tenant; break; case "Subscription": resolvedType = ResourceBaseType.Subscription; break; case "Location": resolvedType = ResourceBaseType.Location; break; case "ResourceGroup": resolvedType = ResourceBaseType.ResourceGroup; break; case "Extension": resolvedType = ResourceBaseType.Extension; break; case "BuiltIn": resolvedType = ResourceBaseType.BuiltIn; break; case "BuiltInSubscription": resolvedType = ResourceBaseType.BuiltInSubscription; break; case "BuiltInResourceGroup": resolvedType = ResourceBaseType.BuiltInResourceGroup; break; } } return resolvedType; } //# sourceMappingURL=resource.js.map