@azure-tools/typespec-azure-resource-manager
Version:
TypeSpec Azure Resource Manager library
903 lines • 36.9 kB
JavaScript
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