Skip to content

Error Handling

Every generated effect method wraps errors with contextual information automatically. When an effect fails, the error tells you which service and which method failed — not just the raw exception.

Auto-Inferred Error Context

The generator infers an error prefix from the interface name by stripping the I prefix:

Interface Inferred Prefix
IPaymentGateway PaymentGateway
IEmailService EmailService
IInventoryService InventoryService

Every generated effect method gets .MapFail() wrapping:

// Generated:
liftEff<RT, Receipt>(async rt => await rt.PaymentGateway.ChargeAsync(amount, currency))
    .MapFail(e => Error.New("PaymentGateway.ChargeAsync failed", e))
    .WithActivity("PaymentGateway.ChargeAsync", ActivitySource);

No code changes needed — declare the module and error context is automatic:

[EffectsModule(typeof(IPaymentGateway))]
public sealed partial class PaymentEffects;

Error Chain in Practice

When a workflow composes multiple services and one fails, the error chain tells you exactly where:

public static Eff<AppRuntime, OrderId> Handle(CreateOrder cmd) =>
    from order   in OrderEffects.Orders.CreateAsync<AppRuntime>(cmd.ToOrder())
    from _charge in PaymentEffects.PaymentGateway.ChargeAsync<AppRuntime>(cmd.Total, "USD")
    from _notify in NotifyEffects.Notifier.SendAsync<AppRuntime>(order.Id, "created")
    select order.Id;

If the payment gateway fails:

Error: PaymentGateway.ChargeAsync failed
  → HttpRequestException: Connection refused

If the notification service fails instead:

Error: Notifier.SendAsync failed
  → SmtpException: Authentication failed

Each error in the chain carries the service and method name, making debugging straightforward — especially in production logs and OpenTelemetry traces.

Overriding the Prefix

The inferred name works for most interfaces. When it doesn't, use ErrorPrefix:

[EffectsModule(typeof(IPaymentGateway), ErrorPrefix = "Payments")]
public sealed partial class PaymentEffects;
// → "Payments.ChargeAsync failed"

[EffectsModule(typeof(IExternalApiV2Client), ErrorPrefix = "ExternalApi")]
public sealed partial class ExternalApiEffects;
// → "ExternalApi.FetchAsync failed"

Composing with Manual .MapFail()

The auto-inferred context provides service-level identification. For domain-level context, compose .MapFail() at the call site:

from _ in PaymentEffects.PaymentGateway.ChargeAsync<AppRuntime>(cmd.Total, "USD")
             .MapFail(e => Error.New($"Payment failed for order {orderId}", e))

This produces a three-level error chain:

Error: Payment failed for order ORD-123
  → Error: PaymentGateway.ChargeAsync failed
    → HttpRequestException: Connection refused

The auto-inferred prefix and manual .MapFail() compose naturally — use both when you need domain context on top of service context.

How Errors Map to HTTP Responses

WebError.ToResult() maps effect errors to appropriate HTTP status codes. Error context is preserved in the response:

Error Type HTTP Status Body
ValidationError 422 Structured {field, code, message} array
Expected Status from Expected.Code Error message
Any other Error 500 Error chain as message

See Dispatch Validation for the validation-specific error story.