Extensions
4.3.1. 📡 OpenTelemetry Integration — WithActivity
Enriches an existing Activity span with result outcome metadata — Tap-style, returns the result unchanged:
using var activity = ActivitySource.StartActivity("GetUser");
Result<User> user = await _userService.GetAsync(id)
.WithActivity(activity); // or Activity.Current
Tags set on the activity:
| Tag | Value |
|---|---|
result.outcome |
"success" or "failure" |
result.error.type |
First error type name (on failure) |
result.error.message |
First error message (on failure) |
result.error.count |
Error count (only when > 1) |
Activity status is set to ActivityStatusCode.Ok on success, ActivityStatusCode.Error on failure. Null-safe — no-op when activity is null. No extra NuGet dependency — uses BCL System.Diagnostics.Activity.
4.3.2. 📝 Structured Logging — WithLogger / LogOnFailure
Tap-style ILogger integration — log result outcomes without breaking the pipeline:
// Log every result: Debug on success, Warning/Error on failure
Result<User> user = await _userService.GetAsync(id)
.WithLogger(_logger, "GetUser");
// Log only failures — success is silent
Result<Order> order = await _orderService.CreateAsync(dto)
.LogOnFailure(_logger, "CreateOrder");
// Composable in pipelines
Result<UserDto> dto = await _userService.GetAsync(id)
.WithLogger(_logger, "GetUser")
.MapAsync(u => _mapper.Map(u));
Log levels per outcome:
| Outcome | Level | When |
|---|---|---|
| Success | Debug |
IsSuccess |
| Domain failure | Warning |
IsFailure, no ExceptionError |
| Exception failure | Error |
IsFailure, contains ExceptionError |
Structured log properties on every failure entry: {OperationName}, {ErrorType}, {ErrorMessage}, {ErrorCount} (when > 1 error). Task<Result<T>> extensions accept CancellationToken.
4.3.3. 🔄 Railway Recovery — Recover / RecoverAsync
The counterpart to Bind: where Bind chains on the success path, Recover chains on the failure path. Transform any failure into a new Result — which can itself succeed or fail:
// Fallback to cache if the primary DB call fails
Result<User> user = await _userRepo.GetAsync(id)
.Recover(errors => _cache.Get(id));
// Async fallback — secondary data source
Result<User> user = await _userRepo.GetAsync(id)
.RecoverAsync(errors => _fallbackApi.GetUserAsync(id));
// Context-aware: skip recovery on ForbiddenError
Result<Document> doc = await FetchDocument(id)
.Recover(errors => errors.Any(e => e is ForbiddenError)
? Result<Document>.Fail(errors)
: _localCache.Get(id));
// Non-generic Result — command recovery
Result result = await DeleteUser(id)
.Recover(errors => ArchiveUser(id));
The recovery func receives the full ImmutableList<IError> — enabling context-aware branching. Pass-through on success. Distinct from Catch<TException>: Catch targets only ExceptionError wrapping a specific exception type and always returns a failure; Recover handles any failure and can return success.
4.3.4. 🔍 Predicate Filtering — Filter / FilterAsync
Convert a successful result to a failure when a predicate on the value is not met. The error factory receives the value — enabling contextual error messages that embed actual data:
// Value-dependent error — the primary Filter use case
Result<User> activeUser = userResult
.Filter(u => u.IsActive, u => new Error($"User '{u.Name}' is not active."));
// Static error — convenience overload
Result<Order> pending = orderResult
.Filter(o => o.Status == OrderStatus.Pending, new ConflictError("Order", "status"));
// String message — convenience overload
Result<Product> inStock = productResult
.Filter(p => p.Stock > 0, "Product is out of stock.");
// Async predicate (e.g. external validation service)
Result<Order> valid = await orderResult
.FilterAsync(async o => await _validator.IsValidAsync(o),
o => new ValidationError("Order", o.Id.ToString(), "failed validation"));
Distinct from Ensure: Ensure takes a static Error fixed at the call site. Filter takes Func<T, IError> — the error is built from the value itself, enabling messages like "User 'John' is not active". Predicate exceptions are wrapped in ExceptionError. Pass-through on failure.