Error Handling
Live on this serverJSON errors for the API, HTML error pages for the website, custom mapping.
A shop with a website for people and an API for programs, each reporting errors in its own way: the API answers with JSON (or the format the client asks for), the website with HTML error pages, such as the 404 for an address that does not exist.
The business logic throws an OutOfStockException without knowing about HTTP, and OutOfStockMapper turns
it into a 409 response. A ProviderException brings its status with it, as for an order that does not
exist.
To run it, create a console app (dotnet new console), add GenHTTP (dotnet add package GenHTTP.Full), replace Program.cs with the code below and start it with dotnet run - it answers at http://localhost:8080/.
using GenHTTP.Engine.Internal;
using GenHTTP.Api.Content;
using GenHTTP.Api.Protocol;
using GenHTTP.Modules.ErrorHandling;
using GenHTTP.Modules.Functional;
using GenHTTP.Modules.IO;
using GenHTTP.Modules.Layouting;
using GenHTTP.Modules.Practices;
var orders = Inline.Create()
// GET /api/orders/42 answers 404 as JSON
.Get(":id", (int id) => (id == 1) ? new Order(1, "Coffee") : throw new ProviderException(ResponseStatus.NotFound, $"There is no order with id {id}"))
// POST /api/orders/ answers 409 via the mapper below
.Post((Order order) => Shop.PlaceOrder(order))
.Add(ErrorHandler.From(new OutOfStockMapper()));
var website = Layout.Create()
.Index(Content.From(Resource.FromString("<h1>Welcome to our shop!</h1>").Type(ContentType.TextHtml)))
.Add(ErrorHandler.Html()); // GET /does-not-exist answers 404 as HTML page
var app = Layout.Create()
.Add("api/orders", orders)
.Add(website);
await Host.Create()
.Handler(app)
.Defaults()
.RunAsync();
public record Order(int Id, string Product);
public class OutOfStockException(string product) : Exception($"Sorry, '{product}' is sold out")
{
public string Product => product;
}
public static class Shop
{
public static Order PlaceOrder(Order order) => throw new OutOfStockException(order.Product);
}
public class OutOfStockMapper : IErrorMapper<OutOfStockException>
{
public ValueTask<IResponse?> Map(IRequest request, IHandler handler, OutOfStockException error, ByteString? acceptedFormat)
{
var response = request.Respond()
.Status(ResponseStatus.Conflict)
.Content(error.Message, ContentType.TextPlain)
.Build();
return new(response);
}
// null passes the request on, so the default error handling renders the 404
public ValueTask<IResponse?> GetNotFound(IRequest request, IHandler handler, ByteString? acceptedFormat) => new();
}
Try it
Send the requests to the sample running on this server - change the path, headers or body as you like, or copy them as curl commands.
Fetch an order
curl -i 'https://genhttp.org/demo/errors/api/orders/1'Fetch an order that does not exist
404 as JSON
curl -i 'https://genhttp.org/demo/errors/api/orders/42'Order something that is sold out
409, mapped from a custom exception
curl -i -X POST -H 'Content-Type: application/json' -d '{ "product": "Tea" }' 'https://genhttp.org/demo/errors/api/orders/'Open a page that does not exist
404 as HTML page