stillbox/pkg/rest/api.go
2024-11-15 10:51:25 -05:00

132 lines
2.5 KiB
Go

package rest
import (
"errors"
"net/http"
"dynatron.me/x/stillbox/pkg/talkgroups"
"github.com/go-chi/chi/v5"
"github.com/go-chi/render"
"github.com/go-viper/mapstructure/v2"
"github.com/jackc/pgx/v5"
"github.com/rs/zerolog/log"
)
type API interface {
Subrouter() http.Handler
}
type api struct {
}
func New() API {
s := new(api)
return s
}
func (a *api) Subrouter() http.Handler {
r := chi.NewMux()
r.Mount("/talkgroup", new(talkgroupAPI).Subrouter())
return r
}
type errResponse struct {
Err error `json:"-"`
Code int `json:"-"`
Error string `json:"error"`
}
func (e *errResponse) Render(w http.ResponseWriter, r *http.Request) error {
switch e.Code {
case http.StatusNotFound:
default:
log.Error().Str("path", r.URL.Path).Err(e.Err).Int("code", e.Code).Str("msg", e.Error).Msg("request failed")
}
render.Status(r, e.Code)
return nil
}
func badRequest(err error) render.Renderer {
return &errResponse{
Err: err,
Code: http.StatusBadRequest,
Error: "Bad request",
}
}
func recordNotFound(err error) render.Renderer {
return &errResponse{
Err: err,
Code: http.StatusNotFound,
Error: "Record not found",
}
}
func internalError(err error) render.Renderer {
return &errResponse{
Err: err,
Code: http.StatusNotFound,
Error: "Internal server error",
}
}
type errResponder func(error) render.Renderer
var statusMapping = map[error]errResponder{
talkgroups.ErrNoSuchSystem: recordNotFound,
talkgroups.ErrNotFound: recordNotFound,
pgx.ErrNoRows: recordNotFound,
}
func autoError(err error) render.Renderer {
c, ok := statusMapping[err]
if ok {
c(err)
}
for e, c := range statusMapping { // check if err wraps an error we know about
if errors.Is(err, e) {
return c(err)
}
}
return internalError(err)
}
func wErr(w http.ResponseWriter, r *http.Request, v render.Renderer) {
err := render.Render(w, r, v)
if err != nil {
log.Error().Err(err).Msg("wErr render error")
}
}
func decodeParams(d interface{}, r *http.Request) error {
params := chi.RouteContext(r.Context()).URLParams
m := make(map[string]string, len(params.Keys))
for i, k := range params.Keys {
m[k] = params.Values[i]
}
dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
Metadata: nil,
Result: d,
TagName: "param",
WeaklyTypedInput: true,
})
if err != nil {
return err
}
return dec.Decode(m)
}
func respond(w http.ResponseWriter, r *http.Request, v interface{}) {
render.DefaultResponder(w, r, v)
}