Skip to content

Generator: Create Command Pattern #42

@JerrettDavis

Description

@JerrettDavis

Summary

Add a source generator that produces a complete implementation of the Command pattern for consumer-defined operations.

The generator lives in PatternKit.Generators and emits self-contained, readable C# with no runtime PatternKit dependency.

Primary goals:

  • Define commands as first-class, strongly-typed operations.
  • Generate boilerplate for execution, optional undo, and optional async execution.
  • Support deterministic ordering and composition (macro commands).
  • Provide actionable diagnostics and test coverage.

Motivation / Problem

Command implementations often repeat the same scaffolding:

  • ICommand interfaces + per-command classes
  • argument and validation boilerplate
  • consistent naming and dispatch wiring
  • optional undo support and history handling
  • sync/async duplication

This is ideal for source generation: generate the boring parts, keep consumer code focused on behavior.


Supported Targets (must-have)

The generator must support commands expressed as:

  • partial class / partial struct
  • partial record class / partial record struct

Two primary command models must be supported:

  1. Explicit command types (commands are real user types; generator emits executor adapters).
  2. Command host (a static/instance host groups multiple command methods).

Proposed User Experience

A) Command type model (recommended)

[Command]
public partial record struct RenameUser(Guid UserId, string NewName);

public sealed class UserService
{
    [CommandHandler]
    private void Handle(in RenameUser cmd) { /* ... */ }
}

Generated (representative shape):

public readonly partial struct RenameUserCommand
{
    public static void Execute(UserService handler, in RenameUser cmd);
    public static ValueTask ExecuteAsync(UserService handler, RenameUser cmd, CancellationToken ct = default);
}

B) Command host model (grouped commands)

[CommandHost]
public static partial class UserCommands
{
    [CommandCase]
    public static void Rename(UserService svc, Guid userId, string newName);

    [CommandCase]
    public static ValueTask DisableAsync(UserService svc, Guid userId, CancellationToken ct);
}

Generated:

  • strongly-typed command wrapper types (optional)
  • consistent Execute/ExecuteAsync entrypoints

Attributes / Surface Area

Namespace: PatternKit.Generators.Command

Core

  • [Command] on command types

    • string? CommandTypeName (default: <TypeName>Command)
    • bool GenerateAsync (default: inferred)
    • bool ForceAsync (default: false)
    • bool GenerateUndo (default: false)
  • [CommandHandler] on methods that handle a command

    • Type? CommandType (optional if inferrable)

Optional grouping:

  • [CommandHost] on a static partial class
  • [CommandCase] on methods within a host

Undo/redo integration (optional v1, recommended v2):

  • [CommandUndo] on undo method
  • caretaker/history generator can be a separate issue or follow-up.

Semantics (must-have)

Handler resolution

V1: no DI scanning.

  • Generator wires a command to an explicitly annotated handler method.
  • If multiple handlers match, error.
  • If no handler found, diagnostic.

Execution signatures

Supported handler signatures:

  • void Handle(in TCommand cmd)
  • void Handle(TCommand cmd)
  • ValueTask HandleAsync(TCommand cmd, CancellationToken ct = default)

Generated entrypoints:

  • Execute(handler, in cmd) for sync handlers
  • ExecuteAsync(handler, cmd, ct) for async handlers (ValueTask)

Command composition (macro commands)

Optional v1 feature (but valuable):

  • generate MacroCommand<TContext> builder that executes multiple commands in order.
  • deterministically executes in registration order.

Diagnostics (must-have)

Stable IDs, actionable:

  • PKCMD001 Type marked [Command] must be partial.
  • PKCMD002 No handler found for command type.
  • PKCMD003 Multiple handlers found for command type.
  • PKCMD004 Handler method signature invalid.
  • PKCMD005 Async handler detected but async generation disabled (enable GenerateAsync/ForceAsync).
  • PKCMD006 Command host marked [CommandHost] must be partial and static.
  • PKCMD007 Command case signature invalid.

Generated Code Layout

  • TypeName.Command.g.cs
  • TypeName.CommandHost.g.cs (if host feature enabled)

Determinism:

  • stable ordering by fully-qualified symbol name for emission.

Testing Expectations

  • Command execution routes to correct handler.

  • Async handler uses ValueTask and respects cancellation.

  • Diagnostics:

    • missing handler
    • duplicate handlers
    • invalid signatures
  • Optional macro command:

    • executes in deterministic order
    • stops on exception (default policy) and documents alternative policies (v2)

Acceptance Criteria

  • [Command] works for class/struct/record class/record struct.
  • Generates Execute and ExecuteAsync (ValueTask) entrypoints when appropriate.
  • Deterministic handler resolution with diagnostics.
  • Optional CommandHost grouping supported (if included in v1).
  • Tests cover execution routing, async behavior, and diagnostics.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions