@azure-tools/typespec-azure-resource-manager
Version:
TypeSpec Azure Resource Manager library
226 lines • 10.1 kB
JavaScript
import { addService, getNamespaceFullName, } from "@typespec/compiler";
import { unsafe_Realm } from "@typespec/compiler/experimental";
import * as http from "@typespec/http";
import { getAuthentication, setAuthentication } from "@typespec/http";
import { unsafe_setRouteOptionsForNamespace as setRouteOptionsForNamespace } from "@typespec/http/experimental";
import { getResourceTypeForKeyParam } from "@typespec/rest";
import { $armCommonTypesVersion } from "./common-types.js";
import { reportDiagnostic } from "./lib.js";
import { getArmVirtualResourceDetails, getSingletonResourceKey } from "./resource.js";
import { ArmStateKeys } from "./state.js";
function getArmCommonTypesVersion(context, entity) {
return entity.decorators.find((x) => x.definition?.name === "@armCommonTypesVersion")?.args[0]
.jsValue;
}
function setArmCommonTypesVersionIfDoesnotExist(context, entity, commonTypeVersion) {
// Determine whether to set a default ARM CommonTypes.Version
const armCommonTypesVersion = entity.decorators.find((x) => x.definition?.name === "@armCommonTypesVersion");
// if no existing @armCommonTypesVersion decorator, add default.
// This will NOT cause error if overrode on version enum.
if (!armCommonTypesVersion) {
context.call($armCommonTypesVersion, entity, commonTypeVersion);
}
}
/**
* Mark the target namespace as containign only ARM library types. This is used to create libraries to share among RPs
* @param context The doecorator context, automatically supplied by the compiler
* @param entity The decorated namespace
*/
export const $armLibraryNamespace = (context, entity) => {
const { program } = context;
program.stateMap(ArmStateKeys.armLibraryNamespaces).set(entity, true);
setArmCommonTypesVersionIfDoesnotExist(context, entity, "v3");
};
/**
* Check if the given namespace contains ARM library types
* @param program The program to process
* @param namespace The namespace to check
* @returns true if the given namespace contains ARM library types only, false otherwise
*/
export function isArmLibraryNamespace(program, namespace) {
return program.stateMap(ArmStateKeys.armLibraryNamespaces).get(namespace) === true;
}
export function isArmProviderNamespace(program, namespace) {
return (namespace !== undefined && program.stateMap(ArmStateKeys.armProviderNamespaces).has(namespace));
}
function isArmNamespaceOverride(program, entity) {
return (program.stateMap(ArmStateKeys.armProviderNamespaces).size === 1 &&
program.stateMap(ArmStateKeys.armProviderNamespaces).has(entity));
}
/**
* Specify which ARM library namespaces this arm provider uses
* @param {DecoratorContext} context Standard DecoratorContext object
* @param {Namespace} entity The namespace the decorator is applied to
* @param {Namespace[]} namespaces The library namespaces that will be used in this namespace
*/
export const $useLibraryNamespace = (context, entity, ...namespaces) => {
const { program } = context;
const provider = program.stateMap(ArmStateKeys.armProviderNamespaces).get(entity);
if (provider) {
setLibraryNamespaceProvider(program, provider, namespaces);
}
program.stateMap(ArmStateKeys.usesArmLibraryNamespaces).set(entity, namespaces);
};
/**
* Determine which library namespaces are used in this provider
* @param {Program} program The program to check
* @param {Namespace} namespace The provider namespace
*/
export function getUsedLibraryNamespaces(program, namespace) {
return program.stateMap(ArmStateKeys.usesArmLibraryNamespaces).get(namespace);
}
function setLibraryNamespaceProvider(program, provider, namespaces) {
for (const namespace of namespaces) {
program.stateMap(ArmStateKeys.armProviderNamespaces).set(namespace, provider);
}
}
/**
* `@armProviderNamespace` sets the ARM provider namespace.
* @param {DecoratorContext} context DecoratorContext object
* @param {type} entity Target of the decorator. Must be `namespace` type
* @param {string} armProviderNamespace Provider namespace
*/
export const $armProviderNamespace = (context, entity, armProviderNamespace) => {
const { program } = context;
const inRealm = unsafe_Realm.realmForType.has(entity);
const override = isArmNamespaceOverride(program, entity);
const namespaceCount = program.stateMap(ArmStateKeys.armProviderNamespaces).size;
if (namespaceCount > 0 && !override && !inRealm) {
reportDiagnostic(program, {
code: "single-arm-provider",
target: context.decoratorTarget,
});
return;
}
// armProviderNamespace will set the service namespace if it's not done already
if (!override || inRealm) {
addService(program, entity);
if (!http.getServers(program, entity)) {
context.call(http.$server, entity, "https://management.azure.com", "Azure Resource Manager url.");
}
}
const armCommonTypesVersion = getArmCommonTypesVersion(context, entity);
// If it is versioned namespace, we will check each Version enum member. If no
// @armCommonTypeVersion decorator, add the one
const versioned = entity.decorators.find((x) => x.definition?.name === "@versioned");
if (versioned) {
const versionEnum = versioned.args[0].value;
versionEnum.members.forEach((v) => {
if (!getArmCommonTypesVersion(context, v)) {
context.call($armCommonTypesVersion, v, armCommonTypesVersion ?? "v3");
}
});
}
else {
// if it is unversioned namespace, set @armCommonTypesVersion and
// no existing @armCommonTypesVersion decorator, add default.
// This will NOT cause error if overrode on version enum.
if (!armCommonTypesVersion) {
context.call($armCommonTypesVersion, entity, "v3");
}
}
// 'namespace' is optional, use the actual namespace string if omitted
const typespecNamespace = getNamespaceFullName(entity);
if (!armProviderNamespace) {
armProviderNamespace = typespecNamespace;
}
program.stateMap(ArmStateKeys.armProviderNamespaces).set(entity, armProviderNamespace);
const libraryNamespace = getUsedLibraryNamespaces(program, entity);
if (libraryNamespace) {
setLibraryNamespaceProvider(program, armProviderNamespace, libraryNamespace);
}
// Set default security definitions
if (!override) {
if (getAuthentication(program, entity) === undefined) {
setAuthentication(program, entity, {
options: [
{
schemes: [
{
id: "azure_auth",
description: "Azure Active Directory OAuth2 Flow.",
type: "oauth2",
model: null,
flows: [
{
type: "implicit",
authorizationUrl: "https://login.microsoftonline.com/common/oauth2/authorize",
scopes: [
{ value: "user_impersonation", description: "impersonate your user account" },
],
},
],
},
],
},
],
});
}
// Set route options for the top level namespace
let topLevelNamespace = entity;
while (topLevelNamespace.namespace) {
topLevelNamespace = topLevelNamespace.namespace;
}
setRouteOptionsForNamespace(program, topLevelNamespace, {
autoRouteOptions: {
// Filter key parameters for singleton resource types to insert the
// singleton key value
routeParamFilter: (operation, param) => {
const paramResourceType = getResourceTypeForKeyParam(program, param);
if (paramResourceType) {
const singletonKey = getSingletonResourceKey(program, paramResourceType);
if (singletonKey) {
return {
routeParamString: singletonKey,
excludeFromOperationParams: true,
};
}
}
// Returning undefined leaves the parameter unaffected
return undefined;
},
},
});
}
};
/**
* Get the ARM provider namespace for a given entity
* @param {Program} program
* @param {Namespace | Model} entity
* @returns {string | undefined} ARM provider namespace
*/
export function getArmProviderNamespace(program, entity) {
if (entity.kind === "Model") {
const details = getArmVirtualResourceDetails(program, entity);
if (details?.provider !== undefined) {
return details.provider;
}
}
const currentNamespace = entity.kind === "Namespace" ? entity : entity.namespace;
return getArmProviderFromNamespace(program, currentNamespace);
}
function getArmProviderFromNamespace(program, ns) {
let armProviderNamespace;
while (ns) {
armProviderNamespace = program.stateMap(ArmStateKeys.armProviderNamespaces).get(ns);
if (armProviderNamespace) {
return armProviderNamespace;
}
ns = ns.namespace;
}
return undefined;
}
export function resolveProviderNamespace(program, ns) {
ns = ns ?? program.getGlobalNamespaceType();
if (program.stateMap(ArmStateKeys.armProviderNamespaces).get(ns)) {
return ns;
}
for (const child of ns.namespaces.values()) {
const providerNs = resolveProviderNamespace(program, child);
if (providerNs) {
return providerNs;
}
}
return undefined;
}
//# sourceMappingURL=namespace.js.map