This library provides base types for working with errors deliberate way within your applications. The main types found in the library are:
Error
Result
/Result<T>/Success<T>/Fail
HttpProblem
- The
Error
object provides an implementation of the error response in the Microsoft REST API Guidelines - The
Result
andResult<T>
objects provide a mechanism for methods to return either a value or an error. - The
HttpProblem
class represents an HttpResponse of typeapplication/problem+json
. See the RFC - A
Prelude
class is provided to add additional helper methods to make working with Errors and Results easier.
The Error
class provides the basic properties required in the Microsoft REST API Guidelines. This class explicitly allows for subclassing in order to create predefined errors or add extra properties beyond the basics. For example, we provide the UnhandledError
class which looks like this (simplified):
public class UnhandledError : Error
{
public UnhandledError()
{
this.Code = "unhandled";
}
}
You can also add other properties to expand on the error as needed:
public class InvalidUserError : Error
{
public InvalidUserError(string username)
{
this.Code = "invalid_user";
this.UserName = username;
}
public string UserName { get; set; }
}
This library provides two main ways of working with errors: Using the Result classes to encapsulate errors, and tuples for errors in the style of the Go language.
In Go, errors are conventionally the last item in a tuple. In C# we would do that by transforming the return type of a method. For example, let's take the following method:
public double Add(double x, double y)
{
return x + y;
}
Might look like this:
public (double, Error?) Add(double x, double y)
{
return (x + y, default);
}
You can use this method in a way that closely resembles Go:
var (result, err) = Add(5, 6);
// OR
(double result, Error? err) = Add(5, 6);
You would then check if err
is null and then proceed:
var (result, err) = Add(5, 6);
if (err is null)
{
// Do some error handling
}
else
{
// Use the result
}
If you choose to use this style of error handling, then this library only provides minimal help around creating and managing the tuples. Since you still need an error type, we recommend using the Error
type in this library as a base.
We do provide several ways for this tuple to interoperate with the Result classes, making it so you are not locked into a single way of propagating errors in your code.
The Result
and Result<T>
classes are implementations of a discriminated union where it can either be an Error
or a value. These classes exist to make error handling more explicit by having developers check if a Result
contains an error before grabbing the value.
AN example usage may look like this:
public class Calc
{
public Result<double> Add(double x, double y)
{
return x + y; // Implicit conversion to Result<double>
}
public Success<double> AddGuarenteed(double x, double y)
{
return x + y; // Implicit conversion to Success<double>
}
public Result<double> Divide(double numerator, double denominator)
{
if (denominator == 0)
{
Error error = Error("divide_by_zero"); // Method provided by the Prelude class
return error; // Implicit conversion to Result<double>.
}
return numerator / denominator;
}
}
The main types used are:
Result
- This is the base type that indicates in there is a success or fail.Success<T>
- Derives fromResult
, and indicates a success.Fail
- Derives fromResult
, and indicates a fail.Result<T>
- Derives fromResult
and indicates either a success or fail.
You can easily convert between these types, and so you should consider using the specific versions when known (i.e. guarenteed success). YHou will also find methods to instantiate these on the Result
class, such as: Result.New<int>(5)
.
The Prelude
class makes use of the C# using static directive. In your class add the following usings:
using Rixian.Extensions.Errors;
using static Rixian.Extensions.Errors.Prelude;
You now have access to the following:
Error()
Error<T>()
BadArgumentError()
NullArgumentDisallowedError()
NullValueDisallowedError()
EmptyGuidDisallowedError()
Result<T>()
NullResult<T>()
RequireGuid()
DefaultResult
(Readonly field)
The HttpProblem
class and its sibling HttpProblemError
are used for working with the HTTP Problem RFC. This class is intended to bridge the gap into the Error
class to take advantage of those helpers.
This library provides extension methods for setting up an HttpClient.
- Configure Basic HttlClient properties, such as SSL Protocols.
- Configure an ITokenClient as the source of the Authorization header.
- Configure the Authorization header with a static Bearer token value.
- Configure any header with a static value.
IServiceCollection services = ...;
services.AddHttpClient("test")
.UseSslProtocols(SslProtocols.Tls12); // Sets the primary HttpClientHandler to use TLS 1.2
IServiceCollection services = ...;
services.AddTokenClient("tokenClient123", ...);
services.AddHttpClient("test")
.AddTokenClient("tokenClient123"); // Adds the header "Authorization: Bearer {access_token}"
IServiceCollection services = ...;
services.AddTokenClient(...);
services.AddHttpClient("test")
.AddTokenClient(svc =>
{
return svc
.GetRequiredService<ITokenClientFactory>()
.GetTokenClient();
}); // Adds the header "Authorization: Bearer {access_token}"
IS
8047
erviceCollection services = ...;
ITokenClient tokenClient = ...;
services.AddHttpClient("test")
.AddTokenClient(tokenClient); // Adds the header "Authorization: Bearer {access_token}"
IServiceCollection services = ...;
string bearerToken = "REPLACE_ME";
services.AddHttpClient("test")
.AddBearerToken(bearerToken); // Adds the header "Authorization: Bearer REPLACE_ME"
IServiceCollection services = ...;
string scheme = "HDR_SCHEME";
string parameter = "HDR_PARAM";
services.AddHttpClient("test")
.AddAuthorizationHeader(scheme, parameter); // Adds the header "Authorization: HDR_SCHEME HDR_PARAM"
IServiceCollection services = ...;
string name = "HDR_NAME";
string value = "HDR_VALUE";
services.AddHttpClient("test")
.AddHeader(name, value); // Adds the header "HDR_NAME: HDR_VALUE"