# Policy Emulator

> 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/.

- Skill: `azure/policy-emulator` (Agent Skill)
- Install (CLI): `npx skillmds@latest add azure/policy-emulator`
- Raw SKILL.md: https://api.skillmd.com/api/skills/azure/policy-emulator/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: Azure (https://skillmd.com/u/azure)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/azure/policy-emulator

---


# Policy Emulator Handler Patterns

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`.

## Handler File — `src/Testing/Emulator/Policies/{PolicyName}Handler.cs`

### Auto-Registration

Handlers are discovered automatically by `SectionContextProxy` via reflection. Requirements:
- Class must be in namespace `Microsoft.Azure.ApiManagement.PolicyToolkit.Testing.Emulator.Policies`
- Class must implement `IPolicyHandler`
- Class must have one or more `[Section(nameof(I{Section}Context))]` attributes
- No manual DI registration needed

### Handler Base Classes

There 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.

```csharp
// 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 Config

Use 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.

```csharp
[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 Parameters

For policies with two direct parameters (e.g., SetHeader takes name + values).

```csharp
[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.

```csharp
[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;
    }
}
```

### Reference Handler Selection Guide

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` |

### Argument Extraction Utilities

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.

---

## Mocking Strategies

Choose based on what the real Azure APIM policy does.

### No-Op with Callbacks (Rate Limiting, Metrics, Token Limits)

Default `Handle` does nothing. All behavior is injected via `CallbackSetup`:

```csharp
protected override void Handle(GatewayContext context, RateLimitConfig config)
{
    // No-op by default in emulator.
    // Test authors use CallbackSetup to simulate rate limit exceeded, etc.
}
```

### Context Mutation (SetHeader, SetBody, SetVariable, RewriteUri)

Actually modifies context state:

```csharp
protected override void Handle(GatewayContext context, string name, string[] values)
{
    context.Request.Headers[name] = values;
}
```

### Validation + Short-Circuit (CheckHeader, ValidateJwt)

Validates conditions, throws `FinishSectionProcessingException` on failure:

```csharp
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();
    }
}
```

### External Service Mock (SendRequest, Dapr, ForwardRequest)

Uses callbacks to provide mock responses. Default should throw or no-op:

```csharp
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(...)");
}
```

### Store Interaction (Cache, LogToEventHub, Trace)

Reads from or writes to emulator stores:

```csharp
protected override void Handle(GatewayContext context, LogToEventHubConfig config)
{
    // Validate and store
    context.LoggerStore.AddEvent(config.LoggerId, message);
}
```

### Wrapper / Flow Control (Retry, Wait)

Executes inner delegate actions. Requires direct `IPolicyHandler` implementation:

```csharp
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;
}
```

---

## Edge Cases

### Wrapper Policies (Retry, Wait, LimitConcurrency)
These policies wrap inner policy delegates. They require custom `IPolicyHandler` implementations that execute delegate parameters. Study `ReturnResponseHandler` for the direct-implementation pattern.

### Inherited Handlers (AzureOpenAi variants)
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.

### Policies Without Config (CrossDomain, RedirectContentUrls)
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 Naming
`JsonToXmlHandle.cs` is a naming inconsistency in the source (missing the `r` in `Handler`). Do not rename it — match the existing name when referencing.

