Documentation
¶
Overview ¶
Package responder centralises JSON rendering, request parsing, and structured error payloads. See ExampleResponder_full for an end-to-end handler using custom classifiers and problem details.
Index ¶
- type ErrorClassifierFunc
- type ProblemDetails
- type Responder
- func (r *Responder) HandleAPIError(w http.ResponseWriter, req *http.Request, status int, err error, ...)
- func (r *Responder) HandleBadRequestError(w http.ResponseWriter, req *http.Request, err error, logMsg ...string)
- func (r *Responder) HandleErrors(w http.ResponseWriter, req *http.Request, err error, msgs ...string)
- func (r *Responder) HandleInternalServerError(w http.ResponseWriter, req *http.Request, err error, logMsg ...string)
- func (r *Responder) HandleUnauthorizedError(w http.ResponseWriter, req *http.Request, err error, logMsg ...string)
- func (r *Responder) Logger() *slog.Logger
- func (r *Responder) ReadRequestBody(w http.ResponseWriter, req *http.Request, v any) bool
- func (r *Responder) RespondWithJSON(w http.ResponseWriter, req *http.Request, status int, v any)
- type ResponderOption
- type StatusMetadata
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type ErrorClassifierFunc ¶
ErrorClassifierFunc inspects an error and returns the HTTP status that should be used for the response. The boolean indicates whether the error was classified and prevents the generic internal server handler from running.
type ProblemDetails ¶
type ProblemDetails struct {
Type string `json:"type,omitempty"`
Title string `json:"title"`
Status int `json:"status"`
Detail string `json:"detail,omitempty"`
Instance string `json:"instance,omitempty"`
TraceID string `json:"traceId,omitempty"`
Timestamp string `json:"timestamp,omitempty"`
}
ProblemDetails aligns HTTP error responses with RFC 9457 problem documents.
type Responder ¶
type Responder struct {
// contains filtered or unexported fields
}
Responder centralises error handling, JSON rendering, and logging for HTTP handlers. It provides structured error payloads with correlation identifiers and consistent log records.
Example (Full) ¶
package main
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"net/http/httptest"
"strings"
"github.com/drblury/apiweaver/responder"
)
func main() {
errDuplicate := errors.New("project already exists")
r := responder.NewResponder(
responder.WithErrorClassifier(func(err error) (int, bool) {
if errors.Is(err, errDuplicate) {
return http.StatusConflict, true
}
return 0, false
}),
)
store := make(map[string]struct{})
handler := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
var body struct {
Name string `json:"name"`
}
if !r.ReadRequestBody(w, req, &body) {
return
}
if body.Name == "" {
r.HandleBadRequestError(w, req, errors.New("name is required"))
return
}
if _, exists := store[body.Name]; exists {
r.HandleErrors(w, req, errDuplicate)
return
}
store[body.Name] = struct{}{}
r.RespondWithJSON(w, req, http.StatusCreated, map[string]string{"name": body.Name})
})
createReq := httptest.NewRequest(http.MethodPost, "/projects", strings.NewReader(`{"name":"weaver"}`))
createRec := httptest.NewRecorder()
handler.ServeHTTP(createRec, createReq)
fmt.Println(createRec.Code)
fmt.Println(strings.TrimSpace(createRec.Body.String()))
dupReq := httptest.NewRequest(http.MethodPost, "/projects", strings.NewReader(`{"name":"weaver"}`))
dupRec := httptest.NewRecorder()
handler.ServeHTTP(dupRec, dupReq)
var problem responder.ProblemDetails
_ = json.Unmarshal(dupRec.Body.Bytes(), &problem)
fmt.Println(problem.Status)
fmt.Println(problem.Title)
}
Output: 201 {"name":"weaver"} 409 Conflict
func NewResponder ¶
func NewResponder(opts ...ResponderOption) *Responder
NewResponder constructs a Responder with default status metadata and the global slog logger. Use ResponderOption functions to override specific behaviours.
func (*Responder) HandleAPIError ¶
func (r *Responder) HandleAPIError(w http.ResponseWriter, req *http.Request, status int, err error, logMsg ...string)
HandleAPIError renders a structured JSON response for the supplied HTTP status and logs the payload using the configured logger.
func (*Responder) HandleBadRequestError ¶
func (r *Responder) HandleBadRequestError(w http.ResponseWriter, req *http.Request, err error, logMsg ...string)
HandleBadRequestError reports client validation errors using HTTP 400.
func (*Responder) HandleErrors ¶
func (r *Responder) HandleErrors(w http.ResponseWriter, req *http.Request, err error, msgs ...string)
HandleErrors inspects the supplied error using the configured classifier and emits an appropriate JSON response.
func (*Responder) HandleInternalServerError ¶
func (r *Responder) HandleInternalServerError(w http.ResponseWriter, req *http.Request, err error, logMsg ...string)
HandleInternalServerError is a shortcut that reports a 500 status code.
func (*Responder) HandleUnauthorizedError ¶
func (r *Responder) HandleUnauthorizedError(w http.ResponseWriter, req *http.Request, err error, logMsg ...string)
HandleUnauthorizedError reports authentication failures using HTTP 401.
func (*Responder) ReadRequestBody ¶
ReadRequestBody parses the request body into the provided value and handles malformed content by returning a JSON error response.
func (*Responder) RespondWithJSON ¶
RespondWithJSON serialises the provided value and writes it to the response using the supplied status code.
type ResponderOption ¶
type ResponderOption func(*Responder)
ResponderOption follows the functional options pattern used by NewResponder to configure optional collaborators.
func WithErrorClassifier ¶
func WithErrorClassifier(classifier ErrorClassifierFunc) ResponderOption
WithErrorClassifier installs a classifier used by HandleErrors to derive the HTTP status code from returned errors.
func WithLogger ¶
func WithLogger(logger *slog.Logger) ResponderOption
WithLogger injects a custom slog logger for error reporting and payload logging.
func WithStatusMetadata ¶
func WithStatusMetadata(status int, meta StatusMetadata) ResponderOption
WithStatusMetadata overrides the error metadata used for a specific HTTP status code.
Example ¶
package main
import (
"errors"
"fmt"
"net/http"
"net/http/httptest"
"strings"
"github.com/drblury/apiweaver/responder"
)
func main() {
r := responder.NewResponder(
responder.WithStatusMetadata(http.StatusUnauthorized, responder.StatusMetadata{
Title: "Missing token",
LogMsg: "authentication failed",
TypeURI: "https://status.example.com/unauthorized",
}),
)
rec := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/secret", nil)
r.HandleUnauthorizedError(rec, req, errors.New("token expired"))
fmt.Println(rec.Code)
fmt.Println(strings.Contains(rec.Body.String(), "\"title\":\"Missing token\""))
}
Output: 401 true