Skip to content

Commit 12a1c9a

Browse files
authored
Revise README for clarity and additional features
Updated README.md to enhance documentation and features description.
1 parent 33e34cb commit 12a1c9a

1 file changed

Lines changed: 213 additions & 89 deletions

File tree

README.md

Lines changed: 213 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,213 @@
11
# httpsuite
22

3+
[![Go Reference](https://pkg.go.dev/badge/github.com/rluders/httpsuite/v3.svg)](https://pkg.go.dev/github.com/rluders/httpsuite/v3)
4+
[![Go Report Card](https://goreportcard.com/badge/github.com/rluders/httpsuite/v3)](https://goreportcard.com/report/github.com/rluders/httpsuite/v3)
5+
[![GitHub release](https://img.shields.io/github/v/release/rluders/httpsuite)](https://github.com/rluders/httpsuite/releases)
6+
[![License](https://img.shields.io/github/license/rluders/httpsuite)](LICENSE)
7+
![stdlib-only](https://img.shields.io/badge/dependencies-stdlib--only-blue)
8+
![RFC 9457](https://img.shields.io/badge/errors-RFC%209457-orange)
9+
310
`httpsuite` is a Go library for request parsing, response writing, and RFC 9457 problem responses.
411

512
`v3` keeps the root module stdlib-only and moves validation to an optional submodule.
613

714
## Features
815

9-
- Parse JSON request bodies with a default `1 MiB` limit
10-
- Return `413 Payload Too Large` when the configured body limit is exceeded
11-
- Bind path params explicitly through a router-specific extractor
12-
- Validate automatically during `ParseRequest` when a global validator is configured
13-
- Keep `ParseRequest` panic-safe for invalid inputs and return regular errors instead
14-
- Return consistent [RFC 9457 Problem Details](https://datatracker.ietf.org/doc/html/rfc9457)
15-
- Write success responses with optional generic metadata
16-
- Support both direct helpers and optional builders
16+
* Parse JSON request bodies with a default `1 MiB` limit
17+
* Return `413 Payload Too Large` when the configured body limit is exceeded
18+
* Bind path params explicitly through a router-specific extractor
19+
* Validate automatically during `ParseRequest` when a global validator is configured
20+
* Keep `ParseRequest` panic-safe for invalid inputs and return regular errors instead
21+
* Return consistent RFC 9457 Problem Details
22+
* Write success responses with optional generic metadata
23+
* Support both direct helpers and optional builders
24+
25+
## Why httpsuite?
26+
27+
Writing HTTP handlers in Go often leads to repetitive and error-prone code:
28+
29+
* Manual JSON decoding
30+
* Manual validation wiring
31+
* Ad-hoc path parameter extraction
32+
* Inconsistent error handling
33+
* Repeated boilerplate across handlers
34+
35+
`httpsuite` provides a consistent and safe flow for parsing requests and writing responses, while staying lightweight and idiomatic.
36+
37+
## What you gain
38+
39+
### Less boilerplate
40+
41+
A single entry point replaces multiple manual steps:
42+
43+
```go
44+
req, err := httpsuite.ParseRequest[*MyRequest](w, r, chi.URLParam, nil, "id")
45+
if err != nil {
46+
return
47+
}
48+
```
49+
50+
* JSON decoding
51+
* Path param binding
52+
* Optional validation
53+
54+
All handled in one place, reducing handler complexity.
55+
56+
### Safe by default
57+
58+
* `ParseRequest` never panics on invalid inputs
59+
* Handles nil request and nil body safely
60+
* Enforces body size limits with proper HTTP responses
61+
62+
This reduces the risk of runtime crashes and undefined behavior.
63+
64+
### Consistent error responses
65+
66+
Errors follow **RFC 9457 Problem Details** out of the box.
67+
68+
Instead of ad-hoc responses like:
69+
70+
```json
71+
{ "error": "invalid request" }
72+
```
73+
74+
You get structured, standardized responses:
75+
76+
```json
77+
{
78+
"type": "...",
79+
"title": "...",
80+
"status": 400,
81+
"detail": "..."
82+
}
83+
```
84+
85+
This improves API consistency and client integration.
86+
87+
### Centralized validation
88+
89+
* Configure once with `SetValidator(...)`
90+
* Automatically applied during `ParseRequest`
91+
* Can be overridden per request
92+
93+
This eliminates duplicated validation logic across handlers.
94+
95+
### Lightweight and modular
96+
97+
* Core module is **stdlib-only**
98+
* No framework lock-in
99+
* Validation is optional
100+
101+
You keep full control over your stack.
102+
103+
### Flexible response API
104+
105+
Choose the level of abstraction you need:
106+
107+
**Simple**
108+
109+
```go
110+
httpsuite.OK(w, data)
111+
```
112+
113+
**Fluent**
114+
115+
```go
116+
httpsuite.Reply().Meta(meta).OK(w, data)
117+
```
118+
119+
**Advanced**
120+
121+
```go
122+
httpsuite.Problem(...).Title(...).Build()
123+
```
124+
125+
### Router-agnostic
126+
127+
Works with:
128+
129+
* Chi
130+
* Gorilla Mux
131+
* net/http
132+
133+
No need to change your router or architecture.
134+
135+
## Comparison
136+
137+
### ❌ Without httpsuite
138+
139+
```go
140+
var req MyRequest
141+
142+
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
143+
http.Error(w, "invalid body", 400)
144+
return
145+
}
146+
147+
idStr := chi.URLParam(r, "id")
148+
id, err := strconv.Atoi(idStr)
149+
if err != nil {
150+
http.Error(w, "invalid id", 400)
151+
return
152+
}
153+
154+
// validation...
155+
// more error handling...
156+
```
157+
158+
**Problems:**
159+
160+
* Repeated logic across handlers
161+
* Inconsistent error formats
162+
* Easy to miss edge cases
163+
164+
### ✅ With httpsuite
165+
166+
```go
167+
req, err := httpsuite.ParseRequest[*MyRequest](w, r, chi.URLParam, nil, "id")
168+
if err != nil {
169+
return
170+
}
171+
172+
httpsuite.OK(w, req)
173+
```
174+
175+
**Benefits:**
176+
177+
* Single, consistent flow
178+
* Less code
179+
* Safer defaults
17180

18181
## Supported routers
19182

20-
- [Chi](https://github.com/go-chi/chi)
21-
- [Gorilla Mux](https://github.com/gorilla/mux)
22-
- Go standard `http.ServeMux`
183+
* Chi
184+
* Gorilla Mux
185+
* Go standard `http.ServeMux`
23186

24187
## Installation
25188

26-
Core:
189+
### Core
27190

28191
```bash
29192
go get github.com/rluders/httpsuite/v3
30193
```
31194

32-
Optional validation adapter:
195+
### Optional validation adapter
33196

34197
```bash
35198
go get github.com/rluders/httpsuite/validation/playground
36199
```
37200

38201
## Mental model
39202

40-
- request in: `ParseRequest(...)`
41-
- success out: `OK(...)`, `Created(...)`, `Reply().Meta(...).OK(...)`
42-
- problem out: `ProblemResponse(...)`, `NewBadRequestProblem(...)`, `Problem(...).Build()`
43-
- validation: configure once with `SetValidator(...)`, override locally with `ParseOptions.Validator`
203+
* request in: `ParseRequest(...)`
204+
* success out: `OK(...)`, `Created(...)`, `Reply().Meta(...).OK(...)`
205+
* problem out: `ProblemResponse(...)`, `NewBadRequestProblem(...)`, `Problem(...).Build()`
206+
* validation: configure once with `SetValidator(...)`, override locally with `ParseOptions.Validator`
44207

45208
For simple handlers, prefer direct helpers.
46209

47-
When a handler needs custom headers, meta, or problem composition, use the optional builders.
210+
When a handler needs custom headers, metadata, or problem composition, use the optional builders.
48211

49212
`ParseRequest` never panics on invalid inputs such as a nil request, nil body, or nil path extractor. These cases return regular Go errors so callers can fail safely.
50213

@@ -114,9 +277,6 @@ validator := playground.NewWithValidator(nil, &httpsuite.ProblemConfig{
114277

115278
httpsuite.SetValidator(validator)
116279

117-
// Validation uses the problem status returned by the configured validator.
118-
// If the validator returns 422, ParseRequest writes 422.
119-
120280
req, err := httpsuite.ParseRequest[*CreateUserRequest](
121281
w,
122282
r,
@@ -128,7 +288,7 @@ req, err := httpsuite.ParseRequest[*CreateUserRequest](
128288
)
129289
```
130290

131-
### Direct helpers
291+
## Direct helpers
132292

133293
```go
134294
httpsuite.OK(w, user)
@@ -137,7 +297,7 @@ httpsuite.Created(w, user, "/users/42")
137297
httpsuite.ProblemResponse(w, httpsuite.NewNotFoundProblem("user not found"))
138298
```
139299

140-
### Fluent helpers
300+
## Fluent helpers
141301

142302
```go
143303
httpsuite.Reply().
@@ -149,7 +309,7 @@ httpsuite.Reply().
149309
Created(w, user, "/users/42")
150310
```
151311

152-
### Builders
312+
## Builders
153313

154314
```go
155315
problem := httpsuite.Problem(http.StatusNotFound).
@@ -166,91 +326,55 @@ httpsuite.RespondProblem(problem).
166326

167327
## Architecture
168328

169-
- root module: `github.com/rluders/httpsuite/v3`
170-
- optional validation adapter: `github.com/rluders/httpsuite/validation/playground`
171-
- root stays stdlib-only
172-
- validation is opt-in at bootstrap, automatic at parse time when configured
173-
- response metadata is generic and can use `PageMeta` or `CursorMeta`
174-
175-
```mermaid
176-
flowchart LR
177-
A[HTTP handler] --> B[ParseRequest]
178-
B --> C[Decode JSON body]
179-
B --> D[Bind path params]
180-
B --> E{validator configured?}
181-
E -- yes --> F[Validate request]
182-
E -- no --> G[typed request]
183-
F --> G
184-
G --> H[OK / Created / Reply]
185-
G --> I[ProblemResponse / Problem builder]
186-
```
187-
188-
```mermaid
189-
flowchart TD
190-
Core[httpsuite/v3 core] --> Request[request helpers]
191-
Core --> Response[response helpers + builders]
192-
Core --> Problem[problem details + config]
193-
Adapter[validation/playground] -->|implements Validator| Core
194-
```
329+
* root module: `github.com/rluders/httpsuite/v3`
330+
* optional validation adapter: `github.com/rluders/httpsuite/validation/playground`
331+
* root stays stdlib-only
332+
* validation is opt-in at bootstrap, automatic at parse time when configured
333+
* response metadata is generic and can use `PageMeta` or `CursorMeta`
195334

196335
## Migration from v2 to v3
197336

198-
- update imports from `github.com/rluders/httpsuite/v2` to `github.com/rluders/httpsuite/v3`
199-
- update `ParseRequest` calls to pass `opts` before `pathParams`
200-
- configure validation globally with `httpsuite.SetValidator(...)` or `playground.RegisterDefault()`
201-
- `ParseRequest` now validates automatically when a validator is configured
202-
- validator-provided `ProblemDetails.Status` is respected when valid
203-
- use `ParseOptions.SkipValidation` to opt out per call
204-
- use `ParseOptions.Validator` to override the global validator per call
205-
- use `ProblemConfig` when you want custom problem type URLs
337+
* update imports from `v2` to `v3`
338+
* update `ParseRequest` calls to pass `opts` before `pathParams`
339+
* configure validation globally with `httpsuite.SetValidator(...)`
340+
* `ParseRequest` now validates automatically when a validator is configured
341+
* validator-provided status is respected
342+
* use `ParseOptions.SkipValidation` to opt out per call
343+
* use `ParseOptions.Validator` to override per call
344+
* use `ProblemConfig` for custom problem type URLs
206345

207346
## Examples
208347

209-
Examples live in [`examples/`](examples/).
348+
Examples live in `examples/`.
210349

211-
- [`examples/stdmux`](examples/stdmux/main.go): core-only with `http.ServeMux`
212-
- [`examples/gorillamux`](examples/gorillamux/main.go): path params with Gorilla Mux
213-
- [`examples/chi`](examples/chi/main.go): global validation with Chi
214-
- [`examples/restapi`](examples/restapi/main.go): fuller REST API example with pagination-style metadata and custom problems
215-
216-
`examples/restapi` shows:
217-
218-
- global validator setup with `playground`
219-
- `ProblemConfig` with custom type URLs
220-
- create, get, and list endpoints
221-
- `PageMeta` and `CursorMeta`
222-
- direct helpers and fluent helpers together
223-
- custom `ProblemDetails` for domain-level `404`s
350+
* `examples/stdmux`: core-only with `http.ServeMux`
351+
* `examples/gorillamux`: path params with Gorilla Mux
352+
* `examples/chi`: global validation with Chi
353+
* `examples/restapi`: full REST example with pagination and custom problems
224354

225355
## Notes for contributors
226356

227-
- request facade and helpers live in `request*.go`
228-
- response facade, helpers, builders, and write internals live in `response*.go`
229-
- problem details, config, builders, and helpers live in `problem*.go`
357+
* request helpers: `request*.go`
358+
* response helpers/builders: `response*.go`
359+
* problem handling: `problem*.go`
230360

231361
## Release workflow
232362

233-
The release workflow supports two paths:
234-
235-
- push an existing `v*` tag to verify and publish that release
236-
- run `Release` with `workflow_dispatch` and choose `major`, `minor`, or `patch`
363+
Supports:
237364

238-
On manual dispatch, the workflow finds the latest `v*` tag, bumps it according to the selected semantic version part, pushes the new tag, and publishes the GitHub release for that tag.
365+
* pushing an existing `v*` tag
366+
* manual release with semantic version bump
239367

240368
## Tutorial
241369

242-
- [Improving Request Validation and Response Handling in Go Microservices](https://medium.com/@rluders/improving-request-validation-and-response-handling-in-go-microservices-cc54208123f2)
243-
244-
> Do you have a project example or a tutorial? Add it here.
370+
* [Improving Request Validation and Response Handling in Go Microservices](https://medium.com/@rluders/improving-request-validation-and-response-handling-in-go-microservices-cc54208123f2), about the first released version.
245371

246372
## Contributing
247373

248-
Contributions are welcome:
249-
250-
- open an issue
251-
- submit a PR
252-
- add a router example
374+
* open an issue
375+
* submit a PR
376+
* add a router example
253377

254378
## License
255379

256-
MIT. See [LICENSE](LICENSE).
380+
MIT

0 commit comments

Comments
 (0)