https://github.com/followtheprocess/problem
RFC-7807 Problem JSON structure in Go
https://github.com/followtheprocess/problem
api go http json rfc-7807
Last synced: about 1 month ago
JSON representation
RFC-7807 Problem JSON structure in Go
- Host: GitHub
- URL: https://github.com/followtheprocess/problem
- Owner: FollowTheProcess
- License: mit
- Created: 2025-08-23T06:42:58.000Z (10 months ago)
- Default Branch: main
- Last Pushed: 2025-08-24T09:23:20.000Z (10 months ago)
- Last Synced: 2025-08-24T09:39:45.124Z (10 months ago)
- Topics: api, go, http, json, rfc-7807
- Language: Go
- Homepage: https://datatracker.ietf.org/doc/html/rfc7807
- Size: 111 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# problem
[](https://github.com/FollowTheProcess/problem)
[](https://pkg.go.dev/go.followtheprocess.codes/problem)
[](https://goreportcard.com/report/github.com/FollowTheProcess/problem)
[](https://github.com/FollowTheProcess/problem)
[](https://github.com/FollowTheProcess/problem/actions?query=workflow%3ACI)
[](https://codecov.io/gh/FollowTheProcess/problem)
[RFC-7807] Problem JSON structure in Go
## Project Description
`problem` is a single source of truth for an [RFC-7807] `application/problem+json` structure, use it whenever you need to return an error from a REST API and your
APIs will all be consistent 👌🏻
## Installation
> [!WARNING]
> This module requires the Go 1.25 jsonv2 experiment: `GOEXPERIMENT=jsonv2` in order to inline the `Extra` map when serializing
> See
```shell
go get go.followtheprocess.codes/problem@latest
```
## Quickstart
Whenever you need a problem:
```go
prob := problem.Problem{
Type: "https://example.com/probs/out-of-credit",
Title: "Not enough credit",
Detail: "Your current balance is 30, but that costs 50",
Instance: "/account/12345/msgs/abc",
Status: http.StatusBadRequest,
}
```
Or you can use the `New` function with a bunch of options if you like:
```go
prob := problem.New(
problem.Type("https://example.com/probs/out-of-credit"),
problem.Title("Not enough credit"),
problem.Detail("Your current balance is 30, but that costs 50"),
problem.Instance("/account/12345/msgs/abc"),
problem.Status(http.StatusBadRequest),
)
```
And these will both serialize to the following JSON:
```json
{
"type": "https://example.com/probs/out-of-credit",
"title": "Not enough credit",
"detail": "Your current balance is 30, but that costs 50",
"instance": "/account/12345/msgs/abc",
"status": 400
}
```
### Any Extras?
[RFC-7807] allows for arbitrary additional fields in this response to convey any additional context your error might have. Using `problem` you do this with the `Extra` map:
```go
prob := problem.Problem{
Type: "https://example.com/probs/out-of-credit",
Title: "Not enough credit",
Detail: "Your current balance is 30, but that costs 50",
Instance: "/account/12345/msgs/abc",
Status: http.StatusBadRequest,
Extra: map[string]any{
"balance": 30,
"accounts": []string{"/accounts/12345", "/accounts/67890"},
},
}
```
Or with the `New` pattern:
```go
prob := problem.New(
problem.Type("https://example.com/probs/out-of-credit"),
problem.Title("Not enough credit"),
problem.Detail("Your current balance is 30, but that costs 50"),
problem.Instance("/account/12345/msgs/abc"),
problem.Status(http.StatusBadRequest),
problem.Extra("balance", 30),
problem.Extra("accounts", []string{"/accounts/12345", "/accounts/67890"}),
)
```
These will both serialize to the following JSON:
```json
{
"type": "https://example.com/probs/out-of-credit",
"title": "Not enough credit",
"detail": "Your current balance is 30, but that costs 50",
"instance": "/account/12345/msgs/abc",
"status": 400,
"balance": 30,
"accounts": [
"/accounts/12345",
"/accounts/67890"
]
}
```
> [!TIP]
> There is also an `ExtraMap` to allow adding a whole map of extra variables in one go rather than one at a time as shown above
### In HTTP Handlers
The package also provides some helpers for use in HTTP services to quickly respond with a problem:
```go
package main
import (
"net/http"
"go.followtheprocess.codes/problem"
)
func Bang(w http.ResponseWriter, r *http.Request) {
problem.Respond(
w,
problem.Title("Uh oh"),
problem.Detail("A thing went wrong"),
problem.Status(http.StatusBadRequest),
)
}
func main() {
http.HandleFunc("/", Bang)
http.ListenAndServe(":8080", nil)
}
````
`Respond` will:
- Set the `Status` code on the response
- Write the `Content-Type` header as `application/problem+json`
- Marshal the `Problem` as JSON to the `http.ResponseWriter`
- If that fails, a default problem is written instead
### Credits
This package was created with [copier] and the [FollowTheProcess/go-template] project template.
[copier]: https://copier.readthedocs.io/en/stable/
[FollowTheProcess/go-template]: https://github.com/FollowTheProcess/go-template
[RFC-7807]: https://datatracker.ietf.org/doc/html/rfc7807