Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions PatternKit.Core/packages.lock.json

This file was deleted.

74 changes: 74 additions & 0 deletions docs/generators/examples.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Generator Examples (DI + Orchestration)

Two production-flavored samples live in `src/PatternKit.Examples/Generators` to show the factory generators in action with `IServiceCollection`.

## 1) ServiceModules with FactoryMethod

`ServiceModules` is a `static partial` class annotated with `[FactoryMethod]`:

```csharp
[FactoryMethod(typeof(string), CreateMethodName = "ConfigureModule")]
public static partial class ServiceModules
{
[FactoryCase("metrics")]
public static IServiceCollection AddMetrics(IServiceCollection services) =>
services.AddSingleton<IMetricsSink, ConsoleMetricsSink>();

[FactoryCase("caching")]
public static IServiceCollection AddCaching(IServiceCollection services) =>
services.AddSingleton<ICacheProvider, MemoryCacheProvider>();

[FactoryDefault]
public static IServiceCollection AddDefaults(IServiceCollection services) =>
services.AddSingleton<IWorker, BackgroundWorker>();
}
```

Generated API (sync):

```csharp
ServiceModules.ConfigureModule("metrics", services);
ServiceModules.TryCreate("workers", out var updated, services);
```

Usage (bootstrap modules from config):

```csharp
var services = new ServiceCollection();
foreach (var module in new[] { "metrics", "caching", "workers" })
{
ServiceModules.ConfigureModule(module, services);
}
var provider = services.BuildServiceProvider();
```

## 2) ApplicationOrchestrator with FactoryClass

An interface/abstract base marked `[FactoryClass]` emits a concrete factory:

```csharp
[FactoryClass(typeof(string), GenerateEnumKeys = true)]
public interface IOrchestratorStep
{
ValueTask ExecuteAsync(IServiceProvider services, CancellationToken ct = default);
}

[FactoryClassKey("seed")] public sealed class SeedDataStep : IOrchestratorStep { … }
[FactoryClassKey("warm-cache")] public sealed class WarmCacheStep : IOrchestratorStep { … }
[FactoryClassKey("start-workers")] public sealed class StartWorkersStep : IOrchestratorStep { … }
```

Generated factory (partial) is `OrchestratorStepFactory` with:

```csharp
var factory = new OrchestratorStepFactory();
var step = await factory.CreateAsync("warm-cache");
await step.ExecuteAsync(provider, cancellationToken);
```

`ApplicationOrchestrator` consumes it to run configured steps in order. This pattern works well for task pipelines defined by configuration or tenant-specific input.

## Where to find the code

- `src/PatternKit.Examples/Generators/FactoryGeneratorExamples.cs`
- Project references `PatternKit.Generators` as an analyzer so the factories are generated at build time.
69 changes: 69 additions & 0 deletions docs/generators/factory-class.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Factory Class Generator

Generate a GoF-style factory class from an abstract base or interface using `[FactoryClass]` on the base and `[FactoryClassKey]` on concrete implementations.

## Quick start

```csharp
using PatternKit.Generators.Factories;

namespace Demo;

[FactoryClass(typeof(string), FactoryTypeName = "NotificationFactory")]
public interface INotification { }

[FactoryClassKey("email")]
public class Email : INotification { }

[FactoryClassKey("sms")]
public class Sms : INotification { }
```

Generated factory (sync):

```csharp
public sealed partial class NotificationFactory
{
public INotification Create(string key);
public bool TryCreate(string key, out INotification result); // unless GenerateTryCreate = false
}
```

Async is generated when any product exposes `public static CreateAsync()` returning `Task<TBase>` or `ValueTask<TBase>`:

```csharp
public ValueTask<INotification> CreateAsync(string key);
public ValueTask<(bool Success, INotification Result)> TryCreateAsync(string key);
```

## Enum keys

Set `GenerateEnumKeys = true` to emit:

- Nested `enum Keys` containing all registered keys (sanitized identifiers).
- Overloads of `Create`/`TryCreate`/`CreateAsync`/`TryCreateAsync` that accept the enum.

## Behavior

- **Base requirement** — `[FactoryClass]` must decorate an interface or abstract class.
- **Product discovery** — `[FactoryClassKey]` types must be concrete and implement/derive exactly one `[FactoryClass]` base.
- **Construction** — Uses public parameterless ctor when present; otherwise looks for `static CreateAsync()` on the product. Async factories are awaited; sync products are wrapped in `ValueTask.FromResult` for async APIs.
- **Key handling** — Keys must convert implicitly to `KeyType`. Duplicate keys per base are rejected.
- **Defaults** — No default branch; unknown keys throw in `Create` and return `false` in `TryCreate`.

## Diagnostics

