Error Handling
Live on this serverJSON errors for the API, HTML error pages for the website, custom mapping.
using GenHTTP.Api.Content;
using GenHTTP.Api.Protocol;
using GenHTTP.Modules.ErrorHandling;
using GenHTTP.Modules.Functional;
using GenHTTP.Modules.IO;
using GenHTTP.Modules.Layouting;
public static class ErrorHandlingSample
{
public static IHandlerBuilder Create()
{
/*
*
* A shop with a website for humans and an API for machines. Errors in the API
* are reported as JSON (or XML, ... as requested by the client), while the website
* renders HTML error pages. Exceptions thrown by the business logic are mapped
* to HTTP status codes without the business logic knowing about HTTP.
*
* See https://genhttp.org/documentation/content/concerns/error-handling/
*
*/
var orders = Inline.Create()
// GET /samples/errors/api/orders/42 (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 /samples/errors/api/orders/ (409 via the custom mapper)
.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());
return Layout.Create()
.Add("api/orders", orders)
.Add(website); // e.g. GET /samples/errors/does-not-exist (404 as HTML page)
}
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/samples/errors/api/orders/1'Fetch an order that does not exist
404 as JSON
curl -i 'https://genhttp.org/samples/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/samples/errors/api/orders/'Open a page that does not exist
404 as HTML page