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
10 changes: 9 additions & 1 deletion .well-known/agent-skills/csharpessentials-meta/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: csharpessentials-meta
description: Use when deciding which CSharpEssentials package to use — overview of all 19 packages organized by concern, the meta-package that bundles core functional modules, and a quick-reference table mapping problems to packages.
description: Use when deciding which CSharpEssentials package to use — overview of all 20 packages organized by concern, the meta-package that bundles core functional modules, and a quick-reference table mapping problems to packages.
---

# CSharpEssentials — Package Index
Expand Down Expand Up @@ -57,6 +57,12 @@ dotnet add package CSharpEssentials
| `CSharpEssentials.RequestResponseLogging` | `dotnet add package CSharpEssentials.RequestResponseLogging` | `csharpessentials-logging` |
| `CSharpEssentials.GcpSecretManager` | `dotnet add package CSharpEssentials.GcpSecretManager` | `csharpessentials-gcpsecretmanager` |

### Resilience

| Package | Install | Skill |
|---------|---------|-------|
| `CSharpEssentials.Resilience` | `dotnet add package CSharpEssentials.Resilience` | `csharpessentials-resilience` |

### Utilities

| Package | Install | Skill |
Expand All @@ -83,6 +89,7 @@ dotnet add package CSharpEssentials
| JSON serialization with string enums + polymorphism | `CSharpEssentials.Json` |
| Log request/response bodies | `CSharpEssentials.RequestResponseLogging` |
| Load secrets from GCP Secret Manager | `CSharpEssentials.GcpSecretManager` |
| Transient fault handling (retry, timeout, circuit breaker, fallback) | `CSharpEssentials.Resilience` |
| Testable time / freeze clock in tests | `CSharpEssentials.Time` |
| Deep-copy entity collections | `CSharpEssentials.Clone` |
| Fast enum-to-string (NativeAOT-safe) | `CSharpEssentials.Enums` |
Expand All @@ -107,6 +114,7 @@ using CSharpEssentials.Entity.Interfaces; // IDomainEvent
using CSharpEssentials.EntityFrameworkCore; // interceptors, pagination
using CSharpEssentials.AspNetCore; // GlobalExceptionHandler, ResultEndpointFilter
using CSharpEssentials.Http; // HttpClientResultExtensions, HttpRequestBuilder
using CSharpEssentials.Resilience; // ResiliencePolicy, ResiliencePolicy<T>
using CSharpEssentials.Json; // JsonOptions, converters
using CSharpEssentials.RequestResponseLogging; // LoggingOptions, SkipLoggingAttributes
using CSharpEssentials.GcpSecretManager; // AddGcpSecretManager()
Expand Down
123 changes: 123 additions & 0 deletions .well-known/agent-skills/csharpessentials-resilience/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
---
name: csharpessentials-resilience
description: Use when adding transient fault handling around Result-based operations — ResiliencePolicy/ResiliencePolicy<T> for retry, timeout, circuit breaker, and fallback composition backed by Polly v8 with Result-aware retry filtering.
---

# CSharpEssentials.Resilience

HTTP-agnostic resilience patterns for `Result` and `Result<T>`. Compose retry, timeout, circuit breaker, and fallback without leaking Polly types into application code.

## Installation

```bash
dotnet add package CSharpEssentials.Resilience
```

## Namespace

```csharp
using CSharpEssentials.Resilience;
```

---

## When to Use / When NOT to Use

| Scenario | Use this package? |
|----------|-------------------|
| Transient fault handling (retry, timeout, circuit breaker) | ✅ Yes |
| Composing resilience policies around `Result<T>` pipelines | ✅ Yes |
| Fallback values after retries are exhausted | ✅ Yes |
| HTTP-specific resilience (redirects, status code mapping) | ❌ No — use `CSharpEssentials.Http` |
| Non-Result exception-only retry logic | ⚠️ Consider Polly directly for simpler scenarios |

---

## Key Types

| Type | Description |
|------|-------------|
| `ResiliencePolicy` | Non-generic policy for `Result` (unit) operations |
| `ResiliencePolicy<T>` | Generic policy for `Result<T>` operations; supports fallback |
| `ResiliencePolicyOptions` | Options record combining Retry, Timeout, CircuitBreaker |
| `RetryOptions` | MaxAttempts, Delay, ExponentialBackoff |
| `TimeoutOptions` | Timeout duration |
| `CircuitBreakerOptions` | MinimumThroughput, SamplingDuration, BreakDuration, FailureRatio |

---

## Builder Pattern

```csharp
Result<User> user = await ResiliencePolicy
.Create()
.WithRetry(maxAttempts: 3, delay: TimeSpan.FromSeconds(1))
.WithTimeout(TimeSpan.FromSeconds(5))
.ExecuteAsync(ct => _db.GetUser(id, ct));

ResiliencePolicy<int> policy = ResiliencePolicy<int>.Create()
.WithRetry(maxAttempts: 3)
.WithCircuitBreaker(minimumThroughput: 10);

Result<int> result = await policy.ExecuteAsync(ct => _api.GetValue(ct));
```

