Skip to content
Draft
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
69 changes: 69 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,75 @@ It doesn't support anything, but it should
2. applying the attribute somewhere in the inheritance hierarchy
3. applying the attribute multiple times in inheritance hierarchy (it will use the most derived).

## How the source generator stitches things together
- The generator scans for `[ResiliencePipeline]` on interface or class methods and validates they are awaitable (`ValueTask`/`ValueTask<T>`/`Task`/`Task<T>` and not async enumerables).
- It also inspects DI registrations that use the generic `ServiceCollectionServiceExtensions` helpers (`AddTransient`, `AddScoped`, `AddSingleton`, keyed variants, etc.).
- For interface registrations, it emits a new wrapper type per `(implementation, interface)` pair and maps the interface to that generated type.
- For concrete-only registrations it derives from the concrete type (requires the class and method to be non-sealed and virtual).
- It then emits tiny interceptor methods that the compiler wires up (via `InterceptsLocation`) when you call `RegisterKinetic2()`, so your existing DI registrations stay untouched while the generator swaps in the wrapped type.

## What the generated class looks like
After turning on `<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>` you will find code similar to the following under `obj/.../generated/Kinetic2.Analyzers/`:

```csharp
[System.CodeDom.Compiler.GeneratedCode("Kinetic2", "1.0.0")]
internal sealed class NotificationService__INotificationService__K2p : INotificationService
{
private readonly IServiceProvider _serviceProvider;
private readonly INotificationService _instance;

internal NotificationService__INotificationService__K2p(
IServiceProvider serviceProvider,
NotificationService instance)
{
_serviceProvider = serviceProvider;
_instance = (INotificationService)instance;
}

public ValueTask NotifyAsync(string to, string subject, string body) =>
ResilienceExtensions.ExecuteResiliencePipeline<NotificationService__INotificationService__K2p>(
_serviceProvider,
"NotifyAsyncFromInterface",
_ => _instance.NotifyAsync(to, subject, body),
CancellationToken.None);
}
```

The wrapper keeps the original implementation instance, resolves the named `ResiliencePipeline` from DI for each call, and executes the original method through that pipeline without changing your calling code.

## Injection / override mechanism
The generator also emits helper methods that re-run your DI registration using the generated wrapper type. Conceptually, when it sees `AddTransient<INotificationService, NotificationService>()` it produces an interceptor like this:

```csharp
[GeneratedCode("Kinetic2", "1.0.0")]
internal static class K2p_0
{
internal static void AddTransient<TService, TImpl>(
this IServiceCollection services,
Func<IServiceProvider, NotificationService>? factory = default)
where TService : INotificationService
{
if (factory is not null)
{
Func<IServiceProvider, NotificationService__INotificationService__K2p> wrapped =
sp => new NotificationService__INotificationService__K2p(sp, factory(sp));
ServiceCollectionServiceExtensions.AddTransient<INotificationService, NotificationService__INotificationService__K2p>(services, wrapped);
return;
}

var key = "generated-guid";
ServiceCollectionServiceExtensions.AddKeyedTransient<NotificationService>(services, key);
ServiceCollectionServiceExtensions.AddTransient<INotificationService, NotificationService__INotificationService__K2p>(
services,
sp => new NotificationService__INotificationService__K2p(
sp,
sp.GetKeyedService<NotificationService>(key)!));
}
}
```

Because the generator uses interceptors, the signature of your registration call does not change; the call site is rewritten to the generated helper which first preserves your original factory (or registers the original implementation under a keyed service) and then registers the generated wrapper as the implementation for the requested service/interface.

## Generated source code

In your .csproj, add
Expand Down