-
Notifications
You must be signed in to change notification settings - Fork 0
Description
Summary
Add a source generator that produces a complete implementation of the Chain of Responsibility pattern as a deterministic, composable chain with optional short-circuiting, sync + async support, and allocation-aware execution.
The generator lives in PatternKit.Generators and emits self-contained, readable C# with no runtime PatternKit dependency.
Primary goals:
- Define chain handlers and routing rules declaratively.
- Generate a chain builder + executor that is deterministic and testable.
- Support both “handle-or-pass” and “pipeline transform” styles.
- Support async chains with
ValueTask.
Motivation / Problem
Chains are often hand-rolled with:
- ad-hoc
if/elsebranching - inconsistent short-circuit semantics
- hard-to-test behavior ordering
- sync/async duplication
We want generated chains that are:
- deterministic in order
- explicit about short-circuit behavior
- allocation-light
- easy to compose and benchmark
Supported Targets (must-have)
The generator must support:
partial class/partial structpartial record class/partial record struct
Two chain models must be supported:
- Responsibility chain: each handler either handles and stops, or passes along.
- Pipeline chain: each handler transforms input to output, forwarding to next.
Proposed User Experience
A) Responsibility chain (try-handle)
public readonly record struct Request(string Path);
public readonly record struct Response(int Status);
[Chain]
public partial class Router
{
[ChainHandler(Order = 0)]
private bool TryStatic(in Request req, out Response res);
[ChainHandler(Order = 1)]
private bool TryApi(in Request req, out Response res);
[ChainDefault]
private Response NotFound(in Request req) => new(404);
}Generated (representative shape):
public partial class Router
{
public Response Handle(in Request req);
public bool TryHandle(in Request req, out Response res);
}B) Pipeline chain (next-style)
[Chain(Model = ChainModel.Pipeline)]
public partial class Middleware
{
[ChainHandler(Order = 0)]
private Response Auth(in Request req, Func<Request, Response> next);
[ChainHandler(Order = 1)]
private Response Logging(in Request req, Func<Request, Response> next);
[ChainTerminal]
private Response Terminal(in Request req) => new(200);
}Async example:
- handlers can be
ValueTask<T>returning and acceptFunc<..., ValueTask<T>> next.
Attributes / Surface Area
Namespace: PatternKit.Generators.Chain
Core
-
[Chain]on the chain hostChainModel Model(default:Responsibility)string HandleMethodName = "Handle"string TryHandleMethodName = "TryHandle"string HandleAsyncMethodName = "HandleAsync"bool GenerateAsync(default: inferred)bool ForceAsync(default: false)
-
[ChainHandler]on handler methodsint Orderstring? Name
Responsibility model markers:
[ChainDefault]for fallback (optional; if missing, default behavior is throw or return default based on config)
Pipeline model markers:
[ChainTerminal]for terminal handler (required)
Semantics (must-have)
Ordering
- Execute handlers in ascending
Order. - Duplicate orders are errors.
Responsibility model
-
Handler signature options:
bool TryHandle(in TIn input, out TOut output)ValueTask<(bool Handled, TOut Output)> TryHandleAsync(TIn input, CancellationToken ct = default)(optional v1)
-
Chain stops at first handler that returns handled.
-
If none handle:
- if
[ChainDefault]exists, invoke it - else apply configured policy: throw vs default
- if
Pipeline model
-
Handler signature options:
TOut Handle(in TIn input, Func<TIn, TOut> next)ValueTask<TOut> HandleAsync(TIn input, Func<TIn, ValueTask<TOut>> next, CancellationToken ct = default)
-
Deterministic wrapping order must be documented:
Order=0is outermost wrapper by default.
Async
- If any handler is async, generate
HandleAsyncreturningValueTask<TOut>. - Cancellation token should be threaded through.
Allocation expectations
- Building the chain can allocate.
- Executing should avoid per-call allocations in sync mode.
- Prefer static delegates or cached function pointers when possible.
Diagnostics (must-have)
Stable IDs, actionable:
PKCH001Type marked[Chain]must bepartial.PKCH002No[ChainHandler]methods found.PKCH003Duplicate handler order detected.PKCH004Handler method signature invalid for selected model.PKCH005Missing[ChainTerminal]for Pipeline model.PKCH006Multiple terminals declared.PKCH007Missing default behavior for Responsibility model (no[ChainDefault]and policy requires one).PKCH008Async handler detected but async generation disabled; enable GenerateAsync/ForceAsync.
Generated Code Layout
TypeName.Chain.g.cs
Determinism:
- handlers ordered by
Order, then by fully-qualified method name.
Testing Expectations
Responsibility model:
- First handler wins.
- Fall-through to default.
- No handler + no default → policy behavior.
Pipeline model:
- Wrapping order is correct and deterministic.
- Terminal invoked exactly once.
- Exception bubbling is correct.
Async:
- Uses
ValueTask. - Cancellation respected (best-effort).
Diagnostics:
- Missing/duplicate orders.
- Invalid signatures.
- Missing terminal.
Acceptance Criteria
-
[Chain]works for class/struct/record class/record struct. - Responsibility model supported with deterministic short-circuit.
- Pipeline model supported with deterministic wrapper order.
- Async variants use
ValueTaskwhen needed. - Clear fallback/default policy and diagnostics.
- Tests cover ordering, short-circuiting, wrapper semantics, and diagnostics.