---

## Delegate Extensions

```csharp
// Direct execution — wraps any Func<Task<T>> in a Result
Result<User> user = await (() => _db.GetUser(id)).ExecuteAsync();

// RetryIfFailed — retries on transient Result failures
Func<CancellationToken, Task<Result<User>>> operation = ct => _db.GetUser(id, ct);
Result<User> retried = await operation.RetryIfFailed(maxAttempts: 3);
```

---

## Result-Aware Retry Filtering

`ResiliencePolicy<T>` retries exceptions and failed `Result<T>` values, but skips these non-transient error types:

- `ErrorType.Unauthorized`
- `ErrorType.Forbidden`
- `ErrorType.NotFound`
- `ErrorType.Validation`

`Conflict` and unexpected failures are retried.

---

## Error Codes

| Code | When |
|---|---|
| `Resilience.Timeout` | Operation exceeded the configured timeout |
| `Resilience.CircuitBroken` | Circuit breaker is open |

Timeout and circuit-breaker failures are returned as failed `Result` values rather than rethrown.

---

## Fallback

Fallback is available on `ResiliencePolicy<T>` and runs after prior strategies in the pipeline have been exhausted.

```csharp
Result<Product> product = await ResiliencePolicy<Product>
.Create()
.WithRetry(maxAttempts: 3)
.WithFallback(ct => _cache.GetAsync<Product>(id, ct))
.ExecuteAsync(ct => _catalog.GetProduct(id, ct));
```

---

## Best Practices

- Use `ResiliencePolicy<T>` for operations that already return `Result<T>`; do not wrap `Result<T>` again.
- Always pass the `CancellationToken` through the callback so Polly timeout and cancellation behave correctly.
- Keep Polly namespaces internal to the package boundary; expose `ResiliencePolicy` from application and library code.
- Prefer the HTTP package only for HTTP-specific helpers; general retry/timeout logic belongs in `CSharpEssentials.Resilience`.
5 changes: 5 additions & 0 deletions .well-known/agent-skills/index.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@
"description": "Use when handling operation outcomes without exceptions — Result and Result<T> for success/failure, railway-oriented chaining with Then/ThenAsync/Ensure, Match for consumption, and Result.And/Or for combining multiple results.",
"files": ["SKILL.md"]
},
{
"name": "csharpessentials-resilience",
"description": "Use when adding transient fault handling around Result-based operations — ResiliencePolicy/ResiliencePolicy<T> for retry, timeout, circuit breaker, and fallback composition backed by Polly v8 with Result-aware retry filtering.",
"files": ["SKILL.md"]
},
{
"name": "csharpessentials-rules",
"description": "Use when composing business validation logic — define rules as classes, Func fields, or inline lambdas; combine with .And()/.Or()/.Linear()/.Next(); evaluate with RuleEngine.Evaluate(); branch with RuleEngine.If().",
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## What

CSharpEssentials is a modular .NET NuGet ecosystem (19 packages) that bridges OOP and Functional Programming in C#. Core patterns: Result/Maybe monads, Discriminated Unions (Any<T1,T2,...>), composable Rules engine, DDD base classes (EntityBase), EF Core interceptors/pagination, and ASP.NET Core utilities. Multi-targets: .NET 9/8, netstandard2.1/2.0. Current version: 3.0.0.
CSharpEssentials is a modular .NET NuGet ecosystem (20 packages) that bridges OOP and Functional Programming in C#. Core patterns: Result/Maybe monads, Discriminated Unions (Any<T1,T2,...>), composable Rules engine, DDD base classes (EntityBase), EF Core interceptors/pagination, and ASP.NET Core utilities. Multi-targets: .NET 9/8, netstandard2.1/2.0. Current version: 3.0.0.

## Why

Expand Down
3 changes: 2 additions & 1 deletion CSharpEssentials.Http/CSharpEssentials.Http.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@
<ProjectReference Include="..\CSharpEssentials.Errors\CSharpEssentials.Errors.csproj" Condition="'$(UseProjectReferences)' == 'true'" />
<PackageReference Include="CSharpEssentials.Json" Condition="'$(UseProjectReferences)' != 'true'" />
<ProjectReference Include="..\CSharpEssentials.Json\CSharpEssentials.Json.csproj" Condition="'$(UseProjectReferences)' == 'true'" />
<PackageReference Include="Polly" />
<PackageReference Include="CSharpEssentials.Resilience" Condition="'$(UseProjectReferences)' != 'true'" />
<ProjectReference Include="..\CSharpEssentials.Resilience\CSharpEssentials.Resilience.csproj" Condition="'$(UseProjectReferences)' == 'true'" />
</ItemGroup>

</Project>
Loading
Loading