.github/skills/policy-emulator/SKILL.md
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/.
npx skillsauth add azure/azure-api-management-policy-toolkit policy-emulatorInstall 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 gateway emulator policy handlers that simulate Azure API Management policy behavior in the local testing framework.
For the current backlog of policies that need implementation, see docs/EmulatorPolicyChecklist.md.
src/Testing/Emulator/Policies/{PolicyName}Handler.csHandlers are discovered automatically by SectionContextProxy via reflection. Requirements:
Microsoft.Azure.ApiManagement.PolicyToolkit.Testing.Emulator.PoliciesIPolicyHandler[Section(nameof(I{Section}Context))] attributesThere are four base class patterns. Choose based on the policy's method signature in the section interfaces.
PolicyHandler<TConfig> — Single Config (Most Common)For policies that take a single config object parameter.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace Microsoft.Azure.ApiManagement.PolicyToolkit.Testing.Emulator.Policies;
[Section(nameof(IInboundContext))]
internal class {PolicyName}Handler : PolicyHandler<{PolicyName}Config>
{
public override string PolicyName => nameof(IInboundContext.{PolicyName});
protected override void Handle(GatewayContext context, {PolicyName}Config config)
{
// Implementation here
}
}
Built-in behavior: Has CallbackSetup list of (predicate, action) tuples. If any predicate matches, the callback is executed instead of Handle().
PolicyHandlerOptionalParam<TConfig> — Optional ConfigUse when the entire policy call can omit the config object (the method can be called with no arguments), not just when individual config properties are optional.
[Section(nameof(IInboundContext))]
internal class {PolicyName}Handler : PolicyHandlerOptionalParam<{PolicyName}Config>
{
public override string PolicyName => nameof(IInboundContext.{PolicyName});
protected override void Handle(GatewayContext context, {PolicyName}Config? config)
{
// config may be null
}
}
PolicyHandler<TParam1, TParam2> — Two ParametersFor policies with two direct parameters (e.g., SetHeader takes name + values).
[Section(nameof(IInboundContext))]
[Section(nameof(IOutboundContext))]
internal class {PolicyName}Handler : PolicyHandler<string, string[]>
{
public override string PolicyName => nameof(IInboundContext.{PolicyName});
protected override void Handle(GatewayContext context, string param1, string[] param2)
{
// Implementation here
}
}
IPolicyHandler — Direct Implementation (Complex)Use for wrapper policies (Retry, Wait), no-config policies, or handlers with non-standard signatures.
Important: When implementing IPolicyHandler directly (instead of inheriting from PolicyHandler<T>), callback discovery via SectionContextProxy still works — the proxy finds handlers by namespace + [Section] attribute. However, you must manually define and check a CallbackHooks property (the base classes provide CallbackSetup automatically, but direct implementations do not). The test mock setup (SetupInbound().{PolicyName}().WithCallback(...)) populates this list via reflection on the handler instance, so the property name must be CallbackHooks for direct implementations.
[Section(nameof(IInboundContext))]
internal class {PolicyName}Handler : IPolicyHandler
{
public string PolicyName => nameof(IInboundContext.{PolicyName});
public List<Tuple<Func<GatewayContext, {PolicyName}Config, bool>, Action<GatewayContext, {PolicyName}Config>>> CallbackHooks { get; } = [];
public object? Handle(GatewayContext context, object?[]? args)
{
var config = args.ExtractArgument<{PolicyName}Config>();
foreach (var (predicate, callback) in CallbackHooks)
{
if (predicate(context, config))
{
callback(context, config);
return null;
}
}
// Default implementation here
return null;
}
}
When implementing a new handler, choose the closest structural match as your reference. See the policy-codebase-reference skill for the full selection guide with reference handler → test mappings.
Quick reference:
| Policy Structure | Reference Handler |
|---|---|
| Single config, simple state change | SetStatusHandler |
| Two parameters (name + values) | SetHeaderHandler |
| Config with callback hooks only (no-op) | RateLimitHandler |
| Optional config | MockResponseHandler |
| Validation + short-circuit | CheckHeaderHandler |
| No-config (no-arg method) | BaseHandler |
| Custom flow control / wrapper | ReturnResponseHandler |
| AzureOpenAi variant (inherits Llm) | AzureOpenAiEmitTokenMetricHandler |
Direct IPolicyHandler implementations use extension methods from ArgumentsExtensions (src/Testing/Emulator/Policies/ArgumentsExtensions.cs) to safely extract typed arguments from object?[]?:
args.ExtractArgument<T>() — Extracts a single required argument of type T. Throws if not found.args.ExtractOptionalArgument<T>() — Returns T?, null if the argument is absent.args.ExtractArguments<T1, T2>() — Extracts two arguments for two-parameter handlers (e.g., SetHeader).The PolicyHandler<T> base classes call these internally — you only need them for direct IPolicyHandler implementations.
Choose based on what the real Azure APIM policy does.
Default Handle does nothing. All behavior is injected via CallbackSetup:
protected override void Handle(GatewayContext context, RateLimitConfig config)
{
// No-op by default in emulator.
// Test authors use CallbackSetup to simulate rate limit exceeded, etc.
}
Actually modifies context state:
protected override void Handle(GatewayContext context, string name, string[] values)
{
context.Request.Headers[name] = values;
}
Validates conditions, throws FinishSectionProcessingException on failure:
protected override void Handle(GatewayContext context, CheckHeaderConfig config)
{
if (!context.Request.Headers.ContainsKey(config.HeaderName))
{
context.Response.StatusCode = config.FailCheckHttpCode;
// set error body...
throw new FinishSectionProcessingException();
}
}
Uses callbacks to provide mock responses. Default should throw or no-op:
protected override void Handle(GatewayContext context, SendRequestConfig config)
{
// Default: no-op, requires callback setup
throw new NotImplementedException(
"SendRequest requires mock setup. Use SetupInbound().SendRequest().WithCallback(...)");
}
Reads from or writes to emulator stores:
protected override void Handle(GatewayContext context, LogToEventHubConfig config)
{
// Validate and store
context.LoggerStore.AddEvent(config.LoggerId, message);
}
Executes inner delegate actions. Requires direct IPolicyHandler implementation:
public object? Handle(GatewayContext context, object?[]? args)
{
var config = args.ExtractArgument<RetryConfig>();
var action = args.ExtractArgument<Action<IInboundContext>>();
for (int i = 0; i < config.Count; i++)
{
try { action(context.InboundProxy.Object); break; }
catch { if (i == config.Count - 1) throw; }
}
return null;
}
These policies wrap inner policy delegates. They require custom IPolicyHandler implementations that execute delegate parameters. Study ReturnResponseHandler for the direct-implementation pattern.
AzureOpenAi handlers inherit from their Llm counterparts. Implement the Llm handler first, then create the AzureOpenAi variant as a thin subclass with only PolicyName override.
Some policies have no config parameter — they are no-arg methods. These need a custom handler approach. Study BaseHandler for the no-config pattern.
JsonToXmlHandle.cs is a naming inconsistency in the source (missing the r in Handler). Do not rename it — match the existing name when referencing.
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 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
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/.
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.