Result Types

RestClient.Net uses discriminated unions to represent HTTP responses. This forces you to handle all possible outcomes at compile time.

Result<TSuccess, TError>

The core result type that represents either success or failure.

public abstract record Result<TSuccess, TError>
{
    public record Ok(TSuccess Value) : Result<TSuccess, TError>;
    public record Error(TError Value) : Result<TSuccess, TError>;
}

Pattern Matching

Use switch expressions to handle all cases:

var message = result switch
{
    Result<User, HttpError<ApiError>>.Ok(var user) =>
        $"Got user: {user.Name}",

    Result<User, HttpError<ApiError>>.Error(var error) =>
        $"Error occurred: {error}"
};

HttpError<TError>

Represents HTTP-specific errors. Can be either a response error (server returned an error status) or an exception error (network failure, timeout, etc.).

public abstract record HttpError<TError>
{
    public record ResponseError(
        TError Error,
        HttpStatusCode StatusCode,
        HttpResponseHeaders Headers
    ) : HttpError<TError>;

    public record ExceptionError(Exception Exception) : HttpError<TError>;
}

ResponseError Properties

Property Type Description
Error TError Your deserialized error model
StatusCode HttpStatusCode The HTTP status code (e.g., 404, 500)
Headers HttpResponseHeaders Response headers for accessing metadata

ExceptionError Properties

Property Type Description
Exception Exception The caught exception (timeout, network error, etc.)

Full Pattern Matching Example

var message = result switch
{
    Result<User, HttpError<ApiError>>.Ok(var user) =>
        $"Success: {user.Name}",

    Result<User, HttpError<ApiError>>.Error(
        HttpError<ApiError>.ResponseError(var err, var status, _)) =>
        $"API Error {status}: {err.Message}",

    Result<User, HttpError<ApiError>>.Error(
        HttpError<ApiError>.ExceptionError(var ex)) =>
        $"Exception: {ex.Message}",
};

Type Aliases

The full type names are verbose. Define global using aliases in GlobalUsings.cs:

// GlobalUsings.cs - Define once, use everywhere

// Result aliases
global using OkUser = Outcome.Result<User, Outcome.HttpError<ApiError>>
    .Ok<User, Outcome.HttpError<ApiError>>;

global using ErrorUser = Outcome.Result<User, Outcome.HttpError<ApiError>>
    .Error<User, Outcome.HttpError<ApiError>>;

// HttpError aliases
global using ResponseErrorUser = Outcome.HttpError<ApiError>.ResponseError;
global using ExceptionErrorUser = Outcome.HttpError<ApiError>.ExceptionError;

Using Type Aliases

With aliases defined, pattern matching becomes much cleaner:

var message = result switch
{
    OkUser(var user) => $"Success: {user.Name}",
    ErrorUser(ResponseErrorUser(var err, var status, _)) => $"API Error {status}: {err.Message}",
    ErrorUser(ExceptionErrorUser(var ex)) => $"Exception: {ex.Message}",
};

Exhaustion Analyzer

The Exhaustion Roslyn analyzer ensures you handle all cases:

// This won't compile!
var message = result switch
{
    OkUser(var user) => "Success",
    ErrorUser(ResponseErrorUser(...)) => "API Error",
    // COMPILE ERROR: Missing ExceptionError case!
};

The compiler error:

error EXHAUSTION001: Switch on Result is not exhaustive;
Missing: Error<User, HttpError<ApiError>> with ExceptionError

Handling Specific Status Codes

var message = result switch
{
    OkUser(var user) => $"Success: {user.Name}",

    ErrorUser(ResponseErrorUser(_, HttpStatusCode.NotFound, _)) =>
        "User not found",

    ErrorUser(ResponseErrorUser(_, HttpStatusCode.Unauthorized, _)) =>
        "Authentication required",

    ErrorUser(ResponseErrorUser(var err, var status, _)) =>
        $"Error {(int)status}: {err.Message}",

    ErrorUser(ExceptionErrorUser(var ex)) =>
        $"Network error: {ex.Message}",
};

See Also