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) }