| Id | Message | Fix |
| --- | --- | --- |
| PKCF001 | `[FactoryClass]` must be interface or abstract class | Mark the base `interface` or `abstract class`. |
| PKCF002 | `[FactoryClassKey]` type maps to multiple bases | Ensure each product implements only one `[FactoryClass]` base. |
| PKCF003 | `[FactoryClassKey]` type must be concrete | Remove `abstract` and supply an accessible ctor. |
| PKCF004 | Duplicate factory key | Remove duplicate keys per base type. |
| PKCF005 | Invalid factory key value | Ensure the key literal converts implicitly to `KeyType`. |
| PKCF006 | Missing accessible constructor | Add public parameterless ctor or `static CreateAsync()` returning `Task/ValueTask<TBase>`. |

## Tips

- Use `FactoryTypeName` to control the emitted factory name; otherwise `{BaseName}Factory` is used.
- When you need enum-friendly calling code (e.g., switches), enable `GenerateEnumKeys` for typed overloads.
- Prefer `ValueTask<TBase>` for `CreateAsync` on products; `Task<TBase>` is wrapped when present.
67 changes: 67 additions & 0 deletions docs/generators/factory-method.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Factory Method Generator

Convert a `static partial` class into a keyed factory using `[FactoryMethod]`, `[FactoryCase]`, and `[FactoryDefault]`. The generator produces `Create`, `TryCreate`, and async siblings when needed.

## Quick start

```csharp
using PatternKit.Generators.Factories;

namespace Demo;

[FactoryMethod(typeof(string), CreateMethodName = "Make")]
public static partial class MimeFactory
{
[FactoryCase("json")]
public static string Json() => "application/json";

[FactoryDefault]
public static string Default() => "application/octet-stream";
}
```

Generated API (sync):

```csharp
public static string Make(string key);
public static bool TryCreate(string key, out string value);
```

Async is generated when any case/default returns `ValueTask<T>` or `Task<T>`:

```csharp
public static ValueTask<string> MakeAsync(string key);
public static ValueTask<(bool Success, string Result)> TryCreateAsync(string key);
```

## Behavior

- **Key matching**
- `KeyType == string` and `CaseInsensitiveStrings == true` (default) → `String.Equals` with `OrdinalIgnoreCase`.
- Otherwise exact match (`Ordinal` for strings).
- **Default path**
- If `[FactoryDefault]` exists, it is used when no case matches.
- Otherwise `Create` throws `ArgumentOutOfRangeException`; `TryCreate` returns `false` and `default`.
- **Parameters**
- All `[FactoryCase]`/`[FactoryDefault]` methods must be `static` with identical signatures (return + parameters). Parameters are forwarded into generated methods.
- **Async rules**
- If any mapping is async, generated async APIs use `ValueTask<T>`.
- `Task<T>` cases are wrapped in `ValueTask<T>`.
- Synchronous cases are wrapped with `ValueTask.FromResult`.

## Diagnostics

| Id | Message | Fix |
| --- | --- | --- |
| PKKF001 | `[FactoryMethod]` type must be static partial | Mark the class `static partial`. |
| PKKF002 | Factory methods must share the same signature | Align return type and parameters across all cases/default. |
| PKKF003 | Duplicate factory key | Remove or rename the duplicate key. |
| PKKF004 | Multiple default factory methods | Keep only one `[FactoryDefault]`. |
| PKKF005 | Invalid factory key value | Ensure the key literal converts implicitly to `KeyType`. |
| PKKF006 | Factory methods must be static | Mark all `[FactoryCase]/[FactoryDefault]` methods `static`. |

## Tips

- Prefer small, pure mapping methods; let the generated factory handle validation and branching.
- For string keys that must be case-sensitive, set `CaseInsensitiveStrings = false` and test both `Create` and `TryCreate`.
- Use `CreateMethodName` to match existing APIs (`Make`, `FromKey`, etc.).
29 changes: 29 additions & 0 deletions docs/generators/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# PatternKit Generators

PatternKit includes a Roslyn incremental generator package (`PatternKit.Generators`) that emits factory code at compile time. Use it when you want predictable, allocation-light factories without writing the boilerplate by hand.

## When to use

- You already express intent with attributes and want the compiler to write the factory.
- You need both synchronous and `ValueTask`-first async entry points.
- You want deterministic codegen with no runtime dependency on PatternKit.

## Package & setup

1. Add the analyzer package to your project:

```bash
dotnet add package PatternKit.Generators
```

2. Ensure the project targets `netstandard2.0+` (for libraries) or any modern .NET target (for apps). No runtime references are required.

3. Mark your types with the attributes below; the generator produces partial classes at compile time.

## Available generators

- **Factory Method** — Turn a `static partial` class into a keyed dispatcher with optional default behavior.
- **Factory Class** — GoF-style factory for an abstract base or interface, mapping keys to concrete products (with optional enum keys and async creation).
- **Examples** — See the samples in `PatternKit.Examples/Generators` for DI module wiring and orchestrated application steps using the generators.

