Skip to content

Commit 21592af

Browse files
committed
docs: add readme and go.mod
1 parent b3a6f37 commit 21592af

2 files changed

Lines changed: 148 additions & 0 deletions

File tree

README.md

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
# problem
2+
3+
[![Go Reference](https://pkg.go.dev/badge/github.com/semmidev/problem.svg)](https://pkg.go.dev/github.com/semmidev/problem)
4+
5+
A comprehensive, idiomatic, and robust Go library for implementing **RFC 7807 (Problem Details for HTTP APIs)**.
6+
7+
This library provides a standard way to return machine-readable errors from your HTTP APIs, ensuring consistency across your microservices and APIs.
8+
9+
## Features
10+
11+
- **Standard RFC 7807 Compliance**: Supports all standard fields (`type`, `title`, `status`, `detail`, `instance`).
12+
- **Custom Extensions**: Easily add custom arbitrary fields (extension members) that automatically flatten into the JSON root.
13+
- **Go 1.13+ Error Wrapping**: Implements `Error()` and `Unwrap()` making it fully compatible with `errors.Is` and `errors.As`.
14+
- **Pre-defined HTTP Templates**: Built-in templates for all common HTTP 4xx and 5xx errors.
15+
- **Fluent Options API**: Clean, chainable API for building problem details.
16+
- **Zero Dependencies**: Relies solely on the Go standard library.
17+
18+
## Installation
19+
20+
```bash
21+
go get github.com/semmidev/problem
22+
```
23+
24+
## Basic Usage
25+
26+
The easiest way to use the library is with the pre-defined templates in your HTTP handlers.
27+
28+
```go
29+
package main
30+
31+
import (
32+
"net/http"
33+
"github.com/semmidev/problem"
34+
)
35+
36+
func myHandler(w http.ResponseWriter, r *http.Request) {
37+
// Create a problem detail
38+
p := problem.New(
39+
problem.NotFound,
40+
problem.WithDetail("The requested user with id 12345 was not found."),
41+
problem.WithInstance(r.URL.Path),
42+
)
43+
44+
// Write directly to the HTTP response
45+
p.Write(w)
46+
}
47+
```
48+
49+
This automatically sets the `Content-Type: application/problem+json` header and writes:
50+
51+
```json
52+
{
53+
"type": "about:blank",
54+
"title": "Not Found",
55+
"status": 404,
56+
"detail": "The requested user with id 12345 was not found.",
57+
"instance": "/users/12345"
58+
}
59+
```
60+
61+
## Adding Custom Extensions
62+
63+
RFC 7807 allows you to add custom fields to provide domain-specific context.
64+
65+
```go
66+
p := problem.New(
67+
problem.UnprocessableEntity,
68+
problem.WithDetail("You do not have enough credit."),
69+
problem.WithExtension("balance", 30),
70+
problem.WithExtensions(map[string]any{
71+
"currency": "USD",
72+
"account": "/account/12345",
73+
}),
74+
)
75+
```
76+
77+
Generates:
78+
79+
```json
80+
{
81+
"type": "about:blank",
82+
"title": "Unprocessable Entity",
83+
"status": 422,
84+
"detail": "You do not have enough credit.",
85+
"balance": 30,
86+
"currency": "USD",
87+
"account": "/account/12345"
88+
}
89+
```
90+
91+
## Creating Custom Problem Types
92+
93+
You are encouraged to define your own problem types for domain-specific errors.
94+
95+
```go
96+
var OutOfCredit = problem.TypeTemplate{
97+
Type: "https://example.com/probs/out-of-credit",
98+
Title: "You do not have enough credit.",
99+
Status: http.StatusForbidden,
100+
}
101+
102+
// Later in a handler:
103+
p := problem.New(OutOfCredit, problem.WithDetail("Current balance is 30, but that costs 50."))
104+
```
105+
106+
## Error Wrapping & Unwrapping
107+
108+
Because `*problem.Problem` implements the `error` interface, it can be seamlessly passed through your service layer and wrapped.
109+
110+
```go
111+
// In your repository/service layer
112+
func queryDB() error {
113+
err := db.Query(...) // let's imagine this fails
114+
115+
// Wrap the internal error inside a Problem
116+
return problem.Wrap(err, problem.InternalServerError,
117+
problem.WithDetail("Database timeout"),
118+
problem.WithExtension("trace_id", "req-789"),
119+
)
120+
}
121+
122+
// In your HTTP handler
123+
func handler(w http.ResponseWriter, r *http.Request) {
124+
err := queryDB()
125+
if err != nil {
126+
// Use errors.As or the provided IsProblem helper to extract it
127+
if p, ok := problem.IsProblem(err); ok {
128+
p.Write(w)
129+
return
130+
}
131+
132+
// Fallback for unknown errors
133+
problem.New(problem.InternalServerError).Write(w)
134+
}
135+
}
136+
```
137+
138+
## Example Application
139+
140+
Check out the [example/kanban](./example/kanban) directory for a complete, runnable example of using the `problem` library.
141+
It features:
142+
- **Clean Architecture**: Clear separation of domain rules, repositories, and HTTP handlers.
143+
- **Clear Error Boundaries**: Mapping standard domain errors to RFC 7807 problem details.
144+
- **Go Chi & Govalidator**: Demonstrates using the library with widely used community packages, mapping validation errors to `422 Unprocessable Entity` problem extensions.
145+

go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module github.com/semmidev/problem
2+
3+
go 1.25.0

0 commit comments

Comments
 (0)