.github/skills/policy-compilation/SKILL.md
Conventions and patterns for creating policy compilers in the Azure API Management policy toolkit. Use this skill when creating or modifying compiler classes in src/Core/Compiling/Policy/.
npx skillsauth add azure/azure-api-management-policy-toolkit policy-compilationInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
This skill describes how to implement compilers that convert authoring models to XML in the Azure API Management policy toolkit.
src/Core/Compiling/Policy/{PolicyName}Compiler.csUse this for policies that take a config object parameter.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Xml.Linq;
using Microsoft.Azure.ApiManagement.PolicyToolkit.Authoring;
using Microsoft.Azure.ApiManagement.PolicyToolkit.Compiling.Diagnostics;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace Microsoft.Azure.ApiManagement.PolicyToolkit.Compiling.Policy;
public class {PolicyName}Compiler : IMethodPolicyHandler
{
public string MethodName => nameof(I{Section}Context.{MethodName});
public void Handle(IDocumentCompilationContext context, InvocationExpressionSyntax node)
{
if (!node.TryExtractingConfigParameter<{PolicyName}Config>(context, "{xml-element-name}", out var values))
{
return;
}
var element = new XElement("{xml-element-name}");
// Required attributes — report diagnostic and return on failure
if (!element.AddAttribute(values, nameof({PolicyName}Config.{RequiredProp}), "{xml-attr-name}"))
{
context.Report(Diagnostic.Create(
CompilationErrors.RequiredParameterNotDefined,
node.GetLocation(),
"{xml-element-name}",
nameof({PolicyName}Config.{RequiredProp})
));
return;
}
// Optional attributes — no error reporting needed
element.AddAttribute(values, nameof({PolicyName}Config.{OptionalProp}), "{xml-attr-name}");
// Child elements — iterate sub-configs if applicable
// (see "Handling Child Elements" below)
context.AddPolicy(element);
}
}
Use this for policies with 1–2 simple parameters and no optional fields (e.g., SetMethod, FindAndReplace).
public class {PolicyName}Compiler : IMethodPolicyHandler
{
public string MethodName => nameof(I{Section}Context.{MethodName});
public void Handle(IDocumentCompilationContext context, InvocationExpressionSyntax node)
{
if (node.ArgumentList.Arguments.Count != {expectedCount})
{
context.Report(Diagnostic.Create(
CompilationErrors.ArgumentCountMissMatchForPolicy,
node.ArgumentList.GetLocation(),
"{xml-element-name}"));
return;
}
var value = node.ArgumentList.Arguments[0].Expression.ProcessParameter(context);
context.AddPolicy(new XElement("{xml-element-name}", value));
}
}
Compiler classes do not need manual DI registration. The CompilerModule.AddMethodPolicyHandlers() method in src/Core/IoC/CompilerModule.cs uses reflection to find all types matching:
IsClass: trueIsAbstract: falseIsPublic: trueNamespace: "Microsoft.Azure.ApiManagement.PolicyToolkit.Compiling.Policy"IMethodPolicyHandlerJust ensure your compiler class meets all these criteria and it will be auto-registered.
All utility methods live in src/Core/Compiling/CompilerUtils.cs and src/Core/Compiling/SyntaxExtensions.cs. You do not need to modify these files — just use their APIs.
TryExtractingConfigParameter<T>Extracts the config object from the method invocation. Returns a dictionary of property name → InitializerValue. Returns false and reports diagnostics if extraction fails.
AddAttributeExtension method on XElement. Adds an XML attribute from the values dictionary:
RenewalPeriod → renewal-periodRetryAfterHeaderName → retry-after-header-nameAddAttribute helper; you supply the kebab-case name in the compiler call.@(...) when the value comes from an expression method.true if the attribute was added, false if the key was not present (property not set).GenericCompiler.HandleListUtility in src/Core/Compiling/Policy/GenericCompiler.cs for compiling string arrays into repeated child elements:
GenericCompiler.HandleList(element, values, nameof(Config.Claims), "required-claims", "claim");
// Produces: <required-claims><claim>value1</claim><claim>value2</claim></required-claims>
Used by ValidateJwtCompiler and ValidateAzureAdTokenCompiler.
ProcessParameterExtension method on ExpressionSyntax. Processes a raw argument into its string value, handling both constants and expressions. Used in direct-parameter compilers.
For child elements backed by sub-config records (e.g., ApiRateLimit[]):
if (values.TryGetValue(nameof({PolicyName}Config.{ChildArray}), out var items))
{
foreach (var item in items.UnnamedValues!)
{
var childElement = new XElement("{child-element-name}");
var childValues = item.NamedValues!;
childElement.AddAttribute(childValues, nameof({SubConfig}.{Prop}), "{attr-name}");
// ... more attributes
element.Add(childElement);
}
}
For simple string arrays (e.g., IP addresses):
if (values.TryGetValue(nameof({PolicyName}Config.{StringArray}), out var items))
{
foreach (var item in items.UnnamedValues!)
{
element.Add(new XElement("{child-element-name}", item.Value!));
}
}
Error reporting uses descriptors from src/Core/Compiling/Diagnostics/CompilationErrors.cs. Common ones:
| Descriptor | When to Use |
|---|---|
| RequiredParameterNotDefined | A required attribute/property is missing |
| RequiredParameterIsEmpty | A required array is present but empty |
| AtLeastOneOfTwoShouldBeDefined | At least one of two mutually supportive properties must be set |
| OnlyOneOfTwoShouldBeDefined | Exactly one of two mutually exclusive properties must be set |
| PolicyArgumentIsNotOfRequiredType | A child element has the wrong type |
| ArgumentCountMissMatchForPolicy | Wrong number of arguments in direct-parameter compilers |
xmlns attributes. XML elements use the element name directly (e.g., <rate-limit />), and the namespace context is managed at the gateway level.IReturnValueMethodPolicyHandlerA second compiler interface exists for policies that return a value assigned to a variable (e.g., AuthenticationManagedIdentity returning a token). This interface is not auto-registered by CompilerModule and the syntax compiler for it (LocalDeclarationStatementCompiler) is currently commented out in CompilerModule.cs. If you encounter a policy that assigns its result to a variable, flag this to the user as a special case requiring manual wiring.
tools
Conventions and patterns for writing policy compilation tests in the Azure API Management policy toolkit. Use this skill when creating or modifying test files in test/Test.Core/Compiling/.
tools
Conventions and patterns for creating gateway emulator policy handlers in the Azure API Management policy toolkit. Use this skill when creating or modifying handler classes in src/Testing/Emulator/Policies/.
tools
Conventions and patterns for writing gateway emulator policy handler tests in the Azure API Management policy toolkit. Use this skill when creating or modifying test files in test/Test.Testing/Emulator/Policies/.
tools
Reference guide for the Azure API Management policy toolkit codebase structure. Use this skill when you need to find existing policies, infrastructure, or naming conventions.