# Result Pattern — Railway-Oriented Programming

> **Scope:** dotnet-vsa
> **Layer:** 0 (always load)
> **Keywords:** result, error-handling, railway, match, monadic
> **Load When:** dotnet VSA projects

**Verified against:** .NET 10 (pure C# pattern — no external package). Last-verified: 2026-05-20.

---

## Overview

The Result pattern replaces exceptions for expected failures. Every handler returns `Result` or `Result<T>`, and endpoints use `Match()` to map success/failure to HTTP responses. No try-catch in business logic.

---

## Result Anatomy

```csharp
public class Result
{
    protected internal Result(bool isSuccess, Error error) { ... }

    public bool IsSuccess { get; }
    public bool IsFailure => !IsSuccess;
    public Error Error { get; }

    // Factory methods
    public static Result Success() => new(true, Error.None);
    public static Result<TValue> Success<TValue>(TValue value) => new(value, true, Error.None);
    public static Result Failure(Error error) => new(false, error);
    public static Result<TValue> Failure<TValue>(Error error) => new(default, false, error);
    public static Result<TValue> Create<TValue>(TValue? value) =>
        value is not null ? Success(value) : Failure<TValue>(Error.Null);
}
```

---

## Result\<T\> with Implicit Conversions

```csharp
public class Result<TValue> : Result
{
    public TValue Value => IsSuccess
        ? _value!
        : throw new InvalidOperationException(
            "The value of a failure result can not be accessed.");

    // Implicit: value -> Result<T> (success)
    public static implicit operator Result<TValue>(TValue? value) => Create(value);

    // Implicit: Error -> Result<T> (failure)
    public static implicit operator Result<TValue>(Error error) => Failure<TValue>(error);
}
```

This allows handlers to return values or errors directly:

```csharp
// Both are valid return statements in a handler returning Result<BookResponse>
return new BookResponse(book.Id, book.Title);    // implicit success
return BookErrors.NotFound(bookId);               // implicit failure
```

---

## Error Types (7 + Custom)

```csharp
public record Error(string Code, string? Description = default, ErrorType Type = ErrorType.Failure)
{
    public static readonly Error None = new(string.Empty);
    public static readonly Error Null = new("Error.NullValue", "The specified result value is null.");

    // Factory methods
    public static Error Failure(string code, string description) =>
        new(code, description, ErrorType.Failure);
    public static Error Unexpected(string code, string description) =>
        new(code, description, ErrorType.Unexpected);
    public static Error Validation(string code, string description) =>
        new(code, description, ErrorType.Validation);
    public static Error Conflict(string code, string description) =>
        new(code, description, ErrorType.Conflict);
    public static Error NotFound(string code, string description) =>
        new(code, description, ErrorType.NotFound);
    public static Error Unauthorized(string code, string description) =>
        new(code, description, ErrorType.Unauthorized);
    public static Error Forbidden(string code, string description) =>
        new(code, description, ErrorType.Forbidden);

    // Implicit: Error -> Result (failure)
    public static implicit operator Result(Error error) => Result.Failure(error);
}

public enum ErrorType
{
    Failure,      // General business rule violation
    Unexpected,   // Unrecoverable system error
    Validation,   // Input validation failure (often aggregated)
    Conflict,     // Duplicate / concurrency conflict
    NotFound,     // Entity does not exist
    Unauthorized, // Missing credentials
    Forbidden,    // Insufficient permissions
    Custom        // Domain-specific extensions
}
```

### When to Use Which Error Type

| Error Type | HTTP Status | Use Case |
|------------|-------------|----------|
| `Failure` | 400 | Business rule violation (e.g., insufficient balance) |
| `Unexpected` | 500 | System error (e.g., external service down) |
| `Validation` | 400 | Input validation (FluentValidation aggregated) |
| `Conflict` | 409 | Duplicate key, concurrency conflict |
| `NotFound` | 404 | Entity lookup returned null |
| `Unauthorized` | 401 | Missing or invalid auth token |
| `Forbidden` | 403 | Authenticated but lacks permission |

---

## ValidationError (Aggregated)

Wraps multiple `Error` instances from FluentValidation:

```csharp
public sealed record ValidationError : Error
{
    public ValidationError(Error[] errors)
        : base("Validation.General", "One or more validation errors occurred", ErrorType.Validation)
    {
        Errors = errors;
    }

    public Error[] Errors { get; }

    public static ValidationError FromResults(IEnumerable<Result> results) =>
        new(results.Where(r => r.IsFailure).Select(r => r.Error).ToArray());
}
```

---

## Match() Pattern

Endpoints use `Match()` to convert `Result<T>` into HTTP responses — never access `.Value` directly:

```csharp
public static class ResultExtensions
{
    // For Result (no value)
    public static TOut Match<TOut>(
        this Result result,
        Func<TOut> onSuccess,
        Func<Error, TOut> onFailure)
    {
        return result.IsSuccess ? onSuccess() : onFailure(result.Error);
    }

    // For Result<T> (with value)
    public static TOut Match<TIn, TOut>(
        this Result<TIn> result,
        Func<TIn, TOut> onSuccess,
        Func<Error, TOut> onFailure)
    {
        return result.IsSuccess ? onSuccess(result.Value) : onFailure(result.Error);
    }
}
```

Usage in an endpoint:

```csharp
var result = await handler.HandleAsync(command, cancellationToken);
return result.Match(
    onSuccess: () => Results.Ok(result.Value),
    onFailure: error => Results.BadRequest(error));
```

---

## Defining Feature Errors

Each feature defines its own static error factory class:

```csharp
public static class BookErrors
{
    public static Error NotFound(Guid bookId) =>
        Error.NotFound("Book.NotFound", $"Book with ID '{bookId}' was not found.");

    public static Error DuplicateIsbn(string isbn) =>
        Error.Conflict("Book.DuplicateIsbn", $"A book with ISBN '{isbn}' already exists.");

    public static Error InvalidPrice =>
        Error.Failure("Book.InvalidPrice", "Book price must be greater than zero.");
}
```

---

## Anti-Patterns

| Do NOT | Do Instead |
|--------|------------|
| Throw exceptions for expected failures | Return `Result.Failure(error)` |
| Access `.Value` without checking `IsSuccess` | Use `Match()` |
| Use string error messages without codes | Use `Error.NotFound("Code", "Description")` |
| Catch exceptions in handlers | Let unexpected exceptions bubble to `CustomExceptionHandler` |
| Return `null` from handlers | Return `Result.Failure<T>(error)` |
| Use generic `Error.Failure` for everything | Choose the correct `ErrorType` for proper HTTP mapping |

---

*MORPH-SPEC by Polymorphism Tech*
