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
- HttpClient Extensions - Extension methods that return Result types
- Serialization - Custom serialization and deserialization