Use the pages in this section for usage, generated API shape, async rules, and diagnostics.
14 changes: 14 additions & 0 deletions docs/generators/toc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
- name: Generators Overview
href: index.md

- name: Factory Method
href: factory-method.md

- name: Factory Class
href: factory-class.md

- name: Examples
href: examples.md

- name: Troubleshooting
href: troubleshooting.md
39 changes: 39 additions & 0 deletions docs/generators/troubleshooting.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Generator Troubleshooting

Common issues and how to resolve them when using the PatternKit factory generators.

## Build errors

- **PKKF001 / static partial**
Make the `[FactoryMethod]` type `public static partial class Foo { }`.

- **PKKF002 / signature mismatch**
Align return type and parameter list across all `[FactoryCase]` and `[FactoryDefault]` methods.

- **PKKF003 / duplicate key** or **PKCF004**
Ensure each key literal is unique per factory/base.

- **PKKF005 / PKCF005 invalid key value**
The key must convert implicitly to `KeyType`. Fix the literal or adjust `KeyType`.

- **PKKF006 / PKCF003 / PKCF006 static/ctor checks**
Mark factory methods `static`; add a public parameterless constructor or a `static CreateAsync()` on products.

## Async behavior surprises

- Async factory methods always surface as `ValueTask<T>` in generated APIs. If your implementation returns `Task<T>`, it is wrapped.
- Sync factory methods are awaited in async paths using `ValueTask.FromResult`; long-running work should be async to avoid blocking.

## Enum key generation

- `GenerateEnumKeys = true` creates a nested `Keys` enum and overloads that accept it. Identifier names are sanitized; if collisions occur (e.g., identical sanitized keys), adjust key values to be distinct.

## How to inspect generated code

- Build the project and look for `*.FactoryMethod.g.cs` or `*.FactoryClass.g.cs` in the `obj/` folder. These are readable partial classes—helpful when debugging behavior.

## Still stuck?

- Verify the analyzer package is referenced in the project being compiled (not only in a shared props file).
- Clean the solution (`dotnet clean`) and rebuild to force regeneration.
- Capture the exact diagnostic ID and message; these map directly to the tables on the generator pages.
4 changes: 4 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ PatternKit will grow to cover **Creational**, **Structural**, and **Behavioral**
| **Structural** | [Adapter](patterns/structural/adapter/fluent-adapter.md) • [Bridge](patterns/structural/bridge/bridge.md) • [Composite](patterns/structural/composite/composite.md) • [Decorator](patterns/structural/decorator/index.md) • [Facade](patterns/structural/facade/facade.md) • [Flyweight](patterns/structural/flyweight/index.md) • [Proxy](patterns/structural/proxy/index.md) |
| **Behavioral** | [Strategy](patterns/behavioral/strategy/strategy.md) • [TryStrategy](patterns/behavioral/strategy/trystrategy.md) • [ActionStrategy](patterns/behavioral/strategy/actionstrategy.md) • [ActionChain](patterns/behavioral/chain/actionchain.md) • [ResultChain](patterns/behavioral/chain/resultchain.md) • [Command](patterns/behavioral/command/command.md) • [ReplayableSequence](patterns/behavioral/iterator/replayablesequence.md) • [WindowSequence](patterns/behavioral/iterator/windowsequence.md) • [Mediator](patterns/behavioral/mediator/mediator.md) • [Memento](patterns/behavioral/memento/memento.md) • [Observer](patterns/behavioral/observer/observer.md) • [AsyncObserver](patterns/behavioral/observer/asyncobserver.md) • [Visitor](patterns/behavioral/visitor/visitor.md) • [State](patterns/behavioral/state/state.md) • [Template Method](patterns/behavioral/template/template.md) |

## 🛠️ Source Generators

Prefer compile-time factories over handwritten boilerplate? See the **Generators** section for Factory Method and Factory Class generators, including async rules, enum keys, and diagnostics.

Each pattern will ship with:


Expand Down
4 changes: 4 additions & 0 deletions docs/toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
- name: Patterns
href: patterns/
homepage: patterns/

- name: Generators
href: generators/
homepage: generators/

- name: Examples
href: examples/
Expand Down
2 changes: 1 addition & 1 deletion src/PatternKit.Core/Behavioral/Visitor/ActionVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ public sealed class Builder
private readonly BranchBuilder<Predicate, ActionHandler> _core = BranchBuilder<Predicate, ActionHandler>.Create();

/// <summary>Registers an action for nodes of type <typeparamref name="T"/>.</summary>
/// <typeparam name="T">A concrete type assignable to <see cref="TBase"/>.</typeparam>
/// <typeparam name="T">A concrete type assignable to the base type.</typeparam>
/// <param name="action">The action invoked when the runtime type is <typeparamref name="T"/>.</param>
public Builder On<T>(Action<T> action) where T : TBase
{
Expand Down
Loading
Loading